Skip to content

Feat/web shopper layout#293

Merged
onerandomdevv merged 4 commits into
devfrom
feat/web-shopper-layout
May 21, 2026
Merged

Feat/web shopper layout#293
onerandomdevv merged 4 commits into
devfrom
feat/web-shopper-layout

Conversation

@amoomustakim-hue
Copy link
Copy Markdown
Collaborator

@amoomustakim-hue amoomustakim-hue commented May 21, 2026

What does this PR do?

Type of change

What does this PR do?

Builds W-08: Shopper Layout + Middleware.

This PR adds the authenticated shopper layout foundation for /buyer/* routes, protects shopper routes through middleware, preserves redirect URLs for unauthenticated users, and adds the Profile Menu Drawer foundation for future shopper pages.

Type of change

  • New feature
  • Bug fix
  • Refactor / cleanup
  • Database migration included
  • Chore / maintenance

How to test this

  1. Checkout feat/web-shopper-layout

  2. Run:

    cd apps/web
    pnpm run lint
    npx tsc --noEmit
    pnpm run build

  3. Visit a protected buyer route while unauthenticated, for example:

    /buyer/cart

  4. Confirm it redirects to:

    /login?redirect=/buyer/cart

  5. Confirm shopper layout uses the existing W-02 shell.

  6. Confirm hasStoreProfile={false} is passed for shopper routes so “Manage Store” does not show for every user.

  7. Confirm ProfileMenuDrawer includes:

    • Orders
    • Cart
    • Wishlist
    • Saved
    • Profile
    • Settings
    • Measurements & Delivery Info
    • Reviews
    • Help Center

Pre-commit checklist

  • Web lint/type/build commands pass
  • No backend files changed
  • No env files changed
  • No unrelated W-01/W-02/W-03/W-04 cleanup included
  • No duplicated layout shell components
  • No hardcoded hex values in page/component files
  • No emojis added to UI
  • No JWT/localStorage token storage
  • Touch targets meet 44px minimum

Notes

The previous retrospective audit found that AppShell defaults hasStoreProfile to true. W-08 explicitly passes hasStoreProfile={false} in the shopper shell, so shopper-route users will not accidentally see the Manage Store link.

Follow-up cleanup items from older tasks will be handled in a separate PR, not in this branch.

Summary by CodeRabbit

Release Notes

New Features

  • Added a dedicated buyer shopping interface with a new layout system
  • Introduced a profile menu drawer providing quick navigation to shopping, account, support sections, and store management options

Security & Authentication

  • Implemented session-based authentication for protected buyer and store sections
  • Unauthenticated users are now redirected to login with automatic return to their original location after signing in
  • Authenticated users are automatically redirected away from login-related pages

Review Change Stack

amoomustakim-hue and others added 2 commits May 21, 2026 12:09
Middleware (middleware.ts):
- Protects /buyer/* and /store/* — redirects to /login?redirect=[url]
  if twizrr_access_token cookie is absent (presence-gated; JWT validity
  is enforced by the backend on every API call)
- Auth pages (/login /register /forgot-password /verify-email) redirect
  to /explore when a session cookie already exists
- Preserves existing /@handle → /stores/:handle rewrite

Shopper layout:
- apps/web/src/app/(shopper)/buyer/layout.tsx — Server Component wrapper
- apps/web/src/app/(shopper)/buyer/_components/ShopperShell.tsx — thin
  'use client' wrapper that reads usePathname() and forwards it to
  AppShell as activeHref; avoids making the layout itself a client component

ProfileMenuDrawer:
- apps/web/src/components/layout/ProfileMenuDrawer.tsx
- Uses existing BottomSheet primitive (W-01) + Radix Dialog
- Sections: Shopping (Orders, Cart, Wishlist, Saved),
  Account (Profile, Settings, Measurements, Reviews-soon),
  Support (Help Center)
- Manage Store link only renders when hasStore prop is true
- Exported from components/layout/index.ts

No backend files changed. No env files changed.
Auth assumption: cookie presence only at the edge; real JWT
validation happens backend-side on every API request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- aria-disabled="true" (string) → aria-disabled={true} (boolean)
- &amp; HTML entity → literal & in Measurements & Delivery label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codesandbox
Copy link
Copy Markdown

codesandbox Bot commented May 21, 2026

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Warning

Rate limit exceeded

@onerandomdevv has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 5 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 06ce87df-2aa5-4eeb-8c07-1a55a624db2e

📥 Commits

Reviewing files that changed from the base of the PR and between 02743dd and fd7d5be.

📒 Files selected for processing (1)
  • apps/web/src/middleware.ts
📝 Walkthrough

Walkthrough

Adds a buyer shopping layout shell with client-side routing awareness, a profile navigation drawer with conditional store management, and middleware-enforced authentication gates that protect buyer/store routes while redirecting authenticated users away from login pages.

Changes

Buyer Shopping Interface and Auth Protection

Layer / File(s) Summary
ShopperShell layout wrapper
apps/web/src/app/(shopper)/buyer/_components/ShopperShell.tsx, apps/web/src/app/(shopper)/buyer/layout.tsx
ShopperShell wraps children in AppShell with mode="SHOPPING", dynamically sets activeHref to the current pathname, and fixes showRightPanel=true and hasStoreProfile=false. BuyerLayout provides the route layout entry point wrapping children in ShopperShell.
ProfileMenuDrawer navigation component
apps/web/src/components/layout/ProfileMenuDrawer.tsx, apps/web/src/components/layout/index.ts
ProfileMenuDrawer renders a bottom-sheet with grouped navigation sections (Shopping, Account, Support) using Lucide icons, includes a disabled "Reviews" item marked as coming soon, conditionally shows "Manage Store" only when hasStore=true, and closes the drawer on link click. Component is exported from the layout index.
Middleware authentication gating and redirects
apps/web/src/middleware.ts
Adds configuration for protected route prefixes (/buyer/*, /store/*) and auth pages (/login, /register, /forgot-password, /verify-email). Implements hasSession() helper to check for twizrr_access_token cookie. Unauthenticated users hitting protected routes are redirected to /login with a redirect query preserving the original path. Authenticated users on auth pages are redirected to /explore. Refactors /@handle rewrite using simplified regex and NextResponse.rewrite().

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • coded-devs/twizrr#280: ShopperShell directly uses the AppShell component with specific mode and panel settings that depend on the layout shell infrastructure added in that PR.
  • coded-devs/twizrr#249: Both PRs add server-side auth redirect logic to middleware for protecting buyer routes with session-based gating.
  • coded-devs/twizrr#246: Middleware redirects authenticated users to /explore, which is the public route page introduced in that PR.

Poem

🐰 A shopper's shell, so snug and bright,
With drawers of menus left and right,
The guards stand tall at every door—
Auth gates to keep and routes explore! 🛍️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat/web shopper layout' directly relates to the main change: adding an authenticated shopper layout foundation for /buyer/* routes with accompanying middleware and UI components.
Description check ✅ Passed The description covers main objectives (W-08 shopper layout + middleware), includes testing steps, pre-commit checklist, and notes. However, 'Area affected' and 'Screenshots' sections are incomplete or missing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-shopper-layout

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/web/src/components/layout/ProfileMenuDrawer.tsx (2)

195-216: 💤 Low value

Consider routing the Manage Store entry through DrawerItem.

The Manage Store <Link> duplicates the layout, sizing, focus ring, and hover styles of DrawerItem — only the text color and weight differ. Extending DrawerItem with an optional variant/accent prop would keep all menu rows visually consistent and avoid drift if the base styles change later. Skip if you'd rather keep it explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/layout/ProfileMenuDrawer.tsx` around lines 195 - 216,
Replace the duplicated styled Link for "Manage Store" with the shared DrawerItem
component: add an optional prop (e.g., variant or accent) to DrawerItem that
toggles the saffron text color and font weight, ensure DrawerItem accepts and
forwards href, onClick, icon (LayoutDashboard) and children, and update
ProfileMenuDrawer's hasStore branch to render <DrawerItem variant="accent"
href="/store/dashboard" onClick={close}>Manage Store</DrawerItem> so all sizing,
focus ring, hover and layout remain centralized in DrawerItem.

29-65: 💤 Low value

Make onClose optional for soon items.

When soon is true the item renders as a <div> and never invokes onClose, but the prop is still required by the type — every disabled item has to pass a dead handler. Marking it optional (or splitting the variants) tightens the contract.

♻️ Proposed change
 interface DrawerItemProps {
   href: string;
   icon: LucideIcon;
   label: string;
   /** Mark item as coming soon — renders disabled with a label */
   soon?: boolean;
