website refactor
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
Reference in New Issue
Block a user