import React, { useEffect, useState, useRef } from 'react'; import { Settings, Shield, DollarSign, CreditCard, Save, Info, Globe, SlidersHorizontal, Eye, EyeOff, Lock, Key, Cookie, Coins, Sparkles, Mail, Building2, Upload, Image as ImageIcon, MessageCircle, Clock, X, CheckCircle2 } from 'lucide-react'; import { toast } from 'react-toastify'; import adminPrivacyService, { CookieIntegrationSettings, CookiePolicySettings, CookiePolicySettingsResponse, } from '../../services/api/adminPrivacyService'; import systemSettingsService, { StripeSettingsResponse, UpdateStripeSettingsRequest, PayPalSettingsResponse, UpdatePayPalSettingsRequest, BoricaSettingsResponse, UpdateBoricaSettingsRequest, SmtpSettingsResponse, UpdateSmtpSettingsRequest, CompanySettingsResponse, UpdateCompanySettingsRequest, } from '../../services/api/systemSettingsService'; import { recaptchaService, RecaptchaSettingsAdminResponse, UpdateRecaptchaSettingsRequest } from '../../services/api/systemSettingsService'; import { useCurrency } from '../../contexts/CurrencyContext'; import { Loading } from '../../components/common'; import { getCurrencySymbol } from '../../utils/format'; type SettingsTab = 'general' | 'cookie' | 'currency' | 'payment' | 'smtp' | 'company' | 'recaptcha'; const SettingsPage: React.FC = () => { const { currency, supportedCurrencies, refreshCurrency } = useCurrency(); const [activeTab, setActiveTab] = useState('general'); const [policy, setPolicy] = useState({ analytics_enabled: true, marketing_enabled: true, preferences_enabled: true, }); const [integrations, setIntegrations] = useState({ ga_measurement_id: '', fb_pixel_id: '', }); const [policyMeta, setPolicyMeta] = useState< Pick | null >(null); const [selectedCurrency, setSelectedCurrency] = useState(currency); const [stripeSettings, setStripeSettings] = useState(null); const [formData, setFormData] = useState({ stripe_secret_key: '', stripe_publishable_key: '', stripe_webhook_secret: '', }); const [showSecretKey, setShowSecretKey] = useState(false); const [showWebhookSecret, setShowWebhookSecret] = useState(false); const [paypalSettings, setPaypalSettings] = useState(null); const [paypalFormData, setPaypalFormData] = useState({ paypal_client_id: '', paypal_client_secret: '', paypal_mode: 'sandbox', }); const [showPayPalSecret, setShowPayPalSecret] = useState(false); const [boricaSettings, setBoricaSettings] = useState(null); const [boricaFormData, setBoricaFormData] = useState({ borica_terminal_id: '', borica_merchant_id: '', borica_private_key_path: '', borica_certificate_path: '', borica_gateway_url: '', borica_mode: 'test', }); const [showBoricaTerminalId, setShowBoricaTerminalId] = useState(false); const [showBoricaMerchantId, setShowBoricaMerchantId] = useState(false); const [uploadingPrivateKey, setUploadingPrivateKey] = useState(false); const [uploadingCertificate, setUploadingCertificate] = useState(false); const privateKeyFileInputRef = useRef(null); const certificateFileInputRef = useRef(null); const [smtpSettings, setSmtpSettings] = useState(null); const [smtpFormData, setSmtpFormData] = useState({ smtp_host: '', smtp_port: '', smtp_user: '', smtp_password: '', smtp_from_email: '', smtp_from_name: '', smtp_use_tls: true, }); const [showSmtpPassword, setShowSmtpPassword] = useState(false); const [testingEmail, setTestingEmail] = useState(false); const [testEmailAddress, setTestEmailAddress] = useState('email@example.com'); const [companySettings, setCompanySettings] = useState(null); const [companyFormData, setCompanyFormData] = useState({ company_name: '', company_tagline: '', company_phone: '', company_email: '', company_address: '', tax_rate: 0, chat_working_hours_start: 9, chat_working_hours_end: 17, }); const [logoPreview, setLogoPreview] = useState(null); const [faviconPreview, setFaviconPreview] = useState(null); const [uploadingLogo, setUploadingLogo] = useState(false); const [uploadingFavicon, setUploadingFavicon] = useState(false); const [recaptchaSettings, setRecaptchaSettings] = useState(null); const [recaptchaFormData, setRecaptchaFormData] = useState({ recaptcha_site_key: '', recaptcha_secret_key: '', recaptcha_enabled: false, }); const [showRecaptchaSecret, setShowRecaptchaSecret] = useState(false); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); // Payment modal state const [openPaymentModal, setOpenPaymentModal] = useState<'stripe' | 'paypal' | 'borica' | null>(null); const currencyNames: Record = { VND: 'Vietnamese Dong', USD: 'US Dollar', EUR: 'Euro', GBP: 'British Pound', JPY: 'Japanese Yen', CNY: 'Chinese Yuan', KRW: 'South Korean Won', SGD: 'Singapore Dollar', THB: 'Thai Baht', AUD: 'Australian Dollar', CAD: 'Canadian Dollar', }; const getCurrencyDisplayName = (code: string): string => { const name = currencyNames[code] || code; const symbol = getCurrencySymbol(code); return `${name} (${symbol})`; }; useEffect(() => { loadAllSettings(); }, []); // Prevent body scrolling when payment modals are open useEffect(() => { if (openPaymentModal) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = 'unset'; } return () => { document.body.style.overflow = 'unset'; }; }, [openPaymentModal]); useEffect(() => { if (activeTab === 'smtp') { loadSmtpSettings(); } if (activeTab === 'company') { loadCompanySettings(); } if (activeTab === 'recaptcha') { loadRecaptchaSettings(); } }, [activeTab]); useEffect(() => { setSelectedCurrency(currency); }, [currency]); const loadAllSettings = async () => { try { setLoading(true); const [policyRes, integrationRes, currencyRes, stripeRes, paypalRes, boricaRes] = await Promise.all([ adminPrivacyService.getCookiePolicy(), adminPrivacyService.getIntegrations(), systemSettingsService.getPlatformCurrency(), systemSettingsService.getStripeSettings(), systemSettingsService.getPayPalSettings(), systemSettingsService.getBoricaSettings(), ]); setPolicy(policyRes.data); setPolicyMeta({ updated_at: policyRes.updated_at, updated_by: policyRes.updated_by, }); setIntegrations(integrationRes.data || {}); setSelectedCurrency(currencyRes.data.currency); setStripeSettings(stripeRes.data); setFormData({ stripe_secret_key: '', stripe_publishable_key: stripeRes.data.stripe_publishable_key || '', stripe_webhook_secret: '', }); setPaypalSettings(paypalRes.data); setPaypalFormData({ paypal_client_id: '', paypal_client_secret: '', paypal_mode: paypalRes.data.paypal_mode || 'sandbox', }); setBoricaSettings(boricaRes.data); setBoricaFormData({ borica_terminal_id: '', borica_merchant_id: '', borica_private_key_path: '', borica_certificate_path: '', borica_gateway_url: '', borica_mode: boricaRes.data.borica_mode || 'test', }); } catch (error: any) { toast.error(error.message || 'Failed to load settings'); } finally { setLoading(false); } }; const loadSmtpSettings = async () => { try { const smtpRes = await systemSettingsService.getSmtpSettings(); setSmtpSettings(smtpRes.data); setSmtpFormData({ smtp_host: smtpRes.data.smtp_host || '', smtp_port: smtpRes.data.smtp_port || '', smtp_user: smtpRes.data.smtp_user || '', smtp_password: '', smtp_from_email: smtpRes.data.smtp_from_email || '', smtp_from_name: smtpRes.data.smtp_from_name || '', smtp_use_tls: smtpRes.data.smtp_use_tls, }); } catch (error: any) { toast.error(error.message || 'Failed to load SMTP settings'); } }; const loadCompanySettings = async () => { try { const companyRes = await systemSettingsService.getCompanySettings(); setCompanySettings(companyRes.data); setCompanyFormData({ company_name: companyRes.data.company_name || '', company_tagline: companyRes.data.company_tagline || '', company_phone: companyRes.data.company_phone || '', company_email: companyRes.data.company_email || '', company_address: companyRes.data.company_address || '', tax_rate: companyRes.data.tax_rate || 0, chat_working_hours_start: companyRes.data.chat_working_hours_start || 9, chat_working_hours_end: companyRes.data.chat_working_hours_end || 17, }); if (companyRes.data.company_logo_url) { const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000'; const logoUrl = companyRes.data.company_logo_url.startsWith('http') ? companyRes.data.company_logo_url : `${baseUrl}${companyRes.data.company_logo_url}`; setLogoPreview(logoUrl); } if (companyRes.data.company_favicon_url) { const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000'; const faviconUrl = companyRes.data.company_favicon_url.startsWith('http') ? companyRes.data.company_favicon_url : `${baseUrl}${companyRes.data.company_favicon_url}`; setFaviconPreview(faviconUrl); } } catch (error: any) { toast.error(error.message || 'Failed to load company settings'); } }; const handleToggle = (key: keyof CookiePolicySettings) => { setPolicy((prev) => ({ ...prev, [key]: !prev[key], })); }; const handleSaveCookie = async () => { try { setSaving(true); const [policyRes, integrationRes] = await Promise.all([ adminPrivacyService.updateCookiePolicy(policy), adminPrivacyService.updateIntegrations(integrations), ]); setPolicy(policyRes.data); setPolicyMeta({ updated_at: policyRes.updated_at, updated_by: policyRes.updated_by, }); setIntegrations(integrationRes.data || {}); toast.success('Cookie policy and integrations updated successfully'); } catch (error: any) { toast.error(error.message || 'Failed to update cookie settings'); } finally { setSaving(false); } }; const handleSaveCurrency = async () => { try { setSaving(true); await systemSettingsService.updatePlatformCurrency(selectedCurrency); await refreshCurrency(); await loadAllSettings(); toast.success('Platform currency updated successfully'); } catch (error: any) { toast.error(error.message || 'Failed to update platform currency'); } finally { setSaving(false); } }; const handleSaveStripe = async () => { try { setSaving(true); const updateData: UpdateStripeSettingsRequest = {}; if (formData.stripe_secret_key && formData.stripe_secret_key.trim()) { updateData.stripe_secret_key = formData.stripe_secret_key.trim(); } if (formData.stripe_publishable_key && formData.stripe_publishable_key.trim()) { updateData.stripe_publishable_key = formData.stripe_publishable_key.trim(); } if (formData.stripe_webhook_secret && formData.stripe_webhook_secret.trim()) { updateData.stripe_webhook_secret = formData.stripe_webhook_secret.trim(); } await systemSettingsService.updateStripeSettings(updateData); await loadAllSettings(); setFormData({ ...formData, stripe_secret_key: '', stripe_webhook_secret: '', }); toast.success('Stripe settings updated successfully'); setOpenPaymentModal(null); } catch (error: any) { toast.error( error.response?.data?.message || error.response?.data?.detail || 'Failed to update Stripe settings' ); } finally { setSaving(false); } }; const handleSavePayPal = async () => { try { setSaving(true); const updateData: UpdatePayPalSettingsRequest = {}; if (paypalFormData.paypal_client_id && paypalFormData.paypal_client_id.trim()) { updateData.paypal_client_id = paypalFormData.paypal_client_id.trim(); } if (paypalFormData.paypal_client_secret && paypalFormData.paypal_client_secret.trim()) { updateData.paypal_client_secret = paypalFormData.paypal_client_secret.trim(); } if (paypalFormData.paypal_mode) { updateData.paypal_mode = paypalFormData.paypal_mode; } await systemSettingsService.updatePayPalSettings(updateData); await loadAllSettings(); setPaypalFormData({ ...paypalFormData, paypal_client_id: '', paypal_client_secret: '', }); toast.success('PayPal settings updated successfully'); setOpenPaymentModal(null); } catch (error: any) { toast.error( error.response?.data?.message || error.response?.data?.detail || 'Failed to update PayPal settings' ); } finally { setSaving(false); } }; const handleUploadBoricaFile = async (file: File, fileType: 'private_key' | 'certificate') => { try { if (fileType === 'private_key') { setUploadingPrivateKey(true); } else { setUploadingCertificate(true); } // Validate file extension const allowedExtensions = ['.pem', '.key', '.crt', '.cer', '.p12', '.pfx']; const fileExt = file.name.substring(file.name.lastIndexOf('.')).toLowerCase(); if (!allowedExtensions.includes(fileExt)) { throw new Error(`Invalid file type. Allowed extensions: ${allowedExtensions.join(', ')}`); } // Validate file size (1MB max) const maxSize = 1024 * 1024; // 1MB if (file.size > maxSize) { throw new Error(`File size exceeds maximum allowed size of ${maxSize / 1024}KB`); } const response = await systemSettingsService.uploadBoricaCertificate(file, fileType); // Reload settings to get updated paths await loadAllSettings(); toast.success(response.message || `${fileType === 'private_key' ? 'Private key' : 'Certificate'} uploaded successfully`); } catch (error: any) { toast.error( error.response?.data?.message || error.response?.data?.detail || error.message || `Failed to upload ${fileType === 'private_key' ? 'private key' : 'certificate'}` ); } finally { if (fileType === 'private_key') { setUploadingPrivateKey(false); } else { setUploadingCertificate(false); } } }; const handleSaveBorica = async () => { try { setSaving(true); const updateData: UpdateBoricaSettingsRequest = {}; if (boricaFormData.borica_terminal_id && boricaFormData.borica_terminal_id.trim()) { updateData.borica_terminal_id = boricaFormData.borica_terminal_id.trim(); } if (boricaFormData.borica_merchant_id && boricaFormData.borica_merchant_id.trim()) { updateData.borica_merchant_id = boricaFormData.borica_merchant_id.trim(); } if (boricaFormData.borica_private_key_path && boricaFormData.borica_private_key_path.trim()) { updateData.borica_private_key_path = boricaFormData.borica_private_key_path.trim(); } if (boricaFormData.borica_certificate_path && boricaFormData.borica_certificate_path.trim()) { updateData.borica_certificate_path = boricaFormData.borica_certificate_path.trim(); } if (boricaFormData.borica_gateway_url && boricaFormData.borica_gateway_url.trim()) { updateData.borica_gateway_url = boricaFormData.borica_gateway_url.trim(); } if (boricaFormData.borica_mode) { updateData.borica_mode = boricaFormData.borica_mode; } await systemSettingsService.updateBoricaSettings(updateData); await loadAllSettings(); setBoricaFormData({ ...boricaFormData, borica_terminal_id: '', borica_merchant_id: '', borica_private_key_path: '', borica_certificate_path: '', borica_gateway_url: '', }); toast.success('Borica settings updated successfully'); setOpenPaymentModal(null); } catch (error: any) { toast.error( error.response?.data?.message || error.response?.data?.detail || 'Failed to update Borica settings' ); } finally { setSaving(false); } }; const handleSaveSmtp = async () => { try { setSaving(true); const updateData: UpdateSmtpSettingsRequest = {}; if (smtpFormData.smtp_host && smtpFormData.smtp_host.trim()) { updateData.smtp_host = smtpFormData.smtp_host.trim(); } if (smtpFormData.smtp_port && smtpFormData.smtp_port.trim()) { updateData.smtp_port = smtpFormData.smtp_port.trim(); } if (smtpFormData.smtp_user && smtpFormData.smtp_user.trim()) { updateData.smtp_user = smtpFormData.smtp_user.trim(); } if (smtpFormData.smtp_password && smtpFormData.smtp_password.trim()) { updateData.smtp_password = smtpFormData.smtp_password.trim(); } if (smtpFormData.smtp_from_email && smtpFormData.smtp_from_email.trim()) { updateData.smtp_from_email = smtpFormData.smtp_from_email.trim(); } if (smtpFormData.smtp_from_name && smtpFormData.smtp_from_name.trim()) { updateData.smtp_from_name = smtpFormData.smtp_from_name.trim(); } if (smtpFormData.smtp_use_tls !== undefined) { updateData.smtp_use_tls = smtpFormData.smtp_use_tls; } await systemSettingsService.updateSmtpSettings(updateData); await loadSmtpSettings(); setSmtpFormData({ ...smtpFormData, smtp_password: '', }); toast.success('SMTP settings updated successfully'); } catch (error: any) { toast.error( error.response?.data?.message || error.response?.data?.detail || 'Failed to update SMTP settings' ); } finally { setSaving(false); } }; const handleTestEmail = async () => { if (!testEmailAddress || !testEmailAddress.trim()) { toast.error('Please enter an email address'); return; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(testEmailAddress.trim())) { toast.error('Please enter a valid email address'); return; } try { setTestingEmail(true); const response = await systemSettingsService.testSmtpEmail(testEmailAddress.trim()); toast.success(`Test email sent successfully to ${response.data.recipient}`); } catch (error: any) { toast.error( error.response?.data?.detail || error.response?.data?.message || 'Failed to send test email. Please check your SMTP settings.' ); } finally { setTestingEmail(false); } }; const handleSaveCompany = async () => { try { setSaving(true); await systemSettingsService.updateCompanySettings(companyFormData); await loadCompanySettings(); if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('refreshCompanySettings')); } toast.success('Company settings updated successfully'); } catch (error: any) { toast.error( error.response?.data?.message || error.response?.data?.detail || 'Failed to update company settings' ); } finally { setSaving(false); } }; const handleLogoUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (!file.type.startsWith('image/')) { toast.error('Please select an image file'); return; } if (file.size > 2 * 1024 * 1024) { toast.error('Logo size must be less than 2MB'); return; } const reader = new FileReader(); reader.onloadend = () => { setLogoPreview(reader.result as string); }; reader.readAsDataURL(file); try { setUploadingLogo(true); const response = await systemSettingsService.uploadCompanyLogo(file); if (response.status === 'success') { await loadCompanySettings(); if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('refreshCompanySettings')); } toast.success('Logo uploaded successfully'); } } catch (error: any) { toast.error( error.response?.data?.detail || error.response?.data?.message || 'Failed to upload logo' ); setLogoPreview(null); } finally { setUploadingLogo(false); e.target.value = ''; } }; const handleFaviconUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const validTypes = ['image/x-icon', 'image/vnd.microsoft.icon', 'image/png', 'image/svg+xml']; const validExtensions = ['.ico', '.png', '.svg']; const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.')); if (!validTypes.includes(file.type) && !validExtensions.includes(fileExtension)) { toast.error('Favicon must be .ico, .png, or .svg file'); return; } if (file.size > 500 * 1024) { toast.error('Favicon size must be less than 500KB'); return; } const reader = new FileReader(); reader.onloadend = () => { setFaviconPreview(reader.result as string); }; reader.readAsDataURL(file); try { setUploadingFavicon(true); const response = await systemSettingsService.uploadCompanyFavicon(file); if (response.status === 'success') { await loadCompanySettings(); if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('refreshCompanySettings')); } toast.success('Favicon uploaded successfully'); const link = document.querySelector("link[rel~='icon']") as HTMLLinkElement; if (link) { link.href = response.data.full_url; } else { const newLink = document.createElement('link'); newLink.rel = 'icon'; newLink.href = response.data.full_url; document.head.appendChild(newLink); } } } catch (error: any) { toast.error( error.response?.data?.detail || error.response?.data?.message || 'Failed to upload favicon' ); setFaviconPreview(null); } finally { setUploadingFavicon(false); e.target.value = ''; } }; const loadRecaptchaSettings = async () => { try { const recaptchaRes = await recaptchaService.getRecaptchaSettingsAdmin(); setRecaptchaSettings(recaptchaRes.data); setRecaptchaFormData({ recaptcha_site_key: recaptchaRes.data.recaptcha_site_key || '', recaptcha_secret_key: '', recaptcha_enabled: recaptchaRes.data.recaptcha_enabled || false, }); } catch (error: any) { toast.error( error.response?.data?.detail || error.response?.data?.message || 'Failed to load reCAPTCHA settings' ); } }; const handleSaveRecaptcha = async () => { try { setSaving(true); await recaptchaService.updateRecaptchaSettings(recaptchaFormData); toast.success('reCAPTCHA settings saved successfully'); await loadRecaptchaSettings(); } catch (error: any) { toast.error( error.response?.data?.detail || error.response?.data?.message || 'Failed to save reCAPTCHA settings' ); } finally { setSaving(false); } }; if (loading) { return ; } const tabs = [ { id: 'general' as SettingsTab, label: 'Overview', icon: Settings }, { id: 'cookie' as SettingsTab, label: 'Privacy & Cookies', icon: Cookie }, { id: 'currency' as SettingsTab, label: 'Currency', icon: Coins }, { id: 'payment' as SettingsTab, label: 'Payment', icon: CreditCard }, { id: 'smtp' as SettingsTab, label: 'Email Server', icon: Mail }, { id: 'company' as SettingsTab, label: 'Company Info', icon: Building2 }, { id: 'recaptcha' as SettingsTab, label: 'reCAPTCHA', icon: Shield }, ]; return (
{}
{}

