Skip to content

refactor: extract Stepper, useMerchantSession, DisconnectWalletMenu, and FormErrorBanner - #143

Open
davidugorji wants to merge 2 commits into
ShadeProtocol:mainfrom
davidugorji:refactor/extract-shared-auth-components
Open

refactor: extract Stepper, useMerchantSession, DisconnectWalletMenu, and FormErrorBanner#143
davidugorji wants to merge 2 commits into
ShadeProtocol:mainfrom
davidugorji:refactor/extract-shared-auth-components

Conversation

@davidugorji

Copy link
Copy Markdown

Summary

Extracts four shared units out of the merchant register and sign-in flows.

Closes #116
Closes #112
Closes #115
Closes #122

#116 — Stepper

src/components/stepper.tsx accepts steps: { step, label, icon }[] and currentStep. RegisterClient renders <Stepper steps={stepMeta} currentStep={step} /> in place of the inline .map() over stepMeta. Markup, classes, and the active/inactive branch are copied verbatim, so the indicator is pixel-identical.

#112 — useMerchantSession

src/hooks/use-merchant-session.ts wraps getMerchantSessionAddress from src/lib/merchant-storage.ts, returns { walletAddress, isLoading }, and performs the router.replace("/sign-in") redirect internally. RegisterClient's inline useEffect and its walletAddress state are removed in favour of the hook; its existing loading spinner branch is untouched, so visiting /register without a session still redirects exactly as before.

#115 — DisconnectWalletMenu

  • clearMerchantSession() added to src/lib/merchant-storage.ts, removing MERCHANT_SESSION_KEY from sessionStorage.
  • src/components/disconnect-wallet-menu.tsx uses the Radix DropdownMenu (same import style as InvoiceRowActions) with a single "Disconnect wallet" item that clears the session and redirects to /sign-in.
  • The Topbar wallet pill is now the dropdown trigger, keeping the same truncated-address presentation.
  • MerchantSessionGuard applies useMerchantSession to the whole (dashboard) route group, which is what makes the acceptance criterion hold: after disconnecting, /dashboard redirects straight back to /sign-in. The group layout stays a server component, so per-page metadata exports are unaffected.

#122 — FormErrorBanner

The issue refers to "the more general Alert primitive", but no Alert existed in the repo yet, so this adds a minimal src/components/ui/alert.tsx following the existing button.tsx cva pattern. Its destructive variant resolves to exactly rounded-lg border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive — the class string previously duplicated in both pages.

src/components/form-error-banner.tsx takes message: string | null, returns null when the message is null, and accepts a className for the differing spacing (mt-5 in RegisterClient, mb-4 in SignInClient). Both pages now render through it with no visual change.

Out-of-scope fix

next build already failed on main at the lint gate with four errors unrelated to this work. They are fixed mechanically in a separate commit (968b5f5) so CI on this PR can pass — drop that commit if you'd rather handle it separately:

  • payment/[id]/page.tsx — unused reject param
  • api-key-reveal.tsx — unused error binding
  • InvoiceRowActions.test.tsx — unused waitFor import
  • ProfileDetailsForm.test.tsx@ts-ignore@ts-expect-error

Testing

Four new test files cover the stepper's label rendering and active-step highlighting, the hook's redirect and no-redirect paths, the disconnect menu clearing sessionStorage and redirecting, and the banner's null/rendered/className behaviour.

  • npx tsc --noEmit — clean
  • npx vitest run — 36 passed (11 files)
  • npx next lint — 0 errors
  • npx prettier --check . — clean
  • npx next build — succeeds

Extract four duplicated or page-local concerns from the register and
sign-in flows into reusable units:

- Stepper (src/components/stepper.tsx) replaces the inline stepMeta
  .map() progress indicator in RegisterClient. Accepts steps and
  currentStep; markup and classes are unchanged.
- useMerchantSession (src/hooks/use-merchant-session.ts) wraps
  getMerchantSessionAddress, returns { walletAddress, isLoading } and
  performs the /sign-in redirect internally. RegisterClient now uses
  the hook instead of its own useEffect.
- clearMerchantSession + DisconnectWalletMenu give merchants a way to
  clear MERCHANT_SESSION_KEY from the UI. The Topbar wallet pill is now
  the dropdown trigger, and MerchantSessionGuard applies the session
  guard to the whole (dashboard) route group so /dashboard redirects
  back to /sign-in once the session is cleared.
- FormErrorBanner wraps a new Alert primitive and replaces the
  duplicated destructive-banner markup in RegisterClient and
  SignInClient. Rendered classes are identical, so there is no visual
  change.

Adds unit tests for the stepper, hook, disconnect menu, and error
banner.
next build failed on main at the lint gate with four errors unrelated to
this branch's changes. Fixed mechanically so CI can pass:

- payment/[id]/page.tsx: drop unused reject param
- api-key-reveal.tsx: use optional catch binding for unused error
- InvoiceRowActions.test.tsx: drop unused waitFor import
- ProfileDetailsForm.test.tsx: @ts-ignore -> @ts-expect-error
@drips-wave

drips-wave Bot commented Jul 30, 2026

Copy link
Copy Markdown

@davidugorji Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant