Frontend update

This commit is contained in:
2026-06-26 21:51:27 -05:00
parent eef79e2403
commit 6dba6a017c
48 changed files with 5004 additions and 0 deletions

87
client/src/api/client.js Normal file
View File

@@ -0,0 +1,87 @@
// Thin fetch wrapper. Always sends cookies (httpOnly JWT) and talks to the
// same-origin API (/api/v1) — proxied to the Express server in dev.
const BASE = '/api/v1'
class ApiError extends Error {
constructor(status, message, body) {
super(message)
this.status = status
this.body = body
}
}
async function req(path, { method = 'GET', body, headers, raw } = {}) {
const opts = { method, credentials: 'include', headers: { ...headers } }
if (body !== undefined) {
if (raw) {
opts.body = body // FormData — let the browser set the content-type
} else {
opts.headers['Content-Type'] = 'application/json'
opts.body = JSON.stringify(body)
}
}
const res = await fetch(BASE + path, opts)
const text = await res.text()
const data = text ? safeParse(text) : null
if (!res.ok) {
const message = (data && data.message) || res.statusText || 'Request failed'
throw new ApiError(res.status, message, data)
}
return data
}
function safeParse(text) {
try {
return JSON.parse(text)
} catch {
return text
}
}
export const api = {
// ----- auth -----
me: () => req('/auth/me'),
login: (username, password) => req('/auth/login', { method: 'POST', body: { username, password } }),
logout: () => req('/auth/logout', { method: 'POST' }),
// ----- public -----
publicSettings: () => req('/public/settings'),
status: () => req('/public/status'),
posts: (category) => req(`/public/posts/${category}`),
post: (category, idOrSlug) => req(`/public/posts/${category}/${idOrSlug}`),
wiki: () => req('/public/wiki'),
wikiPage: (slug) => req(`/public/wiki/${slug}`),
contact: (payload) => req('/public/contact', { method: 'POST', body: payload }),
// ----- admin -----
admin: {
dashboard: () => req('/admin/dashboard'),
setSiteMode: (mode) => req('/admin/site-mode', { method: 'PUT', body: { mode } }),
listPosts: (category) => req(`/admin/posts${category ? `?category=${category}` : ''}`),
getPost: (id) => req(`/admin/posts/${id}`),
createPost: (data) => req('/admin/posts', { method: 'POST', body: data }),
updatePost: (id, data) => req(`/admin/posts/${id}`, { method: 'PUT', body: data }),
deletePost: (id) => req(`/admin/posts/${id}`, { method: 'DELETE' }),
publishPost: (id, published) =>
req(`/admin/posts/${id}/publish`, { method: 'PATCH', body: { published } }),
uploadImage: (file) => {
const fd = new FormData()
fd.append('image', file)
return req('/admin/posts/upload', { method: 'POST', body: fd, raw: true })
},
listWiki: () => req('/admin/wiki'),
getWiki: (slug) => req(`/admin/wiki/${slug}`),
createWiki: (data) => req('/admin/wiki', { method: 'POST', body: data }),
updateWiki: (slug, data) => req(`/admin/wiki/${slug}`, { method: 'PUT', body: data }),
deleteWiki: (slug) => req(`/admin/wiki/${slug}`, { method: 'DELETE' }),
getSettings: () => req('/admin/settings'),
updateSettings: (obj) => req('/admin/settings', { method: 'PUT', body: obj }),
activity: (limit = 50) => req(`/admin/activity?limit=${limit}`),
listUsers: () => req('/admin/users'),
createUser: (data) => req('/admin/users', { method: 'POST', body: data }),
updateUser: (id, data) => req(`/admin/users/${id}`, { method: 'PUT', body: data }),
deleteUser: (id) => req(`/admin/users/${id}`, { method: 'DELETE' }),
},
}
export { ApiError }