MotorSignal-App/website/app/clubs/manage/page.tsx

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&apos;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>
);
}