'use client'; import { useEffect, useMemo, useState, type ReactNode } from 'react'; import { Settings, Save, Loader2, User, Shield, FileText, Building, RefreshCw, Upload, Image as ImageIcon, KeyRound, LayoutDashboard, Search, Copy, Check, Lock, } from 'lucide-react'; import { parseJwt, getAccessToken } from '@/services/api/client'; import { userService, UserResponse } from '@/services/api/userService'; import { toast } from 'sonner'; import { format } from 'date-fns'; type SettingsBlade = 'overview' | 'profile' | 'security' | 'branding'; function PropertyRow({ label, children }: { label: string; children: ReactNode }) { return (
{label}
{typeof children === 'string' ? ( {children} ) : ( children )}
); } export default function SettingsPage() { const [userRole, setUserRole] = useState(''); const [blade, setBlade] = useState('overview'); const [navSearch, setNavSearch] = useState(''); const [copiedId, setCopiedId] = useState(false); const [profile, setProfile] = useState(null); const [loadingProfile, setLoadingProfile] = useState(true); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [userName, setUserName] = useState(''); const [phone, setPhone] = useState(''); const [email, setEmail] = useState(''); const [savingProfile, setSavingProfile] = useState(false); const [currentPassword, setCurrentPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [savingPassword, setSavingPassword] = useState(false); const [invoiceLogoUrl, setInvoiceLogoUrl] = useState('https://ifixkart.com/logo.png'); const [invoiceCompanyName, setInvoiceCompanyName] = useState('iFixKart Solutions Platform'); const [invoiceGstin, setInvoiceGstin] = useState('33AAAAA0000A1Z5'); const [invoiceStoreAddress, setInvoiceStoreAddress] = useState( 'Offline Main Store Counter, Chennai | +91 9876543210' ); const [invoiceGstRate, setInvoiceGstRate] = useState('18.0'); const [uploadingLogo, setUploadingLogo] = useState(false); const isSuperAdmin = userRole === 'super admin' || userRole === 'super_admin' || userRole === 'superadmin'; const fetchBrandingSettings = async () => { if (typeof window === 'undefined') return; const savedBranding = localStorage.getItem('ifixkart_invoice_branding'); if (savedBranding) { try { const parsed = JSON.parse(savedBranding); if (parsed.logoUrl) setInvoiceLogoUrl(parsed.logoUrl); if (parsed.companyName) setInvoiceCompanyName(parsed.companyName); if (parsed.gstin) setInvoiceGstin(parsed.gstin); if (parsed.storeAddress) setInvoiceStoreAddress(parsed.storeAddress); if (parsed.gstRate !== undefined) setInvoiceGstRate(String(parsed.gstRate)); } catch { /* ignore corrupt local branding */ } } try { const token = getAccessToken(); const backendUrl = ''; const res = await fetch(`${backendUrl}/api/v1/settings/key/invoice_branding`, { headers: { Authorization: `Bearer ${token}` }, }); if (res.ok) { const data = await res.json(); const val = data.setting_value; if (val && typeof val === 'object') { if (val.logoUrl) setInvoiceLogoUrl(val.logoUrl); if (val.companyName) setInvoiceCompanyName(val.companyName); if (val.gstin) setInvoiceGstin(val.gstin); if (val.storeAddress) setInvoiceStoreAddress(val.storeAddress); if (val.gstRate !== undefined) setInvoiceGstRate(String(val.gstRate)); } } } catch { /* branding endpoint optional */ } }; const fetchUserProfile = async () => { setLoadingProfile(true); try { const token = getAccessToken(); if (!token) return; const payload = parseJwt(token); const userId = payload?.sub as string; if (!userId) return; const user = await userService.getProfile(userId); setProfile(user); setFirstName(user.first_name || ''); setLastName(user.last_name || ''); setPhone(user.phone || ''); setEmail(user.email || ''); setUserName(user.display_name || user.email.split('@')[0]); } catch { toast.error('Failed to load user profile'); } finally { setLoadingProfile(false); } }; useEffect(() => { const token = getAccessToken(); if (token) { const payload = parseJwt(token); if (payload) { setUserRole(((payload.role as string) || '').toLowerCase()); } } fetchBrandingSettings(); fetchUserProfile(); }, []); useEffect(() => { if (blade === 'branding' && !isSuperAdmin) setBlade('overview'); }, [blade, isSuperAdmin]); const handleSaveProfile = async (e: React.FormEvent) => { e.preventDefault(); if (!profile) return; setSavingProfile(true); try { await userService.update(profile.user_id, { first_name: firstName, last_name: lastName, phone: phone, display_name: userName, }); toast.success('Profile saved'); fetchUserProfile(); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to update profile'; toast.error(message); } finally { setSavingProfile(false); } }; const handleDiscardProfile = () => { if (!profile) return; setFirstName(profile.first_name || ''); setLastName(profile.last_name || ''); setPhone(profile.phone || ''); setEmail(profile.email || ''); setUserName(profile.display_name || profile.email.split('@')[0]); }; const handleUpdatePassword = async (e: React.FormEvent) => { e.preventDefault(); if (!newPassword || !confirmPassword) { toast.error('Enter a new password'); return; } if (newPassword !== confirmPassword) { toast.error('New password and confirmation do not match'); return; } setSavingPassword(true); try { if (!profile) return; await userService.update(profile.user_id, { password: newPassword, }); toast.success('Password updated'); setCurrentPassword(''); setNewPassword(''); setConfirmPassword(''); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Password update failed'; toast.error(message); } finally { setSavingPassword(false); } }; const handleLogoFileUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setUploadingLogo(true); try { const formData = new FormData(); formData.append('file', file); formData.append('entity_type', 'invoice_branding'); formData.append('entity_id', 'logo'); const token = getAccessToken(); const backendUrl = ''; const res = await fetch(`${backendUrl}/api/v1/files/upload`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: formData, }); if (!res.ok) { const reader = new FileReader(); reader.onload = (evt) => { if (evt.target?.result) { setInvoiceLogoUrl(evt.target.result as string); toast.success('Invoice logo uploaded'); } }; reader.readAsDataURL(file); return; } const data = await res.json(); const relativePath = data.webp_path || data.storage_path || data.thumbnail_path || data.raw_path || data.url || data.file_path || ''; const uploadedUrl = relativePath.startsWith('http') ? relativePath : `${backendUrl}${relativePath.startsWith('/') ? '' : '/'}${relativePath}`; setInvoiceLogoUrl(uploadedUrl); toast.success('Invoice logo uploaded'); } catch { const reader = new FileReader(); reader.onload = (evt) => { if (evt.target?.result) { setInvoiceLogoUrl(evt.target.result as string); toast.success('Invoice logo loaded'); } }; reader.readAsDataURL(file); } finally { setUploadingLogo(false); } }; const handleSaveInvoiceBranding = async () => { const brandingData = { logoUrl: invoiceLogoUrl, companyName: invoiceCompanyName, gstin: invoiceGstin, storeAddress: invoiceStoreAddress, gstRate: Number(invoiceGstRate) || 18.0, }; localStorage.setItem('ifixkart_invoice_branding', JSON.stringify(brandingData)); try { const token = getAccessToken(); const backendUrl = ''; await fetch(`${backendUrl}/api/v1/settings/save/invoice_branding`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ setting_value: brandingData, description: 'Invoice logo and store branding configurations', is_public: true, }), }); } catch { /* save still persisted locally */ } toast.success('Invoice logo and branding saved'); }; const copyUserId = async () => { if (!profile?.user_id) return; try { await navigator.clipboard.writeText(profile.user_id); setCopiedId(true); toast.success('User ID copied to clipboard'); setTimeout(() => setCopiedId(false), 1500); } catch { toast.error('Could not copy the user ID'); } }; const reloadAll = () => { fetchUserProfile(); fetchBrandingSettings(); }; const navItems = useMemo( () => [ { id: 'overview' as const, label: 'Overview', hint: 'Resource essentials', icon: LayoutDashboard }, { id: 'profile' as const, label: 'Identity', hint: 'Name, phone, username', icon: User }, { id: 'security' as const, label: 'Access', hint: 'Password credentials', icon: KeyRound }, ...(isSuperAdmin ? [{ id: 'branding' as const, label: 'Invoice branding', hint: 'Logo, GSTIN, store', icon: FileText }] : []), ].filter((item) => { const q = navSearch.trim().toLowerCase(); if (!q) return true; return item.label.toLowerCase().includes(q) || item.hint.toLowerCase().includes(q); }), [isSuperAdmin, navSearch] ); const dataCardShell = 'crm-radius-section border border-border bg-card shadow-[0_1px_3px_rgba(16,24,40,0.06)] overflow-visible min-w-0'; const labelClass = 'block text-[11px] font-semibold text-muted-foreground uppercase mb-1.5'; const inputClass = 'w-full h-9 px-3 crm-radius-control border border-border bg-card text-[13px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors'; const primaryButton = 'inline-flex items-center justify-center gap-2 h-9 min-h-9 px-4 crm-radius-control bg-primary hover:bg-primary/90 text-white text-[13px] font-semibold cursor-pointer disabled:opacity-50 whitespace-nowrap'; const secondaryButton = 'inline-flex items-center justify-center gap-2 h-9 min-h-9 px-4 crm-radius-control border border-border bg-card text-foreground text-[13px] font-medium hover:bg-muted cursor-pointer disabled:opacity-50 whitespace-nowrap'; const fullName = [firstName, lastName].filter(Boolean).join(' ') || profile?.email || 'Administrator'; const roleLabel = userRole ? userRole.replace(/_/g, ' ') : 'Admin'; const bladeMeta: Record = { overview: { title: 'Overview', subtitle: 'Account resource essentials and current identity status.', }, profile: { title: 'Identity', subtitle: 'Update the display credentials used across the admin console.', }, security: { title: 'Access credentials', subtitle: 'Rotate the password used to sign in to this administrator account.', }, branding: { title: 'Invoice branding', subtitle: 'Logo, legal name, GSTIN and store contact printed on invoices.', }, }; return (

System Settings

{roleLabel}

{fullName}

{email || profile?.email || 'Administrator account'} · iFixKart console

{profile?.is_active !== false ? 'Active' : 'Inactive'} {roleLabel} {profile?.email_verified && ( Email verified )}

{bladeMeta[blade].title}

{bladeMeta[blade].subtitle}

{blade === 'profile' && (
)} {blade === 'security' && ( )} {blade === 'branding' && isSuperAdmin && ( )}
{loadingProfile && blade !== 'branding' ? (
) : blade === 'overview' ? (

Essentials

{fullName} {userName || '—'} {email || '—'} {phone || '—'} {roleLabel}

Directory

{profile?.user_id || '—'} {profile?.user_id && ( )}
{profile?.employee_code || '—'} {profile?.last_login ? format(new Date(profile.last_login), 'dd MMM yyyy, hh:mm a') : '—'} {profile?.created_at ? format(new Date(profile.created_at), 'dd MMM yyyy') : '—'} {profile?.is_active !== false ? 'Active' : 'Inactive'}
) : blade === 'profile' ? (
setFirstName(e.target.value)} className={inputClass} />
setLastName(e.target.value)} className={inputClass} />
setUserName(e.target.value)} className={inputClass} />
setPhone(e.target.value)} className={inputClass} />

