diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md index 2c1fb06d..5556a20a 100644 --- a/apps/web/AGENTS.md +++ b/apps/web/AGENTS.md @@ -34,7 +34,7 @@ Full layout spec: see apps/web/WEB_DESIGN.md Section 3. WHAT THE WEB APP SERVES: ├── Public pages: product discovery, store pages, landing page (no auth needed) -├── Shopper pages: cart, orders, profile, checkout (/buyer/*) +├── Shopper pages: /home, /cart, /orders, /wishlist, /saved, /messages └── Store dashboard: product management, orders, payouts (/store/*) WHAT THE WEB APP IS NOT: @@ -65,8 +65,8 @@ apps/web/src/app/ │ │ └── page.tsx ← /explore — product discovery feed │ ├── p/[code]/ │ │ └── page.tsx ← /p/TWZ-ABC123 — product detail -│ ├── @[handle]/ -│ │ └── page.tsx ← /@chiastyles — store page (public) +│ ├── stores/[handle]/ +│ │ └── page.tsx ← /stores/chiastyles — store page (public) │ ├── c/[category]/ │ │ └── page.tsx ← /c/fashion — category browse │ ├── about/page.tsx @@ -81,18 +81,18 @@ apps/web/src/app/ │ ├── forgot-password/page.tsx │ └── verify-email/page.tsx │ -├── (shopper)/ ← Authenticated shopper pages +├── (app)/ ← Authenticated shopper pages │ ├── layout.tsx ← Auth gate + 3-COLUMN SOCIAL LAYOUT -│ ├── buyer/ -│ │ ├── feed/page.tsx ← /buyer/feed — personalised feed -│ │ ├── cart/page.tsx ← /buyer/cart -│ │ ├── checkout/[productId]/page.tsx ← focus mode (no nav chrome) -│ │ ├── orders/ -│ │ │ ├── page.tsx ← order list with filter tabs -│ │ │ └── [id]/page.tsx ← order detail + tracking timeline -│ │ ├── saved/page.tsx -│ │ ├── wishlist/page.tsx -│ │ └── profile/page.tsx +│ ├── home/page.tsx ← /home — personalised feed +│ ├── cart/page.tsx ← /cart +│ ├── checkout/[productId]/page.tsx ← focus mode (no nav chrome) +│ ├── orders/ +│ │ ├── page.tsx ← /orders list with filter tabs +│ │ ├── [id]/page.tsx ← order detail + tracking timeline +│ │ └── confirmed/[orderId]/page.tsx +│ ├── saved/page.tsx +│ ├── wishlist/page.tsx +│ └── messages/page.tsx │ └── (store)/ ← Authenticated store owner pages ├── layout.tsx ← Auth gate + STORE MODE LAYOUT (dark, no right panel) @@ -106,7 +106,6 @@ apps/web/src/app/ │ │ ├── page.tsx │ │ └── [id]/page.tsx │ ├── messages/page.tsx - │ ├── my-store/page.tsx ← store profile preview │ ├── payouts/page.tsx │ ├── settings/page.tsx │ └── verification/page.tsx ← KYB flows @@ -344,7 +343,7 @@ VARIANT SELECTION GATE: Do not proceed to checkout ``` -### Public — Store Page (`/@[handle]`) +### Public — Store Page (`/stores/[handle]`) ``` DATA: GET /stores/:handle (server component) @@ -485,7 +484,7 @@ SCREEN 4 — SIZE GUIDE AND PUBLISH: [Preview] → opens product page preview in new tab ``` -### Shopper — Order Detail (`/buyer/orders/[id]`) +### Shopper — Order Detail (`/orders/[id]`) ``` DATA: GET /orders/:id (protected — own orders only) @@ -585,19 +584,19 @@ export function middleware(request: NextRequest) { const session = request.cookies.get('access_token') // Routes always public (no check): - const publicRoutes = ['/', '/explore', '/p/', '/@', '/c/', '/about', '/terms', '/privacy', '/help'] + const publicRoutes = ['/', '/explore', '/stores', '/u/', '/p/', '/c/', '/about', '/terms', '/privacy', '/help'] const authRoutes = ['/login', '/register', '/forgot-password', '/verify-email'] // Public route — allow through: if (publicRoutes.some(r => pathname.startsWith(r))) return NextResponse.next() - // Auth page + already logged in — redirect to feed: + // Auth page + already logged in — redirect to home: if (authRoutes.some(r => pathname.startsWith(r)) && session) { - return NextResponse.redirect(new URL('/buyer/feed', request.url)) + return NextResponse.redirect(new URL('/home', request.url)) } // Protected route + no session — redirect to login: - if (!session && (pathname.startsWith('/buyer/') || pathname.startsWith('/store/'))) { + if (!session && (pathname.startsWith('/home') || pathname.startsWith('/cart') || pathname.startsWith('/orders') || pathname.startsWith('/store/'))) { return NextResponse.redirect(new URL(`/login?redirect=${pathname}`, request.url)) } @@ -666,7 +665,7 @@ WHEN IN STORE MODE AND USER TAPS [Browse Feed]: - Like / Comment / Share: rendered and functional - Interactions fire as STORE identity (not personal user) API call includes: { interactAs: 'store', storeId: currentStoreId } - - Header shows: "Browsing as @chiastyles" subtle indicator + - Header shows active store handle subtle indicator WHAT THIS MEANS IN CODE: const { mode } = useMode() // 'shopping' | 'store' diff --git a/apps/web/WEB_DESIGN.md b/apps/web/WEB_DESIGN.md index 732e918b..8d5dd6ba 100644 --- a/apps/web/WEB_DESIGN.md +++ b/apps/web/WEB_DESIGN.md @@ -588,7 +588,7 @@ INFINITE SCROLL: ROOT LAYOUT (applies to all pages using the 3-column layout): // apps/web/src/app/(public)/layout.tsx -// apps/web/src/app/(shopper)/buyer/layout.tsx +// apps/web/src/app/(app)/layout.tsx
{/* Left Nav — hidden on mobile */} diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index a64ca917..e1ba0c59 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -14,23 +14,6 @@ const nextConfig = { ], }, transpilePackages: ["@twizrr/shared"], - async rewrites() { - return [ - { - source: '/@:slug', - destination: '/_at_/:slug', - }, - ]; - }, - async redirects() { - return [ - { source: '/buyer/catalogue', destination: '/buyer/feed', permanent: true }, - { source: '/buyer/products/:id', destination: '/p/:id', permanent: true }, - { source: '/buyer/merchants/:id', destination: '/@:id', permanent: true }, - { source: '/buyer/merchants', destination: '/merchants', permanent: true }, - { source: '/m/:slug', destination: '/@:slug', permanent: true }, - ]; - }, }; export default nextConfig; diff --git a/apps/web/src/app/(shopper)/buyer/cart/page.tsx b/apps/web/src/app/(app)/cart/page.tsx similarity index 65% rename from apps/web/src/app/(shopper)/buyer/cart/page.tsx rename to apps/web/src/app/(app)/cart/page.tsx index 81223a69..7803302e 100644 --- a/apps/web/src/app/(shopper)/buyer/cart/page.tsx +++ b/apps/web/src/app/(app)/cart/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { CartClient } from "./CartClient"; +import { CartClient } from "@/app/(shopper)/buyer/cart/CartClient"; export const metadata: Metadata = { - title: "Your cart — twizrr", + title: "Your cart | twizrr", robots: { index: false, follow: false }, }; diff --git a/apps/web/src/app/(shopper)/buyer/checkout/[productId]/page.tsx b/apps/web/src/app/(app)/checkout/[productId]/page.tsx similarity index 85% rename from apps/web/src/app/(shopper)/buyer/checkout/[productId]/page.tsx rename to apps/web/src/app/(app)/checkout/[productId]/page.tsx index a22e5c06..5ded237c 100644 --- a/apps/web/src/app/(shopper)/buyer/checkout/[productId]/page.tsx +++ b/apps/web/src/app/(app)/checkout/[productId]/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { CheckoutClient } from "./CheckoutClient"; +import { CheckoutClient } from "@/app/(shopper)/buyer/checkout/[productId]/CheckoutClient"; export const metadata: Metadata = { - title: "Checkout — twizrr", + title: "Checkout | twizrr", robots: { index: false, follow: false }, }; diff --git a/apps/web/src/app/(app)/home/page.tsx b/apps/web/src/app/(app)/home/page.tsx new file mode 100644 index 00000000..ab005ca6 --- /dev/null +++ b/apps/web/src/app/(app)/home/page.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from "next"; +import { FeedClient } from "@/components/feed/FeedClient"; + +export const metadata: Metadata = { + title: "Home | twizrr", + description: + "Your personalised feed of products and posts from stores you follow on twizrr.", +}; + +export default function HomePage() { + return ( + <> +
+

+ Home +

+
+ + + + ); +} diff --git a/apps/web/src/app/(app)/layout.tsx b/apps/web/src/app/(app)/layout.tsx new file mode 100644 index 00000000..2868e390 --- /dev/null +++ b/apps/web/src/app/(app)/layout.tsx @@ -0,0 +1,6 @@ +import type { ReactNode } from "react"; +import { ShopperShell } from "@/app/(shopper)/buyer/_components/ShopperShell"; + +export default function AppRouteLayout({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/apps/web/src/app/(app)/messages/page.tsx b/apps/web/src/app/(app)/messages/page.tsx new file mode 100644 index 00000000..25d19476 --- /dev/null +++ b/apps/web/src/app/(app)/messages/page.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from "next"; +import { EmptyState } from "@/components/ui/State"; + +export const metadata: Metadata = { + title: "Chats | twizrr", + robots: { index: false, follow: false }, +}; + +export default function MessagesPage() { + return ( +
+ +
+ ); +} diff --git a/apps/web/src/app/(app)/notifications/page.tsx b/apps/web/src/app/(app)/notifications/page.tsx new file mode 100644 index 00000000..54464947 --- /dev/null +++ b/apps/web/src/app/(app)/notifications/page.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from "next"; +import { EmptyState } from "@/components/ui/State"; + +export const metadata: Metadata = { + title: "Notifications | twizrr", + robots: { index: false, follow: false }, +}; + +export default function NotificationsPage() { + return ( +
+ +
+ ); +} diff --git a/apps/web/src/app/(shopper)/buyer/orders/[id]/page.tsx b/apps/web/src/app/(app)/orders/[id]/page.tsx similarity index 74% rename from apps/web/src/app/(shopper)/buyer/orders/[id]/page.tsx rename to apps/web/src/app/(app)/orders/[id]/page.tsx index 13a1c331..1c193d25 100644 --- a/apps/web/src/app/(shopper)/buyer/orders/[id]/page.tsx +++ b/apps/web/src/app/(app)/orders/[id]/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { OrderDetailClient } from "./OrderDetailClient"; +import { OrderDetailClient } from "@/app/(shopper)/buyer/orders/[id]/OrderDetailClient"; export const metadata: Metadata = { - title: "Order details — twizrr", + title: "Order details | twizrr", robots: { index: false, follow: false }, }; diff --git a/apps/web/src/app/(shopper)/buyer/orders/confirmed/[orderId]/page.tsx b/apps/web/src/app/(app)/orders/confirmed/[orderId]/page.tsx similarity index 80% rename from apps/web/src/app/(shopper)/buyer/orders/confirmed/[orderId]/page.tsx rename to apps/web/src/app/(app)/orders/confirmed/[orderId]/page.tsx index 224ccdd5..78e67593 100644 --- a/apps/web/src/app/(shopper)/buyer/orders/confirmed/[orderId]/page.tsx +++ b/apps/web/src/app/(app)/orders/confirmed/[orderId]/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { OrderConfirmedClient } from "./OrderConfirmedClient"; +import { OrderConfirmedClient } from "@/app/(shopper)/buyer/orders/confirmed/[orderId]/OrderConfirmedClient"; export const metadata: Metadata = { - title: "Order Confirmed — twizrr", + title: "Order Confirmed - twizrr", robots: { index: false, follow: false }, }; diff --git a/apps/web/src/app/(shopper)/buyer/orders/page.tsx b/apps/web/src/app/(app)/orders/page.tsx similarity index 62% rename from apps/web/src/app/(shopper)/buyer/orders/page.tsx rename to apps/web/src/app/(app)/orders/page.tsx index e4fcd608..3c7209a5 100644 --- a/apps/web/src/app/(shopper)/buyer/orders/page.tsx +++ b/apps/web/src/app/(app)/orders/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { OrdersListClient } from "./OrdersListClient"; +import { OrdersListClient } from "@/app/(shopper)/buyer/orders/OrdersListClient"; export const metadata: Metadata = { - title: "Your orders — twizrr", + title: "Your orders - twizrr", robots: { index: false, follow: false }, }; diff --git a/apps/web/src/app/(shopper)/buyer/saved/page.tsx b/apps/web/src/app/(app)/saved/page.tsx similarity index 64% rename from apps/web/src/app/(shopper)/buyer/saved/page.tsx rename to apps/web/src/app/(app)/saved/page.tsx index 3cc159d6..cd4bad14 100644 --- a/apps/web/src/app/(shopper)/buyer/saved/page.tsx +++ b/apps/web/src/app/(app)/saved/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { SavedClient } from "./SavedClient"; +import { SavedClient } from "@/app/(shopper)/buyer/saved/SavedClient"; export const metadata: Metadata = { - title: "Saved posts — twizrr", + title: "Saved posts - twizrr", robots: { index: false, follow: false }, }; diff --git a/apps/web/src/app/(shopper)/buyer/wishlist/page.tsx b/apps/web/src/app/(app)/wishlist/page.tsx similarity index 62% rename from apps/web/src/app/(shopper)/buyer/wishlist/page.tsx rename to apps/web/src/app/(app)/wishlist/page.tsx index a5cd8f51..6d4d0d89 100644 --- a/apps/web/src/app/(shopper)/buyer/wishlist/page.tsx +++ b/apps/web/src/app/(app)/wishlist/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { WishlistClient } from "./WishlistClient"; +import { WishlistClient } from "@/app/(shopper)/buyer/wishlist/WishlistClient"; export const metadata: Metadata = { - title: "Your wishlist — twizrr", + title: "Your wishlist - twizrr", robots: { index: false, follow: false }, }; diff --git a/apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx b/apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx index f34ad6b8..b9bca206 100644 --- a/apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx +++ b/apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx @@ -22,6 +22,7 @@ import { useToast } from "@/components/ui/Toast"; import { ProductDetailsSheet } from "@/components/product/ProductDetailsSheet"; import { SizeGuide } from "@/components/product/SizeGuide"; import { DeliveryPreview } from "@/components/product/DeliveryPreview"; +import { getPublicStoreHref } from "@/lib/routes"; import type { PublicProduct, ProductVariant, ProductImage } from "./page"; // ─── Helpers ────────────────────────────────────────────────────────────────── @@ -322,7 +323,7 @@ export function ProductDetailClient({ product }: ProductDetailClientProps) { if (product.productCode) params.set("code", product.productCode); if (selectedVariant?.id) params.set("variantId", selectedVariant.id); const qs = params.toString(); - router.push(`/buyer/checkout/${product.productId}${qs ? `?${qs}` : ""}`); + router.push(`/checkout/${product.productId}${qs ? `?${qs}` : ""}`); } // ── Render ───────────────────────────────────────────────────────────────── @@ -388,7 +389,7 @@ export function ProductDetailClient({ product }: ProductDetailClientProps) { {/* Store card */}
{product.store.logoUrl ? ( -
+
{product.store.name
) : ( -
+
{product.store.handle && ( Visit diff --git a/apps/web/src/app/(public)/u/[username]/page.tsx b/apps/web/src/app/(public)/u/[username]/page.tsx new file mode 100644 index 00000000..ae1ffc6d --- /dev/null +++ b/apps/web/src/app/(public)/u/[username]/page.tsx @@ -0,0 +1,169 @@ +import Image from "next/image"; +import { notFound } from "next/navigation"; +import { CalendarDays, UserRound } from "lucide-react"; + +interface PublicUserProfile { + id: string; + username: string | null; + displayName: string | null; + bio: string | null; + profilePhotoUrl: string | null; + memberSince: string; + followerCount: number; + followingCount: number; + postCount: number; +} + +interface PublicUserPageProps { + params: { + username: string; + }; +} + +const API_BASE = (process.env.NEXT_PUBLIC_API_URL ?? "").replace(/\/$/, ""); + +export default async function PublicUserPage({ params }: PublicUserPageProps) { + const profile = await fetchPublicUser(params.username); + + if (profile.status === "not-found") { + notFound(); + } + + if (profile.status === "error") { + return ( +
+
+
+

+ We could not load this profile +

+

+ Please try again in a moment. +

+
+ ); + } + + const user = profile.data; + const displayName = user.displayName || user.username || "twizrr user"; + const handle = user.username ?? null; + + return ( +
+
+
+
+
+ {user.profilePhotoUrl ? ( + {displayName} + ) : ( +
+
+ )} +
+
+

+ {displayName} +

+ {handle ? ( +

+ {handle} +

+ ) : null} +
+
+
+ + {user.bio ? ( +

+ {user.bio} +

+ ) : null} + +
+ + + +
+ + {user.memberSince ? ( +

+

+ ) : null} +
+
+ ); +} + +function ProfileStat({ label, value }: { label: string; value: number }) { + return ( +
+
+ {label} +
+
+ {value.toLocaleString("en-NG")} +
+
+ ); +} + +function formatMonthYear(value: string): string { + const date = new Date(value); + if (Number.isNaN(date.getTime())) return "recently"; + return new Intl.DateTimeFormat("en-NG", { + month: "long", + year: "numeric", + }).format(date); +} + +async function fetchPublicUser( + username: string, +): Promise< + | { status: "ok"; data: PublicUserProfile } + | { status: "not-found" } + | { status: "error" } +> { + if (!API_BASE) { + return { status: "error" }; + } + + try { + const res = await fetch( + `${API_BASE}/users/${encodeURIComponent(username)}`, + { + cache: "no-store", + }, + ); + + if (res.status === 404) { + return { status: "not-found" }; + } + + if (!res.ok) { + return { status: "error" }; + } + + const json = (await res.json()) as { + data?: PublicUserProfile; + }; + + if (!json.data || typeof json.data.id !== "string") { + return { status: "error" }; + } + + return { status: "ok", data: json.data }; + } catch { + return { status: "error" }; + } +} diff --git a/apps/web/src/app/(settings)/settings/account/page.tsx b/apps/web/src/app/(settings)/settings/account/page.tsx new file mode 100644 index 00000000..e12f219c --- /dev/null +++ b/apps/web/src/app/(settings)/settings/account/page.tsx @@ -0,0 +1,16 @@ +import type { Metadata } from "next"; +import { ShopperShell } from "@/app/(shopper)/buyer/_components/ShopperShell"; +import { AccountSettingsContent } from "@/components/settings/AccountSettingsContent"; + +export const metadata: Metadata = { + title: "Account Settings - twizrr", + robots: { index: false, follow: false }, +}; + +export default function AccountSettingsPage() { + return ( + + + + ); +} diff --git a/apps/web/src/app/(settings)/settings/page.tsx b/apps/web/src/app/(settings)/settings/page.tsx new file mode 100644 index 00000000..973befbb --- /dev/null +++ b/apps/web/src/app/(settings)/settings/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function SettingsIndexPage() { + redirect("/settings/account"); +} diff --git a/apps/web/src/app/(settings)/settings/profile/page.tsx b/apps/web/src/app/(settings)/settings/profile/page.tsx new file mode 100644 index 00000000..6ea3583f --- /dev/null +++ b/apps/web/src/app/(settings)/settings/profile/page.tsx @@ -0,0 +1,16 @@ +import type { Metadata } from "next"; +import { ShopperShell } from "@/app/(shopper)/buyer/_components/ShopperShell"; +import { SettingsClient } from "@/app/(shopper)/buyer/settings/SettingsClient"; + +export const metadata: Metadata = { + title: "Profile Settings - twizrr", + robots: { index: false, follow: false }, +}; + +export default function ProfileSettingsPage() { + return ( + + + + ); +} diff --git a/apps/web/src/app/(settings)/settings/store/account/page.tsx b/apps/web/src/app/(settings)/settings/store/account/page.tsx new file mode 100644 index 00000000..d64a30fd --- /dev/null +++ b/apps/web/src/app/(settings)/settings/store/account/page.tsx @@ -0,0 +1,16 @@ +import type { Metadata } from "next"; +import { StoreShell } from "@/app/(store)/store/_components/StoreShell"; +import { StoreSettingsClient } from "@/app/(store)/store/settings/StoreSettingsClient"; + +export const metadata: Metadata = { + title: "Store Settings - twizrr", + robots: { index: false, follow: false }, +}; + +export default function StoreAccountSettingsPage() { + return ( + + + + ); +} diff --git a/apps/web/src/app/(settings)/settings/store/page.tsx b/apps/web/src/app/(settings)/settings/store/page.tsx new file mode 100644 index 00000000..3e33f5d1 --- /dev/null +++ b/apps/web/src/app/(settings)/settings/store/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function StoreSettingsIndexPage() { + redirect("/settings/store/account"); +} diff --git a/apps/web/src/app/(settings)/settings/store/profile/page.tsx b/apps/web/src/app/(settings)/settings/store/profile/page.tsx new file mode 100644 index 00000000..f5784a93 --- /dev/null +++ b/apps/web/src/app/(settings)/settings/store/profile/page.tsx @@ -0,0 +1,16 @@ +import type { Metadata } from "next"; +import { StoreShell } from "@/app/(store)/store/_components/StoreShell"; +import { StoreSettingsClient } from "@/app/(store)/store/settings/StoreSettingsClient"; + +export const metadata: Metadata = { + title: "Store Profile Settings - twizrr", + robots: { index: false, follow: false }, +}; + +export default function StoreProfileSettingsPage() { + return ( + + + + ); +} diff --git a/apps/web/src/app/(shopper)/buyer/_components/ShopperShell.tsx b/apps/web/src/app/(shopper)/buyer/_components/ShopperShell.tsx index 973ec27a..bea5939a 100644 --- a/apps/web/src/app/(shopper)/buyer/_components/ShopperShell.tsx +++ b/apps/web/src/app/(shopper)/buyer/_components/ShopperShell.tsx @@ -3,19 +3,18 @@ import type { ReactNode } from "react"; import { usePathname } from "next/navigation"; import { AppShell } from "@/components/layout"; +import { useOwnProfile } from "@/hooks/useOwnProfile"; +import { useOwnerStore } from "@/hooks/useOwnerStore"; interface ShopperShellProps { children: ReactNode; } -// Routes inside /buyer/* that should render without the 3-column social shell. +// Routes that should render without the 3-column social shell. // Checkout (W-09a) and the post-checkout confirmation (W-09b) are both // focus-mode routes per apps/web/AGENTS.md ("Order confirmed — centered, // minimal chrome"). -const STANDALONE_ROUTE_PREFIXES = [ - "/buyer/checkout", - "/buyer/orders/confirmed", -]; +const STANDALONE_ROUTE_PREFIXES = ["/checkout", "/orders/confirmed"]; function isStandaloneRoute(pathname: string): boolean { return STANDALONE_ROUTE_PREFIXES.some( @@ -25,6 +24,8 @@ function isStandaloneRoute(pathname: string): boolean { export function ShopperShell({ children }: ShopperShellProps) { const pathname = usePathname(); + const { hasStore, isLoading } = useOwnerStore(); + const { profile } = useOwnProfile(); if (isStandaloneRoute(pathname)) { return ( @@ -39,7 +40,10 @@ export function ShopperShell({ children }: ShopperShellProps) { mode="SHOPPING" activeHref={pathname} showRightPanel={true} - hasStoreProfile={false} + hasStoreProfile={isLoading ? null : hasStore} + displayName={profile?.displayName ?? null} + profilePhotoUrl={profile?.profilePhotoUrl ?? null} + username={profile?.username ?? null} > {children} diff --git a/apps/web/src/app/(shopper)/buyer/cart/CartCheckoutSheet.tsx b/apps/web/src/app/(shopper)/buyer/cart/CartCheckoutSheet.tsx index 8acb724d..6ed53cd1 100644 --- a/apps/web/src/app/(shopper)/buyer/cart/CartCheckoutSheet.tsx +++ b/apps/web/src/app/(shopper)/buyer/cart/CartCheckoutSheet.tsx @@ -223,7 +223,7 @@ export function CartCheckoutSheet({ toast("Payment received. Confirming your order…", { variant: "success", }); - router.push(`/buyer/orders/confirmed/${order.orderId}`); + router.push(`/orders/confirmed/${order.orderId}`); }, onCancel: () => { setSubmitting(false); diff --git a/apps/web/src/app/(shopper)/buyer/cart/CartClient.tsx b/apps/web/src/app/(shopper)/buyer/cart/CartClient.tsx index f4a4a6ff..7dfd9e8d 100644 --- a/apps/web/src/app/(shopper)/buyer/cart/CartClient.tsx +++ b/apps/web/src/app/(shopper)/buyer/cart/CartClient.tsx @@ -17,6 +17,7 @@ import { Button } from "@/components/ui/Button"; import { Skeleton } from "@/components/ui/Skeleton"; import { useToast } from "@/components/ui/Toast"; import { useMode } from "@/hooks/useMode"; +import { getPublicStoreHref } from "@/lib/routes"; import { cn, formatKobo } from "@/lib/utils"; import { clearCart, @@ -287,7 +288,7 @@ function CartItemRow({

{item.store.handle ? ( {item.store.name} diff --git a/apps/web/src/app/(shopper)/buyer/checkout/[productId]/CheckoutClient.tsx b/apps/web/src/app/(shopper)/buyer/checkout/[productId]/CheckoutClient.tsx index 737bb0b6..a3beb2c4 100644 --- a/apps/web/src/app/(shopper)/buyer/checkout/[productId]/CheckoutClient.tsx +++ b/apps/web/src/app/(shopper)/buyer/checkout/[productId]/CheckoutClient.tsx @@ -21,6 +21,7 @@ import { Skeleton } from "@/components/ui/Skeleton"; import { useToast } from "@/components/ui/Toast"; import { cn, formatKobo } from "@/lib/utils"; import { useMode } from "@/hooks/useMode"; +import { getPublicStoreHref } from "@/lib/routes"; import { createDirectOrder, createSourcedOrder, @@ -362,7 +363,7 @@ export function CheckoutClient({ confirmParams.set("variantId", preselectedVariantId); const qs = confirmParams.toString(); router.push( - `/buyer/orders/confirmed/${order.orderId}${qs ? `?${qs}` : ""}`, + `/orders/confirmed/${order.orderId}${qs ? `?${qs}` : ""}`, ); }, onCancel: () => { @@ -672,7 +673,7 @@ export function CheckoutClient({ Sold by{" "} {product.store.handle ? ( {product.store.name} diff --git a/apps/web/src/app/(shopper)/buyer/feed/page.tsx b/apps/web/src/app/(shopper)/buyer/feed/page.tsx deleted file mode 100644 index 1fbef3a5..00000000 --- a/apps/web/src/app/(shopper)/buyer/feed/page.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { Metadata } from "next"; -import { FeedClient } from "@/components/feed/FeedClient"; - -export const metadata: Metadata = { - title: "Feed | twizrr", - description: - "Your personalised feed of products and posts from stores you follow on twizrr.", -}; - -/** - * /buyer/feed — authenticated shopper feed. - * - * This route is protected by middleware (cookie check). By the time this page - * renders the user is guaranteed to have a session cookie, so we pass - * isAuthenticated=true and default to the "for-you" tab. - * - * The ShopperShell layout (buyer/layout.tsx) wraps this page with AppShell - * already, so we don't add AppShell here. - */ -export default function BuyerFeedPage() { - return ( - <> -

-

- Feed -

-
- - - - ); -} diff --git a/apps/web/src/app/(shopper)/buyer/measurements/page.tsx b/apps/web/src/app/(shopper)/buyer/measurements/page.tsx deleted file mode 100644 index 155dd957..00000000 --- a/apps/web/src/app/(shopper)/buyer/measurements/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import type { Metadata } from "next"; -import { MeasurementsClient } from "./MeasurementsClient"; - -export const metadata: Metadata = { - title: "Measurements & delivery — twizrr", - robots: { index: false, follow: false }, -}; - -export default function MeasurementsPage() { - return ; -} diff --git a/apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx b/apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx index d8b3153f..e6d79a77 100644 --- a/apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx +++ b/apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx @@ -20,6 +20,7 @@ import { DeliveryCodeInput } from "@/components/order/DeliveryCodeInput"; import { EscrowBanner } from "@/components/order/EscrowBanner"; import { OrderTimeline } from "@/components/order/OrderTimeline"; import { RaiseDisputeDialog } from "@/components/order/RaiseDisputeDialog"; +import { getPublicStoreHref } from "@/lib/routes"; import { cn, formatKobo } from "@/lib/utils"; import { cancelOrder, @@ -217,7 +218,7 @@ function OrderDetailLoaded({
{/* Back link */}