Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions client/portal/src/pages/admin/_shared/AppSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ScanLine,
Settings,
Star,
Trophy,
UserCheck,
Users,
} from "lucide-react";
Expand Down Expand Up @@ -66,6 +67,11 @@ const eventNav = [
url: "/admin/faq",
icon: MessageSquare,
},
{
name: "Tracks",
url: "/admin/tracks",
icon: Trophy,
},
];

const superAdminNav = [
Expand Down
1 change: 1 addition & 0 deletions client/portal/src/pages/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { default as ReviewsPage } from "./reviews/ReviewsPage";
export { default as ScansPage } from "./scans/ScansPage";
export { default as SchedulePage } from "./schedule/SchedulePage";
export { default as SponsorsPage } from "./sponsors/SponsorsPage";
export { default as TracksPage } from "./tracks/TracksPage";
58 changes: 58 additions & 0 deletions client/portal/src/pages/admin/tracks/TracksPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useEffect } from "react";

import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";

import { TracksTable } from "./components/TracksTable";
import { useTracksStore } from "./store";

export default function TracksPage() {
const {
tracks,
canEdit,
loading,
saving,
fetch: loadTracks,
createTrack,
updateTrack,
deleteTrack,
uploadLogo,
} = useTracksStore();

useEffect(() => {
const controller = new AbortController();
loadTracks(controller.signal);
return () => controller.abort();
}, [loadTracks]);

if (loading && tracks.length === 0) {
return (
<div className="space-y-6 overflow-auto">
<Card>
<CardHeader>
<Skeleton className="h-5 w-32" />
</CardHeader>
<CardContent className="space-y-3">
{[...Array(3)].map((_, i) => (
<Skeleton key={i} className="h-14 w-full" />
))}
</CardContent>
</Card>
</div>
);
}

return (
<div className="flex h-full min-h-0 flex-col overflow-hidden">
<TracksTable
tracks={tracks}
saving={saving}
canEdit={canEdit}
onCreateTrack={createTrack}
onUpdateTrack={updateTrack}
onDeleteTrack={deleteTrack}
onUploadLogo={uploadLogo}
/>
</div>
);
}
61 changes: 61 additions & 0 deletions client/portal/src/pages/admin/tracks/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
deleteRequest,
getRequest,
postRequest,
putRequest,
} from "@/shared/lib/api";
import type { ApiResponse } from "@/types";

import type { Track, TrackListResponse, TrackPayload } from "./types";

export async function fetchTracks(
signal?: AbortSignal,
): Promise<ApiResponse<TrackListResponse>> {
return getRequest<TrackListResponse>("/admin/tracks", "tracks", signal);
}

export async function fetchTrackEditPermission(
signal?: AbortSignal,
): Promise<ApiResponse<{ enabled: boolean }>> {
return getRequest<{ enabled: boolean }>(
"/admin/tracks/edit-permission",
"track edit permission",
signal,
);
}

export async function createTrack(
payload: TrackPayload,
signal?: AbortSignal,
): Promise<ApiResponse<Track>> {
return postRequest<Track>("/admin/tracks", payload, "track", signal);
}

export async function updateTrack(
id: string,
payload: TrackPayload,
signal?: AbortSignal,
): Promise<ApiResponse<Track>> {
return putRequest<Track>(`/admin/tracks/${id}`, payload, "track", signal);
}

export async function deleteTrack(
id: string,
signal?: AbortSignal,
): Promise<ApiResponse<unknown>> {
return deleteRequest<unknown>(`/admin/tracks/${id}`, "track", signal);
}

export async function uploadTrackLogo(
trackId: string,
logoData: string,
contentType: string,
signal?: AbortSignal,
): Promise<ApiResponse<Track>> {
return putRequest<Track>(
`/admin/tracks/${trackId}/logo`,
{ logo_data: logoData, content_type: contentType },
"track logo",
signal,
);
}
Loading
Loading