add leaderboard management

This commit is contained in:
2026-02-22 12:41:28 +01:00
parent d8d008b5e8
commit 8dc8f98add
13 changed files with 874 additions and 5 deletions

View File

@@ -0,0 +1,24 @@
'use server';
import { revalidatePath } from 'next/cache';
import { requireAuth } from '@/lib/auth/server';
import { fetchServerAPI } from '@/lib/api/server/base';
import { leaderboardSchema, type Leaderboard } from '@/lib/schemas/leaderboard';
export async function saveLeaderboardAction(serverId: string, data: Leaderboard) {
try {
const session = await requireAuth();
const validated = leaderboardSchema.safeParse(data);
if (!validated.success) {
return { success: false, message: validated.error.message };
}
await fetchServerAPI(`/server/${serverId}/leaderboard`, session.token!, 'PUT', validated.data);
revalidatePath(`/dashboard/server/${serverId}`);
return { success: true, message: 'Leaderboard saved successfully' };
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to save leaderboard'
};
}
}

View File

@@ -0,0 +1,8 @@
import { fetchServerAPI } from './base';
import { emptyLeaderboard, leaderboardSchema, type Leaderboard } from '@/lib/schemas/leaderboard';
export async function getLeaderboard(token: string, serverId: string): Promise<Leaderboard> {
const response = await fetchServerAPI<Leaderboard>(`/server/${serverId}/leaderboard`, token);
const parsed = leaderboardSchema.safeParse(response.data);
return parsed.success ? parsed.data : emptyLeaderboard;
}

View File

@@ -15,7 +15,8 @@ export enum ServerTab {
assistRules = 'assistRules',
event = 'event',
eventRules = 'eventRules',
settings = 'settings'
settings = 'settings',
leaderboard = 'leaderboard'
}
export const serverTabSchema = z.enum(ServerTab);

View File

@@ -3,3 +3,4 @@ export * from './user';
export * from './config';
export * from './statistics';
export * from './lookups';
export * from './leaderboard';

View File

@@ -0,0 +1,56 @@
import * as z from 'zod';
export const resultValueSchema = z.union([z.number().int().min(0), z.literal('DNF'), z.literal('DNS')]);
export type ResultValue = z.infer<typeof resultValueSchema>;
export const leaderboardDriverSchema = z.object({
name: z.string().min(1),
color: z.string().regex(/^#[0-9a-fA-F]{6}$/),
initials: z.string().min(1).max(10)
});
export type LeaderboardDriver = z.infer<typeof leaderboardDriverSchema>;
const pointsEntryFields = {
points: z.number().int().min(0),
label: z.string().min(1),
color: z.string().regex(/^#[0-9a-fA-F]{6}$/),
textColor: z.string().regex(/^#[0-9a-fA-F]{6}$/),
priority: z.number().int().min(0)
};
export const pointsEntrySchema = z.object(pointsEntryFields);
export type PointsEntry = z.infer<typeof pointsEntrySchema>;
export const flPointsSchema = z.object(pointsEntryFields);
export type FlPoints = z.infer<typeof flPointsSchema>;
export const leaderboardTrackSchema = z.object({
name: z.string().min(1),
results: z.array(resultValueSchema),
fastestLapInitials: z.string()
});
export type LeaderboardTrack = z.infer<typeof leaderboardTrackSchema>;
export const leaderboardSchema = z.object({
drivers: z.array(leaderboardDriverSchema),
pointsTable: z.array(pointsEntrySchema),
flPoints: flPointsSchema,
tracks: z.array(leaderboardTrackSchema)
});
export type Leaderboard = z.infer<typeof leaderboardSchema>;
export const emptyLeaderboard: Leaderboard = {
drivers: [],
pointsTable: [],
flPoints: { points: 1, label: 'FL +1', color: '#8b5cf6', textColor: '#000000', priority: 3 },
tracks: []
};
// Client-side derived type — never sent to API
export type StandingsRow = {
driver: LeaderboardDriver;
driverIndex: number;
totalPoints: number;
trackResults: ResultValue[];
fastestLapCount: number;
};