refactor: rename marketplace to storefront across the entire monorepo
Build & Deploy / Build & Push Docker Image (push) Successful in 1m2s
Test / Type Check (all packages) (push) Failing after 28s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Storefront Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
Build & Deploy / Deploy to VPS (push) Successful in 3s

Comprehensive rename of all marketplace references to storefront:
- API module: apps/api/src/modules/marketplace/ → storefront/
- Components: MarketplaceHeader → StorefrontHeader, MarketplaceShell →
  StorefrontShell, MarketplaceFooter → StorefrontFooter
- Types: marketplace-homepage.ts → storefront-homepage.ts
- Test files: employee-marketplace-* → employee-storefront-*
- All source code identifiers, imports, route paths, and strings
- Documentation (docs/), CI config (.gitlab-ci.yml), scripts
- Dashboard, admin, storefront workspace references
- Prisma field names preserved (isListedOnMarketplace, marketplaceRating,
  marketplaceFunnelEvent) as they map to database schema

Validation:
- API type-check: 0 errors
- Storefront type-check: 0 errors
- Dashboard type-check: 0 errors
- Full monorepo type-check: only pre-existing admin TS18046
This commit is contained in:
root
2026-06-28 01:14:46 -04:00
parent bffda95a7c
commit 8aab968e09
147 changed files with 829 additions and 829 deletions
+7 -7
View File
@@ -29,7 +29,7 @@ The app covers:
It is distinct from:
- the marketplace app, which is public and cross-company
- the storefront app, which is public and cross-company
- the company site app, which is public and company-branded
- the admin app, which is platform-internal rather than tenant-scoped
@@ -65,7 +65,7 @@ This lets the dashboard run:
- directly in dev on port `3001`
- behind a reverse proxy
- embedded under a shared marketplace/admin hostname while still loading its own chunks correctly
- embedded under a shared storefront/admin hostname while still loading its own chunks correctly
## Top-Level Structure
@@ -92,7 +92,7 @@ apps/dashboard/
api.ts cookie-aware API fetch wrapper
preferences.ts language/theme persistence scoped per employee
dashboardPaths.ts normalizes basePath-aware routes
urls.ts resolves marketplace/admin app links
urls.ts resolves storefront/admin app links
src/middleware.ts auth gate and redirect logic
```
@@ -142,7 +142,7 @@ Important behavior:
- public dashboard routes are limited to sign-in and forgot-password flows
- all other `/dashboard/*` routes require the `employee_session` cookie
- unauthenticated users are redirected either to the marketplace root or to `/dashboard/sign-in`, depending on host context
- unauthenticated users are redirected either to the storefront root or to `/dashboard/sign-in`, depending on host context
- duplicate `/dashboard/dashboard` paths are normalized back to `/dashboard`
This means route protection is enforced before React renders the protected pages.
@@ -284,7 +284,7 @@ The onboarding flow then completes the initial tenant setup in three steps:
1. basic company info via `PATCH /companies/me`
2. brand/public profile via `PATCH /companies/me/brand`
3. payment and marketplace settings via `PATCH /companies/me/brand`
3. payment and storefront settings via `PATCH /companies/me/brand`
This makes onboarding a dashboard-owned continuation of the API signup flow.
@@ -604,7 +604,7 @@ This is the broad configuration surface for the tenant.
It includes:
- public brand and marketplace profile
- public brand and storefront profile
- payment account identifiers
- custom domain setup
- rental policies
@@ -645,7 +645,7 @@ Examples in the code:
It also coordinates with sibling apps:
- `marketplaceUrl` links staff back to the public marketplace domain
- `storefrontUrl` links staff back to the public storefront domain
- `adminUrl` supports cookie-based handoff into the admin interface
- host-aware URL rewriting allows the same app to function under external domains, internal Docker hostnames, and proxied environments
+2 -2
View File
@@ -30,9 +30,9 @@ const DASHBOARD_BASE_PATH = '/dashboard'
// basePath is required so the client-side router knows it's mounted at /dashboard.
// Without it, React cannot hydrate because the URL (/dashboard/sign-up) doesn't
// match the route (/sign-up). The Turbopack double-basePath prefetch bug is
// handled by the marketplace middleware (307 redirect /dashboard/dashboard → /dashboard).
// handled by the storefront middleware (307 redirect /dashboard/dashboard → /dashboard).
//
// In local development the dashboard is often viewed through the marketplace
// In local development the dashboard is often viewed through the storefront
// proxy on port 3000 while the dashboard dev server runs on port 3001. Next
// rewrites do not reliably proxy HMR WebSocket upgrades, so emit absolute
// dashboard asset/HMR URLs directly to the dashboard dev server.
@@ -113,7 +113,7 @@ function offerToForm(o: Offer): FormState {
const copy = {
en: {
title: 'Offers',
subtitle: 'Promotions shown on your site and optionally on the marketplace.',
subtitle: 'Promotions shown on your site and optionally on the storefront.',
createOffer: 'Create Offer',
editOffer: 'Edit Offer',
deleteOffer: 'Delete Offer',
@@ -126,7 +126,7 @@ const copy = {
ends: 'ends',
active: 'Active',
inactive: 'Inactive',
marketplace: 'Marketplace',
storefront: 'Storefront',
featured: 'Featured',
empty: 'No offers yet. Create your first offer to attract customers.',
labelTitle: 'Title *',
@@ -146,7 +146,7 @@ const copy = {
labelValidFrom: 'Valid From *',
labelValidUntil: 'Valid Until *',
labelIsActive: 'Active',
labelIsPublic: 'Show on marketplace',
labelIsPublic: 'Show on storefront',
labelIsFeatured: 'Featured',
typePERCENTAGE: 'Percentage (%)',
typeFIXED_AMOUNT: 'Fixed Amount (MAD)',
@@ -174,7 +174,7 @@ const copy = {
},
fr: {
title: 'Offres',
subtitle: 'Promotions affichées sur votre site et éventuellement sur la marketplace.',
subtitle: 'Promotions affichées sur votre site et éventuellement sur la storefront.',
createOffer: 'Créer une offre',
editOffer: "Modifier l'offre",
deleteOffer: "Supprimer l'offre",
@@ -187,7 +187,7 @@ const copy = {
ends: 'expire le',
active: 'Actif',
inactive: 'Inactif',
marketplace: 'Marketplace',
storefront: 'Storefront',
featured: 'Mise en avant',
empty: 'Aucune offre. Créez votre première offre pour attirer des clients.',
labelTitle: 'Titre *',
@@ -207,7 +207,7 @@ const copy = {
labelValidFrom: 'Valable du *',
labelValidUntil: "Valable jusqu'au *",
labelIsActive: 'Actif',
labelIsPublic: 'Afficher sur la marketplace',
labelIsPublic: 'Afficher sur la storefront',
labelIsFeatured: 'Mise en avant',
typePERCENTAGE: 'Pourcentage (%)',
typeFIXED_AMOUNT: 'Montant fixe (MAD)',
@@ -248,7 +248,7 @@ const copy = {
ends: 'تنتهي في',
active: 'نشط',
inactive: 'غير نشط',
marketplace: 'السوق',
storefront: 'السوق',
featured: 'مميز',
empty: 'لا توجد عروض بعد. أنشئ أول عرض لجذب العملاء.',
labelTitle: 'العنوان *',
@@ -514,7 +514,7 @@ export default function OffersPage() {
)}
<div className="flex flex-wrap gap-2">
{offer.isPublic && <span className="badge-blue">{t.marketplace}</span>}
{offer.isPublic && <span className="badge-blue">{t.storefront}</span>}
{offer.isFeatured && <span className="badge-purple">{t.featured}</span>}
{offer.promoCode && <span className="badge-amber">{offer.promoCode}</span>}
{typeof offer.redemptionCount === 'number' && (
@@ -109,7 +109,7 @@ export default function OnlineReservationsPage() {
const copy = {
en: {
title: 'Online Reservations',
subtitle: 'Requests submitted by customers through the marketplace. Confirm or decline each one.',
subtitle: 'Requests submitted by customers through the storefront. Confirm or decline each one.',
refresh: 'Refresh',
pendingApproval: 'Pending approval',
loading: 'Loading…',
@@ -122,7 +122,7 @@ export default function OnlineReservationsPage() {
},
fr: {
title: 'Réservations en ligne',
subtitle: 'Demandes envoyées par les clients via la marketplace. Confirmez ou refusez chaque demande.',
subtitle: 'Demandes envoyées par les clients via la storefront. Confirmez ou refusez chaque demande.',
refresh: 'Actualiser',
pendingApproval: 'En attente de validation',
loading: 'Chargement…',
@@ -80,7 +80,7 @@ export default function SettingsPage() {
save: 'Saving…',
saveBrand: 'Save brand',
brandProfile: 'Brand and public profile',
brandProfileDesc: 'Control how your company appears on the marketplace and public booking site.',
brandProfileDesc: 'Control how your company appears on the storefront and public booking site.',
payments: 'Rental payment methods',
paymentsDesc: 'Configure how renters pay your company on the public booking site.',
savePayments: 'Save payments',
@@ -104,7 +104,7 @@ export default function SettingsPage() {
contractLanguage: 'Contract language',
whatsapp: 'WhatsApp number',
brandColor: 'Brand color',
listedMarketplace: 'Listed on marketplace',
listedStorefront: 'Listed on storefront',
logoUpload: 'Logo upload',
heroUpload: 'Hero image upload',
uploading: 'Uploading…',
@@ -155,7 +155,7 @@ export default function SettingsPage() {
save: 'Enregistrement…',
saveBrand: 'Enregistrer la marque',
brandProfile: 'Marque et profil public',
brandProfileDesc: 'Contrôlez laffichage de votre entreprise sur la marketplace et le site de réservation.',
brandProfileDesc: 'Contrôlez laffichage de votre entreprise sur la storefront et le site de réservation.',
payments: 'Méthodes de paiement location',
paymentsDesc: 'Configurez comment les clients paient votre entreprise.',
savePayments: 'Enregistrer les paiements',
@@ -179,7 +179,7 @@ export default function SettingsPage() {
contractLanguage: 'Langue du contrat',
whatsapp: 'Numéro WhatsApp',
brandColor: 'Couleur de marque',
listedMarketplace: 'Publié sur la marketplace',
listedStorefront: 'Publié sur la storefront',
logoUpload: 'Logo',
heroUpload: 'Image principale',
uploading: 'Téléversement…',
@@ -254,7 +254,7 @@ export default function SettingsPage() {
contractLanguage: 'لغة العقد',
whatsapp: 'رقم واتساب',
brandColor: 'لون العلامة',
listedMarketplace: 'مدرج في السوق',
listedStorefront: 'مدرج في السوق',
logoUpload: 'رفع الشعار',
heroUpload: 'رفع صورة الواجهة',
uploading: 'جارٍ الرفع…',
@@ -597,7 +597,7 @@ export default function SettingsPage() {
<div className="flex items-center gap-4">
<label className="flex items-center gap-2 text-sm font-medium text-slate-700">
<input type="checkbox" checked={brand.isListedOnMarketplace} onChange={(event) => setBrand((current) => current ? { ...current, isListedOnMarketplace: event.target.checked } : current)} />
{copy.listedMarketplace}
{copy.listedStorefront}
</label>
</div>
</div>
@@ -119,7 +119,7 @@ export default function SubscriptionPage() {
statusLabels: { TRIALING: 'Trialing', ACTIVE: 'Active', PAST_DUE: 'Past due', CANCELLED: 'Cancelled', UNPAID: 'Unpaid' } as Record<string, string>,
invoiceStatusLabels: { PAID: 'Paid', PENDING: 'Pending', FAILED: 'Failed', REFUNDED: 'Refunded' } as Record<string, string>,
planFeatures: {
STARTER: ['Up to 10 vehicles', '1 user seat', 'Basic analytics', 'Marketplace listing'],
STARTER: ['Up to 10 vehicles', '1 user seat', 'Basic analytics', 'Storefront listing'],
GROWTH: ['Up to 50 vehicles', '5 user seats', 'Full analytics', 'Priority listing', 'Custom branding'],
PRO: ['Unlimited vehicles', 'Unlimited seats', 'Advanced reports', 'API access', 'Dedicated support'],
} as Record<Plan, string[]>,
@@ -162,7 +162,7 @@ export default function SubscriptionPage() {
statusLabels: { TRIALING: 'Essai', ACTIVE: 'Actif', PAST_DUE: 'En retard', CANCELLED: 'Annulé', UNPAID: 'Impayé' } as Record<string, string>,
invoiceStatusLabels: { PAID: 'Payé', PENDING: 'En attente', FAILED: 'Échec', REFUNDED: 'Remboursé' } as Record<string, string>,
planFeatures: {
STARTER: ['Jusqu’à 10 véhicules', '1 utilisateur', 'Analyses de base', 'Présence marketplace'],
STARTER: ['Jusqu’à 10 véhicules', '1 utilisateur', 'Analyses de base', 'Présence storefront'],
GROWTH: ['Jusqu’à 50 véhicules', '5 utilisateurs', 'Analyses complètes', 'Mise en avant prioritaire', 'Personnalisation'],
PRO: ['Véhicules illimités', 'Utilisateurs illimités', 'Rapports avancés', 'Accès API', 'Support dédié'],
} as Record<Plan, string[]>,
+1 -1
View File
@@ -271,7 +271,7 @@ export default function OnboardingPage() {
checked={payments.isListedOnMarketplace}
onChange={(e) => setPayments({ ...payments, isListedOnMarketplace: e.target.checked })}
/>
<span className="text-sm text-slate-700">List my company on the RentalDriveGo marketplace</span>
<span className="text-sm text-slate-700">List my company on the RentalDriveGo storefront</span>
</label>
<div className="flex gap-3">
<button onClick={() => setStep(2)} className="btn-secondary flex-1 justify-center">Back</button>
@@ -610,7 +610,7 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
},
reservations: {
heading: 'Booking',
subtitle: 'All booking sources, including dashboard, public site, and marketplace.',
subtitle: 'All booking sources, including dashboard, public site, and storefront.',
colCustomer: 'Customer',
colVehicle: 'Vehicle',
colDates: 'Dates',
@@ -966,7 +966,7 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
},
reservations: {
heading: 'Réservations',
subtitle: 'Toutes les sources de réservation : tableau de bord, site public et marketplace.',
subtitle: 'Toutes les sources de réservation : tableau de bord, site public et storefront.',
colCustomer: 'Client',
colVehicle: 'Véhicule',
colDates: 'Dates',
+3 -3
View File
@@ -27,7 +27,7 @@ afterEach(() => {
})
describe('dashboard cross-app URLs', () => {
it('uses default marketplace and admin bases on the server', async () => {
it('uses default storefront and admin bases on the server', async () => {
const { storefrontUrl, adminUrl } = await loadUrls()
expect(storefrontUrl).toBe('http://localhost:3000')
@@ -54,11 +54,11 @@ describe('dashboard cross-app URLs', () => {
it('preserves non-URL values after normalizing their trailing slash', async () => {
const { storefrontUrl, adminUrl } = await loadUrls({
NEXT_PUBLIC_STOREFRONT_URL: '/marketplace/',
NEXT_PUBLIC_STOREFRONT_URL: '/storefront/',
NEXT_PUBLIC_ADMIN_URL: '/admin/',
})
expect(storefrontUrl).toBe('/marketplace')
expect(storefrontUrl).toBe('/storefront')
expect(adminUrl).toBe('/admin')
})
})
+1 -1
View File
@@ -64,7 +64,7 @@ function localJwtMiddleware(req: NextRequest): NextResponse {
const token = req.cookies.get('employee_session')?.value
const pathname = toDashboardAppPath(req.nextUrl.pathname)
// Redirect signed-in users from sign-in to dashboard (through marketplace proxy)
// Redirect signed-in users from sign-in to dashboard (through storefront proxy)
if (token && pathname === '/sign-in') {
const dashboardUrl = resolveProxyUrl(req, DASHBOARD_BASE_PATH)
return NextResponse.redirect(dashboardUrl)