'use client'; import React, { useState, useEffect, useRef } from 'react'; import { type ChatMessage, subscribeClubChat, sendClubChatMessage, FIXTURE_CHAT_MESSAGES, } from '../../lib/club-community'; import { type FounderClubContext, sanitizePortalError } from '../../lib/club-data'; interface FounderChatProps { context: FounderClubContext; } type ChannelId = 'general' | 'officers' | 'ride-intel'; interface ChannelInfo { id: ChannelId; name: string; icon: string; description: string; requiresOfficer: boolean; } const CHANNELS: ChannelInfo[] = [ { id: 'general', name: 'general-squad', icon: 'chat', description: 'General discussions, weekend meetups, and squad banter for all club members.', requiresOfficer: false, }, { id: 'officers', name: 'officers-lounge', icon: 'lock', description: 'Private coordination channel restricted to Club Founder and Admins.', requiresOfficer: true, }, { id: 'ride-intel', name: 'road-intel', icon: 'radar', description: 'Live pavement scouting, weather warnings, radar alerts, and road hazard reports.', requiresOfficer: false, }, ]; export function FounderChat({ context }: FounderChatProps) { const { club, permissions, currentUser, isDemoFixture } = context; const isDemo = Boolean(isDemoFixture); const [activeChannelId, setActiveChannelId] = useState('general'); const [messages, setMessages] = useState([]); const [inputText, setInputText] = useState(''); const [isSending, setIsSending] = useState(false); const [error, setError] = useState(null); const [connectionState, setConnectionState] = useState<'connecting' | 'connected' | 'error'>( isDemo ? 'connected' : 'connecting' ); const messagesEndRef = useRef(null); const isOfficer = permissions.canManageSettings || currentUser.role === 'owner' || currentUser.role === 'admin'; const activeChannel = CHANNELS.find((c) => c.id === activeChannelId) || CHANNELS[0]; const isRestrictedChannel = activeChannel.requiresOfficer && !isOfficer; const hasExampleMessages = messages.length > 0 && messages.every((m) => m.isExample); const isExampleThread = isDemo || hasExampleMessages; useEffect(() => { setError(null); setMessages([]); let unsubscribe: () => void = () => {}; if (isDemo) { setConnectionState('connected'); const channelMsgs = FIXTURE_CHAT_MESSAGES[activeChannelId] || []; setMessages(channelMsgs.map((m) => ({ ...m, clubId: club.id, channelId: activeChannelId, isExample: true }))); } else { setConnectionState('connecting'); unsubscribe = subscribeClubChat( club.id, activeChannelId, (newMsgs) => { setMessages(newMsgs); setConnectionState('connected'); }, (err) => { setConnectionState('error'); setError(sanitizePortalError(err, 'Failed to connect to chat channel.')); } ); } return () => { unsubscribe(); }; }, [club.id, activeChannelId, isDemo]); // Auto-scroll on new message useEffect(() => { if (typeof messagesEndRef.current?.scrollIntoView === 'function') { messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }); } }, [messages]); async function handleSendMessage(e?: React.FormEvent) { if (e) e.preventDefault(); const clean = inputText.trim(); if (!clean || isSending || isRestrictedChannel) return; setIsSending(true); setError(null); try { if (isDemo) { const optimisticMsg: ChatMessage = { id: `demo-msg-${Date.now()}`, clubId: club.id, channelId: activeChannelId, authorUid: currentUser.uid, authorDisplayName: 'Club Founder', authorHandle: '@founder', authorDisplayRank: currentUser.role === 'owner' ? 'President' : 'Admin', authorRole: currentUser.role, content: clean, createdAt: new Date().toISOString(), isExample: true, }; setMessages((prev) => [...prev, optimisticMsg]); } else { await sendClubChatMessage(club.id, activeChannelId, clean); } setInputText(''); } catch (err) { setError(sanitizePortalError(err, 'Failed to send message.')); } finally { setIsSending(false); } } function handleKeyDown(e: React.KeyboardEvent) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } } return (
{/* Header Card */}

Squad Live Chat

{isDemo ? ( visibility Example thread ) : connectionState === 'error' || error ? ( Disconnected ) : connectionState === 'connecting' ? ( Connecting… ) : hasExampleMessages ? ( visibility Example thread ) : ( Live Connected )}

Real-time squad channels for ride organization, road condition warnings, and administrative officer discussions.

{/* Channel quick tabs */}
{CHANNELS.map((ch) => { const isActive = ch.id === activeChannelId; const isLocked = ch.requiresOfficer && !isOfficer; return ( ); })}
{isExampleThread && (
info {isDemo ? ( <> Founder Demo Preview — this is a simulated conversation thread. Messages are not persisted or sent to club members. ) : ( <> These are example messages — this channel has no posts yet. Send a message to start the real conversation. )}
)} {error && (
error {error}
)} {/* Chat Room Area */}
{/* Channel Banner */}
{activeChannel.icon}
#{activeChannel.name}

{activeChannel.description}

{messages.length} messages
{/* Message Stream */}
{isRestrictedChannel ? (
lock

Officers Lounge Restricted

This channel is strictly reserved for club Founders and Admins. Switch to #general-squad or #road-intel to chat with the club.

) : messages.length === 0 ? (
chat_bubble_outline

No messages yet in #{activeChannel.name}. Start the conversation!

) : ( messages.map((msg) => { const isMe = msg.authorUid === currentUser.uid; const isOwnerRole = msg.authorRole === 'owner'; const isAdminRole = msg.authorRole === 'admin'; return (
{/* Avatar / Monogram */}
{msg.authorDisplayName.charAt(0).toUpperCase()}
{/* Message Bubble */}
{/* Author Meta */}
{msg.authorDisplayName} {msg.authorHandle && ( {msg.authorHandle} )} {msg.authorDisplayRank && ( {msg.authorDisplayRank} )} {msg.isExample && ( Example )} {msg.createdAt ? new Date(msg.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', }) : ''}
{/* Content */}

{msg.content}

); }) )}
{/* Message Input Box */}
setInputText(e.target.value)} onKeyDown={handleKeyDown} maxLength={1000} className="flex-1 bg-surface-container border border-brand-border/40 rounded-xl px-4 py-2.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-brand-cyan/60 disabled:opacity-50" />
); }