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
+25 -3
View File
@@ -1,8 +1,9 @@
import { useState } from 'react'
import { mockCoupons } from '../lib/mock-data.ts'
import { useCoupons } from '../hooks/useApi.ts'
import { StoreIcon } from '../components/StoreIcon.tsx'
export function Coupons() {
const { data: coupons = [], isLoading, error } = useCoupons()
const [copied, setCopied] = useState<string | null>(null)
function handleCopy(code: string, id: string) {
@@ -17,12 +18,33 @@ export function Coupons() {
Target: 'target',
}
if (isLoading) {
return (
<div className="animate-pulse">
<div className="h-8 w-40 rounded bg-gray-200" />
<div className="mt-4 space-y-3">
{[1, 2, 3].map((i) => (
<div key={i} className="h-24 rounded-xl bg-gray-200" />
))}
</div>
</div>
)
}
if (error) {
return (
<div className="py-8 text-center">
<p className="text-sm text-red-600">Failed to load coupons.</p>
</div>
)
}
return (
<div>
<h1 className="text-2xl font-bold text-gray-900">Coupons & Deals</h1>
<div className="mt-4 space-y-3">
{mockCoupons.map((coupon) => {
{coupons.map((coupon) => {
const isExpiringSoon =
new Date(coupon.expiresAt).getTime() - Date.now() < 7 * 24 * 60 * 60 * 1000
@@ -54,7 +76,7 @@ export function Coupons() {
{coupon.code && (
<button
onClick={() => handleCopy(coupon.code!, coupon.id)}
className="mt-3 flex min-h-10 w-full items-center justify-center gap-2 rounded-lg border border-dashed border-gray-300 px-4 py-2 text-sm font-mono active:bg-gray-50"
className="mt-3 flex min-h-12 w-full items-center justify-center gap-2 rounded-lg border border-dashed border-gray-300 px-4 py-2 text-sm font-mono active:bg-gray-50"
>
<span className="text-gray-700">{coupon.code}</span>
<span className="text-xs text-brand-blue">