diff --git a/Backend/src/system/routes/__pycache__/system_settings_routes.cpython-312.pyc b/Backend/src/system/routes/__pycache__/system_settings_routes.cpython-312.pyc
index 8bc55002..10f7bfa2 100644
Binary files a/Backend/src/system/routes/__pycache__/system_settings_routes.cpython-312.pyc and b/Backend/src/system/routes/__pycache__/system_settings_routes.cpython-312.pyc differ
diff --git a/Backend/src/system/routes/system_settings_routes.py b/Backend/src/system/routes/system_settings_routes.py
index a7c22c81..f9c5a1f2 100644
--- a/Backend/src/system/routes/system_settings_routes.py
+++ b/Backend/src/system/routes/system_settings_routes.py
@@ -1846,6 +1846,180 @@ async def upload_company_favicon(
logger.error(f"Error uploading favicon: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
+class UpdateThemeSettingsRequest(BaseModel):
+ theme_primary_color: Optional[str] = None
+ theme_primary_light: Optional[str] = None
+ theme_primary_dark: Optional[str] = None
+ theme_primary_accent: Optional[str] = None
+
+@router.get("/theme")
+async def get_theme_settings(
+ db: Session = Depends(get_db)
+):
+ """Get current theme color settings"""
+ try:
+ setting_keys = [
+ "theme_primary_color",
+ "theme_primary_light",
+ "theme_primary_dark",
+ "theme_primary_accent",
+ ]
+
+ settings_dict = {}
+ for key in setting_keys:
+ setting = db.query(SystemSettings).filter(
+ SystemSettings.key == key
+ ).first()
+ if setting:
+ settings_dict[key] = setting.value
+ else:
+ settings_dict[key] = None
+
+ # Get updated_at and updated_by from any theme setting
+ theme_setting = db.query(SystemSettings).filter(
+ SystemSettings.key == "theme_primary_color"
+ ).first()
+
+ updated_at = None
+ updated_by = None
+ if theme_setting:
+ updated_at = theme_setting.updated_at.isoformat() if theme_setting.updated_at else None
+ updated_by = theme_setting.updated_by.full_name if theme_setting.updated_by else None
+
+ return {
+ "status": "success",
+ "data": {
+ "theme_primary_color": settings_dict.get("theme_primary_color", "#d4af37"),
+ "theme_primary_light": settings_dict.get("theme_primary_light", "#f5d76e"),
+ "theme_primary_dark": settings_dict.get("theme_primary_dark", "#c9a227"),
+ "theme_primary_accent": settings_dict.get("theme_primary_accent", "#e8c547"),
+ "updated_at": updated_at,
+ "updated_by": updated_by,
+ }
+ }
+ except Exception as e:
+ logger.error(f"Error getting theme settings: {e}", exc_info=True)
+ raise HTTPException(status_code=500, detail=str(e))
+
+@router.put("/theme")
+async def update_theme_settings(
+ request_data: UpdateThemeSettingsRequest,
+ request: Request,
+ current_user: User = Depends(authorize_roles("admin")),
+ db: Session = Depends(get_db)
+):
+ """Update theme color settings (admin only)"""
+ import re
+
+ client_ip = request.client.host if request.client else None
+ user_agent = request.headers.get('User-Agent')
+ request_id = getattr(request.state, 'request_id', None)
+
+ try:
+ # Validate hex color format
+ hex_color_pattern = re.compile(r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$')
+
+ db_settings = {}
+
+ if request_data.theme_primary_color is not None:
+ if not hex_color_pattern.match(request_data.theme_primary_color):
+ raise HTTPException(
+ status_code=400,
+ detail="Invalid theme_primary_color format. Must be a valid hex color (e.g., #d4af37)"
+ )
+ db_settings["theme_primary_color"] = request_data.theme_primary_color
+
+ if request_data.theme_primary_light is not None:
+ if not hex_color_pattern.match(request_data.theme_primary_light):
+ raise HTTPException(
+ status_code=400,
+ detail="Invalid theme_primary_light format. Must be a valid hex color (e.g., #f5d76e)"
+ )
+ db_settings["theme_primary_light"] = request_data.theme_primary_light
+
+ if request_data.theme_primary_dark is not None:
+ if not hex_color_pattern.match(request_data.theme_primary_dark):
+ raise HTTPException(
+ status_code=400,
+ detail="Invalid theme_primary_dark format. Must be a valid hex color (e.g., #c9a227)"
+ )
+ db_settings["theme_primary_dark"] = request_data.theme_primary_dark
+
+ if request_data.theme_primary_accent is not None:
+ if not hex_color_pattern.match(request_data.theme_primary_accent):
+ raise HTTPException(
+ status_code=400,
+ detail="Invalid theme_primary_accent format. Must be a valid hex color (e.g., #e8c547)"
+ )
+ db_settings["theme_primary_accent"] = request_data.theme_primary_accent
+
+ # Update or create settings
+ for key, value in db_settings.items():
+ setting = db.query(SystemSettings).filter(
+ SystemSettings.key == key
+ ).first()
+
+ if setting:
+ setting.value = value
+ setting.updated_at = datetime.utcnow()
+ setting.updated_by_id = current_user.id
+ else:
+ setting = SystemSettings(
+ key=key,
+ value=value,
+ updated_by_id=current_user.id
+ )
+ db.add(setting)
+
+ db.commit()
+
+ # Get updated settings
+ updated_settings = {}
+ for key in ["theme_primary_color", "theme_primary_light", "theme_primary_dark", "theme_primary_accent"]:
+ setting = db.query(SystemSettings).filter(
+ SystemSettings.key == key
+ ).first()
+ if setting:
+ updated_settings[key] = setting.value
+ else:
+ # Return defaults if not set
+ defaults = {
+ "theme_primary_color": "#d4af37",
+ "theme_primary_light": "#f5d76e",
+ "theme_primary_dark": "#c9a227",
+ "theme_primary_accent": "#e8c547",
+ }
+ updated_settings[key] = defaults.get(key)
+
+ theme_setting = db.query(SystemSettings).filter(
+ SystemSettings.key == "theme_primary_color"
+ ).first()
+
+ updated_at = None
+ updated_by = None
+ if theme_setting:
+ updated_at = theme_setting.updated_at.isoformat() if theme_setting.updated_at else None
+ updated_by = theme_setting.updated_by.full_name if theme_setting.updated_by else None
+
+ return {
+ "status": "success",
+ "message": "Theme settings updated successfully",
+ "data": {
+ "theme_primary_color": updated_settings.get("theme_primary_color", "#d4af37"),
+ "theme_primary_light": updated_settings.get("theme_primary_light", "#f5d76e"),
+ "theme_primary_dark": updated_settings.get("theme_primary_dark", "#c9a227"),
+ "theme_primary_accent": updated_settings.get("theme_primary_accent", "#e8c547"),
+ "updated_at": updated_at,
+ "updated_by": updated_by,
+ }
+ }
+ except HTTPException:
+ raise
+ except Exception as e:
+ db.rollback()
+ logger.error(f"Error updating theme settings: {e}", exc_info=True)
+ raise HTTPException(status_code=500, detail=str(e))
+
@router.get("/recaptcha")
async def get_recaptcha_settings(
db: Session = Depends(get_db)
diff --git a/Frontend/src/App.tsx b/Frontend/src/App.tsx
index d4b11d73..399678ff 100644
--- a/Frontend/src/App.tsx
+++ b/Frontend/src/App.tsx
@@ -12,6 +12,7 @@ import { LoadingProvider, useNavigationLoading, useLoading } from './shared/cont
import { CookieConsentProvider } from './shared/contexts/CookieConsentContext';
import { CurrencyProvider } from './features/payments/contexts/CurrencyContext';
import { CompanySettingsProvider } from './shared/contexts/CompanySettingsContext';
+import { ThemeProvider } from './shared/contexts/ThemeContext';
import { AuthModalProvider } from './features/auth/contexts/AuthModalContext';
import { AntibotProvider } from './features/auth/contexts/AntibotContext';
import { RoomProvider } from './features/rooms/contexts/RoomContext';
@@ -91,7 +92,7 @@ const GroupBookingManagementPage = lazy(() => import('./pages/admin/GroupBooking
const AdminBookingManagementPage = lazy(() => import('./pages/admin/BookingManagementPage'));
const PageContentDashboardPage = lazy(() => import('./pages/admin/PageContentDashboard'));
const AnalyticsDashboardPage = lazy(() => import('./pages/admin/AnalyticsDashboardPage'));
-const BusinessDashboardPage = lazy(() => import('./pages/admin/BusinessDashboardPage'));
+const PromotionsManagementPage = lazy(() => import('./pages/admin/PromotionsManagementPage'));
const SettingsPage = lazy(() => import('./pages/admin/SettingsPage'));
const TaskManagementPage = lazy(() => import('./pages/admin/TaskManagementPage'));
const WorkflowManagementPage = lazy(() => import('./pages/admin/WorkflowManagementPage'));
@@ -249,7 +250,8 @@ function App() {