import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useOutletContext } from 'react-router-dom'; import { Business, User, CustomDomain } from '../types'; import { Save, Globe, Palette, BookKey, Check, Sparkles, CheckCircle, Link2, AlertCircle, ExternalLink, Copy, Crown, ShieldCheck, Trash2, RefreshCw, Star, Eye, EyeOff, Key, ShoppingCart, Building2, Users, Lock, Wallet } from 'lucide-react'; import DomainPurchase from '../components/DomainPurchase'; import { useBusinessOAuthSettings, useUpdateBusinessOAuthSettings } from '../hooks/useBusinessOAuth'; import { useCustomDomains, useAddCustomDomain, useDeleteCustomDomain, useVerifyCustomDomain, useSetPrimaryDomain } from '../hooks/useCustomDomains'; import { useBusinessOAuthCredentials, useUpdateBusinessOAuthCredentials } from '../hooks/useBusinessOAuthCredentials'; import OnboardingWizard from '../components/OnboardingWizard'; // Curated color palettes with complementary primary and secondary colors const colorPalettes = [ { name: 'Ocean Blue', description: 'Professional & trustworthy', primary: '#2563eb', secondary: '#0ea5e9', preview: 'bg-gradient-to-br from-blue-600 to-sky-500', }, { name: 'Forest Green', description: 'Natural & calming', primary: '#059669', secondary: '#10b981', preview: 'bg-gradient-to-br from-emerald-600 to-emerald-400', }, { name: 'Royal Purple', description: 'Creative & luxurious', primary: '#7c3aed', secondary: '#a78bfa', preview: 'bg-gradient-to-br from-violet-600 to-purple-400', }, { name: 'Sunset Orange', description: 'Energetic & warm', primary: '#ea580c', secondary: '#f97316', preview: 'bg-gradient-to-br from-orange-600 to-amber-500', }, { name: 'Rose Pink', description: 'Friendly & modern', primary: '#db2777', secondary: '#f472b6', preview: 'bg-gradient-to-br from-pink-600 to-pink-400', }, { name: 'Slate Gray', description: 'Minimal & sophisticated', primary: '#475569', secondary: '#64748b', preview: 'bg-gradient-to-br from-slate-600 to-slate-400', }, { name: 'Teal Wave', description: 'Fresh & balanced', primary: '#0d9488', secondary: '#14b8a6', preview: 'bg-gradient-to-br from-teal-600 to-teal-400', }, { name: 'Crimson Red', description: 'Bold & dynamic', primary: '#dc2626', secondary: '#ef4444', preview: 'bg-gradient-to-br from-red-600 to-red-400', }, ]; type SettingsTab = 'general' | 'domains' | 'authentication'; const SettingsPage: React.FC = () => { const { t } = useTranslation(); const { business, updateBusiness, user } = useOutletContext<{ business: Business; updateBusiness: (updates: Partial) => void; user: User}>(); const [formState, setFormState] = useState(business); const isOwner = user.role === 'owner'; const [showCustomColors, setShowCustomColors] = useState(false); const [showToast, setShowToast] = useState(false); const [activeTab, setActiveTab] = useState('general'); // OAuth settings const { data: oauthData, isLoading: oauthLoading } = useBusinessOAuthSettings(); const updateOAuthMutation = useUpdateBusinessOAuthSettings(); const [oauthSettings, setOAuthSettings] = useState({ enabledProviders: [] as string[], allowRegistration: false, autoLinkByEmail: true, }); // Custom Domains const { data: customDomains = [], isLoading: domainsLoading } = useCustomDomains(); const addDomainMutation = useAddCustomDomain(); const deleteDomainMutation = useDeleteCustomDomain(); const verifyDomainMutation = useVerifyCustomDomain(); const setPrimaryMutation = useSetPrimaryDomain(); const [newDomain, setNewDomain] = useState(''); const [verifyingDomainId, setVerifyingDomainId] = useState(null); const [verifyError, setVerifyError] = useState(null); // OAuth Credentials const { data: oauthCredentials, isLoading: credentialsLoading } = useBusinessOAuthCredentials(); const updateCredentialsMutation = useUpdateBusinessOAuthCredentials(); const [useCustomCredentials, setUseCustomCredentials] = useState(false); const [credentials, setCredentials] = useState({ google: { client_id: '', client_secret: '' }, apple: { client_id: '', client_secret: '', team_id: '', key_id: '' }, facebook: { client_id: '', client_secret: '' }, linkedin: { client_id: '', client_secret: '' }, microsoft: { client_id: '', client_secret: '', tenant_id: '' }, twitter: { client_id: '', client_secret: '' }, twitch: { client_id: '', client_secret: '' }, }); const [showSecrets, setShowSecrets] = useState<{ [key: string]: boolean }>({}); const [showOnboarding, setShowOnboarding] = useState(false); // Update OAuth settings when data loads useEffect(() => { if (oauthData?.businessSettings) { setOAuthSettings(oauthData.businessSettings); } }, [oauthData]); // Update OAuth credentials when data loads useEffect(() => { if (oauthCredentials) { setUseCustomCredentials(oauthCredentials.use_custom_credentials || false); if (oauthCredentials.google || oauthCredentials.apple || oauthCredentials.facebook || oauthCredentials.linkedin || oauthCredentials.microsoft || oauthCredentials.twitter || oauthCredentials.twitch) { setCredentials({ google: oauthCredentials.google || { client_id: '', client_secret: '' }, apple: oauthCredentials.apple || { client_id: '', client_secret: '', team_id: '', key_id: '' }, facebook: oauthCredentials.facebook || { client_id: '', client_secret: '' }, linkedin: oauthCredentials.linkedin || { client_id: '', client_secret: '' }, microsoft: oauthCredentials.microsoft || { client_id: '', client_secret: '', tenant_id: '' }, twitter: oauthCredentials.twitter || { client_id: '', client_secret: '' }, twitch: oauthCredentials.twitch || { client_id: '', client_secret: '' }, }); } } }, [oauthCredentials]); // Auto-hide toast after 3 seconds useEffect(() => { if (showToast) { const timer = setTimeout(() => setShowToast(false), 3000); return () => clearTimeout(timer); } }, [showToast]); const handleChange = (e: React.ChangeEvent) => { const { name, value, type, checked } = e.target; if (type === 'checkbox') { setFormState(prev => ({...prev, [name]: checked })); } else { setFormState(prev => ({ ...prev, [name]: value })); } }; const handleSave = () => { updateBusiness(formState); setShowToast(true); }; const handleOAuthSave = () => { updateOAuthMutation.mutate(oauthSettings, { onSuccess: () => { setShowToast(true); }, }); }; const toggleProvider = (provider: string) => { setOAuthSettings((prev) => { const isEnabled = prev.enabledProviders.includes(provider); return { ...prev, enabledProviders: isEnabled ? prev.enabledProviders.filter((p) => p !== provider) : [...prev.enabledProviders, provider], }; }); }; // Custom Domain handlers const handleAddDomain = () => { if (!newDomain.trim()) return; addDomainMutation.mutate(newDomain, { onSuccess: () => { setNewDomain(''); setShowToast(true); }, onError: (error: any) => { alert(error.response?.data?.error || 'Failed to add domain'); }, }); }; const handleDeleteDomain = (domainId: number) => { if (!confirm('Are you sure you want to delete this custom domain?')) return; deleteDomainMutation.mutate(domainId, { onSuccess: () => { setShowToast(true); }, }); }; const handleVerifyDomain = (domainId: number) => { setVerifyingDomainId(domainId); setVerifyError(null); verifyDomainMutation.mutate(domainId, { onSuccess: (data) => { setVerifyingDomainId(null); if (data.verified) { setShowToast(true); } else { setVerifyError(data.message); } }, onError: (error: any) => { setVerifyingDomainId(null); setVerifyError(error.response?.data?.message || 'Verification failed'); }, }); }; const handleSetPrimary = (domainId: number) => { setPrimaryMutation.mutate(domainId, { onSuccess: () => { setShowToast(true); }, onError: (error: any) => { alert(error.response?.data?.error || 'Failed to set primary domain'); }, }); }; // OAuth Credentials handlers const handleCredentialsSave = () => { const updateData: any = { use_custom_credentials: useCustomCredentials, }; if (useCustomCredentials) { Object.entries(credentials).forEach(([provider, creds]: [string, any]) => { if (creds.client_id || creds.client_secret) { updateData[provider] = creds; } }); } updateCredentialsMutation.mutate(updateData, { onSuccess: () => { setShowToast(true); }, }); }; const updateCredential = (provider: string, field: string, value: string) => { setCredentials((prev: any) => ({ ...prev, [provider]: { ...prev[provider], [field]: value, }, })); }; const toggleShowSecret = (key: string) => { setShowSecrets((prev) => ({ ...prev, [key]: !prev[key] })); }; // Provider display names and icons const providerInfo: Record = { google: { name: 'Google', icon: '🔍' }, apple: { name: 'Apple', icon: '🍎' }, facebook: { name: 'Facebook', icon: '📘' }, linkedin: { name: 'LinkedIn', icon: '💼' }, microsoft: { name: 'Microsoft', icon: '🪟' }, twitter: { name: 'X (Twitter)', icon: '🐦' }, twitch: { name: 'Twitch', icon: '🎮' }, }; // Tab configuration const tabs = [ { id: 'general' as const, label: 'General', icon: Building2 }, { id: 'domains' as const, label: 'Domains', icon: Globe }, { id: 'authentication' as const, label: 'Authentication', icon: Lock }, ]; return (
{/* Header */}

