+ role="link"
+ tabIndex={0}
+ onClick={handleOpen}
+ onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleOpen(); } }}
+ className="group relative panel-token border border-[color:var(--color-border)] rounded-xl p-6 hover:border-[color:var(--color-border)] hover:bg-[color:var(--color-neutral-100)] hover:shadow-sm hover:-translate-y-0.5 transition-all duration-200 cursor-pointer focus:outline-none focus:ring-2 focus:ring-[color:var(--color-fg)]">
@@ -76,7 +108,7 @@ export const RetailerInventory = () => {
{/* Street address: show on md+, hide on smaller screens to reduce clutter */}
{retailer.location?.address && (
-
+
{retailer.location.address}
@@ -86,8 +118,8 @@ export const RetailerInventory = () => {
{/* Keep phone visible on mobile for quick action */}
{retailer.location?.phone && (
-
+ className="flex items-center gap-2.5 transition">
+
{retailer.location.phone}
)}
@@ -95,8 +127,8 @@ export const RetailerInventory = () => {
{/* Email: hide on mobile, show from md+ */}
{retailer.location?.contactEmail && (
-
+ className="hidden md:flex items-center gap-2.5 transition">
+
Email
)}
@@ -104,8 +136,8 @@ export const RetailerInventory = () => {
{/* Website: hide on mobile, show from md+ */}
{retailer.location?.website && (
-
+ className="hidden md:flex items-center gap-2.5 transition">
+
Website
)}
@@ -124,7 +156,7 @@ export const RetailerInventory = () => {
aria-label="Sync Inventory"
>
-
{syncing ? "Syncing inventory…" : "Sync Inventory from Square"}
+
{syncing ? "Syncing inventory…" : "Sync inventory from Square"}
{syncing ? "Please wait, this may take up to a minute." : ""}
@@ -142,7 +174,7 @@ export const RetailerInventory = () => {
>
- {syncing ? "Syncing inventory…" : "Sync Inventory"}
+ {syncing ? "Syncing inventory…" : "Sync inventory"}
@@ -154,35 +186,122 @@ export const RetailerInventory = () => {
- {/* Inventory Table */}
+ {/* Inventory: List + Detail pattern */}
-
-
{inventory.length} Wines Available
+ {/* Screen header: title + actions */}
+
+
+
Inventory
+
{inventory.length} wines available
+
+
+
+ {/* Controls row: search above table */}
+
+ setQuery(e.target.value)}
+ placeholder="Search inventory…"
+ className="w-full h-10 px-3 border-2 border-[color:var(--color-border)] bg-[color:var(--color-panel)] placeholder-[color:var(--color-fg-muted)]"
+ aria-label="Search inventory"
+ />
{banner && (
+ {banner.type === "success" ? "Success:" : "Error:"}
{banner.message}
)}
- {inventory.length === 0 ? (
+ {filteredInventory.length === 0 ? (
- No wines in inventory yet.
+ {inventory.length === 0 ? (
+ <>
+
No wines in inventory yet.
+ {canSync && (
+
+
+
+ )}
+ >
+ ) : (
+ <>
+
No inventory matches your search.
+
+ >
+ )}
) : (
-
+ setSelected(w)}
+ // capture the focused row to restore focus when closing drawer
+ onRowFocus={(el) => { lastFocusedRowRef.current = el; }}
+ />
)}
+
+ {/* Detail drawer */}
+ {selected && (
+
+ {/* scrim */}
+
+ )}
+
+ {/* Restore focus to triggering row when drawer closes */}
+ {!selected && lastFocusedRowRef.current && (
+
+ )}
);
+};
+
+// Utility component to restore focus after drawer close
+const FocusRestorer = ({target}: {target: HTMLElement}) => {
+ useEffect(() => {
+ target?.focus?.();
+ }, [target]);
+ return null;
};
\ No newline at end of file
diff --git a/src/users/retailer/RetailerInventoryTable.tsx b/src/users/retailer/RetailerInventoryTable.tsx
index a94c895..1fc6970 100644
--- a/src/users/retailer/RetailerInventoryTable.tsx
+++ b/src/users/retailer/RetailerInventoryTable.tsx
@@ -10,10 +10,12 @@ type Wine = {
type Props = {
wines: Wine[];
onRowClick?: (wine: Wine) => void;
+ // Called when a row/card receives focus so caller can restore focus after closing drawer
+ onRowFocus?: (el: HTMLElement, wine: Wine) => void;
};
// A dead-simple, reusable inventory table with responsive stacked rows on mobile
-const RetailerInventoryTable: React.FC
= ({ wines, onRowClick }) => {
+const RetailerInventoryTable: React.FC = ({ wines, onRowClick, onRowFocus }) => {
// Desktop table (≥768px)
const TableDesktop = (
@@ -34,8 +36,17 @@ const RetailerInventoryTable: React.FC = ({ wines, onRowClick }) => {
return (
onRowClick?.(w)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ onRowClick?.(w);
+ }
+ }}
+ onFocus={(e) => onRowFocus?.(e.currentTarget as HTMLElement, w)}
>
| {w.vintage ?? "—"} |
{w.producer} |
@@ -59,8 +70,17 @@ const RetailerInventoryTable: React.FC = ({ wines, onRowClick }) => {
return (
onRowClick?.(w)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ onRowClick?.(w);
+ }
+ }}
+ onFocus={(e) => onRowFocus?.(e.currentTarget as HTMLElement, w)}
>
{w.vintage ? `[${w.vintage}] ` : ""}{w.producer} – {w.name}
diff --git a/src/users/retailer/SquareAuth.tsx b/src/users/retailer/SquareAuth.tsx
index 3e3882b..38355f7 100644
--- a/src/users/retailer/SquareAuth.tsx
+++ b/src/users/retailer/SquareAuth.tsx
@@ -21,29 +21,29 @@ const SquareAuth = () => {
icon: Store,
title: "Square POS",
}}
- className="border-2 hover:border-[color:var(--color-primary)] hover:bg-[color:var(--color-muted)] hover:shadow-md hover:-translate-y-px transition-all duration-300 focus-visible:border-[color:var(--color-primary)] focus-visible:ring-2 focus-visible:ring-[color:var(--color-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--color-bg)]"
+ className="border-2 border-neutral-200 hover:bg-neutral-50 transition-colors focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2 focus-visible:ring-offset-white"
>
{/* Large QrCode on the left — clearly clickable */}
-
+
{/* All text to the right */}
-
- Connect your Square account to sync inventory and share!
+
+ Connect your Square account to sync inventory into Wine Graph.
-
+
Secure OAuth • Takes ~20 seconds
{/* Subtle minimal cue on the far right */}
-
+
Connect
- →
+ →