update payment method, remove stripe, paypal, amanapay
Build & Push / Pipeline Tests (push) Failing after 59s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 48s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped

This commit is contained in:
root
2026-08-17 21:05:29 -04:00
parent c9915a8315
commit fe8ffbeb9f
109 changed files with 568 additions and 2662 deletions
@@ -8,7 +8,7 @@ import { apiFetch } from '@/lib/api'
import { useDashboardI18n } from '@/components/I18nProvider'
type BillingPaymentType = 'CHARGE' | 'DEPOSIT'
type ManualPaymentMethod = 'CASH' | 'CHECK' | 'BANK_TRANSFER' | 'CARD' | 'PAYPAL' | 'OTHER'
type ManualPaymentMethod = 'CHECK' | 'BANK_TRANSFER'
type BillingPayment = {
id: string
@@ -92,7 +92,7 @@ export default function BillingPage() {
const [error, setError] = useState<string | null>(null)
const [paymentInvoice, setPaymentInvoice] = useState<BillingInvoice | null>(null)
const [paymentType, setPaymentType] = useState<BillingPaymentType>('CHARGE')
const [paymentMethod, setPaymentMethod] = useState<ManualPaymentMethod>('CASH')
const [paymentMethod, setPaymentMethod] = useState<ManualPaymentMethod>('BANK_TRANSFER')
const [paymentAmount, setPaymentAmount] = useState('')
const [receivedAt, setReceivedAt] = useState('')
const [reference, setReference] = useState('')
@@ -153,7 +153,7 @@ export default function BillingPage() {
invalidAmount: 'Enter a valid amount within the selected remaining balance.',
paymentStatusLabels: { ALL: 'All', UNPAID: 'Unpaid', PARTIAL: 'Partial', PAID: 'Paid' } as Record<string, string>,
depositStatusLabels: { NOT_REQUIRED: 'Not required', OUTSTANDING: 'Outstanding', PARTIALLY_COLLECTED: 'Partially collected', HELD: 'Held' } as Record<string, string>,
paymentMethodLabels: { CASH: 'Cash', CHECK: 'Check', BANK_TRANSFER: 'Bank transfer', CARD: 'Card', PAYPAL: 'PayPal', OTHER: 'Other' } as Record<string, string>,
paymentMethodLabels: { CHECK: 'Check', BANK_TRANSFER: 'Bank transfer' } as Record<string, string>,
},
fr: {
heading: 'Facturation clients',
@@ -207,7 +207,7 @@ export default function BillingPage() {
invalidAmount: 'Saisissez un montant valide dans la limite du solde sélectionné.',
paymentStatusLabels: { ALL: 'Tous', UNPAID: 'Non payé', PARTIAL: 'Partiel', PAID: 'Payé' } as Record<string, string>,
depositStatusLabels: { NOT_REQUIRED: 'Non requis', OUTSTANDING: 'À collecter', PARTIALLY_COLLECTED: 'Partiellement collecté', HELD: 'Détenu' } as Record<string, string>,
paymentMethodLabels: { CASH: 'Espèces', CHECK: 'Chèque', BANK_TRANSFER: 'Virement bancaire', CARD: 'Carte', PAYPAL: 'PayPal', OTHER: 'Autre' } as Record<string, string>,
paymentMethodLabels: { CHECK: 'Chèque', BANK_TRANSFER: 'Virement bancaire' } as Record<string, string>,
},
ar: {
heading: 'فوترة العملاء',
@@ -261,7 +261,7 @@ export default function BillingPage() {
invalidAmount: 'أدخل مبلغاً صحيحاً ضمن الرصيد المحدد.',
paymentStatusLabels: { ALL: 'الكل', UNPAID: 'غير مدفوع', PARTIAL: 'جزئي', PAID: 'مدفوع' } as Record<string, string>,
depositStatusLabels: { NOT_REQUIRED: 'غير مطلوب', OUTSTANDING: 'مستحق', PARTIALLY_COLLECTED: 'محصل جزئياً', HELD: 'محتجز' } as Record<string, string>,
paymentMethodLabels: { CASH: 'نقداً', CHECK: 'شيك', BANK_TRANSFER: 'تحويل بنكي', CARD: 'بطاقة', PAYPAL: 'PayPal', OTHER: 'أخرى' } as Record<string, string>,
paymentMethodLabels: { CHECK: 'شيك', BANK_TRANSFER: 'تحويل بنكي' } as Record<string, string>,
},
}[language]), [language])
@@ -331,7 +331,7 @@ export default function BillingPage() {
const remaining = nextType === 'DEPOSIT' ? invoice.depositOutstanding : invoice.invoiceBalanceDue
setPaymentInvoice(invoice)
setPaymentType(nextType)
setPaymentMethod('CASH')
setPaymentMethod('BANK_TRANSFER')
setPaymentAmount((remaining / 100).toFixed(2))
setReceivedAt(new Date().toISOString().slice(0, 16))
setReference('')
@@ -524,7 +524,7 @@ export default function ContractDetailPage() {
damageTypeLabels: { SCRATCH: 'Scratch', DENT: 'Dent', CRACK: 'Crack', CHIP: 'Chip', MISSING: 'Missing', STAIN: 'Stain', OTHER: 'Other' },
severityLabels: { MINOR: 'Minor', MODERATE: 'Moderate', MAJOR: 'Major' },
fuelLevelLabels: { FULL: 'Full', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'Empty' },
paymentProviderLabels: { CASH: 'Cash', STRIPE: 'Stripe', BANK_TRANSFER: 'Bank transfer', CHECK: 'Check', PAYPAL: 'PayPal', CREDIT_CARD: 'Credit card', DEBIT_CARD: 'Debit card' },
paymentProviderLabels: { BANK_TRANSFER: 'Bank transfer', CHECK: 'Check' },
},
fr: {
back: 'Retour aux contrats',
@@ -656,7 +656,7 @@ export default function ContractDetailPage() {
damageTypeLabels: { SCRATCH: 'Rayure', DENT: 'Bosse', CRACK: 'Fissure', CHIP: 'Éclat', MISSING: 'Manquant', STAIN: 'Tache', OTHER: 'Autre' },
severityLabels: { MINOR: 'Léger', MODERATE: 'Modéré', MAJOR: 'Majeur' },
fuelLevelLabels: { FULL: 'Plein', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'Vide' },
paymentProviderLabels: { CASH: 'Espèces', STRIPE: 'Stripe', BANK_TRANSFER: 'Virement bancaire', CHECK: 'Chèque', PAYPAL: 'PayPal', CREDIT_CARD: 'Carte de crédit', DEBIT_CARD: 'Carte de débit' },
paymentProviderLabels: { BANK_TRANSFER: 'Virement bancaire', CHECK: 'Chèque' },
},
ar: {
back: 'العودة إلى العقود',
@@ -788,7 +788,7 @@ export default function ContractDetailPage() {
damageTypeLabels: { SCRATCH: 'خدش', DENT: 'انبعاج', CRACK: 'تشقق', CHIP: 'تقشر', MISSING: 'مفقود', STAIN: 'بقعة', OTHER: 'أخرى' },
severityLabels: { MINOR: 'طفيف', MODERATE: 'متوسط', MAJOR: 'شديد' },
fuelLevelLabels: { FULL: 'ممتلئ', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'فارغ' },
paymentProviderLabels: { CASH: 'نقداً', STRIPE: 'Stripe', BANK_TRANSFER: 'تحويل بنكي', CHECK: 'شيك', PAYPAL: 'PayPal', CREDIT_CARD: 'بطاقة ائتمان', DEBIT_CARD: 'بطاقة خصم' },
paymentProviderLabels: { BANK_TRANSFER: 'تحويل بنكي', CHECK: 'شيك' },
},
} as const
@@ -108,6 +108,13 @@ interface ReservationBilling {
type EditMode = 'booking' | 'return' | null
const RENTAL_PAYMENT_MODES = ['BANK_TRANSFER', 'CHECK'] as const
type RentalPaymentMode = (typeof RENTAL_PAYMENT_MODES)[number]
function isRentalPaymentMode(value: string): value is RentalPaymentMode {
return (RENTAL_PAYMENT_MODES as readonly string[]).includes(value)
}
type ReservationFormState = {
startDate: string
endDate: string
@@ -233,6 +240,7 @@ const detailCopy = {
returnLabel: 'Return location',
depositLabel: 'Deposit',
paymentModeLabel: 'Payment mode',
paymentModeLabels: { BANK_TRANSFER: 'Bank transfer', CHECK: 'Check' } as Record<string, string>,
spareWheelLabel: 'Spare wheel',
radioCdLabel: 'Radio and CD',
bookingNotesLabel: 'Booking notes',
@@ -348,6 +356,7 @@ const detailCopy = {
returnLabel: 'Lieu de retour',
depositLabel: 'Dépôt',
paymentModeLabel: 'Mode de paiement',
paymentModeLabels: { BANK_TRANSFER: 'Virement bancaire', CHECK: 'Chèque' } as Record<string, string>,
spareWheelLabel: 'Roue de secours',
radioCdLabel: 'Poste radio et CD',
bookingNotesLabel: 'Notes de réservation',
@@ -463,6 +472,7 @@ const detailCopy = {
returnLabel: 'موقع الإرجاع',
depositLabel: 'العربون',
paymentModeLabel: 'طريقة الدفع',
paymentModeLabels: { BANK_TRANSFER: 'تحويل بنكي', CHECK: 'شيك' } as Record<string, string>,
spareWheelLabel: 'العجلة الاحتياطية',
radioCdLabel: 'الراديو و CD',
bookingNotesLabel: 'ملاحظات الحجز',
@@ -691,7 +701,11 @@ export default function ReservationDetailPage() {
pickupLocation: form.pickupLocation || null,
returnLocation: form.returnLocation || null,
depositAmount: Number(form.depositAmount || 0),
paymentMode: form.paymentMode || null,
paymentMode: isRentalPaymentMode(form.paymentMode)
? form.paymentMode
: form.paymentMode
? undefined
: null,
spareWheel: form.spareWheel,
radioCd: form.radioCd,
contractFields: form.contractFields,
@@ -972,7 +986,7 @@ export default function ReservationDetailPage() {
<p className="font-medium text-slate-900">{reservation.vehicle.make} {reservation.vehicle.model}</p>
<p>{reservation.vehicle.licensePlate}</p>
<p>{formatDate(reservation.startDate)} - {formatDate(reservation.endDate)}</p>
<p>{copy.paymentModeLabel}: <span className="font-medium text-slate-900">{reservation.paymentMode || copy.paymentModeEmpty}</span></p>
<p>{copy.paymentModeLabel}: <span className="font-medium text-slate-900">{reservation.paymentMode ? (copy.paymentModeLabels[reservation.paymentMode] ?? reservation.paymentMode) : copy.paymentModeEmpty}</span></p>
</div>
</div>
</div>
@@ -1033,12 +1047,20 @@ export default function ReservationDetailPage() {
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{copy.paymentModeLabel}</label>
<input
<select
className="input-field disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-500"
value={form.paymentMode}
disabled={bookingInputsDisabled}
onChange={(e) => setForm((current) => current ? { ...current, paymentMode: e.target.value } : current)}
/>
>
<option value="">{copy.paymentModeEmpty}</option>
{form.paymentMode && !isRentalPaymentMode(form.paymentMode) && (
<option value={form.paymentMode}>{form.paymentMode}</option>
)}
{RENTAL_PAYMENT_MODES.map((mode) => (
<option key={mode} value={mode}>{copy.paymentModeLabels[mode]}</option>
))}
</select>
</div>
<label className="flex items-center gap-3 rounded-2xl border border-slate-200 px-4 py-3 text-sm font-medium text-slate-700">
<input
@@ -77,9 +77,6 @@ interface BrandSettings {
heroImageUrl?: string | null
websiteUrl?: string | null
whatsappNumber?: string | null
paypalEmail: string | null
amanpayMerchantId: string | null
amanpaySecretKey?: string | null
defaultLocale?: string
defaultCurrency?: string
isListedOnCarplace: boolean
@@ -196,15 +193,14 @@ function SettingsPageContent() {
lockedTitle: 'This settings area is not included in your current plan.', lockedBody: 'Your saved configuration is preserved and becomes editable again after upgrade.',
readOnly: 'This section is read-only while your subscription access is restricted.', companyHint: 'Default language affects Carplace text and generated contracts when available.',
publicProfile: 'Public profile', carplaceBasics: 'Carplace basics', premiumBranding: 'Available on GROWTH',
paymentsBody: 'Configure renter payment providers. Secret values are never shown after saving.',
paymentsBody: 'Rental payments are recorded as bank transfer or check. Online checkout is not available.',
policies: 'Fuel, driver, and damage policies', additionalDriver: 'Additional-driver automation', insuranceNew: 'New insurance policy',
pricingNew: 'New pricing rule', accountingSchedule: 'Scheduled delivery is available on PRO.', noItems: 'No records configured yet.',
carplace: 'Listed on Carplace', logo: 'Logo', hero: 'Hero image', upload: 'Upload', active: 'Active', inactive: 'Inactive',
labels: {
displayName: 'Display name', tagline: 'Tagline', publicEmail: 'Public email', publicPhone: 'Public phone', whatsapp: 'WhatsApp number',
city: 'City', country: 'Country', websiteUrl: 'Website URL', defaultLocale: 'Default language', defaultCurrency: 'Default currency',
primaryColor: 'Primary color', accentColor: 'Accent color', amanpayMerchantId: 'AmanPay merchant ID', amanpaySecretKey: 'AmanPay secret key',
paypalEmail: 'PayPal business email', fuelPolicy: 'Fuel policy', fuelPolicyType: 'Fuel policy type', fuelPolicyNote: 'Fuel policy note',
primaryColor: 'Primary color', accentColor: 'Accent color', fuelPolicy: 'Fuel policy', fuelPolicyType: 'Fuel policy type', fuelPolicyNote: 'Fuel policy note',
damagePolicy: 'Damage policy', additionalDriverPolicy: 'Driver policy', additionalDriverCharge: 'Additional driver charge',
dailyRate: 'Daily rate', flatRate: 'Flat rate', accountantName: 'Accountant name',
accountantEmail: 'Accountant email', reportingPeriod: 'Reporting period', reportFormat: 'Report format', autoSend: 'Auto-send reports',
@@ -218,15 +214,14 @@ function SettingsPageContent() {
lockedTitle: 'Cette section nest pas incluse dans votre plan actuel.', lockedBody: 'La configuration enregistrée est conservée et redevient modifiable après mise à niveau.',
readOnly: 'Cette section est en lecture seule pendant la restriction daccès.', companyHint: 'La langue par défaut affecte la vitrine et les contrats générés si disponibles.',
publicProfile: 'Profil public', carplaceBasics: 'Paramètres Carplace', premiumBranding: 'Disponible avec GROWTH',
paymentsBody: 'Configurez les prestataires de paiement. Les secrets ne sont jamais affichés après enregistrement.',
paymentsBody: 'Les paiements de location sont enregistrés par virement bancaire ou chèque. Le paiement en ligne nest pas disponible.',
policies: 'Carburant, conducteur et dommages', additionalDriver: 'Automatisation conducteur additionnel', insuranceNew: 'Nouvelle police',
pricingNew: 'Nouvelle règle', accountingSchedule: 'Lenvoi planifié est disponible avec PRO.', noItems: 'Aucun enregistrement.',
carplace: 'Publié sur Carplace', logo: 'Logo', hero: 'Image principale', upload: 'Téléverser', active: 'Actif', inactive: 'Inactif',
labels: {
displayName: 'Nom affiché', tagline: 'Slogan', publicEmail: 'E-mail public', publicPhone: 'Téléphone public', whatsapp: 'Numéro WhatsApp',
city: 'Ville', country: 'Pays', websiteUrl: 'Site web', defaultLocale: 'Langue par défaut', defaultCurrency: 'Devise par défaut',
primaryColor: 'Couleur principale', accentColor: 'Couleur secondaire', amanpayMerchantId: 'ID marchand AmanPay', amanpaySecretKey: 'Clé secrète AmanPay',
paypalEmail: 'Email PayPal business', fuelPolicy: 'Politique carburant', fuelPolicyType: 'Type de politique carburant', fuelPolicyNote: 'Note carburant',
primaryColor: 'Couleur principale', accentColor: 'Couleur secondaire', fuelPolicy: 'Politique carburant', fuelPolicyType: 'Type de politique carburant', fuelPolicyNote: 'Note carburant',
damagePolicy: 'Politique dommages', additionalDriverPolicy: 'Politique conducteur', additionalDriverCharge: 'Supplément conducteur',
dailyRate: 'Tarif journalier', flatRate: 'Tarif fixe', accountantName: 'Nom du comptable',
accountantEmail: 'E-mail du comptable', reportingPeriod: 'Période', reportFormat: 'Format', autoSend: 'Envoi automatique',
@@ -240,15 +235,14 @@ function SettingsPageContent() {
lockedTitle: 'هذا القسم غير مشمول في خطتك الحالية.', lockedBody: 'يتم الاحتفاظ بالإعدادات المحفوظة وتعود قابلة للتعديل بعد الترقية.',
readOnly: 'هذا القسم للقراءة فقط أثناء تقييد الوصول.', companyHint: 'تؤثر اللغة الافتراضية على الواجهة والعقود عند توفرها.',
publicProfile: 'الملف العام', carplaceBasics: 'أساسيات الواجهة', premiumBranding: 'متاح في GROWTH',
paymentsBody: 'إعداد مزودي دفع المستأجرين. لا يتم عرض المفاتيح السرية بعد الحفظ.',
paymentsBody: 'يتم تسجيل مدفوعات الكراء بالتحويل البنكي أو الشيك. الدفع الإلكتروني غير متاح.',
policies: 'سياسات الوقود والسائق والأضرار', additionalDriver: 'أتمتة السائق الإضافي', insuranceNew: 'سياسة تأمين جديدة',
pricingNew: 'قاعدة تسعير جديدة', accountingSchedule: 'الإرسال المجدول متاح في PRO.', noItems: 'لا توجد سجلات بعد.',
carplace: 'مدرج على Carplace', logo: 'الشعار', hero: 'صورة الواجهة', upload: 'رفع', active: 'نشط', inactive: 'غير نشط',
labels: {
displayName: 'اسم العرض', tagline: 'الشعار', publicEmail: 'البريد العام', publicPhone: 'الهاتف العام', whatsapp: 'رقم واتساب',
city: 'المدينة', country: 'الدولة', websiteUrl: 'الموقع', defaultLocale: 'اللغة الافتراضية', defaultCurrency: 'العملة الافتراضية',
primaryColor: 'اللون الأساسي', accentColor: 'لون التمييز', amanpayMerchantId: 'معرف تاجر AmanPay', amanpaySecretKey: فتاح AmanPay السري',
paypalEmail: 'بريد PayPal التجاري', fuelPolicy: 'سياسة الوقود', fuelPolicyType: 'نوع سياسة الوقود', fuelPolicyNote: 'ملاحظة الوقود',
primaryColor: 'اللون الأساسي', accentColor: 'لون التمييز', fuelPolicy: 'سياسة الوقود', fuelPolicyType: 'نوع سياسة الوقود', fuelPolicyNote: لاحظة الوقود',
damagePolicy: 'سياسة الأضرار', additionalDriverPolicy: 'سياسة السائق', additionalDriverCharge: 'رسوم السائق الإضافي',
dailyRate: 'السعر اليومي', flatRate: 'السعر الثابت', accountantName: 'اسم المحاسب',
accountantEmail: 'بريد المحاسب', reportingPeriod: 'فترة التقرير', reportFormat: 'تنسيق التقرير', autoSend: 'إرسال تلقائي',
@@ -349,9 +343,6 @@ function SettingsPageContent() {
websiteUrl: brand.websiteUrl || undefined, defaultLocale: brand.defaultLocale || undefined,
whatsappNumber: brand.whatsappNumber || undefined, defaultCurrency: brand.defaultCurrency || undefined,
isListedOnCarplace: brand.isListedOnCarplace,
amanpayMerchantId: canEdit('settings.renter_payments') ? brand.amanpayMerchantId || undefined : undefined,
amanpaySecretKey: canEdit('settings.renter_payments') ? brand.amanpaySecretKey || undefined : undefined,
paypalEmail: canEdit('settings.renter_payments') ? brand.paypalEmail || undefined : undefined,
}),
})
setBrand(updated); setMessage(copy.saved)
@@ -547,12 +538,7 @@ function SettingsPageContent() {
{activeSection === 'payments' && brand && (
<SectionCard onSave={saveBrand} saving={saving} copy={copy} disabled={!canEdit('settings.renter_payments')}>
<p className="mb-4 text-sm text-slate-500">{copy.paymentsBody}</p>
<div className="grid gap-4 lg:grid-cols-2">
<Input label={copy.labels.amanpayMerchantId} value={brand.amanpayMerchantId ?? ''} disabled={!canEdit('settings.renter_payments')} onChange={(v) => setBrand({ ...brand, amanpayMerchantId: v })} />
<Input label={copy.labels.amanpaySecretKey} type="password" value={brand.amanpaySecretKey ?? ''} disabled={!canEdit('settings.renter_payments')} onChange={(v) => setBrand({ ...brand, amanpaySecretKey: v })} />
<Input label={copy.labels.paypalEmail} type="email" value={brand.paypalEmail ?? ''} disabled={!canEdit('settings.renter_payments')} onChange={(v) => setBrand({ ...brand, paypalEmail: v })} />
</div>
<p className="text-sm text-slate-600">{copy.paymentsBody}</p>
</SectionCard>
)}
@@ -9,7 +9,7 @@ import { buildHomepageSignInPath } from '@/lib/dashboardPaths'
type Plan = 'STARTER' | 'GROWTH' | 'PRO' | 'ENTERPRISE'
type BillingPeriod = 'MONTHLY' | 'ANNUAL'
type CollectionMethod = 'STRIPE' | 'BANK_TRANSFER' | 'CHECK'
type CollectionMethod = 'BANK_TRANSFER' | 'CHECK'
interface Subscription {
id: string
@@ -592,7 +592,7 @@ export default function SubscriptionPage() {
.then(([sub, inv, options, settings]) => {
setPaymentOptions(options.methods ?? [])
setCommunicationSettings(settings)
const firstEnabled = options.methods?.find((option) => option.enabled && option.method !== 'STRIPE')
const firstEnabled = options.methods?.find((option) => option.enabled)
if (firstEnabled) setSelectedMethod(firstEnabled.method)
if (sub) {
setSubscription(sub)
@@ -601,7 +601,7 @@ export default function SubscriptionPage() {
// currency is always MAD
}
setInvoices(inv ?? [])
const latestManual = inv?.find((invoice) => invoice.collectionMethod !== 'STRIPE' && ['OPEN', 'PAYMENT_PENDING', 'PAST_DUE'].includes(invoice.status))
const latestManual = inv?.find((invoice) => ['OPEN', 'PAYMENT_PENDING', 'PAST_DUE'].includes(invoice.status))
if (latestManual) {
const option = options.methods?.find((item) => item.method === latestManual.collectionMethod)
setSelectedMethod(latestManual.collectionMethod)
@@ -975,7 +975,7 @@ export default function SubscriptionPage() {
{/* Plan selector + checkout */}
<div className="card p-6 space-y-6">
{!paymentOptions.some((option) => option.enabled && option.method !== 'STRIPE') ? (
{!paymentOptions.some((option) => option.enabled) ? (
<div className="rounded-xl border border-orange-200 bg-orange-50 px-4 py-3 text-sm text-orange-700">
{copy.noProviderConfigured}
</div>
@@ -1081,7 +1081,7 @@ export default function SubscriptionPage() {
<div>
<p className="text-sm font-medium text-slate-700 mb-2 dark:text-zinc-300">{copy.paymentProvider}</p>
<div className="flex flex-wrap gap-3">
{paymentOptions.filter((option) => option.enabled && option.method !== 'STRIPE').map((option) => (
{paymentOptions.filter((option) => option.enabled).map((option) => (
<button
type="button"
key={option.method}
@@ -1123,7 +1123,7 @@ export default function SubscriptionPage() {
disabled={
paying
|| loading
|| !paymentOptions.some((option) => option.method === selectedMethod && option.enabled && option.method !== 'STRIPE')
|| !paymentOptions.some((option) => option.method === selectedMethod && option.enabled)
|| isInvalidActiveUpgradeSelection
|| (isManualMethod && (paymentReference.trim().length < 3 || evidenceFiles.length === 0))
}
+2 -58
View File
@@ -14,8 +14,6 @@ export default function OnboardingPage() {
const [step, setStep] = useState<Step>(1)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [showSecretKey, setShowSecretKey] = useState(false)
const [company, setCompany] = useState({ name: '', slug: '' })
const [brand, setBrand] = useState({
displayName: '',
@@ -25,9 +23,6 @@ export default function OnboardingPage() {
publicCountry: '',
})
const [payments, setPayments] = useState({
amanpayMerchantId: '',
amanpaySecretKey: '',
paypalEmail: '',
isListedOnCarplace: true,
})
@@ -78,9 +73,6 @@ export default function OnboardingPage() {
await apiFetch('/companies/me/brand', {
method: 'PATCH',
body: JSON.stringify({
amanpayMerchantId: payments.amanpayMerchantId || undefined,
amanpaySecretKey: payments.amanpaySecretKey || undefined,
paypalEmail: payments.paypalEmail || undefined,
isListedOnCarplace: payments.isListedOnCarplace,
}),
})
@@ -213,56 +205,8 @@ export default function OnboardingPage() {
{step === 3 && (
<div className="space-y-5">
<div>
<h2 className="text-lg font-semibold text-slate-900">Payment setup</h2>
<p className="mt-1 text-sm text-slate-500">Connect payment providers so renters can book online. You can skip this for now.</p>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">AmanPay Merchant ID <span className="text-slate-400">(optional)</span></label>
<input
className="input-field"
placeholder="your-merchant-id"
value={payments.amanpayMerchantId}
onChange={(e) => setPayments({ ...payments, amanpayMerchantId: e.target.value })}
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">AmanPay Secret Key <span className="text-slate-400">(optional)</span></label>
<div className="relative">
<input
type={showSecretKey ? 'text' : 'password'}
className="input-field pr-10"
placeholder="your-secret-key"
value={payments.amanpaySecretKey}
onChange={(e) => setPayments({ ...payments, amanpaySecretKey: e.target.value })}
/>
<button
type="button"
onClick={() => setShowSecretKey((v) => !v)}
className="absolute inset-y-0 right-3 flex items-center text-slate-400 hover:text-slate-600"
tabIndex={-1}
>
{showSecretKey ? (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
)}
</button>
</div>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">PayPal email <span className="text-slate-400">(optional)</span></label>
<input
type="email"
className="input-field"
placeholder="payments@yourcompany.com"
value={payments.paypalEmail}
onChange={(e) => setPayments({ ...payments, paypalEmail: e.target.value })}
/>
<h2 className="text-lg font-semibold text-slate-900">Finish setup</h2>
<p className="mt-1 text-sm text-slate-500">Rental payments are recorded manually in the dashboard. Choose whether to list your company on Carplace.</p>
</div>
<label className="flex items-center gap-3 cursor-pointer">
<input
@@ -21,7 +21,7 @@ import type { BilingualField } from '@/components/ui/BilingualInput'
type Language = 'en' | 'fr' | 'ar'
const paymentModes = ['CASH', 'CARD', 'BANK_TRANSFER', 'AMANPAY', 'PAYPAL'] as const
const paymentModes = ['BANK_TRANSFER', 'CHECK'] as const
export function ReservationWizard({
customers,
@@ -84,11 +84,8 @@ export function getReservationWizardCopy(language: Language) {
imageSize: 'Image must be 5 MB or smaller.',
},
paymentModes: {
CASH: 'Cash',
CARD: 'Card',
BANK_TRANSFER: 'Bank transfer',
AMANPAY: 'AmanPay',
PAYPAL: 'PayPal',
CHECK: 'Check',
} as Record<string, string>,
},
fr: {
@@ -171,11 +168,8 @@ export function getReservationWizardCopy(language: Language) {
imageSize: 'Limage doit faire 5 Mo ou moins.',
},
paymentModes: {
CASH: 'Espèces',
CARD: 'Carte',
BANK_TRANSFER: 'Virement',
AMANPAY: 'AmanPay',
PAYPAL: 'PayPal',
CHECK: 'Chèque',
} as Record<string, string>,
},
ar: {
@@ -258,11 +252,8 @@ export function getReservationWizardCopy(language: Language) {
imageSize: 'يجب ألا تتجاوز الصورة 5 ميغابايت.',
},
paymentModes: {
CASH: 'نقدا',
CARD: 'بطاقة',
BANK_TRANSFER: 'تحويل بنكي',
AMANPAY: 'AmanPay',
PAYPAL: 'PayPal',
CHECK: 'شيك',
} as Record<string, string>,
},
}[language]
@@ -60,7 +60,7 @@ export function createInitialDraft(): ReservationDraft {
},
payment: {
depositAmount: '0',
paymentMode: 'CASH',
paymentMode: 'BANK_TRANSFER',
spareWheel: false,
radioCd: false,
notes: '',
@@ -44,7 +44,7 @@ function draft(): ReservationDraft {
},
payment: {
depositAmount: '0',
paymentMode: 'CASH',
paymentMode: 'BANK_TRANSFER',
spareWheel: false,
radioCd: false,
notes: '',
@@ -49,7 +49,7 @@ function validDraft() {
},
payment: {
depositAmount: '0',
paymentMode: 'CASH',
paymentMode: 'BANK_TRANSFER',
spareWheel: false,
radioCd: false,
notes: '',