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
+11 -11
View File
@@ -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,
})
+8 -8
View File
@@ -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
}
+2 -2
View File
@@ -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)
+6 -6
View File
@@ -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 quen 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]
}
+8 -8
View File
@@ -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')
})
})
+3 -3
View File
@@ -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'
}
+11 -11
View File
@@ -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)
})
})
+3 -3
View File
@@ -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'
}
+3 -3
View File
@@ -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', () => {