/** * Admin community rules management page. * URL: /admin/rules * CRUD for named, ordered rules with version history. */ 'use client' // Needs useState, useEffect, useCallback for data management and user interactions. import { useState, useEffect, useCallback } from 'react' import { Plus, ArrowUp, ArrowDown, Pencil, Trash, Clock } from '@phosphor-icons/react' import { AdminLayout } from '@/components/admin/admin-layout' import { ErrorAlert } from '@/components/error-alert' import { SaveButton } from '@/components/admin/save-button' import { useSaveState } from '@/hooks/use-save-state' import { useAuth } from '@/hooks/use-auth' import { useToast } from '@/hooks/use-toast' import { getCommunityRules, createCommunityRule, updateCommunityRule, deleteCommunityRule, reorderCommunityRules, getCommunitySettings, } from '@/lib/api/client' import type { CommunityRule } from '@/lib/api/types' import { FormLabel } from '@/components/ui/form-label' export default function AdminRulesPage() { const { getAccessToken } = useAuth() const { toast } = useToast() const [rules, setRules] = useState([]) const [communityDid, setCommunityDid] = useState(null) const [loading, setLoading] = useState(true) const [loadError, setLoadError] = useState(null) const [actionError, setActionError] = useState(null) // Edit/create state const [editingRule, setEditingRule] = useState(null) const [isCreating, setIsCreating] = useState(false) const [formTitle, setFormTitle] = useState('') const [formDescription, setFormDescription] = useState('') const saveSt = useSaveState() // Reorder state const [hasReorderChanges, setHasReorderChanges] = useState(false) const reorderSaveSt = useSaveState() const fetchData = useCallback(async () => { setLoadError(null) try { const token = getAccessToken() ?? '' const settings = await getCommunitySettings(token) const did = settings.communityDid if (!did) { setLoadError('Community not initialized.') return } setCommunityDid(did) const response = await getCommunityRules(did) setRules(response.data) } catch { setLoadError('Failed to load rules. The API may be unreachable.') } finally { setLoading(false) } }, [getAccessToken]) useEffect(() => { void fetchData() }, [fetchData]) const handleCreate = async () => { if (!communityDid || !formTitle.trim() || !formDescription.trim()) return setActionError(null) saveSt.startSaving() try { await createCommunityRule( communityDid, { title: formTitle.trim(), description: formDescription.trim(), }, getAccessToken() ?? '' ) saveSt.onSaved() setIsCreating(false) setFormTitle('') setFormDescription('') toast({ title: 'Rule created' }) void fetchData() } catch { saveSt.reset() setActionError('Failed to create rule.') } } const handleUpdate = async () => { if (!communityDid || !editingRule || !formTitle.trim() || !formDescription.trim()) return setActionError(null) saveSt.startSaving() try { await updateCommunityRule( communityDid, editingRule.id, { title: formTitle.trim(), description: formDescription.trim(), }, getAccessToken() ?? '' ) saveSt.onSaved() setEditingRule(null) setFormTitle('') setFormDescription('') toast({ title: 'Rule updated (new version created)' }) void fetchData() } catch { saveSt.reset() setActionError('Failed to update rule.') } } const handleDelete = async (ruleId: number) => { if (!communityDid) return const confirmed = window.confirm( 'Are you sure you want to archive this rule? Historical references will be preserved.' ) if (!confirmed) return setActionError(null) try { await deleteCommunityRule(communityDid, ruleId, getAccessToken() ?? '') toast({ title: 'Rule archived' }) void fetchData() } catch { setActionError('Failed to archive rule.') } } const handleMoveUp = (index: number) => { if (index === 0) return const updated = [...rules] const prev = updated[index - 1] const curr = updated[index] if (!prev || !curr) return updated[index - 1] = curr updated[index] = prev setRules(updated) setHasReorderChanges(true) } const handleMoveDown = (index: number) => { if (index >= rules.length - 1) return const updated = [...rules] const next = updated[index + 1] const curr = updated[index] if (!next || !curr) return updated[index + 1] = curr updated[index] = next setRules(updated) setHasReorderChanges(true) } const handleSaveOrder = async () => { if (!communityDid) return reorderSaveSt.startSaving() try { await reorderCommunityRules( communityDid, { order: rules.map((rule, idx) => ({ id: rule.id, displayOrder: idx })), }, getAccessToken() ?? '' ) reorderSaveSt.onSaved() setHasReorderChanges(false) toast({ title: 'Rules reordered' }) } catch { reorderSaveSt.reset() setActionError('Failed to save order.') } } const startEditing = (rule: CommunityRule) => { setEditingRule(rule) setIsCreating(false) setFormTitle(rule.title) setFormDescription(rule.description) saveSt.reset() } const startCreating = () => { setEditingRule(null) setIsCreating(true) setFormTitle('') setFormDescription('') saveSt.reset() } const cancelForm = () => { setEditingRule(null) setIsCreating(false) setFormTitle('') setFormDescription('') saveSt.reset() } const isFormOpen = isCreating || editingRule !== null return (

Community Rules

{!isFormOpen && ( )}
{actionError && setActionError(null)} />} {loadError && ( void fetchData()} /> )} {/* Create/Edit form */} {isFormOpen && (

{isCreating ? 'New Rule' : 'Edit Rule'}

{editingRule && (

)}
Title setFormTitle(e.target.value)} maxLength={200} required className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground" placeholder="e.g., Be respectful" />
Description