Initial commit: Admin panel

This commit is contained in:
Noro Studio
2026-06-30 00:50:13 +03:30
commit 79491cd8ef
36 changed files with 6448 additions and 0 deletions
+290
View File
@@ -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 }