feat: add shadcn admin workspace

This commit is contained in:
2026-03-28 17:56:36 +08:00
parent ec96d91548
commit 178434d63e
41 changed files with 6153 additions and 16 deletions

87
admin/src/lib/api.ts Normal file
View File

@@ -0,0 +1,87 @@
import type {
AdminAiReindexResponse,
AdminDashboardResponse,
AdminSessionResponse,
AdminSiteSettingsResponse,
SiteSettingsPayload,
} from '@/lib/types'
const API_BASE = import.meta.env.VITE_API_BASE?.trim() || ''
export class ApiError extends Error {
status: number
constructor(message: string, status: number) {
super(message)
this.name = 'ApiError'
this.status = status
}
}
async function readErrorMessage(response: Response) {
const text = await response.text().catch(() => '')
if (!text) {
return `Request failed with status ${response.status}.`
}
try {
const parsed = JSON.parse(text) as { description?: string; error?: string; message?: string }
return parsed.description || parsed.error || parsed.message || text
} catch {
return text
}
}
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const headers = new Headers(init?.headers)
if (init?.body && !(init.body instanceof FormData) && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json')
}
const response = await fetch(`${API_BASE}${path}`, {
...init,
headers,
})
if (!response.ok) {
throw new ApiError(await readErrorMessage(response), response.status)
}
if (response.status === 204) {
return undefined as T
}
const contentType = response.headers.get('content-type') || ''
if (contentType.includes('application/json')) {
return (await response.json()) as T
}
return (await response.text()) as T
}
export const adminApi = {
sessionStatus: () => request<AdminSessionResponse>('/api/admin/session'),
login: (payload: { username: string; password: string }) =>
request<AdminSessionResponse>('/api/admin/session/login', {
method: 'POST',
body: JSON.stringify(payload),
}),
logout: () =>
request<AdminSessionResponse>('/api/admin/session', {
method: 'DELETE',
}),
dashboard: () => request<AdminDashboardResponse>('/api/admin/dashboard'),
getSiteSettings: () => request<AdminSiteSettingsResponse>('/api/admin/site-settings'),
updateSiteSettings: (payload: SiteSettingsPayload) =>
request<AdminSiteSettingsResponse>('/api/admin/site-settings', {
method: 'PATCH',
body: JSON.stringify(payload),
}),
reindexAi: () =>
request<AdminAiReindexResponse>('/api/admin/ai/reindex', {
method: 'POST',
}),
}