refactor: rename marketplace to storefront across the entire monorepo
Build & Deploy / Build & Push Docker Image (push) Successful in 1m2s
Test / Type Check (all packages) (push) Failing after 28s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Storefront Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
Build & Deploy / Deploy to VPS (push) Successful in 3s
Build & Deploy / Build & Push Docker Image (push) Successful in 1m2s
Test / Type Check (all packages) (push) Failing after 28s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Storefront Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
Build & Deploy / Deploy to VPS (push) Successful in 3s
Comprehensive rename of all marketplace references to storefront: - API module: apps/api/src/modules/marketplace/ → storefront/ - Components: MarketplaceHeader → StorefrontHeader, MarketplaceShell → StorefrontShell, MarketplaceFooter → StorefrontFooter - Types: marketplace-homepage.ts → storefront-homepage.ts - Test files: employee-marketplace-* → employee-storefront-* - All source code identifiers, imports, route paths, and strings - Documentation (docs/), CI config (.gitlab-ci.yml), scripts - Dashboard, admin, storefront workspace references - Prisma field names preserved (isListedOnMarketplace, marketplaceRating, marketplaceFunnelEvent) as they map to database schema Validation: - API type-check: 0 errors - Storefront type-check: 0 errors - Dashboard type-check: 0 errors - Full monorepo type-check: only pre-existing admin TS18046
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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<CompanyProfile | null>(`/marketplace/${params.slug}`, null)
|
||||
const company = await storefrontFetchOrDefault<CompanyProfile | null>(`/storefront/${params.slug}`, null)
|
||||
|
||||
if (!company) {
|
||||
return (
|
||||
|
||||
@@ -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<VehicleDetail | null>(`/marketplace/${params.slug}/vehicles/${params.id}`, null)
|
||||
const vehicle = await storefrontFetchOrDefault<VehicleDetail | null>(`/storefront/${params.slug}/vehicles/${params.id}`, null)
|
||||
|
||||
if (!vehicle) {
|
||||
return (
|
||||
|
||||
@@ -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<Record<string, string | string[] | undefined>> }) {
|
||||
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<Offer[]>('/marketplace/offers', []),
|
||||
marketplaceFetchOrDefault<Vehicle[]>(`/marketplace/search?${query.toString()}`, []),
|
||||
marketplaceFetchOrDefault<CompanyCard[]>('/marketplace/companies?pageSize=8', []),
|
||||
storefrontFetchOrDefault<Offer[]>('/storefront/offers', []),
|
||||
storefrontFetchOrDefault<Vehicle[]>(`/storefront/search?${query.toString()}`, []),
|
||||
storefrontFetchOrDefault<CompanyCard[]>('/storefront/companies?pageSize=8', []),
|
||||
])
|
||||
const cities = Array.from(
|
||||
new Set(
|
||||
@@ -309,12 +309,12 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Pro
|
||||
<section>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.browseByCompany}</h2>
|
||||
<p className="text-sm text-stone-500 dark:text-stone-400">{dict.marketplacePartners}</p>
|
||||
<p className="text-sm text-stone-500 dark:text-stone-400">{dict.storefrontPartners}</p>
|
||||
</div>
|
||||
<div className="mt-4 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
{companies.map((company) => (
|
||||
<Link key={company.id} href={`/explore/${company.brand?.subdomain ?? ''}`} className="card flex flex-col p-5 transition-colors hover:border-orange-300 dark:hover:border-orange-500">
|
||||
<p className="truncate text-xs uppercase tracking-[0.18em] text-stone-500 dark:text-stone-400">{company.brand?.publicCity ?? dict.marketplacePartner}</p>
|
||||
<p className="truncate text-xs uppercase tracking-[0.18em] text-stone-500 dark:text-stone-400">{company.brand?.publicCity ?? dict.storefrontPartner}</p>
|
||||
<h3 className="mt-3 truncate text-lg font-semibold text-stone-900 dark:text-stone-100">{company.brand?.displayName ?? dict.rentalCompany}</h3>
|
||||
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">{dict.rating} {company.brand?.marketplaceRating?.toFixed(1) ?? dict.new}</p>
|
||||
<p className="mt-1 text-sm text-stone-500 dark:text-stone-400">{company._count.vehicles} {dict.publishedVehicles}</p>
|
||||
|
||||
@@ -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 })))
|
||||
})
|
||||
|
||||
@@ -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
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
"(function(){try{var m=document.cookie.match(/(?:^|; )rentaldrivego-theme=([^;]+)/);var theme=m?decodeURIComponent(m[1]):(localStorage.getItem('rentaldrivego-theme')||localStorage.getItem('marketplace-theme'));if(theme!=='light'&&theme!=='dark'){theme=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}document.documentElement.classList.toggle('dark',theme==='dark');document.documentElement.style.colorScheme=theme;document.body&&document.body.setAttribute('data-theme',theme)}catch(e){}})();",
|
||||
"(function(){try{var m=document.cookie.match(/(?:^|; )rentaldrivego-theme=([^;]+)/);var theme=m?decodeURIComponent(m[1]):(localStorage.getItem('rentaldrivego-theme')||localStorage.getItem('storefront-theme'));if(theme!=='light'&&theme!=='dark'){theme=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}document.documentElement.classList.toggle('dark',theme==='dark');document.documentElement.style.colorScheme=theme;document.body&&document.body.setAttribute('data-theme',theme)}catch(e){}})();",
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body suppressHydrationWarning>
|
||||
<MarketplaceShell initialLanguage={language} initialTheme={theme}>{children}</MarketplaceShell>
|
||||
<StorefrontShell initialLanguage={language} initialTheme={theme}>{children}</StorefrontShell>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<string, string | number | boolean | null>) {
|
||||
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<AvailabilityResponse>(
|
||||
`/marketplace/${companySlug}/vehicles/${vehicleId}/availability?startDate=${encodeURIComponent(start.toISOString())}&endDate=${encodeURIComponent(end.toISOString())}`,
|
||||
const result = await storefrontFetch<AvailabilityResponse>(
|
||||
`/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<BookingResponse>('/marketplace/reservations', {
|
||||
const booking = await storefrontPost<BookingResponse>('/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)
|
||||
|
||||
@@ -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(' ')
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ type FooterItem = {
|
||||
href?: string
|
||||
}
|
||||
|
||||
export default function MarketplaceFooter({
|
||||
export default function StorefrontFooter({
|
||||
primaryItems,
|
||||
secondaryItems,
|
||||
localeLabel,
|
||||
+2
-2
@@ -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'
|
||||
+3
-3
@@ -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}
|
||||
</Link>
|
||||
<Link
|
||||
href="/explore"
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { clearCachedEmployeeProfile, hasCachedEmployeeProfile } from './MarketplaceShell'
|
||||
import { clearCachedEmployeeProfile, hasCachedEmployeeProfile } from './StorefrontShell'
|
||||
|
||||
afterEach(() => {
|
||||
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: {
|
||||
+3
-3
@@ -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)
|
||||
})
|
||||
+39
-39
@@ -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<MarketplaceLanguage, Dictionary> = {
|
||||
const dictionaries: Record<StorefrontLanguage, Dictionary> = {
|
||||
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<MarketplaceLanguage, Dictionary> = {
|
||||
|
||||
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<PreferencesContextValue | null>(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<MarketplaceLanguage>(initialLanguage)
|
||||
const [language, setLanguageState] = useState<StorefrontLanguage>(initialLanguage)
|
||||
const [theme, setThemeState] = useState<Theme>(initialTheme)
|
||||
const [hydrated, setHydrated] = useState(false)
|
||||
const previousLanguage = useRef<MarketplaceLanguage>(initialLanguage)
|
||||
const previousLanguage = useRef<StorefrontLanguage>(initialLanguage)
|
||||
const [companyName, setCompanyName] = useState<string | null>(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])
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
|
||||
// Public marketing footer used by the homepage and other storefront pages.
|
||||
export { default } from '@/components/MarketplaceFooter'
|
||||
export { default } from '@/components/StorefrontFooter'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
|
||||
// Public marketing navbar used by the homepage and other storefront pages.
|
||||
export { default } from '@/components/MarketplaceHeader'
|
||||
export { default } from '@/components/StorefrontHeader'
|
||||
|
||||
@@ -7,12 +7,12 @@ import SiteNavbar from './SiteNavbar'
|
||||
import {
|
||||
getFooterContent,
|
||||
localeOptions,
|
||||
useMarketplacePreferences,
|
||||
} from '@/components/MarketplaceShell'
|
||||
useStorefrontPreferences,
|
||||
} from '@/components/StorefrontShell'
|
||||
import { resolveBrowserAppUrl } from '@/lib/appUrls'
|
||||
|
||||
export default function SitePageLayout({ children }: { children: 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',
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
export async function storefrontFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
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<T>(path: string, fallback: T): Promise<T> {
|
||||
export async function storefrontFetchOrDefault<T>(path: string, fallback: T): Promise<T> {
|
||||
try {
|
||||
return await marketplaceFetch<T>(path)
|
||||
return await storefrontFetch<T>(path)
|
||||
} catch {
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
export async function marketplacePost<T>(path: string, body: unknown): Promise<T> {
|
||||
export async function storefrontPost<T>(path: string, body: unknown): Promise<T> {
|
||||
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<T>(path: string, body: unknown): Promise<T
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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<FooterPageSlug, string> = {
|
||||
'general-conditions': '/footer/general-conditions',
|
||||
}
|
||||
|
||||
const footerContent: Record<MarketplaceLanguage, Record<FooterPageSlug, FooterPageContent>> = {
|
||||
const footerContent: Record<StorefrontLanguage, Record<FooterPageSlug, FooterPageContent>> = {
|
||||
en: {
|
||||
'about-us': {
|
||||
title: 'About Us',
|
||||
@@ -175,7 +175,7 @@ const footerContent: Record<MarketplaceLanguage, Record<FooterPageSlug, FooterPa
|
||||
{
|
||||
heading: '5. Legacy Preference Cookies',
|
||||
paragraphs: [
|
||||
'Names: dashboard-language, marketplace-language',
|
||||
'Names: dashboard-language, storefront-language',
|
||||
'These cookies exist for backward compatibility with earlier versions of the platform and store the same language preference as the primary cookie. They are read only as a fallback and will be phased out in a future update.',
|
||||
],
|
||||
},
|
||||
@@ -419,7 +419,7 @@ const footerContent: Record<MarketplaceLanguage, Record<FooterPageSlug, FooterPa
|
||||
{
|
||||
heading: '5. Cookies de préférence hérités',
|
||||
paragraphs: [
|
||||
'Noms : dashboard-language, marketplace-language',
|
||||
'Noms : dashboard-language, storefront-language',
|
||||
'Ces cookies existent pour assurer la compatibilité ascendante avec les versions antérieures de la plateforme et stockent la même préférence de langue que le cookie principal. Ils ne sont lus qu’en secours et seront supprimés dans une prochaine mise à jour.',
|
||||
],
|
||||
},
|
||||
@@ -663,7 +663,7 @@ const footerContent: Record<MarketplaceLanguage, Record<FooterPageSlug, FooterPa
|
||||
{
|
||||
heading: '5. ملفات تعريف الارتباط الموروثة',
|
||||
paragraphs: [
|
||||
'الأسماء: dashboard-language, marketplace-language',
|
||||
'الأسماء: dashboard-language, storefront-language',
|
||||
'توجد هذه الملفات لضمان التوافق مع الإصدارات السابقة من المنصة، وتخزّن تفضيل اللغة ذاته الموجود في الملف الرئيسي. لا تُقرأ إلا كخيار احتياطي، وستُزال في تحديث مستقبلي.',
|
||||
],
|
||||
},
|
||||
@@ -785,6 +785,6 @@ export function isFooterPageSlug(value: string): value is FooterPageSlug {
|
||||
return footerPageSlugs.includes(value as FooterPageSlug)
|
||||
}
|
||||
|
||||
export function getFooterPageContent(language: MarketplaceLanguage, slug: FooterPageSlug): FooterPageContent {
|
||||
export function getFooterPageContent(language: StorefrontLanguage, slug: FooterPageSlug): FooterPageContent {
|
||||
return footerContent[language][slug]
|
||||
}
|
||||
|
||||
@@ -11,31 +11,31 @@ vi.mock('next/headers', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<MarketplaceLanguage> {
|
||||
export async function getStorefrontLanguage(): Promise<StorefrontLanguage> {
|
||||
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'
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user