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
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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 l’affichage de votre entreprise sur la marketplace et le site de réservation.',
|
||||
brandProfileDesc: 'Contrôlez l’affichage 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[]>,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user