Settings Dashboard

Centralized control center for all platform configurations and system settings

{}
{tabs.map((tab) => { const Icon = tab.icon; const isActive = activeTab === tab.id; return ( ); })}
{} {activeTab === 'general' && (
setActiveTab('cookie')} className="group relative bg-white/90 backdrop-blur-xl rounded-xl sm:rounded-2xl shadow-xl border border-blue-100/50 p-4 sm:p-6 md:p-8 cursor-pointer transition-all duration-300 hover:shadow-2xl hover:scale-[1.02] sm:hover:scale-105 hover:border-blue-300/60 overflow-hidden" >

Privacy & Cookies

Manage cookie preferences, analytics integrations, and privacy controls

Last updated {policyMeta?.updated_at ? new Date(policyMeta.updated_at).toLocaleDateString() : 'Never'}
setActiveTab('currency')} className="group relative bg-white/90 backdrop-blur-xl rounded-xl sm:rounded-2xl shadow-xl border border-emerald-100/50 p-4 sm:p-6 md:p-8 cursor-pointer transition-all duration-300 hover:shadow-2xl hover:scale-[1.02] sm:hover:scale-105 hover:border-emerald-300/60 overflow-hidden" >

Currency

Configure platform-wide currency settings and display preferences

Current currency {currency}
setActiveTab('payment')} className="group relative bg-white/90 backdrop-blur-xl rounded-xl sm:rounded-2xl shadow-xl border border-indigo-100/50 p-4 sm:p-6 md:p-8 cursor-pointer transition-all duration-300 hover:shadow-2xl hover:scale-[1.02] sm:hover:scale-105 hover:border-indigo-300/60 overflow-hidden" >

