Files
carmanagement/dashboard/src/app/(dashboard)/settings/page.tsx
T
root d7fb7b7a7b
Build & Deploy / Build & Push Docker Image (push) Failing after 47s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / API Unit Tests (push) Failing after 5m4s
Test / Marketplace Unit Tests (push) Failing after 4m55s
Test / Admin Unit Tests (push) Successful in 9m37s
Test / Dashboard Unit Tests (push) Successful in 9m37s
Test / API Integration Tests (push) Successful in 9m54s
redesign the homepage
2026-06-26 16:27:21 -04:00

831 lines
41 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useState } from 'react'
import { apiFetch } from '@/lib/api'
import { useDashboardI18n } from '@/components/I18nProvider'
interface BrandSettings {
displayName: string
tagline: string | null
primaryColor: string
accentColor?: string | null
publicEmail: string | null
publicPhone: string | null
publicAddress?: string | null
publicCity: string | null
publicCountry?: string | null
subdomain: string
logoUrl?: string | null
heroImageUrl?: string | null
websiteUrl?: string | null
whatsappNumber?: string | null
paypalEmail: string | null
amanpayMerchantId: string | null
amanpaySecretKey?: string | null
paypalMerchantId?: string | null
customDomain?: string | null
customDomainVerified?: boolean
defaultLocale?: string
defaultCurrency?: string
isListedOnMarketplace: boolean
}
interface ContractSettings {
fuelPolicyType: string
fuelPolicyNote: string | null
additionalDriverCharge: 'FREE' | 'PER_DAY' | 'FLAT'
additionalDriverDailyRate: number
additionalDriverFlatRate: number
damagePolicy: string
}
interface InsurancePolicy {
id: string
name: string
type: string
chargeType: string
chargeValue: number
isRequired: boolean
isActive: boolean
}
interface PricingRule {
id: string
name: string
type: string
condition: string
conditionValue: number
adjustmentType: string
adjustmentValue: number
isActive: boolean
}
interface AccountingSettings {
reportingPeriod: string
accountantEmail: string | null
accountantName: string | null
autoSendReport: boolean
reportFormat: string
}
const emptyInsurance = { name: '', type: 'BASIC', chargeType: 'PER_DAY', chargeValue: 0, isRequired: false, isActive: true }
const emptyRule = { name: '', type: 'SURCHARGE', condition: 'AGE_LESS_THAN', conditionValue: 25, adjustmentType: 'PERCENTAGE', adjustmentValue: 0, isActive: true }
export default function SettingsPage() {
const { language } = useDashboardI18n()
const copy = {
en: {
title: 'Advanced settings',
subtitle: 'Manage insurance, additional-driver rules, pricing adjustments, and reporting defaults.',
save: 'Saving…',
saveBrand: 'Save brand',
brandProfile: 'Brand and public profile',
brandProfileDesc: 'Control how your company appears on the marketplace and public booking site.',
payments: 'Rental payment methods',
paymentsDesc: 'Configure how renters pay your company on the public booking site.',
savePayments: 'Save payments',
customDomain: 'Custom domain',
customDomainDesc: 'Point your own domain to the branded booking site.',
saveDomain: 'Save domain',
removeDomain: 'Remove custom domain',
contractPolicies: 'Contract and driver policies',
savePolicies: 'Save policies',
insurancePolicies: 'Insurance policies',
pricingRules: 'Pricing rules',
accounting: 'Accounting and exports',
saveAccounting: 'Save accounting',
displayName: 'Display name',
tagline: 'Tagline',
publicEmail: 'Public email',
publicPhone: 'Public phone',
city: 'City',
country: 'Country',
websiteUrl: 'Website URL',
contractLanguage: 'Contract language',
whatsapp: 'WhatsApp number',
brandColor: 'Brand color',
listedMarketplace: 'Listed on marketplace',
logoUpload: 'Logo upload',
heroUpload: 'Hero image upload',
uploading: 'Uploading…',
logoUploaded: 'Logo uploaded',
noLogo: 'No logo uploaded yet',
heroUploaded: 'Hero image uploaded',
noHero: 'No hero image uploaded yet',
subdomain: 'Subdomain',
customDomainLabel: 'Custom domain',
domainPlaceholder: 'cars.example.com',
status: 'Status',
verified: 'Verified',
pendingDns: 'Pending DNS verification',
notConfigured: 'Not configured',
fuelPolicyType: 'Fuel policy type',
additionalDriverCharge: 'Additional driver charge',
dailyDriverRate: 'Daily driver rate',
flatDriverRate: 'Flat driver rate',
fuelPolicyNote: 'Fuel policy note',
damagePolicy: 'Damage policy',
active: 'Active',
inactive: 'Inactive',
noInsurance: 'No insurance policies configured yet.',
policyName: 'Policy name',
chargeValue: 'Charge value',
required: 'Required',
addPolicy: 'Add policy',
noRules: 'No pricing rules configured yet.',
ruleName: 'Rule name',
addRule: 'Add rule',
reportingPeriod: 'Reporting period',
reportFormat: 'Report format',
accountantName: 'Accountant name',
accountantEmail: 'Accountant email',
autoSend: 'Auto-send reports to the accountant',
amanpayMerchantId: 'AmanPay merchant ID',
amanpaySecretKey: 'AmanPay secret key',
paypalEmail: 'PayPal business email',
noPaymentConfigured: 'If no payment method is configured, renters can still submit reservation requests and pay on pickup.',
fuelPolicyLabels: { FULL_TO_FULL: 'Full to full', FULL_TO_EMPTY: 'Full to empty', SAME_TO_SAME: 'Same to same', PREPAID: 'Prepaid', FREE: 'Included' } as Record<string, string>,
driverChargeLabels: { FREE: 'Free', PER_DAY: 'Per day', FLAT: 'Flat' } as Record<string, string>,
reportingPeriodLabels: { WEEKLY: 'Weekly', MONTHLY: 'Monthly', QUARTERLY: 'Quarterly', ANNUAL: 'Annual' } as Record<string, string>,
reportFormatLabels: { CSV: 'CSV', PDF: 'PDF', BOTH: 'Both' } as Record<string, string>,
},
fr: {
title: 'Paramètres avancés',
subtitle: 'Gérez les assurances, règles conducteurs, ajustements tarifaires et paramètres de reporting.',
save: 'Enregistrement…',
saveBrand: 'Enregistrer la marque',
brandProfile: 'Marque et profil public',
brandProfileDesc: 'Contrôlez laffichage de votre entreprise sur la marketplace et le site de réservation.',
payments: 'Méthodes de paiement location',
paymentsDesc: 'Configurez comment les clients paient votre entreprise.',
savePayments: 'Enregistrer les paiements',
customDomain: 'Domaine personnalisé',
customDomainDesc: 'Pointez votre propre domaine vers le site de réservation.',
saveDomain: 'Enregistrer le domaine',
removeDomain: 'Supprimer le domaine',
contractPolicies: 'Contrat et politiques conducteur',
savePolicies: 'Enregistrer les politiques',
insurancePolicies: 'Polices dassurance',
pricingRules: 'Règles tarifaires',
accounting: 'Comptabilité et exports',
saveAccounting: 'Enregistrer la comptabilité',
displayName: 'Nom affiché',
tagline: 'Slogan',
publicEmail: 'E-mail public',
publicPhone: 'Téléphone public',
city: 'Ville',
country: 'Pays',
websiteUrl: 'URL du site',
contractLanguage: 'Langue du contrat',
whatsapp: 'Numéro WhatsApp',
brandColor: 'Couleur de marque',
listedMarketplace: 'Publié sur la marketplace',
logoUpload: 'Logo',
heroUpload: 'Image principale',
uploading: 'Téléversement…',
logoUploaded: 'Logo téléversé',
noLogo: 'Aucun logo téléversé',
heroUploaded: 'Image principale téléversée',
noHero: 'Aucune image principale téléversée',
subdomain: 'Sous-domaine',
customDomainLabel: 'Domaine personnalisé',
domainPlaceholder: 'voitures.exemple.com',
status: 'Statut',
verified: 'Vérifié',
pendingDns: 'Vérification DNS en attente',
notConfigured: 'Non configuré',
fuelPolicyType: 'Type de politique carburant',
additionalDriverCharge: 'Supplément conducteur additionnel',
dailyDriverRate: 'Tarif conducteur/jour',
flatDriverRate: 'Tarif conducteur fixe',
fuelPolicyNote: 'Note sur la politique carburant',
damagePolicy: 'Politique dommages',
active: 'Actif',
inactive: 'Inactif',
noInsurance: 'Aucune police dassurance configurée.',
policyName: 'Nom de la police',
chargeValue: 'Valeur de facturation',
required: 'Obligatoire',
addPolicy: 'Ajouter la police',
noRules: 'Aucune règle tarifaire configurée.',
ruleName: 'Nom de la règle',
addRule: 'Ajouter la règle',
reportingPeriod: 'Période de reporting',
reportFormat: 'Format de rapport',
accountantName: 'Nom du comptable',
accountantEmail: 'E-mail du comptable',
autoSend: 'Envoyer automatiquement les rapports au comptable',
amanpayMerchantId: 'ID marchand AmanPay',
amanpaySecretKey: 'Clé secrète AmanPay',
paypalEmail: 'Email business PayPal',
noPaymentConfigured: 'Si aucun moyen de paiement nest configuré, les clients peuvent envoyer une demande puis payer à la prise du véhicule.',
fuelPolicyLabels: { FULL_TO_FULL: 'Plein à plein', FULL_TO_EMPTY: 'Plein à vide', SAME_TO_SAME: 'Même niveau', PREPAID: 'Prépayé', FREE: 'Inclus' } as Record<string, string>,
driverChargeLabels: { FREE: 'Gratuit', PER_DAY: 'Par jour', FLAT: 'Forfait' } as Record<string, string>,
reportingPeriodLabels: { WEEKLY: 'Hebdomadaire', MONTHLY: 'Mensuel', QUARTERLY: 'Trimestriel', ANNUAL: 'Annuel' } as Record<string, string>,
reportFormatLabels: { CSV: 'CSV', PDF: 'PDF', BOTH: 'Les deux' } as Record<string, string>,
},
ar: {
title: 'الإعدادات المتقدمة',
subtitle: 'إدارة التأمين وقواعد السائق الإضافي وتعديلات التسعير وإعدادات التقارير.',
save: 'جارٍ الحفظ…',
saveBrand: 'حفظ العلامة',
brandProfile: 'العلامة والملف العام',
brandProfileDesc: 'تحكم في ظهور شركتك في السوق وموقع الحجز العام.',
payments: 'طرق دفع الإيجار',
paymentsDesc: 'تهيئة طريقة دفع العملاء لشركتك في موقع الحجز.',
savePayments: 'حفظ الدفع',
customDomain: 'نطاق مخصص',
customDomainDesc: 'ربط نطاقك الخاص بموقع الحجز.',
saveDomain: 'حفظ النطاق',
removeDomain: 'إزالة النطاق المخصص',
contractPolicies: 'العقد وسياسات السائق',
savePolicies: 'حفظ السياسات',
insurancePolicies: 'سياسات التأمين',
pricingRules: 'قواعد التسعير',
accounting: 'المحاسبة والتصدير',
saveAccounting: 'حفظ المحاسبة',
displayName: 'اسم العرض',
tagline: 'الشعار',
publicEmail: 'البريد الإلكتروني العام',
publicPhone: 'الهاتف العام',
city: 'المدينة',
country: 'الدولة',
websiteUrl: 'رابط الموقع',
contractLanguage: 'لغة العقد',
whatsapp: 'رقم واتساب',
brandColor: 'لون العلامة',
listedMarketplace: 'مدرج في السوق',
logoUpload: 'رفع الشعار',
heroUpload: 'رفع صورة الواجهة',
uploading: 'جارٍ الرفع…',
logoUploaded: 'تم رفع الشعار',
noLogo: 'لم يتم رفع شعار بعد',
heroUploaded: 'تم رفع صورة الواجهة',
noHero: 'لم يتم رفع صورة واجهة بعد',
subdomain: 'النطاق الفرعي',
customDomainLabel: 'النطاق المخصص',
domainPlaceholder: 'cars.example.com',
status: 'الحالة',
verified: 'موثق',
pendingDns: 'بانتظار التحقق من DNS',
notConfigured: 'غير مُعدّ',
fuelPolicyType: 'نوع سياسة الوقود',
additionalDriverCharge: 'رسوم السائق الإضافي',
dailyDriverRate: 'تعرفة السائق اليومية',
flatDriverRate: 'تعرفة السائق الثابتة',
fuelPolicyNote: 'ملاحظة سياسة الوقود',
damagePolicy: 'سياسة الأضرار',
active: 'نشط',
inactive: 'غير نشط',
noInsurance: 'لا توجد سياسات تأمين مهيأة.',
policyName: 'اسم السياسة',
chargeValue: 'قيمة الرسوم',
required: 'إلزامي',
addPolicy: 'إضافة سياسة',
noRules: 'لا توجد قواعد تسعير مهيأة.',
ruleName: 'اسم القاعدة',
addRule: 'إضافة قاعدة',
reportingPeriod: 'فترة التقرير',
reportFormat: 'تنسيق التقرير',
accountantName: 'اسم المحاسب',
accountantEmail: 'بريد المحاسب',
autoSend: 'إرسال التقارير تلقائياً إلى المحاسب',
amanpayMerchantId: 'معرّف التاجر AmanPay',
amanpaySecretKey: 'المفتاح السري AmanPay',
paypalEmail: 'بريد أعمال PayPal',
noPaymentConfigured: 'إذا لم يتم إعداد وسيلة دفع، لا يزال بإمكان العملاء إرسال طلب حجز والدفع عند الاستلام.',
fuelPolicyLabels: { FULL_TO_FULL: 'ممتلئ إلى ممتلئ', FULL_TO_EMPTY: 'ممتلئ إلى فارغ', SAME_TO_SAME: 'نفس المستوى', PREPAID: 'مدفوع مسبقاً', FREE: 'مشمول' } as Record<string, string>,
driverChargeLabels: { FREE: 'مجاني', PER_DAY: 'يومي', FLAT: 'ثابت' } as Record<string, string>,
reportingPeriodLabels: { WEEKLY: 'أسبوعي', MONTHLY: 'شهري', QUARTERLY: 'ربع سنوي', ANNUAL: 'سنوي' } as Record<string, string>,
reportFormatLabels: { CSV: 'CSV', PDF: 'PDF', BOTH: 'كلاهما' } as Record<string, string>,
},
}[language]
const [brand, setBrand] = useState<BrandSettings | null>(null)
const [contractSettings, setContractSettings] = useState<ContractSettings | null>(null)
const [insurancePolicies, setInsurancePolicies] = useState<InsurancePolicy[]>([])
const [pricingRules, setPricingRules] = useState<PricingRule[]>([])
const [accountingSettings, setAccountingSettings] = useState<AccountingSettings | null>(null)
const [newInsurance, setNewInsurance] = useState(emptyInsurance)
const [newRule, setNewRule] = useState(emptyRule)
const [customDomain, setCustomDomain] = useState('')
const [uploadingAsset, setUploadingAsset] = useState<'logo' | 'hero' | null>(null)
const [error, setError] = useState<string | null>(null)
const [saving, setSaving] = useState(false)
async function load() {
try {
const [brandData, contractData, insuranceData, ruleData, accountingData] = await Promise.all([
apiFetch<BrandSettings | null>('/companies/me/brand'),
apiFetch<ContractSettings | null>('/companies/me/contract-settings'),
apiFetch<InsurancePolicy[]>('/companies/me/insurance-policies'),
apiFetch<PricingRule[]>('/companies/me/pricing-rules'),
apiFetch<AccountingSettings | null>('/companies/me/accounting-settings'),
])
setBrand(brandData)
setCustomDomain(brandData?.customDomain ?? '')
setContractSettings(contractData ?? {
fuelPolicyType: 'FULL_TO_FULL',
fuelPolicyNote: '',
additionalDriverCharge: 'FREE',
additionalDriverDailyRate: 0,
additionalDriverFlatRate: 0,
damagePolicy: '',
})
setInsurancePolicies(insuranceData)
setPricingRules(ruleData)
setAccountingSettings(accountingData ?? {
reportingPeriod: 'MONTHLY',
accountantEmail: '',
accountantName: '',
autoSendReport: false,
reportFormat: 'CSV',
})
setError(null)
} catch (err: any) {
setError(err.message ?? 'Failed to load settings')
}
}
async function saveBrandSettings() {
if (!brand) return
setSaving(true)
setError(null)
try {
const updated = await apiFetch<BrandSettings>('/companies/me/brand', {
method: 'PATCH',
body: JSON.stringify({
displayName: brand.displayName,
tagline: brand.tagline || undefined,
primaryColor: brand.primaryColor,
accentColor: brand.accentColor || undefined,
publicEmail: brand.publicEmail || undefined,
publicPhone: brand.publicPhone || undefined,
publicAddress: brand.publicAddress || undefined,
publicCity: brand.publicCity || undefined,
publicCountry: brand.publicCountry || undefined,
websiteUrl: brand.websiteUrl || undefined,
defaultLocale: brand.defaultLocale || undefined,
whatsappNumber: brand.whatsappNumber || undefined,
paypalEmail: brand.paypalEmail || undefined,
amanpayMerchantId: brand.amanpayMerchantId || undefined,
amanpaySecretKey: brand.amanpaySecretKey || undefined,
paypalMerchantId: brand.paypalMerchantId || undefined,
defaultCurrency: brand.defaultCurrency || undefined,
isListedOnMarketplace: brand.isListedOnMarketplace,
}),
})
setBrand(updated)
} catch (err: any) {
setError(err.message ?? 'Failed to save brand settings')
} finally {
setSaving(false)
}
}
async function uploadBrandAsset(kind: 'logo' | 'hero', file: File | null) {
if (!file) return
setUploadingAsset(kind)
setError(null)
try {
const formData = new FormData()
formData.append('file', file)
const updated = await apiFetch<BrandSettings>(`/companies/me/brand/${kind}`, {
method: 'POST',
body: formData,
})
setBrand(updated)
} catch (err: any) {
setError(err.message ?? `Failed to upload ${kind}`)
} finally {
setUploadingAsset(null)
}
}
async function saveCustomDomain() {
setSaving(true)
setError(null)
try {
await apiFetch('/companies/me/brand/custom-domain', {
method: 'POST',
body: JSON.stringify({ customDomain }),
})
await load()
} catch (err: any) {
setError(err.message ?? 'Failed to save custom domain')
} finally {
setSaving(false)
}
}
async function removeCustomDomain() {
setSaving(true)
setError(null)
try {
await apiFetch('/companies/me/brand/custom-domain', {
method: 'DELETE',
})
setCustomDomain('')
await load()
} catch (err: any) {
setError(err.message ?? 'Failed to remove custom domain')
} finally {
setSaving(false)
}
}
useEffect(() => {
load()
}, [])
async function saveContractSettings() {
if (!contractSettings) return
setSaving(true)
setError(null)
try {
await apiFetch('/companies/me/contract-settings', {
method: 'PATCH',
body: JSON.stringify(contractSettings),
})
} catch (err: any) {
setError(err.message ?? 'Failed to save contract settings')
} finally {
setSaving(false)
}
}
async function saveAccountingSettings() {
if (!accountingSettings) return
setSaving(true)
setError(null)
try {
await apiFetch('/companies/me/accounting-settings', {
method: 'PATCH',
body: JSON.stringify(accountingSettings),
})
} catch (err: any) {
setError(err.message ?? 'Failed to save accounting settings')
} finally {
setSaving(false)
}
}
async function createInsurance() {
setSaving(true)
setError(null)
try {
await apiFetch('/companies/me/insurance-policies', {
method: 'POST',
body: JSON.stringify(newInsurance),
})
setNewInsurance(emptyInsurance)
await load()
} catch (err: any) {
setError(err.message ?? 'Failed to create insurance policy')
} finally {
setSaving(false)
}
}
async function toggleInsurance(policy: InsurancePolicy) {
try {
await apiFetch(`/companies/me/insurance-policies/${policy.id}`, {
method: 'PATCH',
body: JSON.stringify({ isActive: !policy.isActive }),
})
await load()
} catch (err: any) {
setError(err.message ?? 'Failed to update insurance policy')
}
}
async function createRule() {
setSaving(true)
setError(null)
try {
await apiFetch('/companies/me/pricing-rules', {
method: 'POST',
body: JSON.stringify(newRule),
})
setNewRule(emptyRule)
await load()
} catch (err: any) {
setError(err.message ?? 'Failed to create pricing rule')
} finally {
setSaving(false)
}
}
async function toggleRule(rule: PricingRule) {
try {
await apiFetch(`/companies/me/pricing-rules/${rule.id}`, {
method: 'PATCH',
body: JSON.stringify({ isActive: !rule.isActive }),
})
await load()
} catch (err: any) {
setError(err.message ?? 'Failed to update pricing rule')
}
}
return (
<div className="space-y-6">
<div className="rdg-page-heading">
<h2 className="text-xl font-semibold text-slate-900">{copy.title}</h2>
<p className="mt-1 text-sm text-slate-500">{copy.subtitle}</p>
</div>
{error && <div className="card p-6 text-sm text-red-600">{error}</div>}
{brand && (
<div className="grid gap-6">
<div className="card p-6">
<div className="flex items-center justify-between gap-4">
<div>
<h3 className="text-base font-semibold text-slate-900">{copy.brandProfile}</h3>
<p className="mt-1 text-sm text-slate-500">{copy.brandProfileDesc}</p>
</div>
<button onClick={saveBrandSettings} disabled={saving} className="btn-primary">
{saving ? copy.save : copy.saveBrand}
</button>
</div>
<div className="mt-5 grid gap-4 lg:grid-cols-2">
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.displayName}</label>
<input className="input-field" value={brand.displayName} onChange={(event) => setBrand((current) => current ? { ...current, displayName: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.tagline}</label>
<input className="input-field" value={brand.tagline ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, tagline: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.publicEmail}</label>
<input className="input-field" type="email" value={brand.publicEmail ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, publicEmail: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.publicPhone}</label>
<input className="input-field" value={brand.publicPhone ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, publicPhone: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.city}</label>
<input className="input-field" value={brand.publicCity ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, publicCity: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.country}</label>
<input className="input-field" value={brand.publicCountry ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, publicCountry: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.websiteUrl}</label>
<input className="input-field" value={brand.websiteUrl ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, websiteUrl: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.contractLanguage}</label>
<select className="input-field" value={brand.defaultLocale ?? 'en'} onChange={(event) => setBrand((current) => current ? { ...current, defaultLocale: event.target.value } : current)}>
<option value="en">EN</option>
<option value="fr">FR</option>
<option value="ar">AR</option>
</select>
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.whatsapp}</label>
<input className="input-field" value={brand.whatsappNumber ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, whatsappNumber: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.brandColor}</label>
<input className="input-field" value={brand.primaryColor} onChange={(event) => setBrand((current) => current ? { ...current, primaryColor: event.target.value } : current)} />
</div>
<div className="flex items-center gap-4">
<label className="flex items-center gap-2 text-sm font-medium text-slate-700">
<input type="checkbox" checked={brand.isListedOnMarketplace} onChange={(event) => setBrand((current) => current ? { ...current, isListedOnMarketplace: event.target.checked } : current)} />
{copy.listedMarketplace}
</label>
</div>
</div>
<div className="mt-5 grid gap-4 lg:grid-cols-2">
<label className="block">
<span className="mb-1.5 block text-sm font-medium text-slate-700">{copy.logoUpload}</span>
<input type="file" accept="image/*" onChange={(event) => uploadBrandAsset('logo', event.target.files?.[0] ?? null)} className="input-field" />
<p className="mt-2 text-xs text-slate-500">{uploadingAsset === 'logo' ? copy.uploading : brand.logoUrl ? copy.logoUploaded : copy.noLogo}</p>
</label>
<label className="block">
<span className="mb-1.5 block text-sm font-medium text-slate-700">{copy.heroUpload}</span>
<input type="file" accept="image/*" onChange={(event) => uploadBrandAsset('hero', event.target.files?.[0] ?? null)} className="input-field" />
<p className="mt-2 text-xs text-slate-500">{uploadingAsset === 'hero' ? copy.uploading : brand.heroImageUrl ? copy.heroUploaded : copy.noHero}</p>
</label>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-2">
<div className="card p-6">
<div className="flex items-center justify-between gap-4">
<div>
<h3 className="text-base font-semibold text-slate-900">{copy.payments}</h3>
<p className="mt-1 text-sm text-slate-500">{copy.paymentsDesc}</p>
</div>
<button onClick={saveBrandSettings} disabled={saving} className="btn-primary">
{saving ? copy.save : copy.savePayments}
</button>
</div>
<div className="mt-5 grid gap-4">
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.amanpayMerchantId}</label>
<input className="input-field" value={brand.amanpayMerchantId ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, amanpayMerchantId: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.amanpaySecretKey}</label>
<input className="input-field" type="password" value={brand.amanpaySecretKey ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, amanpaySecretKey: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.paypalEmail}</label>
<input className="input-field" type="email" value={brand.paypalEmail ?? ''} onChange={(event) => setBrand((current) => current ? { ...current, paypalEmail: event.target.value } : current)} />
</div>
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600">
{copy.noPaymentConfigured}
</div>
</div>
</div>
<div className="card p-6">
<div className="flex items-center justify-between gap-4">
<div>
<h3 className="text-base font-semibold text-slate-900">{copy.customDomain}</h3>
<p className="mt-1 text-sm text-slate-500">{copy.customDomainDesc}</p>
</div>
<button onClick={saveCustomDomain} disabled={saving || !customDomain.trim()} className="btn-primary">
{saving ? copy.save : copy.saveDomain}
</button>
</div>
<div className="mt-5 space-y-4">
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.subdomain}</label>
<div className="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-700">{brand.subdomain}.RentalDriveGo.com</div>
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.customDomainLabel}</label>
<input className="input-field" placeholder={copy.domainPlaceholder} value={customDomain} onChange={(event) => setCustomDomain(event.target.value)} />
</div>
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600">
{copy.status}: {brand.customDomain ? (brand.customDomainVerified ? copy.verified : copy.pendingDns) : copy.notConfigured}
</div>
{brand.customDomain ? (
<button onClick={removeCustomDomain} disabled={saving} className="btn-secondary">
{copy.removeDomain}
</button>
) : null}
</div>
</div>
</div>
</div>
)}
{contractSettings && (
<div className="card p-6">
<div className="flex items-center justify-between">
<h3 className="text-base font-semibold text-slate-900">{copy.contractPolicies}</h3>
<button onClick={saveContractSettings} disabled={saving} className="btn-primary">
{saving ? copy.save : copy.savePolicies}
</button>
</div>
<div className="mt-5 grid gap-4 lg:grid-cols-2">
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.fuelPolicyType}</label>
<select className="input-field" value={contractSettings.fuelPolicyType} onChange={(event) => setContractSettings((current) => current ? { ...current, fuelPolicyType: event.target.value } : current)}>
{['FULL_TO_FULL', 'FULL_TO_EMPTY', 'SAME_TO_SAME', 'PREPAID', 'FREE'].map((option) => <option key={option} value={option}>{copy.fuelPolicyLabels[option] ?? option}</option>)}
</select>
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.additionalDriverCharge}</label>
<select className="input-field" value={contractSettings.additionalDriverCharge} onChange={(event) => setContractSettings((current) => current ? { ...current, additionalDriverCharge: event.target.value as ContractSettings['additionalDriverCharge'] } : current)}>
{['FREE', 'PER_DAY', 'FLAT'].map((option) => <option key={option} value={option}>{copy.driverChargeLabels[option] ?? option}</option>)}
</select>
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.dailyDriverRate}</label>
<input type="number" className="input-field" value={contractSettings.additionalDriverDailyRate} onChange={(event) => setContractSettings((current) => current ? { ...current, additionalDriverDailyRate: Number(event.target.value) } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.flatDriverRate}</label>
<input type="number" className="input-field" value={contractSettings.additionalDriverFlatRate} onChange={(event) => setContractSettings((current) => current ? { ...current, additionalDriverFlatRate: Number(event.target.value) } : current)} />
</div>
<div className="lg:col-span-2">
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.fuelPolicyNote}</label>
<textarea className="input-field min-h-24" value={contractSettings.fuelPolicyNote ?? ''} onChange={(event) => setContractSettings((current) => current ? { ...current, fuelPolicyNote: event.target.value } : current)} />
</div>
<div className="lg:col-span-2">
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.damagePolicy}</label>
<textarea className="input-field min-h-24" value={contractSettings.damagePolicy ?? ''} onChange={(event) => setContractSettings((current) => current ? { ...current, damagePolicy: event.target.value } : current)} />
</div>
</div>
</div>
)}
<div className="grid gap-6 xl:grid-cols-2">
<div className="card p-6">
<h3 className="text-base font-semibold text-slate-900">{copy.insurancePolicies}</h3>
<div className="mt-5 space-y-3">
{insurancePolicies.map((policy) => (
<div key={policy.id} className="flex items-center justify-between rounded-2xl border border-slate-200 px-4 py-3 text-sm">
<div>
<p className="font-medium text-slate-900">{policy.name}</p>
<p className="text-slate-500">{policy.type} · {policy.chargeType} · {policy.chargeValue}</p>
</div>
<button onClick={() => toggleInsurance(policy)} className={policy.isActive ? 'badge-green' : 'badge-gray'}>
{policy.isActive ? copy.active : copy.inactive}
</button>
</div>
))}
{insurancePolicies.length === 0 && <div className="text-sm text-slate-400">{copy.noInsurance}</div>}
</div>
<div className="mt-5 grid gap-3 sm:grid-cols-2">
<input className="input-field" placeholder={copy.policyName} value={newInsurance.name} onChange={(event) => setNewInsurance((current) => ({ ...current, name: event.target.value }))} />
<select className="input-field" value={newInsurance.type} onChange={(event) => setNewInsurance((current) => ({ ...current, type: event.target.value }))}>
{['BASIC', 'FULL', 'CDW', 'SCDW', 'THEFT', 'THIRD_PARTY', 'ROADSIDE', 'PERSONAL', 'CUSTOM'].map((option) => <option key={option} value={option}>{option}</option>)}
</select>
<select className="input-field" value={newInsurance.chargeType} onChange={(event) => setNewInsurance((current) => ({ ...current, chargeType: event.target.value }))}>
{['PER_DAY', 'PER_RENTAL', 'PERCENTAGE_OF_RENTAL'].map((option) => <option key={option} value={option}>{option}</option>)}
</select>
<input type="number" className="input-field" placeholder={copy.chargeValue} value={newInsurance.chargeValue} onChange={(event) => setNewInsurance((current) => ({ ...current, chargeValue: Number(event.target.value) }))} />
</div>
<div className="mt-3 flex items-center gap-4 text-sm">
<label className="flex items-center gap-2"><input type="checkbox" checked={newInsurance.isRequired} onChange={(event) => setNewInsurance((current) => ({ ...current, isRequired: event.target.checked }))} /> {copy.required}</label>
<button onClick={createInsurance} disabled={saving} className="btn-primary">{saving ? copy.save : copy.addPolicy}</button>
</div>
</div>
<div className="card p-6">
<h3 className="text-base font-semibold text-slate-900">{copy.pricingRules}</h3>
<div className="mt-5 space-y-3">
{pricingRules.map((rule) => (
<div key={rule.id} className="flex items-center justify-between rounded-2xl border border-slate-200 px-4 py-3 text-sm">
<div>
<p className="font-medium text-slate-900">{rule.name}</p>
<p className="text-slate-500">{rule.type} · {rule.condition} {rule.conditionValue} · {rule.adjustmentType} {rule.adjustmentValue}</p>
</div>
<button onClick={() => toggleRule(rule)} className={rule.isActive ? 'badge-green' : 'badge-gray'}>
{rule.isActive ? copy.active : copy.inactive}
</button>
</div>
))}
{pricingRules.length === 0 && <div className="text-sm text-slate-400">{copy.noRules}</div>}
</div>
<div className="mt-5 grid gap-3 sm:grid-cols-2">
<input className="input-field" placeholder={copy.ruleName} value={newRule.name} onChange={(event) => setNewRule((current) => ({ ...current, name: event.target.value }))} />
<select className="input-field" value={newRule.type} onChange={(event) => setNewRule((current) => ({ ...current, type: event.target.value }))}>
{['SURCHARGE', 'DISCOUNT'].map((option) => <option key={option} value={option}>{option}</option>)}
</select>
<select className="input-field" value={newRule.condition} onChange={(event) => setNewRule((current) => ({ ...current, condition: event.target.value }))}>
{['AGE_LESS_THAN', 'AGE_GREATER_THAN', 'LICENSE_YEARS_LESS_THAN', 'LICENSE_YEARS_GREATER_THAN'].map((option) => <option key={option} value={option}>{option}</option>)}
</select>
<input type="number" className="input-field" value={newRule.conditionValue} onChange={(event) => setNewRule((current) => ({ ...current, conditionValue: Number(event.target.value) }))} />
<select className="input-field" value={newRule.adjustmentType} onChange={(event) => setNewRule((current) => ({ ...current, adjustmentType: event.target.value }))}>
{['PERCENTAGE', 'FLAT_PER_DAY', 'FLAT_TOTAL'].map((option) => <option key={option} value={option}>{option}</option>)}
</select>
<input type="number" className="input-field" value={newRule.adjustmentValue} onChange={(event) => setNewRule((current) => ({ ...current, adjustmentValue: Number(event.target.value) }))} />
</div>
<div className="mt-3">
<button onClick={createRule} disabled={saving} className="btn-primary">{saving ? copy.save : copy.addRule}</button>
</div>
</div>
</div>
{accountingSettings && (
<div className="card p-6">
<div className="flex items-center justify-between">
<h3 className="text-base font-semibold text-slate-900">{copy.accounting}</h3>
<button onClick={saveAccountingSettings} disabled={saving} className="btn-primary">
{saving ? copy.save : copy.saveAccounting}
</button>
</div>
<div className="mt-5 grid gap-4 lg:grid-cols-2">
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.reportingPeriod}</label>
<select className="input-field" value={accountingSettings.reportingPeriod} onChange={(event) => setAccountingSettings((current) => current ? { ...current, reportingPeriod: event.target.value } : current)}>
{['WEEKLY', 'MONTHLY', 'QUARTERLY', 'ANNUAL'].map((option) => <option key={option} value={option}>{copy.reportingPeriodLabels[option] ?? option}</option>)}
</select>
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.reportFormat}</label>
<select className="input-field" value={accountingSettings.reportFormat} onChange={(event) => setAccountingSettings((current) => current ? { ...current, reportFormat: event.target.value } : current)}>
{['CSV', 'PDF', 'BOTH'].map((option) => <option key={option} value={option}>{copy.reportFormatLabels[option] ?? option}</option>)}
</select>
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.accountantName}</label>
<input className="input-field" value={accountingSettings.accountantName ?? ''} onChange={(event) => setAccountingSettings((current) => current ? { ...current, accountantName: event.target.value } : current)} />
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.accountantEmail}</label>
<input className="input-field" value={accountingSettings.accountantEmail ?? ''} onChange={(event) => setAccountingSettings((current) => current ? { ...current, accountantEmail: event.target.value } : current)} />
</div>
</div>
<label className="mt-4 flex items-center gap-2 text-sm font-medium text-slate-700">
<input type="checkbox" checked={accountingSettings.autoSendReport} onChange={(event) => setAccountingSettings((current) => current ? { ...current, autoSendReport: event.target.checked } : current)} />
{copy.autoSend}
</label>
</div>
)}
</div>
)
}