2026-01-09 21:36:49 +00:00
|
|
|
import { adminApiClient } from './client'
|
|
|
|
|
import type { AxiosError } from 'axios'
|
|
|
|
|
|
|
|
|
|
export interface ThemeColors {
|
|
|
|
|
bgPrimary: string
|
|
|
|
|
bgOverlay: string
|
|
|
|
|
bgCard: string
|
|
|
|
|
bgCardHover: string
|
|
|
|
|
textPrimary: string
|
|
|
|
|
textSecondary: string
|
|
|
|
|
textGlow: string
|
|
|
|
|
accentPrimary: string
|
|
|
|
|
accentSecondary: string
|
|
|
|
|
accentSuccess: string
|
|
|
|
|
borderColor: string
|
|
|
|
|
borderGlow: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ThemeSettings {
|
|
|
|
|
shadowSm: string
|
|
|
|
|
shadowMd: string
|
|
|
|
|
shadowLg: string
|
|
|
|
|
blurAmount: string
|
|
|
|
|
borderRadiusSm: string
|
|
|
|
|
borderRadiusMd: string
|
|
|
|
|
borderRadiusLg: string
|
|
|
|
|
animationSpeed: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Theme {
|
|
|
|
|
id: string
|
|
|
|
|
name: string
|
2026-01-09 23:27:21 +00:00
|
|
|
icon?: string | null
|
|
|
|
|
description?: string | null
|
2026-01-09 21:36:49 +00:00
|
|
|
isPublic: boolean
|
|
|
|
|
colors: ThemeColors
|
|
|
|
|
settings: ThemeSettings
|
|
|
|
|
createdAt: string
|
|
|
|
|
updatedAt: string
|
|
|
|
|
creator?: {
|
|
|
|
|
id: string
|
|
|
|
|
name: string | null
|
|
|
|
|
email: string | null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ThemePreview {
|
|
|
|
|
id: string
|
|
|
|
|
name: string
|
2026-01-09 23:27:21 +00:00
|
|
|
icon?: string | null
|
|
|
|
|
description?: string | null
|
2026-01-09 21:36:49 +00:00
|
|
|
isPublic: boolean
|
|
|
|
|
colors: ThemeColors
|
|
|
|
|
settings: ThemeSettings
|
|
|
|
|
createdAt: string
|
|
|
|
|
creator?: {
|
|
|
|
|
id: string
|
|
|
|
|
name: string | null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CreateThemeDto {
|
|
|
|
|
name: string
|
2026-01-09 23:27:21 +00:00
|
|
|
icon?: string
|
|
|
|
|
description?: string
|
2026-01-09 21:36:49 +00:00
|
|
|
isPublic?: boolean
|
|
|
|
|
colors: ThemeColors
|
|
|
|
|
settings: ThemeSettings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface UpdateThemeDto {
|
|
|
|
|
name?: string
|
2026-01-09 23:27:21 +00:00
|
|
|
icon?: string
|
|
|
|
|
description?: string
|
2026-01-09 21:36:49 +00:00
|
|
|
isPublic?: boolean
|
|
|
|
|
colors?: ThemeColors
|
|
|
|
|
settings?: ThemeSettings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PaginatedThemesResponse {
|
|
|
|
|
themes: ThemePreview[]
|
|
|
|
|
total: number
|
|
|
|
|
page: number
|
|
|
|
|
limit: number
|
|
|
|
|
totalPages: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ThemesApiError {
|
|
|
|
|
message: string
|
|
|
|
|
statusCode?: number
|
|
|
|
|
originalError?: unknown
|
|
|
|
|
name: 'ThemesApiError'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createThemesApiError(
|
|
|
|
|
message: string,
|
|
|
|
|
statusCode?: number,
|
|
|
|
|
originalError?: unknown
|
|
|
|
|
): ThemesApiError {
|
|
|
|
|
return {
|
|
|
|
|
message,
|
|
|
|
|
statusCode,
|
|
|
|
|
originalError,
|
|
|
|
|
name: 'ThemesApiError',
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isThemesApiError(error: unknown): error is ThemesApiError {
|
|
|
|
|
return (
|
|
|
|
|
typeof error === 'object' &&
|
|
|
|
|
error !== null &&
|
|
|
|
|
'name' in error &&
|
|
|
|
|
error.name === 'ThemesApiError'
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const themesApi = {
|
|
|
|
|
getThemes: async (params?: {
|
|
|
|
|
page?: number
|
|
|
|
|
limit?: number
|
|
|
|
|
search?: string
|
|
|
|
|
isPublic?: boolean
|
|
|
|
|
}): Promise<PaginatedThemesResponse> => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await adminApiClient.get('/api/admin/themes', {
|
|
|
|
|
params: {
|
|
|
|
|
page: params?.page || 1,
|
|
|
|
|
limit: params?.limit || 20,
|
|
|
|
|
search: params?.search,
|
|
|
|
|
isPublic: params?.isPublic,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
return response.data
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const axiosError = error as AxiosError<{ message?: string }>
|
|
|
|
|
throw createThemesApiError(
|
|
|
|
|
axiosError.response?.data?.message || 'Failed to load themes',
|
|
|
|
|
axiosError.response?.status,
|
|
|
|
|
error
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getTheme: async (themeId: string): Promise<Theme> => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await adminApiClient.get(`/api/admin/themes/${themeId}`)
|
|
|
|
|
return response.data
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const axiosError = error as AxiosError<{ message?: string }>
|
|
|
|
|
if (axiosError.response?.status === 404) {
|
|
|
|
|
throw createThemesApiError(
|
|
|
|
|
`Theme not found: The theme with ID "${themeId}" does not exist.`,
|
|
|
|
|
axiosError.response.status,
|
|
|
|
|
error
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
throw createThemesApiError(
|
|
|
|
|
axiosError.response?.data?.message || `Failed to load theme ${themeId}`,
|
|
|
|
|
axiosError.response?.status,
|
|
|
|
|
error
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
createTheme: async (theme: CreateThemeDto): Promise<Theme> => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await adminApiClient.post('/api/admin/themes', theme)
|
|
|
|
|
return response.data
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const axiosError = error as AxiosError<{ message?: string }>
|
|
|
|
|
throw createThemesApiError(
|
|
|
|
|
axiosError.response?.data?.message || 'Failed to create theme',
|
|
|
|
|
axiosError.response?.status,
|
|
|
|
|
error
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
updateTheme: async (themeId: string, theme: UpdateThemeDto): Promise<Theme> => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await adminApiClient.patch(`/api/admin/themes/${themeId}`, theme)
|
|
|
|
|
return response.data
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const axiosError = error as AxiosError<{ message?: string }>
|
|
|
|
|
if (axiosError.response?.status === 404) {
|
|
|
|
|
throw createThemesApiError(
|
|
|
|
|
`Theme not found: The theme with ID "${themeId}" does not exist.`,
|
|
|
|
|
axiosError.response.status,
|
|
|
|
|
error
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
throw createThemesApiError(
|
|
|
|
|
axiosError.response?.data?.message || `Failed to update theme ${themeId}`,
|
|
|
|
|
axiosError.response?.status,
|
|
|
|
|
error
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
deleteTheme: async (themeId: string): Promise<{ message: string }> => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await adminApiClient.delete(`/api/admin/themes/${themeId}`)
|
|
|
|
|
return response.data
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const axiosError = error as AxiosError<{ message?: string }>
|
|
|
|
|
if (axiosError.response?.status === 404) {
|
|
|
|
|
throw createThemesApiError(
|
|
|
|
|
`Theme not found: The theme with ID "${themeId}" does not exist.`,
|
|
|
|
|
axiosError.response.status,
|
|
|
|
|
error
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
throw createThemesApiError(
|
|
|
|
|
axiosError.response?.data?.message || `Failed to delete theme ${themeId}`,
|
|
|
|
|
axiosError.response?.status,
|
|
|
|
|
error
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const DEFAULT_THEME_COLORS: ThemeColors = {
|
|
|
|
|
bgPrimary: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
|
|
|
bgOverlay: 'rgba(0, 0, 0, 0.7)',
|
|
|
|
|
bgCard: 'rgba(255, 255, 255, 0.1)',
|
|
|
|
|
bgCardHover: 'rgba(255, 255, 255, 0.2)',
|
|
|
|
|
textPrimary: '#ffffff',
|
|
|
|
|
textSecondary: 'rgba(255, 255, 255, 0.8)',
|
|
|
|
|
textGlow: '#ffd700',
|
|
|
|
|
accentPrimary: '#ffd700',
|
|
|
|
|
accentSecondary: '#ffed4e',
|
|
|
|
|
accentSuccess: '#4ade80',
|
|
|
|
|
borderColor: 'rgba(255, 255, 255, 0.2)',
|
|
|
|
|
borderGlow: 'rgba(255, 215, 0, 0.3)',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const DEFAULT_THEME_SETTINGS: ThemeSettings = {
|
|
|
|
|
shadowSm: '0 1px 2px rgba(0, 0, 0, 0.1)',
|
|
|
|
|
shadowMd: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
|
|
|
|
shadowLg: '0 10px 15px rgba(0, 0, 0, 0.2)',
|
|
|
|
|
blurAmount: '10px',
|
|
|
|
|
borderRadiusSm: '4px',
|
|
|
|
|
borderRadiusMd: '8px',
|
|
|
|
|
borderRadiusLg: '12px',
|
|
|
|
|
animationSpeed: '0.3s',
|
|
|
|
|
}
|