Payment Gateway

Manage Stripe payment processing credentials and webhook settings

Status {stripeSettings?.has_secret_key && stripeSettings?.has_publishable_key ? '✓ Configured' : 'Not configured'}
setActiveTab('smtp')} className="group relative bg-white/90 backdrop-blur-xl rounded-xl sm:rounded-2xl shadow-xl border border-teal-100/50 p-4 sm:p-6 md:p-8 cursor-pointer transition-all duration-300 hover:shadow-2xl hover:scale-[1.02] sm:hover:scale-105 hover:border-teal-300/60 overflow-hidden" >

Email Server

Configure SMTP server settings for platform-wide email delivery

Status {smtpSettings?.has_host && smtpSettings?.has_user && smtpSettings?.has_password ? '✓ Configured' : 'Not configured'}
setActiveTab('company')} className="group relative bg-white/90 backdrop-blur-xl rounded-xl sm:rounded-2xl shadow-xl border border-purple-100/50 p-4 sm:p-6 md:p-8 cursor-pointer transition-all duration-300 hover:shadow-2xl hover:scale-[1.02] sm:hover:scale-105 hover:border-purple-300/60 overflow-hidden" >

Company Info

Manage company branding, logo, favicon, and contact information

Status {companySettings?.company_name || companySettings?.company_logo_url ? '✓ Configured' : 'Not configured'}
)} {} {activeTab === 'cookie' && (
{}

Privacy & Cookie Controls

Define which cookie categories are allowed in the application. Control user consent preferences and analytics integrations.

{}

How these settings affect the guest experience

Disabling a category here prevents it from being offered to guests as part of the cookie consent flow. For example, if marketing cookies are disabled, the website should not load marketing pixels even if a guest previously opted in.

{}
{[ { key: 'analytics_enabled' as keyof CookiePolicySettings, label: 'Analytics Cookies', desc: 'Anonymous traffic and performance measurement', color: 'emerald' as const, icon: SlidersHorizontal }, { key: 'marketing_enabled' as keyof CookiePolicySettings, label: 'Marketing Cookies', desc: 'Personalised offers and remarketing campaigns', color: 'pink' as const, icon: SlidersHorizontal }, { key: 'preferences_enabled' as keyof CookiePolicySettings, label: 'Preference Cookies', desc: 'Remember guest choices like language and currency', color: 'indigo' as const, icon: SlidersHorizontal }, ].map(({ key, label, desc, color, icon: Icon }) => { const isEnabled = policy[key]; const colorClassesMap = { emerald: { bg: 'from-emerald-500 to-emerald-600', shadow: 'shadow-emerald-500/30', iconBg: 'bg-emerald-50 border-emerald-100', iconColor: 'text-emerald-600', }, pink: { bg: 'from-pink-500 to-pink-600', shadow: 'shadow-pink-500/30', iconBg: 'bg-pink-50 border-pink-100', iconColor: 'text-pink-600', }, indigo: { bg: 'from-indigo-500 to-indigo-600', shadow: 'shadow-indigo-500/30', iconBg: 'bg-indigo-50 border-indigo-100', iconColor: 'text-indigo-600', }, } as const; const colorClasses = colorClassesMap[color] || colorClassesMap.emerald; return (

{label}

{desc}

); })}
{}

Third-Party Integrations

Configure IDs for supported analytics and marketing platforms. The application will only load these when both the policy and user consent allow it.

setIntegrations((prev) => ({ ...prev, ga_measurement_id: e.target.value || undefined, })) } placeholder="G-XXXXXXXXXX" className="w-full px-4 py-3 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Example: G-ABCDE12345

setIntegrations((prev) => ({ ...prev, fb_pixel_id: e.target.value || undefined, })) } placeholder="123456789012345" className="w-full px-4 py-3 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Numeric ID from your Meta Pixel configuration

)} {} {activeTab === 'currency' && (
{}

Platform Currency Settings

Set the default currency that will be displayed across all dashboards and pages throughout the platform

{}

How platform currency works

The platform currency you select here will be used to display all prices, amounts, and financial information across the entire application. This includes customer-facing pages, admin dashboards, reports, and booking pages. All users will see prices in the selected currency.

{}

Select Platform Currency

Choose the currency that will be used throughout the platform for displaying all monetary values

Current platform currency: {currency}
)} {} {activeTab === 'payment' && (
{/* Payment Dashboard Header */}

Payment Gateway Settings

Choose a payment gateway to configure. Click on any card below to edit its settings.

{/* Payment Method Cards Dashboard */}
{/* Stripe Card */} {/* PayPal Card */} {/* Borica Card */}
)} {/* Payment Modals */} {openPaymentModal === 'stripe' && (
setOpenPaymentModal(null)} aria-hidden="true" />
{/* Header */}

Stripe Payment Settings

Configure your Stripe integration

{/* Info Box - Compact */}

How Stripe payments work

Provide your Stripe API keys from your Stripe Dashboard. Leave fields empty to keep existing values.

{/* Form Fields */}
setFormData({ ...formData, stripe_secret_key: e.target.value })} placeholder={stripeSettings?.has_secret_key ? `Current: ${stripeSettings.stripe_secret_key_masked || '****'}` : 'sk_test_... or sk_live_...'} className="w-full px-3 sm:px-4 py-2.5 sm:py-3 pr-10 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm font-mono group-hover:border-gray-300" />

