page wrapper
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useParams } from 'next/navigation';
|
||||
import { LeagueScheduleTemplate } from '@/templates/LeagueScheduleTemplate';
|
||||
|
||||
export default function LeagueScheduleInteractive() {
|
||||
const params = useParams();
|
||||
const leagueId = params.id as string;
|
||||
|
||||
return <LeagueScheduleTemplate leagueId={leagueId} />;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { LeagueScheduleTemplate } from '@/templates/LeagueScheduleTemplate';
|
||||
|
||||
interface LeagueScheduleStaticProps {
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export default async function LeagueScheduleStatic({ leagueId }: LeagueScheduleStaticProps) {
|
||||
// The LeagueScheduleTemplate doesn't need data fetching - it delegates to LeagueSchedule component
|
||||
return <LeagueScheduleTemplate leagueId={leagueId} />;
|
||||
}
|
||||
@@ -1,140 +1,124 @@
|
||||
'use client';
|
||||
|
||||
import Card from '@/components/ui/Card';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import type { LeagueAdminScheduleViewModel } from '@/lib/view-models/LeagueAdminScheduleViewModel';
|
||||
import type { LeagueSeasonSummaryViewModel } from '@/lib/view-models/LeagueSeasonSummaryViewModel';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN, LEAGUE_MEMBERSHIP_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility';
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { LeagueAdminScheduleTemplate } from '@/templates/LeagueAdminScheduleTemplate';
|
||||
import {
|
||||
useLeagueAdminStatus,
|
||||
useLeagueSeasons,
|
||||
useLeagueAdminSchedule
|
||||
} from '@/hooks/league/useLeagueScheduleAdminPageData';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
export default function LeagueAdminSchedulePage() {
|
||||
const params = useParams();
|
||||
const leagueId = params.id as string;
|
||||
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
const currentDriverId = useEffectiveDriverId() || '';
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
const leagueMembershipService = useInject(LEAGUE_MEMBERSHIP_SERVICE_TOKEN);
|
||||
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [membershipLoading, setMembershipLoading] = useState(true);
|
||||
|
||||
const [seasons, setSeasons] = useState<LeagueSeasonSummaryViewModel[]>([]);
|
||||
// Form state
|
||||
const [seasonId, setSeasonId] = useState<string>('');
|
||||
|
||||
const [schedule, setSchedule] = useState<LeagueAdminScheduleViewModel | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [track, setTrack] = useState('');
|
||||
const [car, setCar] = useState('');
|
||||
const [scheduledAtIso, setScheduledAtIso] = useState('');
|
||||
|
||||
const [editingRaceId, setEditingRaceId] = useState<string | null>(null);
|
||||
const isEditing = editingRaceId !== null;
|
||||
|
||||
const publishedLabel = schedule?.published ? 'Published' : 'Unpublished';
|
||||
// Check admin status using domain hook
|
||||
const { data: isAdmin, isLoading: isAdminLoading } = useLeagueAdminStatus(leagueId, currentDriverId);
|
||||
|
||||
const selectedSeasonLabel = useMemo(() => {
|
||||
const selected = seasons.find((s) => s.seasonId === seasonId);
|
||||
return selected?.name ?? seasonId;
|
||||
}, [seasons, seasonId]);
|
||||
// Load seasons using domain hook
|
||||
const { data: seasonsData, isLoading: seasonsLoading } = useLeagueSeasons(leagueId, !!isAdmin);
|
||||
|
||||
const loadSchedule = async (leagueIdToLoad: string, seasonIdToLoad: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const vm = await leagueService.getAdminSchedule(leagueIdToLoad, seasonIdToLoad);
|
||||
setSchedule(vm);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// Auto-select season
|
||||
const selectedSeasonId = seasonId || (seasonsData && seasonsData.length > 0
|
||||
? (seasonsData.find((s) => s.status === 'active') ?? seasonsData[0])?.seasonId
|
||||
: '');
|
||||
|
||||
useEffect(() => {
|
||||
async function checkAdmin() {
|
||||
setMembershipLoading(true);
|
||||
try {
|
||||
await leagueMembershipService.fetchLeagueMemberships(leagueId);
|
||||
} finally {
|
||||
const membership = leagueMembershipService.getMembership(leagueId, currentDriverId);
|
||||
setIsAdmin(membership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(membership.role) : false);
|
||||
setMembershipLoading(false);
|
||||
// Load schedule using domain hook
|
||||
const { data: schedule, isLoading: scheduleLoading, refetch: refetchSchedule } = useLeagueAdminSchedule(leagueId, selectedSeasonId, !!isAdmin);
|
||||
|
||||
// Mutations
|
||||
const publishMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!schedule || !selectedSeasonId) return null;
|
||||
return schedule.published
|
||||
? await leagueService.unpublishAdminSchedule(leagueId, selectedSeasonId)
|
||||
: await leagueService.publishAdminSchedule(leagueId, selectedSeasonId);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['adminSchedule', leagueId, selectedSeasonId] });
|
||||
},
|
||||
});
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!selectedSeasonId || !scheduledAtIso) return null;
|
||||
|
||||
if (!editingRaceId) {
|
||||
return await leagueService.createAdminScheduleRace(leagueId, selectedSeasonId, {
|
||||
track,
|
||||
car,
|
||||
scheduledAtIso,
|
||||
});
|
||||
} else {
|
||||
return await leagueService.updateAdminScheduleRace(leagueId, selectedSeasonId, editingRaceId, {
|
||||
...(track ? { track } : {}),
|
||||
...(car ? { car } : {}),
|
||||
...(scheduledAtIso ? { scheduledAtIso } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
checkAdmin();
|
||||
}, [leagueId, currentDriverId, leagueMembershipService]);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadSeasons() {
|
||||
const loaded = await leagueService.getLeagueSeasonSummaries(leagueId);
|
||||
setSeasons(loaded);
|
||||
|
||||
if (loaded.length > 0) {
|
||||
const active = loaded.find((s) => s.status === 'active') ?? loaded[0];
|
||||
setSeasonId(active?.seasonId ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
if (isAdmin) {
|
||||
loadSeasons();
|
||||
}
|
||||
}, [leagueId, isAdmin, leagueService]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAdmin) return;
|
||||
if (!seasonId) return;
|
||||
|
||||
loadSchedule(leagueId, seasonId);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [leagueId, seasonId, isAdmin]);
|
||||
|
||||
const handlePublishToggle = async () => {
|
||||
if (!schedule) return;
|
||||
|
||||
if (schedule.published) {
|
||||
const vm = await leagueService.unpublishAdminSchedule(leagueId, seasonId);
|
||||
setSchedule(vm);
|
||||
return;
|
||||
}
|
||||
|
||||
const vm = await leagueService.publishAdminSchedule(leagueId, seasonId);
|
||||
setSchedule(vm);
|
||||
};
|
||||
|
||||
const handleAddOrSave = async () => {
|
||||
if (!seasonId) return;
|
||||
|
||||
if (!scheduledAtIso) return;
|
||||
|
||||
if (!isEditing) {
|
||||
const vm = await leagueService.createAdminScheduleRace(leagueId, seasonId, {
|
||||
track,
|
||||
car,
|
||||
scheduledAtIso,
|
||||
});
|
||||
setSchedule(vm);
|
||||
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['adminSchedule', leagueId, selectedSeasonId] });
|
||||
// Reset form
|
||||
setTrack('');
|
||||
setCar('');
|
||||
setScheduledAtIso('');
|
||||
return;
|
||||
}
|
||||
setEditingRaceId(null);
|
||||
},
|
||||
});
|
||||
|
||||
const vm = await leagueService.updateAdminScheduleRace(leagueId, seasonId, editingRaceId, {
|
||||
...(track ? { track } : {}),
|
||||
...(car ? { car } : {}),
|
||||
...(scheduledAtIso ? { scheduledAtIso } : {}),
|
||||
});
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: async (raceId: string) => {
|
||||
return await leagueService.deleteAdminScheduleRace(leagueId, selectedSeasonId, raceId);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['adminSchedule', leagueId, selectedSeasonId] });
|
||||
},
|
||||
});
|
||||
|
||||
setSchedule(vm);
|
||||
// Derived states
|
||||
const isLoading = isAdminLoading || seasonsLoading || scheduleLoading;
|
||||
const isPublishing = publishMutation.isPending;
|
||||
const isSaving = saveMutation.isPending;
|
||||
const isDeleting = deleteMutation.variables || null;
|
||||
|
||||
// Handlers
|
||||
const handleSeasonChange = (newSeasonId: string) => {
|
||||
setSeasonId(newSeasonId);
|
||||
setEditingRaceId(null);
|
||||
setTrack('');
|
||||
setCar('');
|
||||
setScheduledAtIso('');
|
||||
};
|
||||
|
||||
const handlePublishToggle = () => {
|
||||
publishMutation.mutate();
|
||||
};
|
||||
|
||||
const handleAddOrSave = () => {
|
||||
if (!scheduledAtIso) return;
|
||||
saveMutation.mutate();
|
||||
};
|
||||
|
||||
const handleEdit = (raceId: string) => {
|
||||
if (!schedule) return;
|
||||
|
||||
const race = schedule.races.find((r) => r.id === raceId);
|
||||
if (!race) return;
|
||||
|
||||
@@ -144,190 +128,78 @@ export default function LeagueAdminSchedulePage() {
|
||||
setScheduledAtIso(race.scheduledAt.toISOString());
|
||||
};
|
||||
|
||||
const handleDelete = async (raceId: string) => {
|
||||
const handleDelete = (raceId: string) => {
|
||||
const confirmed = window.confirm('Delete this race?');
|
||||
if (!confirmed) return;
|
||||
|
||||
const vm = await leagueService.deleteAdminScheduleRace(leagueId, seasonId, raceId);
|
||||
setSchedule(vm);
|
||||
deleteMutation.mutate(raceId);
|
||||
};
|
||||
|
||||
if (membershipLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<div className="py-6 text-sm text-gray-400">Loading…</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
const handleCancelEdit = () => {
|
||||
setEditingRaceId(null);
|
||||
setTrack('');
|
||||
setCar('');
|
||||
setScheduledAtIso('');
|
||||
};
|
||||
|
||||
if (!isAdmin) {
|
||||
// Prepare template data
|
||||
const templateData = schedule && seasonsData && selectedSeasonId
|
||||
? {
|
||||
schedule,
|
||||
seasons: seasonsData,
|
||||
seasonId: selectedSeasonId,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
// Render admin access required if not admin
|
||||
if (!isLoading && !isAdmin) {
|
||||
return (
|
||||
<Card>
|
||||
<div className="text-center py-12">
|
||||
<div className="space-y-6">
|
||||
<div className="bg-deep-graphite border border-charcoal-outline rounded-lg p-6 text-center">
|
||||
<h3 className="text-lg font-medium text-white mb-2">Admin Access Required</h3>
|
||||
<p className="text-sm text-gray-400">Only league admins can manage the schedule.</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Template component that wraps the actual template with all props
|
||||
const TemplateWrapper = ({ data }: { data: typeof templateData }) => {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<LeagueAdminScheduleTemplate
|
||||
data={data}
|
||||
onSeasonChange={handleSeasonChange}
|
||||
onPublishToggle={handlePublishToggle}
|
||||
onAddOrSave={handleAddOrSave}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
onCancelEdit={handleCancelEdit}
|
||||
track={track}
|
||||
car={car}
|
||||
scheduledAtIso={scheduledAtIso}
|
||||
editingRaceId={editingRaceId}
|
||||
isPublishing={isPublishing}
|
||||
isSaving={isSaving}
|
||||
isDeleting={isDeleting}
|
||||
setTrack={setTrack}
|
||||
setCar={setCar}
|
||||
setScheduledAtIso={setScheduledAtIso}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Schedule Admin</h1>
|
||||
<p className="text-sm text-gray-400">Create, edit, and publish season races.</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm text-gray-300" htmlFor="seasonId">
|
||||
Season
|
||||
</label>
|
||||
{seasons.length > 0 ? (
|
||||
<select
|
||||
id="seasonId"
|
||||
value={seasonId}
|
||||
onChange={(e) => setSeasonId(e.target.value)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
>
|
||||
{seasons.map((s) => (
|
||||
<option key={s.seasonId} value={s.seasonId}>
|
||||
{s.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
id="seasonId"
|
||||
value={seasonId}
|
||||
onChange={(e) => setSeasonId(e.target.value)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
placeholder="season-id"
|
||||
/>
|
||||
)}
|
||||
<p className="text-xs text-gray-500">Selected: {selectedSeasonLabel}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="text-sm text-gray-300">
|
||||
Status: <span className="font-medium text-white">{publishedLabel}</span>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handlePublishToggle}
|
||||
disabled={!schedule}
|
||||
className="px-3 py-1.5 rounded bg-primary-blue text-white disabled:opacity-50"
|
||||
>
|
||||
{schedule?.published ? 'Unpublish' : 'Publish'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-charcoal-outline pt-4 space-y-3">
|
||||
<h2 className="text-lg font-semibold text-white">{isEditing ? 'Edit race' : 'Add race'}</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="track" className="text-sm text-gray-300">
|
||||
Track
|
||||
</label>
|
||||
<input
|
||||
id="track"
|
||||
value={track}
|
||||
onChange={(e) => setTrack(e.target.value)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="car" className="text-sm text-gray-300">
|
||||
Car
|
||||
</label>
|
||||
<input
|
||||
id="car"
|
||||
value={car}
|
||||
onChange={(e) => setCar(e.target.value)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="scheduledAtIso" className="text-sm text-gray-300">
|
||||
Scheduled At (ISO)
|
||||
</label>
|
||||
<input
|
||||
id="scheduledAtIso"
|
||||
value={scheduledAtIso}
|
||||
onChange={(e) => setScheduledAtIso(e.target.value)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
placeholder="2025-01-01T12:00:00.000Z"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddOrSave}
|
||||
className="px-3 py-1.5 rounded bg-primary-blue text-white"
|
||||
>
|
||||
{isEditing ? 'Save' : 'Add race'}
|
||||
</button>
|
||||
|
||||
{isEditing && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditingRaceId(null)}
|
||||
className="px-3 py-1.5 rounded bg-iron-gray text-gray-200"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-charcoal-outline pt-4 space-y-3">
|
||||
<h2 className="text-lg font-semibold text-white">Races</h2>
|
||||
|
||||
{loading ? (
|
||||
<div className="py-4 text-sm text-gray-400">Loading schedule…</div>
|
||||
) : schedule?.races.length ? (
|
||||
<div className="space-y-2">
|
||||
{schedule.races.map((race) => (
|
||||
<div
|
||||
key={race.id}
|
||||
className="flex items-center justify-between gap-3 bg-deep-graphite border border-charcoal-outline rounded p-3"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className="text-white font-medium truncate">{race.name}</p>
|
||||
<p className="text-xs text-gray-400 truncate">{race.scheduledAt.toISOString()}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleEdit(race.id)}
|
||||
className="px-3 py-1.5 rounded bg-iron-gray text-gray-200"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDelete(race.id)}
|
||||
className="px-3 py-1.5 rounded bg-iron-gray text-gray-200"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-4 text-sm text-gray-500">No races yet.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<PageWrapper
|
||||
data={templateData}
|
||||
isLoading={isLoading}
|
||||
error={null}
|
||||
Template={TemplateWrapper}
|
||||
loading={{ variant: 'full-screen', message: 'Loading schedule admin...' }}
|
||||
empty={{
|
||||
title: 'No schedule data available',
|
||||
description: 'Unable to load schedule administration data',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,84 @@
|
||||
import LeagueScheduleInteractive from './LeagueScheduleInteractive';
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { LeagueScheduleTemplate } from '@/templates/LeagueScheduleTemplate';
|
||||
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
||||
import { LeagueService } from '@/lib/services/leagues/LeagueService';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import { SponsorsApiClient } from '@/lib/api/sponsors/SponsorsApiClient';
|
||||
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import { notFound } from 'next/navigation';
|
||||
import type { LeagueScheduleViewModel } from '@/lib/view-models/LeagueScheduleViewModel';
|
||||
|
||||
export default LeagueScheduleInteractive;
|
||||
interface Props {
|
||||
params: { id: string };
|
||||
}
|
||||
|
||||
export default async function Page({ params }: Props) {
|
||||
// Validate params
|
||||
if (!params.id) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Fetch data using PageDataFetcher.fetchManual for multiple dependencies
|
||||
const data = await PageDataFetcher.fetchManual(async () => {
|
||||
// Create dependencies
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
|
||||
// Create API clients
|
||||
const leaguesApiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
const driversApiClient = new DriversApiClient(baseUrl, errorReporter, logger);
|
||||
const sponsorsApiClient = new SponsorsApiClient(baseUrl, errorReporter, logger);
|
||||
const racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
// Create service
|
||||
const service = new LeagueService(
|
||||
leaguesApiClient,
|
||||
driversApiClient,
|
||||
sponsorsApiClient,
|
||||
racesApiClient
|
||||
);
|
||||
|
||||
// Fetch data
|
||||
const result = await service.getLeagueSchedule(params.id);
|
||||
if (!result) {
|
||||
throw new Error('League schedule not found');
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Create a wrapper component that passes data to the template
|
||||
const TemplateWrapper = ({ data }: { data: LeagueScheduleViewModel }) => {
|
||||
return (
|
||||
<LeagueScheduleTemplate
|
||||
data={data}
|
||||
leagueId={params.id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper
|
||||
data={data}
|
||||
Template={TemplateWrapper}
|
||||
loading={{ variant: 'skeleton', message: 'Loading schedule...' }}
|
||||
errorConfig={{ variant: 'full-screen' }}
|
||||
empty={{
|
||||
title: 'Schedule not found',
|
||||
description: 'The schedule for this league is not available.',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user