diff --git a/src/app/[locale]/admin/clubs/AdminClubsList.tsx b/src/app/[locale]/admin/clubs/AdminClubsList.tsx new file mode 100644 index 0000000..0bcd677 --- /dev/null +++ b/src/app/[locale]/admin/clubs/AdminClubsList.tsx @@ -0,0 +1,175 @@ +'use client'; + +import { Building, AlertCircle, Lock, Loader2 } from 'lucide-react'; +import Link from 'next/link'; +import { useEffect, useState } from 'react'; +import { getAdminClubs } from '@/src/lib/api/admin-clubs'; +import type { AdminClubsResponse, AdminApiError } from '@/src/types/admin-api'; + +interface AdminClubsListProps { + locale: string; + t: (key: string) => string; +} + +export default function AdminClubsList({ locale, t }: AdminClubsListProps) { + const [clubs, setClubs] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function loadClubs() { + setLoading(true); + const result = await getAdminClubs(); + + if (result.success) { + setClubs(result.data); + setError(null); + } else { + setError(result.error); + setClubs(null); + } + + setLoading(false); + } + + loadClubs(); + }, []); + + // Loading state + if (loading) { + return ( +
+
+ +

{t('Loading clubs...')}

+
+
+ ); + } + + // Authentication error (401) + if (error && error.status === 401) { + return ( +
+
+
+
+ +
+

+ {t('Authentication Required')} +

+

+ {t('Please log in to access the venue management portal.')} +

+

+ {t('If you are a venue administrator and do not have access, please contact support.')} +

+
+
+
+
+
+ ); + } + + // Other API errors + if (error) { + return ( +
+
+
+
+ +
+

+ {t('Error Loading Clubs')} +

+

+ {error.detail} +

+

+ {t('Error code')}: {error.code} +

+
+
+
+
+
+ ); + } + + // No clubs assigned + if (!clubs || clubs.length === 0) { + return ( +
+
+

+ {t('Club Management')} +

+

+ {t('View and manage your venue locations')} +

+
+ +
+
+ +

+ {t('No Clubs Assigned')} +

+

+ {t('You are not currently assigned as an administrator for any clubs. Contact your organization to request access.')} +

+
+
+
+ ); + } + + // Success - render clubs list + return ( +
+
+

+ {t('Club Management')} +

+

+ {t('View and manage your venue locations')} +

+
+ +
+ {clubs.map((club) => ( + +
+
+
+ +
+
+

+ {club.name} +

+

+ {club.timezone} +

+
+
+
+
+
+ {t('Courts')} + {club.courts} +
+
+ + ))} +
+
+ ); +} diff --git a/src/app/[locale]/admin/clubs/[club_id]/AdminClubDetail.tsx b/src/app/[locale]/admin/clubs/[club_id]/AdminClubDetail.tsx new file mode 100644 index 0000000..e86a593 --- /dev/null +++ b/src/app/[locale]/admin/clubs/[club_id]/AdminClubDetail.tsx @@ -0,0 +1,262 @@ +'use client'; + +import { Building, AlertCircle, Lock, Loader2, ArrowLeft, MapPin, Calendar, Server } from 'lucide-react'; +import Link from 'next/link'; +import { useEffect, useState } from 'react'; +import { getAdminClubDetail } from '@/src/lib/api/admin-clubs'; +import type { AdminClubDetail, AdminApiError } from '@/src/types/admin-api'; + +interface AdminClubDetailProps { + clubId: number; + locale: string; + t: (key: string) => string; +} + +export default function AdminClubDetailComponent({ clubId, locale, t }: AdminClubDetailProps) { + const [clubDetail, setClubDetail] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function loadClubDetail() { + setLoading(true); + const result = await getAdminClubDetail(clubId); + + if (result.success) { + setClubDetail(result.data); + setError(null); + } else { + setError(result.error); + setClubDetail(null); + } + + setLoading(false); + } + + loadClubDetail(); + }, [clubId]); + + // Loading state + if (loading) { + return ( +
+
+ +

{t('Loading club details...')}

+
+
+ ); + } + + // Authentication error (401) + if (error && error.status === 401) { + return ( +
+
+
+
+ +
+

+ {t('Authentication Required')} +

+

+ {t('Please log in to access the venue management portal.')} +

+
+
+
+
+
+ ); + } + + // Forbidden error (403) + if (error && error.status === 403) { + return ( +
+
+
+
+ +
+

+ {t('Access Denied')} +

+

+ {error.detail} +

+ + + {t('Back to clubs')} + +
+
+
+
+
+ ); + } + + // Other API errors + if (error) { + return ( +
+
+
+
+ +
+

+ {t('Error Loading Club')} +

+

+ {error.detail} +

+

+ {t('Error code')}: {error.code} +

+ + + {t('Back to clubs')} + +
+
+
+
+
+ ); + } + + if (!clubDetail) { + return null; + } + + // Success - render club detail + return ( +
+ {/* Breadcrumb */} +
+ + + {t('Back to clubs')} + +
+ + {/* Club Header */} +
+
+
+ +
+
+

+ {clubDetail.club.name} +

+
+
+ + {clubDetail.club.timezone} +
+
+
+
+
+ + {/* Provider Info */} +
+
+ +

