diff --git a/src/lib/api.ts b/src/lib/api.ts index f36fa79..beaced7 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,8 +1,72 @@ import { useAuthStore } from '../stores/auth.ts' +import { + mockPurchases, + mockProducts, + mockCoupons, + mockAlerts, + getMockPriceHistory, +} from './mock-data.ts' const API_BASE = import.meta.env.VITE_API_URL ?? '/api/v1' +const USE_MOCK = import.meta.env.VITE_MOCK_API === 'true' + +// Mock response lookup table +const mockRoutes: Record unknown> = { + '/purchases': () => mockPurchases, + '/products': () => mockProducts, + '/coupons': () => mockCoupons, + '/price-alerts': () => mockAlerts, +} + +function matchMockRoute(path: string): T | null { + // Exact match + if (mockRoutes[path]) return mockRoutes[path](path) as T + + // /purchases/:id + const purchaseMatch = path.match(/^\/purchases\/(.+)$/) + if (purchaseMatch) { + const purchase = mockPurchases.find((p) => p.id === purchaseMatch[1]) + return (purchase ?? null) as T + } + + // /products/:id/price-history + const priceHistoryMatch = path.match(/^\/products\/(.+)\/price-history$/) + if (priceHistoryMatch) { + return getMockPriceHistory(priceHistoryMatch[1]) as T + } + + // /products?q=search or /products/:id + const productMatch = path.match(/^\/products\/(.+)$/) + if (productMatch) { + const product = mockProducts.find((p) => p.id === productMatch[1]) + return (product ?? null) as T + } + + const productsSearch = path.match(/^\/products\?q=(.+)$/) + if (productsSearch) { + const q = decodeURIComponent(productsSearch[1]).toLowerCase() + return mockProducts.filter( + (p) => + p.name.toLowerCase().includes(q) || + p.brand.toLowerCase().includes(q) || + p.category.toLowerCase().includes(q), + ) as T + } + + return null +} async function apiFetch(path: string, options?: RequestInit): Promise { + // Mock interceptor: return mock data without hitting the network + if (USE_MOCK && (!options?.method || options.method === 'GET')) { + const mockResult = matchMockRoute(path) + if (mockResult !== null) { + // Simulate network delay for realistic loading states + await new Promise((r) => setTimeout(r, 300)) + return mockResult + } + } + const token = useAuthStore.getState().token const res = await fetch(`${API_BASE}${path}`, { diff --git a/src/pages/AccountLinking.tsx b/src/pages/AccountLinking.tsx index 96aa15f..3b4d8b2 100644 --- a/src/pages/AccountLinking.tsx +++ b/src/pages/AccountLinking.tsx @@ -44,9 +44,9 @@ export function AccountLinking() { const [connected, setConnected] = useState(['meijer', 'kroger']) const [status, setStatus] = useState<'idle' | 'connecting' | 'success' | 'error'>('idle') - function handleConnect(storeId: string) { + function handleConnect(storeId: string, _fields: Record) { setStatus('connecting') - // Simulate connection + // Simulate connection — fields will be sent to API when available setTimeout(() => { setConnected((prev) => [...prev, storeId]) setStatus('success') @@ -100,7 +100,7 @@ export function AccountLinking() { {isConnected && !isLinking && ( @@ -119,7 +119,7 @@ export function AccountLinking() { handleConnect(store.id)} + onSubmit={(fields) => handleConnect(store.id, fields)} onCancel={() => { setLinking(null) setStatus('idle') @@ -149,9 +149,13 @@ function LinkForm({ }: { store: StoreConfig status: string - onSubmit: () => void + onSubmit: (fields: Record) => void onCancel: () => void }) { + const [values, setValues] = useState>(() => + Object.fromEntries(store.fields.map((f) => [f.key, ''])), + ) + return (
{store.fields.map((field) => ( @@ -159,6 +163,8 @@ function LinkForm({ key={field.key} type={field.type} placeholder={field.label} + value={values[field.key] ?? ''} + onChange={(e) => setValues((prev) => ({ ...prev, [field.key]: e.target.value }))} autoComplete={field.type === 'password' ? 'current-password' : field.type} className="min-h-12 w-full rounded-xl border border-gray-200 px-4 text-base focus:border-brand-blue focus:outline-none focus:ring-1 focus:ring-brand-blue" /> @@ -186,7 +192,7 @@ function LinkForm({ {status === 'idle' && (