feat(admin, settlement, dashboard): strengthen permission gating and billing workflows

This commit is contained in:
2026-06-09 13:44:19 +08:00
parent 7e65c53732
commit b7278e68a4
41 changed files with 900 additions and 199 deletions

View File

@@ -12,6 +12,9 @@ import type { AdminSettingBatchItem } from "@/api/admin-settings";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { PRD_DRAW_RESULT_MANAGE } from "@/lib/admin-prd";
import { useAdminProfile } from "@/stores/admin-session";
interface DrawDraft {
defaultCurrency: string;
@@ -90,6 +93,8 @@ function buildDirtyItems(draft: DrawDraft, saved: DrawDraft): AdminSettingBatchI
export function DrawSettingsPanel() {
const { t } = useTranslation(["config", "adminUsers", "common"]);
const profile = useAdminProfile();
const canManage = adminHasAnyPermission(profile?.permissions, [PRD_DRAW_RESULT_MANAGE]);
const { request: requestConfirm, ConfirmDialog } = useConfirmAction();
const buildItems = useCallback(buildDirtyItems, []);
const section = useSettingsSection({
@@ -113,7 +118,7 @@ export function DrawSettingsPanel() {
<Label className="text-sm font-medium">{t("system.fields.manualReview", { ns: "config" })}</Label>
<Switch
checked={draft.requireManualReview}
disabled={loading || saving}
disabled={!canManage || loading || saving}
aria-label={t("system.fields.manualReview", { ns: "config" })}
onCheckedChange={(value) => updateField("requireManualReview", value)}
/>
@@ -131,7 +136,7 @@ export function DrawSettingsPanel() {
value={draft.defaultCurrency}
placeholder={t("system.placeholders.defaultCurrency", { ns: "config" })}
onChange={(e) => updateField("defaultCurrency", e.target.value.toUpperCase())}
disabled={loading || saving}
disabled={!canManage || loading || saving}
maxLength={16}
/>
</div>
@@ -148,7 +153,7 @@ export function DrawSettingsPanel() {
value={draft.drawIntervalMinutes}
placeholder={t("system.placeholders.drawIntervalMinutes", { ns: "config" })}
onChange={(e) => updateField("drawIntervalMinutes", e.target.value)}
disabled={loading || saving}
disabled={!canManage || loading || saving}
/>
</div>
<div className="grid gap-2">
@@ -163,7 +168,7 @@ export function DrawSettingsPanel() {
value={draft.drawBettingWindowSeconds}
placeholder={t("system.placeholders.drawBettingWindowSeconds", { ns: "config" })}
onChange={(e) => updateField("drawBettingWindowSeconds", e.target.value)}
disabled={loading || saving}
disabled={!canManage || loading || saving}
/>
</div>
<div className="grid gap-2">
@@ -178,7 +183,7 @@ export function DrawSettingsPanel() {
value={draft.drawCloseBeforeDrawSeconds}
placeholder={t("system.placeholders.drawCloseBeforeDrawSeconds", { ns: "config" })}
onChange={(e) => updateField("drawCloseBeforeDrawSeconds", e.target.value)}
disabled={loading || saving}
disabled={!canManage || loading || saving}
/>
</div>
<div className="grid gap-2">
@@ -193,7 +198,7 @@ export function DrawSettingsPanel() {
value={draft.drawBufferDrawsAhead}
placeholder={t("system.placeholders.drawBufferDrawsAhead", { ns: "config" })}
onChange={(e) => updateField("drawBufferDrawsAhead", e.target.value)}
disabled={loading || saving}
disabled={!canManage || loading || saving}
/>
</div>
<div className="grid gap-2">
@@ -208,14 +213,14 @@ export function DrawSettingsPanel() {
value={draft.cooldownMinutes}
placeholder={t("system.placeholders.cooldownMinutes", { ns: "config" })}
onChange={(e) => updateField("cooldownMinutes", e.target.value)}
disabled={loading || saving}
disabled={!canManage || loading || saving}
/>
</div>
</div>
<SettingsSectionActions
dirty={dirty}
loading={loading}
loading={!canManage || loading}
saving={saving}
onSave={() =>
requestConfirm({