diff --git a/.gitea/workflows/build-and-deploy.yml b/.gitea/workflows/build-and-deploy.yml index 2de41b2..c6fccb0 100644 --- a/.gitea/workflows/build-and-deploy.yml +++ b/.gitea/workflows/build-and-deploy.yml @@ -53,13 +53,15 @@ jobs: shell: bash env: NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} - NEXT_PUBLIC_MARKETPLACE_URL: ${{ secrets.NEXT_PUBLIC_MARKETPLACE_URL }} + NEXT_PUBLIC_HOMEPAGE_URL: ${{ secrets.NEXT_PUBLIC_HOMEPAGE_URL }} + NEXT_PUBLIC_STOREFRONT_URL: ${{ secrets.NEXT_PUBLIC_STOREFRONT_URL }} NEXT_PUBLIC_DASHBOARD_URL: ${{ secrets.NEXT_PUBLIC_DASHBOARD_URL }} NEXT_PUBLIC_ADMIN_URL: ${{ secrets.NEXT_PUBLIC_ADMIN_URL }} run: | for variable in \ NEXT_PUBLIC_API_URL \ - NEXT_PUBLIC_MARKETPLACE_URL \ + NEXT_PUBLIC_HOMEPAGE_URL \ + NEXT_PUBLIC_STOREFRONT_URL \ NEXT_PUBLIC_DASHBOARD_URL \ NEXT_PUBLIC_ADMIN_URL do @@ -99,7 +101,8 @@ jobs: DASHBOARD_INTERNAL_URL=http://dashboard:3001 ADMIN_INTERNAL_URL=http://admin:3002 NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }} - NEXT_PUBLIC_MARKETPLACE_URL=${{ secrets.NEXT_PUBLIC_MARKETPLACE_URL }} + NEXT_PUBLIC_HOMEPAGE_URL=${{ secrets.NEXT_PUBLIC_HOMEPAGE_URL }} + NEXT_PUBLIC_STOREFRONT_URL=${{ secrets.NEXT_PUBLIC_STOREFRONT_URL }} NEXT_PUBLIC_DASHBOARD_URL=${{ secrets.NEXT_PUBLIC_DASHBOARD_URL }} NEXT_PUBLIC_ADMIN_URL=${{ secrets.NEXT_PUBLIC_ADMIN_URL }} diff --git a/Dockerfile.production b/Dockerfile.production index 2eae5b3..f731733 100644 --- a/Dockerfile.production +++ b/Dockerfile.production @@ -17,14 +17,16 @@ ARG ADMIN_INTERNAL_URL=http://admin:3002 # NEXT_PUBLIC_* vars must be present at build time — they are inlined into JS bundles ARG NEXT_PUBLIC_API_URL -ARG NEXT_PUBLIC_MARKETPLACE_URL +ARG NEXT_PUBLIC_HOMEPAGE_URL +ARG NEXT_PUBLIC_STOREFRONT_URL ARG NEXT_PUBLIC_DASHBOARD_URL ARG NEXT_PUBLIC_ADMIN_URL ENV API_INTERNAL_URL=$API_INTERNAL_URL \ DASHBOARD_INTERNAL_URL=$DASHBOARD_INTERNAL_URL \ ADMIN_INTERNAL_URL=$ADMIN_INTERNAL_URL \ NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \ - NEXT_PUBLIC_MARKETPLACE_URL=$NEXT_PUBLIC_MARKETPLACE_URL \ + NEXT_PUBLIC_HOMEPAGE_URL=$NEXT_PUBLIC_HOMEPAGE_URL \ + NEXT_PUBLIC_STOREFRONT_URL=$NEXT_PUBLIC_STOREFRONT_URL \ NEXT_PUBLIC_DASHBOARD_URL=$NEXT_PUBLIC_DASHBOARD_URL \ NEXT_PUBLIC_ADMIN_URL=$NEXT_PUBLIC_ADMIN_URL diff --git a/apps/api/src/modules/reservations/reservation.lifecycle.service.ts b/apps/api/src/modules/reservations/reservation.lifecycle.service.ts index 01163f2..1a76af2 100644 --- a/apps/api/src/modules/reservations/reservation.lifecycle.service.ts +++ b/apps/api/src/modules/reservations/reservation.lifecycle.service.ts @@ -193,7 +193,7 @@ export async function closeReservation(id: string, companyId: string, closedBy: const lang = (company?.brand?.defaultLocale ?? 'fr') as Lang const companyName = company?.brand?.displayName ?? company?.name ?? 'the rental company' const vehicleLabel = `${updated.vehicle.year} ${updated.vehicle.make} ${updated.vehicle.model}` - const reviewUrl = `${process.env.MARKETPLACE_URL ?? 'http://localhost:3000'}/review?token=${reservation.reviewToken}` + const reviewUrl = `${process.env.STOREFRONT_URL ?? 'http://localhost:3000'}/review?token=${reservation.reviewToken}` sendTransactionalEmail({ to: customer.email, diff --git a/apps/api/src/modules/reviews/review.service.test.ts b/apps/api/src/modules/reviews/review.service.test.ts index 4d72020..d0ecf12 100644 --- a/apps/api/src/modules/reviews/review.service.test.ts +++ b/apps/api/src/modules/reviews/review.service.test.ts @@ -49,7 +49,7 @@ describe('review.service', () => { beforeEach(() => { vi.clearAllMocks() vi.setSystemTime(new Date('2026-06-08T12:00:00.000Z')) - process.env.MARKETPLACE_URL = 'https://market.example' + process.env.STOREFRONT_URL = 'https://market.example' }) it('returns reviews with rating filters and pagination metadata', async () => { diff --git a/apps/api/src/modules/reviews/review.service.ts b/apps/api/src/modules/reviews/review.service.ts index 63677b4..cfd94c7 100644 --- a/apps/api/src/modules/reviews/review.service.ts +++ b/apps/api/src/modules/reviews/review.service.ts @@ -61,7 +61,7 @@ export async function sendReviewReminder(id: string, companyId: string) { const companyName = company?.brand?.displayName ?? company?.name ?? 'the rental company' const vehicle = reservation.vehicle const vehicleLabel = vehicle ? `${vehicle.year} ${vehicle.make} ${vehicle.model}` : 'your rental vehicle' - const reviewUrl = `${process.env.MARKETPLACE_URL ?? 'http://localhost:3000'}/review?token=${reservation.reviewToken}` + const reviewUrl = `${process.env.STOREFRONT_URL ?? 'http://localhost:3000'}/review?token=${reservation.reviewToken}` await sendTransactionalEmail({ to: customer.email, diff --git a/apps/api/src/modules/site/site.service.ts b/apps/api/src/modules/site/site.service.ts index 098e732..b7605f0 100644 --- a/apps/api/src/modules/site/site.service.ts +++ b/apps/api/src/modules/site/site.service.ts @@ -30,7 +30,7 @@ function assertAllowedPaymentRedirect(urlValue: string, company: any) { allowedHosts.add('127.0.0.1:3000') allowedHosts.add('127.0.0.1:4000') } - for (const value of [process.env.MARKETPLACE_URL, process.env.DASHBOARD_URL, process.env.NEXT_PUBLIC_MARKETPLACE_URL, process.env.NEXT_PUBLIC_DASHBOARD_URL]) { + for (const value of [process.env.STOREFRONT_URL, process.env.DASHBOARD_URL, process.env.NEXT_PUBLIC_STOREFRONT_URL, process.env.NEXT_PUBLIC_DASHBOARD_URL]) { if (!value) continue try { allowedHosts.add(new URL(value).host) } catch {} } diff --git a/apps/dashboard/src/app/forgot-password/ForgotPasswordPageClient.tsx b/apps/dashboard/src/app/forgot-password/ForgotPasswordPageClient.tsx index 1fcf1a1..c10f2e7 100644 --- a/apps/dashboard/src/app/forgot-password/ForgotPasswordPageClient.tsx +++ b/apps/dashboard/src/app/forgot-password/ForgotPasswordPageClient.tsx @@ -6,7 +6,7 @@ import { useState } from "react"; import { useDashboardI18n } from "@/components/I18nProvider"; import PublicShell from "@/components/layout/PublicShell"; import { API_BASE } from "@/lib/api"; -import { marketplaceUrl } from "@/lib/urls"; +import { storefrontUrl } from "@/lib/urls"; const DASHBOARD_LOGO_SRC = "/dashboard/rentaldrivego.png"; @@ -97,7 +97,7 @@ export default function ForgotPasswordPageClient({
- + RentalDriveGo
- RentalDriveGo + RentalDriveGo

