## Changes ### 1. Split Store Identity and Brand Cards ✅ **Before:** Single tall "Store Identity" card **After:** Two focused cards **Store Identity Card:** - Store name - Store tagline - Contact email - Customer support email - Store phone **Brand Card:** - Store logo - Store icon - Brand colors (Primary, Accent, Error) - Reset to default button **Result:** Better organization, easier to scan --- ### 2. Fix Currency Symbol Fallback ✅ **Issue:** When currency has no symbol (like AUD), showed € instead **Screenshot:** Preview showed "€1.234.568" for Australian dollar **Fix:** ```typescript // 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; } ``` **Result:** - AUD → Shows "AUD1234" instead of "€1234" - IDR → Shows "Rp1234" (has symbol) - USD → Shows "$1234" (has symbol) - Currencies without symbols → Show currency code --- ### 3. Move Overview Card to First Position ✅ **Before:** Overview card at the bottom **After:** Overview card at the top **Rationale:** - Quick glance at store location, currency, timezone - Sets context for the rest of the settings - Industry standard (Shopify shows overview first) **Card Content:** ``` 📍 Store Location: Australia Currency: Australian dollar • Timezone: Australia/Sydney ``` --- ## Final Card Order 1. **Store Overview** (new position) 2. **Store Identity** (name, tagline, contacts) 3. **Brand** (logo, icon, colors) 4. **Store Address** 5. **Currency & Formatting** 6. **Standards & Formats** **Result:** Logical flow, better UX, professional layout
598 lines
21 KiB
TypeScript
598 lines
21 KiB
TypeScript
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<StoreSettings>({
|
|
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 = <K extends keyof StoreSettings>(
|
|
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 (
|
|
<SettingsLayout
|
|
title="Store Details"
|
|
description="Manage your store's basic information and regional settings"
|
|
onSave={handleSave}
|
|
isLoading={isLoading}
|
|
>
|
|
{/* Store Overview */}
|
|
<div className="bg-primary/10 border border-primary/20 rounded-lg p-4">
|
|
{(() => {
|
|
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 (
|
|
<>
|
|
<p className="text-sm font-medium">
|
|
📍 Store Location: {countryName}
|
|
</p>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
Currency: {currencyInfo?.name || settings.currency} • Timezone: {settings.timezone}
|
|
</p>
|
|
</>
|
|
);
|
|
})()}
|
|
</div>
|
|
|
|
{/* Store Identity */}
|
|
<SettingsCard
|
|
title="Store Identity"
|
|
description="Basic information about your store"
|
|
>
|
|
<SettingsSection label="Store name" required htmlFor="storeName">
|
|
<Input
|
|
id="storeName"
|
|
value={settings.storeName}
|
|
onChange={(e) => updateSetting('storeName', e.target.value)}
|
|
placeholder="My Awesome Store"
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection
|
|
label="Store tagline"
|
|
description="A short tagline or slogan for your store"
|
|
htmlFor="storeTagline"
|
|
>
|
|
<Input
|
|
id="storeTagline"
|
|
value={settings.storeTagline}
|
|
onChange={(e) => updateSetting('storeTagline', e.target.value)}
|
|
placeholder="Quality products, delivered fast"
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection
|
|
label="Contact email"
|
|
description="Customers will use this email to contact you"
|
|
htmlFor="contactEmail"
|
|
>
|
|
<Input
|
|
id="contactEmail"
|
|
type="email"
|
|
value={settings.contactEmail}
|
|
onChange={(e) => updateSetting('contactEmail', e.target.value)}
|
|
placeholder="contact@example.com"
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection
|
|
label="Customer support email"
|
|
description="Separate email for customer support inquiries"
|
|
htmlFor="supportEmail"
|
|
>
|
|
<Input
|
|
id="supportEmail"
|
|
type="email"
|
|
value={settings.supportEmail}
|
|
onChange={(e) => updateSetting('supportEmail', e.target.value)}
|
|
placeholder="support@example.com"
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection
|
|
label="Store phone"
|
|
description="Optional phone number for customer inquiries"
|
|
htmlFor="phone"
|
|
>
|
|
<Input
|
|
id="phone"
|
|
type="tel"
|
|
value={settings.phone}
|
|
onChange={(e) => updateSetting('phone', e.target.value)}
|
|
placeholder="+62 812 3456 7890"
|
|
/>
|
|
</SettingsSection>
|
|
</SettingsCard>
|
|
|
|
{/* Brand */}
|
|
<SettingsCard
|
|
title="Brand"
|
|
description="Logo, icon, and colors for your store"
|
|
>
|
|
<SettingsSection label="Store logo" description="Recommended: 200x60px PNG with transparent background">
|
|
<ImageUpload
|
|
value={settings.storeLogo}
|
|
onChange={(url) => updateSetting('storeLogo', url)}
|
|
onRemove={() => updateSetting('storeLogo', '')}
|
|
maxSize={2}
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection label="Store icon" description="Favicon for browser tabs (32x32px)">
|
|
<ImageUpload
|
|
value={settings.storeIcon}
|
|
onChange={(url) => updateSetting('storeIcon', url)}
|
|
onRemove={() => updateSetting('storeIcon', '')}
|
|
maxSize={1}
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<div className="pt-4 border-t">
|
|
<h4 className="text-sm font-medium mb-4">Brand Colors</h4>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<ColorPicker
|
|
label="Primary Color"
|
|
description="Main brand color"
|
|
value={settings.primaryColor}
|
|
onChange={(color) => updateSetting('primaryColor', color)}
|
|
/>
|
|
|
|
<ColorPicker
|
|
label="Accent Color"
|
|
description="Success and highlights"
|
|
value={settings.accentColor}
|
|
onChange={(color) => updateSetting('accentColor', color)}
|
|
/>
|
|
|
|
<ColorPicker
|
|
label="Error Color"
|
|
description="Errors and warnings"
|
|
value={settings.errorColor}
|
|
onChange={(color) => updateSetting('errorColor', color)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 mt-4">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => {
|
|
updateSetting('primaryColor', '#3b82f6');
|
|
updateSetting('accentColor', '#10b981');
|
|
updateSetting('errorColor', '#ef4444');
|
|
toast.success('Colors reset to default');
|
|
}}
|
|
>
|
|
Reset to Default
|
|
</Button>
|
|
<p className="text-sm text-muted-foreground">
|
|
Changes apply after saving
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</SettingsCard>
|
|
|
|
{/* Store Address */}
|
|
<SettingsCard
|
|
title="Store Address"
|
|
description="Used for shipping origin, invoices, and tax calculations"
|
|
>
|
|
<SettingsSection label="Country/Region" required htmlFor="country">
|
|
<SearchableSelect
|
|
value={settings.country}
|
|
onChange={(v) => 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..."
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection label="Street address" htmlFor="address">
|
|
<Input
|
|
id="address"
|
|
value={settings.address}
|
|
onChange={(e) => updateSetting('address', e.target.value)}
|
|
placeholder="Jl. Example No. 123"
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<SettingsSection label="City" htmlFor="city">
|
|
<Input
|
|
id="city"
|
|
value={settings.city}
|
|
onChange={(e) => updateSetting('city', e.target.value)}
|
|
placeholder="Jakarta"
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection label="State/Province" htmlFor="state">
|
|
<Input
|
|
id="state"
|
|
value={settings.state}
|
|
onChange={(e) => updateSetting('state', e.target.value)}
|
|
placeholder="DKI Jakarta"
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection label="Postal code" htmlFor="postcode">
|
|
<Input
|
|
id="postcode"
|
|
value={settings.postcode}
|
|
onChange={(e) => updateSetting('postcode', e.target.value)}
|
|
placeholder="12345"
|
|
/>
|
|
</SettingsSection>
|
|
</div>
|
|
</SettingsCard>
|
|
|
|
{/* Currency & Formatting */}
|
|
<SettingsCard
|
|
title="Currency & Formatting"
|
|
description="How prices are displayed in your store"
|
|
>
|
|
<SettingsSection label="Currency" required htmlFor="currency">
|
|
<SearchableSelect
|
|
value={settings.currency}
|
|
onChange={(v) => 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..."
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection label="Currency position" htmlFor="currencyPosition">
|
|
<Select
|
|
value={settings.currencyPosition}
|
|
onValueChange={(v: any) => updateSetting('currencyPosition', v)}
|
|
>
|
|
<SelectTrigger id="currencyPosition">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="left">Left (Rp1234)</SelectItem>
|
|
<SelectItem value="right">Right (1234Rp)</SelectItem>
|
|
<SelectItem value="left_space">Left with space (Rp 1234)</SelectItem>
|
|
<SelectItem value="right_space">Right with space (1234 Rp)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsSection>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<SettingsSection label="Thousand separator" htmlFor="thousandSep">
|
|
<Input
|
|
id="thousandSep"
|
|
value={settings.thousandSep}
|
|
onChange={(e) => updateSetting('thousandSep', e.target.value)}
|
|
maxLength={1}
|
|
placeholder=","
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection label="Decimal separator" htmlFor="decimalSep">
|
|
<Input
|
|
id="decimalSep"
|
|
value={settings.decimalSep}
|
|
onChange={(e) => updateSetting('decimalSep', e.target.value)}
|
|
maxLength={1}
|
|
placeholder="."
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection label="Number of decimals" htmlFor="decimals">
|
|
<Select
|
|
value={settings.decimals.toString()}
|
|
onValueChange={(v) => updateSetting('decimals', parseInt(v))}
|
|
>
|
|
<SelectTrigger id="decimals">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="0">0</SelectItem>
|
|
<SelectItem value="1">1</SelectItem>
|
|
<SelectItem value="2">2</SelectItem>
|
|
<SelectItem value="3">3</SelectItem>
|
|
<SelectItem value="4">4</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsSection>
|
|
</div>
|
|
|
|
{/* Live Preview */}
|
|
<div className="mt-4 p-4 bg-muted rounded-lg">
|
|
<p className="text-sm text-muted-foreground mb-2">Preview:</p>
|
|
<p className="text-2xl font-semibold">{formatCurrency(1234567.89)}</p>
|
|
</div>
|
|
</SettingsCard>
|
|
|
|
{/* Standards & Formats */}
|
|
<SettingsCard
|
|
title="Standards & Formats"
|
|
description="Timezone and measurement units"
|
|
>
|
|
<SettingsSection label="Timezone" htmlFor="timezone">
|
|
<SearchableSelect
|
|
value={settings.timezone}
|
|
onChange={(v) => 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..."
|
|
/>
|
|
</SettingsSection>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<SettingsSection label="Weight unit" htmlFor="weightUnit">
|
|
<Select value={settings.weightUnit} onValueChange={(v) => updateSetting('weightUnit', v)}>
|
|
<SelectTrigger id="weightUnit">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="kg">Kilogram (kg)</SelectItem>
|
|
<SelectItem value="g">Gram (g)</SelectItem>
|
|
<SelectItem value="lb">Pound (lb)</SelectItem>
|
|
<SelectItem value="oz">Ounce (oz)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection label="Dimension unit" htmlFor="dimensionUnit">
|
|
<Select value={settings.dimensionUnit} onValueChange={(v) => updateSetting('dimensionUnit', v)}>
|
|
<SelectTrigger id="dimensionUnit">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="cm">Centimeter (cm)</SelectItem>
|
|
<SelectItem value="m">Meter (m)</SelectItem>
|
|
<SelectItem value="in">Inch (in)</SelectItem>
|
|
<SelectItem value="ft">Foot (ft)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsSection>
|
|
</div>
|
|
</SettingsCard>
|
|
</SettingsLayout>
|
|
);
|
|
}
|