feat(integration): 为集成站点与开奖管理新增 AdminPermissionGate 权限控制

使用 AdminPermissionGate 包裹集成站点与开奖相关组件,根据权限进行访问控制。
新增集成管理与开奖管理相关权限常量。
更新相关 UI 组件以适配权限校验逻辑,提升系统安全性与用户体验。
增强国际化支持,在英文、尼泊尔语与中文语言包中新增集成相关文案。
This commit is contained in:
2026-05-27 16:51:48 +08:00
parent 5eabbcf0ee
commit 788c7998eb
24 changed files with 276 additions and 64 deletions

View File

@@ -36,11 +36,15 @@ import {
} from "@/components/ui/table";
import { Textarea } from "@/components/ui/textarea";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { getAdminPageBundle } from "@/lib/admin-permission-bundles";
import { useAdminProfile } from "@/stores/admin-session";
import { LotteryApiBizError } from "@/types/api/errors";
import type {
AdminIntegrationSiteCreatePayload,
AdminIntegrationSiteConnectivityResult,
AdminIntegrationSiteUpdatePayload,
AdminIntegrationSiteRow,
AdminIntegrationSiteDetail,
AdminIntegrationSiteSecrets,
AdminIntegrationSiteWithSecrets,
} from "@/types/api/admin-integration-site";
@@ -86,7 +90,7 @@ function textToOrigins(text: string): string[] {
.filter(Boolean);
}
function rowToForm(row: AdminIntegrationSiteRow & Partial<FormState>): FormState {
function rowToForm(row: AdminIntegrationSiteDetail): FormState {
return {
code: row.code,
name: row.name,
@@ -103,7 +107,12 @@ function rowToForm(row: AdminIntegrationSiteRow & Partial<FormState>): FormState
};
}
function formToPayload(form: FormState, includeCode: boolean) {
function formToPayload(form: FormState, includeCode: true): AdminIntegrationSiteCreatePayload;
function formToPayload(form: FormState, includeCode: false): AdminIntegrationSiteUpdatePayload;
function formToPayload(
form: FormState,
includeCode: boolean,
): AdminIntegrationSiteCreatePayload | AdminIntegrationSiteUpdatePayload {
const base = {
name: form.name.trim(),
currency_code: form.currency_code.trim() || "NPR",
@@ -128,11 +137,10 @@ function formToPayload(form: FormState, includeCode: boolean) {
export function IntegrationSitesConsole() {
const { t } = useTranslation("config");
const profile = useAdminProfile();
const canView = adminHasAnyPermission(profile?.permissions, [
"prd.integration.view",
"prd.integration.manage",
]);
const canManage = adminHasAnyPermission(profile?.permissions, ["prd.integration.manage"]);
const canManage = adminHasAnyPermission(
profile?.permissions,
getAdminPageBundle("integration-sites", "manage"),
);
const [items, setItems] = useState<AdminIntegrationSiteRow[]>([]);
const [loading, setLoading] = useState(true);
@@ -158,12 +166,6 @@ export function IntegrationSitesConsole() {
const [exportBusyId, setExportBusyId] = useState<number | null>(null);
const load = useCallback(async () => {
if (!canView) {
setItems([]);
setLoading(false);
return;
}
setLoading(true);
try {
const data = await getAdminIntegrationSites();
@@ -175,7 +177,7 @@ export function IntegrationSitesConsole() {
} finally {
setLoading(false);
}
}, [canView, t]);
}, [t]);
useEffect(() => {
queueMicrotask(() => {
@@ -334,14 +336,6 @@ export function IntegrationSitesConsole() {
}
}
if (!canView) {
return (
<AdminPageCard title={t("integrationSites.title")}>
<p className="text-sm text-muted-foreground">{t("integrationSites.noPermission")}</p>
</AdminPageCard>
);
}
return (
<>
<AdminPageCard
@@ -377,13 +371,12 @@ export function IntegrationSitesConsole() {
<TableCell>{row.name}</TableCell>
<TableCell>
<AdminStatusBadge
tone={row.status === 1 ? "success" : "muted"}
label={
row.status === 1
? t("integrationSites.statusEnabled")
: t("integrationSites.statusDisabled")
}
/>
tone={row.status === 1 ? "success" : "neutral"}
>
{row.status === 1
? t("integrationSites.statusEnabled")
: t("integrationSites.statusDisabled")}
</AdminStatusBadge>
</TableCell>
<TableCell className="max-w-[240px] truncate text-xs text-muted-foreground">
{row.wallet_api_url ?? "—"}
@@ -407,9 +400,16 @@ export function IntegrationSitesConsole() {
>
{t("integrationSites.exportParams")}
</Button>
<Button type="button" variant="outline" size="sm" onClick={() => void openEdit(row)}>
{t("integrationSites.edit")}
</Button>
{canManage ? (
<Button
type="button"
variant="outline"
size="sm"
onClick={() => void openEdit(row)}
>
{t("integrationSites.edit")}
</Button>
) : null}
{canManage ? (
<Button
type="button"