Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<!-- Monochrome favicon -->
<link rel="icon" type="image/png" href="/src/public/wine_graph_logo_1024x1024.png" />
<link rel="icon" type="image/png" href="/wine_graph_logo_128x128.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
Expand Down
Binary file added public/google/png@1x/dark/web_dark_rd_SI@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/dark/web_dark_rd_SU@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/dark/web_dark_rd_ctn@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/dark/web_dark_rd_na@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/dark/web_dark_sq_SI@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/dark/web_dark_sq_SU@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/dark/web_dark_sq_ctn@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/dark/web_dark_sq_na@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/light/web_light_rd_SI@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/light/web_light_rd_SU@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/light/web_light_rd_na@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/light/web_light_sq_SI@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/light/web_light_sq_SU@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@1x/light/web_light_sq_na@1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@2x/dark/web_dark_rd_SI@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/google/png@2x/dark/web_dark_rd_SU@2x.png
Binary file added public/google/png@2x/dark/web_dark_rd_ctn@2x.png
Binary file added public/google/png@2x/dark/web_dark_rd_na@2x.png
Binary file added public/google/png@2x/dark/web_dark_sq_SI@2x.png
Binary file added public/google/png@2x/dark/web_dark_sq_SU@2x.png
Binary file added public/google/png@2x/dark/web_dark_sq_ctn@2x.png
Binary file added public/google/png@2x/dark/web_dark_sq_na@2x.png
Binary file added public/google/png@2x/light/web_light_rd_SI@2x.png
Binary file added public/google/png@2x/light/web_light_rd_SU@2x.png
Binary file added public/google/png@2x/light/web_light_sq_SI@2x.png
Binary file added public/google/png@2x/light/web_light_sq_SU@2x.png
Binary file added public/google/png@2x/light/web_light_sq_na@2x.png
Binary file added public/google/png@3x/dark/web_dark_rd_SI@3x.png
Binary file added public/google/png@3x/dark/web_dark_rd_SU@3x.png
Binary file added public/google/png@3x/dark/web_dark_rd_ctn@3x.png
Binary file added public/google/png@3x/dark/web_dark_rd_na@3x.png
Binary file added public/google/png@3x/dark/web_dark_sq_SI@3x.png
Binary file added public/google/png@3x/dark/web_dark_sq_SU@3x.png
Binary file added public/google/png@3x/dark/web_dark_sq_ctn@3x.png
Binary file added public/google/png@3x/dark/web_dark_sq_na@3x.png
Binary file added public/google/png@3x/light/web_light_rd_SI@3x.png
Binary file added public/google/png@3x/light/web_light_rd_SU@3x.png
Binary file added public/google/png@3x/light/web_light_rd_na@3x.png
Binary file added public/google/png@3x/light/web_light_sq_SI@3x.png
Binary file added public/google/png@3x/light/web_light_sq_SU@3x.png
Binary file added public/google/png@4x/dark/web_dark_rd_SI@4x.png
Binary file added public/google/png@4x/dark/web_dark_rd_SU@4x.png
Binary file added public/google/png@4x/dark/web_dark_rd_ctn@4x.png
Binary file added public/google/png@4x/dark/web_dark_rd_na@4x.png
Binary file added public/google/png@4x/dark/web_dark_sq_SI@4x.png
Binary file added public/google/png@4x/dark/web_dark_sq_SU@4x.png
Binary file added public/google/png@4x/dark/web_dark_sq_ctn@4x.png
Binary file added public/google/png@4x/dark/web_dark_sq_na@4x.png
Binary file added public/google/png@4x/light/web_light_rd_SI@4x.png
Binary file added public/google/png@4x/light/web_light_rd_SU@4x.png
Binary file added public/google/png@4x/light/web_light_rd_na@4x.png
Binary file added public/google/png@4x/light/web_light_sq_SI@4x.png
Binary file added public/google/png@4x/light/web_light_sq_SU@4x.png
Binary file added public/google/png@4x/light/web_light_sq_na@4x.png
Binary file added public/pos/clover.png
Binary file added public/pos/shopify.png
Binary file added public/pos/square.png
Binary file added public/wine_graph_logo_128x128.png
Binary file added public/wine_graph_logo_256x256.png
Binary file added public/wine_graph_logo_64x64.png
8 changes: 1 addition & 7 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { Outlet } from "react-router-dom";
import Layout from "./Layout.tsx";

const App = () => {
return (
//@ts-ignore
<Layout>
<Outlet />
</Layout>
);
return <Layout />;
};

