MotorSignal-App/website/components/founder-portal/founder-overview.tsx

250 lines
9.6 KiB
TypeScript

'use client';
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import {
Club,
ClubMetrics,
ClubUpcomingRide,
ClubMember,
ClubActivityItem,
UserPortalPermissions,
ClubRole,
} from './types';
import { ClubMasthead } from './club-masthead';
import { ExecutiveMetrics } from './metric-card';
import { AttentionSection } from './attention-card';
import { UpcomingRidesSection } from './club-ride-card';
import { RolesAndRanksSummary } from './roles-and-ranks-summary';
import { ClubActivityList } from './club-activity-list';
interface FounderOverviewProps {
club: Club;
metrics: ClubMetrics;
upcomingRides: ClubUpcomingRide[];
pendingApplications: ClubMember[];
activities: ClubActivityItem[];
permissions: UserPortalPermissions;
onRefresh?: () => void;
}
export function FounderOverview({
club,
metrics: initialMetrics,
upcomingRides: initialRides,
pendingApplications: initialApps,
activities,
permissions,
onRefresh,
}: FounderOverviewProps) {
const router = useRouter();
const [pendingApps, setPendingApps] = useState<ClubMember[]>(initialApps);
const [rides] = useState<ClubUpcomingRide[]>(initialRides);
const [metrics, setMetrics] = useState<ClubMetrics>(initialMetrics);
const [notification, setNotification] = useState<string | null>(null);
// Modal dialog state
const [activeModal, setActiveModal] = useState<'plan_ride' | 'invite_member' | 'settings' | 'announcement' | 'switch_club' | null>(null);
function showToast(message: string) {
setNotification(message);
setTimeout(() => {
setNotification((curr) => (curr === message ? null : curr));
}, 4000);
}
function handleApproveApplication(uid: string) {
if (!permissions.canApproveApplications) {
showToast('Administrative privileges are required to approve applications.');
return;
}
const target = pendingApps.find((a) => a.uid === uid);
setPendingApps((prev) => prev.filter((a) => a.uid !== uid));
setMetrics((prev) => ({
...prev,
members: prev.members + 1,
pendingApplications: Math.max(0, prev.pendingApplications - 1),
}));
showToast(`Approved ${target?.displayName ?? 'Applicant'}'s membership.`);
}
function handleReviewApplication(uid: string) {
if (uid === 'all') {
showToast('Navigating to Applications triage management queue.');
} else {
const target = pendingApps.find((a) => a.uid === uid);
showToast(`Reviewing applicant profile: ${target?.displayName ?? 'Rider'}`);
}
}
function handleAssignSweep(rideId: string) {
if (!permissions.canPlanRides) {
showToast('Road Captain or administrative role required to assign sweep positions.');
return;
}
showToast('Sweep assignment modal opened. Designate qualified Road Marshal.');
}
return (
<div className="flex flex-col space-y-6">
{/* Toast Notification */}
{notification && (
<div
role="status"
className="fixed bottom-6 right-6 z-50 rounded-lg bg-surface-container-high border border-outline-variant px-4 py-3 shadow-xl flex items-center gap-2.5 text-xs text-on-surface font-body animate-fadeIn"
>
<span className="material-symbols-outlined text-secondary text-[18px]">info</span>
<span>{notification}</span>
<button
onClick={() => setNotification(null)}
className="text-outline hover:text-on-surface ml-2"
>
<span className="material-symbols-outlined text-[16px]">close</span>
</button>
</div>
)}
{/* Member Access Notice if current role is member */}
{!permissions.canManageSettings && (
<div className="p-3.5 rounded-lg bg-surface-container-high border border-outline-variant text-on-surface-variant flex items-center justify-between gap-3 text-xs font-body">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-primary text-[18px]">visibility</span>
<span>
Viewing Founder Portal in <strong className="text-on-surface">Member View Mode</strong>. Administrative actions and mutations are restricted to Club Owner and Admins.
</span>
</div>
<span className="font-mono text-[11px] text-outline shrink-0">View-Only</span>
</div>
)}
{/* 1. CLUB IDENTITY MASTHEAD */}
<ClubMasthead
club={club}
permissions={permissions}
onPlanRide={() => setActiveModal('plan_ride')}
onInviteMember={() => setActiveModal('invite_member')}
onOpenSettings={() => setActiveModal('settings')}
/>
{/* 2. EXECUTIVE METRICS */}
<ExecutiveMetrics metrics={metrics} />
{/* 3. ATTENTION REQUIRED */}
<AttentionSection
pendingApplications={pendingApps}
upcomingRides={rides}
permissions={permissions}
onApproveApplication={handleApproveApplication}
onReviewApplication={handleReviewApplication}
onAssignSweep={handleAssignSweep}
onMarkChecked={() => showToast('All pending review alerts acknowledged.')}
/>
{/* 4. UPCOMING CLUB RIDES */}
<UpcomingRidesSection
rides={rides}
permissions={permissions}
clubId={club.slug || club.id}
onPlanRide={() => setActiveModal('plan_ride')}
onManageRide={(rideId) => showToast(`Opening management cockpit for Ride ID: ${rideId}`)}
onViewRoster={(rideId) => {
if (rideId === 'all') {
showToast('Full ride schedule is planned for a future release.');
} else {
showToast('Member roster details will be available in an upcoming release.');
}
}}
onViewRoute={(routeId) => showToast(`Opening route navigation map: ${routeId ?? 'default'}`)}
/>
{/* 5. ROLES & RANKS */}
<RolesAndRanksSummary
ownerName={club.founderName || 'Club Founder'}
memberCount={club.memberCount}
/>
{/* 6. CLUB ACTIVITY */}
<ClubActivityList activities={activities} />
{/* Modal Dialogs */}
{activeModal && (
<div
role="dialog"
aria-modal="true"
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm"
>
<div className="w-full max-w-md rounded-xl bg-surface-container-lowest border border-outline-variant p-6 shadow-2xl space-y-4">
<div className="flex items-center justify-between pb-3 border-b border-outline-variant">
<h3 className="font-headline text-base font-semibold text-on-surface">
{activeModal === 'plan_ride' && 'Plan Club Ride'}
{activeModal === 'invite_member' && 'Invite New Member'}
{activeModal === 'settings' && 'Club Administration Settings'}
{activeModal === 'announcement' && 'Post Club Announcement'}
{activeModal === 'switch_club' && 'Switch Club Roster'}
</h3>
<button
onClick={() => setActiveModal(null)}
className="text-outline hover:text-on-surface p-1"
aria-label="Close dialog"
>
<span className="material-symbols-outlined text-[20px]">close</span>
</button>
</div>
<div className="font-body text-xs text-on-surface-variant space-y-3">
{activeModal === 'plan_ride' && (
<p>
Create a new scheduled Group Ride associated with <strong>{club.name}</strong>. Designated routes and checkpoints will sync with ride participants.
</p>
)}
{activeModal === 'invite_member' && (
<p>
Send an official membership invitation to a verified rider handle. Once accepted, they will join the club roster.
</p>
)}
{activeModal === 'settings' && (
<p>
Club metadata, visibility policy, admission policy, and regional preferences are administered here.
</p>
)}
{activeModal === 'announcement' && (
<p>
Broadcast an administrative update to all active members of {club.name}.
</p>
)}
{activeModal === 'switch_club' && (
<p>
Select another registered Motorcycle Club from your active memberships.
</p>
)}
<div className="p-3 rounded bg-surface-container border border-outline-variant text-outline font-mono text-[11px]">
FEATURE CONSOLE // STAGE FP-1 PROD FOUNDATION
</div>
</div>
<div className="pt-3 border-t border-outline-variant/50 flex items-center justify-end gap-2">
<button
onClick={() => setActiveModal(null)}
className="h-9 px-4 rounded-xl bg-surface-container text-slate-200 border border-outline-variant/60 font-body text-xs hover:bg-surface-container-high transition-colors cursor-pointer"
>
Close
</button>
<button
onClick={() => {
showToast('Action acknowledged.');
setActiveModal(null);
}}
className="h-9 px-4 rounded-xl bg-primary-container text-on-primary-container font-body text-xs font-bold hover:brightness-110 shadow-[0_0_12px_rgba(0,242,254,0.25)] transition-all cursor-pointer"
>
Confirm
</button>
</div>
</div>
</div>
)}
</div>
);
}