Set up your account

Step {step} of 3

diff --git a/apps/dashboard/src/app/sign-in/[[...sign-in]]/SignInPageClient.tsx b/apps/dashboard/src/app/sign-in/[[...sign-in]]/SignInPageClient.tsx index d19bad8..08b13a1 100644 --- a/apps/dashboard/src/app/sign-in/[[...sign-in]]/SignInPageClient.tsx +++ b/apps/dashboard/src/app/sign-in/[[...sign-in]]/SignInPageClient.tsx @@ -6,7 +6,7 @@ import { useEffect, useRef, useState } from "react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useDashboardI18n } from "@/components/I18nProvider"; import PublicShell from "@/components/layout/PublicShell"; -import { adminUrl, marketplaceUrl } from "@/lib/urls"; +import { adminUrl, storefrontUrl } from "@/lib/urls"; import { API_BASE, EMPLOYEE_PROFILE_KEY } from "@/lib/api"; import { toPublicDashboardPath } from "@/lib/dashboardPaths"; @@ -177,7 +177,7 @@ export default function SignInPageClient({
- + RentalDriveGo
- + RentalDriveGo = [ @@ -36,17 +36,17 @@ function getFooterContent(language: DashboardLanguage): { case 'fr': return { primary: [ - { label: 'À propos de nous', href: `${marketplaceUrl}/footer/about-us` }, - { label: 'Conditions d’utilisation', href: `${marketplaceUrl}/footer/terms-of-service` }, - { label: 'Sécurité', href: `${marketplaceUrl}/footer/security` }, - { label: 'Conformité', href: `${marketplaceUrl}/footer/compliance` }, + { label: 'À propos de nous', href: `${storefrontUrl}/footer/about-us` }, + { label: 'Conditions d’utilisation', href: `${storefrontUrl}/footer/terms-of-service` }, + { label: 'Sécurité', href: `${storefrontUrl}/footer/security` }, + { label: 'Conformité', href: `${storefrontUrl}/footer/compliance` }, { label: 'Politique de confidentialité', href: appPrivacyHref.fr }, - { label: 'Politique relative aux cookies', href: `${marketplaceUrl}/footer/cookie-policy` }, + { label: 'Politique relative aux cookies', href: `${storefrontUrl}/footer/cookie-policy` }, ], secondary: [ - { label: 'Newsletter', href: `${marketplaceUrl}/footer/newsletter` }, - { label: 'Contacter les ventes', href: `${marketplaceUrl}/footer/contact-sales` }, - { label: 'Conditions générales', href: `${marketplaceUrl}/footer/general-conditions` }, + { label: 'Newsletter', href: `${storefrontUrl}/footer/newsletter` }, + { label: 'Contacter les ventes', href: `${storefrontUrl}/footer/contact-sales` }, + { label: 'Conditions générales', href: `${storefrontUrl}/footer/general-conditions` }, ], localeLabel: 'Europe (French)', rightsLabel: 'Tous droits réservés.', @@ -54,17 +54,17 @@ function getFooterContent(language: DashboardLanguage): { case 'ar': return { primary: [ - { label: 'من نحن', href: `${marketplaceUrl}/footer/about-us` }, - { label: 'شروط الاستخدام', href: `${marketplaceUrl}/footer/terms-of-service` }, - { label: 'الأمان', href: `${marketplaceUrl}/footer/security` }, - { label: 'الامتثال', href: `${marketplaceUrl}/footer/compliance` }, + { label: 'من نحن', href: `${storefrontUrl}/footer/about-us` }, + { label: 'شروط الاستخدام', href: `${storefrontUrl}/footer/terms-of-service` }, + { label: 'الأمان', href: `${storefrontUrl}/footer/security` }, + { label: 'الامتثال', href: `${storefrontUrl}/footer/compliance` }, { label: 'سياسة الخصوصية', href: appPrivacyHref.ar }, - { label: 'سياسة ملفات تعريف الارتباط', href: `${marketplaceUrl}/footer/cookie-policy` }, + { label: 'سياسة ملفات تعريف الارتباط', href: `${storefrontUrl}/footer/cookie-policy` }, ], secondary: [ - { label: 'النشرة الإخبارية', href: `${marketplaceUrl}/footer/newsletter` }, - { label: 'تواصل مع المبيعات', href: `${marketplaceUrl}/footer/contact-sales` }, - { label: 'الشروط العامة', href: `${marketplaceUrl}/footer/general-conditions` }, + { label: 'النشرة الإخبارية', href: `${storefrontUrl}/footer/newsletter` }, + { label: 'تواصل مع المبيعات', href: `${storefrontUrl}/footer/contact-sales` }, + { label: 'الشروط العامة', href: `${storefrontUrl}/footer/general-conditions` }, ], localeLabel: 'North Africa (Arabic)', rightsLabel: 'جميع الحقوق محفوظة.', @@ -73,17 +73,17 @@ function getFooterContent(language: DashboardLanguage): { default: return { primary: [ - { label: 'About Us', href: `${marketplaceUrl}/footer/about-us` }, - { label: 'Terms of Service', href: `${marketplaceUrl}/footer/terms-of-service` }, - { label: 'Security', href: `${marketplaceUrl}/footer/security` }, - { label: 'Compliance', href: `${marketplaceUrl}/footer/compliance` }, + { label: 'About Us', href: `${storefrontUrl}/footer/about-us` }, + { label: 'Terms of Service', href: `${storefrontUrl}/footer/terms-of-service` }, + { label: 'Security', href: `${storefrontUrl}/footer/security` }, + { label: 'Compliance', href: `${storefrontUrl}/footer/compliance` }, { label: 'Privacy Policy', href: appPrivacyHref.en }, - { label: 'Cookie Policy', href: `${marketplaceUrl}/footer/cookie-policy` }, + { label: 'Cookie Policy', href: `${storefrontUrl}/footer/cookie-policy` }, ], secondary: [ - { label: 'Newsletter', href: `${marketplaceUrl}/footer/newsletter` }, - { label: 'Contact Sales', href: `${marketplaceUrl}/footer/contact-sales` }, - { label: 'General Conditions', href: `${marketplaceUrl}/footer/general-conditions` }, + { label: 'Newsletter', href: `${storefrontUrl}/footer/newsletter` }, + { label: 'Contact Sales', href: `${storefrontUrl}/footer/contact-sales` }, + { label: 'General Conditions', href: `${storefrontUrl}/footer/general-conditions` }, ], localeLabel: 'Global (English)', rightsLabel: 'All rights reserved.', diff --git a/apps/dashboard/src/components/layout/PublicHeader.tsx b/apps/dashboard/src/components/layout/PublicHeader.tsx index 240bb8a..063fe0b 100644 --- a/apps/dashboard/src/components/layout/PublicHeader.tsx +++ b/apps/dashboard/src/components/layout/PublicHeader.tsx @@ -4,7 +4,7 @@ import { ChevronDown } from "lucide-react"; import Image from "next/image"; import { useEffect, useRef, useState } from "react"; import { useDashboardI18n } from "@/components/I18nProvider"; -import { marketplaceUrl } from "@/lib/urls"; +import { storefrontUrl } from "@/lib/urls"; const BRAND_LOGO_SRC = "/dashboard/rentaldrivego.png"; @@ -84,16 +84,16 @@ export default function PublicHeader({ setThemeMenuOpen((open) => !open); } - const signInHref = `${marketplaceUrl}/${language}#signin`; + const signInHref = `${storefrontUrl}/${language}#signin`; - const signUpHref = `${marketplaceUrl}/${language}#demo`; + const signUpHref = `${storefrontUrl}/${language}#demo`; return (
diff --git a/apps/dashboard/src/components/layout/Sidebar.tsx b/apps/dashboard/src/components/layout/Sidebar.tsx index 57f3abd..8519d04 100644 --- a/apps/dashboard/src/components/layout/Sidebar.tsx +++ b/apps/dashboard/src/components/layout/Sidebar.tsx @@ -24,7 +24,7 @@ import { AlertTriangle, } from 'lucide-react' import { DashboardLanguageSwitcher, DashboardThemeSwitcher, useDashboardI18n } from '@/components/I18nProvider' -import { marketplaceUrl } from '@/lib/urls' +import { storefrontUrl } from '@/lib/urls' import { EMPLOYEE_PROFILE_KEY, apiFetch } from '@/lib/api' import { toDashboardAppPath } from '@/lib/dashboardPaths' import { SHARED_LANGUAGE_KEY, readCurrentUserScopedPreference } from '@/lib/preferences' @@ -351,7 +351,7 @@ export default function Sidebar() { void fetch('/dashboard/api/v1/auth/employee/logout', { method: 'POST', credentials: 'include' }) window.dispatchEvent(new CustomEvent('rentaldrivego:auth-changed')) notifyParent({ type: 'rentaldrivego:employee-logout' }) - window.location.href = marketplaceUrl + window.location.href = storefrontUrl } return ( @@ -388,7 +388,7 @@ export default function Sidebar() { ].join(' ')} > diff --git a/apps/dashboard/src/lib/urls.test.ts b/apps/dashboard/src/lib/urls.test.ts index 651af3c..2319f8f 100644 --- a/apps/dashboard/src/lib/urls.test.ts +++ b/apps/dashboard/src/lib/urls.test.ts @@ -9,7 +9,7 @@ function installWindow(hostname: string, protocol = 'https:') { async function loadUrls(env: Record = {}) { vi.resetModules() - for (const key of ['NEXT_PUBLIC_MARKETPLACE_URL', 'NEXT_PUBLIC_ADMIN_URL']) { + for (const key of ['NEXT_PUBLIC_STOREFRONT_URL', 'NEXT_PUBLIC_ADMIN_URL']) { if (env[key] === undefined) { delete process.env[key] } else { @@ -21,44 +21,44 @@ async function loadUrls(env: Record = {}) { afterEach(() => { Reflect.deleteProperty(globalThis, 'window') - delete process.env.NEXT_PUBLIC_MARKETPLACE_URL + delete process.env.NEXT_PUBLIC_STOREFRONT_URL delete process.env.NEXT_PUBLIC_ADMIN_URL vi.resetModules() }) describe('dashboard cross-app URLs', () => { it('uses default marketplace and admin bases on the server', async () => { - const { marketplaceUrl, adminUrl } = await loadUrls() + const { storefrontUrl, adminUrl } = await loadUrls() - expect(marketplaceUrl).toBe('http://localhost:3000') + expect(storefrontUrl).toBe('http://localhost:3000') expect(adminUrl).toBe('http://localhost:3000/admin') }) it('strips trailing slashes from configured absolute URLs', async () => { - const { marketplaceUrl, adminUrl } = await loadUrls({ - NEXT_PUBLIC_MARKETPLACE_URL: 'https://market.example.com/', + const { storefrontUrl, adminUrl } = await loadUrls({ + NEXT_PUBLIC_STOREFRONT_URL: 'https://market.example.com/', NEXT_PUBLIC_ADMIN_URL: 'https://ops.example.com/admin/', }) - expect(marketplaceUrl).toBe('https://market.example.com') + expect(storefrontUrl).toBe('https://market.example.com') expect(adminUrl).toBe('https://ops.example.com/admin') }) it('rewrites local browser fallbacks onto the current production host', async () => { installWindow('tenant.rentaldrivego.com', 'https:') - const { marketplaceUrl, adminUrl } = await loadUrls() + const { storefrontUrl, adminUrl } = await loadUrls() - expect(marketplaceUrl).toBe('https://tenant.rentaldrivego.com') + expect(storefrontUrl).toBe('https://tenant.rentaldrivego.com') expect(adminUrl).toBe('https://tenant.rentaldrivego.com/admin') }) it('preserves non-URL values after normalizing their trailing slash', async () => { - const { marketplaceUrl, adminUrl } = await loadUrls({ - NEXT_PUBLIC_MARKETPLACE_URL: '/marketplace/', + const { storefrontUrl, adminUrl } = await loadUrls({ + NEXT_PUBLIC_STOREFRONT_URL: '/marketplace/', NEXT_PUBLIC_ADMIN_URL: '/admin/', }) - expect(marketplaceUrl).toBe('/marketplace') + expect(storefrontUrl).toBe('/marketplace') expect(adminUrl).toBe('/admin') }) }) diff --git a/apps/dashboard/src/lib/urls.ts b/apps/dashboard/src/lib/urls.ts index 95ebdaa..7acabbf 100644 --- a/apps/dashboard/src/lib/urls.ts +++ b/apps/dashboard/src/lib/urls.ts @@ -9,8 +9,8 @@ function toAppBase(url: string): string { } } -export const marketplaceUrl = toAppBase( - resolveBrowserAppUrl(process.env.NEXT_PUBLIC_MARKETPLACE_URL ?? 'http://localhost:3000'), +export const storefrontUrl = toAppBase( + resolveBrowserAppUrl(process.env.NEXT_PUBLIC_STOREFRONT_URL ?? 'http://localhost:3000'), ) export const adminUrl = toAppBase( diff --git a/apps/dashboard/src/middleware.test.ts b/apps/dashboard/src/middleware.test.ts index e1deda3..e8e04d9 100644 --- a/apps/dashboard/src/middleware.test.ts +++ b/apps/dashboard/src/middleware.test.ts @@ -37,9 +37,9 @@ function request(input: string, options: { token?: string; headers?: Record { }) afterEach(() => { - delete process.env.NEXT_PUBLIC_MARKETPLACE_URL + delete process.env.NEXT_PUBLIC_STOREFRONT_URL vi.resetModules() }) diff --git a/apps/dashboard/src/middleware.ts b/apps/dashboard/src/middleware.ts index 40f1346..27faa1e 100644 --- a/apps/dashboard/src/middleware.ts +++ b/apps/dashboard/src/middleware.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' -const MARKETPLACE_URL = process.env.NEXT_PUBLIC_MARKETPLACE_URL ?? 'http://localhost:3000' +const STOREFRONT_URL = process.env.NEXT_PUBLIC_STOREFRONT_URL ?? 'http://localhost:3000' const DASHBOARD_BASE_PATH = '/dashboard' function toDashboardAppPath(pathname: string): string { @@ -32,7 +32,7 @@ function deduplicatePublicDashboardPath(pathname: string): string | null { function resolveProxyUrl(req: NextRequest, pathname: string): URL { const forwardedHost = req.headers.get('x-forwarded-host') const forwardedProto = req.headers.get('x-forwarded-proto') - const marketplaceOrigin = new URL(MARKETPLACE_URL) + const storefrontOrigin = new URL(STOREFRONT_URL) const url = new URL(pathname, req.nextUrl.origin) if (forwardedHost && !isInternalHost(forwardedHost)) { @@ -42,8 +42,8 @@ function resolveProxyUrl(req: NextRequest, pathname: string): URL { url.host = req.nextUrl.host url.protocol = req.nextUrl.protocol } else { - url.host = marketplaceOrigin.host - url.protocol = marketplaceOrigin.protocol + url.host = storefrontOrigin.host + url.protocol = storefrontOrigin.protocol } return url } @@ -51,7 +51,7 @@ function resolveProxyUrl(req: NextRequest, pathname: string): URL { function isInternalHost(host: string | null): boolean { if (!host) return false const hostname = host.split(':')[0]?.toLowerCase() - return ['host.docker.internal', 'dashboard', 'admin', 'api', 'marketplace'].includes(hostname) + return ['host.docker.internal', 'dashboard', 'admin', 'api', 'homepage', 'storefront'].includes(hostname) } function isProtectedRoute(req: NextRequest) {