This commit is contained in:
Tomas Dvorak
2025-11-02 21:31:00 +01:00
parent b9cea0cd77
commit 087f30e82c
130 changed files with 20104 additions and 34330 deletions
+18
View File
@@ -23,6 +23,24 @@ export async function adminBanUser(user_id: number, reason: string, duration_hou
return res.data as { ok: boolean };
}
export type CommentBan = {
id: number;
user_id: number;
reason?: string;
until?: string | null;
created_at: string;
};
export async function adminListBans(): Promise<{ items: CommentBan[] }>{
const res = await api.get('/admin/comments/bans');
return res.data as { items: CommentBan[] };
}
export async function adminLiftBan(id: number): Promise<{ ok: boolean }>{
const res = await api.post(`/admin/comments/bans/${id}/lift`);
return res.data as { ok: boolean };
}
export type UnbanRequest = {
id: number;
user_id: number;
+46
View File
@@ -1,5 +1,6 @@
import api from '../api';
import { RewardItem } from '../../services/engagement';
import type { LeaderboardResponse } from '../../services/engagement';
export type AdminRewardItem = RewardItem & {
active: boolean;
@@ -66,3 +67,48 @@ export async function adminUpdateRedemptionStatus(id: number, action: 'approve'|
const res = await api.patch(`/admin/engagement/redemptions/${id}`, { action });
return res.data as { ok: boolean; status: string };
}
export async function adminGetLeaderboard(metric: 'points'|'level'|'xp' = 'points', limit?: number): Promise<LeaderboardResponse> {
const res = await api.get('/admin/engagement/leaderboard', { params: { metric, limit } });
return res.data as LeaderboardResponse;
}
export type AdminPointsTx = {
id: number;
user_id: number;
delta: number;
xp_delta?: number;
reason: string;
meta?: Record<string, any>;
created_at: string;
};
export async function adminListTransactions(params?: { user_id?: number|string; reason?: string; limit?: number }): Promise<AdminPointsTx[]> {
const res = await api.get('/admin/engagement/transactions', { params });
return (res.data?.items || []) as AdminPointsTx[];
}
export async function adminAdjustPoints(body: { user_id: number; delta: number; reason?: string; meta?: Record<string, any> }): Promise<{ ok: boolean }>{
const res = await api.post('/admin/engagement/adjust', body);
return res.data as { ok: boolean };
}
export type AdminUserProfile = {
user_id: number;
first_name?: string;
last_name?: string;
email?: string;
role?: string;
points: number;
level: number;
xp: number;
username?: string;
avatar_url?: string;
animated_avatar_url?: string;
avatar_upload_unlocked?: boolean;
};
export async function adminGetUserProfile(user_id: number | string): Promise<AdminUserProfile> {
const res = await api.get(`/admin/engagement/profile/${user_id}`);
return res.data as AdminUserProfile;
}
+2 -1
View File
@@ -19,8 +19,9 @@ export type CommentItem = {
id: number;
first_name?: string;
last_name?: string;
email?: string;
role?: string;
username?: string;
avatar_url?: string;
};
};
+58
View File
@@ -5,8 +5,11 @@ export type EngagementProfile = {
points: number;
level: number;
xp: number;
username?: string;
avatar_url?: string;
animated_avatar_url?: string;
avatar_upload_unlocked?: boolean;
animated_avatar_upload_unlocked?: boolean;
achievements: number;
};
@@ -15,6 +18,11 @@ export async function getProfile(): Promise<EngagementProfile> {
return res.data as EngagementProfile;
}
export async function patchProfile(body: { username?: string }): Promise<{ ok: boolean }>{
const res = await api.patch('/engagement/profile', body);
return res.data as { ok: boolean };
}
export type RewardItem = {
id: number;
name: string;
@@ -64,3 +72,53 @@ export async function getAchievements(): Promise<AchievementsResponse> {
const res = await api.get('/engagement/achievements');
return res.data as AchievementsResponse;
}
export type LeaderboardItem = {
rank: number;
user_id: number;
first_name?: string;
last_name?: string;
username?: string;
role?: string;
points: number;
level: number;
xp: number;
avatar_url?: string;
animated_avatar_url?: string;
};
export type LeaderboardResponse = { items: LeaderboardItem[] };
export async function getLeaderboard(metric: 'points'|'level'|'xp' = 'points', limit?: number): Promise<LeaderboardResponse> {
const res = await api.get('/engagement/leaderboard', { params: { metric, limit } });
return res.data as LeaderboardResponse;
}
export type PointsTx = {
id: number;
user_id: number;
delta: number;
xp_delta?: number;
reason: string;
meta?: Record<string, any>;
created_at: string;
};
export async function getMyTransactions(params?: { limit?: number; reason?: string }): Promise<PointsTx[]> {
const res = await api.get('/engagement/transactions', { params });
return (res.data?.items || []) as PointsTx[];
}
export type CheckinResponse = { ok: boolean; awarded: boolean; points?: number; level?: number; xp?: number };
export async function checkin(): Promise<CheckinResponse> {
const res = await api.post('/engagement/checkin');
return res.data as CheckinResponse;
}
export type ArticleReadResponse = { ok: boolean; awarded: boolean; points?: number; level?: number; xp?: number };
export async function articleRead(article_id: number): Promise<ArticleReadResponse> {
const res = await api.post('/engagement/article-read', { article_id });
return res.data as ArticleReadResponse;
}
+42
View File
@@ -140,6 +140,48 @@ export async function startSecondHalf(): Promise<void> {
await api.post('/admin/scoreboard/second-half');
}
// Admin: presets
export async function listPresets(): Promise<string[]> {
const res = await api.get<string[]>('/admin/scoreboard/saves');
// API returns an array of filenames
return (res.data || []).slice();
}
export async function savePreset(filename?: string): Promise<{ saved: string }> {
const payload = filename && filename.trim() ? { filename: filename.trim() } : {};
const res = await api.post<{ saved: string }>('/admin/scoreboard/save', payload);
return res.data;
}
export async function loadPreset(filename: string): Promise<void> {
const name = (filename || '').trim();
if (!name) throw new Error('Missing filename');
await api.post('/admin/scoreboard/load', { filename: name });
}
// Admin: sponsors management
export async function listSponsorsAdmin(): Promise<string[]> {
const res = await api.get<string[]>('/admin/scoreboard/sponsors');
return res.data || [];
}
export async function uploadSponsors(files: File[]): Promise<{ saved: number }> {
const fd = new FormData();
for (const f of files) fd.append('files', f);
const res = await api.post<{ saved: number }>('/admin/scoreboard/sponsors/upload', fd, { headers: { 'Content-Type': 'multipart/form-data' } });
return res.data || { saved: 0 };
}
export async function deleteSponsor(name: string): Promise<void> {
await api.delete('/admin/scoreboard/sponsors', { params: { name } });
}
// Public: sponsors list for overlay
export async function listSponsorsPublic(): Promise<string[]> {
const res = await api.get<string[]>('/scoreboard/sponsors');
return res.data || [];
}
// Utilities
export function deriveShort(name?: string): string {
if (!name) return '---';
+5
View File
@@ -120,6 +120,11 @@ export type AdminSettings = PublicSettings & {
api_base_url?: string;
// Homepage matches display configuration
finished_match_display_days?: number; // Number of days to show finished matches with scores on homepage
// Storage quota and thresholds
storage_quota_mb?: number;
storage_warn_threshold?: number;
storage_critical_threshold?: number;
};
export const getPublicSettings = async (): Promise<PublicSettings> => {
+160
View File
@@ -0,0 +1,160 @@
import api from './api';
export type Sweepstake = {
id: number;
title: string;
description?: string;
image_url?: string;
rules_url?: string;
start_at: string;
end_at: string;
status: string;
picker_style?: 'wheel' | 'cycler' | string;
total_prizes?: number;
prize_summary?: string;
winners_selected_at?: string | null;
visibility_until?: string | null;
};
export type SweepstakePrize = {
id: number;
sweepstake_id: number;
name: string;
description?: string;
image_url?: string;
value?: string;
quantity: number;
display_order: number;
kind?: 'physical' | 'points' | 'xp' | 'points_xp';
points?: number;
xp?: number;
};
export type SweepstakeWinner = {
id: number;
sweepstake_id: number;
entry_id: number;
user_id: number;
prize_id?: number | null;
prize_name?: string;
claim_status: string;
announced_at?: string | null;
};
export type CurrentSweepstakeResponse = {
sweepstake: Sweepstake | null;
prizes?: SweepstakePrize[];
winners?: SweepstakeWinner[];
state?: 'upcoming' | 'active' | 'finalized';
has_entered?: boolean;
visual_played_at?: string | null;
};
export async function getCurrentSweepstake(): Promise<CurrentSweepstakeResponse> {
const res = await api.get('/sweepstakes/current');
return res.data;
}
export async function enterSweepstake(id: number): Promise<void> {
await api.post(`/sweepstakes/${id}/enter`, {});
}
export async function markSweepstakeVisualPlayed(id: number): Promise<void> {
await api.post(`/sweepstakes/${id}/played`, {});
}
export async function getMyWinnings(): Promise<{ items: SweepstakeWinner[] }> {
const res = await api.get('/sweepstakes/my-winnings');
return res.data;
}
// Admin
export async function adminListSweepstakes(params?: { status?: string }) {
const res = await api.get('/admin/sweepstakes', { params });
return res.data?.items || [];
}
export async function adminCreateSweepstake(data: Partial<Sweepstake> & { start_at: string; end_at: string }) {
const res = await api.post('/admin/sweepstakes', data);
return res.data;
}
export async function adminUpdateSweepstake(id: number, data: Partial<Sweepstake>) {
const res = await api.put(`/admin/sweepstakes/${id}`, data);
return res.data;
}
export async function adminDeleteSweepstake(id: number) {
const res = await api.delete(`/admin/sweepstakes/${id}`);
return res.data;
}
export async function adminListEntries(id: number) {
const res = await api.get(`/admin/sweepstakes/${id}/entries`);
return res.data?.items || [];
}
export async function adminListWinners(id: number) {
const res = await api.get(`/admin/sweepstakes/${id}/winners`);
return res.data?.items || [];
}
export async function adminFinalizeSweepstake(id: number, seed?: string) {
const res = await api.post(`/admin/sweepstakes/${id}/finalize`, seed ? { seed } : {});
return res.data;
}
// Visualizer data
export type VisualEntry = { user_id: number; display_name: string; avatar_url?: string };
export type VisualWinner = { id?: number; user_id: number; prize_name?: string; claim_status?: string };
export type VisualData = { sweepstake: Sweepstake; entries: VisualEntry[]; winners: VisualWinner[] };
export async function adminGetVisualData(id: number): Promise<VisualData> {
const res = await api.get(`/admin/sweepstakes/${id}/visual`);
return res.data;
}
export async function getPublicVisualData(id: number): Promise<VisualData> {
const res = await api.get(`/sweepstakes/${id}/visual`);
return res.data;
}
// Prizes CRUD
export type SweepstakePrizeInput = Partial<SweepstakePrize> & {
name?: string;
quantity?: number;
display_order?: number;
kind?: 'physical' | 'points' | 'xp' | 'points_xp';
points?: number;
xp?: number;
};
export async function adminListPrizes(id: number): Promise<SweepstakePrize[]> {
const res = await api.get(`/admin/sweepstakes/${id}/prizes`);
return res.data?.items || [];
}
export async function adminCreatePrize(id: number, data: SweepstakePrizeInput) {
const res = await api.post(`/admin/sweepstakes/${id}/prizes`, data);
return res.data;
}
export async function adminUpdatePrize(id: number, prizeId: number, data: SweepstakePrizeInput) {
const res = await api.put(`/admin/sweepstakes/${id}/prizes/${prizeId}`, data);
return res.data;
}
export async function adminDeletePrize(id: number, prizeId: number) {
const res = await api.delete(`/admin/sweepstakes/${id}/prizes/${prizeId}`);
return res.data;
}
export async function adminReorderPrizes(id: number, order: number[]) {
const res = await api.post(`/admin/sweepstakes/${id}/prizes/reorder`, { order });
return res.data;
}
// Winners management
export async function adminUpdateWinner(id: number, winnerId: number, data: { claim_status?: 'pending'|'claimed'|'delivered'; claim_note?: string }) {
const res = await api.patch(`/admin/sweepstakes/${id}/winners/${winnerId}`, data);
return res.data;
}
export async function adminSetWinnerPrize(id: number, winnerId: number, prizeId: number) {
const res = await api.patch(`/admin/sweepstakes/${id}/winners/${winnerId}/prize`, { prize_id: prizeId });
return res.data;
}