add: NEXT_PUBLIC_STOREFRONT_URL and NEXT_PUBLIC_HOMEPAGE_URL. Remove NEXT_PUBLIC_MARKETPLACE_URL.
Build & Deploy / Build & Push Docker Image (push) Failing after 1m5s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 32s
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 / Build & Push Docker Image (push) Failing after 1m5s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 32s
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
This commit is contained in:
@@ -53,13 +53,15 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
|
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_DASHBOARD_URL: ${{ secrets.NEXT_PUBLIC_DASHBOARD_URL }}
|
||||||
NEXT_PUBLIC_ADMIN_URL: ${{ secrets.NEXT_PUBLIC_ADMIN_URL }}
|
NEXT_PUBLIC_ADMIN_URL: ${{ secrets.NEXT_PUBLIC_ADMIN_URL }}
|
||||||
run: |
|
run: |
|
||||||
for variable in \
|
for variable in \
|
||||||
NEXT_PUBLIC_API_URL \
|
NEXT_PUBLIC_API_URL \
|
||||||
NEXT_PUBLIC_MARKETPLACE_URL \
|
NEXT_PUBLIC_HOMEPAGE_URL \
|
||||||
|
NEXT_PUBLIC_STOREFRONT_URL \
|
||||||
NEXT_PUBLIC_DASHBOARD_URL \
|
NEXT_PUBLIC_DASHBOARD_URL \
|
||||||
NEXT_PUBLIC_ADMIN_URL
|
NEXT_PUBLIC_ADMIN_URL
|
||||||
do
|
do
|
||||||
@@ -99,7 +101,8 @@ jobs:
|
|||||||
DASHBOARD_INTERNAL_URL=http://dashboard:3001
|
DASHBOARD_INTERNAL_URL=http://dashboard:3001
|
||||||
ADMIN_INTERNAL_URL=http://admin:3002
|
ADMIN_INTERNAL_URL=http://admin:3002
|
||||||
NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}
|
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_DASHBOARD_URL=${{ secrets.NEXT_PUBLIC_DASHBOARD_URL }}
|
||||||
NEXT_PUBLIC_ADMIN_URL=${{ secrets.NEXT_PUBLIC_ADMIN_URL }}
|
NEXT_PUBLIC_ADMIN_URL=${{ secrets.NEXT_PUBLIC_ADMIN_URL }}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
# NEXT_PUBLIC_* vars must be present at build time — they are inlined into JS bundles
|
||||||
ARG NEXT_PUBLIC_API_URL
|
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_DASHBOARD_URL
|
||||||
ARG NEXT_PUBLIC_ADMIN_URL
|
ARG NEXT_PUBLIC_ADMIN_URL
|
||||||
ENV API_INTERNAL_URL=$API_INTERNAL_URL \
|
ENV API_INTERNAL_URL=$API_INTERNAL_URL \
|
||||||
DASHBOARD_INTERNAL_URL=$DASHBOARD_INTERNAL_URL \
|
DASHBOARD_INTERNAL_URL=$DASHBOARD_INTERNAL_URL \
|
||||||
ADMIN_INTERNAL_URL=$ADMIN_INTERNAL_URL \
|
ADMIN_INTERNAL_URL=$ADMIN_INTERNAL_URL \
|
||||||
NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_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_DASHBOARD_URL=$NEXT_PUBLIC_DASHBOARD_URL \
|
||||||
NEXT_PUBLIC_ADMIN_URL=$NEXT_PUBLIC_ADMIN_URL
|
NEXT_PUBLIC_ADMIN_URL=$NEXT_PUBLIC_ADMIN_URL
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export async function closeReservation(id: string, companyId: string, closedBy:
|
|||||||
const lang = (company?.brand?.defaultLocale ?? 'fr') as Lang
|
const lang = (company?.brand?.defaultLocale ?? 'fr') as Lang
|
||||||
const companyName = company?.brand?.displayName ?? company?.name ?? 'the rental company'
|
const companyName = company?.brand?.displayName ?? company?.name ?? 'the rental company'
|
||||||
const vehicleLabel = `${updated.vehicle.year} ${updated.vehicle.make} ${updated.vehicle.model}`
|
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({
|
sendTransactionalEmail({
|
||||||
to: customer.email,
|
to: customer.email,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ describe('review.service', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
vi.setSystemTime(new Date('2026-06-08T12:00:00.000Z'))
|
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 () => {
|
it('returns reviews with rating filters and pagination metadata', async () => {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export async function sendReviewReminder(id: string, companyId: string) {
|
|||||||
const companyName = company?.brand?.displayName ?? company?.name ?? 'the rental company'
|
const companyName = company?.brand?.displayName ?? company?.name ?? 'the rental company'
|
||||||
const vehicle = reservation.vehicle
|
const vehicle = reservation.vehicle
|
||||||
const vehicleLabel = vehicle ? `${vehicle.year} ${vehicle.make} ${vehicle.model}` : 'your rental 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({
|
await sendTransactionalEmail({
|
||||||
to: customer.email,
|
to: customer.email,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ function assertAllowedPaymentRedirect(urlValue: string, company: any) {
|
|||||||
allowedHosts.add('127.0.0.1:3000')
|
allowedHosts.add('127.0.0.1:3000')
|
||||||
allowedHosts.add('127.0.0.1:4000')
|
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
|
if (!value) continue
|
||||||
try { allowedHosts.add(new URL(value).host) } catch {}
|
try { allowedHosts.add(new URL(value).host) } catch {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useState } from "react";
|
|||||||
import { useDashboardI18n } from "@/components/I18nProvider";
|
import { useDashboardI18n } from "@/components/I18nProvider";
|
||||||
import PublicShell from "@/components/layout/PublicShell";
|
import PublicShell from "@/components/layout/PublicShell";
|
||||||
import { API_BASE } from "@/lib/api";
|
import { API_BASE } from "@/lib/api";
|
||||||
import { marketplaceUrl } from "@/lib/urls";
|
import { storefrontUrl } from "@/lib/urls";
|
||||||
|
|
||||||
const DASHBOARD_LOGO_SRC = "/dashboard/rentaldrivego.png";
|
const DASHBOARD_LOGO_SRC = "/dashboard/rentaldrivego.png";
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ export default function ForgotPasswordPageClient({
|
|||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<a href={marketplaceUrl} target="_top">
|
<a href={storefrontUrl} target="_top">
|
||||||
<Image
|
<Image
|
||||||
src={DASHBOARD_LOGO_SRC}
|
src={DASHBOARD_LOGO_SRC}
|
||||||
alt="RentalDriveGo"
|
alt="RentalDriveGo"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useState } from 'react'
|
|||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { apiFetch } from '@/lib/api'
|
import { apiFetch } from '@/lib/api'
|
||||||
import PublicShell from '@/components/layout/PublicShell'
|
import PublicShell from '@/components/layout/PublicShell'
|
||||||
import { marketplaceUrl } from '@/lib/urls'
|
import { storefrontUrl } from '@/lib/urls'
|
||||||
|
|
||||||
type Step = 1 | 2 | 3
|
type Step = 1 | 2 | 3
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ export default function OnboardingPage() {
|
|||||||
<main className="flex items-center justify-center px-4 py-12">
|
<main className="flex items-center justify-center px-4 py-12">
|
||||||
<div className="w-full max-w-lg">
|
<div className="w-full max-w-lg">
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<a href={marketplaceUrl} className="text-xs font-semibold uppercase tracking-widest text-blue-600">RentalDriveGo</a>
|
<a href={storefrontUrl} className="text-xs font-semibold uppercase tracking-widest text-blue-600">RentalDriveGo</a>
|
||||||
<h1 className="mt-2 text-3xl font-bold text-slate-900">Set up your account</h1>
|
<h1 className="mt-2 text-3xl font-bold text-slate-900">Set up your account</h1>
|
||||||
<p className="mt-1 text-sm text-slate-500">Step {step} of 3</p>
|
<p className="mt-1 text-sm text-slate-500">Step {step} of 3</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useDashboardI18n } from "@/components/I18nProvider";
|
import { useDashboardI18n } from "@/components/I18nProvider";
|
||||||
import PublicShell from "@/components/layout/PublicShell";
|
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 { API_BASE, EMPLOYEE_PROFILE_KEY } from "@/lib/api";
|
||||||
import { toPublicDashboardPath } from "@/lib/dashboardPaths";
|
import { toPublicDashboardPath } from "@/lib/dashboardPaths";
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ export default function SignInPageClient({
|
|||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<a href={marketplaceUrl} target="_top">
|
<a href={storefrontUrl} target="_top">
|
||||||
<Image
|
<Image
|
||||||
src={DASHBOARD_LOGO_SRC}
|
src={DASHBOARD_LOGO_SRC}
|
||||||
alt="RentalDriveGo"
|
alt="RentalDriveGo"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import PublicShell from "@/components/layout/PublicShell";
|
import PublicShell from "@/components/layout/PublicShell";
|
||||||
import { useDashboardI18n } from "@/components/I18nProvider";
|
import { useDashboardI18n } from "@/components/I18nProvider";
|
||||||
import { marketplaceUrl } from "@/lib/urls";
|
import { storefrontUrl } from "@/lib/urls";
|
||||||
|
|
||||||
const DASHBOARD_LOGO_SRC = "/dashboard/rentaldrivego.png";
|
const DASHBOARD_LOGO_SRC = "/dashboard/rentaldrivego.png";
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ export default function SignUpForm({
|
|||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<a href={marketplaceUrl} target="_top">
|
<a href={storefrontUrl} target="_top">
|
||||||
<Image
|
<Image
|
||||||
src={DASHBOARD_LOGO_SRC}
|
src={DASHBOARD_LOGO_SRC}
|
||||||
alt="RentalDriveGo"
|
alt="RentalDriveGo"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
type DashboardLanguage,
|
type DashboardLanguage,
|
||||||
useDashboardI18n,
|
useDashboardI18n,
|
||||||
} from '@/components/I18nProvider'
|
} from '@/components/I18nProvider'
|
||||||
import { marketplaceUrl } from '@/lib/urls'
|
import { storefrontUrl } from '@/lib/urls'
|
||||||
|
|
||||||
type FooterItem = {
|
type FooterItem = {
|
||||||
label: string
|
label: string
|
||||||
@@ -15,9 +15,9 @@ type FooterItem = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appPrivacyHref = {
|
const appPrivacyHref = {
|
||||||
en: `${marketplaceUrl}/app-privacy-en`,
|
en: `${storefrontUrl}/app-privacy-en`,
|
||||||
fr: `${marketplaceUrl}/app-privacy-fr`,
|
fr: `${storefrontUrl}/app-privacy-fr`,
|
||||||
ar: `${marketplaceUrl}/app-privacy-ar`,
|
ar: `${storefrontUrl}/app-privacy-ar`,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const localeOptions: Array<{ value: DashboardLanguage; label: string; flag: string }> = [
|
const localeOptions: Array<{ value: DashboardLanguage; label: string; flag: string }> = [
|
||||||
@@ -36,17 +36,17 @@ function getFooterContent(language: DashboardLanguage): {
|
|||||||
case 'fr':
|
case 'fr':
|
||||||
return {
|
return {
|
||||||
primary: [
|
primary: [
|
||||||
{ label: 'À propos de nous', href: `${marketplaceUrl}/footer/about-us` },
|
{ label: 'À propos de nous', href: `${storefrontUrl}/footer/about-us` },
|
||||||
{ label: 'Conditions d’utilisation', href: `${marketplaceUrl}/footer/terms-of-service` },
|
{ label: 'Conditions d’utilisation', href: `${storefrontUrl}/footer/terms-of-service` },
|
||||||
{ label: 'Sécurité', href: `${marketplaceUrl}/footer/security` },
|
{ label: 'Sécurité', href: `${storefrontUrl}/footer/security` },
|
||||||
{ label: 'Conformité', href: `${marketplaceUrl}/footer/compliance` },
|
{ label: 'Conformité', href: `${storefrontUrl}/footer/compliance` },
|
||||||
{ label: 'Politique de confidentialité', href: appPrivacyHref.fr },
|
{ 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: [
|
secondary: [
|
||||||
{ label: 'Newsletter', href: `${marketplaceUrl}/footer/newsletter` },
|
{ label: 'Newsletter', href: `${storefrontUrl}/footer/newsletter` },
|
||||||
{ label: 'Contacter les ventes', href: `${marketplaceUrl}/footer/contact-sales` },
|
{ label: 'Contacter les ventes', href: `${storefrontUrl}/footer/contact-sales` },
|
||||||
{ label: 'Conditions générales', href: `${marketplaceUrl}/footer/general-conditions` },
|
{ label: 'Conditions générales', href: `${storefrontUrl}/footer/general-conditions` },
|
||||||
],
|
],
|
||||||
localeLabel: 'Europe (French)',
|
localeLabel: 'Europe (French)',
|
||||||
rightsLabel: 'Tous droits réservés.',
|
rightsLabel: 'Tous droits réservés.',
|
||||||
@@ -54,17 +54,17 @@ function getFooterContent(language: DashboardLanguage): {
|
|||||||
case 'ar':
|
case 'ar':
|
||||||
return {
|
return {
|
||||||
primary: [
|
primary: [
|
||||||
{ label: 'من نحن', href: `${marketplaceUrl}/footer/about-us` },
|
{ label: 'من نحن', href: `${storefrontUrl}/footer/about-us` },
|
||||||
{ label: 'شروط الاستخدام', href: `${marketplaceUrl}/footer/terms-of-service` },
|
{ label: 'شروط الاستخدام', href: `${storefrontUrl}/footer/terms-of-service` },
|
||||||
{ label: 'الأمان', href: `${marketplaceUrl}/footer/security` },
|
{ label: 'الأمان', href: `${storefrontUrl}/footer/security` },
|
||||||
{ label: 'الامتثال', href: `${marketplaceUrl}/footer/compliance` },
|
{ label: 'الامتثال', href: `${storefrontUrl}/footer/compliance` },
|
||||||
{ label: 'سياسة الخصوصية', href: appPrivacyHref.ar },
|
{ label: 'سياسة الخصوصية', href: appPrivacyHref.ar },
|
||||||
{ label: 'سياسة ملفات تعريف الارتباط', href: `${marketplaceUrl}/footer/cookie-policy` },
|
{ label: 'سياسة ملفات تعريف الارتباط', href: `${storefrontUrl}/footer/cookie-policy` },
|
||||||
],
|
],
|
||||||
secondary: [
|
secondary: [
|
||||||
{ label: 'النشرة الإخبارية', href: `${marketplaceUrl}/footer/newsletter` },
|
{ label: 'النشرة الإخبارية', href: `${storefrontUrl}/footer/newsletter` },
|
||||||
{ label: 'تواصل مع المبيعات', href: `${marketplaceUrl}/footer/contact-sales` },
|
{ label: 'تواصل مع المبيعات', href: `${storefrontUrl}/footer/contact-sales` },
|
||||||
{ label: 'الشروط العامة', href: `${marketplaceUrl}/footer/general-conditions` },
|
{ label: 'الشروط العامة', href: `${storefrontUrl}/footer/general-conditions` },
|
||||||
],
|
],
|
||||||
localeLabel: 'North Africa (Arabic)',
|
localeLabel: 'North Africa (Arabic)',
|
||||||
rightsLabel: 'جميع الحقوق محفوظة.',
|
rightsLabel: 'جميع الحقوق محفوظة.',
|
||||||
@@ -73,17 +73,17 @@ function getFooterContent(language: DashboardLanguage): {
|
|||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
primary: [
|
primary: [
|
||||||
{ label: 'About Us', href: `${marketplaceUrl}/footer/about-us` },
|
{ label: 'About Us', href: `${storefrontUrl}/footer/about-us` },
|
||||||
{ label: 'Terms of Service', href: `${marketplaceUrl}/footer/terms-of-service` },
|
{ label: 'Terms of Service', href: `${storefrontUrl}/footer/terms-of-service` },
|
||||||
{ label: 'Security', href: `${marketplaceUrl}/footer/security` },
|
{ label: 'Security', href: `${storefrontUrl}/footer/security` },
|
||||||
{ label: 'Compliance', href: `${marketplaceUrl}/footer/compliance` },
|
{ label: 'Compliance', href: `${storefrontUrl}/footer/compliance` },
|
||||||
{ label: 'Privacy Policy', href: appPrivacyHref.en },
|
{ label: 'Privacy Policy', href: appPrivacyHref.en },
|
||||||
{ label: 'Cookie Policy', href: `${marketplaceUrl}/footer/cookie-policy` },
|
{ label: 'Cookie Policy', href: `${storefrontUrl}/footer/cookie-policy` },
|
||||||
],
|
],
|
||||||
secondary: [
|
secondary: [
|
||||||
{ label: 'Newsletter', href: `${marketplaceUrl}/footer/newsletter` },
|
{ label: 'Newsletter', href: `${storefrontUrl}/footer/newsletter` },
|
||||||
{ label: 'Contact Sales', href: `${marketplaceUrl}/footer/contact-sales` },
|
{ label: 'Contact Sales', href: `${storefrontUrl}/footer/contact-sales` },
|
||||||
{ label: 'General Conditions', href: `${marketplaceUrl}/footer/general-conditions` },
|
{ label: 'General Conditions', href: `${storefrontUrl}/footer/general-conditions` },
|
||||||
],
|
],
|
||||||
localeLabel: 'Global (English)',
|
localeLabel: 'Global (English)',
|
||||||
rightsLabel: 'All rights reserved.',
|
rightsLabel: 'All rights reserved.',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ChevronDown } from "lucide-react";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useDashboardI18n } from "@/components/I18nProvider";
|
import { useDashboardI18n } from "@/components/I18nProvider";
|
||||||
import { marketplaceUrl } from "@/lib/urls";
|
import { storefrontUrl } from "@/lib/urls";
|
||||||
|
|
||||||
const BRAND_LOGO_SRC = "/dashboard/rentaldrivego.png";
|
const BRAND_LOGO_SRC = "/dashboard/rentaldrivego.png";
|
||||||
|
|
||||||
@@ -84,16 +84,16 @@ export default function PublicHeader({
|
|||||||
setThemeMenuOpen((open) => !open);
|
setThemeMenuOpen((open) => !open);
|
||||||
}
|
}
|
||||||
|
|
||||||
const signInHref = `${marketplaceUrl}/${language}#signin`;
|
const signInHref = `${storefrontUrl}/${language}#signin`;
|
||||||
|
|
||||||
const signUpHref = `${marketplaceUrl}/${language}#demo`;
|
const signUpHref = `${storefrontUrl}/${language}#demo`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 border-b border-stone-200/80 bg-white/78 backdrop-blur-xl shadow-[0_10px_30px_rgba(15,23,42,0.08)] transition-colors dark:border-blue-900 dark:bg-blue-950/76 dark:shadow-[0_10px_30px_rgba(0,0,0,0.32)]">
|
<header className="sticky top-0 z-50 border-b border-stone-200/80 bg-white/78 backdrop-blur-xl shadow-[0_10px_30px_rgba(15,23,42,0.08)] transition-colors dark:border-blue-900 dark:bg-blue-950/76 dark:shadow-[0_10px_30px_rgba(0,0,0,0.32)]">
|
||||||
<div className="mx-auto flex min-h-14 max-w-7xl flex-col gap-1.5 px-4 py-1.5 sm:px-6 lg:flex-row lg:items-center lg:justify-between lg:gap-3 lg:px-8 lg:py-2">
|
<div className="mx-auto flex min-h-14 max-w-7xl flex-col gap-1.5 px-4 py-1.5 sm:px-6 lg:flex-row lg:items-center lg:justify-between lg:gap-3 lg:px-8 lg:py-2">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<a
|
<a
|
||||||
href={`${marketplaceUrl}/${language}`}
|
href={`${storefrontUrl}/${language}`}
|
||||||
className="flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-[0.14em] text-stone-900 no-underline sm:text-sm sm:tracking-[0.2em] dark:text-stone-100"
|
className="flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-[0.14em] text-stone-900 no-underline sm:text-sm sm:tracking-[0.2em] dark:text-stone-100"
|
||||||
aria-label="RentalDriveGo home"
|
aria-label="RentalDriveGo home"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { DashboardLanguageSwitcher, DashboardThemeSwitcher, useDashboardI18n } from '@/components/I18nProvider'
|
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 { EMPLOYEE_PROFILE_KEY, apiFetch } from '@/lib/api'
|
||||||
import { toDashboardAppPath } from '@/lib/dashboardPaths'
|
import { toDashboardAppPath } from '@/lib/dashboardPaths'
|
||||||
import { SHARED_LANGUAGE_KEY, readCurrentUserScopedPreference } from '@/lib/preferences'
|
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' })
|
void fetch('/dashboard/api/v1/auth/employee/logout', { method: 'POST', credentials: 'include' })
|
||||||
window.dispatchEvent(new CustomEvent('rentaldrivego:auth-changed'))
|
window.dispatchEvent(new CustomEvent('rentaldrivego:auth-changed'))
|
||||||
notifyParent({ type: 'rentaldrivego:employee-logout' })
|
notifyParent({ type: 'rentaldrivego:employee-logout' })
|
||||||
window.location.href = marketplaceUrl
|
window.location.href = storefrontUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -388,7 +388,7 @@ export default function Sidebar() {
|
|||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={marketplaceUrl}
|
href={storefrontUrl}
|
||||||
target="_top"
|
target="_top"
|
||||||
className="flex items-center gap-3 border-b border-blue-200/70 px-5 py-5 transition-colors dark:border-blue-400/10"
|
className="flex items-center gap-3 border-b border-blue-200/70 px-5 py-5 transition-colors dark:border-blue-400/10"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function installWindow(hostname: string, protocol = 'https:') {
|
|||||||
|
|
||||||
async function loadUrls(env: Record<string, string | undefined> = {}) {
|
async function loadUrls(env: Record<string, string | undefined> = {}) {
|
||||||
vi.resetModules()
|
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) {
|
if (env[key] === undefined) {
|
||||||
delete process.env[key]
|
delete process.env[key]
|
||||||
} else {
|
} else {
|
||||||
@@ -21,44 +21,44 @@ async function loadUrls(env: Record<string, string | undefined> = {}) {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
Reflect.deleteProperty(globalThis, 'window')
|
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
|
delete process.env.NEXT_PUBLIC_ADMIN_URL
|
||||||
vi.resetModules()
|
vi.resetModules()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('dashboard cross-app URLs', () => {
|
describe('dashboard cross-app URLs', () => {
|
||||||
it('uses default marketplace and admin bases on the server', async () => {
|
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')
|
expect(adminUrl).toBe('http://localhost:3000/admin')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('strips trailing slashes from configured absolute URLs', async () => {
|
it('strips trailing slashes from configured absolute URLs', async () => {
|
||||||
const { marketplaceUrl, adminUrl } = await loadUrls({
|
const { storefrontUrl, adminUrl } = await loadUrls({
|
||||||
NEXT_PUBLIC_MARKETPLACE_URL: 'https://market.example.com/',
|
NEXT_PUBLIC_STOREFRONT_URL: 'https://market.example.com/',
|
||||||
NEXT_PUBLIC_ADMIN_URL: 'https://ops.example.com/admin/',
|
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')
|
expect(adminUrl).toBe('https://ops.example.com/admin')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rewrites local browser fallbacks onto the current production host', async () => {
|
it('rewrites local browser fallbacks onto the current production host', async () => {
|
||||||
installWindow('tenant.rentaldrivego.com', 'https:')
|
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')
|
expect(adminUrl).toBe('https://tenant.rentaldrivego.com/admin')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('preserves non-URL values after normalizing their trailing slash', async () => {
|
it('preserves non-URL values after normalizing their trailing slash', async () => {
|
||||||
const { marketplaceUrl, adminUrl } = await loadUrls({
|
const { storefrontUrl, adminUrl } = await loadUrls({
|
||||||
NEXT_PUBLIC_MARKETPLACE_URL: '/marketplace/',
|
NEXT_PUBLIC_STOREFRONT_URL: '/marketplace/',
|
||||||
NEXT_PUBLIC_ADMIN_URL: '/admin/',
|
NEXT_PUBLIC_ADMIN_URL: '/admin/',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(marketplaceUrl).toBe('/marketplace')
|
expect(storefrontUrl).toBe('/marketplace')
|
||||||
expect(adminUrl).toBe('/admin')
|
expect(adminUrl).toBe('/admin')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ function toAppBase(url: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const marketplaceUrl = toAppBase(
|
export const storefrontUrl = toAppBase(
|
||||||
resolveBrowserAppUrl(process.env.NEXT_PUBLIC_MARKETPLACE_URL ?? 'http://localhost:3000'),
|
resolveBrowserAppUrl(process.env.NEXT_PUBLIC_STOREFRONT_URL ?? 'http://localhost:3000'),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const adminUrl = toAppBase(
|
export const adminUrl = toAppBase(
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ function request(input: string, options: { token?: string; headers?: Record<stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadMiddleware(marketplaceUrl = 'https://market.example.com') {
|
async function loadMiddleware(storefrontUrl = 'https://market.example.com') {
|
||||||
vi.resetModules()
|
vi.resetModules()
|
||||||
process.env.NEXT_PUBLIC_MARKETPLACE_URL = marketplaceUrl
|
process.env.NEXT_PUBLIC_STOREFRONT_URL = storefrontUrl
|
||||||
return import('./middleware')
|
return import('./middleware')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ beforeEach(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
delete process.env.NEXT_PUBLIC_MARKETPLACE_URL
|
delete process.env.NEXT_PUBLIC_STOREFRONT_URL
|
||||||
vi.resetModules()
|
vi.resetModules()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import type { NextRequest } 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'
|
const DASHBOARD_BASE_PATH = '/dashboard'
|
||||||
|
|
||||||
function toDashboardAppPath(pathname: string): string {
|
function toDashboardAppPath(pathname: string): string {
|
||||||
@@ -32,7 +32,7 @@ function deduplicatePublicDashboardPath(pathname: string): string | null {
|
|||||||
function resolveProxyUrl(req: NextRequest, pathname: string): URL {
|
function resolveProxyUrl(req: NextRequest, pathname: string): URL {
|
||||||
const forwardedHost = req.headers.get('x-forwarded-host')
|
const forwardedHost = req.headers.get('x-forwarded-host')
|
||||||
const forwardedProto = req.headers.get('x-forwarded-proto')
|
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)
|
const url = new URL(pathname, req.nextUrl.origin)
|
||||||
if (forwardedHost && !isInternalHost(forwardedHost)) {
|
if (forwardedHost && !isInternalHost(forwardedHost)) {
|
||||||
@@ -42,8 +42,8 @@ function resolveProxyUrl(req: NextRequest, pathname: string): URL {
|
|||||||
url.host = req.nextUrl.host
|
url.host = req.nextUrl.host
|
||||||
url.protocol = req.nextUrl.protocol
|
url.protocol = req.nextUrl.protocol
|
||||||
} else {
|
} else {
|
||||||
url.host = marketplaceOrigin.host
|
url.host = storefrontOrigin.host
|
||||||
url.protocol = marketplaceOrigin.protocol
|
url.protocol = storefrontOrigin.protocol
|
||||||
}
|
}
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ function resolveProxyUrl(req: NextRequest, pathname: string): URL {
|
|||||||
function isInternalHost(host: string | null): boolean {
|
function isInternalHost(host: string | null): boolean {
|
||||||
if (!host) return false
|
if (!host) return false
|
||||||
const hostname = host.split(':')[0]?.toLowerCase()
|
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) {
|
function isProtectedRoute(req: NextRequest) {
|
||||||
|
|||||||
Reference in New Issue
Block a user