import {RefreshCcw, Save} from 'lucide-react'; import {FormEvent, useEffect, useState} from 'react'; import { listProfileRechargeProducts, upsertProfileRechargeProduct, } from '../api/adminApiClient'; import type { ProfileMembershipTier, ProfileRechargeProductConfigAdminResponse, ProfileRechargeProductKind, } from '../api/adminApiTypes'; import {useAdminWriteConfirm} from '../components/useAdminWriteConfirm'; import {handlePageError} from './pageUtils'; interface AdminRechargeProductPageProps { token: string; result: ProfileRechargeProductConfigAdminResponse | null; onUnauthorized: (message?: string) => void; onResultChange: (result: ProfileRechargeProductConfigAdminResponse) => void; } const productKinds: Array<{value: ProfileRechargeProductKind; label: string}> = [ {value: 'points', label: '泥点'}, {value: 'membership', label: '会员'}, ]; const membershipTiers: Array<{value: ProfileMembershipTier; label: string}> = [ {value: 'month', label: '月卡'}, {value: 'season', label: '季卡'}, {value: 'year', label: '年卡'}, ]; export function AdminRechargeProductPage({ token, result, onUnauthorized, onResultChange, }: AdminRechargeProductPageProps) { const [entries, setEntries] = useState< ProfileRechargeProductConfigAdminResponse[] >([]); const [productId, setProductId] = useState('points_60'); const [title, setTitle] = useState('60泥点'); const [priceCents, setPriceCents] = useState('600'); const [kind, setKind] = useState('points'); const [pointsAmount, setPointsAmount] = useState('60'); const [bonusPoints, setBonusPoints] = useState('60'); const [durationDays, setDurationDays] = useState('0'); const [badgeLabel, setBadgeLabel] = useState('首充双倍'); const [description, setDescription] = useState('首充送60泥点'); const [tier, setTier] = useState('normal'); const [enabled, setEnabled] = useState(true); const [sortOrder, setSortOrder] = useState('0'); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); const [listErrorMessage, setListErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState(''); const {confirmWrite, confirmDialog} = useAdminWriteConfirm(); useEffect(() => { void refreshProducts(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [token]); async function refreshProducts() { setIsLoading(true); setListErrorMessage(''); try { const response = await listProfileRechargeProducts(token); const sortedEntries = sortProducts(response.entries); setEntries(sortedEntries); const firstEntry = sortedEntries[0]; if (firstEntry) { fillForm(firstEntry); } } catch (error: unknown) { handlePageError(error, onUnauthorized, setListErrorMessage); } finally { setIsLoading(false); } } async function handleSave(event: FormEvent) { event.preventDefault(); if (isSaving) { return; } setErrorMessage(''); const confirmed = await confirmWrite({ action: enabled ? '保存充值商品' : '停用充值商品', target: productId.trim(), }); if (!confirmed) { return; } setIsSaving(true); try { const response = await upsertProfileRechargeProduct(token, { productId: productId.trim(), title: title.trim(), priceCents: parsePositiveInteger(priceCents), kind, pointsAmount: kind === 'points' ? parsePositiveInteger(pointsAmount) : 0, bonusPoints: kind === 'points' ? parseNonNegativeInteger(bonusPoints) : 0, durationDays: kind === 'membership' ? parsePositiveInteger(durationDays) : 0, badgeLabel: kind === 'points' ? badgeLabel.trim() : '', description: description.trim(), tier: kind === 'membership' ? tier : 'normal', enabled, sortOrder: parseInteger(sortOrder), }); onResultChange(response); upsertEntry(response); fillForm(response); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsSaving(false); } } function upsertEntry(next: ProfileRechargeProductConfigAdminResponse) { setEntries((current) => { const rest = current.filter((entry) => entry.productId !== next.productId); return sortProducts([...rest, next]); }); } function fillForm(entry: ProfileRechargeProductConfigAdminResponse) { setProductId(entry.productId); setTitle(entry.title); setPriceCents(String(entry.priceCents)); setKind(entry.kind); setPointsAmount(String(entry.pointsAmount)); setBonusPoints(String(entry.bonusPoints)); setDurationDays(String(entry.durationDays)); setBadgeLabel(entry.badgeLabel); setDescription(entry.description); setTier(entry.tier); setEnabled(entry.enabled); setSortOrder(String(entry.sortOrder)); } return (

充值商品

泥点与会员档位

{listErrorMessage ? (
{listErrorMessage}
) : null}
{productKinds.map((item) => ( ))}
{kind === 'points' ? (
) : (
)}
{errorMessage ? (
{errorMessage}
) : null}

商品列表

{entries.length}
{entries.map((entry) => ( ))}
商品 类型 价格 内容 状态
{entry.productId} {formatProductKind(entry.kind)} {formatPrice(entry.priceCents)} {formatProductContent(entry)} {entry.enabled ? '启用' : '停用'}
{result ? (

最近保存

{result.updatedAt}
商品
{result.productId}
状态
{result.enabled ? '启用' : '停用'}
) : null}
{confirmDialog}
); } function sortProducts(entries: ProfileRechargeProductConfigAdminResponse[]) { return [...entries].sort((left, right) => { if (left.sortOrder !== right.sortOrder) { return left.sortOrder - right.sortOrder; } return left.productId.localeCompare(right.productId); }); } function formatProductKind(kind: ProfileRechargeProductKind) { return kind === 'points' ? '泥点' : '会员'; } function formatTier(tier: ProfileMembershipTier) { if (tier === 'month') { return '月卡'; } if (tier === 'season') { return '季卡'; } if (tier === 'year') { return '年卡'; } return '普通'; } function formatProductContent(entry: ProfileRechargeProductConfigAdminResponse) { if (entry.kind === 'points') { return `${entry.pointsAmount}+${entry.bonusPoints}`; } return `${formatTier(entry.tier)} ${entry.durationDays}天`; } function formatPrice(priceCents: number) { return `¥${(priceCents / 100).toFixed(2)}`; } function parsePositiveInteger(value: string) { const parsed = parseInteger(value); return parsed > 0 ? parsed : 0; } function parseNonNegativeInteger(value: string) { const parsed = parseInteger(value); return parsed > 0 ? parsed : 0; } function parseInteger(value: string) { const parsed = Number.parseInt(value, 10); if (!Number.isFinite(parsed)) { return 0; } return parsed; }