Email is bound to this directory account and cannot be changed here.

) : blade === 'security' ? (
setCurrentPassword(e.target.value)} className={inputClass} />
setNewPassword(e.target.value)} className={inputClass} />
setConfirmPassword(e.target.value)} className={inputClass} />

Updating the password signs this administrator in with the new credential on the next login.

) : (
{invoiceLogoUrl ? (
Invoice logo preview
) : (
)}
setInvoiceLogoUrl(e.target.value)} placeholder="https://ifixkart.com/logo.png" className={inputClass} />

PNG, JPG, WebP or SVG. Transparent logos print cleanest.

setInvoiceCompanyName(e.target.value)} placeholder="iFixKart Solutions Platform" className={inputClass} />
setInvoiceGstin(e.target.value)} placeholder="33AAAAA0000A1Z5" className={inputClass} />
setInvoiceGstRate(e.target.value)} placeholder="18.0" className={inputClass} /> %

Configures default GST split on generated tax invoices (e.g. 18% = CGST {(Number(invoiceGstRate) / 2 || 9)}% + SGST {(Number(invoiceGstRate) / 2 || 9)}%).

setInvoiceStoreAddress(e.target.value)} placeholder="Offline Main Store Counter, Chennai | +91 9876543210" className={inputClass} />
)}
); }