- )
-}
diff --git a/carplace/src/components/public/index.ts b/carplace/src/components/public/index.ts
deleted file mode 100644
index b900cd1..0000000
--- a/carplace/src/components/public/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-'use client'
-
-export { default as SiteFooter } from './SiteFooter'
-export { default as SiteNavbar } from './SiteNavbar'
-export { default as SitePageLayout } from './SitePageLayout'
diff --git a/carplace/src/lib/api.test.ts b/carplace/src/lib/api.test.ts
deleted file mode 100644
index 53e871f..0000000
--- a/carplace/src/lib/api.test.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { afterEach, describe, expect, it, vi } from 'vitest'
-import { CarplaceApiError, carplaceFetch, carplaceFetchOrDefault, carplacePost } from './api'
-
-afterEach(() => {
- delete process.env.NEXT_PUBLIC_API_URL
- delete process.env.API_INTERNAL_URL
- vi.restoreAllMocks()
- Reflect.deleteProperty(globalThis, 'fetch')
-})
-
-describe('carplace 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(carplaceFetch('/carplace/vehicles')).resolves.toEqual([{ id: 'vehicle_1' }])
- expect(fetchMock).toHaveBeenCalledWith(expect.stringMatching(/\/api\/v1\/carplace\/vehicles$/), {
- cache: 'no-store',
- credentials: 'include',
- })
- })
-
- it('throws rich carplace API errors', async () => {
- Object.defineProperty(globalThis, 'fetch', {
- configurable: true,
- value: vi.fn(async () => ({
- ok: false,
- status: 409,
- json: async () => ({ message: 'Vehicle unavailable', error: 'VEHICLE_UNAVAILABLE', nextAvailableAt: '2026-07-01T10:00:00.000Z' }),
- })),
- })
-
- await expect(carplaceFetch('/carplace/book')).rejects.toMatchObject({
- name: 'CarplaceApiError',
- message: 'Vehicle unavailable',
- status: 409,
- code: 'VEHICLE_UNAVAILABLE',
- nextAvailableAt: '2026-07-01T10:00:00.000Z',
- })
- })
-
- it('returns fallbacks when GET requests fail', async () => {
- Object.defineProperty(globalThis, 'fetch', {
- configurable: true,
- value: vi.fn(async () => ({ ok: false, status: 500, json: async () => ({ message: 'Down' }) })),
- })
-
- await expect(carplaceFetchOrDefault('/carplace/homepage', { hero: null })).resolves.toEqual({ hero: null })
- })
-
- it('posts JSON payloads and unwraps successful responses', async () => {
- process.env.NEXT_PUBLIC_API_URL = 'https://api.example.com/api/v1'
- const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: { id: 'reservation_1' } }) }))
- Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
-
- await expect(carplacePost('/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',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ vehicleId: 'vehicle_1' }),
- }))
- })
-
- it('uses a generic error message when the response body is not JSON', async () => {
- Object.defineProperty(globalThis, 'fetch', {
- configurable: true,
- value: vi.fn(async () => ({ ok: false, status: 502, json: async () => { throw new Error('bad json') } })),
- })
-
- await expect(carplaceFetch('/site/homepage')).rejects.toMatchObject({
- name: 'CarplaceApiError',
- message: 'Request failed',
- status: 502,
- })
- })
-})
diff --git a/carplace/src/lib/api.ts b/carplace/src/lib/api.ts
deleted file mode 100644
index 3b4935b..0000000
--- a/carplace/src/lib/api.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-const API_BASE =
- (typeof window === 'undefined' ? process.env.API_INTERNAL_URL : process.env.NEXT_PUBLIC_API_URL)
- ?? process.env.NEXT_PUBLIC_API_URL
- ?? 'http://localhost:4000/api/v1'
-
-export class CarplaceApiError extends Error {
- status: number
- code?: string
- nextAvailableAt?: string | null
-
- constructor(message: string, status: number, code?: string, nextAvailableAt?: string | null) {
- super(message)
- this.name = 'CarplaceApiError'
- this.status = status
- this.code = code
- this.nextAvailableAt = nextAvailableAt
- }
-}
-
-export async function carplaceFetch(path: string, init?: RequestInit): Promise {
- 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 CarplaceApiError(json?.message ?? 'Request failed', res.status, json?.error, json?.nextAvailableAt)
- return json.data as T
-}
-
-export async function carplaceFetchOrDefault(path: string, fallback: T): Promise {
- try {
- return await carplaceFetch(path)
- } catch {
- return fallback
- }
-}
-
-export async function carplacePost(path: string, body: unknown): Promise {
- const base = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1'
- const res = await fetch(`${base}${path}`, {
- method: 'POST',
- credentials: 'include',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(body),
- })
- const json = await res.json().catch(() => null)
- if (!res.ok) throw new CarplaceApiError(json?.message ?? 'Request failed', res.status, json?.error, json?.nextAvailableAt)
- return json.data as T
-}
diff --git a/carplace/src/lib/appUrls.test.ts b/carplace/src/lib/appUrls.test.ts
deleted file mode 100644
index 480f927..0000000
--- a/carplace/src/lib/appUrls.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { afterEach, describe, expect, it } from 'vitest'
-import { resolveBrowserAppUrl, resolveServerAppUrl } from './appUrls'
-
-function installWindow(hostname: string, protocol = 'https:') {
- Object.defineProperty(globalThis, 'window', {
- configurable: true,
- value: { location: { hostname, protocol } },
- })
-}
-
-afterEach(() => {
- Reflect.deleteProperty(globalThis, 'window')
-})
-
-describe('carplace app URL resolution', () => {
- it('leaves server-side browser fallback unchanged when window is unavailable', () => {
- expect(resolveBrowserAppUrl('http://localhost:3000')).toBe('http://localhost:3000')
- })
-
- it('preserves localhost fallbacks but removes trailing slash', () => {
- installWindow('localhost')
- expect(resolveBrowserAppUrl('http://localhost:3000/')).toBe('http://localhost:3000')
- })
-
- it('rewrites production browser fallbacks to the active host and protocol', () => {
- installWindow('www.rentaldrivego.ma', 'https:')
- expect(resolveBrowserAppUrl('http://localhost:3000')).toBe('https://www.rentaldrivego.ma')
- })
-
- it('keeps path prefixes while rewriting the browser origin', () => {
- installWindow('rental.example.com', 'https:')
- expect(resolveBrowserAppUrl('http://localhost:3000/carplace')).toBe('https://rental.example.com/carplace')
- })
-
- it('resolves server URLs from forwarded host/proto and handles invalid fallbacks safely', () => {
- expect(resolveServerAppUrl('http://localhost:3000', 'market.example.com', 'https')).toBe('https://market.example.com:3000')
- expect(resolveServerAppUrl('http://localhost:3000', null)).toBe('http://localhost:3000')
- expect(resolveServerAppUrl('/relative', 'market.example.com')).toBe('/relative')
- })
-})
diff --git a/carplace/src/lib/appUrls.ts b/carplace/src/lib/appUrls.ts
deleted file mode 100644
index c9ee9d3..0000000
--- a/carplace/src/lib/appUrls.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-export function resolveBrowserAppUrl(fallback: string): string {
- if (typeof window === 'undefined') return fallback
- const h = window.location.hostname
- if (h === 'localhost' || h === '127.0.0.1') return fallback.replace(/\/$/, '')
-
- try {
- const target = new URL(fallback)
- target.protocol = window.location.protocol
- target.hostname = h
- target.port = ''
- return target.toString().replace(/\/$/, '')
- } catch {
- return fallback
- }
-}
-
-export function resolveServerAppUrl(fallback: string, host: string | null, proto?: string | null): string {
- if (!host) return fallback
-
- try {
- const target = new URL(fallback)
- target.protocol = `${proto || target.protocol.replace(':', '')}:`
- target.host = host
- return target.toString().replace(/\/$/, '')
- } catch {
- return fallback
- }
-}
diff --git a/carplace/src/lib/footerContent.test.ts b/carplace/src/lib/footerContent.test.ts
deleted file mode 100644
index 7b207ea..0000000
--- a/carplace/src/lib/footerContent.test.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import {
- appPrivacyHref,
- appTermsHref,
- footerPageHref,
- footerPageSlugs,
- getFooterPageContent,
- isFooterPageSlug,
-} from './footerContent'
-import type { CarplaceLanguage } from './i18n'
-
-const languages: CarplaceLanguage[] = ['en', 'fr', 'ar']
-
-describe('carplace footer content registry', () => {
- it('keeps every declared slug routable and recognizable', () => {
- expect(footerPageSlugs.length).toBeGreaterThan(5)
-
- for (const slug of footerPageSlugs) {
- expect(isFooterPageSlug(slug)).toBe(true)
- expect(footerPageHref[slug]).toBe(`/footer/${slug}`)
- }
-
- expect(isFooterPageSlug('privacy')).toBe(false)
- expect(isFooterPageSlug('../admin')).toBe(false)
- })
-
- it('provides localized title and paragraph content for every footer page', () => {
- for (const language of languages) {
- for (const slug of footerPageSlugs) {
- const content = getFooterPageContent(language, slug)
-
- expect(content.title.trim()).not.toBe('')
- expect(content.paragraphs.length).toBeGreaterThan(0)
- expect(content.paragraphs.every((paragraph) => paragraph.trim().length > 0)).toBe(true)
-
- for (const section of content.sections ?? []) {
- expect(section.heading.trim()).not.toBe('')
- expect(section.paragraphs.length).toBeGreaterThan(0)
- }
- }
- }
- })
-
- it('keeps website privacy and application privacy links separate by locale', () => {
- expect(footerPageHref['privacy-policy']).toBe('/footer/privacy-policy')
- expect(appPrivacyHref).toEqual({
- en: '/app-privacy-en',
- fr: '/app-privacy-fr',
- ar: '/app-privacy-ar',
- })
- expect(appTermsHref).toEqual({
- en: '/app-tc-en',
- fr: '/app-tc-fr',
- ar: '/app-tc-ar',
- })
- })
-
- it('exposes footer policy copy that points users to the locale-matched app policy pages', () => {
- expect(getFooterPageContent('en', 'privacy-policy').paragraphs[0]).toContain('app-privacy-en')
- expect(getFooterPageContent('fr', 'privacy-policy').paragraphs[0]).toContain('app-privacy-fr')
- expect(getFooterPageContent('ar', 'privacy-policy').paragraphs[0]).toContain('app-privacy-ar')
-
- expect(getFooterPageContent('en', 'general-conditions').paragraphs[0]).toContain('app-tc-en')
- expect(getFooterPageContent('fr', 'general-conditions').paragraphs[0]).toContain('app-tc-fr')
- expect(getFooterPageContent('ar', 'general-conditions').paragraphs[0]).toContain('app-tc-ar')
- })
-})
diff --git a/carplace/src/lib/footerContent.ts b/carplace/src/lib/footerContent.ts
deleted file mode 100644
index 73262e5..0000000
--- a/carplace/src/lib/footerContent.ts
+++ /dev/null
@@ -1,790 +0,0 @@
-import type { CarplaceLanguage } from './i18n'
-
-export const footerPageSlugs = [
- 'about-us',
- 'terms-of-service',
- 'security',
- 'compliance',
- 'privacy-policy',
- 'cookie-policy',
- 'newsletter',
- 'contact-sales',
- 'general-conditions',
-] as const
-
-export type FooterPageSlug = (typeof footerPageSlugs)[number]
-
-export const appPrivacyHref = {
- en: '/app-privacy-en',
- fr: '/app-privacy-fr',
- ar: '/app-privacy-ar',
-} as const
-
-export const appTermsHref = {
- en: '/app-tc-en',
- fr: '/app-tc-fr',
- ar: '/app-tc-ar',
-} as const
-
-export type FooterPageContent = {
- title: string
- paragraphs: string[]
- sections?: Array<{
- heading: string
- paragraphs: string[]
- }>
-}
-
-export const footerPageHref: Record = {
- 'about-us': '/footer/about-us',
- 'terms-of-service': '/footer/terms-of-service',
- security: '/footer/security',
- compliance: '/footer/compliance',
- 'privacy-policy': '/footer/privacy-policy',
- 'cookie-policy': '/footer/cookie-policy',
- newsletter: '/footer/newsletter',
- 'contact-sales': '/footer/contact-sales',
- 'general-conditions': '/footer/general-conditions',
-}
-
-const footerContent: Record> = {
- en: {
- 'about-us': {
- title: 'About Us',
- paragraphs: [
- 'RentalDriveGo helps car rental businesses streamline operations, manage vehicle fleets, and grow reservations through one powerful platform. Our tools are designed to simplify fleet tracking, booking management, customer reservations, payments, and day-to-day rental operations so companies can focus on delivering exceptional service instead of drowning in spreadsheets and phone calls.',
- 'Whether you operate a small local rental agency or a large multi-location fleet, RentalDriveGo provides the technology to help you scale efficiently and serve customers with confidence.',
- ],
- },
- 'terms-of-service': {
- title: 'Terms of Service',
- paragraphs: [
- 'By using RentalDriveGo, you agree to comply with our platform policies, usage guidelines, and applicable laws. Our services are intended for legitimate car rental businesses and their authorized users. Users are responsible for maintaining account security, providing accurate business information, and using the platform responsibly.',
- 'RentalDriveGo reserves the right to suspend accounts involved in fraudulent activity, misuse, or violations of these terms.',
- ],
- },
- security: {
- title: 'Security',
- paragraphs: [
- 'RentalDriveGo takes security seriously. We use modern encryption standards, secure cloud infrastructure, and continuous monitoring to help protect your business data, customer information, and reservation records.',
- 'Our platform is built with security best practices to minimize risks and ensure reliable system performance.',
- ],
- },
- compliance: {
- title: 'Compliance',
- paragraphs: [
- 'RentalDriveGo is committed to operating in accordance with industry standards and applicable privacy and data protection regulations. We continuously review our systems and operational practices to maintain compliance, security, and transparency for our customers and partners.',
- ],
- },
- 'privacy-policy': {
- title: 'Privacy Policy',
- paragraphs: [
- 'You are currently viewing the privacy policy for the RentalDriveGo website. To consult the application privacy policy, please visit: https://www.rentaldrivego.ma/app-privacy-en',
- 'RentalDriveGo respects your privacy and is committed to handling your personal data in a transparent manner. This policy explains how we collect, use, store, and share your personal information when you use our website, and it describes your rights regarding the processing of your personal data.',
- ],
- sections: [
- {
- heading: '1. Data Controller Contact Details',
- paragraphs: [
- 'This privacy information applies to data processing carried out by RentalDriveGo.',
- 'Telephone: +212 6 05 65 17 51',
- 'Email: service-client@rentaldrivego.ma',
- ],
- },
- {
- heading: '2. Personal Data We Collect, How We Use It, and How Long We Keep It',
- paragraphs: [
- 'When you browse our website for information only, without registering for a service or otherwise sending us personal data, your browser automatically sends certain technical information to our hosting provider, such as your browser type, language and version, the date and time of the request, your IP address, pages visited, referring website, volume of data transferred, and operating system.',
- 'This information is temporarily stored to make the website available on your device, support internal analysis, improve the presentation of the website, and detect or prevent misuse. We do not combine this data with other data sources and we do not seek to identify you directly from it. This data is deleted as soon as it is no longer needed for these purposes.',
- 'If you contact us by web form or email, we may collect your name, email address, telephone number, and any other information you choose to provide. We use this information to respond to your questions, contact you when relevant services or registrations become available, or manage participation in campaigns or competitions where applicable.',
- 'We keep your personal data for as long as you maintain an account with us. If you want your personal data to be removed, you must submit a formal request, subject to any legal or contractual retention obligations that may apply.',
- 'You may withdraw your consent at any time where processing is based on consent. Any processing carried out before withdrawal remains lawful.',
- 'We may also use personal data for website review and improvement, market research, analysis, surveys, and marketing activities related to our products and services, subject to applicable law.',
- ],
- },
- {
- heading: '3. Third-Party Service Providers',
- paragraphs: [
- 'We do not transfer your personal data to third parties unless we are legally required to do so, the transfer is necessary for the performance of a contractual relationship, or you have expressly agreed to it in advance.',
- 'We may work with service providers in Morocco and abroad for technical and website-related services, including hosting and maintenance.',
- ],
- },
- {
- heading: '4. Security',
- paragraphs: [
- 'The security of your personal data is important to us. We apply appropriate organizational, technical, and physical measures to protect the personal information we collect, both during transmission and after receipt.',
- 'Our security measures and privacy practices are reviewed and improved on an ongoing basis.',
- ],
- },
- {
- heading: '5. Retention of Personal Data',
- paragraphs: [
- 'We keep personal data only for as long as we are legally entitled to do so, as long as the purpose of processing continues, and provided that no valid objection has been raised.',
- 'Where legal retention periods apply, the data will be stored for the required duration. Once the applicable period has expired, the data will be deleted unless it is still needed for the performance or preparation of a contract.',
- ],
- },
- {
- heading: '6. Transfer to a Foreign Country',
- paragraphs: [
- 'The arrangements described above may result in your personal data being stored or processed in foreign countries.',
- ],
- },
- {
- heading: '7. Your Rights',
- paragraphs: [
- 'Under Law No. 09-08 relating to the protection of individuals with regard to the processing of personal data, you may have the right to access your personal data, obtain additional information, request correction of inaccurate data, and object, where applicable, to the processing of your data or to its transfer to third parties.',
- 'To exercise your rights, you may send a request including your full name and a copy of your identity document to: service-client@rentaldrivego.ma',
- ],
- },
- ],
- },
- 'cookie-policy': {
- title: 'Cookie Policy',
- paragraphs: [
- 'We use cookies exclusively to operate the platform and remember your personal preferences. We do not use cookies for advertising, cross-site tracking, or behavioural profiling, and we do not share cookie data with third parties for marketing purposes.',
- ],
- sections: [
- {
- heading: '1. Authentication Cookie',
- paragraphs: [
- 'Name: employee_session',
- 'This cookie is set when you sign in to the RentalDriveGo workspace. It stores a cryptographically signed token that verifies your identity for the duration of your session. It expires automatically after 8 hours and is deleted when you sign out. This cookie is strictly necessary — without it, access to the dashboard and protected areas is not possible.',
- ],
- },
- {
- heading: '2. Language Preference Cookie',
- paragraphs: [
- 'Name: rentaldrivego-language',
- 'Stores your chosen display language (English, French, or Arabic) so the interface appears in your preferred language on every visit across all parts of the platform. It is retained for one year.',
- ],
- },
- {
- heading: '3. Theme Preference Cookie',
- paragraphs: [
- 'Name: rentaldrivego-theme',
- 'Stores your preferred colour scheme (light or dark mode). It is read immediately when a page loads to apply the correct theme before the interface is drawn, preventing a visual flash. It is retained for one year.',
- ],
- },
- {
- heading: '4. Per-User Preference Cookies',
- paragraphs: [
- 'Names: rentaldrivego-language--[id], rentaldrivego-theme--[id]',
- 'When you are signed in, your language and theme preferences are also saved under a cookie linked to your account identifier. This allows multiple employees sharing a device to each maintain independent preferences. These cookies are retained for one year.',
- ],
- },
- {
- heading: '5. Legacy Preference Cookies',
- paragraphs: [
- 'Names: dashboard-language, carplace-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.',
- ],
- },
- {
- heading: '6. Managing Your Cookies',
- paragraphs: [
- 'You can manage or delete cookies at any time through your browser settings. Blocking the authentication cookie will prevent you from signing in. Blocking preference cookies will cause your language and theme settings to reset on each visit. For general information about cookies, you may visit http://www.allaboutcookies.org/.',
- ],
- },
- ],
- },
- newsletter: {
- title: 'Newsletter',
- paragraphs: [
- 'Stay updated with the latest industry insights, product updates, feature releases, and fleet management tips from RentalDriveGo.',
- 'Subscribe to our newsletter and receive helpful resources designed to help car rental businesses grow smarter and operate more efficiently.',
- ],
- },
- 'contact-sales': {
- title: 'Contact Sales',
- paragraphs: [
- 'Want to see how RentalDriveGo can transform your rental operations? Our sales team is ready to help you explore the right solution for your business.',
- 'Contact us to schedule a demo, discuss pricing, or learn more about our fleet management and reservation platform.',
- ],
- },
- 'general-conditions': {
- title: 'General Conditions of Use',
- paragraphs: [
- 'You are currently viewing the General Conditions of Use for the RentalDriveGo website. To consult the General Conditions of Use of the application, please visit: https://www.rentaldrivego.ma/app-tc-en',
- 'These General Conditions of Use define the rules that apply when you visit, access, browse, contact us through, or otherwise interact with the website www.rentaldrivego.ma.',
- ],
- sections: [
- {
- heading: '1. Purpose',
- paragraphs: [
- 'These conditions govern the use of the website www.rentaldrivego.ma, operated by RentalDriveGo, a limited liability company registered in the Azrou Trade Register under number 00000, with its registered office in Azrou',
- 'They also define the standards relating to the content of the website and apply to any person who visits the website, contacts us or other users through it, links to it, or interacts with it in any way.',
- ],
- },
- {
- heading: '2. Website Offer',
- paragraphs: [
- 'We operate the website solely for information, prospecting, and market research purposes.',
- 'Information relating to the RentalDriveGo loyalty program may be presented to visitors, and download links may be made available through the website.',
- ],
- },
- {
- heading: '3. Contact',
- paragraphs: [
- 'You may contact us through the website or by email at service-client@rentaldrivego.ma.',
- ],
- },
- {
- heading: '4. Acceptance of the General Conditions of Use',
- paragraphs: [
- 'By using the website, you accept these General Conditions of Use and agree to comply with them. If you do not accept them, you must not use the website.',
- 'Our Privacy Policy and Cookie Policy also apply to your use of the website.',
- ],
- },
- {
- heading: '5. Changes to the General Conditions of Use',
- paragraphs: [
- 'We may amend these General Conditions of Use at any time. Each time you use the website, you should review the current version.',
- 'We also reserve the right to amend, correct, or update any information published on the website at any time.',
- ],
- },
- {
- heading: '6. Changes to Website Content and Availability',
- paragraphs: [
- 'We may update or modify the content of the website at any time to reflect changes in the needs of users and partners.',
- 'The website is made available free of charge. While we use reasonable means to prevent and resolve interruptions, we cannot guarantee continuous or uninterrupted availability.',
- 'We may suspend, withdraw, or restrict the availability of all or part of the website for commercial or operational reasons.',
- 'You are responsible for ensuring that all persons accessing the website through your internet connection are aware of these conditions and comply with them.',
- ],
- },
- {
- heading: '7. Scope of Information on the Website',
- paragraphs: [
- 'The content of the website is provided for general information purposes only and does not constitute a recommendation on which you should rely.',
- 'Although we make reasonable efforts to update the information on the website, we do not guarantee, whether expressly or implicitly, that the content is accurate, complete, or current.',
- ],
- },
- {
- heading: '8. Third-Party Websites',
- paragraphs: [
- 'We are not responsible for third-party websites to which the website may link.',
- 'Any hyperlinks to third-party content, websites, or resources are provided for information purposes only, and we cannot be held responsible for their content, accuracy, or quality.',
- ],
- },
- {
- heading: '9. Limitation of Liability',
- paragraphs: [
- 'You agree that access to and use of the website and any associated pages is at your own risk.',
- 'Nothing in these conditions excludes or limits our liability where such exclusion or limitation would be unlawful.',
- 'Neither we nor our partners or affiliates may be held liable for damage resulting from the use of information, files, links, or websites accessible from or received through the website.',
- ],
- },
- {
- heading: '10. Your Responsibilities',
- paragraphs: [
- 'You acknowledge that we may use the information you provide to us where you have given the required consent or where another lawful basis applies.',
- 'You agree to use the website only for lawful purposes.',
- 'If we consider that a breach of these General Conditions of Use has occurred, we may take any action we deem appropriate.',
- ],
- },
- {
- heading: '11. Intellectual Property',
- paragraphs: [
- 'The website is our property and the entire website is protected by intellectual property laws.',
- 'All rights of reproduction and representation are reserved.',
- ],
- },
- ],
- },
- },
- fr: {
- 'about-us': {
- title: 'À propos de nous',
- paragraphs: [
- 'RentalDriveGo aide les entreprises de location de voitures à simplifier leurs opérations, gérer leurs flottes et augmenter leurs réservations grâce à une plateforme complète. Nos outils permettent de gérer les véhicules, les réservations, les paiements et les opérations quotidiennes de manière efficace et centralisée.',
- 'Que vous soyez une petite agence locale ou une grande entreprise multi-sites, RentalDriveGo vous aide à développer votre activité avec des solutions modernes et fiables.',
- ],
- },
- 'terms-of-service': {
- title: 'Conditions d’utilisation',
- paragraphs: [
- 'En utilisant RentalDriveGo, vous acceptez de respecter nos politiques, nos règles d’utilisation et les lois applicables. Nos services sont destinés aux entreprises de location de voitures et à leurs utilisateurs autorisés.',
- 'RentalDriveGo se réserve le droit de suspendre tout compte impliqué dans une activité frauduleuse ou une violation des présentes conditions.',
- ],
- },
- security: {
- title: 'Sécurité',
- paragraphs: [
- 'RentalDriveGo applique des standards de sécurité modernes afin de protéger les données de votre entreprise, les informations de vos clients et vos réservations.',
- 'Notre plateforme utilise le chiffrement, une infrastructure cloud sécurisée et une surveillance continue afin d’assurer la confidentialité et la fiabilité des services.',
- ],
- },
- compliance: {
- title: 'Conformité',
- paragraphs: [
- 'RentalDriveGo s’engage à respecter les réglementations applicables ainsi que les standards du secteur concernant la protection des données et la sécurité.',
- 'Nous améliorons continuellement nos systèmes afin de garantir transparence, conformité et confiance à nos clients.',
- ],
- },
- 'privacy-policy': {
- title: 'Politique de confidentialité',
- paragraphs: [
- 'Vous consultez actuellement la politique de confidentialité du site RentalDriveGo. Pour consulter la politique de confidentialité de l’application, veuillez visiter : https://www.rentaldrivego.ma/app-privacy-fr',
- 'RentalDriveGo respecte votre vie privée et s’engage à traiter vos données personnelles de manière transparente. Cette politique explique comment nous collectons, utilisons, conservons et partageons vos informations personnelles lorsque vous utilisez notre site internet, ainsi que les droits dont vous disposez concernant ce traitement.',
- ],
- sections: [
- {
- heading: '1. Coordonnées du responsable du traitement',
- paragraphs: [
- 'Les présentes informations sur la protection des données s’appliquent aux traitements effectués par RentalDriveGo.',
- 'Téléphone : +212 6 05 65 17 51',
- 'Email : service-client@rentaldrivego.ma',
- ],
- },
- {
- heading: '2. Données personnelles collectées, finalités d’utilisation et durée de conservation',
- paragraphs: [
- 'Lorsque vous consultez notre site à des fins d’information uniquement, sans vous inscrire à un service et sans nous transmettre de données personnelles par un autre moyen, votre navigateur envoie automatiquement certaines informations techniques à notre hébergeur, notamment le type, la langue et la version du navigateur, la date et l’heure de la demande, l’adresse IP, les pages consultées, le site d’origine, le volume de données transférées et le système d’exploitation.',
- 'Ces informations sont temporairement conservées afin de permettre l’affichage du site sur votre appareil, de réaliser des analyses internes, d’améliorer la présentation du site et de détecter ou prévenir les abus. Nous ne croisons pas ces données avec d’autres sources et nous ne cherchons pas à vous identifier directement à partir de celles-ci. Elles sont supprimées dès qu’elles ne sont plus nécessaires à ces finalités.',
- 'Si vous nous contactez via un formulaire ou par email, nous pouvons collecter votre nom, votre adresse email, votre numéro de téléphone et toute autre information que vous choisissez de nous communiquer. Nous utilisons ces données pour répondre à vos questions, vous contacter lorsque certains services ou inscriptions deviennent disponibles, ou gérer votre participation à des campagnes ou concours le cas échéant.',
- 'Nous conservons vos données personnelles aussi longtemps que vous détenez un compte chez nous. Si vous souhaitez la suppression de vos données personnelles, vous devez nous adresser une demande formelle, sous réserve de toute obligation légale ou contractuelle de conservation applicable.',
- 'Vous pouvez retirer votre consentement à tout moment lorsque le traitement repose sur celui-ci. Les traitements effectués avant ce retrait demeurent licites.',
- 'Nous pouvons également utiliser les données personnelles pour l’évaluation et l’amélioration du site, les études de marché, les analyses, les enquêtes ainsi que les actions de marketing liées à nos produits et services, dans le respect de la loi applicable.',
- ],
- },
- {
- heading: '3. Prestataires tiers',
- paragraphs: [
- 'Nous ne communiquons pas vos données personnelles à des tiers sauf en cas d’obligation légale, lorsque ce transfert est nécessaire à l’exécution d’une relation contractuelle, ou lorsque vous y avez expressément consenti au préalable.',
- 'Nous pouvons travailler avec des prestataires situés au Maroc et à l’étranger pour des services techniques et liés au site internet, notamment l’hébergement et la maintenance.',
- ],
- },
- {
- heading: '4. Sécurité',
- paragraphs: [
- 'La sécurité de vos données personnelles est importante pour nous. Nous mettons en place des mesures organisationnelles, techniques et physiques appropriées afin de protéger les informations personnelles collectées, aussi bien pendant leur transmission qu’après leur réception.',
- 'Nos mesures de sécurité et nos pratiques de confidentialité sont revues et améliorées de manière continue.',
- ],
- },
- {
- heading: '5. Conservation des données personnelles',
- paragraphs: [
- 'Nous conservons les données personnelles uniquement aussi longtemps que la loi nous y autorise, tant que la finalité du traitement subsiste et en l’absence d’opposition valable.',
- 'Lorsque des délais légaux de conservation s’appliquent, les données sont conservées pendant la durée requise. À l’expiration de cette période, elles sont supprimées sauf si elles restent nécessaires à l’exécution ou à la préparation d’un contrat.',
- ],
- },
- {
- heading: '6. Transfert vers un pays étranger',
- paragraphs: [
- 'Les traitements décrits ci-dessus peuvent entraîner le stockage ou le traitement de vos données personnelles dans des pays étrangers.',
- ],
- },
- {
- heading: '7. Vos droits',
- paragraphs: [
- 'Conformément à la loi n° 09-08 relative à la protection des personnes physiques à l’égard du traitement des données à caractère personnel, vous pouvez disposer d’un droit d’accès à vos données, d’un droit d’obtenir des informations complémentaires, d’un droit de rectification des données inexactes et, le cas échéant, d’un droit d’opposition au traitement ou au transfert de vos données à des tiers.',
- 'Pour exercer vos droits, vous pouvez envoyer une demande indiquant votre nom complet et accompagnée d’une copie de votre pièce d’identité à : service-client@rentaldrivego.ma',
- ],
- },
- ],
- },
- 'cookie-policy': {
- title: 'Politique relative aux cookies',
- paragraphs: [
- 'Nous utilisons des cookies exclusivement pour assurer le fonctionnement de la plateforme et mémoriser vos préférences personnelles. Nous n’utilisons pas de cookies à des fins publicitaires, de suivi intersites ou de profilage comportemental, et nous ne partageons pas les données des cookies avec des tiers à des fins marketing.',
- ],
- sections: [
- {
- heading: '1. Cookie d’authentification',
- paragraphs: [
- 'Nom : employee_session',
- 'Ce cookie est défini lorsque vous vous connectez à l’espace de travail RentalDriveGo. Il stocke un jeton cryptographiquement signé qui vérifie votre identité pour la durée de votre session. Il expire automatiquement après 8 heures et est supprimé lors de la déconnexion. Ce cookie est strictement nécessaire — sans lui, l’accès au tableau de bord et aux espaces protégés n’est pas possible.',
- ],
- },
- {
- heading: '2. Cookie de préférence de langue',
- paragraphs: [
- 'Nom : rentaldrivego-language',
- 'Enregistre la langue d’affichage choisie (français, anglais ou arabe) afin que l’interface s’affiche dans votre langue préférée à chaque visite sur l’ensemble de la plateforme. Il est conservé pendant un an.',
- ],
- },
- {
- heading: '3. Cookie de préférence de thème',
- paragraphs: [
- 'Nom : rentaldrivego-theme',
- 'Enregistre le schéma de couleurs préféré (mode clair ou sombre). Il est lu dès le chargement de la page afin d’appliquer le bon thème avant l’affichage de l’interface, évitant ainsi un changement visuel brusque. Il est conservé pendant un an.',
- ],
- },
- {
- heading: '4. Cookies de préférence par utilisateur',
- paragraphs: [
- 'Noms : rentaldrivego-language--[id], rentaldrivego-theme--[id]',
- 'Lorsque vous êtes connecté, vos préférences de langue et de thème sont également enregistrées dans un cookie lié à votre identifiant de compte. Cela permet à plusieurs employés partageant un appareil de conserver des préférences indépendantes. Ces cookies sont conservés pendant un an.',
- ],
- },
- {
- heading: '5. Cookies de préférence hérités',
- paragraphs: [
- 'Noms : dashboard-language, carplace-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 qu’en secours et seront supprimés dans une prochaine mise à jour.',
- ],
- },
- {
- heading: '6. Gestion de vos cookies',
- paragraphs: [
- 'Vous pouvez gérer ou supprimer les cookies à tout moment via les paramètres de votre navigateur. Bloquer le cookie d’authentification vous empêchera de vous connecter. Bloquer les cookies de préférence entraînera la réinitialisation de vos paramètres de langue et de thème à chaque visite. Pour des informations générales sur les cookies, vous pouvez consulter http://www.allaboutcookies.org/.',
- ],
- },
- ],
- },
- newsletter: {
- title: 'Newsletter',
- paragraphs: [
- 'Recevez les dernières actualités, mises à jour produits, conseils de gestion de flotte et nouveautés de RentalDriveGo.',
- 'Abonnez-vous à notre newsletter pour rester informé et développer votre activité plus efficacement.',
- ],
- },
- 'contact-sales': {
- title: 'Contacter les ventes',
- paragraphs: [
- 'Vous souhaitez découvrir comment RentalDriveGo peut améliorer vos opérations de location de voitures ?',
- 'Notre équipe commerciale est disponible pour organiser une démonstration, répondre à vos questions et vous proposer la solution adaptée à votre entreprise.',
- ],
- },
- 'general-conditions': {
- title: 'Conditions générales d’utilisation',
- paragraphs: [
- 'Vous consultez actuellement les Conditions générales d’utilisation du site RentalDriveGo. Pour consulter les Conditions générales d’utilisation de l’application, veuillez visiter : https://www.rentaldrivego.ma/app-tc-fr',
- 'Les présentes Conditions générales d’utilisation définissent les règles applicables lorsque vous visitez, accédez, parcourez, contactez ou utilisez de toute autre manière le site www.rentaldrivego.ma.',
- ],
- sections: [
- {
- heading: '1. Objet',
- paragraphs: [
- 'Les présentes conditions régissent l’utilisation du site www.rentaldrivego.ma exploité par RentalDriveGo, société à responsabilité limitée immatriculée au registre du commerce d’Azrou sous le numéro 00000, dont le siège social est situé à Azrou.',
- 'Elles définissent également les règles relatives au contenu du site et s’appliquent à toute personne qui visite le site, nous contacte ou contacte d’autres utilisateurs par son intermédiaire, y crée un lien ou interagit avec lui de quelque manière que ce soit.',
- ],
- },
- {
- heading: '2. Offre du site',
- paragraphs: [
- 'Nous exploitons le site exclusivement à des fins d’information, de prospection et d’étude de marché.',
- 'Des informations relatives au programme de fidélité RentalDriveGo peuvent être portées à la connaissance des visiteurs et des liens de téléchargement peuvent être mis à leur disposition.',
- ],
- },
- {
- heading: '3. Contact',
- paragraphs: [
- 'Vous pouvez nous contacter via le site ou par email à l’adresse service-client@rentaldrivego.ma.',
- ],
- },
- {
- heading: '4. Acceptation des Conditions générales d’utilisation',
- paragraphs: [
- 'En utilisant le site, vous acceptez les présentes Conditions générales d’utilisation et vous vous engagez à les respecter. Si vous ne les acceptez pas, vous ne devez pas utiliser le site.',
- 'Notre Politique de confidentialité et notre Politique relative aux cookies s’appliquent également à votre utilisation du site.',
- ],
- },
- {
- heading: '5. Modification des Conditions générales d’utilisation',
- paragraphs: [
- 'Nous pouvons modifier à tout moment les présentes Conditions générales d’utilisation. À chaque utilisation du site, nous vous invitons à consulter la version en vigueur.',
- 'Nous nous réservons également le droit de modifier, corriger ou mettre à jour à tout moment les informations publiées sur le site.',
- ],
- },
- {
- heading: '6. Modification du contenu et disponibilité du site',
- paragraphs: [
- 'Nous pouvons mettre à jour ou modifier le contenu du site à tout moment afin de refléter l’évolution des besoins de nos utilisateurs et partenaires.',
- 'Le site est mis à votre disposition gratuitement. Bien que nous mettions en œuvre des moyens raisonnables pour prévenir et résoudre les interruptions, nous ne pouvons garantir une disponibilité constante et ininterrompue.',
- 'Nous pouvons suspendre, retirer ou restreindre l’accès à tout ou partie du site pour des raisons commerciales ou opérationnelles.',
- 'Il vous appartient de veiller à ce que toute personne accédant au site par votre connexion internet ait connaissance des présentes conditions et les respecte.',
- ],
- },
- {
- heading: '7. Portée des informations figurant sur le site',
- paragraphs: [
- 'Le contenu du site est fourni à titre d’information générale uniquement et ne constitue ni un conseil ni une recommandation sur lesquels vous devriez vous appuyer.',
- 'Bien que nous fassions des efforts raisonnables pour mettre à jour les informations publiées sur le site, nous ne garantissons pas, expressément ou implicitement, qu’elles soient exactes, complètes ou à jour.',
- ],
- },
- {
- heading: '8. Responsabilité à l’égard des sites tiers',
- paragraphs: [
- 'Nous ne sommes pas responsables des sites internet tiers vers lesquels le site peut contenir des liens.',
- 'Les liens hypertextes vers des contenus, sites ou ressources de tiers sont fournis uniquement à titre informatif, et nous ne pouvons être tenus responsables de leur contenu, de leur exactitude ou de leur qualité.',
- ],
- },
- {
- heading: '9. Limitation de responsabilité',
- paragraphs: [
- 'Vous reconnaissez que l’accès et l’utilisation du site ainsi que des pages qui y sont rattachées se font à vos propres risques.',
- 'Aucune disposition des présentes conditions n’exclut ou ne limite notre responsabilité lorsqu’une telle exclusion ou limitation serait contraire à la loi.',
- 'Ni nous, ni nos partenaires ou affiliés, ne pourrons être tenus responsables des dommages résultant de l’utilisation des informations, fichiers, liens ou sites accessibles depuis le site ou reçus par son intermédiaire.',
- ],
- },
- {
- heading: '10. Vos responsabilités',
- paragraphs: [
- 'Vous reconnaissez que nous pouvons utiliser les informations que vous nous fournissez lorsque vous avez donné le consentement requis ou lorsqu’une autre base légale s’applique.',
- 'Vous vous engagez à utiliser le site uniquement à des fins licites.',
- 'Si nous estimons qu’une violation des présentes Conditions générales d’utilisation a eu lieu, nous pouvons prendre toute mesure que nous jugeons appropriée.',
- ],
- },
- {
- heading: '11. Propriété intellectuelle',
- paragraphs: [
- 'Le site est notre propriété et l’ensemble du site est protégé par la législation relative à la propriété intellectuelle.',
- 'Tous les droits de reproduction et de représentation sont réservés.',
- ],
- },
- ],
- },
- },
- ar: {
- 'about-us': {
- title: 'من نحن',
- paragraphs: [
- 'تساعد منصة RentalDriveGo شركات تأجير السيارات على إدارة عملياتها بسهولة، وتنظيم أساطيلها، وزيادة الحجوزات من خلال منصة متكاملة وحديثة. توفر أدواتنا حلولاً لإدارة المركبات والحجوزات والمدفوعات والعمليات اليومية بكفاءة عالية.',
- 'سواء كنت تدير شركة محلية صغيرة أو أسطولاً كبيراً متعدد الفروع، فإن RentalDriveGo توفر لك التكنولوجيا التي تساعدك على النمو وإدارة أعمالك بثقة.',
- ],
- },
- 'terms-of-service': {
- title: 'شروط الاستخدام',
- paragraphs: [
- 'باستخدامك لمنصة RentalDriveGo، فإنك توافق على الالتزام بسياسات المنصة والقوانين المعمول بها. خدماتنا مخصصة لشركات تأجير السيارات والمستخدمين المصرح لهم فقط.',
- 'تحتفظ RentalDriveGo بحق تعليق أي حساب يشارك في أنشطة احتيالية أو يخالف شروط الاستخدام.',
- ],
- },
- security: {
- title: 'الأمان',
- paragraphs: [
- 'تلتزم RentalDriveGo بتوفير أعلى معايير الأمان لحماية بيانات شركتك ومعلومات عملائك وسجلات الحجوزات.',
- 'تستخدم منصتنا تقنيات تشفير حديثة وبنية سحابية آمنة وأنظمة مراقبة مستمرة لضمان حماية البيانات واستقرار الخدمة.',
- ],
- },
- compliance: {
- title: 'الامتثال',
- paragraphs: [
- 'تلتزم RentalDriveGo بالامتثال للمعايير واللوائح المعمول بها المتعلقة بحماية البيانات والخصوصية والأمان.',
- 'نعمل باستمرار على تطوير أنظمتنا وإجراءاتنا لضمان الشفافية والثقة لعملائنا وشركائنا.',
- ],
- },
- 'privacy-policy': {
- title: 'سياسة الخصوصية',
- paragraphs: [
- 'أنت الآن تطّلع على سياسة الخصوصية الخاصة بموقع RentalDriveGo. للاطلاع على سياسة الخصوصية الخاصة بالتطبيق، يرجى زيارة: https://www.rentaldrivego.ma/app-privacy-ar',
- 'تحترم RentalDriveGo خصوصيتك وتلتزم بالتعامل مع بياناتك الشخصية بشفافية. توضح هذه السياسة كيفية جمع معلوماتك الشخصية واستخدامها وتخزينها ومشاركتها عند استخدامك لموقعنا، كما تشرح حقوقك المتعلقة بمعالجة بياناتك الشخصية.',
- ],
- sections: [
- {
- heading: '1. بيانات الاتصال الخاصة بمسؤول المعالجة',
- paragraphs: [
- 'تنطبق هذه المعلومات المتعلقة بحماية البيانات على المعالجة التي تقوم بها RentalDriveGo.',
- 'الهاتف: +212 6 05 65 17 51',
- 'البريد الإلكتروني: service-client@rentaldrivego.ma',
- ],
- },
- {
- heading: '2. ما هي البيانات الشخصية التي نجمعها، وكيف نستخدمها، ومدة الاحتفاظ بها',
- paragraphs: [
- 'عند زيارتك لموقعنا لأغراض الاطلاع فقط، من دون التسجيل في أي خدمة أو تزويدنا ببيانات شخصية بأي وسيلة أخرى، يرسل متصفحك تلقائياً بعض المعلومات التقنية إلى مزود الاستضافة، مثل نوع المتصفح ولغته وإصداره، وتاريخ ووقت الطلب، وعنوان IP، والصفحات التي تمت زيارتها، والموقع المحيل، وحجم البيانات المنقولة، ونظام التشغيل.',
- 'يتم الاحتفاظ بهذه المعلومات مؤقتاً لتمكين عرض الموقع على جهازك، ودعم التحليل الداخلي، وتحسين طريقة عرض الموقع، واكتشاف أو منع أي إساءة استخدام. نحن لا ندمج هذه البيانات مع مصادر أخرى ولا نسعى إلى التعرف المباشر على هويتك من خلالها. ويتم حذفها بمجرد انتفاء الحاجة إليها لهذه الأغراض.',
- 'إذا تواصلت معنا عبر نموذج إلكتروني أو عبر البريد الإلكتروني، فقد نجمع اسمك وبريدك الإلكتروني ورقم هاتفك وأي معلومات أخرى تختار تقديمها لنا. نستخدم هذه البيانات للرد على استفساراتك، أو التواصل معك عندما تصبح بعض الخدمات أو التسجيلات متاحة، أو لإدارة مشاركتك في الحملات أو المسابقات عند الاقتضاء.',
- 'نحتفظ ببياناتك الشخصية طوال الفترة التي يكون لديك فيها حساب لدينا. وإذا كنت ترغب في حذف بياناتك الشخصية، فيجب عليك تقديم طلب رسمي بذلك، مع مراعاة أي التزامات قانونية أو تعاقدية قد تفرض الاحتفاظ ببعض البيانات.',
- 'يمكنك سحب موافقتك في أي وقت عندما تكون المعالجة قائمة على الموافقة. وتظل المعالجة التي تمت قبل سحب الموافقة مشروعة.',
- 'وقد نستخدم أيضاً البيانات الشخصية لمراجعة الموقع وتحسينه، وإجراء أبحاث السوق، والتحليلات، والاستبيانات، والأنشطة التسويقية المتعلقة بمنتجاتنا وخدماتنا، وفقاً للقانون المعمول به.',
- ],
- },
- {
- heading: '3. مزودو الخدمات من الأطراف الثالثة',
- paragraphs: [
- 'لا نقوم بنقل بياناتك الشخصية إلى أطراف ثالثة إلا إذا كان ذلك مطلوباً بموجب القانون، أو كان ضرورياً لتنفيذ علاقة تعاقدية، أو إذا كنت قد وافقت على ذلك صراحة مسبقاً.',
- 'قد نتعاون مع مزودي خدمات في المغرب وخارجه لتقديم الخدمات التقنية والخدمات المرتبطة بالموقع، بما في ذلك الاستضافة والصيانة.',
- ],
- },
- {
- heading: '4. الأمان',
- paragraphs: [
- 'تمثل حماية بياناتك الشخصية أولوية بالنسبة لنا. لذلك نعتمد تدابير تنظيمية وتقنية ومادية مناسبة لحماية المعلومات الشخصية التي نجمعها، سواء أثناء نقلها أو بعد استلامها.',
- 'نقوم بمراجعة وتحسين إجراءات الأمان وممارسات الخصوصية لدينا بشكل مستمر.',
- ],
- },
- {
- heading: '5. الاحتفاظ بالبيانات الشخصية',
- paragraphs: [
- 'نحتفظ بالبيانات الشخصية فقط للمدة التي يسمح بها القانون، وطالما أن الغرض من المعالجة لا يزال قائماً، وما لم يتم تقديم اعتراض مشروع.',
- 'وعندما تكون هناك مدد قانونية للاحتفاظ بالبيانات، فإننا نلتزم بها. وبعد انتهاء المدة المعمول بها، يتم حذف البيانات ما لم تعد هناك حاجة إليها لتنفيذ عقد أو لاتخاذ خطوات تمهيدية لإبرام عقد.',
- ],
- },
- {
- heading: '6. النقل إلى دولة أجنبية',
- paragraphs: [
- 'قد تؤدي الترتيبات المذكورة أعلاه إلى تخزين بياناتك الشخصية أو معالجتها في دول أجنبية.',
- ],
- },
- {
- heading: '7. حقوقك',
- paragraphs: [
- 'وفقاً للقانون رقم 09-08 المتعلق بحماية الأشخاص تجاه معالجة المعطيات ذات الطابع الشخصي، قد يكون لك الحق في الوصول إلى بياناتك، والحصول على معلومات إضافية، وطلب تصحيح البيانات غير الدقيقة، والاعتراض عند الاقتضاء على معالجة بياناتك أو على نقلها إلى أطراف ثالثة.',
- 'لممارسة حقوقك، يمكنك إرسال طلب يتضمن اسمك الكامل ونسخة من وثيقة هويتك إلى: service-client@rentaldrivego.ma',
- ],
- },
- ],
- },
- 'cookie-policy': {
- title: 'سياسة ملفات تعريف الارتباط',
- paragraphs: [
- 'نستخدم ملفات تعريف الارتباط حصرياً لتشغيل المنصة وتذكّر تفضيلاتك الشخصية. لا نستخدم ملفات تعريف الارتباط لأغراض إعلانية أو تتبع المستخدمين عبر المواقع أو بناء ملفات تعريف سلوكية، ولا نشارك بيانات ملفات تعريف الارتباط مع أطراف ثالثة لأغراض تسويقية.',
- ],
- sections: [
- {
- heading: '1. ملف تعريف الارتباط للمصادقة',
- paragraphs: [
- 'الاسم: employee_session',
- 'يُعيَّن هذا الملف عند تسجيل دخولك إلى مساحة عمل RentalDriveGo. يخزّن رمزاً موقّعاً تشفيرياً يُثبت هويتك طوال مدة الجلسة. تنتهي صلاحيته تلقائياً بعد 8 ساعات ويُحذف عند تسجيل الخروج. هذا الملف ضروري بشكل مطلق — إذ لا يمكن الوصول إلى لوحة التحكم والمناطق المحمية بدونه.',
- ],
- },
- {
- heading: '2. ملف تعريف الارتباط لتفضيل اللغة',
- paragraphs: [
- 'الاسم: rentaldrivego-language',
- 'يحفظ لغة العرض التي اخترتها (العربية أو الفرنسية أو الإنجليزية) حتى تظهر الواجهة بلغتك المفضّلة في كل زيارة وعبر جميع أجزاء المنصة. يُحتفظ به لمدة سنة.',
- ],
- },
- {
- heading: '3. ملف تعريف الارتباط لتفضيل المظهر',
- paragraphs: [
- 'الاسم: rentaldrivego-theme',
- 'يحفظ نظام الألوان المفضّل لديك (الوضع الفاتح أو الداكن). يُقرأ فور تحميل الصفحة لتطبيق المظهر الصحيح قبل عرض الواجهة، ما يمنع حدوث وميض بصري غير مرغوب. يُحتفظ به لمدة سنة.',
- ],
- },
- {
- heading: '4. ملفات تعريف الارتباط للتفضيلات الخاصة بكل مستخدم',
- paragraphs: [
- 'الأسماء: rentaldrivego-language--[id], rentaldrivego-theme--[id]',
- 'عند تسجيل دخولك، تُحفظ تفضيلات اللغة والمظهر الخاصة بك أيضاً في ملف مرتبط بمعرّف حسابك. يتيح ذلك لأكثر من موظف يتشاركون جهازاً واحداً الاحتفاظ بتفضيلات مستقلة. يُحتفظ بهذه الملفات لمدة سنة.',
- ],
- },
- {
- heading: '5. ملفات تعريف الارتباط الموروثة',
- paragraphs: [
- 'الأسماء: dashboard-language, carplace-language',
- 'توجد هذه الملفات لضمان التوافق مع الإصدارات السابقة من المنصة، وتخزّن تفضيل اللغة ذاته الموجود في الملف الرئيسي. لا تُقرأ إلا كخيار احتياطي، وستُزال في تحديث مستقبلي.',
- ],
- },
- {
- heading: '6. إدارة ملفات تعريف الارتباط',
- paragraphs: [
- 'يمكنك إدارة ملفات تعريف الارتباط أو حذفها في أي وقت من خلال إعدادات متصفحك. سيمنعك حظر ملف المصادقة من تسجيل الدخول. أما حظر ملفات التفضيلات فسيؤدي إلى إعادة ضبط إعدادات اللغة والمظهر في كل زيارة. للاطلاع على معلومات عامة حول ملفات تعريف الارتباط، يمكنك زيارة http://www.allaboutcookies.org/.',
- ],
- },
- ],
- },
- newsletter: {
- title: 'النشرة الإخبارية',
- paragraphs: [
- 'ابقَ على اطلاع بآخر تحديثات المنصة وأخبار القطاع ونصائح إدارة الأساطيل من RentalDriveGo.',
- 'اشترك في نشرتنا الإخبارية للحصول على محتوى مفيد يساعدك على تطوير أعمالك بكفاءة أكبر.',
- ],
- },
- 'contact-sales': {
- title: 'تواصل مع المبيعات',
- paragraphs: [
- 'هل ترغب في معرفة كيف يمكن لـ RentalDriveGo تطوير عمليات شركتك؟',
- 'فريق المبيعات لدينا جاهز لتقديم عرض توضيحي والإجابة على استفساراتك ومساعدتك في اختيار الحل المناسب لاحتياجاتك.',
- ],
- },
- 'general-conditions': {
- title: 'الشروط العامة لاستخدام الموقع',
- paragraphs: [
- 'أنت الآن تطّلع على الشروط العامة لاستخدام موقع RentalDriveGo. للاطلاع على الشروط العامة لاستخدام التطبيق، يرجى زيارة: https://www.rentaldrivego.ma/app-tc-ar',
- 'تحدد هذه الشروط العامة القواعد المطبقة عند زيارتك أو دخولك أو تصفحك أو تواصلك أو تفاعلك بأي شكل آخر مع الموقع www.rentaldrivego.ma.',
- ],
- sections: [
- {
- heading: '1. الغرض',
- paragraphs: [
- 'تنظم هذه الشروط استخدام الموقع www.rentaldrivego.ma الذي تديره شركة RentalDriveGo، وهي شركة ذات مسؤولية محدودة مسجلة في السجل التجاري بأزرو تحت رقم 00000، ويقع مقرها الاجتماعي في أزرو.',
- 'كما تحدد هذه الشروط القواعد المتعلقة بمحتوى الموقع، وتطبق على كل شخص يزور الموقع أو يتواصل معنا أو مع مستخدمين آخرين من خلاله أو يضع رابطاً إليه أو يتفاعل معه بأي طريقة كانت.',
- ],
- },
- {
- heading: '2. ما يتيحه الموقع',
- paragraphs: [
- 'نقوم بتشغيل الموقع حصرياً لأغراض الإعلام والاستكشاف ودراسة السوق.',
- 'قد يتم عرض معلومات تتعلق ببرنامج الولاء الخاص بـ RentalDriveGo للزوار، كما قد تُتاح لهم روابط للتنزيل عبر الموقع.',
- ],
- },
- {
- heading: '3. الاتصال',
- paragraphs: [
- 'يمكنك التواصل معنا عبر الموقع أو عبر البريد الإلكتروني على العنوان service-client@rentaldrivego.ma.',
- ],
- },
- {
- heading: '4. قبول الشروط العامة للاستخدام',
- paragraphs: [
- 'باستخدامك للموقع، فإنك توافق على هذه الشروط العامة للاستخدام وتتعهد بالالتزام بها. وإذا كنت لا تقبلها، فيجب عليك عدم استخدام الموقع.',
- 'كما تنطبق سياسة الخصوصية وسياسة ملفات تعريف الارتباط الخاصة بنا على استخدامك للموقع.',
- ],
- },
- {
- heading: '5. تعديل الشروط العامة للاستخدام',
- paragraphs: [
- 'يجوز لنا تعديل هذه الشروط العامة للاستخدام في أي وقت. ولذلك ننصحك بالاطلاع على النسخة السارية كلما رغبت في استخدام الموقع.',
- 'كما نحتفظ بالحق في تعديل أو تصحيح أو تحديث أي معلومات منشورة على الموقع في أي وقت.',
- ],
- },
- {
- heading: '6. تعديل محتوى الموقع وتوفّره',
- paragraphs: [
- 'يجوز لنا تحديث أو تعديل محتوى الموقع في أي وقت بما يعكس تطور احتياجات المستخدمين والشركاء.',
- 'يتم توفير الموقع مجاناً. وعلى الرغم من أننا نبذل وسائل معقولة لمنع الانقطاعات وحلها، فإننا لا نضمن التوفر المستمر أو غير المنقطع للموقع.',
- 'يجوز لنا تعليق أو سحب أو تقييد إتاحة كل الموقع أو جزء منه لأسباب تجارية أو تشغيلية.',
- 'أنت مسؤول عن التأكد من أن جميع الأشخاص الذين يدخلون إلى الموقع عبر اتصالك بالإنترنت على علم بهذه الشروط ويلتزمون بها.',
- ],
- },
- {
- heading: '7. نطاق المعلومات الواردة في الموقع',
- paragraphs: [
- 'يتم تقديم محتوى الموقع لأغراض إعلامية عامة فقط، ولا يشكل خدمة توصية أو مشورة يمكن الاعتماد عليها.',
- 'ورغم أننا نبذل جهوداً معقولة لتحديث المعلومات المنشورة على الموقع، فإننا لا نضمن، صراحةً أو ضمناً، أن يكون المحتوى دقيقاً أو كاملاً أو محدثاً.',
- ],
- },
- {
- heading: '8. المسؤولية تجاه المواقع التابعة للغير',
- paragraphs: [
- 'لا نتحمل المسؤولية عن المواقع الإلكترونية التابعة للغير التي قد يتضمن الموقع روابط إليها.',
- 'أي روابط نحو محتوى أو مواقع أو موارد تابعة للغير يتم توفيرها لأغراض إعلامية فقط، ولا يمكن تحميلنا المسؤولية عن محتواها أو دقتها أو جودتها.',
- ],
- },
- {
- heading: '9. تحديد المسؤولية',
- paragraphs: [
- 'أنت توافق على أن الدخول إلى الموقع واستخدامه، وكذلك الصفحات المرتبطة به، يكون على مسؤوليتك الخاصة.',
- 'لا تستبعد هذه الشروط ولا تحد من مسؤوليتنا في الحالات التي يكون فيها هذا الاستبعاد أو التحديد غير قانوني.',
- 'ولا نحن ولا شركاؤنا أو الجهات المرتبطة بنا نتحمل أي مسؤولية عن الأضرار الناتجة عن استخدام المعلومات أو الملفات أو الروابط أو المواقع التي يمكن الوصول إليها من خلال الموقع أو استلامها عبره.',
- ],
- },
- {
- heading: '10. مسؤولياتك',
- paragraphs: [
- 'أنت تقر بأننا قد نستخدم المعلومات التي تقدمها لنا عندما تكون قد منحت الموافقة المطلوبة أو عندما يوجد أساس قانوني آخر لذلك.',
- 'وتتعهد باستخدام الموقع فقط لأغراض مشروعة.',
- 'وإذا رأينا أن هناك خرقاً لهذه الشروط العامة للاستخدام، فيجوز لنا اتخاذ أي إجراء نراه مناسباً.',
- ],
- },
- {
- heading: '11. الملكية الفكرية',
- paragraphs: [
- 'الموقع ملك لنا، والموقع بكامله محمي بموجب القوانين المتعلقة بالملكية الفكرية.',
- 'وجميع حقوق النسخ والتمثيل محفوظة.',
- ],
- },
- ],
- },
- },
-}
-
-export function isFooterPageSlug(value: string): value is FooterPageSlug {
- return footerPageSlugs.includes(value as FooterPageSlug)
-}
-
-export function getFooterPageContent(language: CarplaceLanguage, slug: FooterPageSlug): FooterPageContent {
- return footerContent[language][slug]
-}
diff --git a/carplace/src/lib/i18n.server.test.ts b/carplace/src/lib/i18n.server.test.ts
deleted file mode 100644
index 1452573..0000000
--- a/carplace/src/lib/i18n.server.test.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { beforeEach, describe, expect, it, vi } from 'vitest'
-
-const cookieValues = vi.hoisted(() => new Map())
-
-vi.mock('next/headers', () => ({
- cookies: async () => ({
- get: (name: string) => {
- const value = cookieValues.get(name)
- return value === undefined ? undefined : { value }
- },
- }),
-}))
-
-import { getCarplaceLanguage } from './i18n.server'
-import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE } from './i18n'
-
-describe('getCarplaceLanguage', () => {
- beforeEach(() => {
- cookieValues.clear()
- })
-
- it('prefers the shared language cookie over the legacy carplace cookie', async () => {
- cookieValues.set(SHARED_LANGUAGE_COOKIE, 'ar')
- cookieValues.set(MARKETPLACE_LANGUAGE_COOKIE, 'fr')
-
- await expect(getCarplaceLanguage()).resolves.toBe('ar')
- })
-
- it('falls back to the legacy carplace cookie during migration', async () => {
- cookieValues.set(MARKETPLACE_LANGUAGE_COOKIE, 'fr')
-
- await expect(getCarplaceLanguage()).resolves.toBe('fr')
- })
-
- it('defaults to English when cookies are absent or invalid', async () => {
- await expect(getCarplaceLanguage()).resolves.toBe('en')
- cookieValues.set(SHARED_LANGUAGE_COOKIE, 'es')
-
- await expect(getCarplaceLanguage()).resolves.toBe('en')
- })
-})
diff --git a/carplace/src/lib/i18n.server.ts b/carplace/src/lib/i18n.server.ts
deleted file mode 100644
index 41e6f73..0000000
--- a/carplace/src/lib/i18n.server.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { cookies } from 'next/headers'
-import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE, isCarplaceLanguage, type CarplaceLanguage } from './i18n'
-
-export async function getCarplaceLanguage(): Promise {
- const cookieStore = await cookies()
- const cookieValue =
- cookieStore.get(SHARED_LANGUAGE_COOKIE)?.value ??
- cookieStore.get(MARKETPLACE_LANGUAGE_COOKIE)?.value
- return isCarplaceLanguage(cookieValue) ? cookieValue : 'en'
-}
diff --git a/carplace/src/lib/i18n.test.ts b/carplace/src/lib/i18n.test.ts
deleted file mode 100644
index ab77752..0000000
--- a/carplace/src/lib/i18n.test.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { isCarplaceLanguage } from './i18n'
-
-describe('carplace language guard', () => {
- it('accepts the supported carplace locales', () => {
- expect(isCarplaceLanguage('en')).toBe(true)
- expect(isCarplaceLanguage('fr')).toBe(true)
- expect(isCarplaceLanguage('ar')).toBe(true)
- })
-
- it('rejects unsupported, missing, and case-mismatched locales', () => {
- expect(isCarplaceLanguage('de')).toBe(false)
- expect(isCarplaceLanguage('EN')).toBe(false)
- expect(isCarplaceLanguage('')).toBe(false)
- expect(isCarplaceLanguage(null)).toBe(false)
- expect(isCarplaceLanguage(undefined)).toBe(false)
- })
-})
diff --git a/carplace/src/lib/i18n.ts b/carplace/src/lib/i18n.ts
deleted file mode 100644
index 93d1be4..0000000
--- a/carplace/src/lib/i18n.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export type CarplaceLanguage = 'en' | 'fr' | 'ar'
-
-export const MARKETPLACE_LANGUAGE_COOKIE = 'carplace-language'
-export const SHARED_LANGUAGE_COOKIE = 'rentaldrivego-language'
-
-export function isCarplaceLanguage(value: string | null | undefined): value is CarplaceLanguage {
- return value === 'en' || value === 'fr' || value === 'ar'
-}
diff --git a/carplace/src/lib/preferences.test.ts b/carplace/src/lib/preferences.test.ts
deleted file mode 100644
index 6a6d3ca..0000000
--- a/carplace/src/lib/preferences.test.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { afterEach, describe, expect, it } from 'vitest'
-import { getScopedPreferenceCookieName, getScopedPreferenceKey, readScopedPreference, writeScopedPreference } from './preferences'
-
-function installBrowser(cookie = '') {
- const store = new Map()
- let cookieValue = cookie
- Object.defineProperty(globalThis, 'window', {
- configurable: true,
- value: {
- localStorage: {
- getItem: (key: string) => store.get(key) ?? null,
- setItem: (key: string, value: string) => store.set(key, value),
- },
- },
- })
- Object.defineProperty(globalThis, 'document', {
- configurable: true,
- value: {
- get cookie() { return cookieValue },
- set cookie(value: string) {
- const [pair] = value.split(';')
- const [name, val] = pair.split('=')
- cookieValue = cookieValue
- .split(';')
- .map((chunk) => chunk.trim())
- .filter(Boolean)
- .filter((chunk) => !chunk.startsWith(`${name}=`))
- .concat(`${name}=${val}`)
- .join('; ')
- },
- },
- })
- Object.defineProperty(globalThis, 'atob', {
- configurable: true,
- value: (value: string) => Buffer.from(value, 'base64').toString('binary'),
- })
- return { store, get cookie() { return cookieValue } }
-}
-
-afterEach(() => {
- Reflect.deleteProperty(globalThis, 'window')
- Reflect.deleteProperty(globalThis, 'document')
- Reflect.deleteProperty(globalThis, 'atob')
-})
-
-describe('carplace scoped preferences', () => {
- it('returns unscoped keys when no usable token exists', () => {
- installBrowser()
- expect(getScopedPreferenceKey('rentaldrivego-language')).toBe('rentaldrivego-language')
- expect(getScopedPreferenceCookieName('rentaldrivego-language')).toBe('rentaldrivego-language')
- })
-
- it('reads shared cookies before legacy local-storage fallbacks', () => {
- const browser = installBrowser('rentaldrivego-language=fr')
- browser.store.set('carplace-language', 'ar')
-
- expect(readScopedPreference('rentaldrivego-language', ['carplace-language'])).toBe('fr')
- })
-
- it('writes shared and legacy values without auth-token scoping', () => {
- const browser = installBrowser()
-
- writeScopedPreference('rentaldrivego-theme', 'dark', ['dashboard-theme'])
-
- expect(browser.store.get('rentaldrivego-theme')).toBe('dark')
- expect(browser.store.get('dashboard-theme')).toBe('dark')
- expect(browser.cookie).toContain('rentaldrivego-theme=dark')
- })
-})
diff --git a/carplace/src/lib/preferences.ts b/carplace/src/lib/preferences.ts
deleted file mode 100644
index 3b0b0d0..0000000
--- a/carplace/src/lib/preferences.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-export const SHARED_LANGUAGE_COOKIE = 'rentaldrivego-language'
-export const SHARED_LANGUAGE_KEY = 'rentaldrivego-language'
-export const SHARED_THEME_KEY = 'rentaldrivego-theme'
-
-function readEmployeeToken() {
- return null
-}
-
-function decodeEmployeeId(token: string | null) {
- if (!token) return null
-
- try {
- const encoded = token.split('.')[1] ?? ''
- const normalized = encoded.replace(/-/g, '+').replace(/_/g, '/')
- const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=')
- const payload = JSON.parse(atob(padded)) as { sub?: string }
- return typeof payload.sub === 'string' && payload.sub ? payload.sub : null
- } catch {
- return null
- }
-}
-
-export function getScopedPreferenceKey(baseKey: string) {
- const employeeId = decodeEmployeeId(readEmployeeToken())
- return employeeId ? `${baseKey}:${employeeId}` : baseKey
-}
-
-export function getScopedPreferenceCookieName(baseKey: string) {
- const employeeId = decodeEmployeeId(readEmployeeToken())
- return employeeId ? `${baseKey}--${employeeId}` : baseKey
-}
-
-function readCookie(name: string) {
- if (typeof document === 'undefined') return null
-
- return document.cookie
- .split(';')
- .map((chunk) => chunk.trim())
- .find((chunk) => chunk.startsWith(`${name}=`))
- ?.slice(name.length + 1) ?? null
-}
-
-function writeCookie(name: string, value: string) {
- if (typeof document === 'undefined') return
- document.cookie = `${name}=${value}; path=/; max-age=31536000; samesite=lax`
-}
-
-export function readCurrentUserScopedPreference(baseKey: string) {
- if (typeof window === 'undefined') return null
-
- const scopedCookie = readCookie(getScopedPreferenceCookieName(baseKey))
- if (scopedCookie) return scopedCookie
-
- return window.localStorage.getItem(getScopedPreferenceKey(baseKey))
-}
-
-export function readScopedPreference(baseKey: string, legacyKeys: string[] = []) {
- if (typeof window === 'undefined') return null
-
- const scopedCookie = readCookie(getScopedPreferenceCookieName(baseKey))
- if (scopedCookie) return scopedCookie
-
- const sharedCookie = readCookie(baseKey)
- if (sharedCookie) return sharedCookie
-
- const scopedKey = getScopedPreferenceKey(baseKey)
- const candidates = [scopedKey, baseKey, ...legacyKeys]
-
- for (const key of candidates) {
- const value = window.localStorage.getItem(key)
- if (value) return value
- }
-
- return null
-}
-
-export function writeScopedPreference(baseKey: string, value: string, legacyKeys: string[] = []) {
- if (typeof window === 'undefined') return
-
- const scopedKey = getScopedPreferenceKey(baseKey)
- const scopedCookie = getScopedPreferenceCookieName(baseKey)
-
- writeCookie(baseKey, value)
- if (scopedCookie !== baseKey) {
- writeCookie(scopedCookie, value)
- }
-
- window.localStorage.setItem(scopedKey, value)
- window.localStorage.setItem(baseKey, value)
-
- for (const key of legacyKeys) {
- window.localStorage.setItem(key, value)
- }
-}
diff --git a/carplace/src/lib/renter.test.ts b/carplace/src/lib/renter.test.ts
deleted file mode 100644
index 74a4ef3..0000000
--- a/carplace/src/lib/renter.test.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { afterEach, describe, expect, it, vi } from 'vitest'
-
-function installBrowser(_token?: string) {
- const store = new Map()
- Object.defineProperty(globalThis, 'window', {
- configurable: true,
- value: {
- localStorage: {
- getItem: vi.fn((key: string) => store.get(key) ?? null),
- setItem: vi.fn((key: string, value: string) => store.set(key, value)),
- removeItem: vi.fn((key: string) => store.delete(key)),
- },
- },
- })
- return store
-}
-
-afterEach(() => {
- vi.restoreAllMocks()
- Reflect.deleteProperty(globalThis, 'window')
- Reflect.deleteProperty(globalThis, 'fetch')
- vi.resetModules()
-})
-
-describe('renter client helpers', () => {
- it('clears renter sessions through the API logout endpoint', async () => {
- installBrowser('renter-token')
- const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: { success: true } }) }))
- Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
- const { clearRenterSession } = await import('./renter')
-
- clearRenterSession()
-
- expect(fetchMock).toHaveBeenCalledWith(expect.stringMatching(/\/api\/v1\/auth\/renter\/logout$/), expect.objectContaining({
- method: 'POST',
- credentials: 'include',
- }))
- })
-
- it('sends cookies and unwraps renter profile data', async () => {
- installBrowser('renter-token')
- const fetchMock = vi.fn(async () => ({ ok: true, status: 200, json: async () => ({ data: { id: 'renter_1' } }) }))
- Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
- const { loadRenterProfile } = await import('./renter')
-
- await expect(loadRenterProfile()).resolves.toEqual({ id: 'renter_1' })
- expect(fetchMock).toHaveBeenCalledWith(expect.stringMatching(/\/api\/v1\/auth\/renter\/me$/), expect.objectContaining({
- credentials: 'include',
- headers: expect.objectContaining({ 'Content-Type': 'application/json' }),
- }))
- })
-
- it('removes stale tokens and throws renter auth errors on 401', async () => {
- installBrowser('expired-token')
- Object.defineProperty(globalThis, 'fetch', {
- configurable: true,
- value: vi.fn(async () => ({ ok: false, status: 401, json: async () => ({ message: 'Unauthorized' }) })),
- })
- const { loadRenterNotifications, RenterAuthError } = await import('./renter')
-
- await expect(loadRenterNotifications()).rejects.toBeInstanceOf(RenterAuthError)
-
- })
-
- it('serializes preference updates to the renter notification endpoint', async () => {
- installBrowser('renter-token')
- const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: { success: true } }) }))
- Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
- const { updateRenterPreferences } = await import('./renter')
- const body = [{ notificationType: 'reservation', channel: 'email', enabled: true }]
-
- await expect(updateRenterPreferences(body)).resolves.toEqual({ success: true })
- expect(fetchMock).toHaveBeenCalledWith(expect.stringMatching(/\/api\/v1\/notifications\/renter\/preferences$/), expect.objectContaining({
- method: 'PATCH',
- body: JSON.stringify(body),
- }))
- })
-})
diff --git a/carplace/src/lib/renter.ts b/carplace/src/lib/renter.ts
deleted file mode 100644
index 32f32df..0000000
--- a/carplace/src/lib/renter.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-'use client'
-
-const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1'
-
-export interface SavedCompany {
- id: string
- brand?: {
- displayName: string
- subdomain: string
- logoUrl?: string | null
- } | null
-}
-
-export interface RenterProfile {
- id: string
- firstName: string
- lastName: string
- email: string
- phone?: string | null
- preferredLocale?: string
- preferredCurrency?: string
- emailVerified?: boolean
- savedCompanies?: SavedCompany[]
-}
-
-export interface RenterPreference {
- notificationType: string
- channel: string
- enabled: boolean
-}
-
-export interface RenterNotification {
- id: string
- title: string
- body: string
- type: string
- status: string
- readAt: string | null
- createdAt: string
-}
-
-export class RenterAuthError extends Error {
- constructor(message = 'Authentication required') {
- super(message)
- this.name = 'RenterAuthError'
- }
-}
-
-async function renterFetch(path: string, init?: RequestInit): Promise {
- const res = await fetch(`${API_BASE}${path}`, {
- ...init,
- credentials: 'include',
- headers: {
- 'Content-Type': 'application/json',
- ...(init?.headers ?? {}),
- },
- })
-
- const json = await res.json().catch(() => null)
- if (res.status === 401) {
- throw new RenterAuthError()
- }
- if (!res.ok) throw new Error(json?.message ?? 'Request failed')
- return (json?.data ?? json) as T
-}
-
-export function clearRenterSession() {
- void fetch(`${API_BASE}/auth/renter/logout`, { method: 'POST', credentials: 'include' })
-}
-
-export function loadRenterProfile() {
- return renterFetch('/auth/renter/me')
-}
-
-export function updateRenterProfile(body: Partial) {
- return renterFetch('/auth/renter/me', {
- method: 'PATCH',
- body: JSON.stringify(body),
- })
-}
-
-export function loadRenterNotifications() {
- return renterFetch('/notifications/renter')
-}
-
-export function markRenterNotificationRead(id: string) {
- return renterFetch<{ success: boolean }>(`/notifications/renter/${id}/read`, {
- method: 'POST',
- })
-}
-
-export function markAllRenterNotificationsRead() {
- return renterFetch<{ success: boolean }>('/notifications/renter/read-all', {
- method: 'POST',
- })
-}
-
-export function loadRenterPreferences() {
- return renterFetch('/notifications/renter/preferences')
-}
-
-export function updateRenterPreferences(body: RenterPreference[]) {
- return renterFetch<{ success: boolean }>('/notifications/renter/preferences', {
- method: 'PATCH',
- body: JSON.stringify(body),
- })
-}
diff --git a/carplace/src/middleware.test.ts b/carplace/src/middleware.test.ts
deleted file mode 100644
index 4aaf77f..0000000
--- a/carplace/src/middleware.test.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import { beforeEach, describe, expect, it, vi } from 'vitest'
-
-const nextServer = vi.hoisted(() => {
- const next = vi.fn((init?: { request?: { headers?: Headers } }) => ({
- kind: 'next',
- init,
- cookies: {
- set: vi.fn(),
- },
- }))
- const redirect = vi.fn((url: URL, status?: number) => ({
- kind: 'redirect',
- url: url.toString(),
- status,
- }))
- const MockNextResponse = vi.fn((body?: string, init?: { status?: number }) => ({
- kind: 'response',
- body,
- status: init?.status,
- })) as any
- MockNextResponse.next = next
- MockNextResponse.redirect = redirect
- return { next, redirect, MockNextResponse }
-})
-
-vi.mock('next/server', () => ({
- NextResponse: nextServer.MockNextResponse,
-}))
-
-function request(options: { cookies?: Record; headers?: Record } = {}) {
- const cookies = new Map(Object.entries(options.cookies ?? {}))
- const headers = new Headers(options.headers ?? {})
- return {
- nextUrl: cloneableUrl('http://localhost:3000/'),
- cookies: {
- get: vi.fn((name: string) => {
- const value = cookies.get(name)
- return value ? { value } : undefined
- }),
- },
- headers,
- }
-}
-
-function cloneableUrl(input: string): URL {
- const url = new URL(input)
- ;(url as URL & { clone: () => URL }).clone = () => cloneableUrl(url.toString())
- return url
-}
-
-function middlewareRequest(input: string) {
- return {
- cookies: {
- get: vi.fn(),
- },
- headers: new Headers(),
- nextUrl: cloneableUrl(input),
- }
-}
-
-beforeEach(() => {
- nextServer.next.mockClear()
- nextServer.redirect.mockClear()
-})
-
-describe('carplace proxy language bootstrap', () => {
-
- it('rejects proxy subrequest headers before language handling', async () => {
- const { proxy } = await import('./proxy')
-
- const response = proxy(request({
- headers: { 'x-middleware-subrequest': 'middleware:middleware' },
- }) as never) as any
-
- expect(response).toEqual({ kind: 'response', body: 'Unsupported internal request header', status: 400 })
- expect(nextServer.next).not.toHaveBeenCalled()
- })
- it('does nothing when the canonical shared language cookie is valid', async () => {
- const { proxy } = await import('./proxy')
-
- const response = proxy(request({ cookies: { 'rentaldrivego-language': 'fr' } }) as never) as any
-
- expect(response.kind).toBe('next')
- expect(nextServer.next).toHaveBeenCalledWith()
- expect(response.cookies.set).not.toHaveBeenCalled()
- })
-
- it('migrates the legacy carplace language cookie into the shared cookie and request headers', async () => {
- const { proxy } = await import('./proxy')
-
- const response = proxy(request({
- cookies: { 'carplace-language': 'ar' },
- headers: { cookie: 'session=abc' },
- }) as never) as any
-
- expect(response.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'ar', expect.objectContaining({
- path: '/',
- sameSite: 'lax',
- }))
- expect(response.init?.request?.headers?.get('cookie')).toBe('session=abc; rentaldrivego-language=ar')
- })
-
- it('detects Arabic and French from Accept-Language before falling back to English', async () => {
- const { proxy } = await import('./proxy')
-
- const arabic = proxy(request({ headers: { 'accept-language': 'ar-MA,fr;q=0.8,en;q=0.6' } }) as never) as any
- expect(arabic.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'ar', expect.any(Object))
- expect(arabic.init?.request?.headers?.get('cookie')).toBe('rentaldrivego-language=ar')
-
- const french = proxy(request({ headers: { 'accept-language': 'de-DE,fr-FR;q=0.7,en;q=0.3' } }) as never) as any
- expect(french.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'fr', expect.any(Object))
-
- const fallback = proxy(request({ headers: { 'accept-language': 'de-DE,es;q=0.9' } }) as never) as any
- expect(fallback.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'en', expect.any(Object))
- })
-
- it('rejects invalid shared and legacy language values before resolving from headers', async () => {
- const { proxy } = await import('./proxy')
-
- const response = proxy(request({
- cookies: { 'rentaldrivego-language': 'es', 'carplace-language': 'it' },
- headers: { 'accept-language': 'fr-CA,ar;q=0.5' },
- }) as never) as any
-
- expect(response.cookies.set).toHaveBeenCalledWith('rentaldrivego-language', 'fr', expect.any(Object))
- expect(response.init?.request?.headers?.get('cookie')).toBe('rentaldrivego-language=fr')
- })
-})
-
-describe('carplace middleware dashboard path canonicalization', () => {
- it('redirects duplicate dashboard prefixes to the clean dashboard URL', async () => {
- const { proxy } = await import('./proxy')
-
- const response = proxy(middlewareRequest('http://localhost:3000/dashboard/dashboard') as never)
-
- expect(response).toEqual({ kind: 'redirect', url: 'http://localhost:3000/dashboard', status: 307 })
- })
-
- it('preserves nested dashboard paths while removing the duplicate prefix', async () => {
- const { proxy } = await import('./proxy')
-
- const response = proxy(middlewareRequest('http://localhost:3000/dashboard/dashboard/fleet?tab=active') as never)
-
- expect(response).toEqual({ kind: 'redirect', url: 'http://localhost:3000/dashboard/fleet?tab=active', status: 307 })
- })
-})
diff --git a/carplace/src/proxy.ts b/carplace/src/proxy.ts
deleted file mode 100644
index 40387e5..0000000
--- a/carplace/src/proxy.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { NextResponse } from 'next/server'
-import type { NextRequest } from 'next/server'
-
-const SHARED_LANGUAGE_COOKIE = 'rentaldrivego-language'
-const MARKETPLACE_LANGUAGE_COOKIE = 'carplace-language'
-const DASHBOARD_BASE_PATH = '/dashboard'
-
-function rejectInternalSubrequest(request: NextRequest): NextResponse | null {
- if (!request.headers.has('x-middleware-subrequest')) return null
- return new NextResponse('Unsupported internal request header', { status: 400 })
-}
-
-function isValidLanguage(val: string | null | undefined): val is 'en' | 'fr' | 'ar' {
- return val === 'en' || val === 'fr' || val === 'ar'
-}
-
-function detectFromAcceptLanguage(header: string | null): 'en' | 'fr' | 'ar' {
- if (!header) return 'en'
- for (const entry of header.split(',')) {
- const code = entry.split(';')[0].trim().split('-')[0].toLowerCase()
- if (code === 'ar' || code === 'fr' || code === 'en') return code as 'en' | 'fr' | 'ar'
- }
- return 'en'
-}
-
-function deduplicateDashboardPath(pathname: string): string | null {
- if (!pathname.startsWith(`${DASHBOARD_BASE_PATH}${DASHBOARD_BASE_PATH}`)) return null
-
- let normalized = pathname
- while (normalized.startsWith(`${DASHBOARD_BASE_PATH}${DASHBOARD_BASE_PATH}`)) {
- normalized = normalized.slice(DASHBOARD_BASE_PATH.length)
- }
- return normalized || DASHBOARD_BASE_PATH
-}
-
-export function proxy(request: NextRequest) {
- const rejected = rejectInternalSubrequest(request)
- if (rejected) return rejected
-
- const deduped = deduplicateDashboardPath(request.nextUrl.pathname)
- if (deduped) {
- const url = request.nextUrl.clone()
- url.pathname = deduped
- return NextResponse.redirect(url, 307)
- }
-
- const sharedLang = request.cookies.get(SHARED_LANGUAGE_COOKIE)?.value
-
- if (isValidLanguage(sharedLang)) return NextResponse.next()
-
- const legacyLang = request.cookies.get(MARKETPLACE_LANGUAGE_COOKIE)?.value
- const resolved: 'en' | 'fr' | 'ar' = isValidLanguage(legacyLang)
- ? legacyLang
- : detectFromAcceptLanguage(request.headers.get('accept-language'))
-
- const requestHeaders = new Headers(request.headers)
- const existing = request.headers.get('cookie') ?? ''
- const injected = `${SHARED_LANGUAGE_COOKIE}=${resolved}`
- requestHeaders.set('cookie', existing ? `${existing}; ${injected}` : injected)
-
- const response = NextResponse.next({ request: { headers: requestHeaders } })
-
- response.cookies.set(SHARED_LANGUAGE_COOKIE, resolved, {
- maxAge: 365 * 24 * 60 * 60,
- path: '/',
- sameSite: 'lax',
- })
-
- return response
-}
-
-export const config = {
- matcher: [
- '/dashboard/dashboard/:path*',
- '/((?!_next/static|_next/image|_next/webpack-hmr|favicon\\.ico|.*\\.(?:png|ico|jpg|jpeg|svg|webp)$).*)',
- ],
-}
diff --git a/carplace/src/styles/phase16-tokens.css b/carplace/src/styles/phase16-tokens.css
deleted file mode 100644
index 938313c..0000000
--- a/carplace/src/styles/phase16-tokens.css
+++ /dev/null
@@ -1,143 +0,0 @@
-/* RentalDriveGo Phase 16 application design tokens.
- * Derived from the approved marketing-site token system and adapted for the
- * class-based theme controls used by the operational applications.
- */
-:root {
- --rdg-blue-50: #eff6ff;
- --rdg-blue-100: #dbeafe;
- --rdg-blue-200: #bfdbfe;
- --rdg-blue-500: #2874e8;
- --rdg-blue-600: #1b5dd8;
- --rdg-blue-700: #174bb5;
- --rdg-blue-800: #183d88;
- --rdg-orange-100: #ffedd5;
- --rdg-orange-400: #fb923c;
- --rdg-orange-600: #ea580c;
- --rdg-orange-700: #c2410c;
- --rdg-navy-950: #081426;
- --rdg-navy-900: #0d1b2e;
- --rdg-navy-850: #11233b;
- --rdg-navy-800: #162b46;
- --rdg-gray-25: #fbfdff;
- --rdg-gray-50: #f7f9fc;
- --rdg-gray-100: #eef2f7;
- --rdg-gray-200: #dbe3ed;
- --rdg-gray-300: #c6d0dc;
- --rdg-gray-500: #64748b;
- --rdg-gray-600: #475569;
- --rdg-gray-700: #334155;
- --rdg-gray-800: #1e293b;
- --rdg-gray-900: #0f172a;
- --rdg-green-100: #dcfce7;
- --rdg-green-700: #15803d;
- --rdg-amber-100: #fef3c7;
- --rdg-amber-800: #92400e;
- --rdg-red-100: #fee2e2;
- --rdg-red-700: #b91c1c;
-
- --rdg-radius-sm: 10px;
- --rdg-radius-md: 16px;
- --rdg-radius-lg: 24px;
- --rdg-radius-pill: 999px;
- --rdg-touch-min: 44px;
- --rdg-container-max: 1280px;
- --rdg-font-latin: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
- --rdg-font-arabic: "Noto Sans Arabic", Tahoma, Arial, sans-serif;
-
- --rdg-surface-page: var(--rdg-gray-25);
- --rdg-surface-primary: #ffffff;
- --rdg-surface-elevated: #ffffff;
- --rdg-surface-muted: #f2f6fb;
- --rdg-surface-strong: #e8f0fa;
- --rdg-text-primary: #102035;
- --rdg-text-secondary: #52647a;
- --rdg-text-subdued: var(--rdg-gray-500);
- --rdg-text-inverse: #f8fafc;
- --rdg-border: #d8e2ee;
- --rdg-border-strong: #bdcad9;
- --rdg-action-primary: var(--rdg-blue-700);
- --rdg-action-primary-hover: var(--rdg-blue-800);
- --rdg-action-conversion: var(--rdg-orange-700);
- --rdg-action-conversion-hover: #9a3412;
- --rdg-focus: #f97316;
- --rdg-soft-blue: #eef5ff;
- --rdg-soft-orange: #fff7ed;
- --rdg-shadow-color: rgba(8, 20, 38, 0.12);
- --rdg-shadow-subtle: 0 1px 2px var(--rdg-shadow-color);
- --rdg-shadow-card: 0 20px 60px var(--rdg-shadow-color);
- --rdg-shadow-overlay: 0 28px 80px var(--rdg-shadow-color);
-
- /* Compatibility aliases for the existing apps. */
- --primary: var(--rdg-action-primary);
- --primary-hover: var(--rdg-action-primary-hover);
- --accent: var(--rdg-action-conversion);
- --bg-elevated: var(--rdg-surface-elevated);
- --border-accent: var(--rdg-border);
- --glass-bg: color-mix(in srgb, var(--rdg-surface-elevated) 88%, transparent);
- --shadow-card: var(--rdg-shadow-card);
- --shadow-glow: 0 0 20px color-mix(in srgb, var(--rdg-action-conversion) 18%, transparent);
-}
-
-html.dark,
-html[data-theme='dark'] {
- --rdg-surface-page: var(--rdg-navy-950);
- --rdg-surface-primary: var(--rdg-navy-900);
- --rdg-surface-elevated: var(--rdg-navy-850);
- --rdg-surface-muted: #0b1a2d;
- --rdg-surface-strong: #172d48;
- --rdg-text-primary: #eef5ff;
- --rdg-text-secondary: #adbed2;
- --rdg-text-subdued: var(--rdg-gray-300);
- --rdg-text-inverse: var(--rdg-navy-950);
- --rdg-border: #263c56;
- --rdg-border-strong: #3a536e;
- --rdg-action-primary: #76a9ff;
- --rdg-action-primary-hover: #9bc2ff;
- --rdg-action-conversion: var(--rdg-orange-400);
- --rdg-action-conversion-hover: #fdba74;
- --rdg-focus: var(--rdg-orange-400);
- --rdg-soft-blue: #10284a;
- --rdg-soft-orange: #3a2015;
- --rdg-shadow-color: rgba(0, 0, 0, 0.36);
- color-scheme: dark;
-}
-
-html.light,
-html[data-theme='light'] {
- color-scheme: light;
-}
-
-html[lang='ar'] body {
- font-family: var(--rdg-font-arabic);
- line-height: 1.8;
-}
-
-::selection {
- background: var(--rdg-blue-200);
- color: var(--rdg-gray-900);
-}
-
-html.dark ::selection,
-html[data-theme='dark'] ::selection {
- background: var(--rdg-blue-800);
- color: var(--rdg-gray-25);
-}
-
-:focus-visible {
- outline: 3px solid var(--rdg-focus);
- outline-offset: 3px;
-}
-
-@media (prefers-reduced-motion: reduce) {
- html {
- scroll-behavior: auto;
- }
-
- *,
- *::before,
- *::after {
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
- }
-}
diff --git a/carplace/tailwind.config.ts b/carplace/tailwind.config.ts
deleted file mode 100644
index 8055fd6..0000000
--- a/carplace/tailwind.config.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Config } from 'tailwindcss'
-
-const config: Config = {
- content: ['./src/**/*.{ts,tsx}'],
- darkMode: 'class',
- theme: {
- extend: {
- fontFamily: {
- sans: ['Inter', 'ui-sans-serif', 'system-ui'],
- },
- },
- },
- plugins: [],
-}
-
-export default config
diff --git a/carplace/tsconfig.json b/carplace/tsconfig.json
deleted file mode 100644
index b575f7d..0000000
--- a/carplace/tsconfig.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2017",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "react-jsx",
- "incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
- "paths": {
- "@/*": [
- "./src/*"
- ]
- }
- },
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts",
- ".next/dev/types/**/*.ts"
- ],
- "exclude": [
- "node_modules"
- ]
-}
diff --git a/carplace/vitest.config.ts b/carplace/vitest.config.ts
deleted file mode 100644
index 474502a..0000000
--- a/carplace/vitest.config.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { fileURLToPath } from 'node:url'
-import { defineConfig } from 'vitest/config'
-
-export default defineConfig({
- resolve: {
- alias: {
- '@': fileURLToPath(new URL('./src', import.meta.url)),
- },
- },
- test: {
- environment: 'node',
- globals: true,
- include: ['src/**/*.test.ts'],
- clearMocks: true,
- restoreMocks: true,
- },
-})