SurfSense/surfsense_web/contexts/SiteConfigContext.tsx
Claude 2a2a909ef4
Implement dynamic site appearance configuration system
This commit implements a comprehensive database-driven site configuration
system that allows administrators to control the visibility and behavior
of homepage elements, navigation links, footer sections, and route
availability without code changes.

Backend Changes:
- Added SiteConfiguration model with singleton pattern (id=1)
- Created migration 38_add_site_configuration_table.py
- Implemented site_configuration_routes.py with public + admin endpoints
- Added Pydantic schemas for validation (Base, Update, Read, Public)
- Registered routes in main app

Frontend Changes:
- Created SiteConfigContext.tsx for global state management
- Updated app/layout.tsx to wrap app in SiteConfigProvider
- Implemented RouteGuard component for disabled routes
- Updated navbar with conditional rendering (pricing, docs, github, signin)
- Updated hero-section with conditional buttons (get started, talk to us)
- Updated footer with conditional sections and custom copyright
- Applied route guards to pricing, contact, terms, privacy pages

Admin Panel:
- Created /dashboard/site-settings page with full UI
- Visual toggle switches for all configuration options
- Real-time updates via API with toast notifications
- Organized by section (Header, Homepage, Footer, Routes, Text)
- Dark mode support and responsive design

Configuration Options:
Header: show_pricing_link, show_docs_link, show_github_link, show_sign_in
Homepage: show_get_started_button, show_talk_to_us_button
Footer: show_pages_section, show_legal_section, show_register_section
Routes: disable_pricing/docs/contact/terms/privacy_route
Custom: custom_copyright text (max 200 chars)

Security:
- Superuser-only admin endpoints with JWT validation
- Public read-only endpoint for frontend consumption
- Input validation via Pydantic schemas
- Singleton pattern with database constraints
- Client-side route guards for disabled routes

Documentation:
- Added comprehensive section to claude.md
- Includes API docs, migration guide, testing checklist
- Example code snippets and configuration tables
- Security considerations and future enhancements

All configuration defaults to minimal/privacy-focused state (most
features disabled by default). Administrators can enable features
as needed via the admin panel.

Files Changed: 16 files (6 backend, 10 frontend, 1 documentation)
2025-11-18 10:58:31 +00:00

111 lines
2.7 KiB
TypeScript

"use client";
import React, { createContext, useContext, useEffect, useState } from "react";
export interface SiteConfig {
// Header/Navbar toggles
show_pricing_link: boolean;
show_docs_link: boolean;
show_github_link: boolean;
show_sign_in: boolean;
// Homepage toggles
show_get_started_button: boolean;
show_talk_to_us_button: boolean;
// Footer toggles
show_pages_section: boolean;
show_legal_section: boolean;
show_register_section: boolean;
// Route disabling
disable_pricing_route: boolean;
disable_docs_route: boolean;
disable_contact_route: boolean;
disable_terms_route: boolean;
disable_privacy_route: boolean;
// Custom text
custom_copyright: string | null;
}
const defaultConfig: SiteConfig = {
show_pricing_link: false,
show_docs_link: false,
show_github_link: false,
show_sign_in: true,
show_get_started_button: false,
show_talk_to_us_button: false,
show_pages_section: false,
show_legal_section: false,
show_register_section: false,
disable_pricing_route: true,
disable_docs_route: true,
disable_contact_route: true,
disable_terms_route: true,
disable_privacy_route: true,
custom_copyright: "SurfSense 2025",
};
interface SiteConfigContextType {
config: SiteConfig;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
const SiteConfigContext = createContext<SiteConfigContextType>({
config: defaultConfig,
loading: true,
error: null,
refetch: async () => {},
});
export function SiteConfigProvider({ children }: { children: React.ReactNode }) {
const [config, setConfig] = useState<SiteConfig>(defaultConfig);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchConfig = async () => {
try {
setLoading(true);
setError(null);
const backendUrl =
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
const response = await fetch(`${backendUrl}/api/v1/site-config/public`);
if (!response.ok) {
throw new Error(`Failed to fetch site configuration: ${response.statusText}`);
}
const data = await response.json();
setConfig(data);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
setError(errorMessage);
console.error("Error fetching site configuration:", err);
// Keep default config on error
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchConfig();
}, []);
return (
<SiteConfigContext.Provider value={{ config, loading, error, refetch: fetchConfig }}>
{children}
</SiteConfigContext.Provider>
);
}
export function useSiteConfig() {
const context = useContext(SiteConfigContext);
if (!context) {
throw new Error("useSiteConfig must be used within a SiteConfigProvider");
}
return context;
}