Must start with sk_

setFormData({ ...formData, stripe_publishable_key: e.target.value })} placeholder="pk_test_... or pk_live_..." className="w-full px-3 sm:px-4 py-2.5 sm:py-3 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm font-mono group-hover:border-gray-300" />

Must start with pk_

setFormData({ ...formData, stripe_webhook_secret: e.target.value })} placeholder={stripeSettings?.has_webhook_secret ? `Current: ${stripeSettings.stripe_webhook_secret_masked || '****'}` : 'whsec_...'} className="w-full px-3 sm:px-4 py-2.5 sm:py-3 pr-10 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm font-mono group-hover:border-gray-300" />

Must start with whsec_

Webhook Endpoint URL

{window.location.origin}/api/payments/stripe/webhook

Configure in Stripe Webhooks Dashboard

{/* Footer */}
)} {openPaymentModal === 'paypal' && (
setOpenPaymentModal(null)} aria-hidden="true" />
{/* Header */}

PayPal Payment Settings

Configure your PayPal integration

{/* Info Box - Compact */}

How PayPal payments work

Provide your PayPal API credentials from your PayPal Developer Dashboard. Leave fields empty to keep existing values.

{/* Form Fields */}
setPaypalFormData({ ...paypalFormData, paypal_client_id: e.target.value })} placeholder={paypalSettings?.has_client_id ? `Current: ${paypalSettings.paypal_client_id || '****'}` : 'Client ID from PayPal Dashboard'} className="w-full px-3 sm:px-4 py-2.5 sm:py-3 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm group-hover:border-gray-300" />
setPaypalFormData({ ...paypalFormData, paypal_client_secret: e.target.value })} placeholder={paypalSettings?.has_client_secret ? `Current: ${paypalSettings.paypal_client_secret_masked || '****'}` : 'Client Secret from PayPal Dashboard'} className="w-full px-3 sm:px-4 py-2.5 sm:py-3 pr-10 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm font-mono group-hover:border-gray-300" />

