profit-planet-frontend/src/app/admin/subscriptions/hooks/useCoffeeShippingFees.ts
2026-03-15 18:34:19 +01:00

121 lines
3.6 KiB
TypeScript

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 <T = any>(
path: string,
init: RequestInit = {},
responseType: 'json' | 'text' | 'blob' = 'json'
): Promise<T> => {
let token = getState().accessToken;
if (!token) {
const ok = await getState().refreshAuthToken();
if (ok) token = getState().accessToken;
}
const headers: Record<string, string> = {
...((init.headers as Record<string, string>) || {}),
...(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<CoffeeShippingFee[]> => {
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<CoffeeShippingFee> => {
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<any>(`/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,
};
}