chore: wire Carplace into dev and production stacks
Build & Deploy / Build & Push Docker Image (push) Successful in 2m55s
Test / Type Check (all packages) (push) Successful in 54s
Build & Deploy / Deploy to VPS (push) Successful in 4s
Test / API Unit Tests (push) Failing after 48s
Test / Homepage Unit Tests (push) Successful in 43s
Test / Storefront Unit Tests (push) Failing after 40s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Failing after 42s
Test / API Integration Tests (push) Successful in 1m0s
Build & Deploy / Build & Push Docker Image (push) Successful in 2m55s
Test / Type Check (all packages) (push) Successful in 54s
Build & Deploy / Deploy to VPS (push) Successful in 4s
Test / API Unit Tests (push) Failing after 48s
Test / Homepage Unit Tests (push) Successful in 43s
Test / Storefront Unit Tests (push) Failing after 40s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Failing after 42s
Test / API Integration Tests (push) Successful in 1m0s
This commit is contained in:
@@ -5,7 +5,7 @@ const apiOrigin = (process.env.API_INTERNAL_URL ?? process.env.API_URL ?? 'http:
|
||||
const apiUrl = new URL(apiOrigin)
|
||||
const ADMIN_BASE_PATH = '/admin'
|
||||
|
||||
// In Docker dev the admin app runs on port 3002 while the storefront proxy
|
||||
// In Docker dev the admin app runs on port 3002 while the Carplace proxy
|
||||
// serves it from port 3000. When ADMIN_ASSET_PREFIX is set, Next emits
|
||||
// absolute chunk URLs so /admin pages load their JS/CSS and HMR directly from
|
||||
// port 3002, bypassing the proxy (which can't upgrade WebSocket connections).
|
||||
|
||||
@@ -71,7 +71,7 @@ interface CompanyDetail {
|
||||
whatsappNumber: string | null
|
||||
defaultLocale: string
|
||||
defaultCurrency: string
|
||||
isListedOnMarketplace: boolean
|
||||
isListedOnCarplace: boolean
|
||||
} | null
|
||||
contractSettings: {
|
||||
legalName: string | null
|
||||
@@ -171,7 +171,7 @@ interface FormState {
|
||||
whatsappNumber: string
|
||||
defaultLocale: string
|
||||
defaultCurrency: string
|
||||
isListedOnMarketplace: boolean
|
||||
isListedOnCarplace: boolean
|
||||
}
|
||||
contractSettings: {
|
||||
legalName: string
|
||||
@@ -277,7 +277,7 @@ function createFormState(company: CompanyDetail): FormState {
|
||||
whatsappNumber: company.brand?.whatsappNumber ?? '',
|
||||
defaultLocale: company.brand?.defaultLocale ?? 'en',
|
||||
defaultCurrency: company.brand?.defaultCurrency ?? 'MAD',
|
||||
isListedOnMarketplace: company.brand?.isListedOnMarketplace ?? true,
|
||||
isListedOnCarplace: company.brand?.isListedOnCarplace ?? true,
|
||||
},
|
||||
contractSettings: {
|
||||
legalName: company.contractSettings?.legalName ?? '',
|
||||
@@ -420,7 +420,7 @@ export default function AdminCompanyDetailPage() {
|
||||
whatsappNumber: toNullable(form.brand.whatsappNumber),
|
||||
defaultLocale: form.brand.defaultLocale,
|
||||
defaultCurrency: form.brand.defaultCurrency,
|
||||
isListedOnMarketplace: form.brand.isListedOnMarketplace,
|
||||
isListedOnCarplace: form.brand.isListedOnCarplace,
|
||||
},
|
||||
contractSettings: {
|
||||
legalName: toNullable(form.contractSettings.legalName),
|
||||
@@ -723,8 +723,8 @@ export default function AdminCompanyDetailPage() {
|
||||
<input className={INPUT_CLASS} value={form.brand.publicAddress} onChange={(e) => updateSection('brand', { publicAddress: e.target.value })} />
|
||||
</label>
|
||||
<label className="flex items-center gap-3 pt-7 text-sm text-zinc-300">
|
||||
<input type="checkbox" checked={form.brand.isListedOnMarketplace} onChange={(e) => updateSection('brand', { isListedOnMarketplace: e.target.checked })} />
|
||||
Listed on storefront
|
||||
<input type="checkbox" checked={form.brand.isListedOnCarplace} onChange={(e) => updateSection('brand', { isListedOnCarplace: e.target.checked })} />
|
||||
Listed on Carplace
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function AdminDashboardPage() {
|
||||
brand: 'RentalDriveGo',
|
||||
eyebrow: 'Operations command',
|
||||
platformOverview: 'RentalDriveGo admin dashboard',
|
||||
subtitle: 'Monitor storefront health, subscription coverage, renter activity, and operator actions from one control surface.',
|
||||
subtitle: 'Monitor Carplace health, subscription coverage, renter activity, and operator actions from one control surface.',
|
||||
liveSignal: 'Live platform signal',
|
||||
readiness: 'Operational readiness',
|
||||
readinessBody: 'Core admin workflows are connected and ready for platform support.',
|
||||
@@ -36,14 +36,14 @@ export default function AdminDashboardPage() {
|
||||
['Renters', 'Support renter trust workflows, blocking, and account recovery.', 'View all'],
|
||||
['Audit logs', 'Trace every sensitive admin action across RentalDriveGo.', 'View logs'],
|
||||
],
|
||||
health: ['Tenant accounts', 'Storefront identity', 'Admin audit trail'],
|
||||
health: ['Tenant accounts', 'Carplace identity', 'Admin audit trail'],
|
||||
},
|
||||
fr: {
|
||||
kpis: ['Entreprises totales', 'Entreprises actives', 'Locataires totaux', 'Réservations totales'],
|
||||
brand: 'RentalDriveGo',
|
||||
eyebrow: 'Centre des opérations',
|
||||
platformOverview: 'Tableau de bord admin RentalDriveGo',
|
||||
subtitle: 'Suivez la santé de la storefront, les abonnements, l’activité des locataires et les actions opérateur depuis une même interface.',
|
||||
subtitle: 'Suivez la santé de la Carplace, les abonnements, l’activité des locataires et les actions opérateur depuis une même interface.',
|
||||
liveSignal: 'Signal plateforme en direct',
|
||||
readiness: 'Disponibilité opérationnelle',
|
||||
readinessBody: 'Les principaux workflows admin sont connectés et prêts pour le support plateforme.',
|
||||
@@ -55,7 +55,7 @@ export default function AdminDashboardPage() {
|
||||
['Locataires', 'Gérer les workflows de confiance, de blocage et de récupération.', 'Voir tout'],
|
||||
['Journaux d’audit', 'Tracer chaque action admin sensible dans RentalDriveGo.', 'Voir les journaux'],
|
||||
],
|
||||
health: ['Comptes locataires', 'Identité storefront', 'Piste d’audit admin'],
|
||||
health: ['Comptes locataires', 'Identité Carplace', 'Piste d’audit admin'],
|
||||
},
|
||||
ar: {
|
||||
kpis: ['إجمالي الشركات', 'الشركات النشطة', 'إجمالي المستأجرين', 'إجمالي الحجوزات'],
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
cloneStorefrontHomepageContent,
|
||||
resolveStorefrontHomepageSections,
|
||||
type StorefrontHomepageConfig,
|
||||
type StorefrontHomepageContent,
|
||||
type StorefrontHomepageSectionType,
|
||||
type StorefrontLanguage,
|
||||
cloneCarplaceHomepageContent,
|
||||
resolveCarplaceHomepageSections,
|
||||
type CarplaceHomepageConfig,
|
||||
type CarplaceHomepageContent,
|
||||
type CarplaceHomepageSectionType,
|
||||
type CarplaceLanguage,
|
||||
} from '@rentaldrivego/types'
|
||||
import { useAdminI18n } from '@/components/I18nProvider'
|
||||
import { ADMIN_API_BASE } from '@/lib/api'
|
||||
@@ -35,15 +35,15 @@ const STATUS_COLORS: Record<string, string> = {
|
||||
const INPUT_CLASS = 'w-full rounded-xl border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-emerald-500'
|
||||
const LABEL_CLASS = 'mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-zinc-500'
|
||||
|
||||
function cloneHomepageContent(content: StorefrontHomepageConfig) {
|
||||
const cloned = JSON.parse(JSON.stringify(content)) as StorefrontHomepageConfig
|
||||
;(['en', 'fr', 'ar'] as StorefrontLanguage[]).forEach((language) => {
|
||||
cloned[language].sections = resolveStorefrontHomepageSections(cloned[language].sections)
|
||||
function cloneHomepageContent(content: CarplaceHomepageConfig) {
|
||||
const cloned = JSON.parse(JSON.stringify(content)) as CarplaceHomepageConfig
|
||||
;(['en', 'fr', 'ar'] as CarplaceLanguage[]).forEach((language) => {
|
||||
cloned[language].sections = resolveCarplaceHomepageSections(cloned[language].sections)
|
||||
})
|
||||
return cloned
|
||||
}
|
||||
|
||||
const HOMEPAGE_SECTIONS: StorefrontHomepageSectionType[] = [
|
||||
const HOMEPAGE_SECTIONS: CarplaceHomepageSectionType[] = [
|
||||
'hero',
|
||||
'surface',
|
||||
'pillars',
|
||||
@@ -58,12 +58,12 @@ export default function AdminSiteConfigPage() {
|
||||
const copy = {
|
||||
en: {
|
||||
title: 'Site configuration',
|
||||
description: 'Edit the main storefront homepage here, or jump into a company to manage its branded public homepage and menu.',
|
||||
description: 'Edit the main Carplace homepage here, or jump into a company to manage its branded public homepage and menu.',
|
||||
homepageTitle: 'Main website homepage',
|
||||
homepageDescription: 'This controls the storefront homepage shown on the main RentalDriveGo website.',
|
||||
homepageDescription: 'This controls the Carplace homepage shown on the main RentalDriveGo website.',
|
||||
saveHomepage: 'Save homepage',
|
||||
savingHomepage: 'Saving…',
|
||||
homepageSaved: 'Storefront homepage saved.',
|
||||
homepageSaved: 'Carplace homepage saved.',
|
||||
search: 'Search by company or slug…',
|
||||
loading: 'Loading…',
|
||||
empty: 'No companies found',
|
||||
@@ -86,7 +86,7 @@ export default function AdminSiteConfigPage() {
|
||||
previewTitle: 'Live preview',
|
||||
previewDescription: 'Draft changes appear here before you save them.',
|
||||
homepageSections: 'Homepage sections',
|
||||
homepageSectionsDescription: 'Add or remove blocks from the main storefront homepage.',
|
||||
homepageSectionsDescription: 'Add or remove blocks from the main Carplace homepage.',
|
||||
addSection: 'Add',
|
||||
removeSection: 'Remove',
|
||||
expand: 'Expand',
|
||||
@@ -94,12 +94,12 @@ export default function AdminSiteConfigPage() {
|
||||
},
|
||||
fr: {
|
||||
title: 'Configuration du site',
|
||||
description: 'Modifiez ici la page d’accueil principale de la storefront, ou ouvrez une entreprise pour gérer sa page publique et son menu.',
|
||||
description: 'Modifiez ici la page d’accueil principale de la Carplace, ou ouvrez une entreprise pour gérer sa page publique et son menu.',
|
||||
homepageTitle: 'Page d’accueil du site principal',
|
||||
homepageDescription: 'Cette section contrôle la page d’accueil storefront affichée sur le site principal de RentalDriveGo.',
|
||||
homepageDescription: 'Cette section contrôle la page d’accueil Carplace affichée sur le site principal de RentalDriveGo.',
|
||||
saveHomepage: 'Enregistrer la page d’accueil',
|
||||
savingHomepage: 'Enregistrement…',
|
||||
homepageSaved: 'Page d’accueil storefront enregistrée.',
|
||||
homepageSaved: 'Page d’accueil Carplace enregistrée.',
|
||||
search: 'Rechercher par entreprise ou slug…',
|
||||
loading: 'Chargement…',
|
||||
empty: 'Aucune entreprise trouvée',
|
||||
@@ -112,7 +112,7 @@ export default function AdminSiteConfigPage() {
|
||||
companiesTitle: 'Sites de marque des entreprises',
|
||||
languageLabel: 'Langue',
|
||||
hero: 'Bloc principal',
|
||||
surface: 'Bloc storefront',
|
||||
surface: 'Bloc Carplace',
|
||||
companySection: 'Bloc opérateur',
|
||||
renterSection: 'Bloc client',
|
||||
valueSection: 'Blocs de valeur',
|
||||
@@ -122,7 +122,7 @@ export default function AdminSiteConfigPage() {
|
||||
previewTitle: 'Aperçu en direct',
|
||||
previewDescription: 'Les brouillons apparaissent ici avant l’enregistrement.',
|
||||
homepageSections: 'Sections de la page d’accueil',
|
||||
homepageSectionsDescription: 'Ajoutez ou retirez des blocs de la page d’accueil storefront principale.',
|
||||
homepageSectionsDescription: 'Ajoutez ou retirez des blocs de la page d’accueil Carplace principale.',
|
||||
addSection: 'Ajouter',
|
||||
removeSection: 'Retirer',
|
||||
expand: 'Ouvrir',
|
||||
@@ -171,8 +171,8 @@ export default function AdminSiteConfigPage() {
|
||||
const [search, setSearch] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [homepage, setHomepage] = useState<StorefrontHomepageConfig>(cloneStorefrontHomepageContent())
|
||||
const [homepageLanguage, setHomepageLanguage] = useState<StorefrontLanguage>(language)
|
||||
const [homepage, setHomepage] = useState<CarplaceHomepageConfig>(cloneCarplaceHomepageContent())
|
||||
const [homepageLanguage, setHomepageLanguage] = useState<CarplaceLanguage>(language)
|
||||
const [homepageSaving, setHomepageSaving] = useState(false)
|
||||
const [homepageMessage, setHomepageMessage] = useState<string | null>(null)
|
||||
const [homepageExpanded, setHomepageExpanded] = useState(false)
|
||||
@@ -189,7 +189,7 @@ export default function AdminSiteConfigPage() {
|
||||
credentials: 'include',
|
||||
cache: 'no-store',
|
||||
}),
|
||||
fetch(`${ADMIN_API_BASE}/admin/site-config/storefront-homepage`, {
|
||||
fetch(`${ADMIN_API_BASE}/admin/site-config/carplace-homepage`, {
|
||||
credentials: 'include',
|
||||
cache: 'no-store',
|
||||
}),
|
||||
@@ -202,7 +202,7 @@ export default function AdminSiteConfigPage() {
|
||||
|
||||
const homepageJson = await homepageRes.json()
|
||||
if (homepageRes.ok && homepageJson?.data) {
|
||||
setHomepage(cloneHomepageContent(homepageJson.data as StorefrontHomepageConfig))
|
||||
setHomepage(cloneHomepageContent(homepageJson.data as CarplaceHomepageConfig))
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message)
|
||||
@@ -225,7 +225,7 @@ export default function AdminSiteConfigPage() {
|
||||
)
|
||||
}, [search, companies])
|
||||
|
||||
function updateHomepageContent(patch: Partial<StorefrontHomepageContent>) {
|
||||
function updateHomepageContent(patch: Partial<CarplaceHomepageContent>) {
|
||||
setHomepage((current) => ({
|
||||
...current,
|
||||
[homepageLanguage]: {
|
||||
@@ -257,16 +257,16 @@ export default function AdminSiteConfigPage() {
|
||||
}
|
||||
|
||||
function getActiveSections() {
|
||||
return resolveStorefrontHomepageSections(activeContent.sections)
|
||||
return resolveCarplaceHomepageSections(activeContent.sections)
|
||||
}
|
||||
|
||||
function addHomepageSection(section: StorefrontHomepageSectionType) {
|
||||
function addHomepageSection(section: CarplaceHomepageSectionType) {
|
||||
const sections = getActiveSections()
|
||||
if (sections.includes(section)) return
|
||||
updateHomepageContent({ sections: [...sections, section] })
|
||||
}
|
||||
|
||||
function removeHomepageSection(section: StorefrontHomepageSectionType) {
|
||||
function removeHomepageSection(section: CarplaceHomepageSectionType) {
|
||||
const sections = getActiveSections().filter((item) => item !== section)
|
||||
if (sections.length === 0) return
|
||||
updateHomepageContent({ sections })
|
||||
@@ -276,7 +276,7 @@ export default function AdminSiteConfigPage() {
|
||||
setHomepageSaving(true)
|
||||
setHomepageMessage(null)
|
||||
try {
|
||||
const res = await fetch(`${ADMIN_API_BASE}/admin/site-config/storefront-homepage`, {
|
||||
const res = await fetch(`${ADMIN_API_BASE}/admin/site-config/carplace-homepage`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -286,7 +286,7 @@ export default function AdminSiteConfigPage() {
|
||||
})
|
||||
const json = await res.json()
|
||||
if (!res.ok) throw new Error(json?.message ?? 'Failed to save homepage')
|
||||
setHomepage(cloneHomepageContent(json.data as StorefrontHomepageConfig))
|
||||
setHomepage(cloneHomepageContent(json.data as CarplaceHomepageConfig))
|
||||
setHomepageMessage(copy.homepageSaved)
|
||||
} catch (err: any) {
|
||||
setError(err.message)
|
||||
@@ -298,7 +298,7 @@ export default function AdminSiteConfigPage() {
|
||||
const activeContent = homepage[homepageLanguage]
|
||||
const activeSections = getActiveSections()
|
||||
const availableSections = HOMEPAGE_SECTIONS.filter((section) => !activeSections.includes(section))
|
||||
const sectionLabels: Record<StorefrontHomepageSectionType, string> = {
|
||||
const sectionLabels: Record<CarplaceHomepageSectionType, string> = {
|
||||
hero: copy.hero,
|
||||
surface: copy.surface,
|
||||
pillars: copy.valueSection,
|
||||
@@ -337,7 +337,7 @@ export default function AdminSiteConfigPage() {
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-xs font-semibold uppercase tracking-[0.16em] text-zinc-500">{copy.languageLabel}</span>
|
||||
{(['en', 'fr', 'ar'] as StorefrontLanguage[]).map((value) => (
|
||||
{(['en', 'fr', 'ar'] as CarplaceLanguage[]).map((value) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user