import { useCallback } from 'react'; import useAuthStore from '../../../store/authStore'; export type CoffeeShippingFeePieceCount = 60 | 120; export type CoffeeShippingFee = { pieceCount: CoffeeShippingFeePieceCount; price: number; }; function isFormData(body: any): body is FormData { return typeof FormData !== 'undefined' && body instanceof FormData; } export default function useCoffeeShippingFees() { const base = process.env.NEXT_PUBLIC_API_BASE_URL || ''; const getState = useAuthStore.getState; const authorizedFetch = useCallback( async ( path: string, init: RequestInit = {}, responseType: 'json' | 'text' | 'blob' = 'json' ): Promise => { let token = getState().accessToken; if (!token) { const ok = await getState().refreshAuthToken(); if (ok) token = getState().accessToken; } const headers: Record = { ...((init.headers as Record) || {}), ...(token ? { Authorization: `Bearer ${token}` } : {}), }; if (!isFormData(init.body) && init.method && init.method !== 'GET') { headers['Content-Type'] = headers['Content-Type'] || 'application/json'; } const res = await fetch(`${base}${path}`, { credentials: 'include', ...init, headers, }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(text || `HTTP ${res.status}`); } if (responseType === 'blob') return (await res.blob()) as unknown as T; if (responseType === 'text') return (await res.text()) as unknown as T; const text = await res.text(); try { return JSON.parse(text) as T; } catch { return {} as T; } }, [base, getState] ); const listShippingFees = useCallback(async (): Promise => { const res = await fetch(`${base}/api/shipping-fees`, { method: 'GET', credentials: 'include', }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(text || `HTTP ${res.status}`); } const raw = (await res.json().catch(() => [])) as any; if (!Array.isArray(raw)) return []; const normalizePieceCount = (v: any): CoffeeShippingFeePieceCount | null => { const n = typeof v === 'number' ? v : (typeof v === 'string' && /^\d+$/.test(v) ? Number(v) : NaN); if (n === 60 || n === 120) return n; return null; }; return raw .map((r: any) => { const pieceCount = normalizePieceCount(r?.pieceCount); if (!pieceCount) return null; const price = r?.price != null && r?.price !== '' ? Number(r.price) : 0; return { pieceCount, price } satisfies CoffeeShippingFee; }) .filter(Boolean) as CoffeeShippingFee[]; }, [base]); const updateShippingFee = useCallback( async (pieceCount: CoffeeShippingFeePieceCount, price: number): Promise => { if (!(pieceCount === 60 || pieceCount === 120)) { throw new Error('pieceCount must be 60 or 120'); } if (!Number.isFinite(price) || price < 0) { throw new Error('price must be a number >= 0'); } const row = await authorizedFetch(`/api/admin/shipping-fees/${pieceCount}`, { method: 'PUT', body: JSON.stringify({ price }), }); const normalized: CoffeeShippingFee = { pieceCount, price: row?.price != null && row?.price !== '' ? Number(row.price) : price, }; return normalized; }, [authorizedFetch] ); return { listShippingFees, updateShippingFee, }; }