The Wholesale Portal admin client was the largest single file in the app
(2391 lines) and mixed 11 distinct UI concerns in one module.
Split into focused files under src/components/wholesale/admin/:
types.ts shared types (MsgFn, PendingRegistration, ...)
WholesaleIcon.tsx header SVG
WholesaleLoadingSkeleton.tsx loading state
StatusBadge.tsx order status pill
AddRecipientForm.tsx notification recipient input
PriceSheetModal.tsx bulk price-sheet send confirmation
CustomerPricingPanel.tsx per-customer pricing overrides
WebhookSettingsSection.tsx outbound webhook config + test dispatch
DashboardTab.tsx stat cards + recent orders + webhook feed
ProductsTab.tsx wholesale product CRUD
CustomersTab.tsx customers + registrations + pricing
OrdersTab.tsx orders + bulk ops + deposit modals
SettingsTab.tsx portal settings + webhook + recipients
WholesaleClient.tsx is now a 189-line shell that owns data loading and
tab routing. No behavior changes; the original logic was preserved
verbatim. Mechanical extraction only.
Also fixed an existing import bug where getPendingWholesaleRegistrations
was being imported from @/actions/wholesale instead of
@/actions/wholesale-register (typecheck caught it).
Verified: typecheck clean for new code (pre-existing dahlia/fetch mock
errors unrelated). Build compiles through to the same pre-existing
billing type error. Zero new lint warnings introduced.
Refactor-progress tracked at /tmp/refactor-routecomm.md.
- Add MinIO/S3-compatible storage client (src/lib/storage.ts) with uploadObject,
deleteObject, presigned URL helpers, and BUCKETS constant
- Wire product images, brand logos, and water log photos to MinIO via the
new storage client
- Migrate forgot-password to Neon Auth (remove Supabase /auth/v1/recover call)
- Migrate send-scheduled cron to direct Postgres + Resend (remove Supabase Edge
Function proxy)
- Add logoUrl to email types (OrderReceipt, Welcome, PasswordReset) and pass
brand_settings.logo_url from all call sites
- Update email templates to use dynamic logoUrl instead of hardcoded Supabase
bucket URLs
- Remove hardcoded Supabase URLs from TuxedoVideoHero, TuxedoAboutPage,
TimeTrackingFieldClient; use brand_settings props + local public/ fallback
- Download brand logos (3) and tuxedo-hero.mp4 (36MB) from Supabase bucket to
public/ for local development
- Add MinIO env vars to .env.example (endpoint, access key, secret, buckets)
- Fix TimeTrackingFieldClient to destructure logoUrl and brandAccent props
- Fix admin/users.ts logoUrl type (null → undefined for optional string)
- Remove stale sb- cookie from wholesale-auth
- Migrate tuxedo/about page to remove supabase import and use pool query for
wholesale_settings lookup
Server-side / caching refactor (Grok):
- New RPC get_public_stops_for_brand (migration 148) for public storefront stops
- New server action getPublicStopsForBrand with revalidate=300 + tags
- Add revalidateTag invalidation to createStopsBatch + publishStop
- Convert /tuxedo/stops and /indian-river-direct/stops to Server Components
- Extract TuxedoStopsList + IndianRiverStopsList as client islands (GSAP only)
- Removes supabase-js from browser bundle on those routes
- Both pages now statically prerendered (5m ISR)
Parallel agent changes also staged:
- AI provider model list refresh (claude-sonnet-4-5, etc.)
- ESLint directive patches for react-hooks/set-state-in-effect
- Admin + storefront + checkout + cart updates
- New admin_create_stop_rpcs migration (147)
- Misc fixes across ~90 files
Build verified: typecheck clean, lint clean on new files, production build succeeds.
- LoadingSkeleton: updated to warm stone palette instead of slate
- WholesaleClient: converted from dark zinc to warm stone theme
- Updated stat cards, tables, and section backgrounds to match admin theme
- Replaced loading screen from dark to light theme