Files
gridpilot.gg/apps/website/client-wrapper/AdminUsersWrapper.tsx
2026-01-19 02:14:53 +01:00

164 lines
5.3 KiB
TypeScript

'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/actions/adminActions';
import { routes } from '@/lib/routing/RouteConfig';
import { SharedConfirmDialog } from '@/components/shared/UIComponents';
import { ClientWrapperProps } from '@/lib/contracts/components/ComponentContracts';
export function AdminUsersWrapper({ viewData }: ClientWrapperProps<AdminUsersViewData>) {
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);
const [userToDelete, setUserToDelete] = useState<string | null>(null);
const [selectedUserIds, setSelectedUserIds] = useState<string[]>([]);
// Current filter values from URL
const search = searchParams.get('search') || '';
const roleFilter = searchParams.get('role') || '';
const statusFilter = searchParams.get('status') || '';
// Selection handlers
const handleSelectUser = useCallback((userId: string) => {
setSelectedUserIds(prev =>
prev.includes(userId)
? prev.filter(id => id !== userId)
: [...prev, userId]
);
}, []);
const handleSelectAll = useCallback(() => {
if (selectedUserIds.length === viewData.users.length) {
setSelectedUserIds([]);
} else {
setSelectedUserIds(viewData.users.map(u => u.id));
}
}, [selectedUserIds.length, viewData.users]);
const handleClearSelection = useCallback(() => {
setSelectedUserIds([]);
}, []);
// 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) => {
setUserToDelete(userId);
}, []);
const confirmDeleteUser = useCallback(async () => {
if (!userToDelete) return;
try {
setDeletingUser(userToDelete);
setError(null);
const result = await deleteUser(userToDelete);
if (result.isErr()) {
setError(result.getError());
return;
}
// Revalidate data
router.refresh();
setUserToDelete(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete user');
} finally {
setDeletingUser(null);
}
}, [router, userToDelete]);
return (
<>
<AdminUsersTemplate
viewData={viewData}
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}
selectedUserIds={selectedUserIds}
onSelectUser={handleSelectUser}
onSelectAll={handleSelectAll}
onClearSelection={handleClearSelection}
/>
<SharedConfirmDialog
isOpen={!!userToDelete}
onClose={() => setUserToDelete(null)}
onConfirm={confirmDeleteUser}
title="Delete User"
description="Are you sure you want to delete this user? This action cannot be undone and will permanently remove the user's access."
confirmLabel="Delete User"
variant="danger"
isLoading={!!deletingUser}
/>
</>
);
}