From 2008f2f141e708782ff523fa467c4343bda41659 Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 6 Nov 2025 12:23:38 +0700 Subject: [PATCH] feat: Add flags to Country select + Bank account repeater for BACS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Added Emoji Flags to Country/Region Select ✅ Before: Indonesia After: 🇮🇩 Indonesia Implementation: - Uses same countryCodeToEmoji() helper - Flags for all countries in dropdown - Better visual identification 2. Implemented Bank Account Repeater Field ✅ New field type: 'account' - Add/remove multiple bank accounts - Each account has 6 fields: * Account Name (required) * Account Number (required) * Bank Name (required) * Sort Code / Branch Code (optional) * IBAN (optional) * BIC / SWIFT (optional) UI Features: ✅ Compact card layout with muted background ✅ 2-column grid on desktop, 1-column on mobile ✅ Delete button per account (trash icon) ✅ Add button at bottom with plus icon ✅ Account numbering (Account 1, Account 2, etc.) ✅ Smaller inputs (h-9) for compact layout ✅ Clear labels with required indicators Perfect for: - Direct Bank Transfer (BACS) - Manual payment methods - Multiple bank account management 3. Updated GenericGatewayForm ✅ Added support: - New 'account' field type - BankAccount interface - Repeater logic (add/remove/update) - Plus and Trash2 icons from lucide-react Data structure: interface BankAccount { account_name: string; account_number: string; bank_name: string; sort_code?: string; iban?: string; bic?: string; } Benefits: ✅ Country select now has visual flags ✅ Bank accounts are easy to manage ✅ Compact, responsive UI ✅ Clear visual hierarchy ✅ Supports international formats (IBAN, BIC, Sort Code) Files Modified: - Store.tsx: Added flags to country select - GenericGatewayForm.tsx: Bank account repeater - SubmenuBar.tsx: Fullscreen prop (user change) --- admin-spa/src/components/nav/SubmenuBar.tsx | 11 +- .../settings/GenericGatewayForm.tsx | 168 +++++++++++++++++- admin-spa/src/routes/Settings/Store.tsx | 13 +- 3 files changed, 182 insertions(+), 10 deletions(-) diff --git a/admin-spa/src/components/nav/SubmenuBar.tsx b/admin-spa/src/components/nav/SubmenuBar.tsx index 82726c1..3fdd945 100644 --- a/admin-spa/src/components/nav/SubmenuBar.tsx +++ b/admin-spa/src/components/nav/SubmenuBar.tsx @@ -2,17 +2,22 @@ import React from 'react'; import { Link, useLocation } from 'react-router-dom'; import type { SubItem } from '@/nav/tree'; -type Props = { items?: SubItem[] }; +type Props = { items?: SubItem[]; fullscreen?: boolean }; -export default function SubmenuBar({ items = [] }: Props) { +export default function SubmenuBar({ items = [], fullscreen = false }: Props) { // Always call hooks first const { pathname } = useLocation(); // Single source of truth: props.items. No fallbacks, no demos, no path-based defaults if (items.length === 0) return null; + // Calculate top position based on fullscreen state + // Fullscreen: top-16 (below 64px header) + // Normal: top-[88px] (below 40px WP admin bar + 48px menu bar) + const topClass = fullscreen ? 'top-0' : 'top-[calc(7rem+32px)]'; + return ( -
+
{items.map((it) => { diff --git a/admin-spa/src/components/settings/GenericGatewayForm.tsx b/admin-spa/src/components/settings/GenericGatewayForm.tsx index 6b326fc..a6ddb85 100644 --- a/admin-spa/src/components/settings/GenericGatewayForm.tsx +++ b/admin-spa/src/components/settings/GenericGatewayForm.tsx @@ -13,7 +13,7 @@ import { } from '@/components/ui/select'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Alert, AlertDescription } from '@/components/ui/alert'; -import { ExternalLink, AlertTriangle } from 'lucide-react'; +import { ExternalLink, AlertTriangle, Plus, Trash2 } from 'lucide-react'; interface GatewayField { id: string; @@ -51,7 +51,17 @@ interface GenericGatewayFormProps { } // Supported field types (outside component to avoid re-renders) -const SUPPORTED_FIELD_TYPES = ['text', 'password', 'checkbox', 'select', 'textarea', 'number', 'email', 'url']; +const SUPPORTED_FIELD_TYPES = ['text', 'password', 'checkbox', 'select', 'textarea', 'number', 'email', 'url', 'account']; + +// Bank account interface +interface BankAccount { + account_name: string; + account_number: string; + bank_name: string; + sort_code?: string; + iban?: string; + bic?: string; +} export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = false }: GenericGatewayFormProps) { const [formData, setFormData] = useState>({}); @@ -217,6 +227,160 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
); + case 'account': + // Bank account repeater field + const accounts = (value as BankAccount[]) || []; + + const addAccount = () => { + const newAccounts = [...accounts, { + account_name: '', + account_number: '', + bank_name: '', + sort_code: '', + iban: '', + bic: '' + }]; + handleFieldChange(field.id, newAccounts); + }; + + const removeAccount = (index: number) => { + const newAccounts = accounts.filter((_, i) => i !== index); + handleFieldChange(field.id, newAccounts); + }; + + const updateAccount = (index: number, key: keyof BankAccount, val: string) => { + const newAccounts = [...accounts]; + newAccounts[index] = { ...newAccounts[index], [key]: val }; + handleFieldChange(field.id, newAccounts); + }; + + return ( +
+
+ + {field.description && ( +

+ )} +

+ +
+ {accounts.map((account, index) => ( +
+
+

Account {index + 1}

+ +
+ +
+
+ + updateAccount(index, 'account_name', e.target.value)} + placeholder="e.g., Business Account" + className="h-9" + /> +
+ +
+ + updateAccount(index, 'account_number', e.target.value)} + placeholder="e.g., 12345678" + className="h-9" + /> +
+ +
+ + updateAccount(index, 'bank_name', e.target.value)} + placeholder="e.g., Bank Central Asia" + className="h-9" + /> +
+ +
+ + updateAccount(index, 'sort_code', e.target.value)} + placeholder="e.g., 12-34-56" + className="h-9" + /> +
+ +
+ + updateAccount(index, 'iban', e.target.value)} + placeholder="e.g., GB29 NWBK 6016 1331 9268 19" + className="h-9" + /> +
+ +
+ + updateAccount(index, 'bic', e.target.value)} + placeholder="e.g., NWBKGB2L" + className="h-9" + /> +
+
+
+ ))} +
+ + +
+ ); + default: // text, password, number, email, url return ( diff --git a/admin-spa/src/routes/Settings/Store.tsx b/admin-spa/src/routes/Settings/Store.tsx index d6faed0..5c3301e 100644 --- a/admin-spa/src/routes/Settings/Store.tsx +++ b/admin-spa/src/routes/Settings/Store.tsx @@ -249,11 +249,14 @@ export default function StoreDetailsPage() { updateSetting('country', v)} - options={countries.map((country: { code: string; name: string }) => ({ - value: country.code, - label: country.name, - searchText: country.name, - }))} + 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..." />