Use sandbox mode for testing with test credentials, or live mode for production payments.

{/* Footer */}
)} {openPaymentModal === 'borica' && (
setOpenPaymentModal(null)} aria-hidden="true" />
{/* Header */}

Borica Payment Settings

Configure your Borica integration

{/* Info Box - Compact */}

How Borica payments work

Provide your Terminal ID, Merchant ID, and certificate files from your Borica merchant account. Leave fields empty to keep existing values. Certificate paths should be absolute paths.

{/* Form Fields */}
setBoricaFormData({ ...boricaFormData, borica_terminal_id: e.target.value })} placeholder={boricaSettings?.has_terminal_id ? `Current: ${boricaSettings.borica_terminal_id_masked || '****'}` : 'Terminal ID from Borica'} className="w-full px-3 sm:px-4 py-2.5 sm:py-3 pr-10 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm font-mono group-hover:border-gray-300" />
setBoricaFormData({ ...boricaFormData, borica_merchant_id: e.target.value })} placeholder={boricaSettings?.has_merchant_id ? `Current: ${boricaSettings.borica_merchant_id_masked || '****'}` : 'Merchant ID from Borica'} className="w-full px-3 sm:px-4 py-2.5 sm:py-3 pr-10 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm font-mono group-hover:border-gray-300" />
setBoricaFormData({ ...boricaFormData, borica_private_key_path: e.target.value })} placeholder={boricaSettings?.has_private_key_path ? `Current: ${boricaSettings.borica_private_key_path || 'Not set'}` : '/path/to/private_key.pem'} className="w-full px-3 sm:px-4 py-2.5 sm:py-3 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm font-mono group-hover:border-gray-300" />
{ const file = e.target.files?.[0]; if (file) { handleUploadBoricaFile(file, 'private_key'); } e.target.value = ''; }} className="hidden" disabled={uploadingPrivateKey} />