export default App;
228 changes: 103 additions & 125 deletions src/app/Layout.tsx

Large diffs are not rendered by default.

128 changes: 98 additions & 30 deletions src/app/index.css
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
/* Single modern font */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap");
/* Primary sans system */
@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@400..800&display=swap");
@import "tailwindcss";

/* Monochrome Design Tokens */
/* Design Tokens */
:root {
/* semantic (monochrome only) */
--color-primary: #111111; /* use black for primary emphasis in monochrome */
--color-accent: #6b7280; /* neutral gray used only for focus/selection */
/* semantic */
--color-primary: #121212;
--color-accent: #7a2e2e; /* wine-inspired accent */
--color-accent-soft: #f7eceb;
--color-accent-border: #d7b2ae;

/* surface */
--color-bg: #ffffff; /* pure white background */
--color-panel: #ffffff; /* panels match background unless elevated by border */
--color-muted: #f5f5f5; /* subtle gray surface */
--color-border: #d1d5db; /* neutral-300 border */
--color-bg: #fcfbfa;
--color-panel: #ffffff;
--color-muted: #f5f2f1;
--color-border: #ddd6d4;

/* text */
--color-fg: #111111; /* high-contrast text */
--color-fg-muted: #6b7280; /* neutral-muted text */
--color-fg: #171717;
--color-fg-muted: #655b58;

/* states (kept neutral; do not use for accents) */
--color-success: #111111;
--color-danger: #111111;
/* states */
--color-success: #2e6a49;
--color-danger: #8b1d2c;

/* typography */
--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto,
Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, "Apple Color Emoji",
"Segoe UI Emoji";
--font-sans: "Manrope", "Avenir Next", "Segoe UI", sans-serif;
--font-display: var(--font-sans);

/* radii */
--radius-xs: 2px;
Expand All @@ -41,11 +42,15 @@
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;

--shadow-soft: 0 1px 2px rgb(18 18 18 / 0.05), 0 8px 20px rgb(18 18 18 / 0.06);
--shadow-pop: 0 1px 2px rgb(18 18 18 / 0.06), 0 14px 30px rgb(18 18 18 / 0.1);
}

