Initial commit: Admin panel
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
const TOKEN_KEY = 'noro_admin_token'
|
||||
|
||||
export interface User {
|
||||
email: string
|
||||
role: string
|
||||
}
|
||||
|
||||
export interface NavItem {
|
||||
label: string
|
||||
to: string
|
||||
}
|
||||
|
||||
export interface PortfolioCategory {
|
||||
slug: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: string
|
||||
title: string
|
||||
category: string
|
||||
description: string
|
||||
longDescription?: string
|
||||
year?: string
|
||||
featured?: boolean
|
||||
imageUrl?: string
|
||||
galleryUrls?: string[]
|
||||
client?: string
|
||||
deliverables?: string[]
|
||||
seoTitle?: string
|
||||
seoDescription?: string
|
||||
}
|
||||
|
||||
export interface ServiceStep {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface ServiceFaq {
|
||||
question: string
|
||||
answer: string
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
id: string
|
||||
title: string
|
||||
subtitle: string
|
||||
description: string
|
||||
intro: string
|
||||
includes: string[]
|
||||
benefits: string[]
|
||||
process: ServiceStep[]
|
||||
deliverables: string[]
|
||||
faq: ServiceFaq[]
|
||||
iconUrl?: string
|
||||
backgroundUrl?: string
|
||||
portfolioCategorySlugs?: string[]
|
||||
}
|
||||
|
||||
export interface Client {
|
||||
id: string
|
||||
name: string
|
||||
logoUrl: string
|
||||
}
|
||||
|
||||
export interface BlogPost {
|
||||
id: string
|
||||
title: string
|
||||
slug: string
|
||||
excerpt?: string
|
||||
content?: string
|
||||
coverUrl?: string
|
||||
publishedAt?: string
|
||||
published?: boolean
|
||||
seoTitle?: string
|
||||
seoDescription?: string
|
||||
}
|
||||
|
||||
export interface MediaItem {
|
||||
id: string
|
||||
filename: string
|
||||
url: string
|
||||
mimeType?: string
|
||||
size?: number
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
export interface SiteSettings {
|
||||
name?: string
|
||||
nameEn?: string
|
||||
tagline?: string
|
||||
description?: string
|
||||
email?: string
|
||||
website?: string
|
||||
phone?: string
|
||||
phoneTel?: string
|
||||
location?: string
|
||||
messengerHandle?: string
|
||||
messengersLabel?: string
|
||||
messengers?: Array<{ id: string; name: string; url: string; iconUrl?: string }>
|
||||
seo?: { defaultTitle?: string; defaultDescription?: string }
|
||||
hero?: { darkImageUrl?: string; lightImageUrl?: string }
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
class ApiError extends Error {
|
||||
status: number
|
||||
|
||||
constructor(message: string, status: number) {
|
||||
super(message)
|
||||
this.status = status
|
||||
this.name = 'ApiError'
|
||||
}
|
||||
}
|
||||
|
||||
export function getToken(): string | null {
|
||||
return localStorage.getItem(TOKEN_KEY)
|
||||
}
|
||||
|
||||
export function setToken(token: string): void {
|
||||
localStorage.setItem(TOKEN_KEY, token)
|
||||
}
|
||||
|
||||
export function clearToken(): void {
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
}
|
||||
|
||||
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
...(options.headers as Record<string, string> | undefined),
|
||||
}
|
||||
|
||||
const token = getToken()
|
||||
if (token) headers.Authorization = `Bearer ${token}`
|
||||
|
||||
const isFormData = options.body instanceof FormData
|
||||
if (options.body && !isFormData && !headers['Content-Type']) {
|
||||
headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
|
||||
const res = await fetch(`/api${path}`, { ...options, headers })
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({ error: res.statusText }))
|
||||
throw new ApiError(body.error || res.statusText, res.status)
|
||||
}
|
||||
|
||||
if (res.status === 204) return undefined as T
|
||||
const text = await res.text()
|
||||
return text ? JSON.parse(text) : (undefined as T)
|
||||
}
|
||||
|
||||
export const api = {
|
||||
login(email: string, password: string) {
|
||||
return request<{ token: string; user: User }>('/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password }),
|
||||
})
|
||||
},
|
||||
|
||||
me() {
|
||||
return request<{ user: User }>('/auth/me')
|
||||
},
|
||||
|
||||
getSite() {
|
||||
return request<SiteSettings>('/site')
|
||||
},
|
||||
|
||||
putSite(data: SiteSettings) {
|
||||
return request<SiteSettings>('/site', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
},
|
||||
|
||||
getNavigation() {
|
||||
return request<NavItem[]>('/navigation')
|
||||
},
|
||||
|
||||
putNavigation(data: NavItem[]) {
|
||||
return request<NavItem[]>('/navigation', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
},
|
||||
|
||||
getPortfolioCategories() {
|
||||
return request<PortfolioCategory[]>('/portfolio/categories')
|
||||
},
|
||||
|
||||
getProjects() {
|
||||
return request<Project[]>('/portfolio/projects')
|
||||
},
|
||||
|
||||
getProject(id: string) {
|
||||
return request<Project>(`/portfolio/projects/${id}`)
|
||||
},
|
||||
|
||||
createProject(data: Partial<Project>) {
|
||||
return request<Project>('/portfolio/projects', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
},
|
||||
|
||||
updateProject(id: string, data: Partial<Project>) {
|
||||
return request<Project>(`/portfolio/projects/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
},
|
||||
|
||||
deleteProject(id: string) {
|
||||
return request<void>(`/portfolio/projects/${id}`, { method: 'DELETE' })
|
||||
},
|
||||
|
||||
getServices() {
|
||||
return request<Service[]>('/services')
|
||||
},
|
||||
|
||||
getService(id: string) {
|
||||
return request<Service>(`/services/${id}`)
|
||||
},
|
||||
|
||||
putServices(data: Service[]) {
|
||||
return request<Service[]>('/services', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
},
|
||||
|
||||
updateService(id: string, data: Partial<Service>) {
|
||||
return request<Service>(`/services/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
},
|
||||
|
||||
getClients() {
|
||||
return request<Client[]>('/clients')
|
||||
},
|
||||
|
||||
putClients(data: Client[]) {
|
||||
return request<Client[]>('/clients', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
},
|
||||
|
||||
getBlogPosts() {
|
||||
return request<BlogPost[]>('/blog/posts/all')
|
||||
},
|
||||
|
||||
getBlogPost(id: string) {
|
||||
return request<BlogPost>(`/blog/posts/${id}`)
|
||||
},
|
||||
|
||||
createBlogPost(data: Partial<BlogPost>) {
|
||||
return request<BlogPost>('/blog/posts', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
},
|
||||
|
||||
updateBlogPost(id: string, data: Partial<BlogPost>) {
|
||||
return request<BlogPost>(`/blog/posts/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
},
|
||||
|
||||
deleteBlogPost(id: string) {
|
||||
return request<void>(`/blog/posts/${id}`, { method: 'DELETE' })
|
||||
},
|
||||
|
||||
getMedia() {
|
||||
return request<MediaItem[]>('/media')
|
||||
},
|
||||
|
||||
uploadMedia(file: File) {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
return request<MediaItem>('/media/upload', {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export { ApiError }
|
||||
Reference in New Issue
Block a user