Upload file (.pem, .key, .crt, .cer, .p12, .pfx) or enter absolute path

setBoricaFormData({ ...boricaFormData, borica_certificate_path: e.target.value })} placeholder={boricaSettings?.has_certificate_path ? `Current: ${boricaSettings.borica_certificate_path || 'Not set'}` : '/path/to/certificate.pem'} className="w-full px-3 sm:px-4 py-2.5 sm:py-3 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm font-mono group-hover:border-gray-300" />
{ const file = e.target.files?.[0]; if (file) { handleUploadBoricaFile(file, 'certificate'); } e.target.value = ''; }} className="hidden" disabled={uploadingCertificate} />

Upload file (.pem, .key, .crt, .cer, .p12, .pfx) or enter absolute path

setBoricaFormData({ ...boricaFormData, borica_gateway_url: e.target.value })} placeholder={boricaSettings?.borica_gateway_url ? `Current: ${boricaSettings.borica_gateway_url}` : 'https://3dsgate-dev.borica.bg/cgi-bin/cgi_link'} className="w-full px-3 sm:px-4 py-2.5 sm:py-3 bg-white border-2 border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-xs sm:text-sm group-hover:border-gray-300" />

Test: https://3dsgate-dev.borica.bg/... | Prod: https://3dsgate.borica.bg/...

