@burdenoff/microfe-kadaikodi 2026.719.2 → 2026.719.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/kadaikodi/hooks/useKadaikodiApi.d.ts +36 -0
- package/dist/kadaikodi/hooks/useKadaikodiApi.d.ts.map +1 -1
- package/dist/kadaikodi/hooks/useKadaikodiApi.js +148 -27
- package/dist/kadaikodi/hooks/useKadaikodiApi.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CheckoutPage.d.ts.map +1 -1
- package/dist/kadaikodi/pages/customer/CheckoutPage.js +235 -219
- package/dist/kadaikodi/pages/customer/CheckoutPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/PaymentMethodsPage.d.ts +4 -0
- package/dist/kadaikodi/pages/customer/PaymentMethodsPage.d.ts.map +1 -1
- package/dist/kadaikodi/pages/customer/PaymentMethodsPage.js +209 -192
- package/dist/kadaikodi/pages/customer/PaymentMethodsPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/SavedAddressesPage.d.ts +3 -1
- package/dist/kadaikodi/pages/customer/SavedAddressesPage.d.ts.map +1 -1
- package/dist/kadaikodi/pages/customer/SavedAddressesPage.js +195 -173
- package/dist/kadaikodi/pages/customer/SavedAddressesPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/shared.d.ts +1 -2
- package/dist/kadaikodi/pages/customer/shared.d.ts.map +1 -1
- package/dist/kadaikodi/pages/customer/shared.js +15 -37
- package/dist/kadaikodi/pages/customer/shared.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SavedAddressesPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/customer/SavedAddressesPage.tsx"],"sourcesContent":["/**\n * Saved Addresses Page\n * @module kadaikodi/pages/customer\n *\n * Manage delivery addresses: list, add (inline expandable), edit (modal),\n * delete (with default protection), set default, usage count, last used date,\n * address nickname support. 4 mock addresses.\n */\n\nimport { useState } from 'react';\nimport { MapPin, Plus, Pencil, Trash2, CheckCircle2, Home, Briefcase, Star, X } from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { useKadaikodi } from '../../context/KadaikodiContext';\nimport { cn } from '../../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface SavedAddress {\n id: string;\n type: 'home' | 'office' | 'other';\n nickname: string;\n addressLine1: string;\n addressLine2: string;\n city: string;\n state: string;\n zipCode: string;\n isDefault: boolean;\n usageCount: number;\n lastUsed?: string;\n}\n\ntype AddressFormData = Omit<SavedAddress, 'id' | 'usageCount' | 'lastUsed'>;\n\nconst emptyForm: AddressFormData = {\n type: 'home',\n nickname: '',\n addressLine1: '',\n addressLine2: '',\n city: '',\n state: '',\n zipCode: '',\n isDefault: false,\n};\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst iconMap = {\n home: Home,\n office: Briefcase,\n other: MapPin,\n};\n\nconst typeBadgeStyles: Record<string, string> = {\n home: 'bg-status-success-bg-subtle text-status-success-text',\n office: 'bg-[var(--color-accent-soft)] text-text-primary',\n other: 'bg-accent-purple-subtle text-text-primary',\n};\n\n// ============================================================================\n// Address Form Component\n// ============================================================================\n\nfunction AddressForm({\n data,\n onChange,\n onSubmit,\n onCancel,\n submitLabel,\n}: {\n data: AddressFormData;\n onChange: (field: string, value: string | boolean) => void;\n onSubmit: () => void;\n onCancel: () => void;\n submitLabel: string;\n}) {\n return (\n <div className=\"space-y-4\">\n {/* Type + Nickname row */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n Address Type <span className=\"text-status-error-text\">*</span>\n </label>\n <select\n value={data.type}\n onChange={(e) => onChange('type', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"home\">Home</option>\n <option value=\"office\">Office</option>\n <option value=\"other\">Other</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">Nickname</label>\n <input\n type=\"text\"\n placeholder=\"e.g., Home Sweet Home\"\n value={data.nickname}\n onChange={(e) => onChange('nickname', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Address Line 1 */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n Address Line 1 <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"House/Flat number, Street name\"\n value={data.addressLine1}\n onChange={(e) => onChange('addressLine1', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* Address Line 2 */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">Address Line 2</label>\n <input\n type=\"text\"\n placeholder=\"Area, Landmark\"\n value={data.addressLine2}\n onChange={(e) => onChange('addressLine2', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* City / State / ZIP */}\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n City <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"City\"\n value={data.city}\n onChange={(e) => onChange('city', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n State <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"State\"\n value={data.state}\n onChange={(e) => onChange('state', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n ZIP Code <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"560001\"\n value={data.zipCode}\n onChange={(e) => onChange('zipCode', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Set Default checkbox */}\n <label className=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={data.isDefault}\n onChange={(e) => onChange('isDefault', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle text-text-primary\"\n />\n <span className=\"text-sm text-text-secondary\">Set as default address</span>\n </label>\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onSubmit}\n className=\"rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity\"\n >\n {submitLabel}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-accent transition-colors\"\n >\n Cancel\n </button>\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\nexport function SavedAddressesPage() {\n const { navigateTo } = useKadaikodi();\n const [addresses, setAddresses] = useState<SavedAddress[]>([]);\n const [showAddForm, setShowAddForm] = useState(false);\n const [editingAddress, setEditingAddress] = useState<SavedAddress | null>(null);\n const [newAddress, setNewAddress] = useState<AddressFormData>({ ...emptyForm });\n const [statusMessage, setStatusMessage] = useState<string | null>(null);\n\n // --- Helpers ---\n const showStatus = (msg: string) => {\n setStatusMessage(msg);\n setTimeout(() => setStatusMessage(null), 3000);\n };\n\n const validateForm = (data: AddressFormData): boolean => {\n if (\n !data.addressLine1.trim() ||\n !data.city.trim() ||\n !data.state.trim() ||\n !data.zipCode.trim()\n ) {\n alert('Please fill in all required fields (Address Line 1, City, State, ZIP Code).');\n return false;\n }\n return true;\n };\n\n // --- Handlers ---\n const handleAddAddress = () => {\n if (!validateForm(newAddress)) return;\n\n const addressToAdd: SavedAddress = {\n id: `addr-${Date.now()}`,\n type: newAddress.type,\n nickname: newAddress.nickname,\n addressLine1: newAddress.addressLine1,\n addressLine2: newAddress.addressLine2,\n city: newAddress.city,\n state: newAddress.state,\n zipCode: newAddress.zipCode,\n isDefault: newAddress.isDefault,\n usageCount: 0,\n };\n\n setAddresses((prev) => {\n let updated = [...prev];\n if (addressToAdd.isDefault) {\n updated = updated.map((a) => ({ ...a, isDefault: false }));\n }\n return [...updated, addressToAdd];\n });\n\n setNewAddress({ ...emptyForm });\n setShowAddForm(false);\n showStatus('Address added successfully');\n };\n\n const handleEditSave = () => {\n if (!editingAddress) return;\n if (!validateForm(editingAddress)) return;\n\n setAddresses((prev) =>\n prev.map((a) => {\n if (a.id === editingAddress.id) return editingAddress;\n if (editingAddress.isDefault) return { ...a, isDefault: false };\n return a;\n })\n );\n setEditingAddress(null);\n showStatus('Address updated successfully');\n };\n\n const handleDelete = (id: string) => {\n const addr = addresses.find((a) => a.id === id);\n if (addr?.isDefault) {\n alert('Cannot delete default address. Set another address as default first.');\n return;\n }\n setAddresses((prev) => prev.filter((a) => a.id !== id));\n showStatus('Address deleted successfully');\n };\n\n const handleSetDefault = (id: string) => {\n setAddresses((prev) => prev.map((a) => ({ ...a, isDefault: a.id === id })));\n showStatus('Default address updated');\n };\n\n const handleFormChange = (\n setter: React.Dispatch<React.SetStateAction<any>>,\n field: string,\n value: string | boolean\n ) => {\n setter((prev: any) => ({ ...prev, [field]: value }));\n };\n\n return (\n <PageLayout\n title=\"Saved Addresses\"\n description=\"Manage your delivery addresses for faster checkout\"\n actions={\n !showAddForm ? (\n <button\n type=\"button\"\n onClick={() => setShowAddForm(true)}\n className={cn(\n 'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-opacity',\n 'bg-foreground text-background hover:opacity-90'\n )}\n >\n <Plus className=\"h-4 w-4\" />\n Add New Address\n </button>\n ) : undefined\n }\n >\n <PagePurpose className=\"mb-6\">\n Save the places you order to most — home, work, a friend's flat — so checkout is one tap and\n your courier always knows exactly where to go. Set a default to make it the automatic pick\n at checkout.\n </PagePurpose>\n\n {/* Summary — the one emphasis zone */}\n {addresses.length > 0 && (\n <EmphasisPanel className=\"mb-6\">\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <MapPin className=\"h-4 w-4\" />\n <span>\n {addresses.length} saved {addresses.length === 1 ? 'address' : 'addresses'}\n {addresses.some((a) => a.isDefault)\n ? ` · default set to ${addresses.find((a) => a.isDefault)?.nickname ?? 'one address'}`\n : ' · no default set yet'}\n </span>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Status message */}\n {statusMessage && (\n <div className=\"mb-4 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle p-3 flex items-center gap-2\">\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text shrink-0\" />\n <p className=\"text-sm text-status-success-text\">{statusMessage}</p>\n </div>\n )}\n\n {/* Inline Add Form */}\n {showAddForm && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 mb-6\">\n <h3 className=\"text-base font-semibold text-text-primary mb-4\">Add New Address</h3>\n <AddressForm\n data={newAddress}\n onChange={(field, value) => handleFormChange(setNewAddress, field, value)}\n onSubmit={handleAddAddress}\n onCancel={() => {\n setShowAddForm(false);\n setNewAddress({ ...emptyForm });\n }}\n submitLabel=\"Save Address\"\n />\n </div>\n )}\n\n {/* Address List */}\n {addresses.length > 0 ? (\n <div className=\"space-y-3\">\n {addresses.map((address) => {\n const Icon = iconMap[address.type];\n return (\n <div\n key={address.id}\n className={cn(\n 'rounded-lg border bg-bg-surface p-4',\n address.isDefault ? 'border-foreground/30' : 'border-border-subtle'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n {/* Left: icon + details */}\n <div className=\"flex items-start gap-3 flex-1 min-w-0\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-bg-sunken\">\n <Icon className=\"h-5 w-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n {/* Badges row */}\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium capitalize',\n typeBadgeStyles[address.type]\n )}\n >\n {address.type}\n </span>\n {address.isDefault && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-success-text\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Default\n </span>\n )}\n </div>\n\n {/* Nickname */}\n {address.nickname && (\n <p className=\"mt-1 text-sm font-semibold text-text-primary\">\n {address.nickname}\n </p>\n )}\n\n {/* Address lines */}\n <p className=\"mt-0.5 text-sm text-text-primary\">{address.addressLine1}</p>\n {address.addressLine2 && (\n <p className=\"text-sm text-text-secondary\">{address.addressLine2}</p>\n )}\n <p className=\"text-sm text-text-secondary\">\n {address.city}, {address.state} - {address.zipCode}\n </p>\n </div>\n </div>\n\n {/* Right: actions */}\n <div className=\"flex items-center gap-1 shrink-0\">\n {!address.isDefault && (\n <button\n type=\"button\"\n onClick={() => handleSetDefault(address.id)}\n title=\"Set as default\"\n className=\"rounded-md p-2 text-text-secondary hover:bg-accent hover:text-text-primary transition-colors\"\n >\n <Star className=\"h-4 w-4\" />\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => setEditingAddress({ ...address })}\n title=\"Edit address\"\n className=\"rounded-md p-2 text-text-secondary hover:bg-accent hover:text-text-primary transition-colors\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleDelete(address.id)}\n title=\"Delete address\"\n className=\"rounded-md p-2 text-text-secondary hover:bg-status-error-bg/10 hover:text-status-error-text transition-colors\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-files\"\n icon={<MapPin className=\"h-12 w-12\" />}\n title=\"No saved addresses\"\n description=\"Add a delivery address to get started. Address book sync is coming soon — for now, entries are kept for this session.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddForm(true)}\n className=\"rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity\"\n >\n Add Address\n </button>\n }\n />\n )}\n\n {/* Edit Address Modal */}\n {editingAddress && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\">\n <div className=\"w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-lg border border-border-subtle bg-bg-surface shadow-lg\">\n <div className=\"p-5\">\n {/* Modal header */}\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Edit Address</h3>\n <button\n type=\"button\"\n onClick={() => setEditingAddress(null)}\n className=\"rounded-md p-2 text-text-secondary hover:bg-accent hover:text-text-primary transition-colors\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n\n <AddressForm\n data={editingAddress}\n onChange={(field, value) => handleFormChange(setEditingAddress, field, value)}\n onSubmit={handleEditSave}\n onCancel={() => setEditingAddress(null)}\n submitLabel=\"Save Changes\"\n />\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SavedAddressesPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/customer/SavedAddressesPage.tsx"],"sourcesContent":["/**\n * Saved Addresses Page\n * @module kadaikodi/pages/customer\n *\n * Manage delivery addresses: list, add (inline expandable), edit (modal),\n * delete (with default protection), set default, usage count, last used date,\n * address nickname support. Backed by real, actor-owned server persistence\n * (kadaikodiMyAddresses / kadaikodiAddAddress / kadaikodiUpdateAddress /\n * kadaikodiDeleteAddress / kadaikodiSetDefaultAddress) — no client-only state.\n */\n\nimport { useState } from 'react';\nimport {\n MapPin,\n Plus,\n Pencil,\n Trash2,\n CheckCircle2,\n Home,\n Briefcase,\n Star,\n X,\n Loader2,\n} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport {\n useMyAddresses,\n useAddAddress,\n useUpdateAddress,\n useDeleteAddress,\n useSetDefaultAddress,\n type KadaikodiCustomerAddressRow,\n} from '../../hooks/useKadaikodiApi';\nimport { cn } from '../../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype AddressType = 'HOME' | 'OFFICE' | 'OTHER';\n\ninterface AddressFormData {\n type: AddressType;\n nickname: string;\n addressLine1: string;\n addressLine2: string;\n city: string;\n state: string;\n zipCode: string;\n isDefault: boolean;\n}\n\nconst emptyForm: AddressFormData = {\n type: 'HOME',\n nickname: '',\n addressLine1: '',\n addressLine2: '',\n city: '',\n state: '',\n zipCode: '',\n isDefault: false,\n};\n\nconst toFormData = (address: KadaikodiCustomerAddressRow): AddressFormData & { id: string } => ({\n id: address.id,\n type: address.type,\n nickname: address.nickname ?? '',\n addressLine1: address.addressLine1,\n addressLine2: address.addressLine2 ?? '',\n city: address.city,\n state: address.state,\n zipCode: address.zipCode,\n isDefault: address.isDefault,\n});\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst iconMap: Record<AddressType, typeof Home> = {\n HOME: Home,\n OFFICE: Briefcase,\n OTHER: MapPin,\n};\n\nconst typeBadgeStyles: Record<AddressType, string> = {\n HOME: 'bg-status-success-bg-subtle text-status-success-text',\n OFFICE: 'bg-[var(--color-accent-soft)] text-text-primary',\n OTHER: 'bg-accent-purple-subtle text-text-primary',\n};\n\nconst typeLabel: Record<AddressType, string> = {\n HOME: 'Home',\n OFFICE: 'Office',\n OTHER: 'Other',\n};\n\n// ============================================================================\n// Address Form Component\n// ============================================================================\n\nfunction AddressForm({\n data,\n onChange,\n onSubmit,\n onCancel,\n submitLabel,\n}: {\n data: AddressFormData;\n onChange: (field: keyof AddressFormData, value: string | boolean) => void;\n onSubmit: () => void;\n onCancel: () => void;\n submitLabel: string;\n}) {\n return (\n <div className=\"space-y-4\">\n {/* Type + Nickname row */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n Address Type <span className=\"text-status-error-text\">*</span>\n </label>\n <select\n value={data.type}\n onChange={(e) => onChange('type', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"HOME\">Home</option>\n <option value=\"OFFICE\">Office</option>\n <option value=\"OTHER\">Other</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">Nickname</label>\n <input\n type=\"text\"\n placeholder=\"e.g., Home Sweet Home\"\n value={data.nickname}\n onChange={(e) => onChange('nickname', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Address Line 1 */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n Address Line 1 <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"House/Flat number, Street name\"\n value={data.addressLine1}\n onChange={(e) => onChange('addressLine1', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* Address Line 2 */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">Address Line 2</label>\n <input\n type=\"text\"\n placeholder=\"Area, Landmark\"\n value={data.addressLine2}\n onChange={(e) => onChange('addressLine2', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* City / State / ZIP */}\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n City <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"City\"\n value={data.city}\n onChange={(e) => onChange('city', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n State <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"State\"\n value={data.state}\n onChange={(e) => onChange('state', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n ZIP Code <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"560001\"\n value={data.zipCode}\n onChange={(e) => onChange('zipCode', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Set Default checkbox */}\n <label className=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={data.isDefault}\n onChange={(e) => onChange('isDefault', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle text-text-primary\"\n />\n <span className=\"text-sm text-text-secondary\">Set as default address</span>\n </label>\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onSubmit}\n className=\"rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity\"\n >\n {submitLabel}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-accent transition-colors\"\n >\n Cancel\n </button>\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\nexport function SavedAddressesPage() {\n const { data: addresses = [], isLoading } = useMyAddresses();\n const addAddressMutation = useAddAddress();\n const updateAddressMutation = useUpdateAddress();\n const deleteAddressMutation = useDeleteAddress();\n const setDefaultAddressMutation = useSetDefaultAddress();\n\n const [showAddForm, setShowAddForm] = useState(false);\n const [editingAddress, setEditingAddress] = useState<(AddressFormData & { id: string }) | null>(\n null\n );\n const [newAddress, setNewAddress] = useState<AddressFormData>({ ...emptyForm });\n const [statusMessage, setStatusMessage] = useState<string | null>(null);\n\n // --- Helpers ---\n const showStatus = (msg: string) => {\n setStatusMessage(msg);\n setTimeout(() => setStatusMessage(null), 3000);\n };\n\n const validateForm = (data: AddressFormData): boolean => {\n if (\n !data.addressLine1.trim() ||\n !data.city.trim() ||\n !data.state.trim() ||\n !data.zipCode.trim()\n ) {\n alert('Please fill in all required fields (Address Line 1, City, State, ZIP Code).');\n return false;\n }\n return true;\n };\n\n // --- Handlers ---\n const handleAddAddress = async () => {\n if (!validateForm(newAddress)) return;\n try {\n await addAddressMutation.mutateAsync({ ...newAddress });\n setNewAddress({ ...emptyForm });\n setShowAddForm(false);\n showStatus('Address added successfully');\n } catch {\n alert('Failed to save address. Please try again.');\n }\n };\n\n const handleEditSave = async () => {\n if (!editingAddress) return;\n if (!validateForm(editingAddress)) return;\n const { id, ...input } = editingAddress;\n try {\n await updateAddressMutation.mutateAsync({ id, input });\n setEditingAddress(null);\n showStatus('Address updated successfully');\n } catch {\n alert('Failed to update address. Please try again.');\n }\n };\n\n const handleDelete = async (id: string) => {\n const addr = addresses.find((a) => a.id === id);\n if (addr?.isDefault) {\n alert('Cannot delete default address. Set another address as default first.');\n return;\n }\n try {\n await deleteAddressMutation.mutateAsync(id);\n showStatus('Address deleted successfully');\n } catch {\n alert('Failed to delete address. Please try again.');\n }\n };\n\n const handleSetDefault = async (id: string) => {\n try {\n await setDefaultAddressMutation.mutateAsync(id);\n showStatus('Default address updated');\n } catch {\n alert('Failed to set default address. Please try again.');\n }\n };\n\n const handleNewAddressChange = (field: keyof AddressFormData, value: string | boolean) => {\n setNewAddress((prev) => ({ ...prev, [field]: value }));\n };\n\n const handleEditingAddressChange = (field: keyof AddressFormData, value: string | boolean) => {\n setEditingAddress((prev) => (prev ? { ...prev, [field]: value } : prev));\n };\n\n if (isLoading) {\n return (\n <PageLayout\n title=\"Saved Addresses\"\n description=\"Manage your delivery addresses for faster checkout\"\n >\n <div className=\"flex h-40 items-center justify-center\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Saved Addresses\"\n description=\"Manage your delivery addresses for faster checkout\"\n actions={\n !showAddForm ? (\n <button\n type=\"button\"\n onClick={() => setShowAddForm(true)}\n className={cn(\n 'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-opacity',\n 'bg-foreground text-background hover:opacity-90'\n )}\n >\n <Plus className=\"h-4 w-4\" />\n Add New Address\n </button>\n ) : undefined\n }\n >\n <PagePurpose className=\"mb-6\">\n Save the places you order to most — home, work, a friend's flat — so checkout is one tap and\n your courier always knows exactly where to go. Set a default to make it the automatic pick\n at checkout.\n </PagePurpose>\n\n {/* Summary — the one emphasis zone */}\n {addresses.length > 0 && (\n <EmphasisPanel className=\"mb-6\">\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <MapPin className=\"h-4 w-4\" />\n <span>\n {addresses.length} saved {addresses.length === 1 ? 'address' : 'addresses'}\n {addresses.some((a) => a.isDefault)\n ? ` · default set to ${addresses.find((a) => a.isDefault)?.nickname || 'one address'}`\n : ' · no default set yet'}\n </span>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Status message */}\n {statusMessage && (\n <div className=\"mb-4 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle p-3 flex items-center gap-2\">\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text shrink-0\" />\n <p className=\"text-sm text-status-success-text\">{statusMessage}</p>\n </div>\n )}\n\n {/* Inline Add Form */}\n {showAddForm && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 mb-6\">\n <h3 className=\"text-base font-semibold text-text-primary mb-4\">Add New Address</h3>\n <AddressForm\n data={newAddress}\n onChange={handleNewAddressChange}\n onSubmit={handleAddAddress}\n onCancel={() => {\n setShowAddForm(false);\n setNewAddress({ ...emptyForm });\n }}\n submitLabel=\"Save Address\"\n />\n </div>\n )}\n\n {/* Address List */}\n {addresses.length > 0 ? (\n <div className=\"space-y-3\">\n {addresses.map((address) => {\n const Icon = iconMap[address.type];\n return (\n <div\n key={address.id}\n className={cn(\n 'rounded-lg border bg-bg-surface p-4',\n address.isDefault ? 'border-foreground/30' : 'border-border-subtle'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n {/* Left: icon + details */}\n <div className=\"flex items-start gap-3 flex-1 min-w-0\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-bg-sunken\">\n <Icon className=\"h-5 w-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n {/* Badges row */}\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium',\n typeBadgeStyles[address.type]\n )}\n >\n {typeLabel[address.type]}\n </span>\n {address.isDefault && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-success-text\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Default\n </span>\n )}\n </div>\n\n {/* Nickname */}\n {address.nickname && (\n <p className=\"mt-1 text-sm font-semibold text-text-primary\">\n {address.nickname}\n </p>\n )}\n\n {/* Address lines */}\n <p className=\"mt-0.5 text-sm text-text-primary\">{address.addressLine1}</p>\n {address.addressLine2 && (\n <p className=\"text-sm text-text-secondary\">{address.addressLine2}</p>\n )}\n <p className=\"text-sm text-text-secondary\">\n {address.city}, {address.state} - {address.zipCode}\n </p>\n </div>\n </div>\n\n {/* Right: actions */}\n <div className=\"flex items-center gap-1 shrink-0\">\n {!address.isDefault && (\n <button\n type=\"button\"\n onClick={() => handleSetDefault(address.id)}\n title=\"Set as default\"\n className=\"rounded-md p-2 text-text-secondary hover:bg-accent hover:text-text-primary transition-colors\"\n >\n <Star className=\"h-4 w-4\" />\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => setEditingAddress(toFormData(address))}\n title=\"Edit address\"\n className=\"rounded-md p-2 text-text-secondary hover:bg-accent hover:text-text-primary transition-colors\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleDelete(address.id)}\n title=\"Delete address\"\n className=\"rounded-md p-2 text-text-secondary hover:bg-status-error-bg/10 hover:text-status-error-text transition-colors\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-files\"\n icon={<MapPin className=\"h-12 w-12\" />}\n title=\"No saved addresses\"\n description=\"Add a delivery address to get started — it's saved to your account for faster checkout next time.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddForm(true)}\n className=\"rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity\"\n >\n Add Address\n </button>\n }\n />\n )}\n\n {/* Edit Address Modal */}\n {editingAddress && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\">\n <div className=\"w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-lg border border-border-subtle bg-bg-surface shadow-lg\">\n <div className=\"p-5\">\n {/* Modal header */}\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Edit Address</h3>\n <button\n type=\"button\"\n onClick={() => setEditingAddress(null)}\n className=\"rounded-md p-2 text-text-secondary hover:bg-accent hover:text-text-primary transition-colors\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n\n <AddressForm\n data={editingAddress}\n onChange={handleEditingAddressChange}\n onSubmit={handleEditSave}\n onCancel={() => setEditingAddress(null)}\n submitLabel=\"Save Changes\"\n />\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;AAsDA,IAAM,IAA6B;CACjC,MAAM;CACN,UAAU;CACV,cAAc;CACd,cAAc;CACd,MAAM;CACN,OAAO;CACP,SAAS;CACT,WAAW;CACZ,EAEK,KAAc,OAA4E;CAC9F,IAAI,EAAQ;CACZ,MAAM,EAAQ;CACd,UAAU,EAAQ,YAAY;CAC9B,cAAc,EAAQ;CACtB,cAAc,EAAQ,gBAAgB;CACtC,MAAM,EAAQ;CACd,OAAO,EAAQ;CACf,SAAS,EAAQ;CACjB,WAAW,EAAQ;CACpB,GAMK,IAA4C;CAChD,MAAM;CACN,QAAQ;CACR,OAAO;CACR,EAEK,IAA+C;CACnD,MAAM;CACN,QAAQ;CACR,OAAO;CACR,EAEK,IAAyC;CAC7C,MAAM;CACN,QAAQ;CACR,OAAO;CACR;AAMD,SAAS,EAAY,EACnB,SACA,aACA,aACA,aACA,kBAOC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,WAAU;eAAjB,CAAsE,iBACvD,kBAAC,QAAD;MAAM,WAAU;gBAAyB;MAAQ,CAAA,CACxD;QACR,kBAAC,UAAD;KACE,OAAO,EAAK;KACZ,WAAW,MAAM,EAAS,QAAQ,EAAE,OAAO,MAAM;KACjD,WAAU;eAHZ;MAKE,kBAAC,UAAD;OAAQ,OAAM;iBAAO;OAAa,CAAA;MAClC,kBAAC,UAAD;OAAQ,OAAM;iBAAS;OAAe,CAAA;MACtC,kBAAC,UAAD;OAAQ,OAAM;iBAAQ;OAAc,CAAA;MAC7B;OACL,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,WAAU;eAAqD;KAAgB,CAAA,EACtF,kBAAC,SAAD;KACE,MAAK;KACL,aAAY;KACZ,OAAO,EAAK;KACZ,WAAW,MAAM,EAAS,YAAY,EAAE,OAAO,MAAM;KACrD,WAAU;KACV,CAAA,CACE,EAAA,CAAA,CACF;;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,WAAU;cAAjB,CAAsE,mBACrD,kBAAC,QAAD;KAAM,WAAU;eAAyB;KAAQ,CAAA,CAC1D;OACR,kBAAC,SAAD;IACE,MAAK;IACL,aAAY;IACZ,OAAO,EAAK;IACZ,WAAW,MAAM,EAAS,gBAAgB,EAAE,OAAO,MAAM;IACzD,WAAU;IACV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,WAAU;cAAqD;IAAsB,CAAA,EAC5F,kBAAC,SAAD;IACE,MAAK;IACL,aAAY;IACZ,OAAO,EAAK;IACZ,WAAW,MAAM,EAAS,gBAAgB,EAAE,OAAO,MAAM;IACzD,WAAU;IACV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CAAsE,SAC/D,kBAAC,QAAD;OAAM,WAAU;iBAAyB;OAAQ,CAAA,CAChD;SACR,kBAAC,SAAD;MACE,MAAK;MACL,aAAY;MACZ,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,QAAQ,EAAE,OAAO,MAAM;MACjD,WAAU;MACV,CAAA,CACE,EAAA,CAAA;KACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CAAsE,UAC9D,kBAAC,QAAD;OAAM,WAAU;iBAAyB;OAAQ,CAAA,CACjD;SACR,kBAAC,SAAD;MACE,MAAK;MACL,aAAY;MACZ,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,SAAS,EAAE,OAAO,MAAM;MAClD,WAAU;MACV,CAAA,CACE,EAAA,CAAA;KACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CAAsE,aAC3D,kBAAC,QAAD;OAAM,WAAU;iBAAyB;OAAQ,CAAA,CACpD;SACR,kBAAC,SAAD;MACE,MAAK;MACL,aAAY;MACZ,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,WAAW,EAAE,OAAO,MAAM;MACpD,WAAU;MACV,CAAA,CACE,EAAA,CAAA;KACF;;GAGN,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,SAAD;KACE,MAAK;KACL,SAAS,EAAK;KACd,WAAW,MAAM,EAAS,aAAa,EAAE,OAAO,QAAQ;KACxD,WAAU;KACV,CAAA,EACF,kBAAC,QAAD;KAAM,WAAU;eAA8B;KAA6B,CAAA,CACrE;;GAGR,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET;KACM,CAAA,EACT,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eACX;KAEQ,CAAA,CACL;;GACF;;;AAQV,SAAgB,IAAqB;CACnC,IAAM,EAAE,MAAM,IAAY,EAAE,EAAE,iBAAc,GAAgB,EACtD,IAAqB,GAAe,EACpC,IAAwB,GAAkB,EAC1C,IAAwB,GAAkB,EAC1C,IAA4B,GAAsB,EAElD,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAgB,KAAqB,EAC1C,KACD,EACK,CAAC,GAAY,KAAiB,EAA0B,EAAE,GAAG,GAAW,CAAC,EACzE,CAAC,GAAe,KAAoB,EAAwB,KAAK,EAGjE,KAAc,MAAgB;AAElC,EADA,EAAiB,EAAI,EACrB,iBAAiB,EAAiB,KAAK,EAAE,IAAK;IAG1C,KAAgB,MAElB,CAAC,EAAK,aAAa,MAAM,IACzB,CAAC,EAAK,KAAK,MAAM,IACjB,CAAC,EAAK,MAAM,MAAM,IAClB,CAAC,EAAK,QAAQ,MAAM,IAEpB,MAAM,8EAA8E,EAC7E,MAEF,IAIH,IAAmB,YAAY;AAC9B,QAAa,EAAW,CAC7B,KAAI;AAIF,GAHA,MAAM,EAAmB,YAAY,EAAE,GAAG,GAAY,CAAC,EACvD,EAAc,EAAE,GAAG,GAAW,CAAC,EAC/B,EAAe,GAAM,EACrB,EAAW,6BAA6B;UAClC;AACN,SAAM,4CAA4C;;IAIhD,IAAiB,YAAY;AAEjC,MADI,CAAC,KACD,CAAC,EAAa,EAAe,CAAE;EACnC,IAAM,EAAE,OAAI,GAAG,MAAU;AACzB,MAAI;AAGF,GAFA,MAAM,EAAsB,YAAY;IAAE;IAAI;IAAO,CAAC,EACtD,EAAkB,KAAK,EACvB,EAAW,+BAA+B;UACpC;AACN,SAAM,8CAA8C;;IAIlD,IAAe,OAAO,MAAe;AAEzC,MADa,EAAU,MAAM,MAAM,EAAE,OAAO,EAAG,EACrC,WAAW;AACnB,SAAM,uEAAuE;AAC7E;;AAEF,MAAI;AAEF,GADA,MAAM,EAAsB,YAAY,EAAG,EAC3C,EAAW,+BAA+B;UACpC;AACN,SAAM,8CAA8C;;IAIlD,IAAmB,OAAO,MAAe;AAC7C,MAAI;AAEF,GADA,MAAM,EAA0B,YAAY,EAAG,EAC/C,EAAW,0BAA0B;UAC/B;AACN,SAAM,mDAAmD;;;AAyB7D,QAbI,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA;GAC5D,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACG,IAYG,KAAA,IAXF,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAe,GAAK;GACnC,WAAW,EACT,uFACA,iDACD;aANH,CAQE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBAErB;;YAff;GAmBE,kBAAC,GAAD;IAAa,WAAU;cAAO;IAIhB,CAAA;GAGb,EAAU,SAAS,KAClB,kBAAC,GAAD;IAAe,WAAU;cACvB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC9B,kBAAC,QAAD,EAAA,UAAA;MACG,EAAU;MAAO;MAAQ,EAAU,WAAW,IAAI,YAAY;MAC9D,EAAU,MAAM,MAAM,EAAE,UAAU,GAC/B,qBAAqB,EAAU,MAAM,MAAM,EAAE,UAAU,EAAE,YAAY,kBACrE;MACC,EAAA,CAAA,CACH;;IACQ,CAAA;GAIjB,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAc,WAAU,6CAA8C,CAAA,EACtE,kBAAC,KAAD;KAAG,WAAU;eAAoC;KAAkB,CAAA,CAC/D;;GAIP,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAiD;KAAoB,CAAA,EACnF,kBAAC,GAAD;KACE,MAAM;KACN,WA5EsB,GAA8B,MAA4B;AACxF,SAAe,OAAU;OAAE,GAAG;QAAO,IAAQ;OAAO,EAAE;;KA4E9C,UAAU;KACV,gBAAgB;AAEd,MADA,EAAe,GAAM,EACrB,EAAc,EAAE,GAAG,GAAW,CAAC;;KAEjC,aAAY;KACZ,CAAA,CACE;;GAIP,EAAU,SAAS,IAClB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAU,KAAK,MAAY;KAC1B,IAAM,IAAO,EAAQ,EAAQ;AAC7B,YACE,kBAAC,OAAD;MAEE,WAAW,EACT,uCACA,EAAQ,YAAY,yBAAyB,uBAC9C;gBAED,kBAAC,OAAD;OAAK,WAAU;iBAAf,CAEE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAM,WAAU,+BAAgC,CAAA;SAC5C,CAAA,EACN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UAEE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YACE,WAAW,EACT,6EACA,EAAgB,EAAQ,MACzB;sBAEA,EAAU,EAAQ;YACd,CAAA,EACN,EAAQ,aACP,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CACE,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,EAAA,UAE/B;cAEL;;UAGL,EAAQ,YACP,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAQ;WACP,CAAA;UAIN,kBAAC,KAAD;WAAG,WAAU;qBAAoC,EAAQ;WAAiB,CAAA;UACzE,EAAQ,gBACP,kBAAC,KAAD;WAAG,WAAU;qBAA+B,EAAQ;WAAiB,CAAA;UAEvE,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAQ;YAAK;YAAG,EAAQ;YAAM;YAAI,EAAQ;YACzC;;UACA;WACF;WAGN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACG,CAAC,EAAQ,aACR,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAiB,EAAQ,GAAG;UAC3C,OAAM;UACN,WAAU;oBAEV,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;UACrB,CAAA;SAEX,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAkB,EAAW,EAAQ,CAAC;UACrD,OAAM;UACN,WAAU;oBAEV,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;UACvB,CAAA;SACT,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAa,EAAQ,GAAG;UACvC,OAAM;UACN,WAAU;oBAEV,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;UACvB,CAAA;SACL;UACF;;MACF,EA/EC,EAAQ,GA+ET;MAER;IACE,CAAA,GAEN,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,aAAc,CAAA;IACtC,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAe,GAAK;KACnC,WAAU;eACX;KAEQ,CAAA;IAEX,CAAA;GAIH,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CAEE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAiB,CAAA,EACzE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAkB,KAAK;QACtC,WAAU;kBAEV,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;QAClB,CAAA,CACL;UAEN,kBAAC,GAAD;OACE,MAAM;OACN,WAlNsB,GAA8B,MAA4B;AAC5F,WAAmB,MAAU,KAAO;SAAE,GAAG;UAAO,IAAQ;SAAO,CAAS;;OAkN5D,UAAU;OACV,gBAAgB,EAAkB,KAAK;OACvC,aAAY;OACZ,CAAA,CACE;;KACF,CAAA;IACF,CAAA;GAEG"}
|
|
@@ -16,13 +16,12 @@ interface ImageWithFallbackProps {
|
|
|
16
16
|
export declare function ImageWithFallback({ src, alt, seed, className, icon }: ImageWithFallbackProps): import("react").JSX.Element;
|
|
17
17
|
/** Convenience wrapper for provider/cart imagery. */
|
|
18
18
|
export declare function ProviderImage(props: Omit<ImageWithFallbackProps, 'icon'>): import("react").JSX.Element;
|
|
19
|
-
/** Toggle a provider id in the favorites store and notify subscribers. */
|
|
20
|
-
export declare function toggleFavorite(id: string): void;
|
|
21
19
|
/** Reactive access to the customer's saved-provider ids. */
|
|
22
20
|
export declare function useFavorites(): {
|
|
23
21
|
ids: string[];
|
|
24
22
|
has: (id: string) => boolean;
|
|
25
23
|
toggle: (id: string) => void;
|
|
24
|
+
isLoading: boolean;
|
|
26
25
|
};
|
|
27
26
|
interface StylizedRouteMapProps {
|
|
28
27
|
/** 0–1 progress of the courier along the illustrative route. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/kadaikodi/pages/customer/shared.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/kadaikodi/pages/customer/shared.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS9D,UAAU,sBAAsB;IAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,sBAAsB,+BAgC5F;AAED,qDAAqD;AACrD,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC,+BAExE;AAOD,4DAA4D;AAC5D,wBAAgB,YAAY,IAAI;IAC9B,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7B,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;CACpB,CAWA;AAMD,UAAU,qBAAqB;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAc,EACd,WAAoB,EACpB,gBAAwB,EACxB,SAAS,GACV,EAAE,qBAAqB,+BAgFvB"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import {
|
|
2
|
+
import { useMyFavoriteProviderIds as t, useToggleFavorite as n } from "../../hooks/useKadaikodiApi.js";
|
|
3
|
+
import { useCallback as r, useState as i } from "react";
|
|
3
4
|
import { Home as a, MapPin as o, Navigation as s, Store as c } from "lucide-react";
|
|
4
5
|
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
5
6
|
//#region src/kadaikodi/pages/customer/shared.tsx
|
|
6
|
-
function d({ src: t, alt: n, seed:
|
|
7
|
-
let [s, d] =
|
|
7
|
+
function d({ src: t, alt: n, seed: r, className: a, icon: o }) {
|
|
8
|
+
let [s, d] = i(!1);
|
|
8
9
|
if (t && !s) return /* @__PURE__ */ l("img", {
|
|
9
10
|
src: t,
|
|
10
11
|
alt: n,
|
|
@@ -12,7 +13,7 @@ function d({ src: t, alt: n, seed: i, className: a, icon: o }) {
|
|
|
12
13
|
onError: () => d(!0),
|
|
13
14
|
className: e("h-full w-full object-cover", a)
|
|
14
15
|
});
|
|
15
|
-
let f = (
|
|
16
|
+
let f = (r ?? n).trim().charAt(0).toUpperCase() || "?";
|
|
16
17
|
return /* @__PURE__ */ l("div", {
|
|
17
18
|
"aria-label": n,
|
|
18
19
|
role: "img",
|
|
@@ -38,41 +39,18 @@ function f(e) {
|
|
|
38
39
|
})
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return typeof localStorage > "u" ? "[]" : localStorage.getItem(p) ?? "[]";
|
|
44
|
-
}
|
|
45
|
-
function g(e) {
|
|
46
|
-
try {
|
|
47
|
-
let t = JSON.parse(e);
|
|
48
|
-
return Array.isArray(t) ? t.filter((e) => typeof e == "string") : [];
|
|
49
|
-
} catch {
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function _(e) {
|
|
54
|
-
if (typeof localStorage > "u") return;
|
|
55
|
-
let t = g(h()), n = t.includes(e) ? t.filter((t) => t !== e) : [...t, e];
|
|
56
|
-
localStorage.setItem(p, JSON.stringify(n)), m.forEach((e) => e());
|
|
57
|
-
}
|
|
58
|
-
var v = (e) => {
|
|
59
|
-
m.add(e);
|
|
60
|
-
let t = (t) => {
|
|
61
|
-
t.key === p && e();
|
|
62
|
-
};
|
|
63
|
-
return typeof window < "u" && window.addEventListener("storage", t), () => {
|
|
64
|
-
m.delete(e), typeof window < "u" && window.removeEventListener("storage", t);
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
function y() {
|
|
68
|
-
let e = i(v, h, () => "[]"), r = n(() => g(e), [e]);
|
|
42
|
+
function p() {
|
|
43
|
+
let { data: e = [], isLoading: i } = t(), a = n();
|
|
69
44
|
return {
|
|
70
|
-
ids:
|
|
71
|
-
has:
|
|
72
|
-
toggle:
|
|
45
|
+
ids: e,
|
|
46
|
+
has: r((t) => e.includes(t), [e]),
|
|
47
|
+
toggle: r((e) => {
|
|
48
|
+
a.mutate(e);
|
|
49
|
+
}, [a]),
|
|
50
|
+
isLoading: i
|
|
73
51
|
};
|
|
74
52
|
}
|
|
75
|
-
function
|
|
53
|
+
function m({ progress: t = .5, originLabel: n = "Cart", destinationLabel: r = "You", className: i }) {
|
|
76
54
|
let c = Math.min(1, Math.max(0, t)), d = {
|
|
77
55
|
x: 60,
|
|
78
56
|
y: 168
|
|
@@ -176,6 +154,6 @@ function b({ progress: t = .5, originLabel: n = "Cart", destinationLabel: r = "Y
|
|
|
176
154
|
});
|
|
177
155
|
}
|
|
178
156
|
//#endregion
|
|
179
|
-
export { f as ProviderImage,
|
|
157
|
+
export { f as ProviderImage, m as StylizedRouteMap, p as useFavorites };
|
|
180
158
|
|
|
181
159
|
//# sourceMappingURL=shared.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","names":[],"sources":["../../../../src/kadaikodi/pages/customer/shared.tsx"],"sourcesContent":["/**\n * Shared presentational helpers for the customer pages.\n * @module kadaikodi/pages/customer\n *\n * - `ProviderImage` / `OfferingImage`: render a real media image when the\n * backend supplies one, otherwise a tokenized, themeable placeholder (never a\n * broken <img> and never a fabricated stock photo).\n * - `useFavorites`: a real, user-owned favorites store persisted in\n * localStorage and shared reactively across the customer pages (Discover adds,\n * Favorites reads). This is genuine user data — nothing is fabricated.\n * - `StylizedRouteMap`: an honest, clearly-labelled illustrative route panel for\n * order tracking (there is no live-GPS backend, so we never fake live coords).\n */\n\nimport { useCallback, useMemo, useState, useSyncExternalStore, type ReactNode } from 'react';\nimport { Store, MapPin, Navigation, Home } from 'lucide-react';\nimport { cn } from '../../utils/cn';\n\n// ============================================================================\n// Media with graceful, tokenized fallback\n// ============================================================================\n\ninterface ImageWithFallbackProps {\n src?: string | null;\n alt: string;\n /** Seed text for the placeholder initial (usually the name). */\n seed?: string;\n className?: string;\n /** Icon shown in the placeholder (defaults to a storefront). */\n icon?: ReactNode;\n}\n\n/**\n * Renders `src` when present and loadable; otherwise a tokenized accent-gradient\n * placeholder with the seed's initial. Fills its positioned parent.\n */\nexport function ImageWithFallback({ src, alt, seed, className, icon }: ImageWithFallbackProps) {\n const [failed, setFailed] = useState(false);\n const showImage = Boolean(src) && !failed;\n\n if (showImage) {\n return (\n <img\n src={src as string}\n alt={alt}\n loading=\"lazy\"\n onError={() => setFailed(true)}\n className={cn('h-full w-full object-cover', className)}\n />\n );\n }\n\n const initial = (seed ?? alt).trim().charAt(0).toUpperCase() || '?';\n return (\n <div\n aria-label={alt}\n role=\"img\"\n className={cn(\n 'flex h-full w-full items-center justify-center bg-gradient-to-br from-accent-primary/25 via-accent-primary/10 to-bg-sunken',\n className\n )}\n >\n <div className=\"flex flex-col items-center gap-1 text-accent-primary/70\">\n {icon ?? <Store className=\"h-6 w-6\" aria-hidden=\"true\" />}\n <span className=\"text-lg font-bold leading-none\">{initial}</span>\n </div>\n </div>\n );\n}\n\n/** Convenience wrapper for provider/cart imagery. */\nexport function ProviderImage(props: Omit<ImageWithFallbackProps, 'icon'>) {\n return <ImageWithFallback {...props} icon={<Store className=\"h-6 w-6\" aria-hidden=\"true\" />} />;\n}\n\n// ============================================================================\n// Favorites — real localStorage-backed store, reactive across pages\n// ============================================================================\n\nconst FAVORITES_KEY = 'kadaikodi:favorites:providers';\nconst listeners = new Set<() => void>();\n\nfunction readRaw(): string {\n if (typeof localStorage === 'undefined') return '[]';\n return localStorage.getItem(FAVORITES_KEY) ?? '[]';\n}\n\nfunction parse(raw: string): string[] {\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) ? parsed.filter((x): x is string => typeof x === 'string') : [];\n } catch {\n return [];\n }\n}\n\n/** Toggle a provider id in the favorites store and notify subscribers. */\nexport function toggleFavorite(id: string): void {\n if (typeof localStorage === 'undefined') return;\n const current = parse(readRaw());\n const next = current.includes(id) ? current.filter((x) => x !== id) : [...current, id];\n localStorage.setItem(FAVORITES_KEY, JSON.stringify(next));\n listeners.forEach((l) => l());\n}\n\nconst subscribe = (cb: () => void): (() => void) => {\n listeners.add(cb);\n const onStorage = (e: StorageEvent): void => {\n if (e.key === FAVORITES_KEY) cb();\n };\n if (typeof window !== 'undefined') window.addEventListener('storage', onStorage);\n return () => {\n listeners.delete(cb);\n if (typeof window !== 'undefined') window.removeEventListener('storage', onStorage);\n };\n};\n\n/** Reactive access to the customer's saved-provider ids. */\nexport function useFavorites(): {\n ids: string[];\n has: (id: string) => boolean;\n toggle: (id: string) => void;\n} {\n const raw = useSyncExternalStore(subscribe, readRaw, () => '[]');\n const ids = useMemo(() => parse(raw), [raw]);\n const has = useCallback((id: string) => ids.includes(id), [ids]);\n return { ids, has, toggle: toggleFavorite };\n}\n\n// ============================================================================\n// Stylised route map (honest illustration — no live GPS backend)\n// ============================================================================\n\ninterface StylizedRouteMapProps {\n /** 0–1 progress of the courier along the illustrative route. */\n progress?: number;\n originLabel?: string;\n destinationLabel?: string;\n className?: string;\n}\n\n/**\n * A tokenized, animated-dashed SVG \"route\" between the cart and the delivery\n * address. It is explicitly labelled as illustrative — Kadaikodi has no live-GPS\n * feed, so this never pretends to show a real courier position.\n */\nexport function StylizedRouteMap({\n progress = 0.5,\n originLabel = 'Cart',\n destinationLabel = 'You',\n className,\n}: StylizedRouteMapProps) {\n const clamped = Math.min(1, Math.max(0, progress));\n // Path points in the 0..400 x 0..220 viewBox.\n const start = { x: 60, y: 168 };\n const end = { x: 340, y: 56 };\n const c1 = { x: 150, y: 60 };\n const c2 = { x: 250, y: 200 };\n const t = clamped;\n const mt = 1 - t;\n // Cubic bezier point at t for the courier marker.\n const cx =\n mt * mt * mt * start.x + 3 * mt * mt * t * c1.x + 3 * mt * t * t * c2.x + t * t * t * end.x;\n const cy =\n mt * mt * mt * start.y + 3 * mt * mt * t * c1.y + 3 * mt * t * t * c2.y + t * t * t * end.y;\n\n return (\n <div\n className={cn(\n 'relative overflow-hidden rounded-xl border border-border-subtle bg-bg-sunken',\n className\n )}\n >\n <svg\n viewBox=\"0 0 400 220\"\n className=\"h-full w-full\"\n role=\"img\"\n aria-label=\"Illustrative delivery route\"\n >\n {/* Faint \"streets\" grid */}\n <g stroke=\"var(--color-border-subtle)\" strokeWidth=\"1\" opacity=\"0.6\">\n <path d=\"M0 40 H400 M0 90 H400 M0 140 H400 M0 190 H400\" />\n <path d=\"M70 0 V220 M150 0 V220 M230 0 V220 M310 0 V220\" />\n </g>\n {/* Route */}\n <path\n d={`M${start.x} ${start.y} C ${c1.x} ${c1.y}, ${c2.x} ${c2.y}, ${end.x} ${end.y}`}\n fill=\"none\"\n stroke=\"var(--color-accent-primary)\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeDasharray=\"2 8\"\n opacity=\"0.9\"\n />\n {/* Origin (cart) */}\n <circle\n cx={start.x}\n cy={start.y}\n r=\"16\"\n fill=\"var(--color-accent-primary)\"\n opacity=\"0.12\"\n />\n <circle cx={start.x} cy={start.y} r=\"6\" fill=\"var(--color-accent-primary)\" />\n {/* Destination (home) */}\n <circle cx={end.x} cy={end.y} r=\"9\" fill=\"var(--color-status-success-text)\" />\n {/* Courier marker */}\n <circle cx={cx} cy={cy} r=\"12\" fill=\"var(--color-accent-primary)\" opacity=\"0.18\">\n <animate attributeName=\"r\" values=\"10;16;10\" dur=\"2.4s\" repeatCount=\"indefinite\" />\n </circle>\n <circle\n cx={cx}\n cy={cy}\n r=\"6\"\n fill=\"var(--color-accent-primary)\"\n stroke=\"var(--color-bg-surface)\"\n strokeWidth=\"2\"\n />\n </svg>\n\n {/* Corner labels */}\n <span className=\"absolute bottom-2 left-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm\">\n <MapPin className=\"h-3 w-3 text-accent-primary\" /> {originLabel}\n </span>\n <span className=\"absolute right-2 top-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm\">\n <Home className=\"h-3 w-3 text-status-success-text\" /> {destinationLabel}\n </span>\n <span className=\"absolute right-2 bottom-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/70 px-2 py-0.5 text-[10px] text-text-muted backdrop-blur-sm\">\n <Navigation className=\"h-3 w-3\" /> Illustrative route\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shared.js","names":[],"sources":["../../../../src/kadaikodi/pages/customer/shared.tsx"],"sourcesContent":["/**\n * Shared presentational helpers for the customer pages.\n * @module kadaikodi/pages/customer\n *\n * - `ProviderImage` / `OfferingImage`: render a real media image when the\n * backend supplies one, otherwise a tokenized, themeable placeholder (never a\n * broken <img> and never a fabricated stock photo).\n * - `useFavorites`: a real, user-owned favorites store persisted server-side\n * (self-scoped `kadaikodiMyFavoriteProviderIds` / `kadaikodiToggleFavorite`)\n * and shared reactively across the customer pages via the React Query cache\n * (Discover adds, Favorites reads). This is genuine user data — nothing is\n * fabricated.\n * - `StylizedRouteMap`: an honest, clearly-labelled illustrative route panel for\n * order tracking (there is no live-GPS backend, so we never fake live coords).\n */\n\nimport { useCallback, useState, type ReactNode } from 'react';\nimport { Store, MapPin, Navigation, Home } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { useMyFavoriteProviderIds, useToggleFavorite } from '../../hooks/useKadaikodiApi';\n\n// ============================================================================\n// Media with graceful, tokenized fallback\n// ============================================================================\n\ninterface ImageWithFallbackProps {\n src?: string | null;\n alt: string;\n /** Seed text for the placeholder initial (usually the name). */\n seed?: string;\n className?: string;\n /** Icon shown in the placeholder (defaults to a storefront). */\n icon?: ReactNode;\n}\n\n/**\n * Renders `src` when present and loadable; otherwise a tokenized accent-gradient\n * placeholder with the seed's initial. Fills its positioned parent.\n */\nexport function ImageWithFallback({ src, alt, seed, className, icon }: ImageWithFallbackProps) {\n const [failed, setFailed] = useState(false);\n const showImage = Boolean(src) && !failed;\n\n if (showImage) {\n return (\n <img\n src={src as string}\n alt={alt}\n loading=\"lazy\"\n onError={() => setFailed(true)}\n className={cn('h-full w-full object-cover', className)}\n />\n );\n }\n\n const initial = (seed ?? alt).trim().charAt(0).toUpperCase() || '?';\n return (\n <div\n aria-label={alt}\n role=\"img\"\n className={cn(\n 'flex h-full w-full items-center justify-center bg-gradient-to-br from-accent-primary/25 via-accent-primary/10 to-bg-sunken',\n className\n )}\n >\n <div className=\"flex flex-col items-center gap-1 text-accent-primary/70\">\n {icon ?? <Store className=\"h-6 w-6\" aria-hidden=\"true\" />}\n <span className=\"text-lg font-bold leading-none\">{initial}</span>\n </div>\n </div>\n );\n}\n\n/** Convenience wrapper for provider/cart imagery. */\nexport function ProviderImage(props: Omit<ImageWithFallbackProps, 'icon'>) {\n return <ImageWithFallback {...props} icon={<Store className=\"h-6 w-6\" aria-hidden=\"true\" />} />;\n}\n\n// ============================================================================\n// Favorites — server-persisted (self-scoped), reactive across pages via\n// the shared React Query cache key ['kadaikodi', 'my-favorite-provider-ids']\n// ============================================================================\n\n/** Reactive access to the customer's saved-provider ids. */\nexport function useFavorites(): {\n ids: string[];\n has: (id: string) => boolean;\n toggle: (id: string) => void;\n isLoading: boolean;\n} {\n const { data: ids = [], isLoading } = useMyFavoriteProviderIds();\n const toggleMutation = useToggleFavorite();\n const has = useCallback((id: string) => ids.includes(id), [ids]);\n const toggle = useCallback(\n (id: string) => {\n toggleMutation.mutate(id);\n },\n [toggleMutation]\n );\n return { ids, has, toggle, isLoading };\n}\n\n// ============================================================================\n// Stylised route map (honest illustration — no live GPS backend)\n// ============================================================================\n\ninterface StylizedRouteMapProps {\n /** 0–1 progress of the courier along the illustrative route. */\n progress?: number;\n originLabel?: string;\n destinationLabel?: string;\n className?: string;\n}\n\n/**\n * A tokenized, animated-dashed SVG \"route\" between the cart and the delivery\n * address. It is explicitly labelled as illustrative — Kadaikodi has no live-GPS\n * feed, so this never pretends to show a real courier position.\n */\nexport function StylizedRouteMap({\n progress = 0.5,\n originLabel = 'Cart',\n destinationLabel = 'You',\n className,\n}: StylizedRouteMapProps) {\n const clamped = Math.min(1, Math.max(0, progress));\n // Path points in the 0..400 x 0..220 viewBox.\n const start = { x: 60, y: 168 };\n const end = { x: 340, y: 56 };\n const c1 = { x: 150, y: 60 };\n const c2 = { x: 250, y: 200 };\n const t = clamped;\n const mt = 1 - t;\n // Cubic bezier point at t for the courier marker.\n const cx =\n mt * mt * mt * start.x + 3 * mt * mt * t * c1.x + 3 * mt * t * t * c2.x + t * t * t * end.x;\n const cy =\n mt * mt * mt * start.y + 3 * mt * mt * t * c1.y + 3 * mt * t * t * c2.y + t * t * t * end.y;\n\n return (\n <div\n className={cn(\n 'relative overflow-hidden rounded-xl border border-border-subtle bg-bg-sunken',\n className\n )}\n >\n <svg\n viewBox=\"0 0 400 220\"\n className=\"h-full w-full\"\n role=\"img\"\n aria-label=\"Illustrative delivery route\"\n >\n {/* Faint \"streets\" grid */}\n <g stroke=\"var(--color-border-subtle)\" strokeWidth=\"1\" opacity=\"0.6\">\n <path d=\"M0 40 H400 M0 90 H400 M0 140 H400 M0 190 H400\" />\n <path d=\"M70 0 V220 M150 0 V220 M230 0 V220 M310 0 V220\" />\n </g>\n {/* Route */}\n <path\n d={`M${start.x} ${start.y} C ${c1.x} ${c1.y}, ${c2.x} ${c2.y}, ${end.x} ${end.y}`}\n fill=\"none\"\n stroke=\"var(--color-accent-primary)\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeDasharray=\"2 8\"\n opacity=\"0.9\"\n />\n {/* Origin (cart) */}\n <circle\n cx={start.x}\n cy={start.y}\n r=\"16\"\n fill=\"var(--color-accent-primary)\"\n opacity=\"0.12\"\n />\n <circle cx={start.x} cy={start.y} r=\"6\" fill=\"var(--color-accent-primary)\" />\n {/* Destination (home) */}\n <circle cx={end.x} cy={end.y} r=\"9\" fill=\"var(--color-status-success-text)\" />\n {/* Courier marker */}\n <circle cx={cx} cy={cy} r=\"12\" fill=\"var(--color-accent-primary)\" opacity=\"0.18\">\n <animate attributeName=\"r\" values=\"10;16;10\" dur=\"2.4s\" repeatCount=\"indefinite\" />\n </circle>\n <circle\n cx={cx}\n cy={cy}\n r=\"6\"\n fill=\"var(--color-accent-primary)\"\n stroke=\"var(--color-bg-surface)\"\n strokeWidth=\"2\"\n />\n </svg>\n\n {/* Corner labels */}\n <span className=\"absolute bottom-2 left-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm\">\n <MapPin className=\"h-3 w-3 text-accent-primary\" /> {originLabel}\n </span>\n <span className=\"absolute right-2 top-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm\">\n <Home className=\"h-3 w-3 text-status-success-text\" /> {destinationLabel}\n </span>\n <span className=\"absolute right-2 bottom-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/70 px-2 py-0.5 text-[10px] text-text-muted backdrop-blur-sm\">\n <Navigation className=\"h-3 w-3\" /> Illustrative route\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-kadaikodi",
|
|
3
|
-
"version": "2026.719.
|
|
3
|
+
"version": "2026.719.4",
|
|
4
4
|
"description": "Burdenoff Kadaikodi Microfrontend - Mobile services marketplace platform for connecting service providers with customers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|