From 8aab968e09da87115eb8cc1974c35e20fc9ed681 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 28 Jun 2026 01:14:46 -0400 Subject: [PATCH] refactor: rename marketplace to storefront across the entire monorepo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .gitlab-ci.yml | 4 +- PHASE16_DESIGN_MIGRATION_MANIFEST.json | 4 +- SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md | 6 +- apps/admin/next.config.js | 2 +- .../src/app/dashboard/companies/[id]/page.tsx | 2 +- apps/admin/src/app/dashboard/page.tsx | 8 +- .../src/app/dashboard/site-config/page.tsx | 64 +++++++-------- apps/api/src/app.ts | 8 +- apps/api/src/data/platform-content.json | 50 ++++++------ apps/api/src/lib/emailTranslations.test.ts | 6 +- apps/api/src/lib/emailTranslations.ts | 4 +- apps/api/src/middleware/rateLimiter.ts | 2 +- apps/api/src/modules/admin/admin.routes.ts | 8 +- apps/api/src/modules/admin/admin.schemas.ts | 38 ++++----- apps/api/src/modules/admin/admin.service.ts | 12 +-- .../site/site.service.boundary.test.ts | 2 +- apps/api/src/modules/site/site.service.ts | 4 +- apps/api/src/modules/site/site.test.ts | 2 +- .../storefront.presenter.test.ts} | 4 +- .../storefront.presenter.ts} | 0 .../storefront.repo.edge.test.ts} | 16 ++-- .../storefront.repo.ts} | 8 +- .../storefront.routes.ts} | 16 ++-- .../storefront.schemas.test.ts} | 12 +-- .../storefront.schemas.ts} | 2 +- .../storefront.service.edge.test.ts} | 10 +-- .../storefront.service.ts} | 26 +++---- .../storefront.test.ts} | 40 +++++----- .../services/platformContentService.test.ts | 14 ++-- .../src/services/platformContentService.ts | 12 +-- apps/api/src/swagger/openapi.boundary.test.ts | 2 +- apps/api/src/swagger/openapi.ts | 52 ++++++------- ...loyee-storefront-notification.api.test.ts} | 22 +++--- .../tests/api/public-validation.api.test.ts | 34 ++++---- ...mployee-storefront-boundaries.e2e.test.ts} | 4 +- .../e2e/public-validation-smoke.e2e.test.ts | 10 +-- ...ing-storefront-vehicle-boundaries.test.ts} | 4 +- ...torefront-notification-boundaries.test.ts} | 6 +- .../public-validation-boundaries.test.ts | 8 +- apps/dashboard/README.md | 14 ++-- apps/dashboard/next.config.js | 4 +- .../src/app/(dashboard)/offers/page.tsx | 16 ++-- .../(dashboard)/online-reservations/page.tsx | 4 +- .../src/app/(dashboard)/settings/page.tsx | 12 +-- .../src/app/(dashboard)/subscription/page.tsx | 4 +- apps/dashboard/src/app/onboarding/page.tsx | 2 +- .../dashboard/src/components/I18nProvider.tsx | 4 +- apps/dashboard/src/lib/urls.test.ts | 6 +- apps/dashboard/src/middleware.ts | 2 +- apps/storefront/next.config.js | 2 +- .../src/app/(public)/app-policy-pages.test.ts | 2 +- .../src/app/(public)/explore/[slug]/page.tsx | 20 ++--- .../explore/[slug]/vehicles/[id]/page.tsx | 16 ++-- .../src/app/(public)/explore/page.tsx | 36 ++++----- .../app/(public)/footer/[slug]/page.test.ts | 2 +- apps/storefront/src/app/(public)/layout.tsx | 12 +-- apps/storefront/src/app/layout.tsx | 14 ++-- .../src/app/renter/dashboard/page.tsx | 8 +- .../src/app/renter/notifications/page.tsx | 4 +- .../src/app/renter/profile/page.tsx | 4 +- .../src/app/renter/saved-companies/page.tsx | 8 +- .../storefront/src/components/BookingForm.tsx | 24 +++--- .../src/components/FooterContentPage.test.ts | 6 +- .../src/components/FooterContentPage.tsx | 8 +- .../storefront/src/components/RenterShell.tsx | 4 +- .../src/components/StorefrontFooter.tsx | 2 +- .../StorefrontHeader.boundary.test.ts | 4 +- ...etplaceHeader.tsx => StorefrontHeader.tsx} | 6 +- .../components/StorefrontShell.auth.test.ts | 4 +- .../StorefrontShell.content.test.ts | 6 +- .../src/components/StorefrontShell.tsx | 78 +++++++++---------- .../WorkspaceFrame.boundary.test.ts | 2 +- .../src/components/WorkspaceFrame.tsx | 4 +- apps/storefront/src/lib/api.test.ts | 22 +++--- apps/storefront/src/lib/api.ts | 16 ++-- apps/storefront/src/lib/appUrls.test.ts | 4 +- apps/storefront/src/lib/footerContent.test.ts | 6 +- apps/storefront/src/lib/footerContent.ts | 12 +-- apps/storefront/src/lib/i18n.server.test.ts | 16 ++-- apps/storefront/src/lib/i18n.server.ts | 6 +- apps/storefront/src/lib/i18n.test.ts | 22 +++--- apps/storefront/src/lib/i18n.ts | 6 +- apps/storefront/src/lib/preferences.test.ts | 6 +- apps/storefront/src/middleware.test.ts | 10 +-- apps/storefront/src/proxy.ts | 2 +- docs/CAR_BOOKING_PROCESS_IMPROVEMENT_PLAN.md | 4 +- docs/DOCKER.md | 14 ++-- docs/README.md | 24 +++--- ...URITY_HARDENING_LEFTOVER_APPLIED_REPORT.md | 6 +- docs/create-account-guide.md | 18 ++--- docs/dev_docker_run.md | 2 +- docs/frontend_redesign_plan.md | 20 ++--- docs/progressive-signup-plan.md | 6 +- docs/project-design/API_REFACTOR_TASK_LIST.md | 2 +- docs/project-design/COOKIE_POLICY.md | 4 +- docs/project-design/FEATURES.md | 14 ++-- docs/project-design/PAGES.md | 18 ++--- .../VULNERABILITY_FIXES_2026-05-22.md | 8 +- docs/project-design/api-routes.md | 16 ++-- docs/project-design/schema.md | 2 +- packages/types/src/api.ts | 4 +- packages/types/src/index.ts | 2 +- ...ace-homepage.ts => storefront-homepage.ts} | 70 ++++++++--------- scripts/backup-restore-guide.md | 2 +- scripts/docker-prod-common.sh | 4 +- scripts/docker-prod-deploy.sh | 4 +- scripts/docker-prod-restore.sh | 2 +- scripts/docker-prod-run-pulled-image.sh | 4 +- scripts/docker-prod-up-all.sh | 6 +- storefront/next.config.js | 2 +- .../src/app/(public)/app-policy-pages.test.ts | 2 +- .../src/app/(public)/explore/[slug]/page.tsx | 20 ++--- .../explore/[slug]/vehicles/[id]/page.tsx | 16 ++-- storefront/src/app/(public)/explore/page.tsx | 36 ++++----- .../app/(public)/footer/[slug]/page.test.ts | 2 +- storefront/src/app/layout.tsx | 14 ++-- storefront/src/app/renter/dashboard/page.tsx | 8 +- .../src/app/renter/notifications/page.tsx | 4 +- storefront/src/app/renter/profile/page.tsx | 4 +- .../src/app/renter/saved-companies/page.tsx | 8 +- storefront/src/components/BookingForm.tsx | 24 +++--- .../src/components/FooterContentPage.test.ts | 6 +- .../src/components/FooterContentPage.tsx | 8 +- storefront/src/components/RenterShell.tsx | 4 +- .../src/components/StorefrontFooter.tsx | 2 +- .../StorefrontHeader.boundary.test.ts | 4 +- ...etplaceHeader.tsx => StorefrontHeader.tsx} | 6 +- .../components/StorefrontShell.auth.test.ts | 4 +- .../StorefrontShell.content.test.ts | 6 +- .../src/components/StorefrontShell.tsx | 78 +++++++++---------- .../WorkspaceFrame.boundary.test.ts | 2 +- storefront/src/components/WorkspaceFrame.tsx | 4 +- .../src/components/public/SiteFooter.tsx | 2 +- .../src/components/public/SiteNavbar.tsx | 2 +- .../src/components/public/SitePageLayout.tsx | 6 +- storefront/src/lib/api.test.ts | 22 +++--- storefront/src/lib/api.ts | 16 ++-- storefront/src/lib/appUrls.test.ts | 4 +- storefront/src/lib/footerContent.test.ts | 6 +- storefront/src/lib/footerContent.ts | 12 +-- storefront/src/lib/i18n.server.test.ts | 16 ++-- storefront/src/lib/i18n.server.ts | 6 +- storefront/src/lib/i18n.test.ts | 22 +++--- storefront/src/lib/i18n.ts | 6 +- storefront/src/lib/preferences.test.ts | 6 +- storefront/src/middleware.test.ts | 10 +-- storefront/src/proxy.ts | 2 +- 147 files changed, 829 insertions(+), 829 deletions(-) rename apps/api/src/modules/{marketplace/marketplace.presenter.test.ts => storefront/storefront.presenter.test.ts} (86%) rename apps/api/src/modules/{marketplace/marketplace.presenter.ts => storefront/storefront.presenter.ts} (100%) rename apps/api/src/modules/{marketplace/marketplace.repo.edge.test.ts => storefront/storefront.repo.edge.test.ts} (87%) rename apps/api/src/modules/{marketplace/marketplace.repo.ts => storefront/storefront.repo.ts} (96%) rename apps/api/src/modules/{marketplace/marketplace.routes.ts => storefront/storefront.routes.ts} (90%) rename apps/api/src/modules/{marketplace/marketplace.schemas.test.ts => storefront/storefront.schemas.test.ts} (73%) rename apps/api/src/modules/{marketplace/marketplace.schemas.ts => storefront/storefront.schemas.ts} (98%) rename apps/api/src/modules/{marketplace/marketplace.service.edge.test.ts => storefront/storefront.service.edge.test.ts} (94%) rename apps/api/src/modules/{marketplace/marketplace.service.ts => storefront/storefront.service.ts} (94%) rename apps/api/src/modules/{marketplace/marketplace.test.ts => storefront/storefront.test.ts} (87%) rename apps/api/src/tests/api/{employee-marketplace-notification.api.test.ts => employee-storefront-notification.api.test.ts} (82%) rename apps/api/src/tests/e2e/{employee-marketplace-boundaries.e2e.test.ts => employee-storefront-boundaries.e2e.test.ts} (82%) rename apps/api/src/tests/integration/{billing-marketplace-vehicle-boundaries.test.ts => billing-storefront-vehicle-boundaries.test.ts} (69%) rename apps/api/src/tests/integration/{employee-marketplace-notification-boundaries.test.ts => employee-storefront-notification-boundaries.test.ts} (66%) rename storefront/src/components/MarketplaceFooter.tsx => apps/storefront/src/components/StorefrontFooter.tsx (99%) rename storefront/src/components/MarketplaceHeader.boundary.test.ts => apps/storefront/src/components/StorefrontHeader.boundary.test.ts (94%) rename apps/storefront/src/components/{MarketplaceHeader.tsx => StorefrontHeader.tsx} (99%) rename storefront/src/components/MarketplaceShell.auth.test.ts => apps/storefront/src/components/StorefrontShell.auth.test.ts (93%) rename storefront/src/components/MarketplaceShell.content.test.ts => apps/storefront/src/components/StorefrontShell.content.test.ts (88%) rename storefront/src/components/MarketplaceShell.tsx => apps/storefront/src/components/StorefrontShell.tsx (83%) rename packages/types/src/{marketplace-homepage.ts => storefront-homepage.ts} (89%) rename apps/storefront/src/components/MarketplaceFooter.tsx => storefront/src/components/StorefrontFooter.tsx (99%) rename apps/storefront/src/components/MarketplaceHeader.boundary.test.ts => storefront/src/components/StorefrontHeader.boundary.test.ts (94%) rename storefront/src/components/{MarketplaceHeader.tsx => StorefrontHeader.tsx} (99%) rename apps/storefront/src/components/MarketplaceShell.auth.test.ts => storefront/src/components/StorefrontShell.auth.test.ts (93%) rename apps/storefront/src/components/MarketplaceShell.content.test.ts => storefront/src/components/StorefrontShell.content.test.ts (88%) rename apps/storefront/src/components/MarketplaceShell.tsx => storefront/src/components/StorefrontShell.tsx (83%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a44fe95..56542d1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,14 +28,14 @@ api_tests: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' -marketplace_tests: +storefront_tests: stage: test image: node:20-bookworm before_script: - npm ci - npm run build --workspace @rentaldrivego/types script: - - npm run test:marketplace + - npm run test:storefront rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' diff --git a/PHASE16_DESIGN_MIGRATION_MANIFEST.json b/PHASE16_DESIGN_MIGRATION_MANIFEST.json index 2af58ef..8d95c4f 100644 --- a/PHASE16_DESIGN_MIGRATION_MANIFEST.json +++ b/PHASE16_DESIGN_MIGRATION_MANIFEST.json @@ -49,13 +49,13 @@ "sha256": "49e03206558248eb5de2b9a8463be6e0b43e01df518bc0b625fe2953a18185c6" }, { - "path": "storefront/src/components/MarketplaceFooter.tsx", + "path": "storefront/src/components/StorefrontFooter.tsx", "status": "modified", "bytes": 4632, "sha256": "169d04dcf0976d2637ef5371ca295e9c362bd089d8d6ec313b4e8b4131d5bdd3" }, { - "path": "storefront/src/components/MarketplaceHeader.tsx", + "path": "storefront/src/components/StorefrontHeader.tsx", "status": "modified", "bytes": 10438, "sha256": "25afc57ff9893f255eddf19580e84b510e6032764daaa5727e2e8d669dca4213" diff --git a/SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md b/SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md index f641a28..a5a14bb 100644 --- a/SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md +++ b/SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md @@ -60,15 +60,15 @@ Changed files: - `apps/api/src/app.ts` - `apps/dashboard/src/middleware.ts` -- `apps/marketplace/src/middleware.ts` +- `apps/storefront/src/middleware.ts` - `apps/admin/src/middleware.ts` - `apps/dashboard/src/middleware.test.ts` -- `apps/marketplace/src/middleware.test.ts` +- `apps/storefront/src/middleware.test.ts` What changed: - API now rejects requests containing `x-middleware-subrequest` before route handling. -- Dashboard, marketplace, and admin Next middleware now reject the same header at the app layer. +- Dashboard, storefront, and admin Next middleware now reject the same header at the app layer. - Added/updated middleware tests for the rejection path. Security effect: diff --git a/apps/admin/next.config.js b/apps/admin/next.config.js index eef4f4a..80b7b8f 100644 --- a/apps/admin/next.config.js +++ b/apps/admin/next.config.js @@ -5,7 +5,7 @@ const apiOrigin = (process.env.API_INTERNAL_URL ?? process.env.API_URL ?? 'http: const apiUrl = new URL(apiOrigin) const ADMIN_BASE_PATH = '/admin' -// In Docker dev the admin app runs on port 3002 while the marketplace proxy +// In Docker dev the admin app runs on port 3002 while the storefront proxy // serves it from port 3000. When ADMIN_ASSET_PREFIX is set, Next emits // absolute chunk URLs so /admin pages load their JS/CSS and HMR directly from // port 3002, bypassing the proxy (which can't upgrade WebSocket connections). diff --git a/apps/admin/src/app/dashboard/companies/[id]/page.tsx b/apps/admin/src/app/dashboard/companies/[id]/page.tsx index fc8822e..c068a5a 100644 --- a/apps/admin/src/app/dashboard/companies/[id]/page.tsx +++ b/apps/admin/src/app/dashboard/companies/[id]/page.tsx @@ -724,7 +724,7 @@ export default function AdminCompanyDetailPage() { diff --git a/apps/admin/src/app/dashboard/page.tsx b/apps/admin/src/app/dashboard/page.tsx index b3f1119..ae5f740 100644 --- a/apps/admin/src/app/dashboard/page.tsx +++ b/apps/admin/src/app/dashboard/page.tsx @@ -24,7 +24,7 @@ export default function AdminDashboardPage() { brand: 'RentalDriveGo', eyebrow: 'Operations command', platformOverview: 'RentalDriveGo admin dashboard', - subtitle: 'Monitor marketplace health, subscription coverage, renter activity, and operator actions from one control surface.', + subtitle: 'Monitor storefront health, subscription coverage, renter activity, and operator actions from one control surface.', liveSignal: 'Live platform signal', readiness: 'Operational readiness', readinessBody: 'Core admin workflows are connected and ready for platform support.', @@ -36,14 +36,14 @@ export default function AdminDashboardPage() { ['Renters', 'Support renter trust workflows, blocking, and account recovery.', 'View all'], ['Audit logs', 'Trace every sensitive admin action across RentalDriveGo.', 'View logs'], ], - health: ['Tenant accounts', 'Marketplace identity', 'Admin audit trail'], + health: ['Tenant accounts', 'Storefront identity', 'Admin audit trail'], }, fr: { kpis: ['Entreprises totales', 'Entreprises actives', 'Locataires totaux', 'Réservations totales'], brand: 'RentalDriveGo', eyebrow: 'Centre des opérations', platformOverview: 'Tableau de bord admin RentalDriveGo', - subtitle: 'Suivez la santé de la marketplace, les abonnements, l’activité des locataires et les actions opérateur depuis une même interface.', + subtitle: 'Suivez la santé de la storefront, les abonnements, l’activité des locataires et les actions opérateur depuis une même interface.', liveSignal: 'Signal plateforme en direct', readiness: 'Disponibilité opérationnelle', readinessBody: 'Les principaux workflows admin sont connectés et prêts pour le support plateforme.', @@ -55,7 +55,7 @@ export default function AdminDashboardPage() { ['Locataires', 'Gérer les workflows de confiance, de blocage et de récupération.', 'Voir tout'], ['Journaux d’audit', 'Tracer chaque action admin sensible dans RentalDriveGo.', 'Voir les journaux'], ], - health: ['Comptes locataires', 'Identité marketplace', 'Piste d’audit admin'], + health: ['Comptes locataires', 'Identité storefront', 'Piste d’audit admin'], }, ar: { kpis: ['إجمالي الشركات', 'الشركات النشطة', 'إجمالي المستأجرين', 'إجمالي الحجوزات'], diff --git a/apps/admin/src/app/dashboard/site-config/page.tsx b/apps/admin/src/app/dashboard/site-config/page.tsx index 9b818fb..99d6ffd 100644 --- a/apps/admin/src/app/dashboard/site-config/page.tsx +++ b/apps/admin/src/app/dashboard/site-config/page.tsx @@ -3,12 +3,12 @@ import { useEffect, useState } from 'react' import Link from 'next/link' import { - cloneMarketplaceHomepageContent, - resolveMarketplaceHomepageSections, - type MarketplaceHomepageConfig, - type MarketplaceHomepageContent, - type MarketplaceHomepageSectionType, - type MarketplaceLanguage, + cloneStorefrontHomepageContent, + resolveStorefrontHomepageSections, + type StorefrontHomepageConfig, + type StorefrontHomepageContent, + type StorefrontHomepageSectionType, + type StorefrontLanguage, } from '@rentaldrivego/types' import { useAdminI18n } from '@/components/I18nProvider' import { ADMIN_API_BASE } from '@/lib/api' @@ -35,15 +35,15 @@ const STATUS_COLORS: Record = { const INPUT_CLASS = 'w-full rounded-xl border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-emerald-500' const LABEL_CLASS = 'mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-zinc-500' -function cloneHomepageContent(content: MarketplaceHomepageConfig) { - const cloned = JSON.parse(JSON.stringify(content)) as MarketplaceHomepageConfig - ;(['en', 'fr', 'ar'] as MarketplaceLanguage[]).forEach((language) => { - cloned[language].sections = resolveMarketplaceHomepageSections(cloned[language].sections) +function cloneHomepageContent(content: StorefrontHomepageConfig) { + const cloned = JSON.parse(JSON.stringify(content)) as StorefrontHomepageConfig + ;(['en', 'fr', 'ar'] as StorefrontLanguage[]).forEach((language) => { + cloned[language].sections = resolveStorefrontHomepageSections(cloned[language].sections) }) return cloned } -const HOMEPAGE_SECTIONS: MarketplaceHomepageSectionType[] = [ +const HOMEPAGE_SECTIONS: StorefrontHomepageSectionType[] = [ 'hero', 'surface', 'pillars', @@ -58,12 +58,12 @@ export default function AdminSiteConfigPage() { const copy = { en: { title: 'Site configuration', - description: 'Edit the main marketplace homepage here, or jump into a company to manage its branded public homepage and menu.', + description: 'Edit the main storefront homepage here, or jump into a company to manage its branded public homepage and menu.', homepageTitle: 'Main website homepage', - homepageDescription: 'This controls the marketplace homepage shown on the main RentalDriveGo website.', + homepageDescription: 'This controls the storefront homepage shown on the main RentalDriveGo website.', saveHomepage: 'Save homepage', savingHomepage: 'Saving…', - homepageSaved: 'Marketplace homepage saved.', + homepageSaved: 'Storefront homepage saved.', search: 'Search by company or slug…', loading: 'Loading…', empty: 'No companies found', @@ -86,7 +86,7 @@ export default function AdminSiteConfigPage() { previewTitle: 'Live preview', previewDescription: 'Draft changes appear here before you save them.', homepageSections: 'Homepage sections', - homepageSectionsDescription: 'Add or remove blocks from the main marketplace homepage.', + homepageSectionsDescription: 'Add or remove blocks from the main storefront homepage.', addSection: 'Add', removeSection: 'Remove', expand: 'Expand', @@ -94,12 +94,12 @@ export default function AdminSiteConfigPage() { }, fr: { title: 'Configuration du site', - description: 'Modifiez ici la page d’accueil principale de la marketplace, ou ouvrez une entreprise pour gérer sa page publique et son menu.', + description: 'Modifiez ici la page d’accueil principale de la storefront, ou ouvrez une entreprise pour gérer sa page publique et son menu.', homepageTitle: 'Page d’accueil du site principal', - homepageDescription: 'Cette section contrôle la page d’accueil marketplace affichée sur le site principal de RentalDriveGo.', + homepageDescription: 'Cette section contrôle la page d’accueil storefront affichée sur le site principal de RentalDriveGo.', saveHomepage: 'Enregistrer la page d’accueil', savingHomepage: 'Enregistrement…', - homepageSaved: 'Page d’accueil marketplace enregistrée.', + homepageSaved: 'Page d’accueil storefront enregistrée.', search: 'Rechercher par entreprise ou slug…', loading: 'Chargement…', empty: 'Aucune entreprise trouvée', @@ -112,7 +112,7 @@ export default function AdminSiteConfigPage() { companiesTitle: 'Sites de marque des entreprises', languageLabel: 'Langue', hero: 'Bloc principal', - surface: 'Bloc marketplace', + surface: 'Bloc storefront', companySection: 'Bloc opérateur', renterSection: 'Bloc client', valueSection: 'Blocs de valeur', @@ -122,7 +122,7 @@ export default function AdminSiteConfigPage() { previewTitle: 'Aperçu en direct', previewDescription: 'Les brouillons apparaissent ici avant l’enregistrement.', homepageSections: 'Sections de la page d’accueil', - homepageSectionsDescription: 'Ajoutez ou retirez des blocs de la page d’accueil marketplace principale.', + homepageSectionsDescription: 'Ajoutez ou retirez des blocs de la page d’accueil storefront principale.', addSection: 'Ajouter', removeSection: 'Retirer', expand: 'Ouvrir', @@ -171,8 +171,8 @@ export default function AdminSiteConfigPage() { const [search, setSearch] = useState('') const [loading, setLoading] = useState(true) const [error, setError] = useState(null) - const [homepage, setHomepage] = useState(cloneMarketplaceHomepageContent()) - const [homepageLanguage, setHomepageLanguage] = useState(language) + const [homepage, setHomepage] = useState(cloneStorefrontHomepageContent()) + const [homepageLanguage, setHomepageLanguage] = useState(language) const [homepageSaving, setHomepageSaving] = useState(false) const [homepageMessage, setHomepageMessage] = useState(null) const [homepageExpanded, setHomepageExpanded] = useState(false) @@ -189,7 +189,7 @@ export default function AdminSiteConfigPage() { credentials: 'include', cache: 'no-store', }), - fetch(`${ADMIN_API_BASE}/admin/site-config/marketplace-homepage`, { + fetch(`${ADMIN_API_BASE}/admin/site-config/storefront-homepage`, { credentials: 'include', cache: 'no-store', }), @@ -202,7 +202,7 @@ export default function AdminSiteConfigPage() { const homepageJson = await homepageRes.json() if (homepageRes.ok && homepageJson?.data) { - setHomepage(cloneHomepageContent(homepageJson.data as MarketplaceHomepageConfig)) + setHomepage(cloneHomepageContent(homepageJson.data as StorefrontHomepageConfig)) } } catch (err: any) { setError(err.message) @@ -225,7 +225,7 @@ export default function AdminSiteConfigPage() { ) }, [search, companies]) - function updateHomepageContent(patch: Partial) { + function updateHomepageContent(patch: Partial) { setHomepage((current) => ({ ...current, [homepageLanguage]: { @@ -257,16 +257,16 @@ export default function AdminSiteConfigPage() { } function getActiveSections() { - return resolveMarketplaceHomepageSections(activeContent.sections) + return resolveStorefrontHomepageSections(activeContent.sections) } - function addHomepageSection(section: MarketplaceHomepageSectionType) { + function addHomepageSection(section: StorefrontHomepageSectionType) { const sections = getActiveSections() if (sections.includes(section)) return updateHomepageContent({ sections: [...sections, section] }) } - function removeHomepageSection(section: MarketplaceHomepageSectionType) { + function removeHomepageSection(section: StorefrontHomepageSectionType) { const sections = getActiveSections().filter((item) => item !== section) if (sections.length === 0) return updateHomepageContent({ sections }) @@ -276,7 +276,7 @@ export default function AdminSiteConfigPage() { setHomepageSaving(true) setHomepageMessage(null) try { - const res = await fetch(`${ADMIN_API_BASE}/admin/site-config/marketplace-homepage`, { + const res = await fetch(`${ADMIN_API_BASE}/admin/site-config/storefront-homepage`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', @@ -286,7 +286,7 @@ export default function AdminSiteConfigPage() { }) const json = await res.json() if (!res.ok) throw new Error(json?.message ?? 'Failed to save homepage') - setHomepage(cloneHomepageContent(json.data as MarketplaceHomepageConfig)) + setHomepage(cloneHomepageContent(json.data as StorefrontHomepageConfig)) setHomepageMessage(copy.homepageSaved) } catch (err: any) { setError(err.message) @@ -298,7 +298,7 @@ export default function AdminSiteConfigPage() { const activeContent = homepage[homepageLanguage] const activeSections = getActiveSections() const availableSections = HOMEPAGE_SECTIONS.filter((section) => !activeSections.includes(section)) - const sectionLabels: Record = { + const sectionLabels: Record = { hero: copy.hero, surface: copy.surface, pillars: copy.valueSection, @@ -337,7 +337,7 @@ export default function AdminSiteConfigPage() {
{copy.languageLabel} - {(['en', 'fr', 'ar'] as MarketplaceLanguage[]).map((value) => ( + {(['en', 'fr', 'ar'] as StorefrontLanguage[]).map((value) => ( diff --git a/apps/dashboard/src/components/I18nProvider.tsx b/apps/dashboard/src/components/I18nProvider.tsx index 1465685..29316e0 100644 --- a/apps/dashboard/src/components/I18nProvider.tsx +++ b/apps/dashboard/src/components/I18nProvider.tsx @@ -610,7 +610,7 @@ const dictionaries: Record = { }, 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 = { }, 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', diff --git a/apps/dashboard/src/lib/urls.test.ts b/apps/dashboard/src/lib/urls.test.ts index 2319f8f..a3e678d 100644 --- a/apps/dashboard/src/lib/urls.test.ts +++ b/apps/dashboard/src/lib/urls.test.ts @@ -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') }) }) diff --git a/apps/dashboard/src/middleware.ts b/apps/dashboard/src/middleware.ts index 27faa1e..5a72977 100644 --- a/apps/dashboard/src/middleware.ts +++ b/apps/dashboard/src/middleware.ts @@ -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) diff --git a/apps/storefront/next.config.js b/apps/storefront/next.config.js index da5d24b..20d26b7 100644 --- a/apps/storefront/next.config.js +++ b/apps/storefront/next.config.js @@ -2,7 +2,7 @@ const { buildSecurityHeaders, normalizeAssetPrefix } = require('../../config/nextSecurityHeaders') -// The marketplace proxies /dashboard and /admin to their own Next dev servers. +// The storefront proxies /dashboard and /admin to their own Next dev servers. // Allow those asset-prefix origins so proxied pages can load chunks and HMR. const dashboardAssetSource = normalizeAssetPrefix( process.env.DASHBOARD_ASSET_PREFIX ?? (process.env.NODE_ENV !== 'production' ? 'http://localhost:3001' : undefined), diff --git a/apps/storefront/src/app/(public)/app-policy-pages.test.ts b/apps/storefront/src/app/(public)/app-policy-pages.test.ts index 337f232..313dfca 100644 --- a/apps/storefront/src/app/(public)/app-policy-pages.test.ts +++ b/apps/storefront/src/app/(public)/app-policy-pages.test.ts @@ -16,7 +16,7 @@ function expectPolicyPage(element: unknown, slug: string, forcedLanguage: string expect(page.props.forcedLanguage).toBe(forcedLanguage) } -describe('marketplace static app policy pages', () => { +describe('storefront static app policy pages', () => { it('binds app privacy pages to explicit locales', () => { expectPolicyPage(AppPrivacyEnPage(), 'privacy-policy', 'en') expectPolicyPage(AppPrivacyFrPage(), 'privacy-policy', 'fr') diff --git a/apps/storefront/src/app/(public)/explore/[slug]/page.tsx b/apps/storefront/src/app/(public)/explore/[slug]/page.tsx index 4325db8..a314bbf 100644 --- a/apps/storefront/src/app/(public)/explore/[slug]/page.tsx +++ b/apps/storefront/src/app/(public)/explore/[slug]/page.tsx @@ -1,6 +1,6 @@ import Link from 'next/link' -import { marketplaceFetchOrDefault } from '@/lib/api' -import { getMarketplaceLanguage } from '@/lib/i18n.server' +import { storefrontFetchOrDefault } from '@/lib/api' +import { getStorefrontLanguage } from '@/lib/i18n.server' import { formatCurrency } from '@rentaldrivego/types' interface CompanyProfile { @@ -21,14 +21,14 @@ interface CompanyProfile { } export default async function CompanyProfilePage({ params }: { params: { slug: string } }) { - const language = await getMarketplaceLanguage() + const language = await getStorefrontLanguage() const dict = { en: { - unavailable: 'Marketplace unavailable', + unavailable: 'Storefront unavailable', unavailableTitle: 'Company details are temporarily unavailable.', - unavailableBody: 'The marketplace API is running, but the database is not reachable in this local environment.', + unavailableBody: 'The storefront API is running, but the database is not reachable in this local environment.', backToExplore: 'Back to explore', - partner: 'Marketplace partner', + partner: 'Storefront partner', vehicles: 'vehicles', offers: 'active offers', rating: 'Rating', @@ -42,11 +42,11 @@ export default async function CompanyProfilePage({ params }: { params: { slug: s unavailableNoDate: 'Temporarily unavailable for new reservations.', }, fr: { - unavailable: 'Marketplace indisponible', + unavailable: 'Storefront indisponible', unavailableTitle: "Les détails de l'entreprise sont temporairement indisponibles.", - unavailableBody: "L'API marketplace fonctionne, mais la base de données n'est pas accessible dans cet environnement local.", + unavailableBody: "L'API storefront fonctionne, mais la base de données n'est pas accessible dans cet environnement local.", backToExplore: "Retour à l'exploration", - partner: 'Partenaire marketplace', + partner: 'Partenaire storefront', vehicles: 'véhicules', offers: 'offres actives', rating: 'Note', @@ -78,7 +78,7 @@ export default async function CompanyProfilePage({ params }: { params: { slug: s unavailableNoDate: 'غير متاحة مؤقتاً للحجوزات الجديدة.', }, }[language] - const company = await marketplaceFetchOrDefault(`/marketplace/${params.slug}`, null) + const company = await storefrontFetchOrDefault(`/storefront/${params.slug}`, null) if (!company) { return ( diff --git a/apps/storefront/src/app/(public)/explore/[slug]/vehicles/[id]/page.tsx b/apps/storefront/src/app/(public)/explore/[slug]/vehicles/[id]/page.tsx index ac02fc9..a5a446c 100644 --- a/apps/storefront/src/app/(public)/explore/[slug]/vehicles/[id]/page.tsx +++ b/apps/storefront/src/app/(public)/explore/[slug]/vehicles/[id]/page.tsx @@ -1,6 +1,6 @@ import Link from 'next/link' -import { marketplaceFetchOrDefault } from '@/lib/api' -import { getMarketplaceLanguage } from '@/lib/i18n.server' +import { storefrontFetchOrDefault } from '@/lib/api' +import { getStorefrontLanguage } from '@/lib/i18n.server' import { formatCurrency } from '@rentaldrivego/types' import BookingForm from '@/components/BookingForm' @@ -33,12 +33,12 @@ interface VehicleDetail { } export default async function VehicleDetailPage({ params }: { params: { slug: string; id: string } }) { - const language = await getMarketplaceLanguage() + const language = await getStorefrontLanguage() const dict = { en: { - unavailable: 'Marketplace unavailable', + unavailable: 'Storefront unavailable', unavailableTitle: 'Vehicle details are temporarily unavailable.', - unavailableBody: 'The marketplace frontend is running, but the backing database is not reachable right now.', + unavailableBody: 'The storefront frontend is running, but the backing database is not reachable right now.', backToFleet: 'Back to fleet', backToExplore: 'Back to explore', noPhotos: 'No photos available.', @@ -59,9 +59,9 @@ export default async function VehicleDetailPage({ params }: { params: { slug: st photos: 'Photos', }, fr: { - unavailable: 'Marketplace indisponible', + unavailable: 'Storefront indisponible', unavailableTitle: 'Les détails du véhicule sont temporairement indisponibles.', - unavailableBody: "Le frontend marketplace fonctionne, mais la base de données n'est pas accessible pour le moment.", + unavailableBody: "Le frontend storefront fonctionne, mais la base de données n'est pas accessible pour le moment.", backToFleet: 'Retour à la flotte', backToExplore: 'Retour à l’exploration', noPhotos: 'Aucune photo disponible.', @@ -106,7 +106,7 @@ export default async function VehicleDetailPage({ params }: { params: { slug: st }, }[language] - const vehicle = await marketplaceFetchOrDefault(`/marketplace/${params.slug}/vehicles/${params.id}`, null) + const vehicle = await storefrontFetchOrDefault(`/storefront/${params.slug}/vehicles/${params.id}`, null) if (!vehicle) { return ( diff --git a/apps/storefront/src/app/(public)/explore/page.tsx b/apps/storefront/src/app/(public)/explore/page.tsx index 1d657a4..7251068 100644 --- a/apps/storefront/src/app/(public)/explore/page.tsx +++ b/apps/storefront/src/app/(public)/explore/page.tsx @@ -1,6 +1,6 @@ import Link from 'next/link' -import { marketplaceFetchOrDefault } from '@/lib/api' -import { getMarketplaceLanguage } from '@/lib/i18n.server' +import { storefrontFetchOrDefault } from '@/lib/api' +import { getStorefrontLanguage } from '@/lib/i18n.server' import ExploreSearchForm from './ExploreSearchForm' import ExploreVehicleGrid from './ExploreVehicleGrid' @@ -68,7 +68,7 @@ const CATEGORY_VALUES = [ export default async function ExplorePage({ searchParams }: { searchParams?: Promise> }) { const resolvedSearchParams = await searchParams - const language = await getMarketplaceLanguage() + const language = await getStorefrontLanguage() const dict = { en: { kicker: 'Discovery only', @@ -89,10 +89,10 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro ageOptions: ['18+', '21+', '23+', '25+', '30+'], search: 'Search', currentDeals: 'Current deals', - featuredOffers: 'Featured marketplace offers', + featuredOffers: 'Featured storefront offers', company: 'Company', validUntil: 'Valid until', - offersUnavailable: 'Marketplace offers are unavailable right now.', + offersUnavailable: 'Storefront offers are unavailable right now.', availableVehicles: 'Available vehicles', listings: 'listings', rentalCompany: 'Rental company', @@ -108,8 +108,8 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro vehicleUnavailable: 'Vehicle listings are unavailable right now.', browseByCategory: 'Browse by category', browseByCompany: 'Browse by company', - marketplacePartners: 'Marketplace partners', - marketplacePartner: 'Marketplace partner', + storefrontPartners: 'Storefront partners', + storefrontPartner: 'Storefront partner', rating: 'Rating', new: 'New', publishedVehicles: 'published vehicles', @@ -135,10 +135,10 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro ageOptions: ['18+', '21+', '23+', '25+', '30+'], search: 'Rechercher', currentDeals: 'Offres du moment', - featuredOffers: 'Offres marketplace mises en avant', + featuredOffers: 'Offres storefront mises en avant', company: 'Entreprise', validUntil: "Valable jusqu'au", - offersUnavailable: 'Les offres marketplace sont indisponibles pour le moment.', + offersUnavailable: 'Les offres storefront sont indisponibles pour le moment.', availableVehicles: 'Véhicules disponibles', listings: 'annonces', rentalCompany: 'Société de location', @@ -154,8 +154,8 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro vehicleUnavailable: 'Les annonces véhicules sont indisponibles pour le moment.', browseByCategory: 'Parcourir par catégorie', browseByCompany: 'Parcourir par entreprise', - marketplacePartners: 'Partenaires marketplace', - marketplacePartner: 'Partenaire marketplace', + storefrontPartners: 'Partenaires storefront', + storefrontPartner: 'Partenaire storefront', rating: 'Note', new: 'Nouveau', publishedVehicles: 'véhicules publiés', @@ -200,8 +200,8 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro vehicleUnavailable: 'قوائم السيارات غير متاحة حالياً.', browseByCategory: 'تصفح حسب الفئة', browseByCompany: 'تصفح حسب الشركة', - marketplacePartners: 'شركاء السوق', - marketplacePartner: 'شريك في السوق', + storefrontPartners: 'شركاء السوق', + storefrontPartner: 'شريك في السوق', rating: 'التقييم', new: 'جديد', publishedVehicles: 'سيارات منشورة', @@ -241,9 +241,9 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro query.set('pageSize', '24') const [offers, vehicles, companies] = await Promise.all([ - marketplaceFetchOrDefault('/marketplace/offers', []), - marketplaceFetchOrDefault(`/marketplace/search?${query.toString()}`, []), - marketplaceFetchOrDefault('/marketplace/companies?pageSize=8', []), + storefrontFetchOrDefault('/storefront/offers', []), + storefrontFetchOrDefault(`/storefront/search?${query.toString()}`, []), + storefrontFetchOrDefault('/storefront/companies?pageSize=8', []), ]) const cities = Array.from( new Set( @@ -309,12 +309,12 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro

{dict.browseByCompany}

-

{dict.marketplacePartners}

+

{dict.storefrontPartners}

{companies.map((company) => ( -

{company.brand?.publicCity ?? dict.marketplacePartner}

+

{company.brand?.publicCity ?? dict.storefrontPartner}

{company.brand?.displayName ?? dict.rentalCompany}

{dict.rating} {company.brand?.marketplaceRating?.toFixed(1) ?? dict.new}

{company._count.vehicles} {dict.publishedVehicles}

diff --git a/apps/storefront/src/app/(public)/footer/[slug]/page.test.ts b/apps/storefront/src/app/(public)/footer/[slug]/page.test.ts index ac35998..fe57631 100644 --- a/apps/storefront/src/app/(public)/footer/[slug]/page.test.ts +++ b/apps/storefront/src/app/(public)/footer/[slug]/page.test.ts @@ -13,7 +13,7 @@ import FooterContentPage from '@/components/FooterContentPage' import FooterPage, { generateStaticParams } from './page' import { footerPageSlugs } from '@/lib/footerContent' -describe('marketplace footer route', () => { +describe('storefront footer route', () => { it('generates one static route per registered footer slug', () => { expect(generateStaticParams()).toEqual(footerPageSlugs.map((slug) => ({ slug }))) }) diff --git a/apps/storefront/src/app/(public)/layout.tsx b/apps/storefront/src/app/(public)/layout.tsx index dfe401d..c4fb68f 100644 --- a/apps/storefront/src/app/(public)/layout.tsx +++ b/apps/storefront/src/app/(public)/layout.tsx @@ -1,12 +1,12 @@ 'use client' -import MarketplaceHeader from '@/components/MarketplaceHeader' -import MarketplaceFooter from '@/components/MarketplaceFooter' -import { useMarketplacePreferences, getFooterContent, localeOptions } from '@/components/MarketplaceShell' +import StorefrontHeader from '@/components/StorefrontHeader' +import StorefrontFooter from '@/components/StorefrontFooter' +import { useStorefrontPreferences, getFooterContent, localeOptions } from '@/components/StorefrontShell' import { resolveBrowserAppUrl } from '@/lib/appUrls' export default function PublicLayout({ children }: { children: React.ReactNode }) { - const { language, theme, dict, companyName, setLanguage, setTheme } = useMarketplacePreferences() + const { language, theme, dict, companyName, setLanguage, setTheme } = useStorefrontPreferences() const dashboardUrl = resolveBrowserAppUrl(process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3000/dashboard') const footerContent = getFooterContent(language) const available = localeOptions.filter((o) => o.value !== language) @@ -14,7 +14,7 @@ export default function PublicLayout({ children }: { children: React.ReactNode } return (
-
{children}
- - {children} + {children} ) diff --git a/apps/storefront/src/app/renter/dashboard/page.tsx b/apps/storefront/src/app/renter/dashboard/page.tsx index 68f6567..badf423 100644 --- a/apps/storefront/src/app/renter/dashboard/page.tsx +++ b/apps/storefront/src/app/renter/dashboard/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' -import { useMarketplacePreferences } from '@/components/MarketplaceShell' +import { useStorefrontPreferences } from '@/components/StorefrontShell' const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1' @@ -29,7 +29,7 @@ type Status = 'loading' | 'ready' | 'error' export default function RenterDashboardPage() { const router = useRouter() - const { language } = useMarketplacePreferences() + const { language } = useStorefrontPreferences() const dict = { en: { loadProfile: 'Could not load profile.', @@ -52,7 +52,7 @@ export default function RenterDashboardPage() { savedCompanies: 'Saved companies', noneSaved: 'None saved yet', saved: 'saved', - saveCompaniesPrompt: 'Save companies you like while browsing the marketplace.', + saveCompaniesPrompt: 'Save companies you like while browsing the storefront.', startExploring: 'Start exploring', rentalCompany: 'Rental company', }, @@ -77,7 +77,7 @@ export default function RenterDashboardPage() { savedCompanies: 'Entreprises sauvegardées', noneSaved: 'Aucune pour le moment', saved: 'sauvegardées', - saveCompaniesPrompt: 'Enregistrez les entreprises que vous aimez en parcourant la marketplace.', + saveCompaniesPrompt: 'Enregistrez les entreprises que vous aimez en parcourant la storefront.', startExploring: 'Commencer à explorer', rentalCompany: 'Société de location', }, diff --git a/apps/storefront/src/app/renter/notifications/page.tsx b/apps/storefront/src/app/renter/notifications/page.tsx index d4def24..a502df9 100644 --- a/apps/storefront/src/app/renter/notifications/page.tsx +++ b/apps/storefront/src/app/renter/notifications/page.tsx @@ -9,11 +9,11 @@ import { markRenterNotificationRead, type RenterNotification, } from '@/lib/renter' -import { useMarketplacePreferences } from '@/components/MarketplaceShell' +import { useStorefrontPreferences } from '@/components/StorefrontShell' export default function RenterNotificationsPage() { const router = useRouter() - const { language } = useMarketplacePreferences() + const { language } = useStorefrontPreferences() const dict = { en: { load: 'Could not load notifications.', diff --git a/apps/storefront/src/app/renter/profile/page.tsx b/apps/storefront/src/app/renter/profile/page.tsx index 95abee5..a563d87 100644 --- a/apps/storefront/src/app/renter/profile/page.tsx +++ b/apps/storefront/src/app/renter/profile/page.tsx @@ -12,7 +12,7 @@ import { type RenterPreference, type RenterProfile, } from '@/lib/renter' -import { useMarketplacePreferences } from '@/components/MarketplaceShell' +import { useStorefrontPreferences } from '@/components/StorefrontShell' type Status = 'loading' | 'ready' | 'error' const RENTER_EVENTS = ['BOOKING_CONFIRMED', 'BOOKING_REMINDER', 'RETURN_REMINDER', 'REFUND_ISSUED', 'NEW_OFFER'] @@ -20,7 +20,7 @@ const RENTER_CHANNELS = ['EMAIL', 'SMS', 'WHATSAPP', 'PUSH', 'IN_APP'] export default function RenterProfilePage() { const router = useRouter() - const { language } = useMarketplacePreferences() + const { language } = useStorefrontPreferences() const dict = { en: { profile: 'Profile', diff --git a/apps/storefront/src/app/renter/saved-companies/page.tsx b/apps/storefront/src/app/renter/saved-companies/page.tsx index bed4cfe..d9b9da7 100644 --- a/apps/storefront/src/app/renter/saved-companies/page.tsx +++ b/apps/storefront/src/app/renter/saved-companies/page.tsx @@ -4,11 +4,11 @@ import { useEffect, useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' import { RenterAuthError, loadRenterProfile, type SavedCompany } from '@/lib/renter' -import { useMarketplacePreferences } from '@/components/MarketplaceShell' +import { useStorefrontPreferences } from '@/components/StorefrontShell' export default function RenterSavedCompaniesPage() { const router = useRouter() - const { language } = useMarketplacePreferences() + const { language } = useStorefrontPreferences() const dict = { en: { load: 'Could not load saved companies.', @@ -17,7 +17,7 @@ export default function RenterSavedCompaniesPage() { back: 'Back to dashboard', loading: 'Loading saved companies…', empty: 'You have not saved any companies yet.', - browse: 'Browse the marketplace', + browse: 'Browse the storefront', rentalCompany: 'Rental company', }, fr: { @@ -27,7 +27,7 @@ export default function RenterSavedCompaniesPage() { back: 'Retour au tableau de bord', loading: 'Chargement des entreprises sauvegardées…', empty: 'Vous n’avez encore enregistré aucune entreprise.', - browse: 'Parcourir la marketplace', + browse: 'Parcourir la storefront', rentalCompany: 'Société de location', }, ar: { diff --git a/apps/storefront/src/components/BookingForm.tsx b/apps/storefront/src/components/BookingForm.tsx index 11818e0..6c8cb41 100644 --- a/apps/storefront/src/components/BookingForm.tsx +++ b/apps/storefront/src/components/BookingForm.tsx @@ -2,8 +2,8 @@ import { useEffect, useRef, useState } from 'react' import { formatCurrency } from '@rentaldrivego/types' -import { MarketplaceApiError, marketplaceFetch, marketplacePost } from '@/lib/api' -import { useMarketplacePreferences } from '@/components/MarketplaceShell' +import { StorefrontApiError, storefrontFetch, storefrontPost } from '@/lib/api' +import { useStorefrontPreferences } from '@/components/StorefrontShell' type Props = { vehicleId: string @@ -314,7 +314,7 @@ function formatLocalizedDate(value: string, language: 'en' | 'fr' | 'ar') { function getBookingSessionId() { if (typeof window === 'undefined') return 'server' - const existing = window.sessionStorage.getItem('marketplace-booking-session-id') + const existing = window.sessionStorage.getItem('storefront-booking-session-id') if (existing) return existing const created = @@ -322,7 +322,7 @@ function getBookingSessionId() { ? window.crypto.randomUUID() : `booking-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` - window.sessionStorage.setItem('marketplace-booking-session-id', created) + window.sessionStorage.setItem('storefront-booking-session-id', created) return created } @@ -364,7 +364,7 @@ export default function BookingForm({ allowDifferentDropoff, dropoffLocations, }: Props) { - const { language } = useMarketplacePreferences() + const { language } = useStorefrontPreferences() const t = copy[language] const pickupOptions = Array.isArray(pickupLocations) ? pickupLocations : [] const dropoffOptions = Array.isArray(dropoffLocations) ? dropoffLocations : [] @@ -400,7 +400,7 @@ export default function BookingForm({ } async function trackEvent(eventName: FunnelEventName, metadata?: Record) { - await marketplacePost('/marketplace/events', { + await storefrontPost('/storefront/events', { eventName, companySlug, vehicleId, @@ -413,8 +413,8 @@ export default function BookingForm({ async function checkAvailability(startDate: string, endDate: string, signal?: AbortSignal) { const start = new Date(startDate) const end = new Date(endDate) - const result = await marketplaceFetch( - `/marketplace/${companySlug}/vehicles/${vehicleId}/availability?startDate=${encodeURIComponent(start.toISOString())}&endDate=${encodeURIComponent(end.toISOString())}`, + const result = await storefrontFetch( + `/storefront/${companySlug}/vehicles/${vehicleId}/availability?startDate=${encodeURIComponent(start.toISOString())}&endDate=${encodeURIComponent(end.toISOString())}`, signal ? { signal } : undefined, ) @@ -471,7 +471,7 @@ export default function BookingForm({ void checkAvailability(fields.startDate, fields.endDate, controller.signal).catch((err) => { if (controller.signal.aborted) return - if (err instanceof MarketplaceApiError && err.code === 'invalid_dates') { + if (err instanceof StorefrontApiError && err.code === 'invalid_dates') { setAvailability({ status: 'idle', nextAvailableAt: null }) return } @@ -520,7 +520,7 @@ export default function BookingForm({ return } - const booking = await marketplacePost('/marketplace/reservations', { + const booking = await storefrontPost('/storefront/reservations', { vehicleId, companySlug, firstName: fields.firstName.trim(), @@ -546,7 +546,7 @@ export default function BookingForm({ elapsedMs: Date.now() - startedAtRef.current, }) } catch (err: unknown) { - if (err instanceof MarketplaceApiError && err.code === 'unavailable') { + if (err instanceof StorefrontApiError && err.code === 'unavailable') { setAvailability({ status: 'unavailable', nextAvailableAt: err.nextAvailableAt ?? null }) setError(buildUnavailableMessage(err.nextAvailableAt ?? null)) } else { @@ -554,7 +554,7 @@ export default function BookingForm({ } void trackEvent('booking_request_failed', { - reason: err instanceof MarketplaceApiError ? err.code ?? 'request_failed' : 'request_failed', + reason: err instanceof StorefrontApiError ? err.code ?? 'request_failed' : 'request_failed', }) } finally { setSubmitting(false) diff --git a/apps/storefront/src/components/FooterContentPage.test.ts b/apps/storefront/src/components/FooterContentPage.test.ts index 31a5e68..098ee73 100644 --- a/apps/storefront/src/components/FooterContentPage.test.ts +++ b/apps/storefront/src/components/FooterContentPage.test.ts @@ -3,8 +3,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const preferenceState = vi.hoisted(() => ({ language: 'en' as 'en' | 'fr' | 'ar' })) -vi.mock('@/components/MarketplaceShell', () => ({ - useMarketplacePreferences: () => ({ language: preferenceState.language, theme: 'light' }), +vi.mock('@/components/StorefrontShell', () => ({ + useStorefrontPreferences: () => ({ language: preferenceState.language, theme: 'light' }), })) import FooterContentPage from './FooterContentPage' @@ -29,7 +29,7 @@ describe('FooterContentPage', () => { preferenceState.language = 'en' }) - it('uses the current marketplace language when no forced language is provided', () => { + it('uses the current storefront language when no forced language is provided', () => { preferenceState.language = 'fr' const page = FooterContentPage({ slug: 'contact-sales' }) const text = collectText(page).join(' ') diff --git a/apps/storefront/src/components/FooterContentPage.tsx b/apps/storefront/src/components/FooterContentPage.tsx index bfeba25..c1cfe48 100644 --- a/apps/storefront/src/components/FooterContentPage.tsx +++ b/apps/storefront/src/components/FooterContentPage.tsx @@ -2,9 +2,9 @@ import React from 'react' -import { useMarketplacePreferences } from '@/components/MarketplaceShell' +import { useStorefrontPreferences } from '@/components/StorefrontShell' import { type FooterPageSlug, getFooterPageContent } from '@/lib/footerContent' -import { type MarketplaceLanguage } from '@/lib/i18n' +import { type StorefrontLanguage } from '@/lib/i18n' const pageMeta = { en: { @@ -53,8 +53,8 @@ function renderParagraphText(paragraph: string) { }) } -export default function FooterContentPage({ slug, forcedLanguage }: { slug: FooterPageSlug; forcedLanguage?: MarketplaceLanguage }) { - const { language } = useMarketplacePreferences() +export default function FooterContentPage({ slug, forcedLanguage }: { slug: FooterPageSlug; forcedLanguage?: StorefrontLanguage }) { + const { language } = useStorefrontPreferences() const activeLanguage = forcedLanguage ?? language const content = getFooterPageContent(activeLanguage, slug) const meta = pageMeta[activeLanguage] diff --git a/apps/storefront/src/components/RenterShell.tsx b/apps/storefront/src/components/RenterShell.tsx index af6346d..5ee530b 100644 --- a/apps/storefront/src/components/RenterShell.tsx +++ b/apps/storefront/src/components/RenterShell.tsx @@ -13,7 +13,7 @@ import { Car, } from 'lucide-react' import { useEffect, useState } from 'react' -import { useMarketplacePreferences } from '@/components/MarketplaceShell' +import { useStorefrontPreferences } from '@/components/StorefrontShell' import { clearRenterSession, loadRenterProfile } from '@/lib/renter' const NAV_ITEMS = [ @@ -63,7 +63,7 @@ function computeInitials(name: string): string { export default function RenterShell({ children }: { children: React.ReactNode }) { const pathname = usePathname() const router = useRouter() - const { language } = useMarketplacePreferences() + const { language } = useStorefrontPreferences() const dict = dicts[language] const isRtl = language === 'ar' const [open, setOpen] = useState(false) diff --git a/storefront/src/components/MarketplaceFooter.tsx b/apps/storefront/src/components/StorefrontFooter.tsx similarity index 99% rename from storefront/src/components/MarketplaceFooter.tsx rename to apps/storefront/src/components/StorefrontFooter.tsx index b6e1090..0734b11 100644 --- a/storefront/src/components/MarketplaceFooter.tsx +++ b/apps/storefront/src/components/StorefrontFooter.tsx @@ -15,7 +15,7 @@ type FooterItem = { href?: string } -export default function MarketplaceFooter({ +export default function StorefrontFooter({ primaryItems, secondaryItems, localeLabel, diff --git a/storefront/src/components/MarketplaceHeader.boundary.test.ts b/apps/storefront/src/components/StorefrontHeader.boundary.test.ts similarity index 94% rename from storefront/src/components/MarketplaceHeader.boundary.test.ts rename to apps/storefront/src/components/StorefrontHeader.boundary.test.ts index 7be497b..0b2dcb1 100644 --- a/storefront/src/components/MarketplaceHeader.boundary.test.ts +++ b/apps/storefront/src/components/StorefrontHeader.boundary.test.ts @@ -4,9 +4,9 @@ import { companyInitial, localeMenuPositionClass, ownerWorkspaceHref, -} from './MarketplaceHeader' +} from './StorefrontHeader' -describe('MarketplaceHeader helpers', () => { +describe('StorefrontHeader helpers', () => { it('builds dashboard sign-in links with language and theme query parameters', () => { expect(buildDashboardSignInHref('https://app.example.com/dashboard', 'fr', 'dark')).toBe( 'https://app.example.com/dashboard/sign-in?lang=fr&theme=dark' diff --git a/apps/storefront/src/components/MarketplaceHeader.tsx b/apps/storefront/src/components/StorefrontHeader.tsx similarity index 99% rename from apps/storefront/src/components/MarketplaceHeader.tsx rename to apps/storefront/src/components/StorefrontHeader.tsx index 1dfc4a7..5d48ec8 100644 --- a/apps/storefront/src/components/MarketplaceHeader.tsx +++ b/apps/storefront/src/components/StorefrontHeader.tsx @@ -9,7 +9,7 @@ export type Theme = 'light' | 'dark' export type Language = 'en' | 'fr' | 'ar' type Dictionary = { - marketplace: string + storefront: string explore: string signIn: string ownerSignIn: string @@ -44,7 +44,7 @@ export function companyInitial(companyName: string): string { return companyName.trim().charAt(0).toUpperCase() } -export default function MarketplaceHeader({ +export default function StorefrontHeader({ dict, theme, setTheme, @@ -124,7 +124,7 @@ export default function MarketplaceHeader({ href="/" className="rounded-full px-2.5 py-1 text-[12px] font-medium text-stone-600 transition hover:bg-stone-100 hover:text-stone-900 dark:text-stone-300 dark:hover:bg-blue-900/40 dark:hover:text-stone-100 sm:px-4 sm:py-2 sm:text-sm" > - {dict.marketplace} + {dict.storefront} { vi.unstubAllGlobals() }) -describe('MarketplaceShell auth helpers', () => { +describe('StorefrontShell auth helpers', () => { it('does not report an employee profile when the browser cache is empty', () => { vi.stubGlobal('window', { localStorage: { diff --git a/storefront/src/components/MarketplaceShell.content.test.ts b/apps/storefront/src/components/StorefrontShell.content.test.ts similarity index 88% rename from storefront/src/components/MarketplaceShell.content.test.ts rename to apps/storefront/src/components/StorefrontShell.content.test.ts index f597533..59b2db6 100644 --- a/storefront/src/components/MarketplaceShell.content.test.ts +++ b/apps/storefront/src/components/StorefrontShell.content.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest' -import { getFooterContent, localeOptions } from './MarketplaceShell' +import { getFooterContent, localeOptions } from './StorefrontShell' -describe('MarketplaceShell footer content registry', () => { - it('exposes exactly the supported marketplace locales in selector order', () => { +describe('StorefrontShell footer content registry', () => { + it('exposes exactly the supported storefront locales in selector order', () => { expect(localeOptions.map((option) => option.value)).toEqual(['en', 'ar', 'fr']) expect(localeOptions.every((option) => option.label.length > 0 && option.flag.length > 0)).toBe(true) }) diff --git a/storefront/src/components/MarketplaceShell.tsx b/apps/storefront/src/components/StorefrontShell.tsx similarity index 83% rename from storefront/src/components/MarketplaceShell.tsx rename to apps/storefront/src/components/StorefrontShell.tsx index ce66e84..c173f9f 100644 --- a/storefront/src/components/MarketplaceShell.tsx +++ b/apps/storefront/src/components/StorefrontShell.tsx @@ -3,13 +3,13 @@ import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react' import { usePathname, useRouter } from 'next/navigation' import { appPrivacyHref, footerPageHref } from '@/lib/footerContent' -import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE, isMarketplaceLanguage, type MarketplaceLanguage } from '@/lib/i18n' +import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE, isStorefrontLanguage, type StorefrontLanguage } from '@/lib/i18n' import { SHARED_LANGUAGE_KEY, SHARED_THEME_KEY, readCurrentUserScopedPreference, readScopedPreference, writeScopedPreference } from '@/lib/preferences' type Theme = 'light' | 'dark' type Dictionary = { - marketplace: string + storefront: string explore: string signIn: string ownerSignIn: string @@ -20,31 +20,31 @@ type Dictionary = { preferences: string } -const dictionaries: Record = { +const dictionaries: Record = { en: { - marketplace: 'Home', - explore: 'Marketplace', + storefront: 'Home', + explore: 'Storefront', signIn: 'Sign in', ownerSignIn: 'Create Agency Space', language: 'Language', theme: 'Theme', light: 'Light', dark: 'Dark', - preferences: 'Marketplace preferences', + preferences: 'Storefront preferences', }, fr: { - marketplace: 'Accueil', - explore: 'Marketplace', + storefront: 'Accueil', + explore: 'Storefront', signIn: 'Connexion', ownerSignIn: "Créer un espace d'agence", language: 'Langue', theme: 'Mode', light: 'Clair', dark: 'Sombre', - preferences: 'Préférences marketplace', + preferences: 'Préférences storefront', }, ar: { - marketplace: 'الرئيسية', + storefront: 'الرئيسية', explore: 'السوق', signIn: 'تسجيل الدخول', ownerSignIn: 'إنشاء مساحة الوكالة', @@ -58,13 +58,13 @@ const dictionaries: Record = { const EMPLOYEE_PROFILE_KEY = 'employee_profile' -export const localeOptions: Array<{ value: MarketplaceLanguage; label: string; flag: string }> = [ +export const localeOptions: Array<{ value: StorefrontLanguage; label: string; flag: string }> = [ { value: 'en', label: 'Global (English)', flag: '🇺🇸' }, { value: 'ar', label: 'North Africa (Arabic)', flag: '🇲🇦' }, { value: 'fr', label: 'Europe (French)', flag: '🇫🇷' }, ] -export function getFooterContent(language: MarketplaceLanguage): { +export function getFooterContent(language: StorefrontLanguage): { primary: Array<{ label: string; href?: string }> secondary: Array<{ label: string; href?: string }> localeLabel: string @@ -158,50 +158,50 @@ function detectBrowserLanguage(): string | null { } type PreferencesContextValue = { - language: MarketplaceLanguage + language: StorefrontLanguage theme: Theme dict: Dictionary companyName: string | null - setLanguage: (language: MarketplaceLanguage) => void + setLanguage: (language: StorefrontLanguage) => void setTheme: (theme: Theme) => void } const PreferencesContext = createContext(null) -export function useMarketplacePreferences() { +export function useStorefrontPreferences() { const context = useContext(PreferencesContext) - if (!context) throw new Error('useMarketplacePreferences must be used within MarketplaceShell') + if (!context) throw new Error('useStorefrontPreferences must be used within StorefrontShell') return context } -export default function MarketplaceShell({ +export default function StorefrontShell({ children, initialLanguage = 'en', initialTheme = 'light', }: { children: React.ReactNode - initialLanguage?: MarketplaceLanguage + initialLanguage?: StorefrontLanguage initialTheme?: Theme }) { const router = useRouter() const pathname = usePathname() const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1' - const [language, setLanguageState] = useState(initialLanguage) + const [language, setLanguageState] = useState(initialLanguage) const [theme, setThemeState] = useState(initialTheme) const [hydrated, setHydrated] = useState(false) - const previousLanguage = useRef(initialLanguage) + const previousLanguage = useRef(initialLanguage) const [companyName, setCompanyName] = useState(null) - function applyLanguage(nextLanguage: MarketplaceLanguage) { + function applyLanguage(nextLanguage: StorefrontLanguage) { if (nextLanguage === language) return if (typeof window !== 'undefined') { document.documentElement.lang = nextLanguage document.documentElement.dir = nextLanguage === 'ar' ? 'rtl' : 'ltr' try { - sessionStorage.setItem('marketplace-language', nextLanguage) + sessionStorage.setItem('storefront-language', nextLanguage) } catch {} - writeScopedPreference(SHARED_LANGUAGE_KEY, nextLanguage, ['marketplace-language']) + writeScopedPreference(SHARED_LANGUAGE_KEY, nextLanguage, ['storefront-language']) } setLanguageState(nextLanguage) @@ -214,7 +214,7 @@ export default function MarketplaceShell({ document.documentElement.classList.toggle('dark', nextTheme === 'dark') document.documentElement.style.colorScheme = nextTheme === 'light' ? 'light' : 'dark' document.body.dataset.theme = nextTheme - writeScopedPreference(SHARED_THEME_KEY, nextTheme, ['marketplace-theme']) + writeScopedPreference(SHARED_THEME_KEY, nextTheme, ['storefront-theme']) } setThemeState(nextTheme) @@ -247,10 +247,10 @@ export default function MarketplaceShell({ } } - function readSessionLanguage(): MarketplaceLanguage | null { + function readSessionLanguage(): StorefrontLanguage | null { try { - const val = sessionStorage.getItem('marketplace-language') - return isMarketplaceLanguage(val) ? val : null + const val = sessionStorage.getItem('storefront-language') + return isStorefrontLanguage(val) ? val : null } catch { return null } @@ -262,7 +262,7 @@ export default function MarketplaceShell({ if (sessionLang !== language) setLanguageState(sessionLang) } else { const scopedLanguage = readCurrentUserScopedPreference(SHARED_LANGUAGE_KEY) - if (isMarketplaceLanguage(scopedLanguage) && scopedLanguage !== language) { + if (isStorefrontLanguage(scopedLanguage) && scopedLanguage !== language) { setLanguageState(scopedLanguage) } } @@ -271,7 +271,7 @@ export default function MarketplaceShell({ if ((scopedTheme === 'light' || scopedTheme === 'dark') && scopedTheme !== theme) { setThemeState(scopedTheme) } else { - writeScopedPreference(SHARED_THEME_KEY, theme, ['marketplace-theme']) + writeScopedPreference(SHARED_THEME_KEY, theme, ['storefront-theme']) } } @@ -279,22 +279,22 @@ export default function MarketplaceShell({ const sessionLang = readSessionLanguage() if (sessionLang) { setLanguageState(sessionLang) - writeScopedPreference(SHARED_LANGUAGE_KEY, sessionLang, ['marketplace-language']) + writeScopedPreference(SHARED_LANGUAGE_KEY, sessionLang, ['storefront-language']) } else { - const storedLanguage = readScopedPreference(SHARED_LANGUAGE_KEY, ['marketplace-language']) - if (isMarketplaceLanguage(storedLanguage)) { + const storedLanguage = readScopedPreference(SHARED_LANGUAGE_KEY, ['storefront-language']) + if (isStorefrontLanguage(storedLanguage)) { setLanguageState(storedLanguage) - writeScopedPreference(SHARED_LANGUAGE_KEY, storedLanguage, ['marketplace-language']) + writeScopedPreference(SHARED_LANGUAGE_KEY, storedLanguage, ['storefront-language']) } else { const detected = detectBrowserLanguage() - if (isMarketplaceLanguage(detected)) { + if (isStorefrontLanguage(detected)) { setLanguageState(detected) - writeScopedPreference(SHARED_LANGUAGE_KEY, detected, ['marketplace-language']) + writeScopedPreference(SHARED_LANGUAGE_KEY, detected, ['storefront-language']) } } } - const storedTheme = readScopedPreference(SHARED_THEME_KEY, ['marketplace-theme']) as Theme | null + const storedTheme = readScopedPreference(SHARED_THEME_KEY, ['storefront-theme']) as Theme | null if (storedTheme === 'light' || storedTheme === 'dark') { setThemeState(storedTheme) } @@ -337,8 +337,8 @@ export default function MarketplaceShell({ if (!hydrated) return - try { sessionStorage.setItem('marketplace-language', language) } catch {} - writeScopedPreference(SHARED_LANGUAGE_KEY, language, ['marketplace-language']) + try { sessionStorage.setItem('storefront-language', language) } catch {} + writeScopedPreference(SHARED_LANGUAGE_KEY, language, ['storefront-language']) if (previousLanguage.current !== language && pathname !== '/sign-in') { router.refresh() @@ -351,7 +351,7 @@ export default function MarketplaceShell({ document.documentElement.style.colorScheme = theme === 'dark' ? 'dark' : 'light' document.body.dataset.theme = theme if (hydrated) { - writeScopedPreference(SHARED_THEME_KEY, theme, ['marketplace-theme']) + writeScopedPreference(SHARED_THEME_KEY, theme, ['storefront-theme']) } }, [theme, hydrated]) diff --git a/apps/storefront/src/components/WorkspaceFrame.boundary.test.ts b/apps/storefront/src/components/WorkspaceFrame.boundary.test.ts index 4ebe240..76e84d7 100644 --- a/apps/storefront/src/components/WorkspaceFrame.boundary.test.ts +++ b/apps/storefront/src/components/WorkspaceFrame.boundary.test.ts @@ -29,7 +29,7 @@ describe('WorkspaceFrame helpers', () => { }) it('keeps unauthenticated visitors on the embedded sign-in path', () => { - stubBrowser({ cookie: 'marketplace-language=fr' }) + stubBrowser({ cookie: 'storefront-language=fr' }) expect(getDefaultFramePath('sign-in')).toBe('/dashboard/sign-in') }) diff --git a/apps/storefront/src/components/WorkspaceFrame.tsx b/apps/storefront/src/components/WorkspaceFrame.tsx index 7a9f7d4..1d46fdb 100644 --- a/apps/storefront/src/components/WorkspaceFrame.tsx +++ b/apps/storefront/src/components/WorkspaceFrame.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { resolveBrowserAppUrl } from '@/lib/appUrls' -import { useMarketplacePreferences } from './MarketplaceShell' +import { useStorefrontPreferences } from './StorefrontShell' export type FrameId = 'sign-in' @@ -37,7 +37,7 @@ export function buildFrameUrl(appUrl: string, path: string) { export const FRAME_HEIGHT = 'calc(100vh - 56px)' export default function WorkspaceFrame({ target }: { target: FrameId }) { - const { language, theme } = useMarketplacePreferences() + const { language, theme } = useStorefrontPreferences() const [src, setSrc] = useState('') const [framePath, setFramePath] = useState(() => getDefaultFramePath(target)) const config = frameConfig[target] diff --git a/apps/storefront/src/lib/api.test.ts b/apps/storefront/src/lib/api.test.ts index 20b94c1..ce0b0ae 100644 --- a/apps/storefront/src/lib/api.test.ts +++ b/apps/storefront/src/lib/api.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { MarketplaceApiError, marketplaceFetch, marketplaceFetchOrDefault, marketplacePost } from './api' +import { StorefrontApiError, storefrontFetch, storefrontFetchOrDefault, storefrontPost } from './api' afterEach(() => { delete process.env.NEXT_PUBLIC_API_URL @@ -8,19 +8,19 @@ afterEach(() => { Reflect.deleteProperty(globalThis, 'fetch') }) -describe('marketplace API helpers', () => { +describe('storefront API helpers', () => { it('unwraps successful GET responses from the data envelope', async () => { const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: [{ id: 'vehicle_1' }] }) })) Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock }) - await expect(marketplaceFetch('/marketplace/vehicles')).resolves.toEqual([{ id: 'vehicle_1' }]) - expect(fetchMock).toHaveBeenCalledWith(expect.stringMatching(/\/api\/v1\/marketplace\/vehicles$/), { + await expect(storefrontFetch('/storefront/vehicles')).resolves.toEqual([{ id: 'vehicle_1' }]) + expect(fetchMock).toHaveBeenCalledWith(expect.stringMatching(/\/api\/v1\/storefront\/vehicles$/), { cache: 'no-store', credentials: 'include', }) }) - it('throws rich marketplace API errors', async () => { + it('throws rich storefront API errors', async () => { Object.defineProperty(globalThis, 'fetch', { configurable: true, value: vi.fn(async () => ({ @@ -30,8 +30,8 @@ describe('marketplace API helpers', () => { })), }) - await expect(marketplaceFetch('/marketplace/book')).rejects.toMatchObject({ - name: 'MarketplaceApiError', + await expect(storefrontFetch('/storefront/book')).rejects.toMatchObject({ + name: 'StorefrontApiError', message: 'Vehicle unavailable', status: 409, code: 'VEHICLE_UNAVAILABLE', @@ -45,7 +45,7 @@ describe('marketplace API helpers', () => { value: vi.fn(async () => ({ ok: false, status: 500, json: async () => ({ message: 'Down' }) })), }) - await expect(marketplaceFetchOrDefault('/marketplace/homepage', { hero: null })).resolves.toEqual({ hero: null }) + await expect(storefrontFetchOrDefault('/storefront/homepage', { hero: null })).resolves.toEqual({ hero: null }) }) it('posts JSON payloads and unwraps successful responses', async () => { @@ -53,7 +53,7 @@ describe('marketplace API helpers', () => { const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: { id: 'reservation_1' } }) })) Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock }) - await expect(marketplacePost('/site/reservations', { vehicleId: 'vehicle_1' })).resolves.toEqual({ id: 'reservation_1' }) + await expect(storefrontPost('/site/reservations', { vehicleId: 'vehicle_1' })).resolves.toEqual({ id: 'reservation_1' }) expect(fetchMock).toHaveBeenCalledWith('https://api.example.com/api/v1/site/reservations', expect.objectContaining({ method: 'POST', credentials: 'include', @@ -68,8 +68,8 @@ describe('marketplace API helpers', () => { value: vi.fn(async () => ({ ok: false, status: 502, json: async () => { throw new Error('bad json') } })), }) - await expect(marketplaceFetch('/site/homepage')).rejects.toMatchObject({ - name: 'MarketplaceApiError', + await expect(storefrontFetch('/site/homepage')).rejects.toMatchObject({ + name: 'StorefrontApiError', message: 'Request failed', status: 502, }) diff --git a/apps/storefront/src/lib/api.ts b/apps/storefront/src/lib/api.ts index b5d9118..c2ac328 100644 --- a/apps/storefront/src/lib/api.ts +++ b/apps/storefront/src/lib/api.ts @@ -3,40 +3,40 @@ const API_BASE = ?? process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1' -export class MarketplaceApiError extends Error { +export class StorefrontApiError extends Error { status: number code?: string nextAvailableAt?: string | null constructor(message: string, status: number, code?: string, nextAvailableAt?: string | null) { super(message) - this.name = 'MarketplaceApiError' + this.name = 'StorefrontApiError' this.status = status this.code = code this.nextAvailableAt = nextAvailableAt } } -export async function marketplaceFetch(path: string, init?: RequestInit): Promise { +export async function storefrontFetch(path: string, init?: RequestInit): Promise { const res = await fetch(`${API_BASE}${path}`, { cache: 'no-store', credentials: 'include', ...init, }) const json = await res.json().catch(() => null) - if (!res.ok) throw new MarketplaceApiError(json?.message ?? 'Request failed', res.status, json?.error, json?.nextAvailableAt) + if (!res.ok) throw new StorefrontApiError(json?.message ?? 'Request failed', res.status, json?.error, json?.nextAvailableAt) return json.data as T } -export async function marketplaceFetchOrDefault(path: string, fallback: T): Promise { +export async function storefrontFetchOrDefault(path: string, fallback: T): Promise { try { - return await marketplaceFetch(path) + return await storefrontFetch(path) } catch { return fallback } } -export async function marketplacePost(path: string, body: unknown): Promise { +export async function storefrontPost(path: string, body: unknown): Promise { const base = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1' const res = await fetch(`${base}${path}`, { method: 'POST', @@ -45,6 +45,6 @@ export async function marketplacePost(path: string, body: unknown): Promise null) - if (!res.ok) throw new MarketplaceApiError(json?.message ?? 'Request failed', res.status, json?.error, json?.nextAvailableAt) + if (!res.ok) throw new StorefrontApiError(json?.message ?? 'Request failed', res.status, json?.error, json?.nextAvailableAt) return json.data as T } diff --git a/apps/storefront/src/lib/appUrls.test.ts b/apps/storefront/src/lib/appUrls.test.ts index 6f7911f..fb739d5 100644 --- a/apps/storefront/src/lib/appUrls.test.ts +++ b/apps/storefront/src/lib/appUrls.test.ts @@ -12,7 +12,7 @@ afterEach(() => { Reflect.deleteProperty(globalThis, 'window') }) -describe('marketplace app URL resolution', () => { +describe('storefront app URL resolution', () => { it('leaves server-side browser fallback unchanged when window is unavailable', () => { expect(resolveBrowserAppUrl('http://localhost:3000')).toBe('http://localhost:3000') }) @@ -29,7 +29,7 @@ describe('marketplace app URL resolution', () => { it('keeps path prefixes while rewriting the browser origin', () => { installWindow('rental.example.com', 'https:') - expect(resolveBrowserAppUrl('http://localhost:3000/marketplace')).toBe('https://rental.example.com/marketplace') + expect(resolveBrowserAppUrl('http://localhost:3000/storefront')).toBe('https://rental.example.com/storefront') }) it('resolves server URLs from forwarded host/proto and handles invalid fallbacks safely', () => { diff --git a/apps/storefront/src/lib/footerContent.test.ts b/apps/storefront/src/lib/footerContent.test.ts index b6ed7a0..21ca33d 100644 --- a/apps/storefront/src/lib/footerContent.test.ts +++ b/apps/storefront/src/lib/footerContent.test.ts @@ -7,11 +7,11 @@ import { getFooterPageContent, isFooterPageSlug, } from './footerContent' -import type { MarketplaceLanguage } from './i18n' +import type { StorefrontLanguage } from './i18n' -const languages: MarketplaceLanguage[] = ['en', 'fr', 'ar'] +const languages: StorefrontLanguage[] = ['en', 'fr', 'ar'] -describe('marketplace footer content registry', () => { +describe('storefront footer content registry', () => { it('keeps every declared slug routable and recognizable', () => { expect(footerPageSlugs.length).toBeGreaterThan(5) diff --git a/apps/storefront/src/lib/footerContent.ts b/apps/storefront/src/lib/footerContent.ts index 40c1cf7..6cf85ad 100644 --- a/apps/storefront/src/lib/footerContent.ts +++ b/apps/storefront/src/lib/footerContent.ts @@ -1,4 +1,4 @@ -import type { MarketplaceLanguage } from './i18n' +import type { StorefrontLanguage } from './i18n' export const footerPageSlugs = [ 'about-us', @@ -47,7 +47,7 @@ export const footerPageHref: Record = { 'general-conditions': '/footer/general-conditions', } -const footerContent: Record> = { +const footerContent: Record> = { en: { 'about-us': { title: 'About Us', @@ -175,7 +175,7 @@ const footerContent: Record ({ }), })) -import { getMarketplaceLanguage } from './i18n.server' +import { getStorefrontLanguage } from './i18n.server' import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE } from './i18n' -describe('getMarketplaceLanguage', () => { +describe('getStorefrontLanguage', () => { beforeEach(() => { cookieValues.clear() }) - it('prefers the shared language cookie over the legacy marketplace cookie', async () => { + it('prefers the shared language cookie over the legacy storefront cookie', async () => { cookieValues.set(SHARED_LANGUAGE_COOKIE, 'ar') cookieValues.set(MARKETPLACE_LANGUAGE_COOKIE, 'fr') - await expect(getMarketplaceLanguage()).resolves.toBe('ar') + await expect(getStorefrontLanguage()).resolves.toBe('ar') }) - it('falls back to the legacy marketplace cookie during migration', async () => { + it('falls back to the legacy storefront cookie during migration', async () => { cookieValues.set(MARKETPLACE_LANGUAGE_COOKIE, 'fr') - await expect(getMarketplaceLanguage()).resolves.toBe('fr') + await expect(getStorefrontLanguage()).resolves.toBe('fr') }) it('defaults to English when cookies are absent or invalid', async () => { - await expect(getMarketplaceLanguage()).resolves.toBe('en') + await expect(getStorefrontLanguage()).resolves.toBe('en') cookieValues.set(SHARED_LANGUAGE_COOKIE, 'es') - await expect(getMarketplaceLanguage()).resolves.toBe('en') + await expect(getStorefrontLanguage()).resolves.toBe('en') }) }) diff --git a/apps/storefront/src/lib/i18n.server.ts b/apps/storefront/src/lib/i18n.server.ts index 62ee928..9d5f960 100644 --- a/apps/storefront/src/lib/i18n.server.ts +++ b/apps/storefront/src/lib/i18n.server.ts @@ -1,10 +1,10 @@ import { cookies } from 'next/headers' -import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE, isMarketplaceLanguage, type MarketplaceLanguage } from './i18n' +import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE, isStorefrontLanguage, type StorefrontLanguage } from './i18n' -export async function getMarketplaceLanguage(): Promise { +export async function getStorefrontLanguage(): Promise { const cookieStore = await cookies() const cookieValue = cookieStore.get(SHARED_LANGUAGE_COOKIE)?.value ?? cookieStore.get(MARKETPLACE_LANGUAGE_COOKIE)?.value - return isMarketplaceLanguage(cookieValue) ? cookieValue : 'en' + return isStorefrontLanguage(cookieValue) ? cookieValue : 'en' } diff --git a/apps/storefront/src/lib/i18n.test.ts b/apps/storefront/src/lib/i18n.test.ts index eae404c..383b255 100644 --- a/apps/storefront/src/lib/i18n.test.ts +++ b/apps/storefront/src/lib/i18n.test.ts @@ -1,18 +1,18 @@ import { describe, expect, it } from 'vitest' -import { isMarketplaceLanguage } from './i18n' +import { isStorefrontLanguage } from './i18n' -describe('marketplace language guard', () => { - it('accepts the supported marketplace locales', () => { - expect(isMarketplaceLanguage('en')).toBe(true) - expect(isMarketplaceLanguage('fr')).toBe(true) - expect(isMarketplaceLanguage('ar')).toBe(true) +describe('storefront language guard', () => { + it('accepts the supported storefront locales', () => { + expect(isStorefrontLanguage('en')).toBe(true) + expect(isStorefrontLanguage('fr')).toBe(true) + expect(isStorefrontLanguage('ar')).toBe(true) }) it('rejects unsupported, missing, and case-mismatched locales', () => { - expect(isMarketplaceLanguage('de')).toBe(false) - expect(isMarketplaceLanguage('EN')).toBe(false) - expect(isMarketplaceLanguage('')).toBe(false) - expect(isMarketplaceLanguage(null)).toBe(false) - expect(isMarketplaceLanguage(undefined)).toBe(false) + expect(isStorefrontLanguage('de')).toBe(false) + expect(isStorefrontLanguage('EN')).toBe(false) + expect(isStorefrontLanguage('')).toBe(false) + expect(isStorefrontLanguage(null)).toBe(false) + expect(isStorefrontLanguage(undefined)).toBe(false) }) }) diff --git a/apps/storefront/src/lib/i18n.ts b/apps/storefront/src/lib/i18n.ts index fff6162..88e56fd 100644 --- a/apps/storefront/src/lib/i18n.ts +++ b/apps/storefront/src/lib/i18n.ts @@ -1,8 +1,8 @@ -export type MarketplaceLanguage = 'en' | 'fr' | 'ar' +export type StorefrontLanguage = 'en' | 'fr' | 'ar' -export const MARKETPLACE_LANGUAGE_COOKIE = 'marketplace-language' +export const MARKETPLACE_LANGUAGE_COOKIE = 'storefront-language' export const SHARED_LANGUAGE_COOKIE = 'rentaldrivego-language' -export function isMarketplaceLanguage(value: string | null | undefined): value is MarketplaceLanguage { +export function isStorefrontLanguage(value: string | null | undefined): value is StorefrontLanguage { return value === 'en' || value === 'fr' || value === 'ar' } diff --git a/apps/storefront/src/lib/preferences.test.ts b/apps/storefront/src/lib/preferences.test.ts index 57cd3cd..f6b8946 100644 --- a/apps/storefront/src/lib/preferences.test.ts +++ b/apps/storefront/src/lib/preferences.test.ts @@ -43,7 +43,7 @@ afterEach(() => { Reflect.deleteProperty(globalThis, 'atob') }) -describe('marketplace scoped preferences', () => { +describe('storefront scoped preferences', () => { it('returns unscoped keys when no usable token exists', () => { installBrowser() expect(getScopedPreferenceKey('rentaldrivego-language')).toBe('rentaldrivego-language') @@ -52,9 +52,9 @@ describe('marketplace scoped preferences', () => { it('reads shared cookies before legacy local-storage fallbacks', () => { const browser = installBrowser('rentaldrivego-language=fr') - browser.store.set('marketplace-language', 'ar') + browser.store.set('storefront-language', 'ar') - expect(readScopedPreference('rentaldrivego-language', ['marketplace-language'])).toBe('fr') + expect(readScopedPreference('rentaldrivego-language', ['storefront-language'])).toBe('fr') }) it('writes shared and legacy values without auth-token scoping', () => { diff --git a/apps/storefront/src/middleware.test.ts b/apps/storefront/src/middleware.test.ts index 56fd74f..52732df 100644 --- a/apps/storefront/src/middleware.test.ts +++ b/apps/storefront/src/middleware.test.ts @@ -63,7 +63,7 @@ beforeEach(() => { nextServer.redirect.mockClear() }) -describe('marketplace proxy language bootstrap', () => { +describe('storefront proxy language bootstrap', () => { it('rejects proxy subrequest headers before language handling', async () => { const { proxy } = await import('./proxy') @@ -85,11 +85,11 @@ describe('marketplace proxy language bootstrap', () => { expect(response.cookies.set).not.toHaveBeenCalled() }) - it('migrates the legacy marketplace language cookie into the shared cookie and request headers', async () => { + it('migrates the legacy storefront language cookie into the shared cookie and request headers', async () => { const { proxy } = await import('./proxy') const response = proxy(request({ - cookies: { 'marketplace-language': 'ar' }, + cookies: { 'storefront-language': 'ar' }, headers: { cookie: 'session=abc' }, }) as never) as any @@ -118,7 +118,7 @@ describe('marketplace proxy language bootstrap', () => { const { proxy } = await import('./proxy') const response = proxy(request({ - cookies: { 'rentaldrivego-language': 'es', 'marketplace-language': 'it' }, + cookies: { 'rentaldrivego-language': 'es', 'storefront-language': 'it' }, headers: { 'accept-language': 'fr-CA,ar;q=0.5' }, }) as never) as any @@ -127,7 +127,7 @@ describe('marketplace proxy language bootstrap', () => { }) }) -describe('marketplace middleware dashboard path canonicalization', () => { +describe('storefront middleware dashboard path canonicalization', () => { it('redirects duplicate dashboard prefixes to the clean dashboard URL', async () => { const { proxy } = await import('./proxy') diff --git a/apps/storefront/src/proxy.ts b/apps/storefront/src/proxy.ts index 075525a..3e4a556 100644 --- a/apps/storefront/src/proxy.ts +++ b/apps/storefront/src/proxy.ts @@ -2,7 +2,7 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' const SHARED_LANGUAGE_COOKIE = 'rentaldrivego-language' -const MARKETPLACE_LANGUAGE_COOKIE = 'marketplace-language' +const MARKETPLACE_LANGUAGE_COOKIE = 'storefront-language' const DASHBOARD_BASE_PATH = '/dashboard' function rejectInternalSubrequest(request: NextRequest): NextResponse | null { diff --git a/docs/CAR_BOOKING_PROCESS_IMPROVEMENT_PLAN.md b/docs/CAR_BOOKING_PROCESS_IMPROVEMENT_PLAN.md index fc64561..49819da 100644 --- a/docs/CAR_BOOKING_PROCESS_IMPROVEMENT_PLAN.md +++ b/docs/CAR_BOOKING_PROCESS_IMPROVEMENT_PLAN.md @@ -28,7 +28,7 @@ This reduces cognitive load, reduces privacy anxiety, and makes mobile completio Replace the current booking form with the revised version in: -`apps/marketplace/src/components/BookingForm.tsx` +`apps/storefront/src/components/BookingForm.tsx` Acceptance criteria: @@ -50,7 +50,7 @@ Recommended behavior: Backend candidate: -- Add or expose `GET /marketplace/:slug/vehicles/:id/availability?startDate=&endDate=`. +- Add or expose `GET /storefront/:slug/vehicles/:id/availability?startDate=&endDate=`. - Reuse the existing `getVehicleAvailabilitySummary` service instead of inventing yet another inconsistent truth source, because apparently systems enjoy lying to themselves. ### Phase 3: Booking Status Transparency diff --git a/docs/DOCKER.md b/docs/DOCKER.md index 6dc10f0..ae0187c 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -20,7 +20,7 @@ docker compose -f docker-compose.dev.yml --profile full up --build Services: -- marketplace: `http://localhost:3000` +- storefront: `http://localhost:3000` - dashboard: `http://localhost:3000/dashboard` - admin: `http://localhost:3000/admin` - public-site: `http://localhost:3003` @@ -31,12 +31,12 @@ Each dev app now runs in its own container and can be started independently with ```bash docker compose -f docker-compose.dev.yml --profile api up --build -docker compose -f docker-compose.dev.yml --profile marketplace up --build +docker compose -f docker-compose.dev.yml --profile storefront up --build docker compose -f docker-compose.dev.yml --profile dashboard up --build docker compose -f docker-compose.dev.yml --profile admin up --build docker compose -f docker-compose.dev.yml --profile public-site up --build docker compose -f docker-compose.dev.yml --profile tools up --build -docker compose -f docker-compose.dev.yml --profile api --profile marketplace --profile dashboard --profile admin up --build +docker compose -f docker-compose.dev.yml --profile api --profile storefront --profile dashboard --profile admin up --build ``` Notes: @@ -121,7 +121,7 @@ Add an A record for every subdomain to your server's public IP before deploying | Subdomain | Service | |---|---| -| `rentaldrivego.ma` | marketplace and public site | +| `rentaldrivego.ma` | storefront and public site | | `api.rentaldrivego.ma` | API | | `pgmanage.rentaldrivego.ma` | pgManage (DB admin) | | `portainer.rentaldrivego.ma` | Portainer | @@ -163,7 +163,7 @@ Open `.env.docker.production` and fill in every value. The minimum required secr Production now derives `DATABASE_URL` inside the app container from `POSTGRES_HOST`, `POSTGRES_PORT`, `POSTGRES_DB`, `POSTGRES_USER`, and `POSTGRES_PASSWORD` when `DATABASE_URL_FROM_POSTGRES=true`. That avoids Prisma auth failures when the database password contains reserved URL characters such as `@`, `:`, or `/`. -The example file uses `rentaldrivego.ma` for the marketplace and public site. The dashboard and admin panel are routed under that same host at `/dashboard` and `/admin`. +The example file uses `rentaldrivego.ma` for the storefront and public site. The dashboard and admin panel are routed under that same host at `/dashboard` and `/admin`. Set `PORTAINER_DOMAIN=portainer.rentaldrivego.ma` in `.env.docker.production` to expose Portainer through Traefik. Set `PGMANAGE_DOMAIN=pgmanage.rentaldrivego.ma` to expose pgManage through Traefik, and set `PGMANAGE_LICENSE_KEY` if you are running the enterprise image. Set `REGISTRY_DOMAIN=registry.rentaldrivego.ma` to expose the Docker registry through Traefik. @@ -255,7 +255,7 @@ npm run docker:prod:start:traefik npm run docker:prod:start:postgres npm run docker:prod:start:redis npm run docker:prod:start:api -npm run docker:prod:start:marketplace +npm run docker:prod:start:storefront npm run docker:prod:start:dashboard npm run docker:prod:start:admin npm run docker:prod:start:frontends @@ -268,7 +268,7 @@ npm run docker:prod:start:api && npm run docker:prod:start:frontends Docker will: 1. Build the monorepo image on the server -2. Start all app services (`api`, `marketplace`, `dashboard`, `admin`, `pgmanage`) +2. Start all app services (`api`, `storefront`, `dashboard`, `admin`, `pgmanage`) Traefik automatically picks up the containers and provisions TLS certificates. Services are live at their `https://` URLs within ~30 seconds. diff --git a/docs/README.md b/docs/README.md index 7d72de2..fc999b0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,10 @@ -# 🚗 RentalDriveGo— Multi-Tenant Car Rental SaaS + Marketplace +# 🚗 RentalDriveGo— Multi-Tenant Car Rental SaaS + Storefront ## Platform Model **Rental Companies (B2B)** — pay RentalDriveGoa subscription (AmanPay or PayPal), get: - A fully private dashboard (zero data overlap with any other company) -- Fleet management with vehicle photo upload (photos auto-appear on global marketplace) +- Fleet management with vehicle photo upload (photos auto-appear on global storefront) - Promotional offers management - A white-label site at `company.RentalDriveGo.com` where renters book and pay @@ -29,21 +29,21 @@ --- -## 🌐 Marketplace is Discovery Only — No Booking on RentalDriveGo.com/explore +## 🌐 Storefront is Discovery Only — No Booking on RentalDriveGo.com/explore -The global marketplace shows all vehicles with photos from all companies. When a renter clicks "Book this vehicle", they are **redirected to the company's branded site** (`{slug}.RentalDriveGo.com/book?vehicleId=X&from=Y&to=Z&ref=marketplace`). The booking form is pre-filled. All payment happens on the company site using the company's own payment accounts. +The global storefront shows all vehicles with photos from all companies. When a renter clicks "Book this vehicle", they are **redirected to the company's branded site** (`{slug}.RentalDriveGo.com/book?vehicleId=X&from=Y&to=Z&ref=storefront`). The booking form is pre-filled. All payment happens on the company site using the company's own payment accounts. --- -## 📸 Vehicle Photos → Marketplace Visibility +## 📸 Vehicle Photos → Storefront Visibility ``` Company uploads photos in /dashboard/fleet → Stored in Cloudinary → Vehicle.photos[] holds the URLs - → First photo = cover image on marketplace cards - → All photos shown in marketplace vehicle gallery - → isPublished=true makes vehicle appear on marketplace + → First photo = cover image on storefront cards + → All photos shown in storefront vehicle gallery + → isPublished=true makes vehicle appear on storefront ``` --- @@ -52,7 +52,7 @@ Company uploads photos in /dashboard/fleet ``` Layer 1A: RentalDriveGo.com — B2B marketing site -Layer 1B: RentalDriveGo.com/explore — B2C marketplace (discovery + redirect only) +Layer 1B: RentalDriveGo.com/explore — B2C storefront (discovery + redirect only) Layer 2: app.RentalDriveGo.com — Company dashboard (private, subscription-gated) Layer 3: {slug}.RentalDriveGo.com — Company branded site (booking + payment here) Layer 4: api.RentalDriveGo.com — REST API @@ -66,9 +66,9 @@ Layer 4: api.RentalDriveGo.com — REST API rental-car-site/ ├── README.md ├── docs/ -│ ├── ARCHITECTURE.md ← Payment providers, marketplace redirect model, photo flow +│ ├── ARCHITECTURE.md ← Payment providers, storefront redirect model, photo flow │ ├── DESIGN_SYSTEM.md -│ ├── PAGES.md ← All pages. Marketplace = discovery only. Booking = company site. +│ ├── PAGES.md ← All pages. Storefront = discovery only. Booking = company site. │ ├── FEATURES.md │ └── DEPLOYMENT.md └── skills/ @@ -80,7 +80,7 @@ rental-car-site/ │ ├── api-routes.md ← All routes │ ├── payment-service.md ← AmanPay + PayPal implementation ← KEY FILE │ ├── subscription-service.md ← Manual renewal, plan prices in MAD/USD/EUR - │ ├── subdomain-service.md ← Marketplace redirect + company site + │ ├── subdomain-service.md ← Storefront redirect + company site │ └── notification-service.md ← All 5 channels └── rental-car-i18n/ ``` diff --git a/docs/SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md b/docs/SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md index f641a28..a5a14bb 100644 --- a/docs/SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md +++ b/docs/SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md @@ -60,15 +60,15 @@ Changed files: - `apps/api/src/app.ts` - `apps/dashboard/src/middleware.ts` -- `apps/marketplace/src/middleware.ts` +- `apps/storefront/src/middleware.ts` - `apps/admin/src/middleware.ts` - `apps/dashboard/src/middleware.test.ts` -- `apps/marketplace/src/middleware.test.ts` +- `apps/storefront/src/middleware.test.ts` What changed: - API now rejects requests containing `x-middleware-subrequest` before route handling. -- Dashboard, marketplace, and admin Next middleware now reject the same header at the app layer. +- Dashboard, storefront, and admin Next middleware now reject the same header at the app layer. - Added/updated middleware tests for the rejection path. Security effect: diff --git a/docs/create-account-guide.md b/docs/create-account-guide.md index 4265bf4..7e23113 100644 --- a/docs/create-account-guide.md +++ b/docs/create-account-guide.md @@ -15,7 +15,7 @@ Creating an account registers a new rental company on the platform. The flow has ## 1. Accessing the Sign-up Page -### From the marketplace homepage (port 3000) +### From the storefront homepage (port 3000) - Click **"Create Agency Space"** in the header navigation. - Click **"Get Started"** on any pricing plan card (`/pricing` page). @@ -26,7 +26,7 @@ Both actions navigate to: http://localhost:3000/dashboard/sign-up ``` -The marketplace proxies this request to the dashboard app (port 3001), which renders the multi-step form. +The storefront proxies this request to the dashboard app (port 3001), which renders the multi-step form. ### URL parameters (optional) @@ -111,7 +111,7 @@ Select one of three plans: | Plan | Description | |---|---| | **STARTER** | Core fleet, offers, and bookings | -| **GROWTH** | Featured marketplace placement and more room to scale | +| **GROWTH** | Featured storefront placement and more room to scale | | **PRO** | Full white-label and premium controls | Additional options: @@ -213,7 +213,7 @@ Navigate to `/sign-in` (redirects to `/dashboard/sign-in`). Enter the owner emai After first sign-in, the user is directed to the **onboarding** page (`/onboarding`) where they can: 1. **Step 1 — Company profile**: Set display name, tagline, brand color, and public location. -2. **Step 2 — Payments**: Configure AmanPay merchant ID / PayPal email and marketplace listing preference. +2. **Step 2 — Payments**: Configure AmanPay merchant ID / PayPal email and storefront listing preference. 3. **Step 3 — Completion**: Redirect to the dashboard home. ### Subscription @@ -228,7 +228,7 @@ New accounts start on a **90-day free trial** with the selected plan. The subscr | File | Purpose | |---|---| -| `apps/marketplace/src/app/renter/sign-up/page.tsx` | Marketplace redirect page to dashboard sign-up | +| `apps/storefront/src/app/renter/sign-up/page.tsx` | Storefront redirect page to dashboard sign-up | | `apps/dashboard/src/app/sign-up/[[...sign-up]]/page.tsx` | Main multi-step sign-up form (4 steps, 937 lines) | | `apps/dashboard/src/components/ui/BilingualInput.tsx` | Bilingual (FR/AR) input component | | `apps/dashboard/src/components/layout/PublicShell.tsx` | Public layout wrapper for the sign-up page | @@ -241,10 +241,10 @@ New accounts start on a **90-day free trial** with the selected plan. The subscr | File | Location | |---|---| -| `apps/marketplace/src/components/MarketplaceHeader.tsx` | "Create Agency Space" button | -| `apps/marketplace/src/components/WorkspaceTabs.tsx` | "Owner sign in" link | -| `apps/marketplace/src/app/(public)/pricing/PricingClient.tsx` | "Get Started" pricing CTA | -| `apps/marketplace/src/app/(public)/HomeContent.tsx` | Homepage CTA | +| `apps/storefront/src/components/StorefrontHeader.tsx` | "Create Agency Space" button | +| `apps/storefront/src/components/WorkspaceTabs.tsx` | "Owner sign in" link | +| `apps/storefront/src/app/(public)/pricing/PricingClient.tsx` | "Get Started" pricing CTA | +| `apps/storefront/src/app/(public)/HomeContent.tsx` | Homepage CTA | ### Backend diff --git a/docs/dev_docker_run.md b/docs/dev_docker_run.md index eba9521..0665c4c 100644 --- a/docs/dev_docker_run.md +++ b/docs/dev_docker_run.md @@ -4,7 +4,7 @@ docker compose -f docker-compose.dev.yml up -d redis # app services docker compose -f docker-compose.dev.yml --profile api up --build -docker compose -f docker-compose.dev.yml --profile marketplace up --build +docker compose -f docker-compose.dev.yml --profile storefront up --build docker compose -f docker-compose.dev.yml --profile dashboard up --build docker compose -f docker-compose.dev.yml --profile admin up --build diff --git a/docs/frontend_redesign_plan.md b/docs/frontend_redesign_plan.md index 8fe77f7..5d2129d 100644 --- a/docs/frontend_redesign_plan.md +++ b/docs/frontend_redesign_plan.md @@ -44,7 +44,7 @@ ## 3. What to Redesign (Inspired by isysolution.com) -### 3.1 Marketplace — Public Pages +### 3.1 Storefront — Public Pages #### Home Page (`/`) Current issues to fix: @@ -70,7 +70,7 @@ Stats Strip How It Works ├── Step 1: Create your workspace ├── Step 2: Add your fleet - ├── Step 3: Go live on marketplace + ├── Step 3: Go live on storefront └── Step 4: Manage everything from dashboard Services / Features Grid @@ -154,8 +154,8 @@ Redesign: ## 4. Component Redesign Checklist ### Shared Components -- [ ] `MarketplaceHeader` — add mobile hamburger menu, improve nav spacing -- [ ] `MarketplaceFooter` — add logo, social links, newsletter input +- [ ] `StorefrontHeader` — add mobile hamburger menu, improve nav spacing +- [ ] `StorefrontFooter` — add logo, social links, newsletter input - [ ] `BookingForm` — improve step layout, add progress indicator - [ ] `WorkspaceTabs` — improve active tab styling @@ -187,7 +187,7 @@ Redesign: | Numbered step process | Home "How it works" | | Stats/counters strip | Home page | | Testimonials carousel | Home page | -| Mega footer with columns | Marketplace footer | +| Mega footer with columns | Storefront footer | | Sidebar with grouped nav | Dashboard | | KPI metric cards | Dashboard home | @@ -202,7 +202,7 @@ Redesign: - Wide: `xl: 1280px`, `2xl: 1536px` ### Mobile Improvements Needed -- [ ] Hamburger menu for marketplace header +- [ ] Hamburger menu for storefront header - [ ] Collapsible sidebar for dashboard - [ ] Stack pricing cards vertically - [ ] Full-width booking form @@ -228,7 +228,7 @@ Redesign: ## 8. Implementation Phases -### Phase 1 — Marketplace Public Pages (Priority) +### Phase 1 — Storefront Public Pages (Priority) 1. Home page redesign - Stats strip - How it works section @@ -254,15 +254,15 @@ Redesign: ## 9. Files to Modify -### Marketplace +### Storefront | File | Change | |---|---| | `src/app/(public)/HomeContent.tsx` | Add stats, how-it-works, testimonials sections | | `src/app/(public)/features/page.tsx` | Alternating layout | | `src/app/(public)/pricing/PricingPageContent.tsx` | Toggle + improved cards | | `src/app/(public)/explore/ExploreVehicleGrid.tsx` | Improved vehicle cards | -| `src/components/MarketplaceHeader.tsx` | Mobile menu | -| `src/components/MarketplaceFooter.tsx` | Mega footer | +| `src/components/StorefrontHeader.tsx` | Mobile menu | +| `src/components/StorefrontFooter.tsx` | Mega footer | | `src/app/globals.css` | No changes needed | ### Dashboard diff --git a/docs/progressive-signup-plan.md b/docs/progressive-signup-plan.md index 627487f..2df6f79 100644 --- a/docs/progressive-signup-plan.md +++ b/docs/progressive-signup-plan.md @@ -70,13 +70,13 @@ Replace the rigid 4-step form with an explicit состояние/state on the ` | `DRAFT` | Step 0 above | Browsing dashboard UI, exploring settings, reading docs | | `IDENTIFIED` | Owner first/last name + company display name | Personalizing UI, inviting teammates | | `LISTABLE` | Company contact info + address | Creating/saving a fleet item or offer as **draft** | -| `PUBLISHABLE` | Legal identity block (legal name, legal form, RC, ICE, IF, license) | Publishing a listing live on the marketplace | +| `PUBLISHABLE` | Legal identity block (legal name, legal form, RC, ICE, IF, license) | Publishing a listing live on the storefront | | `PAYABLE` | Payment provider config (AmanPay/PayPal) + responsible person | Accepting bookings/payments | | `SUBSCRIBED` | Plan + billing period selection | Past the 90-day trial, billed account | Each state is just a derived boolean/computed field (`companyState`) based on which fields are filled — not a separate workflow table. The UI reads this state to decide what to show/lock. -This also directly maps the **existing legal/compliance fields** (RC, ICE, IF, operating license) to the **one moment they actually matter**: right before something goes live on the marketplace. Today they're collected on day 1, before the user has even decided to publish anything. +This also directly maps the **existing legal/compliance fields** (RC, ICE, IF, operating license) to the **one moment they actually matter**: right before something goes live on the storefront. Today they're collected on day 1, before the user has even decided to publish anything. --- @@ -171,7 +171,7 @@ Confirmation email still sends immediately after Step 0, but its copy changes fr | Risk | Mitigation | |---|---| | Users abandon before ever reaching `PUBLISHABLE`/`PAYABLE` | That's expected and fine — they were never going to finish the old form either. Track funnel by state instead of all-or-nothing. | -| Marketplace ends up with unpublishable "ghost" companies | `DRAFT`/`IDENTIFIED` companies are simply never shown publicly — only `PUBLISHABLE`+ companies appear, so this is a non-issue by construction. | +| Storefront ends up with unpublishable "ghost" companies | `DRAFT`/`IDENTIFIED` companies are simply never shown publicly — only `PUBLISHABLE`+ companies appear, so this is a non-issue by construction. | | Legal/compliance requirements might mandate some data before *any* account exists, in some jurisdictions | Confirm with legal/compliance which fields (if any) are truly required by regulation before allowing a transaction vs. before allowing signup at all. Likely only the `PAYABLE` gate has real regulatory weight. | | Users get repeatedly interrupted by modals | Batch by group (Section 5), not by individual field; show the completion indicator so users see how close they are; never re-ask for data already given. | | Existing accounts (already fully filled out under the old flow) | No migration needed — they'll simply compute as `SUBSCRIBED`/fully complete already. | diff --git a/docs/project-design/API_REFACTOR_TASK_LIST.md b/docs/project-design/API_REFACTOR_TASK_LIST.md index bedcd0b..62f16b7 100644 --- a/docs/project-design/API_REFACTOR_TASK_LIST.md +++ b/docs/project-design/API_REFACTOR_TASK_LIST.md @@ -53,7 +53,7 @@ whenever new endpoints are added. Integration tests exist, but the heaviest workflows still benefit from deeper coverage: - subscription billing transitions -- marketplace reservation intake +- storefront reservation intake - public booking and payment initialization - reservation inspection and close flows - admin billing operations diff --git a/docs/project-design/COOKIE_POLICY.md b/docs/project-design/COOKIE_POLICY.md index 790add7..98cf797 100644 --- a/docs/project-design/COOKIE_POLICY.md +++ b/docs/project-design/COOKIE_POLICY.md @@ -52,7 +52,7 @@ We do not use analytics cookies, advertising cookies, or third-party tracking co | **Scope** | All pages (`path=/`) | | **Third-party** | No | -**Purpose:** Stores your chosen display language so the interface appears in your preferred language on every visit across all parts of the platform (public marketplace, dashboard, and admin panel). Supported values are English (`en`), French (`fr`), and Arabic (`ar`). +**Purpose:** Stores your chosen display language so the interface appears in your preferred language on every visit across all parts of the platform (public storefront, dashboard, and admin panel). Supported values are English (`en`), French (`fr`), and Arabic (`ar`). **When it is set:** When you change the language using the language selector, or automatically on first visit based on your browser's language settings. **Can you opt out?** You can block this cookie. If you do, the platform will fall back to a default language and you may need to select your language on every visit. @@ -97,7 +97,7 @@ We do not use analytics cookies, advertising cookies, or third-party tracking co | Field | Value | |---|---| -| **Names** | `dashboard-language`, `marketplace-language` | +| **Names** | `dashboard-language`, `storefront-language` | | **Category** | Functional / preference | | **Duration** | 1 year | | **Scope** | All pages (`path=/`) | diff --git a/docs/project-design/FEATURES.md b/docs/project-design/FEATURES.md index 9c907b0..07784f3 100644 --- a/docs/project-design/FEATURES.md +++ b/docs/project-design/FEATURES.md @@ -4,7 +4,7 @@ This document lists the features that are active in the current codebase. Source of truth: -- `apps/marketplace` +- `apps/storefront` - `apps/dashboard` - `apps/admin` - `apps/api` @@ -14,7 +14,7 @@ Source of truth: The current workspace contains four active apps: -- `apps/marketplace` +- `apps/storefront` - `apps/dashboard` - `apps/admin` - `apps/api` @@ -61,12 +61,12 @@ There is no separate frontend app for a white-label company public site in this - Notification inbox and preferences - Subscription management -### Marketplace and public platform +### Storefront and public platform - Public marketing homepage - Public pricing page - Public features page -- Public marketplace/explore flow +- Public storefront/explore flow - Explore company profile pages under `/explore/[slug]` - Public review submission page via review token - Public legal/app policy pages @@ -120,7 +120,7 @@ There is no separate frontend app for a white-label company public site in this - Billing operations - Pricing configuration and promotions - Notification review -- Marketplace/site config management +- Storefront/site config management ## Present But Not Active Product Features @@ -144,9 +144,9 @@ The following old design ideas should not be treated as active features unless c - standalone about/contact page set - Google renter auth - automatic custom-domain provisioning -- Zapier/webhook marketplace integrations beyond the current API/webhook handlers +- Zapier/webhook storefront integrations beyond the current API/webhook handlers ## Notes - The database still contains legacy fields such as `Employee.clerkUserId`, but those fields are no longer evidence of active Clerk integration. -- Some renter-facing `/renter/*` pages exist in the marketplace app, but the auth entrypoints they depend on are not active. They are therefore not listed as active product scope here. +- Some renter-facing `/renter/*` pages exist in the storefront app, but the auth entrypoints they depend on are not active. They are therefore not listed as active product scope here. diff --git a/docs/project-design/PAGES.md b/docs/project-design/PAGES.md index 6bd0e2f..7ae2f96 100644 --- a/docs/project-design/PAGES.md +++ b/docs/project-design/PAGES.md @@ -4,15 +4,15 @@ This document lists the pages that are actually present in the current frontend Source of truth: -- `apps/marketplace/src/app` +- `apps/storefront/src/app` - `apps/dashboard/src/app` - `apps/admin/src/app` -## Marketplace App +## Storefront App App: -- `apps/marketplace` +- `apps/storefront` ### Public routes @@ -37,11 +37,11 @@ App: - `/` is the public marketing home. - `/pricing` consumes platform pricing from `/site/platform/pricing`. -- `/explore` is the public marketplace search/discovery page. -- `/explore/[slug]` is the company marketplace profile page. +- `/explore` is the public storefront search/discovery page. +- `/explore/[slug]` is the company storefront profile page. - `/review` is the review submission page driven by a reservation review token. -### Not present in the current marketplace app +### Not present in the current storefront app - `/about` - `/contact` @@ -51,7 +51,7 @@ App: ### Renter routes present in code but not active product entrypoints -These routes exist in the marketplace app: +These routes exist in the storefront app: - `/renter/dashboard` - `/renter/notifications` @@ -114,7 +114,7 @@ Base path: - `/dashboard` shows KPIs and booking-source analytics. - `/dashboard/fleet*` manages vehicles, maintenance, and calendar blocks. - `/dashboard/reservations*` manages the booking lifecycle and inspection workflows. -- `/dashboard/online-reservations` handles public/marketplace booking intake. +- `/dashboard/online-reservations` handles public/storefront booking intake. - `/dashboard/customers` is the company CRM view. - `/dashboard/offers` manages promotions. - `/dashboard/team` manages employees. @@ -164,7 +164,7 @@ App: - `/dashboard/billing` manages platform billing operations. - `/dashboard/pricing` edits platform pricing, features, and promotions. - `/dashboard/notifications` inspects platform notifications. -- `/dashboard/site-config` edits marketplace/site configuration content. +- `/dashboard/site-config` edits storefront/site configuration content. ## Deliberately Not Listed diff --git a/docs/project-design/VULNERABILITY_FIXES_2026-05-22.md b/docs/project-design/VULNERABILITY_FIXES_2026-05-22.md index 441d28e..cc1e6bb 100644 --- a/docs/project-design/VULNERABILITY_FIXES_2026-05-22.md +++ b/docs/project-design/VULNERABILITY_FIXES_2026-05-22.md @@ -8,7 +8,7 @@ ## Summary -A full security review of the API codebase identified 3 confirmed, high-confidence vulnerabilities. All 3 were fixed in the same session. A separate frontend scan (dashboard, admin, marketplace, public-site) found no confirmed vulnerabilities above the reporting threshold. +A full security review of the API codebase identified 3 confirmed, high-confidence vulnerabilities. All 3 were fixed in the same session. A separate frontend scan (dashboard, admin, storefront, public-site) found no confirmed vulnerabilities above the reporting threshold. | # | Severity | Category | Status | |---|----------|----------|--------| @@ -127,7 +127,7 @@ An authenticated employee from Company A could fetch the full maintenance histor ### Exploit Scenario -An employee of Company A calls `GET /api/v1/vehicles//maintenance`. Vehicle UUIDs can leak through marketplace listings, shared bookings, or support interactions. The response contains the complete maintenance history of Company B's vehicle — including service records, mileage data, and operational schedules — across tenant boundaries. +An employee of Company A calls `GET /api/v1/vehicles//maintenance`. Vehicle UUIDs can leak through storefront listings, shared bookings, or support interactions. The response contains the complete maintenance history of Company B's vehicle — including service records, mileage data, and operational schedules — across tenant boundaries. ### Fix @@ -156,7 +156,7 @@ An employee of Company A calls `GET /api/v1/vehicles// ## Frontend Scan Results -A full scan of all four frontend applications (dashboard, admin, marketplace, public-site) was performed. No vulnerabilities above the reporting threshold (confidence ≥ 8/10) were confirmed. +A full scan of all four frontend applications (dashboard, admin, storefront, public-site) was performed. No vulnerabilities above the reporting threshold (confidence ≥ 8/10) were confirmed. ### Hardening Recommendations (not vulnerabilities) @@ -164,7 +164,7 @@ Two items were identified as best-practice improvements: 1. **Admin auth-redirect `next` param** (`apps/admin/src/app/auth-redirect/page.tsx`) — Validate the `next` query parameter is a relative path (starts with `/`, does not contain `://`) before passing it to `router.replace`. The router itself does not perform cross-origin navigation, but defense-in-depth is recommended. -2. **`postMessage` wildcard origin + missing `frame-ancestors` CSP** (`apps/dashboard/src/app/sign-in/[[...sign-in]]/SignInPageClient.tsx`, `apps/dashboard/next.config.js`) — Replace `targetOrigin: '*'` with the marketplace origin, and add `Content-Security-Policy: frame-ancestors 'self' ` to prevent the sign-in page from being embeddable by arbitrary third-party domains. +2. **`postMessage` wildcard origin + missing `frame-ancestors` CSP** (`apps/dashboard/src/app/sign-in/[[...sign-in]]/SignInPageClient.tsx`, `apps/dashboard/next.config.js`) — Replace `targetOrigin: '*'` with the storefront origin, and add `Content-Security-Policy: frame-ancestors 'self' ` to prevent the sign-in page from being embeddable by arbitrary third-party domains. --- diff --git a/docs/project-design/api-routes.md b/docs/project-design/api-routes.md index 5a347c1..50eaad1 100644 --- a/docs/project-design/api-routes.md +++ b/docs/project-design/api-routes.md @@ -68,7 +68,7 @@ This is the default for dashboard/company management routes. ### Subscription Guard -`requireSubscription` blocks company routes when the company status is `PENDING` or `SUSPENDED`. Public site and marketplace routes are intentionally not behind this guard. +`requireSubscription` blocks company routes when the company status is `PENDING` or `SUSPENDED`. Public site and storefront routes are intentionally not behind this guard. ### Role-Based Access Control @@ -90,7 +90,7 @@ Admin roles are hierarchical: `requireRenterAuth` validates a Bearer token with `type === "renter"` and attaches `req.renterId`. -`optionalRenterAuth` is used on marketplace routes so public reads still work without a token. +`optionalRenterAuth` is used on storefront routes so public reads still work without a token. ### API Key @@ -136,7 +136,7 @@ The table below describes the current route groups mounted in `app.ts`. | `/api/v1/auth/employee` | Public plus employee JWT | Employee login, password reset, profile, language | Dashboard/staff authentication path | | `/api/v1/auth/renter` | Renter JWT for profile routes | Renter profile and push token | `signup` and `login` are currently disabled in this codebase | | `/api/v1/admin` | Admin JWT | Platform operations | Includes company admin, billing, subscriptions, pricing, audit, and admin-user management | -| `/api/v1/marketplace` | Public, optional renter JWT | Marketplace discovery and reservation intake | Designed for discovery and lead capture across companies | +| `/api/v1/storefront` | Public, optional renter JWT | Storefront discovery and reservation intake | Designed for discovery and lead capture across companies | | `/api/v1/site` | Public | White-label company site APIs | Drives each company-branded booking site | | `/api/v1/subscriptions` | Mixed | Plan listing, webhooks, subscription lifecycle | Public, webhook, and authenticated sub-routers share the same prefix | | `/api/v1/vehicles` | Employee JWT + tenant + subscription | Fleet management | Includes photos, status, calendar blocks, maintenance, and availability | @@ -228,17 +228,17 @@ Customers are company-scoped CRM records, even when a renter identity also exist This is intentionally separate from renter identity because one renter may interact with multiple companies while each company still needs its own operational customer record. -### Marketplace and public site +### Storefront and public site The public surface is split in two modules on purpose. -`marketplace` is the cross-company discovery layer: +`storefront` is the cross-company discovery layer: - featured/public offers -- marketplace cities +- storefront cities - listed companies - vehicle search -- marketplace reservation intake +- storefront reservation intake - review submission by token - company public pages under `/:slug` @@ -318,7 +318,7 @@ The admin router is broad because it covers platform operations across multiple - billing account and billing invoice operations - pricing config, plan features, and promotions - subscription overrides and extensions -- marketplace homepage configuration +- storefront homepage configuration This is the only route group allowed to work across tenants. diff --git a/docs/project-design/schema.md b/docs/project-design/schema.md index cc69976..9663378 100644 --- a/docs/project-design/schema.md +++ b/docs/project-design/schema.md @@ -243,7 +243,7 @@ Purpose: - public contact and social metadata - default locale and currency - company-owned payment provider settings for renter payments -- marketplace visibility and rating +- storefront visibility and rating - homepage/menu JSON configuration Important uniqueness: diff --git a/packages/types/src/api.ts b/packages/types/src/api.ts index 1d69796..88184a5 100644 --- a/packages/types/src/api.ts +++ b/packages/types/src/api.ts @@ -40,13 +40,13 @@ export const PLAN_FEATURES: Record = { 'Up to 10 vehicles', '1 user account', 'Basic analytics', - 'Marketplace listing', + 'Storefront listing', ], GROWTH: [ 'Up to 50 vehicles', '5 user accounts', 'Full analytics', - 'Priority marketplace placement', + 'Priority storefront placement', 'Custom branding', ], PRO: [ diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index e72cb1d..53498ce 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,4 +1,4 @@ export * from './damage' export * from './fuel' export * from './api' -export * from './marketplace-homepage' +export * from './storefront-homepage' diff --git a/packages/types/src/marketplace-homepage.ts b/packages/types/src/storefront-homepage.ts similarity index 89% rename from packages/types/src/marketplace-homepage.ts rename to packages/types/src/storefront-homepage.ts index 6658f92..61ab50b 100644 --- a/packages/types/src/marketplace-homepage.ts +++ b/packages/types/src/storefront-homepage.ts @@ -1,35 +1,35 @@ -export type MarketplaceLanguage = 'en' | 'fr' | 'ar' +export type StorefrontLanguage = 'en' | 'fr' | 'ar' -export type MarketplaceHomepageMetric = { +export type StorefrontHomepageMetric = { value: string label: string } -export type MarketplaceHomepagePillar = { +export type StorefrontHomepagePillar = { title: string body: string } -export type MarketplaceHomepageStep = { +export type StorefrontHomepageStep = { step: string title: string body: string } -export type MarketplaceHomepageTestimonial = { +export type StorefrontHomepageTestimonial = { quote: string author: string role: string company?: string } -export type MarketplaceHomepageHowItWorksStep = { +export type StorefrontHomepageHowItWorksStep = { number: string title: string description: string } -export type MarketplaceHomepageSectionType = +export type StorefrontHomepageSectionType = | 'hero' | 'surface' | 'pillars' @@ -40,8 +40,8 @@ export type MarketplaceHomepageSectionType = | 'testimonials' | 'closing' -export type MarketplaceHomepageContent = { - sections: MarketplaceHomepageSectionType[] +export type StorefrontHomepageContent = { + sections: StorefrontHomepageSectionType[] heroKicker: string heroTitle: string heroBody: string @@ -60,19 +60,19 @@ export type MarketplaceHomepageContent = { renterKicker: string renterTitle: string renterBody: string - pillars: MarketplaceHomepagePillar[] - metrics: MarketplaceHomepageMetric[] + pillars: StorefrontHomepagePillar[] + metrics: StorefrontHomepageMetric[] featureLabel: string features: string[] howitworksKicker: string howitworksTitle: string - howitworksSteps: MarketplaceHomepageHowItWorksStep[] + howitworksSteps: StorefrontHomepageHowItWorksStep[] stepsTitle: string - steps: MarketplaceHomepageStep[] + steps: StorefrontHomepageStep[] stepLabel: string testimonialsKicker: string testimonialsTitle: string - testimonials: MarketplaceHomepageTestimonial[] + testimonials: StorefrontHomepageTestimonial[] readyKicker: string readyTitle: string readyBody: string @@ -80,9 +80,9 @@ export type MarketplaceHomepageContent = { createWorkspace: string } -export type MarketplaceHomepageConfig = Record +export type StorefrontHomepageConfig = Record -export const defaultMarketplaceHomepageSections: MarketplaceHomepageSectionType[] = [ +export const defaultStorefrontHomepageSections: StorefrontHomepageSectionType[] = [ 'hero', 'surface', 'pillars', @@ -94,9 +94,9 @@ export const defaultMarketplaceHomepageSections: MarketplaceHomepageSectionType[ 'closing', ] -export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { +export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = { en: { - sections: defaultMarketplaceHomepageSections, + sections: defaultStorefrontHomepageSections, heroKicker: 'RentalDriveGo', heroTitle: 'The operating system for modern fleet management.', heroBody: @@ -120,7 +120,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { renterBody: 'RentalDriveGo powers discovery and conversion — not a walled garden. Browse here, book there, and pay directly to the fleet owner.', pillars: [ - ['Unified command', 'Vehicle inventory, dynamic pricing, and promotional offers flow from one admin hub into marketplace discovery and your own branded storefront.'], + ['Unified command', 'Vehicle inventory, dynamic pricing, and promotional offers flow from one admin hub into storefront discovery and your own branded storefront.'], ['Smart discovery', 'Location-aware search, reputation scoring, and AI-curated listings help renters find the right vehicle in seconds.'], ['Direct revenue', 'Every booking routes through your own payment gateway — you own the customer relationship and the cash flow.'], ].map(([title, body]) => ({ title: title!, body: body! })), @@ -147,7 +147,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { { number: '2', title: 'Onboard Your Fleet', - description: 'Upload vehicles, configure pricing, and publish offers that appear on the marketplace instantly.', + description: 'Upload vehicles, configure pricing, and publish offers that appear on the storefront instantly.', }, { number: '3', @@ -174,7 +174,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { }, { quote: - 'The white-label booking engine is a game-changer. Our customers never leave our brand experience, yet we get marketplace-level visibility.', + 'The white-label booking engine is a game-changer. Our customers never leave our brand experience, yet we get storefront-level visibility.', author: 'Ahmed Hassan', role: 'Operations Director', company: 'Desert Wheels', @@ -195,7 +195,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { createWorkspace: 'Create workspace', }, fr: { - sections: defaultMarketplaceHomepageSections, + sections: defaultStorefrontHomepageSections, heroKicker: 'RentalDriveGo', heroTitle: "Le système d'exploitation pour la gestion de flotte moderne.", heroBody: @@ -219,7 +219,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { renterBody: 'RentalDriveGo alimente la découverte et la conversion — pas un jardin fermé. Parcourez ici, réservez là-bas et payez directement au propriétaire de la flotte.', pillars: [ - ["Commande unifiée", "L'inventaire, la tarification dynamique et les offres promotionnelles circulent depuis un hub d'administration unique vers la marketplace et votre vitrine de marque."], + ["Commande unifiée", "L'inventaire, la tarification dynamique et les offres promotionnelles circulent depuis un hub d'administration unique vers la storefront et votre vitrine de marque."], ['Découverte intelligente', 'Recherche géolocalisée, score de réputation et annonces optimisées aident les clients à trouver le bon véhicule en quelques secondes.'], ['Revenus directs', 'Chaque réservation passe par votre propre passerelle de paiement — vous gardez la relation client et la trésorerie.'], ].map(([title, body]) => ({ title: title!, body: body! })), @@ -246,7 +246,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { { number: '2', title: 'Intégrez votre flotte', - description: 'Ajoutez vos véhicules, configurez les tarifs et publiez des offres visibles instantanément sur la marketplace.', + description: 'Ajoutez vos véhicules, configurez les tarifs et publiez des offres visibles instantanément sur la storefront.', }, { number: '3', @@ -273,7 +273,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { }, { quote: - 'Le moteur de réservation en marque blanche change la donne. Nos clients ne quittent jamais notre expérience de marque, tout en bénéficiant de la visibilité marketplace.', + 'Le moteur de réservation en marque blanche change la donne. Nos clients ne quittent jamais notre expérience de marque, tout en bénéficiant de la visibilité storefront.', author: 'Jean-Claude Barbier', role: 'Directeur des Opérations', company: 'Sahara Cars', @@ -294,7 +294,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { createWorkspace: "Créer l'espace", }, ar: { - sections: defaultMarketplaceHomepageSections, + sections: defaultStorefrontHomepageSections, heroKicker: 'RentalDriveGo', heroTitle: 'نظام التشغيل لإدارة الأساطيل الحديثة.', heroBody: @@ -318,7 +318,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { renterBody: 'RentalDriveGo يشغّل الاكتشاف والتحويل — وليس حديقة مغلقة. تصفح هنا، احجز هناك، وادفع مباشرة لمالك الأسطول.', pillars: [ - ['قيادة موحدة', 'المخزون، التسعير الديناميكي والعروض الترويجية تتدفق من مركز إدارة واحد إلى marketplace السوق وواجهة متجرك الخاصة.'], + ['قيادة موحدة', 'المخزون، التسعير الديناميكي والعروض الترويجية تتدفق من مركز إدارة واحد إلى storefront السوق وواجهة متجرك الخاصة.'], ['اكتشاف ذكي', 'بحث حسب الموقع، تقييم السمعة وقوائم منسقة بالذكاء الاصطناعي تساعد المستأجرين في العثور على المركبة المناسبة بثوانٍ.'], ['إيرادات مباشرة', 'كل حجز يمر عبر بوابة الدفع الخاصة بك — أنت تملك علاقة العميل والتدفق النقدي.'], ].map(([title, body]) => ({ title: title!, body: body! })), @@ -345,7 +345,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { { number: '2', title: 'أضف أسطولك', - description: 'أضف المركبات، واضبط الأسعار، وانشر العروض التي تظهر فوراً في marketplace السوق.', + description: 'أضف المركبات، واضبط الأسعار، وانشر العروض التي تظهر فوراً في storefront السوق.', }, { number: '3', @@ -372,7 +372,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { }, { quote: - 'محرك الحجز بالعلامة البيضاء يغير قواعد اللعبة. عملاؤنا لا يغادرون تجربة علامتنا التجارية أبداً، مع الاستفادة من ظهور marketplace السوق.', + 'محرك الحجز بالعلامة البيضاء يغير قواعد اللعبة. عملاؤنا لا يغادرون تجربة علامتنا التجارية أبداً، مع الاستفادة من ظهور storefront السوق.', author: 'محمد علي', role: 'مدير العمليات', company: 'صحراء ويلز', @@ -394,13 +394,13 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = { }, } -export function cloneMarketplaceHomepageContent(): MarketplaceHomepageConfig { - return JSON.parse(JSON.stringify(defaultMarketplaceHomepageContent)) as MarketplaceHomepageConfig +export function cloneStorefrontHomepageContent(): StorefrontHomepageConfig { + return JSON.parse(JSON.stringify(defaultStorefrontHomepageContent)) as StorefrontHomepageConfig } -export function resolveMarketplaceHomepageSections( - sections?: MarketplaceHomepageSectionType[] | null, -): MarketplaceHomepageSectionType[] { - const source = sections?.length ? sections : defaultMarketplaceHomepageSections +export function resolveStorefrontHomepageSections( + sections?: StorefrontHomepageSectionType[] | null, +): StorefrontHomepageSectionType[] { + const source = sections?.length ? sections : defaultStorefrontHomepageSections return source.filter((section, index) => source.indexOf(section) === index) } diff --git a/scripts/backup-restore-guide.md b/scripts/backup-restore-guide.md index 62c3404..5400ae3 100644 --- a/scripts/backup-restore-guide.md +++ b/scripts/backup-restore-guide.md @@ -49,7 +49,7 @@ The `--yes` flag is required. Without it, the script exits with instructions. ### What the restore does (in order) -1. Stops all app services (api, dashboard, marketplace, admin, pgmanage, redis, traefik) +1. Stops all app services (api, dashboard, storefront, admin, pgmanage, redis, traefik) 2. Starts PostgreSQL 3. Restores the database with `pg_restore --clean --if-exists` 4. Restores uploaded files into the api_uploads volume diff --git a/scripts/docker-prod-common.sh b/scripts/docker-prod-common.sh index a45f562..5e9dae7 100644 --- a/scripts/docker-prod-common.sh +++ b/scripts/docker-prod-common.sh @@ -137,7 +137,7 @@ start_prod_services() { for service in "${services[@]}"; do case "${service}" in - api|marketplace|dashboard|admin) + api|storefront|dashboard|admin) needs_migrations=1 break ;; @@ -181,7 +181,7 @@ login_registry_if_configured() { pull_prod_release_images() { require_release_image - prod_compose pull migrate api marketplace dashboard admin + prod_compose pull migrate api storefront dashboard admin } run_prod_migrations() { diff --git a/scripts/docker-prod-deploy.sh b/scripts/docker-prod-deploy.sh index 5483c83..d410efc 100644 --- a/scripts/docker-prod-deploy.sh +++ b/scripts/docker-prod-deploy.sh @@ -25,9 +25,9 @@ echo "Running database migrations" run_prod_migrations echo "Starting application services" -prod_compose up -d api marketplace dashboard admin +prod_compose up -d api storefront dashboard admin wait_for_healthy api 180 -wait_for_healthy marketplace 180 +wait_for_healthy storefront 180 wait_for_healthy dashboard 180 wait_for_healthy admin 180 diff --git a/scripts/docker-prod-restore.sh b/scripts/docker-prod-restore.sh index c439a30..f5b16f1 100644 --- a/scripts/docker-prod-restore.sh +++ b/scripts/docker-prod-restore.sh @@ -44,7 +44,7 @@ SKIP_RAW_RESTORE_VOLUMES=( ) echo "Stopping application services before restore..." -prod_compose stop api marketplace dashboard admin pgmanage >/dev/null || true +prod_compose stop api storefront dashboard admin pgmanage >/dev/null || true prod_compose stop redis >/dev/null || true stop_traefik diff --git a/scripts/docker-prod-run-pulled-image.sh b/scripts/docker-prod-run-pulled-image.sh index 16148bf..6a539dc 100644 --- a/scripts/docker-prod-run-pulled-image.sh +++ b/scripts/docker-prod-run-pulled-image.sh @@ -90,7 +90,7 @@ echo "Applying migrations" prod_compose run --rm migrate echo "Starting application services" -app_services=(api marketplace dashboard admin) +app_services=(api storefront dashboard admin) if [[ "${INCLUDE_PGMANAGE}" == "1" ]]; then prod_compose --profile private-tools up -d "${app_services[@]}" pgmanage @@ -98,7 +98,7 @@ else prod_compose up -d "${app_services[@]}" fi wait_for_healthy api 180 -wait_for_healthy marketplace 180 +wait_for_healthy storefront 180 wait_for_healthy dashboard 180 wait_for_healthy admin 180 diff --git a/scripts/docker-prod-up-all.sh b/scripts/docker-prod-up-all.sh index 68078dd..8f875ae 100644 --- a/scripts/docker-prod-up-all.sh +++ b/scripts/docker-prod-up-all.sh @@ -6,10 +6,10 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" source "${ROOT_DIR}/scripts/docker-prod-common.sh" start_traefik -start_prod_services postgres redis api marketplace dashboard admin +start_prod_services postgres redis api storefront dashboard admin wait_for_healthy api -wait_for_healthy marketplace +wait_for_healthy storefront wait_for_healthy dashboard wait_for_healthy admin -echo "Production stack is up and healthy: traefik, postgres, redis, api, marketplace, dashboard, admin" +echo "Production stack is up and healthy: traefik, postgres, redis, api, storefront, dashboard, admin" diff --git a/storefront/next.config.js b/storefront/next.config.js index da5d24b..20d26b7 100644 --- a/storefront/next.config.js +++ b/storefront/next.config.js @@ -2,7 +2,7 @@ const { buildSecurityHeaders, normalizeAssetPrefix } = require('../../config/nextSecurityHeaders') -// The marketplace proxies /dashboard and /admin to their own Next dev servers. +// The storefront proxies /dashboard and /admin to their own Next dev servers. // Allow those asset-prefix origins so proxied pages can load chunks and HMR. const dashboardAssetSource = normalizeAssetPrefix( process.env.DASHBOARD_ASSET_PREFIX ?? (process.env.NODE_ENV !== 'production' ? 'http://localhost:3001' : undefined), diff --git a/storefront/src/app/(public)/app-policy-pages.test.ts b/storefront/src/app/(public)/app-policy-pages.test.ts index 5729758..1417d71 100644 --- a/storefront/src/app/(public)/app-policy-pages.test.ts +++ b/storefront/src/app/(public)/app-policy-pages.test.ts @@ -16,7 +16,7 @@ function expectPolicyPage(element: unknown, slug: string, forcedLanguage: string expect(page.props.forcedLanguage).toBe(forcedLanguage) } -describe('marketplace static app policy pages', () => { +describe('storefront static app policy pages', () => { it('binds app privacy pages to explicit locales', () => { expectPolicyPage(AppPrivacyEnPage(), 'privacy-policy', 'en') expectPolicyPage(AppPrivacyFrPage(), 'privacy-policy', 'fr') diff --git a/storefront/src/app/(public)/explore/[slug]/page.tsx b/storefront/src/app/(public)/explore/[slug]/page.tsx index 4325db8..a314bbf 100644 --- a/storefront/src/app/(public)/explore/[slug]/page.tsx +++ b/storefront/src/app/(public)/explore/[slug]/page.tsx @@ -1,6 +1,6 @@ import Link from 'next/link' -import { marketplaceFetchOrDefault } from '@/lib/api' -import { getMarketplaceLanguage } from '@/lib/i18n.server' +import { storefrontFetchOrDefault } from '@/lib/api' +import { getStorefrontLanguage } from '@/lib/i18n.server' import { formatCurrency } from '@rentaldrivego/types' interface CompanyProfile { @@ -21,14 +21,14 @@ interface CompanyProfile { } export default async function CompanyProfilePage({ params }: { params: { slug: string } }) { - const language = await getMarketplaceLanguage() + const language = await getStorefrontLanguage() const dict = { en: { - unavailable: 'Marketplace unavailable', + unavailable: 'Storefront unavailable', unavailableTitle: 'Company details are temporarily unavailable.', - unavailableBody: 'The marketplace API is running, but the database is not reachable in this local environment.', + unavailableBody: 'The storefront API is running, but the database is not reachable in this local environment.', backToExplore: 'Back to explore', - partner: 'Marketplace partner', + partner: 'Storefront partner', vehicles: 'vehicles', offers: 'active offers', rating: 'Rating', @@ -42,11 +42,11 @@ export default async function CompanyProfilePage({ params }: { params: { slug: s unavailableNoDate: 'Temporarily unavailable for new reservations.', }, fr: { - unavailable: 'Marketplace indisponible', + unavailable: 'Storefront indisponible', unavailableTitle: "Les détails de l'entreprise sont temporairement indisponibles.", - unavailableBody: "L'API marketplace fonctionne, mais la base de données n'est pas accessible dans cet environnement local.", + unavailableBody: "L'API storefront fonctionne, mais la base de données n'est pas accessible dans cet environnement local.", backToExplore: "Retour à l'exploration", - partner: 'Partenaire marketplace', + partner: 'Partenaire storefront', vehicles: 'véhicules', offers: 'offres actives', rating: 'Note', @@ -78,7 +78,7 @@ export default async function CompanyProfilePage({ params }: { params: { slug: s unavailableNoDate: 'غير متاحة مؤقتاً للحجوزات الجديدة.', }, }[language] - const company = await marketplaceFetchOrDefault(`/marketplace/${params.slug}`, null) + const company = await storefrontFetchOrDefault(`/storefront/${params.slug}`, null) if (!company) { return ( diff --git a/storefront/src/app/(public)/explore/[slug]/vehicles/[id]/page.tsx b/storefront/src/app/(public)/explore/[slug]/vehicles/[id]/page.tsx index bafc3da..dc12698 100644 --- a/storefront/src/app/(public)/explore/[slug]/vehicles/[id]/page.tsx +++ b/storefront/src/app/(public)/explore/[slug]/vehicles/[id]/page.tsx @@ -1,6 +1,6 @@ import Link from 'next/link' -import { marketplaceFetchOrDefault } from '@/lib/api' -import { getMarketplaceLanguage } from '@/lib/i18n.server' +import { storefrontFetchOrDefault } from '@/lib/api' +import { getStorefrontLanguage } from '@/lib/i18n.server' import { formatCurrency } from '@rentaldrivego/types' import BookingForm from '@/components/BookingForm' @@ -33,12 +33,12 @@ interface VehicleDetail { } export default async function VehicleDetailPage({ params }: { params: { slug: string; id: string } }) { - const language = await getMarketplaceLanguage() + const language = await getStorefrontLanguage() const dict = { en: { - unavailable: 'Marketplace unavailable', + unavailable: 'Storefront unavailable', unavailableTitle: 'Vehicle details are temporarily unavailable.', - unavailableBody: 'The marketplace frontend is running, but the backing database is not reachable right now.', + unavailableBody: 'The storefront frontend is running, but the backing database is not reachable right now.', backToFleet: 'Back to fleet', backToExplore: 'Back to explore', noPhotos: 'No photos available.', @@ -59,9 +59,9 @@ export default async function VehicleDetailPage({ params }: { params: { slug: st photos: 'Photos', }, fr: { - unavailable: 'Marketplace indisponible', + unavailable: 'Storefront indisponible', unavailableTitle: 'Les détails du véhicule sont temporairement indisponibles.', - unavailableBody: "Le frontend marketplace fonctionne, mais la base de données n'est pas accessible pour le moment.", + unavailableBody: "Le frontend storefront fonctionne, mais la base de données n'est pas accessible pour le moment.", backToFleet: 'Retour à la flotte', backToExplore: 'Retour à l’exploration', noPhotos: 'Aucune photo disponible.', @@ -106,7 +106,7 @@ export default async function VehicleDetailPage({ params }: { params: { slug: st }, }[language] - const vehicle = await marketplaceFetchOrDefault(`/marketplace/${params.slug}/vehicles/${params.id}`, null) + const vehicle = await storefrontFetchOrDefault(`/storefront/${params.slug}/vehicles/${params.id}`, null) if (!vehicle) { return ( diff --git a/storefront/src/app/(public)/explore/page.tsx b/storefront/src/app/(public)/explore/page.tsx index 1d657a4..7251068 100644 --- a/storefront/src/app/(public)/explore/page.tsx +++ b/storefront/src/app/(public)/explore/page.tsx @@ -1,6 +1,6 @@ import Link from 'next/link' -import { marketplaceFetchOrDefault } from '@/lib/api' -import { getMarketplaceLanguage } from '@/lib/i18n.server' +import { storefrontFetchOrDefault } from '@/lib/api' +import { getStorefrontLanguage } from '@/lib/i18n.server' import ExploreSearchForm from './ExploreSearchForm' import ExploreVehicleGrid from './ExploreVehicleGrid' @@ -68,7 +68,7 @@ const CATEGORY_VALUES = [ export default async function ExplorePage({ searchParams }: { searchParams?: Promise> }) { const resolvedSearchParams = await searchParams - const language = await getMarketplaceLanguage() + const language = await getStorefrontLanguage() const dict = { en: { kicker: 'Discovery only', @@ -89,10 +89,10 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro ageOptions: ['18+', '21+', '23+', '25+', '30+'], search: 'Search', currentDeals: 'Current deals', - featuredOffers: 'Featured marketplace offers', + featuredOffers: 'Featured storefront offers', company: 'Company', validUntil: 'Valid until', - offersUnavailable: 'Marketplace offers are unavailable right now.', + offersUnavailable: 'Storefront offers are unavailable right now.', availableVehicles: 'Available vehicles', listings: 'listings', rentalCompany: 'Rental company', @@ -108,8 +108,8 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro vehicleUnavailable: 'Vehicle listings are unavailable right now.', browseByCategory: 'Browse by category', browseByCompany: 'Browse by company', - marketplacePartners: 'Marketplace partners', - marketplacePartner: 'Marketplace partner', + storefrontPartners: 'Storefront partners', + storefrontPartner: 'Storefront partner', rating: 'Rating', new: 'New', publishedVehicles: 'published vehicles', @@ -135,10 +135,10 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro ageOptions: ['18+', '21+', '23+', '25+', '30+'], search: 'Rechercher', currentDeals: 'Offres du moment', - featuredOffers: 'Offres marketplace mises en avant', + featuredOffers: 'Offres storefront mises en avant', company: 'Entreprise', validUntil: "Valable jusqu'au", - offersUnavailable: 'Les offres marketplace sont indisponibles pour le moment.', + offersUnavailable: 'Les offres storefront sont indisponibles pour le moment.', availableVehicles: 'Véhicules disponibles', listings: 'annonces', rentalCompany: 'Société de location', @@ -154,8 +154,8 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro vehicleUnavailable: 'Les annonces véhicules sont indisponibles pour le moment.', browseByCategory: 'Parcourir par catégorie', browseByCompany: 'Parcourir par entreprise', - marketplacePartners: 'Partenaires marketplace', - marketplacePartner: 'Partenaire marketplace', + storefrontPartners: 'Partenaires storefront', + storefrontPartner: 'Partenaire storefront', rating: 'Note', new: 'Nouveau', publishedVehicles: 'véhicules publiés', @@ -200,8 +200,8 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro vehicleUnavailable: 'قوائم السيارات غير متاحة حالياً.', browseByCategory: 'تصفح حسب الفئة', browseByCompany: 'تصفح حسب الشركة', - marketplacePartners: 'شركاء السوق', - marketplacePartner: 'شريك في السوق', + storefrontPartners: 'شركاء السوق', + storefrontPartner: 'شريك في السوق', rating: 'التقييم', new: 'جديد', publishedVehicles: 'سيارات منشورة', @@ -241,9 +241,9 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro query.set('pageSize', '24') const [offers, vehicles, companies] = await Promise.all([ - marketplaceFetchOrDefault('/marketplace/offers', []), - marketplaceFetchOrDefault(`/marketplace/search?${query.toString()}`, []), - marketplaceFetchOrDefault('/marketplace/companies?pageSize=8', []), + storefrontFetchOrDefault('/storefront/offers', []), + storefrontFetchOrDefault(`/storefront/search?${query.toString()}`, []), + storefrontFetchOrDefault('/storefront/companies?pageSize=8', []), ]) const cities = Array.from( new Set( @@ -309,12 +309,12 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro

{dict.browseByCompany}

-

{dict.marketplacePartners}

+

{dict.storefrontPartners}

{companies.map((company) => ( -

{company.brand?.publicCity ?? dict.marketplacePartner}

+

{company.brand?.publicCity ?? dict.storefrontPartner}

{company.brand?.displayName ?? dict.rentalCompany}

{dict.rating} {company.brand?.marketplaceRating?.toFixed(1) ?? dict.new}

{company._count.vehicles} {dict.publishedVehicles}

diff --git a/storefront/src/app/(public)/footer/[slug]/page.test.ts b/storefront/src/app/(public)/footer/[slug]/page.test.ts index ac35998..fe57631 100644 --- a/storefront/src/app/(public)/footer/[slug]/page.test.ts +++ b/storefront/src/app/(public)/footer/[slug]/page.test.ts @@ -13,7 +13,7 @@ import FooterContentPage from '@/components/FooterContentPage' import FooterPage, { generateStaticParams } from './page' import { footerPageSlugs } from '@/lib/footerContent' -describe('marketplace footer route', () => { +describe('storefront footer route', () => { it('generates one static route per registered footer slug', () => { expect(generateStaticParams()).toEqual(footerPageSlugs.map((slug) => ({ slug }))) }) diff --git a/storefront/src/app/layout.tsx b/storefront/src/app/layout.tsx index 8328375..6d853cf 100644 --- a/storefront/src/app/layout.tsx +++ b/storefront/src/app/layout.tsx @@ -1,11 +1,11 @@ import type { Metadata } from 'next' import { cookies } from 'next/headers' -import MarketplaceShell from '@/components/MarketplaceShell' -import { getMarketplaceLanguage } from '@/lib/i18n.server' +import StorefrontShell from '@/components/StorefrontShell' +import { getStorefrontLanguage } from '@/lib/i18n.server' import './globals.css' export const metadata: Metadata = { - title: 'RentalDriveGo Marketplace', + title: 'RentalDriveGo Storefront', description: 'Discover vehicles from trusted rental companies.', icons: { icon: '/rentaldrivego.png', @@ -15,11 +15,11 @@ export const metadata: Metadata = { } export default async function RootLayout({ children }: { children: React.ReactNode }) { - const language = await getMarketplaceLanguage() + const language = await getStorefrontLanguage() const cookieStore = await cookies() const rawTheme = cookieStore.get('rentaldrivego-theme')?.value ?? - cookieStore.get('marketplace-theme')?.value + cookieStore.get('storefront-theme')?.value const theme = rawTheme === 'dark' ? 'dark' : 'light' return ( @@ -29,12 +29,12 @@ export default async function RootLayout({ children }: { children: React.ReactNo