--- name: admin-panel-builder description: Expert assistant for creating and maintaining admin panel pages in the KR92 Bible Voice project. Use when creating admin pages, building admin components, integrating with admin navigation, or adding admin features. --- # Admin Panel Builder ## Current Architecture (2026) ### Layout Pattern All admin pages now use the **SidebarProvider + AppSidebar + AdminHeader** pattern: ```typescript import { SidebarProvider } from "@/components/ui/sidebar"; import { AppSidebar } from "@/components/AppSidebar"; import AdminHeader from "@/components/admin/AdminHeader"; return (
{}} onNavigateToContinueText={() => {}} />
} showBackButton={true} />
{/* Content */}
); ``` ### Authentication Pattern Use `@shared-auth/hooks/useUserRole` from the shared package: ```typescript import { useUserRole } from "@shared-auth/hooks/useUserRole"; const { isAdmin } = useUserRole(); if (!isAdmin) { return (

Sinulla ei ole oikeuksia tähän sivuun.

); } ``` ### Import Patterns ```typescript // UI components from @ui aliases import { Button } from "@ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs"; // Shared auth from @shared-auth import { useUserRole } from "@shared-auth/hooks/useUserRole"; // Local imports with @/ import AdminHeader from "@/components/admin/AdminHeader"; import { supabase } from "@/integrations/supabase/client"; ``` ## Existing Admin Pages Structure ### Dashboard (`AdminDashboardPage.tsx`) - Central hub with overview cards - Grid layout with clickable admin cards - Stats overview widget with quick metrics - Uses lucide-react icons (Bot, Users, Video, etc.) - Each card has: title, description, icon, path, stats, isLoading ### Specialized Admin Pages - `AdminAIPage.tsx` - AI management with tabs (usage, prompts, features, pricing) - `AdminAudioPage.tsx` - Audio/TTS management - `AdminAuthTokensPage.tsx` - Authentication and API tokens - `AdminTopicsPage.tsx` - Topic management and translations - `AdminUsersPage.tsx` - User and role management - `AdminTranslationsPage.tsx` - Term translation cache - `AdminVideoPage.tsx` - Video series and clips - `AdminWidgetAnalyticsPage.tsx` - Widget usage statistics ## Creating New Admin Pages ### Step 1: Create Page Component ```typescript // apps/raamattu-nyt/src/pages/AdminExamplePage.tsx import { useUserRole } from "@shared-auth/hooks/useUserRole"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs"; import { Database, Loader2 } from "lucide-react"; import { AppSidebar } from "@/components/AppSidebar"; import AdminHeader from "@/components/admin/AdminHeader"; import { SidebarProvider } from "@/components/ui/sidebar"; import { supabase } from "@/integrations/supabase/client"; import { useQuery } from "@tanstack/react-query"; import { toast } from "sonner"; // Use sonner for toast notifications const AdminExamplePage = () => { const { isAdmin } = useUserRole(); // Access control if (!isAdmin) { return (

