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

This commit is contained in:
root
2026-07-02 18:15:42 -04:00
parent ad5f5ebab7
commit 7ff2dbb139
214 changed files with 5258 additions and 5906 deletions
@@ -44,7 +44,7 @@ CREATE TYPE "OfferType" AS ENUM ('PERCENTAGE', 'FIXED_AMOUNT', 'FREE_DAY', 'SPEC
CREATE TYPE "ReservationStatus" AS ENUM ('DRAFT', 'CONFIRMED', 'ACTIVE', 'COMPLETED', 'CANCELLED', 'NO_SHOW');
-- CreateEnum
CREATE TYPE "BookingSource" AS ENUM ('DASHBOARD', 'PUBLIC_SITE', 'MARKETPLACE', 'API');
CREATE TYPE "BookingSource" AS ENUM ('DASHBOARD', 'PUBLIC_SITE', 'CARPLACE', 'API');
-- CreateEnum
CREATE TYPE "CancelledBy" AS ENUM ('COMPANY', 'RENTER', 'SYSTEM');
@@ -200,8 +200,8 @@ CREATE TABLE "brand_settings" (
"paypalEmail" TEXT,
"paypalMerchantId" TEXT,
"paymentMethodsEnabled" "PaymentProvider"[],
"isListedOnMarketplace" BOOLEAN NOT NULL DEFAULT true,
"marketplaceRating" DOUBLE PRECISION,
"isListedOnCarplace" BOOLEAN NOT NULL DEFAULT true,
"carplaceRating" DOUBLE PRECISION,
"home_page_config" JSONB,
"menu_config" JSONB,
"updatedAt" TIMESTAMP(3) NOT NULL,
@@ -368,7 +368,7 @@ CREATE TABLE "reservations" (
"offerId" TEXT,
"promoCodeUsed" TEXT,
"source" "BookingSource" NOT NULL DEFAULT 'DASHBOARD',
"marketplaceRef" TEXT,
"carplaceRef" TEXT,
"status" "ReservationStatus" NOT NULL DEFAULT 'DRAFT',
"startDate" TIMESTAMP(3) NOT NULL,
"endDate" TIMESTAMP(3) NOT NULL,
@@ -2,7 +2,7 @@
ALTER TABLE "reservations" ADD COLUMN "reviewToken" TEXT;
CREATE UNIQUE INDEX "reservations_reviewToken_key" ON "reservations"("reviewToken");
-- Make renterId optional on reviews (reviews can come from non-marketplace customers via token link)
-- Make renterId optional on reviews (reviews can come from non-Carplace customers via token link)
ALTER TABLE "reviews" ALTER COLUMN "renterId" DROP NOT NULL;
ALTER TABLE "reviews" DROP CONSTRAINT IF EXISTS "reviews_renterId_fkey";
ALTER TABLE "reviews" ADD CONSTRAINT "reviews_renterId_fkey"
@@ -15,11 +15,11 @@ INSERT INTO "plan_features" ("id", "plan", "label", "sortOrder") VALUES
('plf_starter_1', 'STARTER', 'Up to 10 vehicles', 10),
('plf_starter_2', 'STARTER', '1 user account', 20),
('plf_starter_3', 'STARTER', 'Basic analytics', 30),
('plf_starter_4', 'STARTER', 'Marketplace listing', 40),
('plf_starter_4', 'STARTER', 'Carplace listing', 40),
('plf_growth_1', 'GROWTH', 'Up to 50 vehicles', 10),
('plf_growth_2', 'GROWTH', '5 user accounts', 20),
('plf_growth_3', 'GROWTH', 'Full analytics', 30),
('plf_growth_4', 'GROWTH', 'Priority marketplace placement', 40),
('plf_growth_4', 'GROWTH', 'Priority Carplace placement', 40),
('plf_growth_5', 'GROWTH', 'Custom branding', 50),
('plf_pro_1', 'PRO', 'Unlimited vehicles', 10),
('plf_pro_2', 'PRO', 'Unlimited user accounts', 20),
@@ -0,0 +1,30 @@
ALTER TYPE "NotificationType" ADD VALUE IF NOT EXISTS 'DOCUMENTS_REQUIRED';
ALTER TYPE "NotificationType" ADD VALUE IF NOT EXISTS 'PAYMENT_REQUIRED';
CREATE TABLE IF NOT EXISTS "carplace_funnel_events" (
"id" TEXT PRIMARY KEY,
"eventName" TEXT NOT NULL,
"companyId" TEXT,
"companySlug" TEXT NOT NULL,
"vehicleId" TEXT NOT NULL,
"renterId" TEXT,
"sessionId" TEXT,
"path" TEXT,
"metadata" JSONB NOT NULL DEFAULT '{}',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS "carplace_funnel_events_eventName_createdAt_idx"
ON "carplace_funnel_events"("eventName", "createdAt");
CREATE INDEX IF NOT EXISTS "carplace_funnel_events_companyId_createdAt_idx"
ON "carplace_funnel_events"("companyId", "createdAt");
CREATE INDEX IF NOT EXISTS "carplace_funnel_events_companySlug_createdAt_idx"
ON "carplace_funnel_events"("companySlug", "createdAt");
CREATE INDEX IF NOT EXISTS "carplace_funnel_events_vehicleId_createdAt_idx"
ON "carplace_funnel_events"("vehicleId", "createdAt");
CREATE INDEX IF NOT EXISTS "carplace_funnel_events_renterId_createdAt_idx"
ON "carplace_funnel_events"("renterId", "createdAt");
@@ -1,30 +0,0 @@
ALTER TYPE "NotificationType" ADD VALUE IF NOT EXISTS 'DOCUMENTS_REQUIRED';
ALTER TYPE "NotificationType" ADD VALUE IF NOT EXISTS 'PAYMENT_REQUIRED';
CREATE TABLE IF NOT EXISTS "marketplace_funnel_events" (
"id" TEXT PRIMARY KEY,
"eventName" TEXT NOT NULL,
"companyId" TEXT,
"companySlug" TEXT NOT NULL,
"vehicleId" TEXT NOT NULL,
"renterId" TEXT,
"sessionId" TEXT,
"path" TEXT,
"metadata" JSONB NOT NULL DEFAULT '{}',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS "marketplace_funnel_events_eventName_createdAt_idx"
ON "marketplace_funnel_events"("eventName", "createdAt");
CREATE INDEX IF NOT EXISTS "marketplace_funnel_events_companyId_createdAt_idx"
ON "marketplace_funnel_events"("companyId", "createdAt");
CREATE INDEX IF NOT EXISTS "marketplace_funnel_events_companySlug_createdAt_idx"
ON "marketplace_funnel_events"("companySlug", "createdAt");
CREATE INDEX IF NOT EXISTS "marketplace_funnel_events_vehicleId_createdAt_idx"
ON "marketplace_funnel_events"("vehicleId", "createdAt");
CREATE INDEX IF NOT EXISTS "marketplace_funnel_events_renterId_createdAt_idx"
ON "marketplace_funnel_events"("renterId", "createdAt");
+6 -6
View File
@@ -255,7 +255,7 @@ enum ReservationStatus {
enum BookingSource {
DASHBOARD
PUBLIC_SITE
MARKETPLACE
CARPLACE
API
}
@@ -980,8 +980,8 @@ model BrandSettings {
paypalEmail String?
paypalMerchantId String?
paymentMethodsEnabled PaymentProvider[]
isListedOnMarketplace Boolean @default(true)
marketplaceRating Float?
isListedOnCarplace Boolean @default(true)
carplaceRating Float?
homePageConfig Json? @map("home_page_config")
menuConfig Json? @map("menu_config")
@@ -1279,7 +1279,7 @@ model Reservation {
promoCodeUsed String?
vehicleCategory VehicleCategory?
source BookingSource @default(DASHBOARD)
marketplaceRef String?
carplaceRef String?
status ReservationStatus @default(DRAFT)
startDate DateTime
endDate DateTime
@@ -1352,7 +1352,7 @@ model ReservationPublicAccess {
@@map("reservation_public_access")
}
model MarketplaceFunnelEvent {
model CarplaceFunnelEvent {
id String @id @default(cuid())
eventName String
companyId String?
@@ -1369,7 +1369,7 @@ model MarketplaceFunnelEvent {
@@index([companySlug, createdAt])
@@index([vehicleId, createdAt])
@@index([renterId, createdAt])
@@map("marketplace_funnel_events")
@@map("carplace_funnel_events")
}
+2 -2
View File
@@ -40,13 +40,13 @@ export const PLAN_FEATURES: Record<string, string[]> = {
'Up to 10 vehicles',
'1 user account',
'Basic analytics',
'Storefront listing',
'Carplace listing',
],
GROWTH: [
'Up to 50 vehicles',
'5 user accounts',
'Full analytics',
'Priority storefront placement',
'Priority Carplace placement',
'Custom branding',
],
PRO: [
@@ -1,35 +1,35 @@
export type StorefrontLanguage = 'en' | 'fr' | 'ar'
export type CarplaceLanguage = 'en' | 'fr' | 'ar'
export type StorefrontHomepageMetric = {
export type CarplaceHomepageMetric = {
value: string
label: string
}
export type StorefrontHomepagePillar = {
export type CarplaceHomepagePillar = {
title: string
body: string
}
export type StorefrontHomepageStep = {
export type CarplaceHomepageStep = {
step: string
title: string
body: string
}
export type StorefrontHomepageTestimonial = {
export type CarplaceHomepageTestimonial = {
quote: string
author: string
role: string
company?: string
}
export type StorefrontHomepageHowItWorksStep = {
export type CarplaceHomepageHowItWorksStep = {
number: string
title: string
description: string
}
export type StorefrontHomepageSectionType =
export type CarplaceHomepageSectionType =
| 'hero'
| 'surface'
| 'pillars'
@@ -40,8 +40,8 @@ export type StorefrontHomepageSectionType =
| 'testimonials'
| 'closing'
export type StorefrontHomepageContent = {
sections: StorefrontHomepageSectionType[]
export type CarplaceHomepageContent = {
sections: CarplaceHomepageSectionType[]
heroKicker: string
heroTitle: string
heroBody: string
@@ -60,19 +60,19 @@ export type StorefrontHomepageContent = {
renterKicker: string
renterTitle: string
renterBody: string
pillars: StorefrontHomepagePillar[]
metrics: StorefrontHomepageMetric[]
pillars: CarplaceHomepagePillar[]
metrics: CarplaceHomepageMetric[]
featureLabel: string
features: string[]
howitworksKicker: string
howitworksTitle: string
howitworksSteps: StorefrontHomepageHowItWorksStep[]
howitworksSteps: CarplaceHomepageHowItWorksStep[]
stepsTitle: string
steps: StorefrontHomepageStep[]
steps: CarplaceHomepageStep[]
stepLabel: string
testimonialsKicker: string
testimonialsTitle: string
testimonials: StorefrontHomepageTestimonial[]
testimonials: CarplaceHomepageTestimonial[]
readyKicker: string
readyTitle: string
readyBody: string
@@ -80,9 +80,9 @@ export type StorefrontHomepageContent = {
createWorkspace: string
}
export type StorefrontHomepageConfig = Record<StorefrontLanguage, StorefrontHomepageContent>
export type CarplaceHomepageConfig = Record<CarplaceLanguage, CarplaceHomepageContent>
export const defaultStorefrontHomepageSections: StorefrontHomepageSectionType[] = [
export const defaultCarplaceHomepageSections: CarplaceHomepageSectionType[] = [
'hero',
'surface',
'pillars',
@@ -94,9 +94,9 @@ export const defaultStorefrontHomepageSections: StorefrontHomepageSectionType[]
'closing',
]
export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
export const defaultCarplaceHomepageContent: CarplaceHomepageConfig = {
en: {
sections: defaultStorefrontHomepageSections,
sections: defaultCarplaceHomepageSections,
heroKicker: 'RentalDriveGo',
heroTitle: 'The operating system for modern fleet management.',
heroBody:
@@ -120,7 +120,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
renterBody:
'RentalDriveGo powers discovery and conversion — not a walled garden. Browse here, book there, and pay directly to the fleet owner.',
pillars: [
['Unified command', 'Vehicle inventory, dynamic pricing, and promotional offers flow from one admin hub into storefront discovery and your own branded storefront.'],
['Unified command', 'Vehicle inventory, dynamic pricing, and promotional offers flow from one admin hub into Carplace discovery and your own branded booking website.'],
['Smart discovery', 'Location-aware search, reputation scoring, and AI-curated listings help renters find the right vehicle in seconds.'],
['Direct revenue', 'Every booking routes through your own payment gateway — you own the customer relationship and the cash flow.'],
].map(([title, body]) => ({ title: title!, body: body! })),
@@ -147,7 +147,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
{
number: '2',
title: 'Onboard Your Fleet',
description: 'Upload vehicles, configure pricing, and publish offers that appear on the storefront instantly.',
description: 'Upload vehicles, configure pricing, and publish offers that appear on the Carplace instantly.',
},
{
number: '3',
@@ -174,7 +174,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
},
{
quote:
'The white-label booking engine is a game-changer. Our customers never leave our brand experience, yet we get storefront-level visibility.',
'The white-label booking engine is a game-changer. Our customers never leave our brand experience, yet we get Carplace visibility.',
author: 'Ahmed Hassan',
role: 'Operations Director',
company: 'Desert Wheels',
@@ -195,7 +195,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
createWorkspace: 'Create workspace',
},
fr: {
sections: defaultStorefrontHomepageSections,
sections: defaultCarplaceHomepageSections,
heroKicker: 'RentalDriveGo',
heroTitle: "Le système d'exploitation pour la gestion de flotte moderne.",
heroBody:
@@ -219,7 +219,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
renterBody:
'RentalDriveGo alimente la découverte et la conversion — pas un jardin fermé. Parcourez ici, réservez là-bas et payez directement au propriétaire de la flotte.',
pillars: [
["Commande unifiée", "L'inventaire, la tarification dynamique et les offres promotionnelles circulent depuis un hub d'administration unique vers la storefront et votre vitrine de marque."],
["Commande unifiée", "L'inventaire, la tarification dynamique et les offres promotionnelles circulent depuis un hub d'administration unique vers Carplace et votre vitrine de marque."],
['Découverte intelligente', 'Recherche géolocalisée, score de réputation et annonces optimisées aident les clients à trouver le bon véhicule en quelques secondes.'],
['Revenus directs', 'Chaque réservation passe par votre propre passerelle de paiement — vous gardez la relation client et la trésorerie.'],
].map(([title, body]) => ({ title: title!, body: body! })),
@@ -246,7 +246,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
{
number: '2',
title: 'Intégrez votre flotte',
description: 'Ajoutez vos véhicules, configurez les tarifs et publiez des offres visibles instantanément sur la storefront.',
description: 'Ajoutez vos véhicules, configurez les tarifs et publiez des offres visibles instantanément sur Carplace.',
},
{
number: '3',
@@ -273,7 +273,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
},
{
quote:
'Le moteur de réservation en marque blanche change la donne. Nos clients ne quittent jamais notre expérience de marque, tout en bénéficiant de la visibilité storefront.',
'Le moteur de réservation en marque blanche change la donne. Nos clients ne quittent jamais notre expérience de marque, tout en bénéficiant de la visibilité Carplace.',
author: 'Jean-Claude Barbier',
role: 'Directeur des Opérations',
company: 'Sahara Cars',
@@ -294,7 +294,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
createWorkspace: "Créer l'espace",
},
ar: {
sections: defaultStorefrontHomepageSections,
sections: defaultCarplaceHomepageSections,
heroKicker: 'RentalDriveGo',
heroTitle: 'نظام التشغيل لإدارة الأساطيل الحديثة.',
heroBody:
@@ -318,7 +318,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
renterBody:
'RentalDriveGo يشغّل الاكتشاف والتحويل — وليس حديقة مغلقة. تصفح هنا، احجز هناك، وادفع مباشرة لمالك الأسطول.',
pillars: [
['قيادة موحدة', 'المخزون، التسعير الديناميكي والعروض الترويجية تتدفق من مركز إدارة واحد إلى storefront السوق وواجهة متجرك الخاصة.'],
['قيادة موحدة', 'المخزون، التسعير الديناميكي والعروض الترويجية تتدفق من مركز إدارة واحد إلى Carplace وواجهة متجرك الخاصة.'],
['اكتشاف ذكي', 'بحث حسب الموقع، تقييم السمعة وقوائم منسقة بالذكاء الاصطناعي تساعد المستأجرين في العثور على المركبة المناسبة بثوانٍ.'],
['إيرادات مباشرة', 'كل حجز يمر عبر بوابة الدفع الخاصة بك — أنت تملك علاقة العميل والتدفق النقدي.'],
].map(([title, body]) => ({ title: title!, body: body! })),
@@ -345,7 +345,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
{
number: '2',
title: 'أضف أسطولك',
description: 'أضف المركبات، واضبط الأسعار، وانشر العروض التي تظهر فوراً في storefront السوق.',
description: 'أضف المركبات، واضبط الأسعار، وانشر العروض التي تظهر فوراً في Carplace.',
},
{
number: '3',
@@ -372,7 +372,7 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
},
{
quote:
'محرك الحجز بالعلامة البيضاء يغير قواعد اللعبة. عملاؤنا لا يغادرون تجربة علامتنا التجارية أبداً، مع الاستفادة من ظهور storefront السوق.',
'محرك الحجز بالعلامة البيضاء يغير قواعد اللعبة. عملاؤنا لا يغادرون تجربة علامتنا التجارية أبداً، مع الاستفادة من ظهور Carplace.',
author: 'محمد علي',
role: 'مدير العمليات',
company: 'صحراء ويلز',
@@ -394,13 +394,13 @@ export const defaultStorefrontHomepageContent: StorefrontHomepageConfig = {
},
}
export function cloneStorefrontHomepageContent(): StorefrontHomepageConfig {
return JSON.parse(JSON.stringify(defaultStorefrontHomepageContent)) as StorefrontHomepageConfig
export function cloneCarplaceHomepageContent(): CarplaceHomepageConfig {
return JSON.parse(JSON.stringify(defaultCarplaceHomepageContent)) as CarplaceHomepageConfig
}
export function resolveStorefrontHomepageSections(
sections?: StorefrontHomepageSectionType[] | null,
): StorefrontHomepageSectionType[] {
const source = sections?.length ? sections : defaultStorefrontHomepageSections
export function resolveCarplaceHomepageSections(
sections?: CarplaceHomepageSectionType[] | null,
): CarplaceHomepageSectionType[] {
const source = sections?.length ? sections : defaultCarplaceHomepageSections
return source.filter((section, index) => source.indexOf(section) === index)
}
+1 -1
View File
@@ -1,4 +1,4 @@
export * from './damage'
export * from './fuel'
export * from './api'
export * from './storefront-homepage'
export * from './carplace-homepage'