website refactor

This commit is contained in:
2026-01-14 10:51:05 +01:00
parent 4522d41aef
commit 0d89ad027e
291 changed files with 6887 additions and 3685 deletions

View File

@@ -3,6 +3,8 @@
import { UpdateUserStatusMutation } from '@/lib/mutations/admin/UpdateUserStatusMutation';
import { DeleteUserMutation } from '@/lib/mutations/admin/DeleteUserMutation';
import { revalidatePath } from 'next/cache';
import { Result } from '@/lib/contracts/Result';
import { routes } from '@/lib/routing/RouteConfig';
/**
* Server actions for admin operations
@@ -10,34 +12,44 @@ import { revalidatePath } from 'next/cache';
* All write operations must enter through server actions.
* Actions are thin wrappers that handle framework concerns (revalidation).
* Business logic is handled by Mutations.
* All actions return Result types for type-safe error handling.
*/
/**
* Update user status
*
* @param userId - The ID of the user to update
* @param status - The new status to set
* @returns Result with success indicator or error
*/
export async function updateUserStatus(userId: string, status: string) {
export async function updateUserStatus(userId: string, status: string): Promise<Result<{ success: boolean }, string>> {
const mutation = new UpdateUserStatusMutation();
const result = await mutation.execute({ userId, status });
if (result.isErr()) {
console.error('updateUserStatus failed:', result.getError());
throw new Error('Failed to update user status');
return Result.err(result.getError());
}
revalidatePath('/admin/users');
revalidatePath(routes.admin.users);
return Result.ok({ success: true });
}
/**
* Delete user
*
* @param userId - The ID of the user to delete
* @returns Result with success indicator or error
*/
export async function deleteUser(userId: string) {
export async function deleteUser(userId: string): Promise<Result<{ success: boolean }, string>> {
const mutation = new DeleteUserMutation();
const result = await mutation.execute({ userId });
if (result.isErr()) {
console.error('deleteUser failed:', result.getError());
throw new Error('Failed to delete user');
return Result.err(result.getError());
}
revalidatePath('/admin/users');
revalidatePath(routes.admin.users);
return Result.ok({ success: true });
}

View File

@@ -1,6 +1,7 @@
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { createRouteGuard } from '@/lib/auth/createRouteGuard';
import Section from '@/components/ui/Section';
interface AdminLayoutProps {
children: React.ReactNode;
@@ -23,8 +24,8 @@ export default async function AdminLayout({ children }: AdminLayoutProps) {
}
return (
<div className="min-h-screen bg-deep-graphite">
<Section variant="default" className="min-h-screen">
{children}
</div>
</Section>
);
}

View File

@@ -1,5 +1,6 @@
import { AdminDashboardPageQuery } from '@/lib/page-queries/AdminDashboardPageQuery';
import { AdminDashboardTemplate } from '@/templates/AdminDashboardTemplate';
import { ErrorBanner } from '@/components/ui/ErrorBanner';
export default async function AdminPage() {
const result = await AdminDashboardPageQuery.execute();
@@ -8,25 +9,25 @@ export default async function AdminPage() {
const error = result.getError();
if (error === 'notFound') {
return (
<div className="container mx-auto p-6">
<div className="bg-racing-red/10 border border-racing-red text-racing-red px-4 py-3 rounded-lg">
Access denied - You must be logged in as an Owner or Admin
</div>
</div>
<ErrorBanner
title="Access Denied"
message="You must be logged in as an Owner or Admin"
variant="error"
/>
);
}
return (
<div className="container mx-auto p-6">
<div className="bg-racing-red/10 border border-racing-red text-racing-red px-4 py-3 rounded-lg">
Failed to load dashboard: {error}
</div>
</div>
<ErrorBanner
title="Load Failed"
message={`Failed to load dashboard: ${error}`}
variant="error"
/>
);
}
const viewData = result.unwrap();
const output = result.unwrap();
// For now, use empty callbacks. In a real app, these would be Server Actions
// that trigger revalidation or navigation
return <AdminDashboardTemplate adminDashboardViewData={viewData} onRefresh={() => {}} isLoading={false} />;
return <AdminDashboardTemplate adminDashboardViewData={output} onRefresh={() => {}} isLoading={false} />;
}

View File

@@ -5,6 +5,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
import { AdminUsersTemplate } from '@/templates/AdminUsersTemplate';
import { AdminUsersViewData } from '@/lib/view-data/AdminUsersViewData';
import { updateUserStatus, deleteUser } from '../actions';
import { routes } from '@/lib/routing/RouteConfig';
interface AdminUsersWrapperProps {
initialViewData: AdminUsersViewData;
@@ -30,7 +31,7 @@ export function AdminUsersWrapper({ initialViewData }: AdminUsersWrapperProps) {
if (newSearch) params.set('search', newSearch);
else params.delete('search');
params.delete('page'); // Reset to page 1
router.push(`/admin/users?${params.toString()}`);
router.push(`${routes.admin.users}?${params.toString()}`);
}, [router, searchParams]);
const handleFilterRole = useCallback((role: string) => {
@@ -38,7 +39,7 @@ export function AdminUsersWrapper({ initialViewData }: AdminUsersWrapperProps) {
if (role) params.set('role', role);
else params.delete('role');
params.delete('page');
router.push(`/admin/users?${params.toString()}`);
router.push(`${routes.admin.users}?${params.toString()}`);
}, [router, searchParams]);
const handleFilterStatus = useCallback((status: string) => {
@@ -46,11 +47,11 @@ export function AdminUsersWrapper({ initialViewData }: AdminUsersWrapperProps) {
if (status) params.set('status', status);
else params.delete('status');
params.delete('page');
router.push(`/admin/users?${params.toString()}`);
router.push(`${routes.admin.users}?${params.toString()}`);
}, [router, searchParams]);
const handleClearFilters = useCallback(() => {
router.push('/admin/users');
router.push(routes.admin.users);
}, [router]);
const handleRefresh = useCallback(() => {
@@ -61,7 +62,13 @@ export function AdminUsersWrapper({ initialViewData }: AdminUsersWrapperProps) {
const handleUpdateStatus = useCallback(async (userId: string, newStatus: string) => {
try {
setLoading(true);
await updateUserStatus(userId, newStatus);
const result = await updateUserStatus(userId, newStatus);
if (result.isErr()) {
setError(result.getError());
return;
}
// Revalidate data
router.refresh();
} catch (err) {
@@ -78,7 +85,13 @@ export function AdminUsersWrapper({ initialViewData }: AdminUsersWrapperProps) {
try {
setDeletingUser(userId);
await deleteUser(userId);
const result = await deleteUser(userId);
if (result.isErr()) {
setError(result.getError());
return;
}
// Revalidate data
router.refresh();
} catch (err) {

View File

@@ -1,5 +1,6 @@
import { AdminUsersPageQuery } from '@/lib/page-queries/AdminUsersPageQuery';
import { AdminUsersWrapper } from './AdminUsersWrapper';
import { ErrorBanner } from '@/components/ui/ErrorBanner';
interface AdminUsersPageProps {
searchParams?: {
@@ -28,24 +29,24 @@ export default async function AdminUsersPage({ searchParams }: AdminUsersPagePro
const error = result.getError();
if (error === 'notFound') {
return (
<div className="container mx-auto p-6">
<div className="bg-racing-red/10 border border-racing-red text-racing-red px-4 py-3 rounded-lg">
Access denied - You must be logged in as an Owner or Admin
</div>
</div>
<ErrorBanner
title="Access Denied"
message="You must be logged in as an Owner or Admin"
variant="error"
/>
);
}
return (
<div className="container mx-auto p-6">
<div className="bg-racing-red/10 border border-racing-red text-racing-red px-4 py-3 rounded-lg">
Failed to load users: {error}
</div>
</div>
<ErrorBanner
title="Load Failed"
message={`Failed to load users: ${error}`}
variant="error"
/>
);
}
const viewData = result.unwrap();
const output = result.unwrap();
// Pass to client wrapper for UI interactions
return <AdminUsersWrapper initialViewData={viewData} />;
return <AdminUsersWrapper initialViewData={output} />;
}