From 39a215c1889cdf137ab76914ff12f8f846861333 Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 6 Nov 2025 12:08:04 +0700 Subject: [PATCH] fix: Sticky submenu + emoji flags instead of images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Made Settings Submenu Sticky ✅ Problem: Settings submenu wasn't sticky like Dashboard Solution: Added sticky positioning to SubmenuBar Added classes: - sticky top-0 z-20 - bg-background/95 backdrop-blur - supports-[backdrop-filter]:bg-background/60 Result: ✅ Settings submenu now stays at top when scrolling 2. Switched to Emoji Flags ✅ Problem: Base64 images not showing in select options Better Solution: Use native emoji flags Benefits: - ✅ No image loading required - ✅ Native OS rendering - ✅ Smaller bundle size - ✅ Better performance - ✅ Always works (no broken images) Implementation: function countryCodeToEmoji(countryCode: string): string { const codePoints = countryCode .toUpperCase() .split('') .map(char => 127397 + char.charCodeAt(0)); return String.fromCodePoint(...codePoints); } // AE → 🇦🇪 // US → 🇺🇸 // ID → 🇮🇩 3. Updated Currency Select ✅ Before: [Image] United Arab Emirates dirham (AED) After: 🇦🇪 United Arab Emirates dirham (AED) - Emoji flag in label - No separate icon prop needed - Works immediately 4. Updated Store Summary ✅ Before: [Image] Your store is located in Indonesia After: 🇮🇩 Your store is located in Indonesia - Dynamic emoji flag based on currency - Cleaner implementation - No image loading 5. Simplified SearchableSelect ✅ - Removed icon prop (not needed with emoji) - Removed image rendering code - Simpler component API Files Modified: - SubmenuBar.tsx: Added sticky positioning - Store.tsx: Emoji flags + helper function - searchable-select.tsx: Removed icon support Why Emoji > Images: ✅ Universal support (all modern browsers/OS) ✅ No loading time ✅ No broken images ✅ Smaller code ✅ Native rendering ✅ Accessibility friendly --- admin-spa/src/components/nav/SubmenuBar.tsx | 2 +- .../src/components/ui/searchable-select.tsx | 14 ++------ admin-spa/src/routes/Settings/Store.tsx | 34 +++++++++---------- 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/admin-spa/src/components/nav/SubmenuBar.tsx b/admin-spa/src/components/nav/SubmenuBar.tsx index 63ba7af..82726c1 100644 --- a/admin-spa/src/components/nav/SubmenuBar.tsx +++ b/admin-spa/src/components/nav/SubmenuBar.tsx @@ -12,7 +12,7 @@ export default function SubmenuBar({ items = [] }: Props) { if (items.length === 0) return null; return ( -
+
{items.map((it) => { diff --git a/admin-spa/src/components/ui/searchable-select.tsx b/admin-spa/src/components/ui/searchable-select.tsx index ef7ebe2..934cbaf 100644 --- a/admin-spa/src/components/ui/searchable-select.tsx +++ b/admin-spa/src/components/ui/searchable-select.tsx @@ -22,8 +22,6 @@ export interface Option { label: React.ReactNode; /** Optional text used for filtering. Falls back to string label or value. */ searchText?: string; - /** Optional icon (base64 image or URL) to display before the label */ - icon?: string; } interface Props { @@ -67,12 +65,7 @@ export function SearchableSelect({ aria-disabled={disabled} tabIndex={disabled ? -1 : 0} > -
- {selected?.icon && ( - - )} - {selected ? selected.label : placeholder} -
+ {selected ? selected.label : placeholder} @@ -111,10 +104,7 @@ export function SearchableSelect({ )} /> )} - {opt.icon && ( - - )} - {opt.label} + {opt.label} ))} diff --git a/admin-spa/src/routes/Settings/Store.tsx b/admin-spa/src/routes/Settings/Store.tsx index 06667dd..d6faed0 100644 --- a/admin-spa/src/routes/Settings/Store.tsx +++ b/admin-spa/src/routes/Settings/Store.tsx @@ -10,6 +10,16 @@ import { SearchableSelect } from '@/components/ui/searchable-select'; 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; @@ -302,16 +312,14 @@ export default function StoreDetailsPage() { ? currency.code : currency.symbol; - // Find matching flag data + // 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: flagInfo - ? `${currency.name} (${displaySymbol})` - : `${currency.name} (${displaySymbol})`, + label: `${flagEmoji} ${currency.name} (${displaySymbol})`.trim(), searchText: `${currency.code} ${currency.name} ${displaySymbol}`, - icon: flagInfo?.flag, // Add flag as icon }; })} placeholder="Select currency..." @@ -439,21 +447,13 @@ export default function StoreDetailsPage() { // Find flag for current currency const currencyFlag = flagsData.find((f: any) => f.code === settings.currency); const currencyInfo = currencies.find((c: any) => c.code === settings.currency); + const flagEmoji = currencyFlag ? countryCodeToEmoji(currencyFlag.countryCode) : ''; return ( <> -
- {currencyFlag?.flag && ( - {currencyFlag.country} - )} -

- Your store is located in {currencyFlag?.country || settings.country} -

-
+

+ {flagEmoji} Your store is located in {currencyFlag?.country || settings.country} +

Prices will be displayed in {currencyInfo?.name || settings.currency} • Timezone: {settings.timezone}