This commit is contained in:
2026-01-15 01:26:30 +01:00
parent 4a2d7d15a5
commit c3b308e960
102 changed files with 2532 additions and 4744 deletions

View File

@@ -1,32 +0,0 @@
'use client';
import { useState, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { AdminDashboardTemplate } from '@/templates/AdminDashboardTemplate';
import { AdminDashboardViewData } from '@/lib/view-data/AdminDashboardViewData';
interface AdminDashboardWrapperProps {
initialViewData: AdminDashboardViewData;
}
export function AdminDashboardWrapper({ initialViewData }: AdminDashboardWrapperProps) {
const router = useRouter();
// UI state (not business logic)
const [loading, setLoading] = useState(false);
const handleRefresh = useCallback(() => {
setLoading(true);
router.refresh();
// Reset loading after a short delay to show the spinner
setTimeout(() => setLoading(false), 1000);
}, [router]);
return (
<AdminDashboardTemplate
viewData={initialViewData}
onRefresh={handleRefresh}
isLoading={loading}
/>
);
}

View File

@@ -1,122 +0,0 @@
'use client';
import { useState, useCallback } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { AdminUsersTemplate } from '@/templates/AdminUsersTemplate';
import { AdminUsersViewData } from '@/lib/view-data/AdminUsersViewData';
import { updateUserStatus, deleteUser } from '@/app/admin/actions';
import { routes } from '@/lib/routing/RouteConfig';
interface AdminUsersWrapperProps {
initialViewData: AdminUsersViewData;
}
export function AdminUsersWrapper({ initialViewData }: AdminUsersWrapperProps) {
const router = useRouter();
const searchParams = useSearchParams();
// UI state (not business logic)
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [deletingUser, setDeletingUser] = useState<string | null>(null);
// Current filter values from URL
const search = searchParams.get('search') || '';
const roleFilter = searchParams.get('role') || '';
const statusFilter = searchParams.get('status') || '';
// Callbacks that update URL (triggers RSC re-render)
const handleSearch = useCallback((newSearch: string) => {
const params = new URLSearchParams(searchParams);
if (newSearch) params.set('search', newSearch);
else params.delete('search');
params.delete('page'); // Reset to page 1
router.push(`${routes.admin.users}?${params.toString()}`);
}, [router, searchParams]);
const handleFilterRole = useCallback((role: string) => {
const params = new URLSearchParams(searchParams);
if (role) params.set('role', role);
else params.delete('role');
params.delete('page');
router.push(`${routes.admin.users}?${params.toString()}`);
}, [router, searchParams]);
const handleFilterStatus = useCallback((status: string) => {
const params = new URLSearchParams(searchParams);
if (status) params.set('status', status);
else params.delete('status');
params.delete('page');
router.push(`${routes.admin.users}?${params.toString()}`);
}, [router, searchParams]);
const handleClearFilters = useCallback(() => {
router.push(routes.admin.users);
}, [router]);
const handleRefresh = useCallback(() => {
router.refresh();
}, [router]);
// Mutation callbacks (call Server Actions)
const handleUpdateStatus = useCallback(async (userId: string, newStatus: string) => {
try {
setLoading(true);
const result = await updateUserStatus(userId, newStatus);
if (result.isErr()) {
setError(result.getError());
return;
}
// Revalidate data
router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to update status');
} finally {
setLoading(false);
}
}, [router]);
const handleDeleteUser = useCallback(async (userId: string) => {
if (!confirm('Are you sure you want to delete this user? This action cannot be undone.')) {
return;
}
try {
setDeletingUser(userId);
const result = await deleteUser(userId);
if (result.isErr()) {
setError(result.getError());
return;
}
// Revalidate data
router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete user');
} finally {
setDeletingUser(null);
}
}, [router]);
return (
<AdminUsersTemplate
viewData={initialViewData}
onRefresh={handleRefresh}
onSearch={handleSearch}
onFilterRole={handleFilterRole}
onFilterStatus={handleFilterStatus}
onClearFilters={handleClearFilters}
onUpdateStatus={handleUpdateStatus}
onDeleteUser={handleDeleteUser}
search={search}
roleFilter={roleFilter}
statusFilter={statusFilter}
loading={loading}
error={error}
deletingUser={deletingUser}
/>
);
}

View File

@@ -4,7 +4,6 @@ import React from 'react';
import { Filter, Search } from 'lucide-react';
import { Card } from '@/ui/Card';
import { Stack } from '@/ui/Stack';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { Grid } from '@/ui/Grid';
@@ -51,18 +50,13 @@ export function UserFilters({
</Stack>
<Grid cols={3} gap={4}>
<Box position="relative">
<Box style={{ position: 'absolute', left: '0.75rem', top: '50%', transform: 'translateY(-50%)', zIndex: 10 }}>
<Icon icon={Search} size={4} color="#9ca3af" />
</Box>
<Input
type="text"
placeholder="Search by email or name..."
value={search}
onChange={(e) => onSearch(e.target.value)}
style={{ paddingLeft: '2.25rem' }}
/>
</Box>
<Input
type="text"
placeholder="Search by email or name..."
value={search}
onChange={(e) => onSearch(e.target.value)}
icon={<Icon icon={Search} size={4} color="#9ca3af" />}
/>
<Select
value={roleFilter}

View File

@@ -3,7 +3,6 @@
import React from 'react';
import { Users, Shield } from 'lucide-react';
import { Grid } from '@/ui/Grid';
import { Card } from '@/ui/Card';
import { Stack } from '@/ui/Stack';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
@@ -19,7 +18,7 @@ interface UserStatsSummaryProps {
export function UserStatsSummary({ total, activeCount, adminCount }: UserStatsSummaryProps) {
return (
<Grid cols={3} gap={4}>
<Surface variant="muted" rounded="xl" border padding={4} style={{ background: 'linear-gradient(to bottom right, rgba(30, 58, 138, 0.2), rgba(29, 78, 216, 0.1))', borderColor: 'rgba(59, 130, 246, 0.2)' }}>
<Surface variant="gradient-blue" rounded="xl" border padding={4}>
<Stack direction="row" align="center" justify="between">
<Box>
<Text size="sm" color="text-gray-400" block mb={1}>Total Users</Text>
@@ -28,7 +27,7 @@ export function UserStatsSummary({ total, activeCount, adminCount }: UserStatsSu
<Icon icon={Users} size={6} color="#60a5fa" />
</Stack>
</Surface>
<Surface variant="muted" rounded="xl" border padding={4} style={{ background: 'linear-gradient(to bottom right, rgba(20, 83, 45, 0.2), rgba(21, 128, 61, 0.1))', borderColor: 'rgba(16, 185, 129, 0.2)' }}>
<Surface variant="gradient-green" rounded="xl" border padding={4}>
<Stack direction="row" align="center" justify="between">
<Box>
<Text size="sm" color="text-gray-400" block mb={1}>Active</Text>
@@ -37,7 +36,7 @@ export function UserStatsSummary({ total, activeCount, adminCount }: UserStatsSu
<Text color="text-performance-green" weight="bold"></Text>
</Stack>
</Surface>
<Surface variant="muted" rounded="xl" border padding={4} style={{ background: 'linear-gradient(to bottom right, rgba(88, 28, 135, 0.2), rgba(126, 34, 206, 0.1))', borderColor: 'rgba(168, 85, 247, 0.2)' }}>
<Surface variant="gradient-purple" rounded="xl" border padding={4}>
<Stack direction="row" align="center" justify="between">
<Box>
<Text size="sm" color="text-gray-400" block mb={1}>Admins</Text>