-  onClose: () => void;
+  onClose?: () => void;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/layout/ProfileMenuDrawer.tsx` around lines 29 - 65,
The DrawerItemProps type currently requires onClose even for disabled "soon"
items; update DrawerItemProps to make onClose optional (onClose?: () => void)
and adjust the DrawerItem function signature to accept the optional onClose,
ensuring any code paths that would call onClose (the non-soon rendering) still
call it only when defined; leave the soon block as-is (it won't call onClose)
and ensure any callers creating "soon" items no longer must pass a dummy
handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/middleware.ts`:
- Line 10: PROTECTED_PREFIXES currently contains trailing slashes which makes
pathname.startsWith("/buyer/") miss the exact "/buyer" route; update the
matching strategy in middleware.ts: either remove trailing slashes in
PROTECTED_PREFIXES (use "/buyer" and "/store") and change the check to (pathname
=== prefix || pathname.startsWith(prefix + "/")), or keep prefixes without
slashes and use segment-boundary-aware logic so both the root ("/buyer") and
nested routes ("/buyer/...") are gated; adjust the code that iterates
PROTECTED_PREFIXES and uses startsWith accordingly.
- Around line 12-17: AUTH_PAGE_PREFIXES currently uses plain prefixes which
makes pathname.startsWith("/login") over-match routes like "/login-help"; update
the check to match only exact auth routes or a path segment by changing
AUTH_PAGE_PREFIXES entries to include a trailing "/" (e.g. "/login/") or by
performing a segment-aware test (compare pathname === "/login" ||
pathname.startsWith("/login/")) wherever pathname.startsWith is used (refer to
AUTH_PAGE_PREFIXES and the middleware check using pathname.startsWith around the
code block at lines ~49-55) so authenticated users are only matched for the
intended auth pages.