{t('settings.businessSettings')}

{t('settings.businessSettingsDescription')}

{/* Tab Navigation */}
{/* Tab Content */}
{/* GENERAL TAB */} {activeTab === 'general' && ( <> {/* Business Identity */} {isOwner && (

Business Identity

.smoothschedule.com
)} {/* Branding */}

{t('settings.branding')}

{/* Color Palette Selection */}

Recommended Palettes

Choose a professionally designed color scheme

{colorPalettes.map((palette) => { const isSelected = formState.primaryColor.toLowerCase() === palette.primary.toLowerCase() && formState.secondaryColor.toLowerCase() === palette.secondary.toLowerCase(); return ( ); })}
{/* Custom Colors Toggle */}
{showCustomColors && (
)}
{/* Live Preview */}

Live Preview

{formState.name.charAt(0)}
{formState.name}
{/* Payments */} {isOwner && (

{t('settings.payments')}

{t('settings.acceptPayments')}

{t('settings.acceptPaymentsDescription')}

{formState.paymentsEnabled && !business.stripeConnectAccountId && (

{t('settings.stripeSetupRequired')}

{t('settings.stripeSetupDescription')}

)}
)} {/* Booking Policy */}

{t('settings.bookingPolicy')}

Require Payment to Book

Customers must have a card on file to book.

)} {/* DOMAINS TAB */} {activeTab === 'domains' && ( <> {/* Quick Domain Setup */} {isOwner && (

Your Booking URL

{formState.subdomain}.smoothschedule.com
)} {/* Custom Domains Management */} {isOwner && business.plan !== 'Free' && (

Custom Domains

Use your own domains for your booking pages

{/* Add New Domain Form */}
setNewDomain(e.target.value)} placeholder="booking.yourdomain.com" className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white rounded-lg focus:ring-2 focus:ring-brand-500 font-mono text-sm" onKeyPress={(e) => { if (e.key === 'Enter') { handleAddDomain(); } }} />
{/* Custom Domains List */} {domainsLoading ? (
Loading domains...
) : customDomains.length === 0 ? (

No custom domains yet. Add one above.

) : (
{customDomains.map((domain) => (

{domain.domain}

{domain.is_primary && ( Primary )} {domain.is_verified ? ( Verified ) : ( Pending )}
{!domain.is_verified && (

Add DNS TXT record:

Name: {domain.dns_txt_record_name}
Value: {domain.dns_txt_record}
{verifyError && verifyingDomainId === domain.id && (

{verifyError}

)}
)}
{!domain.is_verified && ( )} {domain.is_verified && !domain.is_primary && ( )}
))}
)}
)} {/* Domain Purchase */} {isOwner && business.plan !== 'Free' && (

Purchase a Domain

Search and register a new domain name

)} {/* Upgrade prompt for free plans */} {isOwner && business.plan === 'Free' && (

Unlock Custom Domains

Upgrade to use your own domain (e.g., book.yourbusiness.com) or purchase a new one.

)} )} {/* AUTHENTICATION TAB */} {activeTab === 'authentication' && isOwner && ( <> {/* OAuth & Social Login */}

Social Login

Choose which providers customers can use to sign in

{oauthLoading ? (
) : oauthData?.availableProviders && oauthData.availableProviders.length > 0 ? (
{oauthData.availableProviders.map((provider) => { const isEnabled = oauthSettings.enabledProviders.includes(provider); const info = providerInfo[provider] || { name: provider, icon: '🔐' }; return ( ); })}

Allow OAuth Registration

New customers can create accounts via OAuth

Auto-link by Email

Link OAuth accounts to existing accounts by email

) : (

No OAuth Providers Available

Contact your platform administrator to enable OAuth providers.

)}
{/* Custom OAuth Credentials */}

Custom OAuth Credentials {business.plan === 'Free' && ( Pro )}

Use your own OAuth app credentials for complete branding control

{business.plan !== 'Free' && ( )}
{business.plan === 'Free' ? (

Upgrade to use your own OAuth credentials for custom branding and higher rate limits.

) : credentialsLoading ? (
) : (
{/* Toggle Custom Credentials */}

Use Custom Credentials

{useCustomCredentials ? 'Using your custom OAuth credentials' : 'Using platform shared credentials'}

{useCustomCredentials && (
{/* Provider credentials - collapsible */} {(['google', 'apple', 'facebook', 'linkedin', 'microsoft', 'twitter', 'twitch'] as const).map((provider) => { const info = providerInfo[provider]; const providerCreds = credentials[provider]; const hasCredentials = providerCreds.client_id || providerCreds.client_secret; return (
{info.icon} {info.name} {hasCredentials && ( Configured )}
updateCredential(provider, 'client_id', e.target.value)} placeholder={`Enter ${info.name} Client ID`} className="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 text-sm" />
updateCredential(provider, 'client_secret', e.target.value)} placeholder={`Enter ${info.name} Client Secret`} className="w-full px-3 py-1.5 pr-8 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 text-sm" />
{provider === 'apple' && ( <>
updateCredential(provider, 'team_id', e.target.value)} placeholder="Enter Apple Team ID" className="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 text-sm" />
updateCredential(provider, 'key_id', e.target.value)} placeholder="Enter Apple Key ID" className="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 text-sm" />
)} {provider === 'microsoft' && (
updateCredential(provider, 'tenant_id', e.target.value)} placeholder="common" className="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 text-sm" />
)}
); })}
)} {!useCustomCredentials && (

Using platform credentials. Enable custom credentials above to use your own OAuth apps with your branding.

)}
)}
)}
{/* Toast Notification */}
{t('settings.savedSuccessfully')}
{/* Onboarding Wizard for Stripe Connect */} {showOnboarding && ( { setShowOnboarding(false); updateBusiness({ initialSetupComplete: true }); }} onSkip={() => { setShowOnboarding(false); // If they skip, disable payments setFormState(prev => ({ ...prev, paymentsEnabled: false })); updateBusiness({ paymentsEnabled: false }); }} /> )}
); }; export default SettingsPage;