Files
Genarrative/apps/admin-web/src/pages/AdminProfileWalletConfigPage.tsx
T
kdletters afc2f03497 新增后台账号初始泥点配置
新增 profile_wallet_config 表和后台读写接口

注册赠送泥点改为读取后台配置并保留默认 100

后台新增账号配置页并同步数据契约文档
2026-06-23 17:42:49 +08:00

175 lines
5.1 KiB
TypeScript

import {RefreshCcw, Save} from 'lucide-react';
import {FormEvent, useEffect, useState} from 'react';
import {
getProfileWalletConfig,
upsertProfileWalletConfig,
} from '../api/adminApiClient';
import type {ProfileWalletConfigAdminResponse} from '../api/adminApiTypes';
import {useAdminWriteConfirm} from '../components/useAdminWriteConfirm';
import {handlePageError} from './pageUtils';
interface AdminProfileWalletConfigPageProps {
token: string;
result: ProfileWalletConfigAdminResponse | null;
onUnauthorized: (message?: string) => void;
onResultChange: (result: ProfileWalletConfigAdminResponse) => void;
}
export function AdminProfileWalletConfigPage({
token,
result,
onUnauthorized,
onResultChange,
}: AdminProfileWalletConfigPageProps) {
const [initialMudPoints, setInitialMudPoints] = useState('100');
const [isLoading, setIsLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [loadErrorMessage, setLoadErrorMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const {confirmWrite, confirmDialog} = useAdminWriteConfirm();
useEffect(() => {
void refreshConfig();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
async function refreshConfig() {
setIsLoading(true);
setLoadErrorMessage('');
try {
const response = await getProfileWalletConfig(token);
onResultChange(response);
setInitialMudPoints(String(response.initialMudPoints));
} catch (error: unknown) {
handlePageError(error, onUnauthorized, setLoadErrorMessage);
} finally {
setIsLoading(false);
}
}
async function handleSave(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (isSaving) {
return;
}
const normalizedInitialMudPoints = parsePositiveInteger(initialMudPoints);
if (!normalizedInitialMudPoints) {
setErrorMessage('账号初始泥点数必须是大于 0 的整数');
return;
}
setErrorMessage('');
const confirmed = await confirmWrite({
action: '保存账号配置',
target: `${normalizedInitialMudPoints}泥点`,
});
if (!confirmed) {
return;
}
setIsSaving(true);
try {
const response = await upsertProfileWalletConfig(token, {
initialMudPoints: normalizedInitialMudPoints,
});
onResultChange(response);
setInitialMudPoints(String(response.initialMudPoints));
} catch (error: unknown) {
handlePageError(error, onUnauthorized, setErrorMessage);
} finally {
setIsSaving(false);
}
}
return (
<section className="admin-page">
<div className="admin-page-heading">
<div>
<h2>账号配置</h2>
<p>泥点钱包</p>
</div>
<button
className="admin-secondary-button"
disabled={isLoading}
type="button"
onClick={refreshConfig}
>
<RefreshCcw size={17} aria-hidden="true" />
<span>{isLoading ? '刷新中' : '刷新'}</span>
</button>
</div>
{loadErrorMessage ? (
<div className="admin-alert" role="status">
{loadErrorMessage}
</div>
) : null}
<div className="admin-two-column">
<form className="admin-panel admin-form" onSubmit={handleSave}>
<label className="admin-field">
<span>账号初始泥点数</span>
<input
min={1}
step={1}
type="number"
value={initialMudPoints}
onChange={(event) => setInitialMudPoints(event.target.value)}
/>
</label>
{errorMessage ? (
<div className="admin-alert" role="status">
{errorMessage}
</div>
) : null}
<button
className="admin-primary-button"
disabled={isSaving || !parsePositiveInteger(initialMudPoints)}
type="submit"
>
<Save size={17} aria-hidden="true" />
<span>{isSaving ? '保存中' : '保存'}</span>
</button>
</form>
<section className="admin-panel admin-result-panel">
<div className="admin-panel-heading">
<h3>当前配置</h3>
<span>{result?.configId ?? '-'}</span>
</div>
{result ? (
<dl className="admin-info-list">
<div>
<dt>初始泥点</dt>
<dd>{result.initialMudPoints}</dd>
</div>
<div>
<dt>更新人</dt>
<dd>{result.updatedBy || '-'}</dd>
</div>
<div>
<dt>更新</dt>
<dd>{result.updatedAt}</dd>
</div>
</dl>
) : (
<div className="admin-empty-state">
{isLoading ? '加载中' : '暂无记录'}
</div>
)}
</section>
</div>
{confirmDialog}
</section>
);
}
function parsePositiveInteger(value: string) {
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
}