---

Nitpick comments:
In `@apps/web/src/components/layout/ProfileMenuDrawer.tsx`:
- Around line 195-216: Replace the duplicated styled Link for "Manage Store"
with the shared DrawerItem component: add an optional prop (e.g., variant or
accent) to DrawerItem that toggles the saffron text color and font weight,
ensure DrawerItem accepts and forwards href, onClick, icon (LayoutDashboard) and
children, and update ProfileMenuDrawer's hasStore branch to render <DrawerItem
variant="accent" href="/store/dashboard" onClick={close}>Manage
Store</DrawerItem> so all sizing, focus ring, hover and layout remain
centralized in DrawerItem.
- Around line 29-65: The DrawerItemProps type currently requires onClose even
for disabled "soon" items; update DrawerItemProps to make onClose optional
(onClose?: () => void) and adjust the DrawerItem function signature to accept
the optional onClose, ensuring any code paths that would call onClose (the
non-soon rendering) still call it only when defined; leave the soon block as-is
(it won't call onClose) and ensure any callers creating "soon" items no longer
must pass a dummy handler.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f09fadb9-e990-42e4-a3f8-74961fb8ea5a

📥 Commits

Reviewing files that changed from the base of the PR and between 4609cb3 and 02743dd.

📒 Files selected for processing (5)
  • apps/web/src/app/(shopper)/buyer/_components/ShopperShell.tsx
  • apps/web/src/app/(shopper)/buyer/layout.tsx
  • apps/web/src/components/layout/ProfileMenuDrawer.tsx
  • apps/web/src/components/layout/index.ts
  • apps/web/src/middleware.ts

Comment thread apps/web/src/middleware.ts Outdated
Comment thread apps/web/src/middleware.ts
pathname.startsWith("/buyer/") missed the exact path /buyer.
Now checks pathname === p || pathname.startsWith(p + "/") so both
/buyer and /buyer/* (and equivalently /store, /store/*) are gated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@onerandomdevv onerandomdevv merged commit b310799 into dev May 21, 2026
8 checks passed
@onerandomdevv onerandomdevv deleted the feat/web-shopper-layout branch May 21, 2026 13:07
@coderabbitai coderabbitai Bot mentioned this pull request May 22, 2026
21 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants