feat: Add CSS template system with custom gallery styling support
## Changes ### CSS Template System - Added CSS class hooks to gallery components for custom template targeting - Gallery sidebar, header, footer, and photo cards can now be styled via CSS templates - CSS variables on :root allow themes to override colors, effects, and spacing ### Gallery Component CSS Classes Added - `.gallery-page` - Main gallery container - `.gallery-header` - Top header bar - `.gallery-sidebar` - Filter/download sidebar - `.gallery-sidebar-header`, `.gallery-sidebar-title`, `.gallery-sidebar-close` - `.gallery-sidebar-content`, `.gallery-sidebar-section` - `.gallery-sidebar-search-input`, `.gallery-sidebar-search-icon` - `.gallery-sidebar-backdrop` - Mobile overlay - `.gallery-btn`, `.gallery-btn-download` - Sidebar buttons - `.gallery-footer` - Footer section - `.photo-card`, `.photo-grid` - Photo display elements ### CSS Templates (Database) - Elegant Dark (id=1): Dark navy theme with light text and red accents - Liquid Glass Light (id=2): iOS 26 frosted glass effect with gradient background ### Bug Fixes - Fixed CSS variables not inheriting (moved from .gallery-page to :root) - Fixed sidebar position breaking layout (removed position: relative override) - Fixed Elegant Dark sidebar text visibility (white on white issue) ### Other Changes - Settings page refactoring and cleanup - i18n locale updates for new gallery features - Vite proxy port configuration fix - Admin auth route improvements - CSS templates service updates
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Custom error classes for standardized error handling across the application.
|
||||
* These errors are caught by the global error handler and converted to appropriate HTTP responses.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for operational errors (expected errors that can occur during normal operation)
|
||||
*/
|
||||
class AppError extends Error {
|
||||
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.code = code;
|
||||
this.isOperational = true;
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
error: this.message,
|
||||
code: this.code,
|
||||
...(process.env.NODE_ENV === 'development' && { stack: this.stack })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation error - for invalid input data (400 Bad Request)
|
||||
*/
|
||||
class ValidationError extends AppError {
|
||||
constructor(message = 'Validation failed', details = null) {
|
||||
super(message, 400, 'VALIDATION_ERROR');
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
...(this.details && { details: this.details })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not found error - for resources that don't exist (404 Not Found)
|
||||
*/
|
||||
class NotFoundError extends AppError {
|
||||
constructor(resource = 'Resource', identifier = null) {
|
||||
const message = identifier
|
||||
? `${resource} with identifier '${identifier}' not found`
|
||||
: `${resource} not found`;
|
||||
super(message, 404, 'NOT_FOUND');
|
||||
this.resource = resource;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unauthorized error - for missing or invalid authentication (401 Unauthorized)
|
||||
*/
|
||||
class UnauthorizedError extends AppError {
|
||||
constructor(message = 'Authentication required') {
|
||||
super(message, 401, 'UNAUTHORIZED');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forbidden error - for insufficient permissions (403 Forbidden)
|
||||
*/
|
||||
class ForbiddenError extends AppError {
|
||||
constructor(message = 'Access denied') {
|
||||
super(message, 403, 'FORBIDDEN');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Conflict error - for resource conflicts (409 Conflict)
|
||||
*/
|
||||
class ConflictError extends AppError {
|
||||
constructor(message = 'Resource conflict', field = null) {
|
||||
super(message, 409, 'CONFLICT');
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
...(this.field && { field: this.field })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate limit error - for too many requests (429 Too Many Requests)
|
||||
*/
|
||||
class RateLimitError extends AppError {
|
||||
constructor(message = 'Too many requests', retryAfter = null) {
|
||||
super(message, 429, 'RATE_LIMIT_EXCEEDED');
|
||||
this.retryAfter = retryAfter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Service unavailable error - for maintenance mode or service issues (503 Service Unavailable)
|
||||
*/
|
||||
class ServiceUnavailableError extends AppError {
|
||||
constructor(message = 'Service temporarily unavailable') {
|
||||
super(message, 503, 'SERVICE_UNAVAILABLE');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AppError,
|
||||
ValidationError,
|
||||
NotFoundError,
|
||||
UnauthorizedError,
|
||||
ForbiddenError,
|
||||
ConflictError,
|
||||
RateLimitError,
|
||||
ServiceUnavailableError
|
||||
};
|
||||
Reference in New Issue
Block a user