fix: address Chip's review — secure auth, wire TanStack Query, fix UX issues

Must-fix:
- Exclude JWT token from Zustand persist (partialize) to prevent
  localStorage XSS exfiltration — token now lives in memory only
- Wire all pages through TanStack Query hooks (usePurchases, useProduct,
  useProducts, usePriceHistory, useCoupons, usePriceAlerts) with proper
  loading skeletons and error states
- Add mock interceptor in api.ts (VITE_MOCK_API=true) so mock data flows
  through the same fetch path — single flag to switch to live API

Should-fix:
- Wire theme toggle to DOM (dark class on <html>)
- Fix AccountLinking form inputs (controlled with value/onChange)
- Remove unused err in catch blocks (Login, Register)
- Bump remaining min-h-10 touch targets to min-h-12 (48px)

Build: 128KB initial JS, Recharts 498KB lazy chunk. 5/5 tests pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Frontend Frankie
2026-03-18 11:54:06 +00:00
parent 5563b5f145
commit d0185fd93d
14 changed files with 307 additions and 62 deletions
+12 -6
View File
@@ -44,9 +44,9 @@ export function AccountLinking() {
const [connected, setConnected] = useState<string[]>(['meijer', 'kroger'])
const [status, setStatus] = useState<'idle' | 'connecting' | 'success' | 'error'>('idle')
function handleConnect(storeId: string) {
function handleConnect(storeId: string, _fields: Record<string, string>) {
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 && (
<button
onClick={() => handleDisconnect(store.id)}
className="mt-3 min-h-10 w-full rounded-xl border border-red-200 px-4 py-2 text-sm font-medium text-red-600 active:bg-red-50"
className="mt-3 min-h-12 w-full rounded-xl border border-red-200 px-4 py-2 text-sm font-medium text-red-600 active:bg-red-50"
>
Disconnect
</button>
@@ -119,7 +119,7 @@ export function AccountLinking() {
<LinkForm
store={store}
status={status}
onSubmit={() => 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<string, string>) => void
onCancel: () => void
}) {
const [values, setValues] = useState<Record<string, string>>(() =>
Object.fromEntries(store.fields.map((f) => [f.key, ''])),
)
return (
<div className="mt-3 space-y-3">
{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' && (
<div className="flex gap-3">
<button
onClick={onSubmit}
onClick={() => onSubmit(values)}
className="min-h-12 flex-1 rounded-xl bg-brand-blue px-4 py-3 text-base font-medium text-white active:bg-brand-blue/90"
>
Connect