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

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

Validation:
- API type-check: 0 errors
- Storefront type-check: 0 errors
- Dashboard type-check: 0 errors
- Full monorepo type-check: only pre-existing admin TS18046
This commit is contained in:
root
2026-06-28 01:14:46 -04:00
parent bffda95a7c
commit 8aab968e09
147 changed files with 829 additions and 829 deletions
+12 -12
View File
@@ -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)
@@ -15,7 +15,7 @@ type FooterItem = {
href?: string
}
export default function MarketplaceFooter({
export default function StorefrontFooter({
primaryItems,
secondaryItems,
localeLabel,
@@ -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'
@@ -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"
@@ -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: {
@@ -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)
})
@@ -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]