feat: add manager region invite switch
This commit is contained in:
parent
c206c72612
commit
8ef5b0c08d
@ -8875,8 +8875,11 @@
|
||||
},
|
||||
"ApiPageManager": {
|
||||
"type": "object",
|
||||
"required": ["items", "page", "pageSize", "total"],
|
||||
"required": ["items", "page", "pageSize", "total", "bdLeaderInviteScopePolicy"],
|
||||
"properties": {
|
||||
"bdLeaderInviteScopePolicy": {
|
||||
"$ref": "#/components/schemas/ManagerRoleScopePolicy"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -10112,6 +10115,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ManagerRoleScopePolicy": {
|
||||
"type": "object",
|
||||
"required": ["baseScope", "regionExpansionConfigurable"],
|
||||
"properties": {
|
||||
"baseScope": {
|
||||
"type": "string",
|
||||
"enum": ["country", "region", "global"]
|
||||
},
|
||||
"regionExpansionConfigurable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Manager": {
|
||||
"type": "object",
|
||||
"required": ["userId"],
|
||||
@ -10128,6 +10144,20 @@
|
||||
"canAddBdLeader": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canAddBdLeaderInRegion": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"bdLeaderInviteBaseScope": {
|
||||
"type": "string",
|
||||
"enum": ["country", "region", "global"]
|
||||
},
|
||||
"bdLeaderInviteEffectiveScope": {
|
||||
"type": "string",
|
||||
"enum": ["country", "region", "global"]
|
||||
},
|
||||
"bdLeaderRegionExpansionConfigurable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canAddSuperadmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -10638,6 +10668,9 @@
|
||||
"canAddBdLeader": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canAddBdLeaderInRegion": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canAddSuperadmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -10680,6 +10713,9 @@
|
||||
"canAddBdLeader": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canAddBdLeaderInRegion": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canAddSuperadmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@ -69,6 +69,7 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
||||
status: "active",
|
||||
});
|
||||
await createManager({
|
||||
canAddBdLeaderInRegion: false,
|
||||
canBlockUser: false,
|
||||
canGrantBadge: false,
|
||||
canGrantVip: true,
|
||||
@ -76,6 +77,7 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
||||
targetUserId: "1003",
|
||||
});
|
||||
await updateManager("1003", {
|
||||
canAddBdLeaderInRegion: true,
|
||||
canBlockUser: false,
|
||||
canGrantVehicle: true,
|
||||
canGrantVip: false,
|
||||
@ -158,6 +160,7 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
||||
expect(String(managerCreateUrl)).toContain("/api/v1/admin/managers");
|
||||
expect(managerCreateInit?.method).toBe("POST");
|
||||
expect(JSON.parse(String(managerCreateInit?.body))).toEqual({
|
||||
canAddBdLeaderInRegion: false,
|
||||
canBlockUser: false,
|
||||
canGrantBadge: false,
|
||||
canGrantVip: true,
|
||||
@ -167,6 +170,7 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
||||
expect(String(managerUpdateUrl)).toContain("/api/v1/admin/managers/1003");
|
||||
expect(managerUpdateInit?.method).toBe("PUT");
|
||||
expect(JSON.parse(String(managerUpdateInit?.body))).toEqual({
|
||||
canAddBdLeaderInRegion: true,
|
||||
canBlockUser: false,
|
||||
canGrantVehicle: true,
|
||||
canGrantVip: false,
|
||||
|
||||
@ -17,29 +17,44 @@ export const managerPermissionFields = [
|
||||
"canGrantVip",
|
||||
"canUpdateUserLevel",
|
||||
"canAddBdLeader",
|
||||
"canAddBdLeaderInRegion",
|
||||
"canAddAdmin",
|
||||
"canAddSuperadmin",
|
||||
"canBlockUser",
|
||||
"canTransferUserCountry",
|
||||
];
|
||||
|
||||
const emptyPermissions = () => Object.fromEntries(managerPermissionFields.map((field) => [field, true]));
|
||||
const managerPermissionDefault = (field) => field !== "canAddBdLeaderInRegion";
|
||||
|
||||
const emptyManagerForm = () => ({ contact: "", targetUserId: "", ...emptyPermissions() });
|
||||
const managerPermissionEnabled = (source, field) => {
|
||||
const value = source?.[field];
|
||||
return value === undefined || value === null ? managerPermissionDefault(field) : value !== false;
|
||||
};
|
||||
|
||||
const emptyPermissions = () =>
|
||||
Object.fromEntries(managerPermissionFields.map((field) => [field, managerPermissionDefault(field)]));
|
||||
|
||||
const emptyManagerForm = (regionExpansionConfigurable = false) => ({
|
||||
bdLeaderRegionExpansionConfigurable: regionExpansionConfigurable,
|
||||
contact: "",
|
||||
targetUserId: "",
|
||||
...emptyPermissions(),
|
||||
});
|
||||
|
||||
const managerPermissionPayload = (form) =>
|
||||
Object.fromEntries(managerPermissionFields.map((field) => [field, form[field] !== false]));
|
||||
Object.fromEntries(managerPermissionFields.map((field) => [field, managerPermissionEnabled(form, field)]));
|
||||
|
||||
const managerStatusPayload = (item, status) => ({
|
||||
contact: item?.contact || "",
|
||||
status,
|
||||
...Object.fromEntries(managerPermissionFields.map((field) => [field, item?.[field] !== false])),
|
||||
...Object.fromEntries(managerPermissionFields.map((field) => [field, managerPermissionEnabled(item, field)])),
|
||||
});
|
||||
|
||||
const managerFormFromItem = (item) => ({
|
||||
bdLeaderRegionExpansionConfigurable: item?.bdLeaderRegionExpansionConfigurable === true,
|
||||
contact: item?.contact || "",
|
||||
targetUserId: String(item?.userId || ""),
|
||||
...Object.fromEntries(managerPermissionFields.map((field) => [field, item?.[field] !== false])),
|
||||
...Object.fromEntries(managerPermissionFields.map((field) => [field, managerPermissionEnabled(item, field)])),
|
||||
});
|
||||
|
||||
export function useHostManagersPage() {
|
||||
@ -118,7 +133,7 @@ export function useHostManagersPage() {
|
||||
});
|
||||
showToast("经理已添加", "success");
|
||||
}
|
||||
setManagerForm(emptyManagerForm());
|
||||
setManagerForm(emptyManagerForm(data.bdLeaderInviteScopePolicy?.regionExpansionConfigurable === true));
|
||||
setActiveAction("");
|
||||
setEditingManager(null);
|
||||
await reload();
|
||||
@ -132,12 +147,12 @@ export function useHostManagersPage() {
|
||||
const closeAction = () => {
|
||||
setActiveAction("");
|
||||
setEditingManager(null);
|
||||
setManagerForm(emptyManagerForm());
|
||||
setManagerForm(emptyManagerForm(data.bdLeaderInviteScopePolicy?.regionExpansionConfigurable === true));
|
||||
};
|
||||
|
||||
const openManagerForm = () => {
|
||||
setEditingManager(null);
|
||||
setManagerForm(emptyManagerForm());
|
||||
setManagerForm(emptyManagerForm(data.bdLeaderInviteScopePolicy?.regionExpansionConfigurable === true));
|
||||
setActiveAction("manager");
|
||||
};
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ const managerPermissionOptions = [
|
||||
{ key: "canGrantVip", label: "VIP" },
|
||||
{ key: "canUpdateUserLevel", label: "升级等级" },
|
||||
{ key: "canAddBdLeader", label: "BD Leader" },
|
||||
{ key: "canAddBdLeaderInRegion", label: "区域邀请 BD Leader", regionExpansionOnly: true },
|
||||
{ key: "canAddAdmin", label: "Admin" },
|
||||
{ key: "canAddSuperadmin", label: "Superadmin" },
|
||||
{ key: "canBlockUser", label: "Block User" },
|
||||
@ -201,20 +202,28 @@ function ManagerStatusSwitch({ item, page }) {
|
||||
function ManagerPermissions({ item }) {
|
||||
return (
|
||||
<div className={styles.permissionList}>
|
||||
{managerPermissionOptions.map((permission) => {
|
||||
const enabled = item?.[permission.key] !== false;
|
||||
return (
|
||||
<span
|
||||
className={
|
||||
enabled ? styles.permissionPill : `${styles.permissionPill} ${styles.permissionPillOff}`
|
||||
}
|
||||
key={permission.key}
|
||||
>
|
||||
{permission.label}
|
||||
<span>{enabled ? "开" : "关"}</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
{managerPermissionOptions
|
||||
.filter(
|
||||
(permission) =>
|
||||
!permission.regionExpansionOnly || item?.bdLeaderRegionExpansionConfigurable === true,
|
||||
)
|
||||
.map((permission) => {
|
||||
const enabled =
|
||||
permission.key === "canAddBdLeaderInRegion"
|
||||
? item?.[permission.key] === true
|
||||
: item?.[permission.key] !== false;
|
||||
return (
|
||||
<span
|
||||
className={
|
||||
enabled ? styles.permissionPill : `${styles.permissionPill} ${styles.permissionPillOff}`
|
||||
}
|
||||
key={permission.key}
|
||||
>
|
||||
{permission.label}
|
||||
<span>{enabled ? "开" : "关"}</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -237,19 +246,28 @@ function ManagerActions({ item, page }) {
|
||||
function PermissionSwitchGrid({ disabled, form, onChange }) {
|
||||
return (
|
||||
<div className={styles.permissionGrid}>
|
||||
{managerPermissionOptions.map((permission) => (
|
||||
<label className={styles.permissionSwitch} key={permission.key}>
|
||||
<span>{permission.label}</span>
|
||||
<AdminSwitch
|
||||
checked={form?.[permission.key] !== false}
|
||||
checkedLabel="开"
|
||||
disabled={disabled}
|
||||
label={permission.label}
|
||||
uncheckedLabel="关"
|
||||
onChange={(event) => onChange({ ...form, [permission.key]: event.target.checked })}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
{managerPermissionOptions
|
||||
.filter(
|
||||
(permission) =>
|
||||
!permission.regionExpansionOnly || form?.bdLeaderRegionExpansionConfigurable === true,
|
||||
)
|
||||
.map((permission) => (
|
||||
<label className={styles.permissionSwitch} key={permission.key}>
|
||||
<span>{permission.label}</span>
|
||||
<AdminSwitch
|
||||
checked={
|
||||
permission.key === "canAddBdLeaderInRegion"
|
||||
? form?.[permission.key] === true
|
||||
: form?.[permission.key] !== false
|
||||
}
|
||||
checkedLabel="开"
|
||||
disabled={disabled}
|
||||
label={permission.label}
|
||||
uncheckedLabel="关"
|
||||
onChange={(event) => onChange({ ...form, [permission.key]: event.target.checked })}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -17,7 +17,9 @@ afterEach(() => {
|
||||
|
||||
test("manager status column uses a switch and toggles through the page hook", () => {
|
||||
const manager = managerFixture();
|
||||
vi.mocked(useHostManagersPage).mockReturnValue(pageFixture({ data: { items: [manager], page: 1, pageSize: 50, total: 1 } }));
|
||||
vi.mocked(useHostManagersPage).mockReturnValue(
|
||||
pageFixture({ data: { items: [manager], page: 1, pageSize: 50, total: 1 } }),
|
||||
);
|
||||
|
||||
render(<HostManagersPage />);
|
||||
|
||||
@ -31,6 +33,35 @@ test("manager status column uses a switch and toggles through the page hook", ()
|
||||
expect(mocks.toggleManager).toHaveBeenCalledWith(manager, false);
|
||||
});
|
||||
|
||||
test("manager form only shows the region invitation switch when backend policy allows configuration", () => {
|
||||
vi.mocked(useHostManagersPage).mockReturnValue(
|
||||
pageFixture({
|
||||
activeAction: "manager",
|
||||
managerForm: {
|
||||
...pageFixture().managerForm,
|
||||
bdLeaderRegionExpansionConfigurable: true,
|
||||
canAddBdLeaderInRegion: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const { rerender } = render(<HostManagersPage />);
|
||||
expect(screen.getByRole("switch", { name: "区域邀请 BD Leader" })).not.toBeChecked();
|
||||
|
||||
vi.mocked(useHostManagersPage).mockReturnValue(
|
||||
pageFixture({
|
||||
activeAction: "manager",
|
||||
managerForm: {
|
||||
...pageFixture().managerForm,
|
||||
bdLeaderRegionExpansionConfigurable: false,
|
||||
canAddBdLeaderInRegion: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
rerender(<HostManagersPage />);
|
||||
expect(screen.queryByRole("switch", { name: "区域邀请 BD Leader" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
function pageFixture(patch = {}) {
|
||||
return {
|
||||
abilities: {
|
||||
@ -50,8 +81,10 @@ function pageFixture(patch = {}) {
|
||||
loadingAction: "",
|
||||
loadingRegions: false,
|
||||
managerForm: {
|
||||
bdLeaderRegionExpansionConfigurable: true,
|
||||
canAddAdmin: true,
|
||||
canAddBdLeader: true,
|
||||
canAddBdLeaderInRegion: false,
|
||||
canAddSuperadmin: true,
|
||||
canBlockUser: true,
|
||||
canGrantAvatarFrame: true,
|
||||
@ -86,6 +119,8 @@ function managerFixture() {
|
||||
bdLeaderCount: 3,
|
||||
canAddAdmin: true,
|
||||
canAddBdLeader: false,
|
||||
canAddBdLeaderInRegion: false,
|
||||
bdLeaderRegionExpansionConfigurable: true,
|
||||
canAddSuperadmin: true,
|
||||
canBlockUser: true,
|
||||
canGrantAvatarFrame: true,
|
||||
|
||||
@ -132,6 +132,7 @@ test("manager schema defaults vip grant permission on and preserves explicit fal
|
||||
expect(disabledResult.success).toBe(true);
|
||||
if (defaultResult.success) {
|
||||
expect(defaultResult.data.canGrantVip).toBe(true);
|
||||
expect(defaultResult.data.canAddBdLeaderInRegion).toBe(false);
|
||||
}
|
||||
if (disabledResult.success) {
|
||||
expect(disabledResult.data.canGrantVip).toBe(false);
|
||||
|
||||
@ -112,6 +112,7 @@ export const createCoinSellerSchema = commandContactSchema.extend({
|
||||
export const createManagerSchema = z.object({
|
||||
canAddAdmin: z.boolean().optional().default(true),
|
||||
canAddBdLeader: z.boolean().optional().default(true),
|
||||
canAddBdLeaderInRegion: z.boolean().optional().default(false),
|
||||
canAddSuperadmin: z.boolean().optional().default(true),
|
||||
canBlockUser: z.boolean().optional().default(true),
|
||||
canGrantAvatarFrame: z.boolean().optional().default(true),
|
||||
|
||||
14
src/shared/api/generated/schema.d.ts
vendored
14
src/shared/api/generated/schema.d.ts
vendored
@ -4677,6 +4677,7 @@ export interface components {
|
||||
total: number;
|
||||
};
|
||||
ApiPageManager: {
|
||||
bdLeaderInviteScopePolicy: components["schemas"]["ManagerRoleScopePolicy"];
|
||||
items: components["schemas"]["Manager"][];
|
||||
page: number;
|
||||
pageSize: number;
|
||||
@ -5056,11 +5057,22 @@ export interface components {
|
||||
status?: string;
|
||||
updatedAtMs?: number;
|
||||
};
|
||||
ManagerRoleScopePolicy: {
|
||||
/** @enum {string} */
|
||||
baseScope: "country" | "region" | "global";
|
||||
regionExpansionConfigurable: boolean;
|
||||
};
|
||||
Manager: {
|
||||
avatar?: string;
|
||||
bdLeaderCount?: number;
|
||||
canAddAdmin?: boolean;
|
||||
canAddBdLeader?: boolean;
|
||||
canAddBdLeaderInRegion?: boolean;
|
||||
/** @enum {string} */
|
||||
bdLeaderInviteBaseScope?: "country" | "region" | "global";
|
||||
/** @enum {string} */
|
||||
bdLeaderInviteEffectiveScope?: "country" | "region" | "global";
|
||||
bdLeaderRegionExpansionConfigurable?: boolean;
|
||||
canAddSuperadmin?: boolean;
|
||||
canBlockUser?: boolean;
|
||||
canGrantAvatarFrame?: boolean;
|
||||
@ -5232,6 +5244,7 @@ export interface components {
|
||||
CreateManagerPayload: {
|
||||
canAddAdmin?: boolean;
|
||||
canAddBdLeader?: boolean;
|
||||
canAddBdLeaderInRegion?: boolean;
|
||||
canAddSuperadmin?: boolean;
|
||||
canBlockUser?: boolean;
|
||||
canGrantAvatarFrame?: boolean;
|
||||
@ -5246,6 +5259,7 @@ export interface components {
|
||||
UpdateManagerPayload: {
|
||||
canAddAdmin?: boolean;
|
||||
canAddBdLeader?: boolean;
|
||||
canAddBdLeaderInRegion?: boolean;
|
||||
canAddSuperadmin?: boolean;
|
||||
canBlockUser?: boolean;
|
||||
canGrantAvatarFrame?: boolean;
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
export interface ApiPage<TItem> {
|
||||
bdLeaderInviteScopePolicy?: ManagerRoleScopePolicy;
|
||||
items: TItem[];
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface ManagerRoleScopePolicy {
|
||||
baseScope: "country" | "region" | "global";
|
||||
regionExpansionConfigurable: boolean;
|
||||
}
|
||||
|
||||
export interface ApiList<TItem> {
|
||||
items: TItem[];
|
||||
total: number;
|
||||
@ -756,6 +762,10 @@ export interface ManagerDto {
|
||||
bdLeaderCount?: number;
|
||||
canAddAdmin?: boolean;
|
||||
canAddBdLeader?: boolean;
|
||||
canAddBdLeaderInRegion?: boolean;
|
||||
bdLeaderInviteBaseScope?: "country" | "region" | "global";
|
||||
bdLeaderInviteEffectiveScope?: "country" | "region" | "global";
|
||||
bdLeaderRegionExpansionConfigurable?: boolean;
|
||||
canAddSuperadmin?: boolean;
|
||||
canBlockUser?: boolean;
|
||||
canGrantAvatarFrame?: boolean;
|
||||
@ -779,6 +789,7 @@ export interface ManagerDto {
|
||||
export interface CreateManagerPayload {
|
||||
canAddAdmin?: boolean;
|
||||
canAddBdLeader?: boolean;
|
||||
canAddBdLeaderInRegion?: boolean;
|
||||
canAddSuperadmin?: boolean;
|
||||
canBlockUser?: boolean;
|
||||
canGrantAvatarFrame?: boolean;
|
||||
@ -794,6 +805,7 @@ export interface CreateManagerPayload {
|
||||
export interface UpdateManagerPayload {
|
||||
canAddAdmin?: boolean;
|
||||
canAddBdLeader?: boolean;
|
||||
canAddBdLeaderInRegion?: boolean;
|
||||
canAddSuperadmin?: boolean;
|
||||
canBlockUser?: boolean;
|
||||
canGrantAvatarFrame?: boolean;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user