'use client'; import React, { useState, useEffect } from 'react'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { Club, UserPortalPermissions, ClubVisibility, ClubAdmissionPolicy, ClubMember, } from './types'; import { updateClubAccessSettings, fetchEligibleTransferMembers, transferClubOwnership, archiveClub, sanitizePortalError, } from '../../lib/club-data'; interface FounderSettingsEditorProps { club: Club; permissions: UserPortalPermissions; callerUid?: string; isDemoFixture?: boolean; onClubUpdated?: (updated: Partial) => void; } export function FounderSettingsEditor({ club, permissions, callerUid, isDemoFixture = false, onClubUpdated, }: FounderSettingsEditorProps) { const router = useRouter(); const searchParams = useSearchParams(); const isDemo = searchParams?.get('preview') === 'demo' || isDemoFixture; const queryParam = isDemo ? '?preview=demo' : ''; const clubParam = club.slug || club.id; // Access Settings Form State const [visibility, setVisibility] = useState(club.visibility || 'public'); const [admissionPolicy, setAdmissionPolicy] = useState(club.admissionPolicy || 'request'); // Mutation / UI feedback states const [isSavingAccess, setIsSavingAccess] = useState(false); const [accessSaveStatus, setAccessSaveStatus] = useState<'idle' | 'success' | 'error'>('idle'); const [accessErrorMessage, setAccessErrorMessage] = useState(null); // Ownership Transfer Modal State const [isTransferModalOpen, setIsTransferModalOpen] = useState(false); const [eligibleMembers, setEligibleMembers] = useState([]); const [loadingMembers, setLoadingMembers] = useState(false); const [selectedTargetUid, setSelectedTargetUid] = useState(''); const [isTransferring, setIsTransferring] = useState(false); const [transferError, setTransferError] = useState(null); // Archive Modal State const [isArchiveModalOpen, setIsArchiveModalOpen] = useState(false); const [isArchiving, setIsArchiving] = useState(false); const [archiveError, setArchiveError] = useState(null); const isOwner = permissions.role === 'owner' || permissions.canTransferOwnership; const isAdmin = permissions.role === 'admin' || isOwner; const canManageAccess = isAdmin && permissions.canManageSettings; const isArchived = Boolean(club.archivedAt); const isAccessDirty = visibility !== club.visibility || admissionPolicy !== club.admissionPolicy; // Load eligible members for ownership transfer when modal opens useEffect(() => { if (isTransferModalOpen && callerUid) { setLoadingMembers(true); setTransferError(null); fetchEligibleTransferMembers(club.id, callerUid, { allowDemoPreview: isDemo }) .then((members) => { setEligibleMembers(members); if (members.length > 0) { setSelectedTargetUid(members[0].uid); } }) .catch((err) => { setTransferError(sanitizePortalError(err, 'Failed to load eligible active members.')); }) .finally(() => { setLoadingMembers(false); }); } }, [isTransferModalOpen, club.id, callerUid, isDemo]); // Handle Save Access Settings async function handleSaveAccess(e: React.FormEvent) { e.preventDefault(); if (!canManageAccess || isArchived || isSavingAccess || !isAccessDirty) return; setIsSavingAccess(true); setAccessSaveStatus('idle'); setAccessErrorMessage(null); try { if (isDemo) { await new Promise((resolve) => setTimeout(resolve, 600)); setAccessSaveStatus('success'); onClubUpdated?.({ visibility, admissionPolicy }); } else { if (!callerUid) { throw new Error('Authentication required to update access settings.'); } await updateClubAccessSettings(club.id, callerUid, { visibility, admissionPolicy, }); setAccessSaveStatus('success'); onClubUpdated?.({ visibility, admissionPolicy }); } } catch (err) { setAccessSaveStatus('error'); setAccessErrorMessage(sanitizePortalError(err, 'Failed to update access settings. Please try again.')); } finally { setIsSavingAccess(false); } } // Handle Ownership Transfer async function handleConfirmTransfer() { if (!isOwner || isArchived || isTransferring || !selectedTargetUid) return; setIsTransferring(true); setTransferError(null); try { if (isDemo) { await new Promise((resolve) => setTimeout(resolve, 800)); setIsTransferModalOpen(false); onClubUpdated?.({ ownerUid: selectedTargetUid, founderName: 'Transferred Owner', }); } else { if (!callerUid) { throw new Error('Authentication required to transfer ownership.'); } await transferClubOwnership(club.id, callerUid, selectedTargetUid); setIsTransferModalOpen(false); onClubUpdated?.({ ownerUid: selectedTargetUid, }); router.refresh(); } } catch (err) { setTransferError(sanitizePortalError(err, 'Failed to transfer ownership. Please try again.')); } finally { setIsTransferring(false); } } // Handle Archive Club async function handleConfirmArchive() { if (!isOwner || isArchived || isArchiving) return; setIsArchiving(true); setArchiveError(null); try { if (isDemo) { await new Promise((resolve) => setTimeout(resolve, 800)); setIsArchiveModalOpen(false); onClubUpdated?.({ archivedAt: new Date().toISOString() }); } else { if (!callerUid) { throw new Error('Authentication required to archive club.'); } await archiveClub(club.id, callerUid); setIsArchiveModalOpen(false); onClubUpdated?.({ archivedAt: new Date().toISOString() }); router.refresh(); } } catch (err) { setArchiveError(sanitizePortalError(err, 'Failed to archive club. Please try again.')); } finally { setIsArchiving(false); } } return (
{/* 2. HEADER */}
PRECISION, NOT AGGRESSION // {club.homeRegion ? `${club.homeRegion.toUpperCase()} ZONE` : 'REGIONAL SQUADRON'}

SETTINGS

Configure Club access, governance, and administrative lifecycle.

{/* 3. TOP SUMMARY KPI CARDS (Row of 4) */}
{/* Card 1: Club Visibility */}
CLUB VISIBILITY public
{visibility}
{visibility === 'public' ? 'Listed in directory & search' : visibility === 'unlisted' ? 'Direct handle only' : 'Restricted private club'}
{/* Card 2: Admission Policy */}
ADMISSION POLICY assignment_ind
{admissionPolicy === 'open' ? 'OPEN' : admissionPolicy === 'request' ? 'REQUEST' : 'INVITE ONLY'}
{admissionPolicy === 'open' ? 'Open join without review' : admissionPolicy === 'request' ? 'Leadership review required' : 'Direct invitation only'}
{/* Card 3: Club Handle */}
CLUB HANDLE lock
@{club.slug}
Immutable reservation
{/* Card 4: Lifecycle State */}
LIFECYCLE STATE {isArchived ? 'archive' : 'verified'}
{isArchived ? 'ARCHIVED' : 'ACTIVE'}
{isArchived ? 'Discovery Removed • Retained' : 'Operational • Nominal'}
{/* Archived Status Warning Banner */} {isArchived && (
archive
Club is Archived
This Club has been archived. It is removed from public discovery queries and does not accept new join requests. Existing organizational records are retained.
)} {/* 4. MAIN WORKSPACE (12-Column Grid) */}
{/* Left Column (8 cols) */}
{/* SECTION 1: CLUB ACCESS & DISCOVERY */}
travel_explore

SECTION 1: CLUB ACCESS & DISCOVERY

Controls visibility across public index registries and member admission requirements.

{canManageAccess ? 'LEADERSHIP ACTION' : 'LEADERSHIP ONLY'}
{/* Save Feedback Alerts */} {accessSaveStatus === 'success' && (
check_circle Club access settings saved successfully.
)} {accessSaveStatus === 'error' && accessErrorMessage && (
error {accessErrorMessage}
)} {/* Subsection A: Visibility */}
CURRENT: {club.visibility.toUpperCase()}
{/* PUBLIC */} {/* UNLISTED */} {/* PRIVATE */}
info Unlisted and private clubs are hidden from public directory and unauthenticated discovery.
{/* Subsection B: Admission Policy */}
CURRENT: {club.admissionPolicy.toUpperCase()}
{/* OPEN */} {/* REQUEST TO JOIN */} {/* INVITE ONLY */}
lock Admission policy and visibility are independent dimensions.
{/* Action Bar */}
sync_saved_locally Persists visibility and admission policy changes.
{/* SECTION 2: CLUB IDENTIFIER & PUBLIC PROFILE */}
fingerprint

SECTION 2: CLUB IDENTIFIER & PUBLIC PROFILE

Permanent club handle and navigation to public profile.

lock Permanent Club Handle IMMUTABLE RESERVATION
@{club.slug}
Edit Club Profile arrow_forward
Club handles are permanently reserved and cannot be renamed or reassigned.
{/* SECTION 3: CLUB OWNERSHIP & GOVERNANCE */}
admin_panel_settings

SECTION 3: CLUB OWNERSHIP & GOVERNANCE

Root administrative authority holder and ownership assignment ledger.

{club.founderName ? club.founderName .split(/\s+/) .map((w) => w[0]) .join('') .slice(0, 2) .toUpperCase() : 'FO'}
{club.founderName || 'Current Owner'} {club.founderHandle && ( {club.founderHandle} )} OWNER

Primary administrative account holder since {club.foundedYear || '2024'}

{/* Warning Notice */}
warning Notice: The selected active member will become Club Owner. You will become a Club Admin. Ownership controls will transfer immediately after confirmation.
{/* SECTION 4: CLUB LIFECYCLE */}
archive

SECTION 4: CLUB LIFECYCLE

Administrative lifecycle state.

ARCHIVE CLUB

Archiving removes the Club from public discovery and prevents new open joins and join requests. Existing Club records are not deleted.

verified_user Invariant Guarantee: Club deletion is not supported. Archiving preserves existing Club records while removing the Club from public discovery queries.
{/* Right Column (4 cols) - GOVERNANCE RAIL */}
security GOVERNANCE & BOUNDARIES
{/* Card 1: CLUB VS. PERSONAL SCOPE */}
domain

CLUB VS. PERSONAL SCOPE

Club settings apply strictly to {club.name}. Club leadership cannot configure or override member telemetry preferences, GPS tracking, personal ride history, or individual account security.

LEAST-PRIVILEGE RBAC ENFORCED
{/* Card 2: LEADERSHIP GOVERNANCE */}
vpn_key

LEADERSHIP GOVERNANCE

Club Owners and Admins may update visibility and admission policy. Only the Club Owner can transfer ownership or archive the Club. Admins cannot seize ownership or archive the Club.

OWNER SEAT 1 OWNER
{/* Card 3: CLUB HANDLE */}
link_off

CLUB HANDLE

Club handles are permanently reserved and cannot be renamed or reassigned.

CANONICAL SLUG PERMANENT
{/* Card 4: PRIVACY CONTRACT */}
shield PRIVACY CONTRACT
“Public Club Profile is organizational identity only. Member telemetry, GPS tracks, personal ride logs, and private account data are not included.”

Contract enforced across all 12 Founder Portal administrative modules.

{/* OWNERSHIP TRANSFER MODAL */} {isTransferModalOpen && (
swap_horiz

Transfer Club Ownership

Irreversible Authority Transfer: Transferring ownership gives complete administrative control of {club.name} to the selected member. You will immediately step down to Club Admin.
{transferError && (
{transferError}
)}
{loadingMembers ? (
progress_activity Loading eligible active members...
) : eligibleMembers.length === 0 ? (
No eligible active members found in this Club to receive ownership.
) : ( )}
)} {/* ARCHIVE CONFIRMATION MODAL */} {isArchiveModalOpen && (
archive

Archive Club

Archiving removes the Club from discovery and prevents new membership requests. Existing Club records are retained.

This action cannot be undone through the portal. Archiving is not deletion: historical ride records, club achievements, and membership history remain safely preserved.

{archiveError && (
{archiveError}
)}
)}
); }