Inline filtering results, select-all across pages, country flags, settings RBAC, and inline role changes
- Round detail: add skeleton loading for filtering stats, inline results table with expandable rows, pagination, override/reinstate, CSV export, and tooltip on AI summaries button (removes need for separate results page) - Projects: add select-all-across-pages with Gmail-style banner, show country flags with tooltip instead of country codes (table + card views), add listAllIds backend endpoint - Settings: allow PROGRAM_ADMIN access to settings page, restrict infrastructure tabs (AI, Email, Storage, Security, Webhooks) to SUPER_ADMIN only - Members: add inline role change via dropdown submenu in user actions, enforce role hierarchy (only super admins can modify admin/super-admin roles) in both backend and UI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -61,9 +61,10 @@ function SettingsSkeleton() {
|
||||
|
||||
interface SettingsContentProps {
|
||||
initialSettings: Record<string, string>
|
||||
isSuperAdmin?: boolean
|
||||
}
|
||||
|
||||
export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
export function SettingsContent({ initialSettings, isSuperAdmin = true }: SettingsContentProps) {
|
||||
// We use the initial settings passed from the server
|
||||
// Forms will refetch on mutation success
|
||||
|
||||
@@ -168,10 +169,12 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
<Globe className="h-4 w-4" />
|
||||
Locale
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="email" className="gap-2 shrink-0">
|
||||
<Mail className="h-4 w-4" />
|
||||
Email
|
||||
</TabsTrigger>
|
||||
{isSuperAdmin && (
|
||||
<TabsTrigger value="email" className="gap-2 shrink-0">
|
||||
<Mail className="h-4 w-4" />
|
||||
Email
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="notifications" className="gap-2 shrink-0">
|
||||
<Bell className="h-4 w-4" />
|
||||
Notif.
|
||||
@@ -180,18 +183,22 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
<Newspaper className="h-4 w-4" />
|
||||
Digest
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="security" className="gap-2 shrink-0">
|
||||
<Shield className="h-4 w-4" />
|
||||
Security
|
||||
</TabsTrigger>
|
||||
{isSuperAdmin && (
|
||||
<TabsTrigger value="security" className="gap-2 shrink-0">
|
||||
<Shield className="h-4 w-4" />
|
||||
Security
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="audit" className="gap-2 shrink-0">
|
||||
<ShieldAlert className="h-4 w-4" />
|
||||
Audit
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="ai" className="gap-2 shrink-0">
|
||||
<Bot className="h-4 w-4" />
|
||||
AI
|
||||
</TabsTrigger>
|
||||
{isSuperAdmin && (
|
||||
<TabsTrigger value="ai" className="gap-2 shrink-0">
|
||||
<Bot className="h-4 w-4" />
|
||||
AI
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="tags" className="gap-2 shrink-0">
|
||||
<Tags className="h-4 w-4" />
|
||||
Tags
|
||||
@@ -200,10 +207,12 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
Analytics
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="storage" className="gap-2 shrink-0">
|
||||
<HardDrive className="h-4 w-4" />
|
||||
Storage
|
||||
</TabsTrigger>
|
||||
{isSuperAdmin && (
|
||||
<TabsTrigger value="storage" className="gap-2 shrink-0">
|
||||
<HardDrive className="h-4 w-4" />
|
||||
Storage
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
|
||||
<div className="lg:flex lg:gap-8">
|
||||
@@ -230,10 +239,12 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
<div>
|
||||
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Communication</p>
|
||||
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
|
||||
<TabsTrigger value="email" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<Mail className="h-4 w-4" />
|
||||
Email
|
||||
</TabsTrigger>
|
||||
{isSuperAdmin && (
|
||||
<TabsTrigger value="email" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<Mail className="h-4 w-4" />
|
||||
Email
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="notifications" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<Bell className="h-4 w-4" />
|
||||
Notifications
|
||||
@@ -247,10 +258,12 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
<div>
|
||||
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Security</p>
|
||||
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
|
||||
<TabsTrigger value="security" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<Shield className="h-4 w-4" />
|
||||
Security
|
||||
</TabsTrigger>
|
||||
{isSuperAdmin && (
|
||||
<TabsTrigger value="security" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<Shield className="h-4 w-4" />
|
||||
Security
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="audit" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<ShieldAlert className="h-4 w-4" />
|
||||
Audit
|
||||
@@ -260,10 +273,12 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
<div>
|
||||
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Features</p>
|
||||
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
|
||||
<TabsTrigger value="ai" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<Bot className="h-4 w-4" />
|
||||
AI
|
||||
</TabsTrigger>
|
||||
{isSuperAdmin && (
|
||||
<TabsTrigger value="ai" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<Bot className="h-4 w-4" />
|
||||
AI
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="tags" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<Tags className="h-4 w-4" />
|
||||
Tags
|
||||
@@ -274,35 +289,39 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Infrastructure</p>
|
||||
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
|
||||
<TabsTrigger value="storage" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<HardDrive className="h-4 w-4" />
|
||||
Storage
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
{isSuperAdmin && (
|
||||
<div>
|
||||
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Infrastructure</p>
|
||||
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
|
||||
<TabsTrigger value="storage" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
|
||||
<HardDrive className="h-4 w-4" />
|
||||
Storage
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Content area */}
|
||||
<div className="flex-1 min-w-0">
|
||||
|
||||
<TabsContent value="ai" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>AI Configuration</CardTitle>
|
||||
<CardDescription>
|
||||
Configure AI-powered features like smart jury assignment
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AISettingsForm settings={aiSettings} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AIUsageCard />
|
||||
</TabsContent>
|
||||
{isSuperAdmin && (
|
||||
<TabsContent value="ai" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>AI Configuration</CardTitle>
|
||||
<CardDescription>
|
||||
Configure AI-powered features like smart jury assignment
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AISettingsForm settings={aiSettings} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AIUsageCard />
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
<TabsContent value="tags">
|
||||
<Card>
|
||||
@@ -350,19 +369,21 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="email">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Email Configuration</CardTitle>
|
||||
<CardDescription>
|
||||
Configure email settings for notifications and magic links
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<EmailSettingsForm settings={emailSettings} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
{isSuperAdmin && (
|
||||
<TabsContent value="email">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Email Configuration</CardTitle>
|
||||
<CardDescription>
|
||||
Configure email settings for notifications and magic links
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<EmailSettingsForm settings={emailSettings} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
<TabsContent value="notifications">
|
||||
<Card>
|
||||
@@ -378,33 +399,37 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="storage">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>File Storage</CardTitle>
|
||||
<CardDescription>
|
||||
Configure file upload limits and allowed types
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<StorageSettingsForm settings={storageSettings} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
{isSuperAdmin && (
|
||||
<TabsContent value="storage">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>File Storage</CardTitle>
|
||||
<CardDescription>
|
||||
Configure file upload limits and allowed types
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<StorageSettingsForm settings={storageSettings} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
<TabsContent value="security">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Security Settings</CardTitle>
|
||||
<CardDescription>
|
||||
Configure security and access control settings
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<SecuritySettingsForm settings={securitySettings} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
{isSuperAdmin && (
|
||||
<TabsContent value="security">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Security Settings</CardTitle>
|
||||
<CardDescription>
|
||||
Configure security and access control settings
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<SecuritySettingsForm settings={securitySettings} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
<TabsContent value="defaults">
|
||||
<Card>
|
||||
@@ -502,26 +527,28 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Webhook className="h-4 w-4" />
|
||||
Webhooks
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Configure webhook endpoints for platform events
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button asChild>
|
||||
<Link href="/admin/settings/webhooks">
|
||||
<Webhook className="mr-2 h-4 w-4" />
|
||||
Manage Webhooks
|
||||
<ExternalLink className="ml-2 h-3 w-3" />
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{isSuperAdmin && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Webhook className="h-4 w-4" />
|
||||
Webhooks
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Configure webhook endpoints for platform events
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button asChild>
|
||||
<Link href="/admin/settings/webhooks">
|
||||
<Webhook className="mr-2 h-4 w-4" />
|
||||
Manage Webhooks
|
||||
<ExternalLink className="ml-2 h-3 w-3" />
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user