88 lines
2.4 KiB
TypeScript
88 lines
2.4 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
|
|
export type CoffeeShippingFeePieceCount = 60 | 120;
|
|
|
|
export type CoffeeShippingFee = {
|
|
pieceCount: CoffeeShippingFeePieceCount;
|
|
price: number;
|
|
};
|
|
|
|
type ShippingFeeMap = Record<CoffeeShippingFeePieceCount, number>;
|
|
|
|
function 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;
|
|
}
|
|
|
|
export function useShippingFees() {
|
|
const base = process.env.NEXT_PUBLIC_API_BASE_URL || '';
|
|
|
|
const [fees, setFees] = useState<CoffeeShippingFee[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const feeByPieceCount: ShippingFeeMap = useMemo(() => {
|
|
const map: ShippingFeeMap = { 60: 0, 120: 0 };
|
|
for (const row of fees) {
|
|
map[row.pieceCount] = row.price;
|
|
}
|
|
return map;
|
|
}, [fees]);
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
(async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
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 (!active) return;
|
|
|
|
if (!Array.isArray(raw)) {
|
|
setFees([]);
|
|
return;
|
|
}
|
|
|
|
const next = 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: Number.isFinite(price) && price >= 0 ? price : 0,
|
|
} satisfies CoffeeShippingFee;
|
|
})
|
|
.filter(Boolean) as CoffeeShippingFee[];
|
|
|
|
next.sort((a, b) => a.pieceCount - b.pieceCount);
|
|
setFees(next);
|
|
} catch (e: any) {
|
|
if (!active) return;
|
|
setError(e?.message ?? 'Failed to load shipping fees');
|
|
setFees([]);
|
|
} finally {
|
|
if (active) setLoading(false);
|
|
}
|
|
})();
|
|
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, [base]);
|
|
|
|
return { fees, feeByPieceCount, loading, error };
|
|
}
|