{t('Provider Information')}

+
+
+
+ {t('Type')}: + {clubDetail.provider.remote_type} +
+
+ {t('Manages Slot Storage')}: + + {clubDetail.provider.capabilities.manages_slot_storage ? t('Yes') : t('No')} + +
+
+ {t('Supports Payment Verification')}: + + {clubDetail.provider.capabilities.supports_payment_verification ? t('Yes') : t('No')} + +
+
+
+ + {/* Courts */} +
+

{t('Courts')}

+ {clubDetail.courts.length === 0 ? ( +

{t('No courts configured')}

+ ) : ( +
+ {clubDetail.courts.map((court) => ( +
+

{court.name}

+
+ ))} +
+ )} +
+ + {/* Slot Definitions */} +
+
+ +

{t('Slot Definitions')}

+
+ {clubDetail.slot_definitions.length === 0 ? ( +

{t('No slot definitions configured')}

+ ) : ( +
+ {clubDetail.slot_definitions.map((slotDef, index) => ( +
+

{t('Slot definition')} #{index + 1}

+
+ ))} +
+ )} +
+ + {/* Upcoming Slots */} +
+

{t('Upcoming Slots')}

+ {clubDetail.upcoming_slots.length === 0 ? ( +

{t('No upcoming slots available')}

+ ) : ( +
+ {clubDetail.upcoming_slots.map((slot, index) => ( +
+

{t('Slot')} #{index + 1}

+
+ ))} +
+ )} +
+
+ ); +} diff --git a/src/app/[locale]/admin/clubs/[club_id]/page.tsx b/src/app/[locale]/admin/clubs/[club_id]/page.tsx new file mode 100644 index 0000000..0d0de1e --- /dev/null +++ b/src/app/[locale]/admin/clubs/[club_id]/page.tsx @@ -0,0 +1,15 @@ +import { Locale } from '@/i18n-config'; +import { getTranslate } from '../../../dictionaries'; +import AdminClubDetailComponent from './AdminClubDetail'; + +export default async function AdminClubDetailPage({ + params +}: { + params: Promise<{ locale: Locale; club_id: string }> +}) { + const { locale, club_id } = await params; + const {t} = await getTranslate(locale); + const clubId = parseInt(club_id, 10); + + return ; +} diff --git a/src/app/[locale]/admin/clubs/page.tsx b/src/app/[locale]/admin/clubs/page.tsx index d22a12e..e0dff73 100644 --- a/src/app/[locale]/admin/clubs/page.tsx +++ b/src/app/[locale]/admin/clubs/page.tsx @@ -1,131 +1,10 @@ import { Locale } from '@/i18n-config'; import { getTranslate } from '../../dictionaries'; -import { Building, AlertCircle, Lock } from 'lucide-react'; -import { getAdminClubs } from '@/src/lib/api/admin-clubs'; -import Link from 'next/link'; -import { cookies } from 'next/headers'; +import AdminClubsList from './AdminClubsList'; export default async function AdminClubsPage({ params }: { params: Promise<{ locale: Locale }>}) { const { locale } = await params; const {t} = await getTranslate(locale); - // Get cookies for SSR auth - const cookieStore = await cookies(); - const cookieHeader = cookieStore.toString(); - - // Fetch clubs from API - this will handle auth states via HTTP status codes - const result = await getAdminClubs(cookieHeader); - - // Handle authentication error (401) - if (!result.success && result.error.status === 401) { - return ( -
-
-
-
- -
-

- {t('Authentication Required')} -

-

- {t('Please log in to access the venue management portal.')} -

-

- {t('If you are a venue administrator and do not have access, please contact support.')} -

-
-
-
-
-
- ); - } - - // Handle other API errors - if (!result.success) { - return ( -
-
-
-
- -
-

- {t('Error Loading Clubs')} -

-

- {result.error.detail} -

-

- {t('Error code')}: {result.error.code} -

-
-
-
-
-
- ); - } - - const clubs = result.data; - - return ( -
-
-

- {t('Club Management')} -

-

- {t('View and manage your venue locations')} -

-
- - {clubs.length === 0 ? ( -
-
- -

- {t('No Clubs Assigned')} -

-

- {t('You are not currently assigned as an administrator for any clubs. Contact your organization to request access.')} -

-
-
- ) : ( -
- {clubs.map((club) => ( - -
-
-
- -
-
-

- {club.name} -

-

- {club.timezone} -

-
-
-
-
-
- {t('Courts')} - {club.courts} -
-
- - ))} -
- )} -
- ); + return ; } diff --git a/src/app/[locale]/dashboard/page.tsx b/src/app/[locale]/dashboard/page.tsx new file mode 100644 index 0000000..52204a3 --- /dev/null +++ b/src/app/[locale]/dashboard/page.tsx @@ -0,0 +1,13 @@ +import { redirect } from 'next/navigation'; +import { Locale } from '@/i18n-config'; + +export default async function DashboardPage({ + params +}: { + params: Promise<{ locale: Locale }> +}) { + const { locale } = await params; + + // Manager portal dashboard redirects to club management + redirect(`/${locale}/admin/clubs`); +}