chrysopedia/frontend/src/api/auth.ts
jlightner 4969935c76 feat: Added ConfirmModal component, Edit As button with write-mode conf…
- "frontend/src/components/ConfirmModal.tsx"
- "frontend/src/components/ConfirmModal.module.css"
- "frontend/src/api/auth.ts"
- "frontend/src/context/AuthContext.tsx"
- "frontend/src/pages/AdminUsers.tsx"
- "frontend/src/pages/AdminUsers.module.css"
- "frontend/src/components/ImpersonationBanner.tsx"
- "frontend/src/components/ImpersonationBanner.module.css"

GSD-Task: S07/T02
2026-04-04 06:27:38 +00:00

142 lines
3.8 KiB
TypeScript

import { request, BASE } from "./client";
// ── Types ────────────────────────────────────────────────────────────────────
export interface RegisterRequest {
email: string;
password: string;
display_name: string;
invite_code: string;
creator_slug?: string | null;
}
export interface LoginRequest {
email: string;
password: string;
}
export interface TokenResponse {
access_token: string;
token_type: string;
}
export interface UserResponse {
id: string;
email: string;
display_name: string;
role: string;
creator_id: string | null;
is_active: boolean;
created_at: string;
impersonating?: boolean;
}
export interface UserListItem {
id: string;
email: string;
display_name: string;
role: string;
creator_id: string | null;
is_active: boolean;
}
export interface ImpersonateResponse {
access_token: string;
token_type: string;
target_user: UserListItem;
}
export interface UpdateProfileRequest {
display_name?: string | null;
current_password?: string | null;
new_password?: string | null;
}
// ── Functions ────────────────────────────────────────────────────────────────
export async function authRegister(data: RegisterRequest): Promise<UserResponse> {
return request<UserResponse>(`${BASE}/auth/register`, {
method: "POST",
body: JSON.stringify(data),
});
}
export async function authLogin(email: string, password: string): Promise<TokenResponse> {
return request<TokenResponse>(`${BASE}/auth/login`, {
method: "POST",
body: JSON.stringify({ email, password }),
});
}
export async function authGetMe(token: string): Promise<UserResponse> {
return request<UserResponse>(`${BASE}/auth/me`, {
headers: { Authorization: `Bearer ${token}` },
});
}
export async function authUpdateProfile(
token: string,
data: UpdateProfileRequest,
): Promise<UserResponse> {
return request<UserResponse>(`${BASE}/auth/me`, {
method: "PUT",
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify(data),
});
}
// ── Admin: Impersonation ─────────────────────────────────────────────────────
export async function fetchUsers(token: string): Promise<UserListItem[]> {
return request<UserListItem[]>(`${BASE}/admin/users`, {
headers: { Authorization: `Bearer ${token}` },
});
}
export interface ImpersonationLogEntry {
id: number;
admin_name: string;
target_name: string;
action: string;
write_mode: boolean;
ip_address: string | null;
created_at: string;
}
export async function impersonateUser(
token: string,
userId: string,
writeMode?: boolean,
): Promise<ImpersonateResponse> {
const opts: RequestInit = {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
};
if (writeMode) {
(opts.headers as Record<string, string>)["Content-Type"] = "application/json";
opts.body = JSON.stringify({ write_mode: true });
}
return request<ImpersonateResponse>(
`${BASE}/admin/impersonate/${userId}`,
opts,
);
}
export async function fetchImpersonationLog(
token: string,
page: number = 1,
): Promise<ImpersonationLogEntry[]> {
return request<ImpersonationLogEntry[]>(
`${BASE}/admin/impersonation-log?page=${page}`,
{ headers: { Authorization: `Bearer ${token}` } },
);
}
export async function stopImpersonation(
token: string,
): Promise<{ message: string }> {
return request<{ message: string }>(`${BASE}/admin/impersonate/stop`, {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
});
}