website refactor
This commit is contained in:
55
apps/website/app/actions/adminActions.ts
Normal file
55
apps/website/app/actions/adminActions.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
'use server';
|
||||
|
||||
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
|
||||
*
|
||||
* 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): 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());
|
||||
return Result.err(result.getError());
|
||||
}
|
||||
|
||||
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): Promise<Result<{ success: boolean }, string>> {
|
||||
const mutation = new DeleteUserMutation();
|
||||
const result = await mutation.execute({ userId });
|
||||
|
||||
if (result.isErr()) {
|
||||
console.error('deleteUser failed:', result.getError());
|
||||
return Result.err(result.getError());
|
||||
}
|
||||
|
||||
revalidatePath(routes.admin.users);
|
||||
return Result.ok({ success: true });
|
||||
}
|
||||
30
apps/website/app/actions/completeOnboardingAction.ts
Normal file
30
apps/website/app/actions/completeOnboardingAction.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
'use server';
|
||||
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { CompleteOnboardingMutation } from '@/lib/mutations/onboarding/CompleteOnboardingMutation';
|
||||
import { CompleteOnboardingInputDTO } from '@/lib/types/generated/CompleteOnboardingInputDTO';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
|
||||
/**
|
||||
* Complete onboarding - thin wrapper around mutation
|
||||
*
|
||||
* Pattern: Server Action → Mutation → Service → API Client
|
||||
*
|
||||
* Authentication is handled automatically by the API via cookies.
|
||||
* The BaseApiClient includes credentials: 'include', so cookies are sent automatically.
|
||||
* If authentication fails, the API returns 401/403 which gets converted to domain errors.
|
||||
*/
|
||||
export async function completeOnboardingAction(
|
||||
input: CompleteOnboardingInputDTO
|
||||
): Promise<Result<{ success: boolean }, string>> {
|
||||
const mutation = new CompleteOnboardingMutation();
|
||||
const result = await mutation.execute(input);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(result.getError());
|
||||
}
|
||||
|
||||
revalidatePath(routes.protected.dashboard);
|
||||
return Result.ok({ success: true });
|
||||
}
|
||||
26
apps/website/app/actions/generateAvatarsAction.ts
Normal file
26
apps/website/app/actions/generateAvatarsAction.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
'use server';
|
||||
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { GenerateAvatarsMutation } from '@/lib/mutations/onboarding/GenerateAvatarsMutation';
|
||||
|
||||
/**
|
||||
* Generate avatars - thin wrapper around mutation
|
||||
*
|
||||
* Note: This action requires userId to be passed from the client.
|
||||
* The client should get userId from session and pass it as a parameter.
|
||||
*/
|
||||
export async function generateAvatarsAction(params: {
|
||||
userId: string;
|
||||
facePhotoData: string;
|
||||
suitColor: string;
|
||||
}): Promise<Result<{ success: boolean; avatarUrls?: string[] }, string>> {
|
||||
const mutation = new GenerateAvatarsMutation();
|
||||
const result = await mutation.execute(params);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(result.getError());
|
||||
}
|
||||
|
||||
const data = result.unwrap();
|
||||
return Result.ok({ success: data.success, avatarUrls: data.avatarUrls });
|
||||
}
|
||||
75
apps/website/app/actions/leagueScheduleActions.ts
Normal file
75
apps/website/app/actions/leagueScheduleActions.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
'use server';
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { ScheduleAdminMutation } from '@/lib/mutations/leagues/ScheduleAdminMutation';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
|
||||
// eslint-disable-next-line gridpilot-rules/server-actions-interface
|
||||
export async function publishScheduleAction(leagueId: string, seasonId: string): Promise<Result<void, string>> {
|
||||
const mutation = new ScheduleAdminMutation();
|
||||
const result = await mutation.publishSchedule(leagueId, seasonId);
|
||||
|
||||
if (result.isOk()) {
|
||||
revalidatePath(routes.league.schedule(leagueId));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line gridpilot-rules/server-actions-interface
|
||||
export async function unpublishScheduleAction(leagueId: string, seasonId: string): Promise<Result<void, string>> {
|
||||
const mutation = new ScheduleAdminMutation();
|
||||
const result = await mutation.unpublishSchedule(leagueId, seasonId);
|
||||
|
||||
if (result.isOk()) {
|
||||
revalidatePath(routes.league.schedule(leagueId));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line gridpilot-rules/server-actions-interface
|
||||
export async function createRaceAction(
|
||||
leagueId: string,
|
||||
seasonId: string,
|
||||
input: { track: string; car: string; scheduledAtIso: string }
|
||||
): Promise<Result<void, string>> {
|
||||
const mutation = new ScheduleAdminMutation();
|
||||
const result = await mutation.createRace(leagueId, seasonId, input);
|
||||
|
||||
if (result.isOk()) {
|
||||
revalidatePath(routes.league.schedule(leagueId));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line gridpilot-rules/server-actions-interface
|
||||
export async function updateRaceAction(
|
||||
leagueId: string,
|
||||
seasonId: string,
|
||||
raceId: string,
|
||||
input: Partial<{ track: string; car: string; scheduledAtIso: string }>
|
||||
): Promise<Result<void, string>> {
|
||||
const mutation = new ScheduleAdminMutation();
|
||||
const result = await mutation.updateRace(leagueId, seasonId, raceId, input);
|
||||
|
||||
if (result.isOk()) {
|
||||
revalidatePath(routes.league.schedule(leagueId));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line gridpilot-rules/server-actions-interface
|
||||
export async function deleteRaceAction(leagueId: string, seasonId: string, raceId: string): Promise<Result<void, string>> {
|
||||
const mutation = new ScheduleAdminMutation();
|
||||
const result = await mutation.deleteRace(leagueId, seasonId, raceId);
|
||||
|
||||
if (result.isOk()) {
|
||||
revalidatePath(routes.league.schedule(leagueId));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
20
apps/website/app/actions/profileActions.ts
Normal file
20
apps/website/app/actions/profileActions.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
'use server';
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { UpdateDriverProfileMutation } from '@/lib/mutations/drivers/UpdateDriverProfileMutation';
|
||||
|
||||
export async function updateProfileAction(
|
||||
updates: { bio?: string; country?: string },
|
||||
): Promise<Result<void, string>> {
|
||||
const mutation = new UpdateDriverProfileMutation();
|
||||
const result = await mutation.execute({ bio: updates.bio, country: updates.country });
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(result.getError());
|
||||
}
|
||||
|
||||
revalidatePath(routes.protected.profile);
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
57
apps/website/app/actions/sponsorshipActions.ts
Normal file
57
apps/website/app/actions/sponsorshipActions.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
'use server';
|
||||
|
||||
import { AcceptSponsorshipRequestMutation } from '@/lib/mutations/sponsors/AcceptSponsorshipRequestMutation';
|
||||
import { RejectSponsorshipRequestMutation } from '@/lib/mutations/sponsors/RejectSponsorshipRequestMutation';
|
||||
import { SessionGateway } from '@/lib/gateways/SessionGateway';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
|
||||
export async function acceptSponsorshipRequest(
|
||||
requestId: string,
|
||||
): Promise<Result<void, string>> {
|
||||
// Get session for actorDriverId
|
||||
const sessionGateway = new SessionGateway();
|
||||
const session = await sessionGateway.getSession();
|
||||
const actorDriverId = session?.user?.primaryDriverId;
|
||||
|
||||
if (!actorDriverId) {
|
||||
return Result.err('Not authenticated');
|
||||
}
|
||||
|
||||
const mutation = new AcceptSponsorshipRequestMutation();
|
||||
const result = await mutation.execute({ requestId, actorDriverId });
|
||||
|
||||
if (result.isErr()) {
|
||||
console.error('Failed to accept sponsorship request:', result.getError());
|
||||
return Result.err(result.getError());
|
||||
}
|
||||
|
||||
revalidatePath(routes.protected.profileSponsorshipRequests);
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
|
||||
export async function rejectSponsorshipRequest(
|
||||
requestId: string,
|
||||
reason?: string,
|
||||
): Promise<Result<void, string>> {
|
||||
// Get session for actorDriverId
|
||||
const sessionGateway = new SessionGateway();
|
||||
const session = await sessionGateway.getSession();
|
||||
const actorDriverId = session?.user?.primaryDriverId;
|
||||
|
||||
if (!actorDriverId) {
|
||||
return Result.err('Not authenticated');
|
||||
}
|
||||
|
||||
const mutation = new RejectSponsorshipRequestMutation();
|
||||
const result = await mutation.execute({ requestId, actorDriverId, reason: reason || null });
|
||||
|
||||
if (result.isErr()) {
|
||||
console.error('Failed to reject sponsorship request:', result.getError());
|
||||
return Result.err(result.getError());
|
||||
}
|
||||
|
||||
revalidatePath(routes.protected.profileSponsorshipRequests);
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
Reference in New Issue
Block a user