Sinulla ei ole oikeuksia tähän sivuun.

); } return (
{}} onNavigateToContinueText={() => {}} />
} showBackButton={true} />
List Create Settings Items View and manage items {/* Component content */} Create Item Add a new item {/* Form content */}
); }; export default AdminExamplePage; ``` ### Step 2: Add Route to App.tsx ```typescript // apps/raamattu-nyt/src/App.tsx import AdminExamplePage from "./pages/AdminExamplePage"; // In Routes: } /> ``` ### Step 3: Add Card to Admin Dashboard ```typescript // In AdminDashboardPage.tsx adminCards array: { title: "Example Management", description: "Manage example resources", icon: Database, path: "/admin/example", stats: [{ label: "items", value: itemCount || 0 }], isLoading: itemsLoading, }, ``` ## Creating Admin Components ### Data Table Pattern ```typescript // apps/raamattu-nyt/src/components/admin/ExampleTable.tsx import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Button } from "@ui/button"; import { Badge } from "@ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ui/table"; import { Trash2, Edit, Loader2 } from "lucide-react"; import { toast } from "sonner"; import { supabase } from "@/integrations/supabase/client"; export const ExampleTable = () => { const queryClient = useQueryClient(); // Fetch data const { data: items, isLoading } = useQuery({ queryKey: ["admin-examples"], queryFn: async () => { const { data, error } = await supabase .from("examples") .select("*") .order("created_at", { ascending: false }); if (error) throw error; return data; }, }); // Delete mutation const deleteMutation = useMutation({ mutationFn: async (id: string) => { const { error } = await supabase .from("examples") .delete() .eq("id", id); if (error) throw error; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["admin-examples"] }); toast.success("Item deleted successfully"); }, onError: (error: Error) => { toast.error(`Error: ${error.message}`); }, }); if (isLoading) { return (
); } return ( Name Status Created Actions {items?.map((item) => ( {item.name} {item.is_active ? "Active" : "Inactive"} {new Date(item.created_at).toLocaleDateString()} ))}
); }; ``` ### Form Component Pattern ```typescript // apps/raamattu-nyt/src/components/admin/ExampleForm.tsx import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { Button } from "@ui/button"; import { Input } from "@ui/input"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@ui/form"; import { Switch } from "@ui/switch"; import { toast } from "sonner"; import { supabase } from "@/integrations/supabase/client"; const formSchema = z.object({ name: z.string().min(3, "Name must be at least 3 characters"), description: z.string().optional(), is_active: z.boolean().default(true), }); type FormData = z.infer; export const ExampleForm = () => { const queryClient = useQueryClient(); const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { name: "", description: "", is_active: true, }, }); const createMutation = useMutation({ mutationFn: async (values: FormData) => { const { error } = await supabase .from("examples") .insert([values]); if (error) throw error; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["admin-examples"] }); toast.success("Item created successfully"); form.reset(); }, onError: (error: Error) => { toast.error(`Error: ${error.message}`); }, }); const onSubmit = (values: FormData) => { createMutation.mutate(values); }; return (
( Name The name of the item )} /> ( Description )} /> (
Active Enable this item
)} /> ); }; ``` ## Best Practices ### 1. Access Control ✅ Always check `isAdmin` from `useUserRole()` ✅ Show user-friendly Finnish message: "Sinulla ei ole oikeuksia tähän sivuun." ✅ No loading spinner needed (handled by hook) ### 2. Toast Notifications ✅ Use `toast` from "sonner" (not useToast hook) ✅ `toast.success()` for success messages ✅ `toast.error()` for errors ✅ Keep messages concise and actionable ### 3. Data Fetching ✅ Use React Query (`useQuery`, `useMutation`) ✅ Always invalidate queries after mutations ✅ Handle loading states with Loader2 spinner ✅ Show empty states when no data ### 4. Layout & Styling ✅ Use SidebarProvider + AppSidebar + AdminHeader pattern ✅ Wrap content in `
` ✅ Use `max-w-6xl mx-auto` for centered content ✅ Use `space-y-6` for vertical spacing ✅ Cards for section containers ✅ Tabs for multi-section pages ### 5. Icons ✅ Import from lucide-react ✅ Use consistent size: `h-4 w-4` for buttons, `h-6 w-6` for headers ✅ Add text-primary to header icons ### 6. Finnish UI Text ✅ Page titles and descriptions in Finnish when user-facing ✅ Error messages in Finnish ✅ Admin navigation in Finnish ✅ Code/technical terms can be in English ## Common UI Components | Component | Import Path | Use For | |-----------|-------------|---------| | Card | @ui/card | Section containers | | Tabs | @ui/tabs | Multi-section pages | | Table | @ui/table | Data lists | | Form | @ui/form | Input forms with validation | | Button | @ui/button | Actions | | Badge | @ui/badge | Status indicators | | Input | @ui/input | Text inputs | | Switch | @ui/switch | Boolean toggles | | Select | @ui/select | Dropdowns | | Dialog | @ui/dialog | Modals | | AlertDialog | @ui/alert-dialog | Confirmations | | Alert | @ui/alert | Notices and warnings | ## AdminHeader Props ```typescript interface AdminHeaderProps { title: string; // Page title (Finnish) description?: string; // Optional subtitle icon?: React.ReactNode; // Optional icon element showBackButton?: boolean; // Show back to admin (default: true) showSidebarTrigger?: boolean; // Show sidebar toggle (default: true) } ``` ## Dashboard Card Props ```typescript interface AdminCardProps { title: string; // Card title (Finnish) description: string; // Card description (Finnish) icon: React.ElementType; // Lucide icon component path: string; // Navigation path stats?: Array<{ // Optional statistics label: string; value: string | number; }>; isLoading?: boolean; // Show skeleton for stats isExternal?: boolean; // Open in new window } ``` ## Token Management ### Important Admin Features - **Authentication Tokens**: Manage Supabase keys, Turnstile tokens - **OAuth Providers**: Google, Apple (planned) - **Integration Tokens**: OpenAI, ElevenLabs API keys - **Direct Dashboard Links**: Quick access to external services - **Copy-to-clipboard**: Environment variable names - **Regeneration Guidelines**: When and how to rotate keys - **Security Warnings**: Sensitive key indicators ### Token Card Pattern ```typescript {token.name} {token.isSensitive && ( Sensitive )} {/* Environment variable with copy button */} {/* Location info */} {/* Usage badges */} {/* Dashboard links */} {/* Regeneration info */} ``` ## Related Files ### Key Admin Files - `apps/raamattu-nyt/src/pages/AdminDashboardPage.tsx` - Dashboard hub - `apps/raamattu-nyt/src/pages/AdminAuthTokensPage.tsx` - Token management example - `apps/raamattu-nyt/src/components/admin/AdminHeader.tsx` - Header component - `apps/raamattu-nyt/src/App.tsx` - Route definitions ### Documentation - `Docs/07-ADMIN-GUIDE.md` - Admin features overview - `Docs/12-AUTHENTICATION.md` - Auth system details - `Docs/context/supabase-map.md` - Database schema reference ## Common Patterns ### Stats Query Pattern ```typescript const { data: itemCount, isLoading } = useQuery({ queryKey: ["admin-item-count"], queryFn: async () => { const { count } = await supabase .from("items") .select("*", { count: "exact", head: true }); return count || 0; }, enabled: isAdmin, // Only fetch if admin }); ``` ### RPC Call Pattern ```typescript const { data } = await supabase.rpc("get_admin_stats", { p_limit: 1000, }); ``` ### Conditional Rendering ```typescript {stats && stats.length > 0 && (
{isLoading ? ( ) : ( stats.map((stat, i) => (
{stat.value} {stat.label}
)) )}
)} ``` ## Tips 1. **Keep components focused**: One component per file, single responsibility 2. **Extract complex logic**: Move business logic to separate functions/hooks 3. **Type everything**: Use TypeScript interfaces for all data structures 4. **Test queries first**: Verify Supabase queries in SQL editor before implementing 5. **Handle empty states**: Always show something when data is empty 6. **Loading states matter**: Use skeletons for stats, spinners for content 7. **Error boundaries**: Wrap risky operations in try-catch 8. **Invalidate smartly**: Only invalidate affected queries after mutations 9. **Consistent naming**: Use Finnish for UI, English for code 10. **Document complex logic**: Add comments for non-obvious implementations