/* Expose tokens to Tailwind via @theme (TW v4) */
@theme {
--font-sans: var(--font-sans);
--font-display: var(--font-display);

--color-background: var(--color-bg);
--color-foreground: var(--color-fg);
Expand All @@ -54,6 +59,8 @@
--color-border: var(--color-border);
--color-primary: var(--color-primary);
--color-accent: var(--color-accent);
--color-accent-soft: var(--color-accent-soft);
--color-accent-border: var(--color-accent-border);
--color-success: var(--color-success);
--color-danger: var(--color-danger);

Expand Down Expand Up @@ -86,23 +93,34 @@
.flex-center { @apply flex items-center justify-center; }
.container-max { @apply max-w-7xl mx-auto px-4 sm:px-6; }
.divider { height: 2px; background: var(--color-border); width: 100%; }
.tap-target { @apply min-h-11 min-w-11; }

/* Typography scale (use these exclusively) */
/* Hero/primary heading */
.text-heading { @apply text-[44px] sm:text-[56px] font-black leading-[1.05]; }
/* Slightly smaller heading for PageHeader specifically (keeps Hero large) */
.text-heading-page { @apply text-[24px] sm:text-[48px] font-black leading-[1.05]; }
.text-title { @apply text-[28px] sm:text-[32px] font-semibold leading-tight; }
.text-heading {
@apply text-[44px] sm:text-[58px] font-semibold leading-[1.03];
font-family: var(--font-sans);
letter-spacing: -0.02em;
}
.text-heading-page {
@apply text-[30px] sm:text-[48px] font-semibold leading-[1.04];
font-family: var(--font-sans);
letter-spacing: -0.02em;
}
.text-title { @apply text-[28px] sm:text-[34px] font-semibold leading-tight; }
.text-large { @apply text-[19px] leading-snug; }
.text-body { @apply text-[15px] leading-snug; }
.text-label { @apply text-xs uppercase tracking-widest font-semibold; }
.text-label { @apply text-[11px] uppercase tracking-[0.18em] font-semibold; }
.text-muted { color: var(--color-fg-muted); }
.text-fg-muted { color: var(--color-fg-muted); }
.text-textPrimary { color: var(--color-fg); }
.text-textSecondary { color: var(--color-fg-muted); }
.font-merriweather { font-family: var(--font-sans); }

/* Compact line-height utility */
.text-compact { line-height: 1.35; }

.btn {
@apply inline-flex items-center gap-2 uppercase tracking-wide text-[12px] font-semibold transition-colors duration-150 border focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
@apply inline-flex items-center gap-2 uppercase tracking-wide text-[12px] font-semibold transition-all duration-150 border focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
border-color: var(--color-border);
border-width: 2px;
border-radius: var(--radius-sm);
Expand All @@ -114,8 +132,13 @@
background: var(--color-primary);
color: #ffffff;
border-color: var(--color-primary);
box-shadow: var(--shadow-soft);
}
.btn-primary:hover {
filter: saturate(115%) brightness(1.02);
transform: translateY(-1px);
box-shadow: var(--shadow-pop);
}
.btn-primary:hover { filter: saturate(115%) brightness(1.02); }
.btn-secondary {
background: transparent;
color: var(--color-fg);
Expand All @@ -124,13 +147,61 @@
.btn-secondary:hover { background: var(--color-muted); }
.btn-ghost:hover { background: var(--color-muted); }
.kbd { @apply px-2 py-[2px] border rounded-sm text-[11px] uppercase tracking-wider; border-color: var(--color-border); color: var(--color-fg-muted); }
.btn-minimal {
@apply inline-flex items-center justify-center rounded-[var(--radius-sm)] p-2 transition-colors;
color: var(--color-fg-muted);
}
.btn-minimal:hover {
background: var(--color-muted);
color: var(--color-fg);
}
.input-field {
@apply w-full h-10 px-3 rounded-[var(--radius-sm)] border bg-[color:var(--color-panel)] focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
border-color: var(--color-border);
color: var(--color-fg);
}
.input-field::placeholder {
color: var(--color-fg-muted);
}
.input-field:focus-visible {
box-shadow: 0 0 0 2px color-mix(in oklab, var(--color-accent) 25%, transparent);
}
.notice-error {
border: 1px solid color-mix(in oklab, var(--color-danger) 35%, transparent);
background: color-mix(in oklab, var(--color-danger) 10%, white);
color: var(--color-danger);
border-radius: var(--radius-sm);
padding: 0.5rem 0.75rem;
}
.notice-success {
border: 1px solid var(--color-accent-border);
background: var(--color-accent-soft);
color: var(--color-accent);
border-radius: var(--radius-sm);
padding: 0.5rem 0.75rem;
}

/* Tokens as utilities to avoid inline styles */
.bg-token { background: var(--color-bg); }
.bg-panel-token { background: var(--color-panel); }
.panel-token { background: var(--color-panel); }
.muted-token { background: var(--color-muted); }
.border-token { border-color: var(--color-border); }
.text-token { color: var(--color-fg); }
.ring-accent { box-shadow: inset 0 0 0 1px var(--color-accent-border); }
.surface-elevated {
background: var(--color-panel);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-soft);
}
.surface-pop {
background: var(--color-panel);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-pop);
}
.accent-strip {
background: linear-gradient(90deg, var(--color-accent) 0%, color-mix(in oklab, var(--color-accent) 70%, white) 70%);
}

/* Focus ring uses neutral gray in monochrome */
.focus-accent { @apply focus-visible:ring-[color:var(--color-accent)] focus-visible:ring-offset-[color:var(--color-bg)]; }
Expand All @@ -142,21 +213,18 @@
}

