import React, { useState, useEffect, useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '@/lib/api'; import { SettingsLayout } from './components/SettingsLayout'; import { SettingsCard } from './components/SettingsCard'; import { SettingsSection } from './components/SettingsSection'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { SearchableSelect } from '@/components/ui/searchable-select'; import { ImageUpload } from '@/components/ui/image-upload'; import { ColorPicker } from '@/components/ui/color-picker'; import { Button } from '@/components/ui/button'; import { toast } from 'sonner'; import flagsData from '@/data/flags.json'; // Convert country code to emoji flag function countryCodeToEmoji(countryCode: string): string { if (!countryCode || countryCode.length !== 2) return ''; const codePoints = countryCode .toUpperCase() .split('') .map(char => 127397 + char.charCodeAt(0)); return String.fromCodePoint(...codePoints); } interface StoreSettings { storeName: string; contactEmail: string; supportEmail: string; phone: string; country: string; address: string; city: string; state: string; postcode: string; currency: string; currencyPosition: 'left' | 'right' | 'left_space' | 'right_space'; thousandSep: string; decimalSep: string; decimals: number; timezone: string; weightUnit: string; dimensionUnit: string; // Branding storeLogo: string; storeIcon: string; storeTagline: string; primaryColor: string; accentColor: string; errorColor: string; } export default function StoreDetailsPage() { const queryClient = useQueryClient(); const [settings, setSettings] = useState({ storeName: '', contactEmail: '', supportEmail: '', phone: '', country: 'ID', address: '', city: '', state: '', postcode: '', currency: 'IDR', currencyPosition: 'left', thousandSep: ',', decimalSep: '.', decimals: 0, timezone: 'Asia/Jakarta', weightUnit: 'kg', dimensionUnit: 'cm', storeLogo: '', storeIcon: '', storeTagline: '', primaryColor: '#3b82f6', accentColor: '#10b981', errorColor: '#ef4444', }); // Fetch store settings const { data: storeData, isLoading } = useQuery({ queryKey: ['store-settings'], queryFn: () => api.get('/store/settings'), }); // Fetch countries const { data: countries = [] } = useQuery({ queryKey: ['store-countries'], queryFn: () => api.get('/store/countries'), staleTime: 60 * 60 * 1000, // 1 hour }); // Fetch timezones const { data: timezones = {} } = useQuery({ queryKey: ['store-timezones'], queryFn: () => api.get('/store/timezones'), staleTime: 60 * 60 * 1000, // 1 hour }); // Fetch currencies const { data: currencies = [] } = useQuery({ queryKey: ['store-currencies'], queryFn: () => api.get('/store/currencies'), staleTime: 60 * 60 * 1000, // 1 hour }); // Initialize state from data - use useMemo instead of useEffect to avoid cascading renders const initialSettings = useMemo(() => { if (!storeData) return settings; return { storeName: storeData.store_name || '', contactEmail: storeData.contact_email || '', supportEmail: storeData.support_email || '', phone: storeData.phone || '', country: storeData.country || 'ID', address: storeData.address || '', city: storeData.city || '', state: '', postcode: storeData.postcode || '', currency: storeData.currency || 'IDR', currencyPosition: storeData.currency_position || 'left', thousandSep: storeData.thousand_separator || ',', decimalSep: storeData.decimal_separator || '.', decimals: storeData.number_of_decimals || 0, timezone: storeData.timezone || 'Asia/Jakarta', weightUnit: storeData.weight_unit || 'kg', dimensionUnit: storeData.dimension_unit || 'cm', storeLogo: storeData.store_logo || '', storeIcon: storeData.store_icon || '', storeTagline: storeData.store_tagline || '', primaryColor: storeData.primary_color || '#3b82f6', accentColor: storeData.accent_color || '#10b981', errorColor: storeData.error_color || '#ef4444', }; }, [storeData]); // Update settings when initialSettings changes useEffect(() => { if (storeData) { // eslint-disable-next-line react-hooks/set-state-in-effect setSettings(initialSettings); } }, [initialSettings, storeData]); // Save mutation const saveMutation = useMutation({ mutationFn: (data: StoreSettings) => api.post('/store/settings', { store_name: data.storeName, contact_email: data.contactEmail, support_email: data.supportEmail, phone: data.phone, country: data.country, address: data.address, city: data.city, postcode: data.postcode, currency: data.currency, currency_position: data.currencyPosition, thousand_separator: data.thousandSep, decimal_separator: data.decimalSep, number_of_decimals: data.decimals, timezone: data.timezone, weight_unit: data.weightUnit, dimension_unit: data.dimensionUnit, store_logo: data.storeLogo, store_icon: data.storeIcon, store_tagline: data.storeTagline, primary_color: data.primaryColor, accent_color: data.accentColor, error_color: data.errorColor, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['store-settings'] }); toast.success('Your store details have been updated successfully.'); // Dispatch event to update site title in header window.dispatchEvent(new CustomEvent('woonoow:store:updated', { detail: { store_name: settings.storeName } })); }, onError: () => { toast.error('Failed to save store settings'); }, }); const handleSave = async () => { await saveMutation.mutateAsync(settings); }; const updateSetting = ( key: K, value: StoreSettings[K] ) => { setSettings((prev) => ({ ...prev, [key]: value })); }; // Currency preview const formatCurrency = (amount: number) => { const formatted = amount.toFixed(settings.decimals) .replace('.', settings.decimalSep) .replace(/\B(?=(\d{3})+(?!\d))/g, settings.thousandSep); // Get currency symbol from currencies data, fallback to currency code const currencyInfo = currencies.find((c: any) => c.code === settings.currency); let symbol = settings.currency; // Default to currency code if (currencyInfo?.symbol && !currencyInfo.symbol.includes('&#')) { // Use symbol only if it exists and doesn't contain HTML entities symbol = currencyInfo.symbol; } switch (settings.currencyPosition) { case 'left': return `${symbol}${formatted}`; case 'right': return `${formatted}${symbol}`; case 'left_space': return `${symbol} ${formatted}`; case 'right_space': return `${formatted} ${symbol}`; default: return `${symbol}${formatted}`; } }; return ( {/* Store Overview */}
{(() => { const currencyFlag = flagsData.find((f: any) => f.code === settings.currency); const currencyInfo = currencies.find((c: any) => c.code === settings.currency); const countryName = currencyFlag?.country || settings.country; return ( <>

📍 Store Location: {countryName}

Currency: {currencyInfo?.name || settings.currency} • Timezone: {settings.timezone}

); })()}
{/* Store Identity */} updateSetting('storeName', e.target.value)} placeholder="My Awesome Store" /> updateSetting('storeTagline', e.target.value)} placeholder="Quality products, delivered fast" /> updateSetting('contactEmail', e.target.value)} placeholder="contact@example.com" /> updateSetting('supportEmail', e.target.value)} placeholder="support@example.com" /> updateSetting('phone', e.target.value)} placeholder="+62 812 3456 7890" /> {/* Brand */} updateSetting('storeLogo', url)} onRemove={() => updateSetting('storeLogo', '')} maxSize={2} /> updateSetting('storeIcon', url)} onRemove={() => updateSetting('storeIcon', '')} maxSize={1} />