Use test mode for testing or production mode for live payments.

{/* Footer */}
)} {} {activeTab === 'smtp' && (
{}

SMTP Email Server Settings

Configure your SMTP server settings for sending emails. These settings will be used platform-wide for all outgoing emails.

{}

Test Email Configuration

Send a test email to verify that your SMTP settings are working correctly.

setTestEmailAddress(e.target.value)} placeholder="Enter email address to send test email" className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-teal-500/50 focus:border-teal-500 transition-all duration-200 text-sm" disabled={testingEmail} />

Enter the email address where you want to receive the test email

{}

How SMTP settings work

The SMTP server configured here will be used for all outgoing emails across the platform, including welcome emails, password resets, booking confirmations, and notifications. Common SMTP services include Gmail, SendGrid, Mailgun, and AWS SES.

Note: Leave fields empty to keep existing values. Only enter new values when you want to update them. The password field will be masked for security.

{}

SMTP Server Configuration

Enter your SMTP server details. These are typically provided by your email service provider.

{}
setSmtpFormData({ ...smtpFormData, smtp_host: e.target.value }) } placeholder={ smtpSettings?.has_host ? smtpSettings.smtp_host : 'smtp.gmail.com' } className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Server hostname (e.g., smtp.gmail.com, smtp.sendgrid.net)

