feat(admin, settlement, dashboard): strengthen permission gating and billing workflows
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -12,6 +12,9 @@ import type { AdminSettingBatchItem } from "@/api/admin-settings";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||
import { PRD_ODDS_MANAGE, PRD_REBATE_MANAGE } from "@/lib/admin-prd";
|
||||
import { useAdminProfile } from "@/stores/admin-session";
|
||||
|
||||
interface FrontendDraft {
|
||||
playRulesHtmlZh: string;
|
||||
@@ -51,6 +54,8 @@ function buildDirtyItems(draft: FrontendDraft, saved: FrontendDraft): AdminSetti
|
||||
|
||||
export function FrontendSettingsPanel() {
|
||||
const { t } = useTranslation(["config", "adminUsers", "common"]);
|
||||
const profile = useAdminProfile();
|
||||
const canManage = adminHasAnyPermission(profile?.permissions, [PRD_ODDS_MANAGE, PRD_REBATE_MANAGE]);
|
||||
const { request: requestConfirm, ConfirmDialog } = useConfirmAction();
|
||||
const buildItems = useCallback(buildDirtyItems, []);
|
||||
const section = useSettingsSection({
|
||||
@@ -84,7 +89,7 @@ export function FrontendSettingsPanel() {
|
||||
id="play-rules-html-zh"
|
||||
value={draft.playRulesHtmlZh}
|
||||
onChange={(e) => updateField("playRulesHtmlZh", e.target.value)}
|
||||
disabled={loading || saving}
|
||||
disabled={!canManage || loading || saving}
|
||||
className="min-h-[200px] font-mono text-xs"
|
||||
placeholder="<div>...</div>"
|
||||
/>
|
||||
@@ -94,7 +99,7 @@ export function FrontendSettingsPanel() {
|
||||
id="play-rules-html-en"
|
||||
value={draft.playRulesHtmlEn}
|
||||
onChange={(e) => updateField("playRulesHtmlEn", e.target.value)}
|
||||
disabled={loading || saving}
|
||||
disabled={!canManage || loading || saving}
|
||||
className="min-h-[200px] font-mono text-xs"
|
||||
placeholder="<div>...</div>"
|
||||
/>
|
||||
@@ -104,7 +109,7 @@ export function FrontendSettingsPanel() {
|
||||
id="play-rules-html-ne"
|
||||
value={draft.playRulesHtmlNe}
|
||||
onChange={(e) => updateField("playRulesHtmlNe", e.target.value)}
|
||||
disabled={loading || saving}
|
||||
disabled={!canManage || loading || saving}
|
||||
className="min-h-[200px] font-mono text-xs"
|
||||
placeholder="<div>...</div>"
|
||||
/>
|
||||
@@ -113,7 +118,7 @@ export function FrontendSettingsPanel() {
|
||||
|
||||
<SettingsSectionActions
|
||||
dirty={dirty}
|
||||
loading={loading}
|
||||
loading={!canManage || loading}
|
||||
saving={saving}
|
||||
onSave={() =>
|
||||
requestConfirm({
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { AdminSettingBatchItem } from "@/api/admin-settings";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||
import { PRD_PAYOUT_MANAGE } from "@/lib/admin-prd";
|
||||
import { useAdminProfile } from "@/stores/admin-session";
|
||||
|
||||
interface SettlementDraft {
|
||||
@@ -57,7 +58,7 @@ function buildDirtyItems(draft: SettlementDraft, saved: SettlementDraft): AdminS
|
||||
export function SettlementSettingsPanel() {
|
||||
const { t } = useTranslation(["config", "adminUsers", "common"]);
|
||||
const profile = useAdminProfile();
|
||||
const canManage = adminHasAnyPermission(profile?.permissions, ["prd.payout.manage"]);
|
||||
const canManage = adminHasAnyPermission(profile?.permissions, [PRD_PAYOUT_MANAGE]);
|
||||
const { request: requestConfirm, ConfirmDialog } = useConfirmAction();
|
||||
const buildItems = useCallback(buildDirtyItems, []);
|
||||
const section = useSettingsSection({
|
||||
|
||||
Reference in New Issue
Block a user