@layer components {
/* SINGLE PRIMARY BUTTON — used for ALL important actions */
.btn-primary {
@apply inline-flex items-center justify-center gap-3 px-7 py-3.5
border-2 border-[color:var(--color-fg)]
bg-[color:var(--color-fg)] text-[color:var(--color-bg)]
font-semibold uppercase tracking-wider text-xs
transition-colors duration-150 active:scale-98 min-h-11 min-w-11;
transition-all duration-150 min-h-11 min-w-11;
}

/* Icon sizing — every primary button MUST have a Lucide icon on the left */
.btn-primary > svg:first-child {
@apply w-4.5 h-4.5 -ml-1; /* slight left nudge for optical balance */
}

/* Optional right arrow */
.btn-primary > svg:last-child {
@apply w-4 h-4 ml-2;
}
Expand Down
37 changes: 24 additions & 13 deletions src/app/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ import {createBrowserRouter, createRoutesFromElements, Outlet, Route, RouterProv
import {AuthProvider} from "../auth";
import RoleRoute from "./RoleRoute.tsx";
import Spinner from "../components/Spinner.tsx";
import {RetailerMarketplace} from "../users/retailer/RetailerMarketplace.tsx";
import {likelyPathsForRole, prefetchPaths, routeLoaders} from "./routePrefetch.ts";

const DiscoverPage = lazy(() => import("../pages/Discover.tsx").then(m => ({ default: m.DiscoverPage })));
const ProfilePage = lazy(() => import("../pages/Profile.tsx").then(m => ({ default: m.ProfilePage })));
const MarketplacePage = lazy(() => import("../pages/Marketplace.tsx").then(m => ({ default: m.MarketplacePage })));
const ProducerMarketplace = lazy(() => import("../users/producer/ProducerMarketplace.tsx").then(m => ({ default: m.ProducerMarketplace })));
const RetailerInventory = lazy(() => import("../users/retailer/inventory/RetailerInventoryPage.tsx").then(m => ({ default: m.RetailerInventoryPage })));
const RetailerProfile = lazy(() => import("../users/retailer/RetailerProfile.tsx").then(m => ({ default: m.RetailerProfile })));
const ProducerProfile = lazy(() => import("../users/producer/ProducerProfile.tsx").then(m => ({ default: m.ProducerProfile })));
const GraphFeed = lazy(() => import("../pages/GraphFeed.tsx"));
const ProducerInventory = lazy(() => import("../users/producer/ProducerInventory.tsx"));
const ProducerPage = lazy(() => import("../pages/ProducerPage.tsx"));
const WinePage = lazy(() => import("../pages/WinePage.tsx"));
const DiscoverPage = lazy(routeLoaders.discover);
const ProfilePage = lazy(routeLoaders.profile);
const MarketplacePage = lazy(routeLoaders.marketplace);
const ProducerMarketplace = lazy(routeLoaders.producerMarketplace);
const RetailerInventory = lazy(routeLoaders.retailerInventory);
const RetailerProfile = lazy(routeLoaders.retailerProfile);
const ProducerProfile = lazy(routeLoaders.producerProfile);
const GraphFeed = lazy(routeLoaders.graphFeed);
const ProducerInventory = lazy(routeLoaders.producerInventory);
const ProducerPage = lazy(routeLoaders.producerPage);
const WinePage = lazy(routeLoaders.winePage);
const RetailerMarketplace = lazy(routeLoaders.retailerMarketplace);

const RetailerCellar = lazy(() => import("../users/retailer/RetailerCellar.tsx").then(m => ({ default: m.RetailerCellar })));
const RetailerCellar = lazy(routeLoaders.retailerCellar);

const router = createBrowserRouter(
createRoutesFromElements(
Expand Down Expand Up @@ -105,3 +106,13 @@ createRoot(document.getElementById("root")!).render(
</Suspense>
</StrictMode>
);

if (typeof window !== "undefined") {
const warm = () => prefetchPaths(likelyPathsForRole({role: "visitor"}).slice(0, 3));
const ric = window.requestIdleCallback;
if (ric) {
ric(warm, {timeout: 1500});
} else {
window.setTimeout(warm, 350);
}
}
6 changes: 3 additions & 3 deletions src/app/roleNavConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type NavLinkDef = {
const baseLinks: NavLinkDef[] = [
{title: "Home", icon: BarChart3, route: "/"},
{title: "Discover", icon: Globe, route: "/explore"},
{title: "Marketplace", icon: Store, route: "/marketplace"},
{title: "Locate", icon: Store, route: "/marketplace"},
{title: "Profile", icon: User, route: "/profile"},
//...genericLinks,
];
Expand All @@ -33,7 +33,7 @@ const baseLinks: NavLinkDef[] = [
function retailerLinks(retailerId: string): NavLinkDef[] {
// Use dynamic retailerId paths to match router: /retailer/:retailerId/...
const cellar: NavLinkDef = {title: "Cellar", icon: Package, route: `/retailer/${retailerId}/cellar`};
const marketplace: NavLinkDef = {title: "Marketplace", icon: Store, route: "/retailer/marketplace"};
const marketplace: NavLinkDef = {title: "Producers", icon: Store, route: "/retailer/marketplace"};
//const profile: NavLinkDef = {title: "Profile", icon: User, route: `/retailer/${retailerId}/profile`};
return [baseLinks[0], marketplace, cellar, baseLinks[3]];
}
Expand All @@ -48,7 +48,7 @@ function enthusiastLinks(): NavLinkDef[] {

function producerLinks(producerId: string): NavLinkDef[] {
const cellar: NavLinkDef = {title: "Cellar", icon: Package, route: `/producer/${producerId}/cellar`};
const marketplace: NavLinkDef = {title: "Marketplace", icon: Store, route: "/retailer/marketplace"};
const marketplace: NavLinkDef = {title: "Retailers", icon: Store, route: "/retailer/marketplace"};
return [baseLinks[0], marketplace, cellar, baseLinks[3]];
}

Expand Down
77 changes: 77 additions & 0 deletions src/app/routePrefetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const prefetched = new Set<string>();

export const routeLoaders = {
graphFeed: () => import("../pages/GraphFeed.tsx"),
discover: () => import("../pages/Discover.tsx").then((m) => ({default: m.DiscoverPage})),
profile: () => import("../pages/Profile.tsx").then((m) => ({default: m.ProfilePage})),
marketplace: () => import("../pages/Marketplace.tsx").then((m) => ({default: m.MarketplacePage})),
producerMarketplace: () => import("../users/producer/ProducerMarketplace.tsx").then((m) => ({default: m.ProducerMarketplace})),
retailerMarketplace: () => import("../users/retailer/RetailerMarketplace.tsx").then((m) => ({default: m.RetailerMarketplace})),
retailerInventory: () => import("../users/retailer/inventory/RetailerInventoryPage.tsx").then((m) => ({default: m.RetailerInventoryPage})),
retailerProfile: () => import("../users/retailer/RetailerProfile.tsx").then((m) => ({default: m.RetailerProfile})),
retailerCellar: () => import("../users/retailer/RetailerCellar.tsx").then((m) => ({default: m.RetailerCellar})),
producerProfile: () => import("../users/producer/ProducerProfile.tsx").then((m) => ({default: m.ProducerProfile})),
producerInventory: () => import("../users/producer/ProducerInventory.tsx"),
producerPage: () => import("../pages/ProducerPage.tsx"),
winePage: () => import("../pages/WinePage.tsx"),
} as const;

type LoaderKey = keyof typeof routeLoaders;
type UserRole = "retailer" | "producer" | "enthusiast" | "visitor" | string;

function getLoaderKeysForPath(path: string): LoaderKey[] {
const clean = path.split("?")[0].split("#")[0];

if (clean === "/") return ["graphFeed"];
if (clean === "/explore") return ["discover"];
if (clean === "/marketplace") return ["marketplace"];
if (clean === "/profile") return ["profile"];
if (clean === "/retailer/marketplace") return ["producerMarketplace"];
if (clean === "/producer/marketplace") return ["retailerMarketplace"];
if (/^\/retailer\/[^/]+\/inventory$/.test(clean)) return ["retailerInventory"];
if (/^\/retailer\/[^/]+\/profile$/.test(clean)) return ["retailerProfile"];
if (/^\/retailer\/[^/]+\/cellar$/.test(clean)) return ["retailerCellar"];
if (/^\/producer\/[^/]+\/profile$/.test(clean)) return ["producerProfile"];
if (/^\/producer\/[^/]+\/cellar$/.test(clean)) return ["producerInventory"];
if (/^\/producer\/[^/]+\/[^/]+$/.test(clean)) return ["producerPage"];
if (/^\/wine\/[^/]+\/[^/]+$/.test(clean)) return ["winePage"];

return [];
}

export function prefetchPath(path: string) {
const keys = getLoaderKeysForPath(path);
keys.forEach((key) => {
if (prefetched.has(key)) return;
prefetched.add(key);
void routeLoaders[key]();
});
}

export function prefetchPaths(paths: string[]) {
paths.forEach(prefetchPath);
}

type LikelyPathOptions = {
role?: UserRole;
retailerId?: string;
producerId?: string;
};

export function likelyPathsForRole({role, retailerId, producerId}: LikelyPathOptions): string[] {
const normalized = (role ?? "visitor").toLowerCase();

if (normalized === "retailer") {
const paths = ["/", "/retailer/marketplace", "/marketplace", "/profile"];
if (retailerId) paths.push(`/retailer/${retailerId}/cellar`, `/retailer/${retailerId}/profile`);
return paths;
}

if (normalized === "producer") {
const paths = ["/", "/producer/marketplace", "/explore", "/profile"];
if (producerId) paths.push(`/producer/${producerId}/cellar`, `/producer/${producerId}/profile`);
return paths;
}

return ["/", "/explore", "/marketplace", "/profile"];
}
Loading
Loading