{smtpSettings?.has_host && ( Currently configured )}
{}
setSmtpFormData({ ...smtpFormData, smtp_port: e.target.value }) } placeholder={ smtpSettings?.smtp_port ? smtpSettings.smtp_port : '587' } className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Common ports: 587 (STARTTLS), 465 (SSL), 25 (Plain)

{}
setSmtpFormData({ ...smtpFormData, smtp_user: e.target.value }) } placeholder={ smtpSettings?.has_user ? smtpSettings.smtp_user : 'your-email@gmail.com' } className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Your SMTP login email or username

{smtpSettings?.has_user && ( Currently configured )}
{}
setSmtpFormData({ ...smtpFormData, smtp_password: e.target.value }) } placeholder={ smtpSettings?.has_password ? `Current: ${smtpSettings.smtp_password_masked || '****'}` : 'Enter password' } className="w-full px-4 py-3.5 pr-12 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Your SMTP password or app password

{smtpSettings?.has_password && ( Currently configured )}
{}
setSmtpFormData({ ...smtpFormData, smtp_from_email: e.target.value }) } placeholder={ smtpSettings?.smtp_from_email ? smtpSettings.smtp_from_email : 'noreply@example.com' } className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Default sender email address (can be same as SMTP user)

{}
setSmtpFormData({ ...smtpFormData, smtp_from_name: e.target.value }) } placeholder={ smtpSettings?.smtp_from_name ? smtpSettings.smtp_from_name : 'Hotel Booking' } className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Display name for outgoing emails

{}

Enable for port 465 (SSL/TLS), disable for port 587 (STARTTLS)

{}

Common SMTP Settings

Gmail: smtp.gmail.com:587 (STARTTLS) or 465 (SSL) - Requires App Password

SendGrid: smtp.sendgrid.net:587 - Use API key as password

Mailgun: smtp.mailgun.org:587 or 465 - Use SMTP credentials from dashboard

AWS SES: email-smtp.[region].amazonaws.com:587 or 465 - Use SMTP credentials

)} {} {activeTab === 'company' && (
{}

Company Information

Manage your company branding, logo, favicon, and contact information. These will be displayed across the platform and in email notifications.

{}
{}

Company Logo

Upload your company logo. This will be displayed in the header, emails, and across the platform. (Max 2MB)

{logoPreview && (
Logo preview
)}

Favicon

Upload your favicon (site icon). This appears in browser tabs and bookmarks. (Max 500KB, .ico, .png, or .svg)

{faviconPreview && (
Favicon preview
)} {uploadingFavicon && (
Uploading favicon...
)}
{}

Company Details

Enter your company information. This will be used in email notifications, invoices, and displayed across the platform.

{}
setCompanyFormData({ ...companyFormData, company_name: e.target.value }) } placeholder="Luxury Hotel" className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Your company or hotel name as it should appear in emails and invoices

{}
setCompanyFormData({ ...companyFormData, company_tagline: e.target.value }) } placeholder="Excellence Redefined" className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

A short tagline that appears below your company name in the header and footer

{}
setCompanyFormData({ ...companyFormData, company_phone: e.target.value }) } placeholder="+1 (555) 123-4567" className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Contact phone number for customers

{}
setCompanyFormData({ ...companyFormData, company_email: e.target.value }) } placeholder="info@hotel.com" className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" />

Contact email address for customers

{}