Brand Colors

updateSetting('primaryColor', color)} /> updateSetting('accentColor', color)} /> updateSetting('errorColor', color)} />

Changes apply after saving

{/* Store Address */} updateSetting('country', v)} options={countries.map((country: { code: string; name: string }) => { const flagEmoji = countryCodeToEmoji(country.code); return { value: country.code, label: `${flagEmoji} ${country.name}`.trim(), searchText: `${country.code} ${country.name}`, }; })} placeholder="Select country..." /> updateSetting('address', e.target.value)} placeholder="Jl. Example No. 123" />
updateSetting('city', e.target.value)} placeholder="Jakarta" /> updateSetting('state', e.target.value)} placeholder="DKI Jakarta" /> updateSetting('postcode', e.target.value)} placeholder="12345" />
{/* Currency & Formatting */} updateSetting('currency', v)} options={currencies.map((currency: { code: string; name: string; symbol: string }) => { // Use currency code if symbol contains HTML entities (&#x...) or is empty const displaySymbol = (!currency.symbol || currency.symbol.includes('&#')) ? currency.code : currency.symbol; // Find matching flag data and convert to emoji const flagInfo = flagsData.find((f: any) => f.code === currency.code); const flagEmoji = flagInfo ? countryCodeToEmoji(flagInfo.countryCode) : ''; return { value: currency.code, label: `${flagEmoji} ${currency.name} (${displaySymbol})`.trim(), searchText: `${currency.code} ${currency.name} ${displaySymbol}`, }; })} placeholder="Select currency..." />
updateSetting('thousandSep', e.target.value)} maxLength={1} placeholder="," /> updateSetting('decimalSep', e.target.value)} maxLength={1} placeholder="." />
{/* Live Preview */}

Preview:

{formatCurrency(1234567.89)}

{/* Standards & Formats */} updateSetting('timezone', v)} options={Object.entries(timezones).flatMap(([continent, tzList]: [string, any]) => tzList.map((tz: { value: string; label: string; offset: string }) => ({ value: tz.value, label: `${tz.label} (${tz.offset})`, searchText: `${continent} ${tz.label} ${tz.offset}`, })) )} placeholder="Select timezone..." />
); }