feat: complete login and user management flow

This commit is contained in:
hectorzhao
2026-07-02 16:28:48 +08:00
parent 9c639d54b9
commit e26d8324c3
22 changed files with 1320 additions and 300 deletions
+73 -6
View File
@@ -1,3 +1,5 @@
import { getSessionTenantId, readSession, type LoginSession } from './session';
type RequestOptions = RequestInit & {
tenantId?: string;
};
@@ -7,8 +9,13 @@ export const DEFAULT_CLIENT_TENANT_ID = 'tenant-a';
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
const headers = new Headers(options.headers);
headers.set('Content-Type', 'application/json');
if (options.tenantId) {
headers.set('x-tenant-id', options.tenantId);
const session = readSession();
if (session?.accessToken) {
headers.set('Authorization', `${session.tokenType} ${session.accessToken}`);
}
const tenantId = options.tenantId ?? (path.startsWith('/client') ? getSessionTenantId() : undefined);
if (tenantId) {
headers.set('x-tenant-id', tenantId);
}
const response = await fetch(`/api${path}`, { ...options, headers });
if (!response.ok) {
@@ -83,6 +90,40 @@ export type TenantOption = {
status: string;
};
export type CaptchaResponse = {
captchaId: string;
challenge: string;
expiresInSeconds: number;
};
export type ManagedUser = {
id: string;
tenantId?: string | null;
username: string;
email?: string | null;
phone?: string | null;
displayName: string;
status: string;
failedLoginCount: number;
lockedUntil?: string | null;
lastLoginAt?: string | null;
createdAt: string;
tenant?: TenantOption | null;
roles: Array<{ role: { code: string; name: string; scope: string } }>;
};
export type UserPayload = {
tenantId?: string | null;
username?: string;
email?: string | null;
phone?: string | null;
displayName: string;
password?: string;
status?: string;
roleCode: 'platform_admin' | 'enterprise_admin';
operatorId?: string;
};
export type DashboardResponse = {
taskCount: number;
messageStatus: Array<{ status: string; _count: { _all: number }; _sum: { amountCents?: number | null; billingUnits?: number | null } }>;
@@ -223,7 +264,18 @@ function withQuery(path: string, query: Record<string, string | number | undefin
}
export const adminApi = {
getCaptcha: () => request<CaptchaResponse>('/admin/auth/captcha'),
login: (body: { login: string; password: string; captchaId: string; captchaText: string }) =>
request<LoginSession>('/admin/auth/login', { method: 'POST', body: JSON.stringify(body) }),
listTenants: () => request<TenantOption[]>('/admin/tenants'),
listUsers: (query: { tenantId?: string; roleCode?: string } = {}) => request<ManagedUser[]>(withQuery('/admin/users', query)),
createUser: (body: UserPayload) => request<ManagedUser>('/admin/users', { method: 'POST', body: JSON.stringify(body) }),
updateUser: (id: string, body: Omit<UserPayload, 'password'>) => request<ManagedUser>(`/admin/users/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
changeUserStatus: (id: string, status: string, operatorId?: string) =>
request<ManagedUser>(`/admin/users/${id}/status`, { method: 'POST', body: JSON.stringify({ status, operatorId }) }),
deleteUser: (id: string, operatorId?: string) => request<ManagedUser>(`/admin/users/${id}`, { method: 'DELETE', body: JSON.stringify({ operatorId }) }),
changeUserPassword: (id: string, password: string, operatorId?: string) =>
request<ManagedUser>(`/admin/users/${id}/password`, { method: 'POST', body: JSON.stringify({ password, operatorId }) }),
getDashboard: (tenantId?: string) => request<DashboardResponse>(withQuery('/admin/operations/dashboard/statistics', { tenantId })),
listSystemLogs: (query: { tenantId?: string; keyword?: string; level?: string; module?: string; range?: string; page?: number; pageSize?: number }) =>
request<OperationLogResponse>(withQuery('/admin/system-logs', query)),
@@ -293,12 +345,27 @@ export const adminApi = {
};
export const clientApi = {
getDashboard: (tenantId = DEFAULT_CLIENT_TENANT_ID) =>
getCaptcha: () => request<CaptchaResponse>('/client/auth/captcha'),
login: (body: { login: string; password: string; captchaId: string; captchaText: string }) =>
request<LoginSession>('/client/auth/login', { method: 'POST', body: JSON.stringify(body) }),
listUsers: (tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ManagedUser[]>('/client/users', { tenantId }),
createUser: (body: UserPayload, tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ManagedUser>('/client/users', { method: 'POST', tenantId, body: JSON.stringify(body) }),
updateUser: (id: string, body: Omit<UserPayload, 'password'>, tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ManagedUser>(`/client/users/${id}`, { method: 'PUT', tenantId, body: JSON.stringify(body) }),
changeUserStatus: (id: string, status: string, operatorId?: string, tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ManagedUser>(`/client/users/${id}/status`, { method: 'POST', tenantId, body: JSON.stringify({ status, operatorId }) }),
deleteUser: (id: string, operatorId?: string, tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ManagedUser>(`/client/users/${id}`, { method: 'DELETE', tenantId, body: JSON.stringify({ operatorId }) }),
changeUserPassword: (id: string, password: string, operatorId?: string, tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ManagedUser>(`/client/users/${id}/password`, { method: 'POST', tenantId, body: JSON.stringify({ password, operatorId }) }),
getDashboard: (tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<DashboardResponse>('/client/operations/dashboard', { tenantId }),
listSystemLogs: (query: { keyword?: string; level?: string; module?: string; range?: string; page?: number; pageSize?: number }, tenantId = DEFAULT_CLIENT_TENANT_ID) =>
listSystemLogs: (query: { keyword?: string; level?: string; module?: string; range?: string; page?: number; pageSize?: number }, tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<OperationLogResponse>(withQuery('/client/operations/system-logs', query), { tenantId }),
listTransactions: (tenantId = DEFAULT_CLIENT_TENANT_ID) =>
listTransactions: (tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<AccountTransaction[]>('/client/billing/transactions', { tenantId }),
listOrders: (tenantId = DEFAULT_CLIENT_TENANT_ID) =>
listOrders: (tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<RechargeOrder[]>('/client/billing/orders', { tenantId }),
};
+42
View File
@@ -0,0 +1,42 @@
export type Portal = 'admin' | 'client';
export type SessionUser = {
id: string;
tenantId?: string | null;
tenantName?: string | null;
username: string;
email?: string | null;
phone?: string | null;
displayName: string;
roles: string[];
};
export type LoginSession = {
accessToken: string;
tokenType: string;
portal: Portal;
user: SessionUser;
};
const sessionKey = 'cmpp-auth-session';
export function readSession(): LoginSession | null {
try {
const raw = window.localStorage.getItem(sessionKey);
return raw ? JSON.parse(raw) as LoginSession : null;
} catch {
return null;
}
}
export function writeSession(session: LoginSession) {
window.localStorage.setItem(sessionKey, JSON.stringify(session));
}
export function clearSession() {
window.localStorage.removeItem(sessionKey);
}
export function getSessionTenantId() {
return readSession()?.user.tenantId ?? undefined;
}