288 lines
13 KiB
TypeScript
288 lines
13 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import { useRouter } from 'next/navigation';
|
|
import { onAuthStateChanged, User } from 'firebase/auth';
|
|
import { firebaseAuth } from '../../../lib/firebase-client';
|
|
import { fetchManageableClubs, ManageableClubSummary } from '../../../lib/club-data';
|
|
import { OverviewSkeleton } from '../../../components/founder-portal/skeleton-state';
|
|
import { SafeErrorState } from '../../../components/founder-portal/safe-error-state';
|
|
|
|
export default function ClubManageDiscoveryPage() {
|
|
const router = useRouter();
|
|
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
|
const [authChecked, setAuthChecked] = useState(false);
|
|
const [loading, setLoading] = useState(true);
|
|
const [clubs, setClubs] = useState<ManageableClubSummary[]>([]);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
const unsubscribe = onAuthStateChanged(firebaseAuth, (user) => {
|
|
setCurrentUser(user);
|
|
setAuthChecked(true);
|
|
});
|
|
return () => unsubscribe();
|
|
}, []);
|
|
|
|
async function loadClubs(uid: string) {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const results = await fetchManageableClubs(uid);
|
|
setClubs(results);
|
|
if (results.length === 1) {
|
|
// Automatically redirect to the single manageable club
|
|
const target = results[0].slug || results[0].id;
|
|
router.replace(`/clubs/${target}/manage`);
|
|
}
|
|
} catch {
|
|
setError("Unable to discover manageable clubs. Please check your network connection.");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!authChecked) return;
|
|
if (currentUser) {
|
|
void loadClubs(currentUser.uid);
|
|
} else {
|
|
setLoading(false);
|
|
}
|
|
}, [authChecked, currentUser]);
|
|
|
|
// Loading state (auth checking or discovering clubs)
|
|
if (!authChecked || loading) {
|
|
return (
|
|
<div className="min-h-screen bg-brand-bg telemetry-grid-bg pt-28 pb-20 px-4 sm:px-6 lg:px-8">
|
|
<div className="max-w-5xl mx-auto">
|
|
<OverviewSkeleton />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Unauthenticated state: prompt sign-in
|
|
if (!currentUser) {
|
|
return (
|
|
<div className="min-h-screen bg-brand-bg telemetry-grid-bg flex items-center justify-center p-4 sm:p-6 pt-28 pb-20">
|
|
<div className="glass-card rounded-2xl border border-brand-border p-8 sm:p-10 max-w-lg w-full text-center shadow-2xl space-y-6 relative overflow-hidden">
|
|
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-brand-cyan/80 via-blue-500/40 to-transparent"></div>
|
|
|
|
<div className="w-16 h-16 rounded-2xl bg-surface-container border border-brand-cyan/40 text-brand-cyan shadow-[0_0_20px_rgba(0,242,254,0.2)] flex items-center justify-center mx-auto">
|
|
<span className="material-symbols-outlined text-[32px]">lock</span>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="inline-flex items-center gap-1.5 font-mono text-xs tracking-wider text-brand-cyan font-semibold mb-1.5">
|
|
<span className="w-2 h-2 rounded-full bg-brand-cyan animate-pulse"></span>
|
|
<span>Rider Authentication Required</span>
|
|
</div>
|
|
<h1 className="font-headline text-3xl font-extrabold text-white tracking-tight">
|
|
Sign In to Access Portal
|
|
</h1>
|
|
<p className="font-body text-xs sm:text-sm text-slate-400 mt-2 leading-relaxed">
|
|
Founder Portal requires an active MotorSignal rider account with Club Owner or Admin administration permissions.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-3 pt-2">
|
|
<Link
|
|
href="/login?returnUrl=/clubs/manage"
|
|
className="w-full py-3 rounded-xl bg-primary-container text-on-primary-container font-headline-sm text-xs sm:text-sm font-bold hover:brightness-110 shadow-[0_0_20px_rgba(0,242,254,0.35)] transition-all flex items-center justify-center gap-2"
|
|
>
|
|
<span className="material-symbols-outlined text-[18px]">login</span>
|
|
<span>Sign In with Rider Account</span>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/register"
|
|
className="w-full py-3 rounded-xl bg-surface-container border border-outline-variant/60 text-slate-200 hover:text-white hover:bg-surface-container-high transition-colors font-body text-xs sm:text-sm font-medium flex items-center justify-center"
|
|
>
|
|
<span>Create New Account</span>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/clubs"
|
|
className="font-mono text-xs text-slate-400 hover:text-brand-cyan transition-colors mt-2"
|
|
>
|
|
Browse Public Motorcycle Clubs →
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Error state
|
|
if (error) {
|
|
return (
|
|
<div className="min-h-screen bg-brand-bg telemetry-grid-bg flex items-center justify-center p-4 sm:p-6 pt-28 pb-20">
|
|
<SafeErrorState
|
|
title="Discovery Error"
|
|
message={error}
|
|
onRetry={() => loadClubs(currentUser.uid)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Single club redirecting transition
|
|
if (clubs.length === 1) {
|
|
return (
|
|
<div className="min-h-screen bg-brand-bg telemetry-grid-bg flex items-center justify-center p-4 sm:p-6 pt-28 pb-20">
|
|
<div className="text-center space-y-4 font-body glass-card rounded-2xl border border-brand-border p-8 max-w-sm w-full">
|
|
<div className="w-12 h-12 border-3 border-brand-cyan border-t-transparent rounded-full animate-spin mx-auto shadow-[0_0_16px_rgba(0,242,254,0.3)]" />
|
|
<p className="font-mono text-xs text-brand-cyan font-medium">
|
|
Redirecting to {clubs[0].name}...
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 0 clubs: No clubs to manage
|
|
if (clubs.length === 0) {
|
|
return (
|
|
<div className="min-h-screen bg-brand-bg telemetry-grid-bg flex items-center justify-center p-4 sm:p-6 pt-28 pb-20">
|
|
<div className="glass-card rounded-2xl border border-brand-border p-8 sm:p-10 max-w-lg w-full text-center shadow-2xl space-y-6 relative overflow-hidden">
|
|
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-brand-cyan/80 via-blue-500/40 to-transparent"></div>
|
|
|
|
<div className="w-16 h-16 rounded-2xl bg-surface-container border border-outline-variant/60 text-slate-400 flex items-center justify-center mx-auto">
|
|
<span className="material-symbols-outlined text-[32px]">hub</span>
|
|
</div>
|
|
|
|
<div>
|
|
<span className="font-mono text-xs tracking-wider text-slate-400 font-semibold">
|
|
Management Roster // 0 Clubs
|
|
</span>
|
|
<h1 className="font-headline text-3xl font-extrabold text-white tracking-tight mt-1.5">
|
|
No Clubs to Manage
|
|
</h1>
|
|
<p className="font-body text-xs sm:text-sm text-slate-400 mt-2 leading-relaxed max-w-md mx-auto">
|
|
Your rider account doesn't currently have Owner or Admin permissions for any registered clubs. To manage a club, create a new club or request admin privileges from your club owner.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 pt-2">
|
|
<Link
|
|
href="/clubs#register-club"
|
|
className="w-full sm:w-auto px-6 py-3 rounded-xl bg-primary-container text-on-primary-container font-headline-sm text-xs sm:text-sm font-bold hover:brightness-110 shadow-[0_0_16px_rgba(0,242,254,0.3)] transition-all flex items-center justify-center gap-2"
|
|
>
|
|
<span className="material-symbols-outlined text-[18px]">add_circle</span>
|
|
<span>Register Your Club</span>
|
|
</Link>
|
|
|
|
<Link
|
|
href="/clubs"
|
|
className="w-full sm:w-auto px-6 py-3 rounded-xl bg-surface-container border border-outline-variant/60 text-slate-200 font-body text-xs sm:text-sm font-medium hover:bg-surface-container-high transition-colors flex items-center justify-center gap-2"
|
|
>
|
|
<span className="material-symbols-outlined text-[18px]">explore</span>
|
|
<span>Browse Public Clubs</span>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// >1 clubs: Clean Club Selector
|
|
return (
|
|
<div className="min-h-screen bg-brand-bg telemetry-grid-bg text-slate-200 pt-28 pb-20 px-4 sm:px-6 lg:px-8">
|
|
<div className="max-w-5xl mx-auto space-y-8">
|
|
{/* Header */}
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 pb-6 border-b border-brand-border">
|
|
<div>
|
|
<div className="inline-flex items-center gap-1.5 font-mono text-xs tracking-wider text-brand-cyan font-semibold mb-1">
|
|
<span className="w-2 h-2 rounded-full bg-brand-cyan animate-pulse"></span>
|
|
<span>Founder Portal Cockpit</span>
|
|
</div>
|
|
<h1 className="font-headline text-3xl sm:text-4xl font-extrabold text-white tracking-tight">
|
|
Select Club to Manage
|
|
</h1>
|
|
<p className="font-body text-sm sm:text-base text-slate-400 mt-1">
|
|
Choose an active motorcycle club from your authorized administrative rosters.
|
|
</p>
|
|
</div>
|
|
|
|
<Link
|
|
href="/clubs#register-club"
|
|
className="self-start sm:self-auto px-4 py-2.5 rounded-xl bg-surface-container border border-outline-variant/60 text-slate-200 hover:text-white hover:border-brand-cyan/50 font-body text-xs sm:text-sm font-medium transition-all shadow-sm flex items-center gap-2"
|
|
>
|
|
<span className="material-symbols-outlined text-[18px] text-brand-cyan">add</span>
|
|
<span>Register New Club</span>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Club Cards Grid */}
|
|
<div className="grid grid-cols-1 gap-4">
|
|
{clubs.map((c) => (
|
|
<Link
|
|
key={c.id}
|
|
href={`/clubs/${c.slug || c.id}/manage`}
|
|
className="group rounded-2xl glass-card border border-brand-border hover:border-brand-cyan/60 p-6 transition-all shadow-lg hover:shadow-[0_0_24px_rgba(0,242,254,0.15)] flex flex-col sm:flex-row sm:items-center sm:justify-between gap-5 relative overflow-hidden"
|
|
>
|
|
<div className="absolute top-0 left-0 right-0 h-0.5 bg-gradient-to-r from-brand-cyan via-blue-500/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
|
|
|
<div className="flex items-center gap-4 sm:gap-5 min-w-0">
|
|
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-brand-borderLight to-slate-900 border border-brand-cyan/30 flex items-center justify-center text-brand-cyan shrink-0 group-hover:border-brand-cyan group-hover:shadow-[0_0_16px_rgba(0,242,254,0.35)] transition-all">
|
|
<span className="material-symbols-outlined text-[28px]">sports_motorsports</span>
|
|
</div>
|
|
|
|
<div className="min-w-0">
|
|
<div className="flex items-center gap-2.5">
|
|
<h2 className="font-headline text-lg sm:text-xl font-bold text-white group-hover:text-brand-cyan transition-colors truncate">
|
|
{c.name}
|
|
</h2>
|
|
<span
|
|
className={`px-2.5 py-0.5 rounded-full font-mono text-xs font-semibold tracking-normal ${
|
|
c.role === 'owner'
|
|
? 'bg-primary-container/15 text-primary-container border border-primary-container/30'
|
|
: 'bg-secondary-container/15 text-secondary-fixed border border-secondary-container/30'
|
|
}`}
|
|
>
|
|
{c.role === 'owner' ? 'Owner' : 'Admin'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-2.5 font-mono text-xs text-slate-400 mt-1.5">
|
|
{c.slug && <span className="text-slate-300">/{c.slug}</span>}
|
|
<span>•</span>
|
|
<span>{c.memberCount} {c.memberCount === 1 ? 'member' : 'members'}</span>
|
|
{c.homeRegion && (
|
|
<>
|
|
<span>•</span>
|
|
<span>{c.homeRegion}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 self-end sm:self-center font-mono text-xs text-brand-cyan group-hover:translate-x-1 transition-transform shrink-0 font-medium">
|
|
<span>Manage Cockpit</span>
|
|
<span className="material-symbols-outlined text-[20px]">
|
|
arrow_forward
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
{/* Footer info */}
|
|
<div className="p-4 rounded-xl glass-card border border-outline-variant/40 text-slate-400 font-body text-xs flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className="material-symbols-outlined text-[18px] text-brand-cyan">shield</span>
|
|
<span>Only clubs where you are an active Owner or Admin appear here.</span>
|
|
</div>
|
|
<Link href="/clubs" className="font-mono text-xs text-brand-cyan hover:underline">
|
|
All Public Clubs →
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|