| |
|
| |
|
| | "use client"; |
| |
|
| | import React, { createContext, useContext, useState, useCallback } from 'react'; |
| | import { ref, update, remove, set } from "firebase/database"; |
| | import { storageService } from '@/lib/storage-service'; |
| | import type { User, ReplyTo, PrivacySettings, DataMode, ChatRecipient } from '@/lib/types'; |
| | import { useAuth } from '@/contexts/auth-context'; |
| | import { useFirebase } from '@/contexts/firebase-context'; |
| | import { useSettings } from '@/contexts/settings-context'; |
| |
|
| |
|
| | export const useChatUtilsCore = () => { |
| | const { currentUser } = useAuth(); |
| | const { rtdb } = useFirebase(); |
| | const { addToast, t, dataMode, privacySettings } = useSettings(); |
| |
|
| | const clearChatHistory = useCallback(async (chatId: string) => { |
| | try { |
| | await storageService.clearMessages(chatId); |
| | addToast(t('historyCleared')); |
| | window.dispatchEvent(new CustomEvent('localDataChange', { detail: { chatId } })); |
| | } catch (error) { |
| | console.error("Error clearing local chat history:", error); |
| | addToast(t('historyClearError'), { variant: "destructive" }); |
| | } |
| | }, [addToast, t]); |
| | |
| | const deleteMessage = useCallback(async (chatId: string, messageId: string) => { |
| | if (!currentUser) return; |
| | try { |
| | const messageRef = ref(rtdb, `chats/${chatId}/messages/${messageId}`); |
| | const updatePayload = { |
| | text: null, |
| | encryptedText: null, |
| | isDeleted: true, |
| | audioKey: null, |
| | imageKey: null, |
| | videoKey: null, |
| | replyTo: null, |
| | transcription: null, |
| | urlPreviewData: null, |
| | compressedText: null, |
| | reactions: null, |
| | }; |
| | await update(messageRef, updatePayload); |
| | await storageService.updateMessage(chatId, messageId, { ...updatePayload, isDeleted: true }); |
| | window.dispatchEvent(new CustomEvent('localDataChange', { detail: { chatId } })); |
| | } catch (error) { |
| | console.error("Error deleting message:", error); |
| | addToast(t('deleteMessageError'), { variant: "destructive" }); |
| | } |
| | }, [currentUser, addToast, rtdb, t]); |
| |
|
| | const setUserTyping = useCallback((chatId: string, isTyping: boolean) => { |
| | if (!currentUser || dataMode === 'ultra' || !privacySettings.showTyping) return; |
| | const typingRef = ref(rtdb, `typing/${chatId}/${currentUser.uid}`); |
| | if (isTyping) { |
| | set(typingRef, currentUser.displayName); |
| | } else { |
| | remove(typingRef); |
| | } |
| | }, [currentUser, dataMode, rtdb, privacySettings.showTyping]); |
| |
|
| | const uploadFile = useCallback(async (file: File, onProgress: (progress: number) => void) => { |
| | const formData = new FormData(); |
| | formData.append('file', file); |
| |
|
| | return new Promise<{ fileKey: string }>((resolve, reject) => { |
| | const xhr = new XMLHttpRequest(); |
| | xhr.open('POST', `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/media`, true); |
| |
|
| | xhr.upload.onprogress = (event) => { |
| | if (event.lengthComputable) { |
| | const progress = (event.loaded / event.total) * 100; |
| | onProgress(progress); |
| | } |
| | }; |
| |
|
| | xhr.onload = () => { |
| | if (xhr.status >= 200 && xhr.status < 300) { |
| | try { |
| | const response = JSON.parse(xhr.responseText); |
| | resolve({ fileKey: response.fileKey }); |
| | } catch (e) { |
| | reject(new Error('Invalid JSON response from server.')); |
| | } |
| | } else { |
| | reject(new Error(`Upload failed: ${xhr.statusText}`)); |
| | } |
| | }; |
| | |
| | xhr.onerror = () => { |
| | reject(new Error('Upload failed due to a network error.')); |
| | }; |
| |
|
| | xhr.send(formData); |
| | }); |
| | }, []); |
| |
|
| | const transcribeAndSet = useCallback(async (messageId: string, audioFile: File, recipient: ChatRecipient) => { |
| | if (!currentUser) return; |
| | const chatId = recipient.isGroup |
| | ? recipient.uid |
| | : (currentUser.uid < recipient.uid |
| | ? `private_'''${currentUser.uid}'''_'''${recipient.uid}'''` |
| | : `private_'''${recipient.uid}'''_'''${currentUser.uid}'''`); |
| | |
| | if (!chatId) return; |
| |
|
| | try { |
| | |
| | } catch (error) { |
| | console.warn("Could not transcribe audio:", error); |
| | } |
| | }, [currentUser, rtdb]); |
| |
|
| | return { |
| | clearChatHistory, |
| | deleteMessage, |
| | setUserTyping, |
| | uploadFile, |
| | transcribeAndSet, |
| | }; |
| | }; |
| |
|
| | type ChatUtilsContextType = ReturnType<typeof useChatUtilsCore>; |
| |
|
| | const ChatUtilsContext = createContext<ChatUtilsContextType | null>(null); |
| |
|
| | export const useChatUtils = () => { |
| | const context = useContext(ChatUtilsContext); |
| | if (!context) { |
| | throw new Error('useChatUtils must be used within a ChatUtilsProvider'); |
| | } |
| | return context; |
| | }; |
| |
|
| | export const ChatUtilsProvider = ({ children }: { children: React.ReactNode }) => { |
| | const chatUtilsData = useChatUtilsCore(); |
| | |
| | return ( |
| | <ChatUtilsContext.Provider value={chatUtilsData}> |
| | {children} |
| | </ChatUtilsContext.Provider> |
| | ); |
| | }; |
| |
|