looood / src /components /chat-interface.tsx
looda3131's picture
Clean push without any binary history
cc276cc
"use client";
import { useState, useCallback, useEffect } from "react";
import { useSearchParams, useRouter } from 'next/navigation';
import type { ChatRecipient, User, Group, CallType } from "@/lib/types";
import { useAuth } from "@/contexts/auth-context";
import { useSettings } from "@/contexts/settings-context";
import { useCalls } from "@/contexts/calls-context";
import { useContacts } from "@/contexts/contacts-context";
import { UserList } from "@/components/user-list";
import { ChatWindow } from "@/app/chat-window";
import { CreateGroupModal } from "@/components/create-group-modal";
import { ViewProfileModal } from "@/components/view-profile-modal";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { ImagePreviewModal } from "@/components/image-preview-modal";
import { AddGroupMembersModal } from "@/components/add-group-members-modal";
import { CallModal } from "@/components/call-modal";
import { SettingsPage } from "@/components/settings-page";
import { MobileBottomNav } from "@/components/mobile-bottom-nav";
import { Input } from "@/components/ui/input";
import { ChangeNameModal } from "@/components/change-name-modal";
import { ChangeStatusModal } from "@/components/change-status-modal";
import { ChangeBioModal } from "@/components/change-bio-modal";
import { cn } from "@/lib/utils";
import { NetworkStatusIndicator } from "@/components/network-status-indicator";
import { Capacitor } from '@capacitor/core';
import { PushNotifications } from '@capacitor/push-notifications';
import { WelcomeScreen } from "./welcome-screen";
export function ChatInterface() {
const { currentUser, authStatus, signOutUser } = useAuth();
const { t } = useSettings();
const { callState, startVideoCall, answerCall, startAudioCall, endCall } = useCalls();
const { findUserByPublicId } = useContacts();
const searchParams = useSearchParams();
const router = useRouter();
const [recipient, setRecipient] = useState<ChatRecipient | null>(null);
const [isCreateGroupModalOpen, setCreateGroupModalOpen] = useState(false);
const [isSettingsOpen, setSettingsOpen] = useState(false);
const [settingsSection, setSettingsSection] = useState<string | undefined>(undefined);
const [viewingProfile, setViewingProfile] = useState<User | null>(null);
const [isSignOutModalOpen, setSignOutModalOpen] = useState(false);
const [signOutConfirmText, setSignOutConfirmText] = useState('');
const [imageToPreview, setImageToPreview] = useState<string | null>(null);
const [groupToAddMembers, setGroupToAddMembers] = useState<Group | null>(null);
// State for granular modals
const [isChangeNameModalOpen, setChangeNameModalOpen] = useState(false);
const [isChangeStatusModalOpen, setChangeStatusModalOpen] = useState(false);
const [isChangeBioModalOpen, setChangeBioModalOpen] = useState(false);
const handleStartCall = useCallback((peer: User, type: CallType) => {
if (type === 'video') {
startVideoCall(peer);
} else {
startAudioCall(peer);
}
}, [startAudioCall, startVideoCall]);
const handleUserSelection = useCallback((userOrGroup: User | Group) => {
if (!currentUser) return;
if ('members' in userOrGroup) { // It's a Group
if (userOrGroup.members[currentUser.uid]) {
setRecipient({ ...userOrGroup.info, uid: userOrGroup.id, isGroup: true, displayName: userOrGroup.info.name, photoURL: userOrGroup.info.photoURL, publicId: userOrGroup.id });
}
} else { // It's a User
if (userOrGroup.uid !== currentUser.uid) {
setRecipient({ ...userOrGroup, isGroup: false, uid: userOrGroup.uid, displayName: userOrGroup.displayName });
}
}
}, [currentUser]);
useEffect(() => {
const senderId = searchParams.get('senderId');
if (senderId && currentUser) {
findUserByPublicId(senderId).then(user => {
if (user) {
handleUserSelection(user);
}
});
}
}, [searchParams, currentUser, findUserByPublicId, handleUserSelection]);
useEffect(() => {
if (Capacitor.getPlatform() === 'web') return;
// Clear any existing listeners to avoid duplicates when the component re-renders
PushNotifications.removeAllListeners().then(() => {
PushNotifications.addListener('pushNotificationActionPerformed', (action) => {
const { actionId, notification } = action;
const data = notification.data || {};
console.log(`[Push Action] Received action: ${actionId}`, data);
if (data && data.type === 'incoming-call') {
// Important: Check if this action corresponds to the current incoming call
if (callState.status === 'incoming' && callState.channelName === data.channelName) {
if (actionId === 'answer') {
console.log('[Push Action] Answering call...');
answerCall();
} else if (actionId === 'decline') {
console.log('[Push Action] Declining call...');
endCall();
}
} else {
console.warn(`[Push Action] Received action for call ${data.channelName}, but current call state is ${callState.status} for channel ${callState.channelName}. Action ignored.`);
}
}
});
});
// Cleanup on component unmount
return () => {
PushNotifications.removeAllListeners();
}
}, [callState.status, callState.channelName, answerCall, endCall]); // Dependencies ensure the listener always has the latest state handlers
const openSettings = useCallback((section?: string) => {
setSettingsSection(section);
setSettingsOpen(true);
setRecipient(null); // Close any open chat window
}, []);
const openChangeNameModal = useCallback(() => setChangeNameModalOpen(true), []);
const openChangeStatusModal = useCallback(() => setChangeStatusModalOpen(true), []);
const openChangeBioModal = useCallback(() => setChangeBioModalOpen(true), []);
if (authStatus !== 'authenticated' || !currentUser) {
return null;
}
const handleSignOut = async () => {
await signOutUser();
setSignOutModalOpen(false);
setSignOutConfirmText('');
};
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;
const showMobileNav = isMobile && !recipient && !isSettingsOpen;
const isSignOutConfirmValid = signOutConfirmText.toLowerCase() === t('leaveWord') || signOutConfirmText.toLowerCase() === 'leave';
const numericUid = parseInt(currentUser.uid.replace(/[^0-9]/g, '').substring(0, 8), 10) || Math.floor(Math.random() * 100000);
return (
<>
<NetworkStatusIndicator />
<div className="relative h-full w-full flex overflow-hidden">
{/* Pane 1: User List (always visible on desktop, conditionally on mobile) */}
<div className={cn(
"h-full flex-col md:flex md:w-80 lg:w-96 md:flex-shrink-0 md:border-r",
(recipient || isSettingsOpen) ? "hidden" : "flex w-full"
)}>
<UserList
onSelectRecipient={(r) => { setRecipient(r); setSettingsOpen(false); }}
onOpenCreateGroup={() => setCreateGroupModalOpen(true)}
onOpenSettings={openSettings}
onSignOut={() => setSignOutModalOpen(true)}
onViewProfile={setViewingProfile}
/>
</div>
{/* Pane 2: Main Content (Chat, Settings, or Welcome) */}
<div className={cn(
"h-full flex-1 flex-col",
// On mobile, this pane is only visible if a chat or settings is open
(!recipient && !isSettingsOpen) ? "hidden md:flex" : "flex"
)}>
{isSettingsOpen ? (
<SettingsPage
onClose={() => setSettingsOpen(false)}
initialSection={settingsSection}
/>
) : recipient ? (
<ChatWindow
recipient={recipient}
onClose={() => setRecipient(null)}
onViewProfile={setViewingProfile}
onPreviewImage={setImageToPreview}
onAddMembers={(group) => setGroupToAddMembers(group)}
onStartCall={handleStartCall}
onOpenSettings={openSettings}
openChangeNameModal={openChangeNameModal}
openChangeStatusModal={openChangeStatusModal}
openChangeBioModal={openChangeBioModal}
/>
) : (
// This only shows on desktop when no chat/settings are open
<WelcomeScreen />
)}
</div>
{/* Mobile nav is only shown on the UserList screen */}
{showMobileNav && <MobileBottomNav onOpenSettings={openSettings} />}
</div>
<CreateGroupModal isOpen={isCreateGroupModalOpen} onClose={() => setCreateGroupModalOpen(false)} />
<ViewProfileModal
user={viewingProfile}
isOpen={!!viewingProfile}
onClose={() => setViewingProfile(null)}
onStartChat={handleUserSelection}
/>
{groupToAddMembers && (
<AddGroupMembersModal
isOpen={!!groupToAddMembers}
onClose={() => setGroupToAddMembers(null)}
group={groupToAddMembers}
/>
)}
<ImagePreviewModal imageUrl={imageToPreview} isOpen={!!imageToPreview} onClose={() => setImageToPreview(null)} />
<ChangeNameModal isOpen={isChangeNameModalOpen} onClose={() => setChangeNameModalOpen(false)} />
<ChangeStatusModal isOpen={isChangeStatusModalOpen} onClose={() => setChangeStatusModalOpen(false)} />
<ChangeBioModal isOpen={isChangeBioModalOpen} onClose={() => setChangeBioModalOpen(false)} />
<AlertDialog open={isSignOutModalOpen} onOpenChange={setSignOutModalOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('signOutConfirmationTitle')}</AlertDialogTitle>
<AlertDialogDescription>
{t('signOutConfirmationDescription', { word: t('leaveWord') })}
</AlertDialogDescription>
</AlertDialogHeader>
<Input
value={signOutConfirmText}
onChange={(e) => setSignOutConfirmText(e.target.value)}
placeholder={t('leaveWord')}
autoFocus
/>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setSignOutConfirmText('')}>{t('cancel')}</AlertDialogCancel>
<AlertDialogAction onClick={handleSignOut} disabled={!isSignOutConfirmValid} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
{t('signOut')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<CallModal
callState={callState}
onAnswerCall={answerCall}
onEndCall={endCall}
uid={numericUid}
userName={currentUser.displayName}
/>
</>
);
}