import React, { createContext, useContext, useState, useEffect } from 'react'; import { Cell, Post, Comment, OpchanMessage } from '@/types'; import { useToast } from '@/components/ui/use-toast'; import { useAuth } from '@/contexts/AuthContext'; import { createPost, createComment, vote, createCell } from './forum/actions'; import { setupPeriodicQueries, monitorNetworkHealth, initializeNetwork } from './forum/network'; import messageManager from '@/lib/waku'; import { transformCell, transformComment, transformPost } from './forum/transformers'; interface ForumContextType { cells: Cell[]; posts: Post[]; comments: Comment[]; // Granular loading states isInitialLoading: boolean; isPostingCell: boolean; isPostingPost: boolean; isPostingComment: boolean; isVoting: boolean; isRefreshing: boolean; // Network status isNetworkConnected: boolean; error: string | null; getCellById: (id: string) => Cell | undefined; getPostsByCell: (cellId: string) => Post[]; getCommentsByPost: (postId: string) => Comment[]; createPost: (cellId: string, title: string, content: string) => Promise; createComment: (postId: string, content: string) => Promise; votePost: (postId: string, isUpvote: boolean) => Promise; voteComment: (commentId: string, isUpvote: boolean) => Promise; createCell: (name: string, description: string, icon: string) => Promise; refreshData: () => Promise; } const ForumContext = createContext(undefined); export function ForumProvider({ children }: { children: React.ReactNode }) { const [cells, setCells] = useState([]); const [posts, setPosts] = useState([]); const [comments, setComments] = useState([]); const [isInitialLoading, setIsInitialLoading] = useState(true); const [isPostingCell, setIsPostingCell] = useState(false); const [isPostingPost, setIsPostingPost] = useState(false); const [isPostingComment, setIsPostingComment] = useState(false); const [isVoting, setIsVoting] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [isNetworkConnected, setIsNetworkConnected] = useState(false); const [error, setError] = useState(null); const { toast } = useToast(); const { currentUser, isAuthenticated, messageSigning } = useAuth(); // Transform message cache data to the expected types const updateStateFromCache = () => { // Use the verifyMessage function from messageSigning if available const verifyFn = isAuthenticated && messageSigning ? (message: OpchanMessage) => messageSigning.verifyMessage(message) : undefined; // Transform cells with verification setCells( Object.values(messageManager.messageCache.cells) .map(cell => transformCell(cell, verifyFn)) .filter(cell => cell !== null) as Cell[] ); // Transform posts with verification setPosts( Object.values(messageManager.messageCache.posts) .map(post => transformPost(post, verifyFn)) .filter(post => post !== null) as Post[] ); // Transform comments with verification setComments( Object.values(messageManager.messageCache.comments) .map(comment => transformComment(comment, verifyFn)) .filter(comment => comment !== null) as Comment[] ); }; const handleRefreshData = async () => { setIsRefreshing(true); try { // Manually query the network for updates await messageManager.queryStore(); updateStateFromCache(); } catch (error) { console.error("Error refreshing data:", error); toast({ title: "Refresh Failed", description: "Could not fetch the latest data. Please try again.", variant: "destructive", }); } finally { setIsRefreshing(false); } }; // Monitor network connection status useEffect(() => { const { unsubscribe } = monitorNetworkHealth(setIsNetworkConnected, toast); return unsubscribe; }, [toast]); useEffect(() => { const loadData = async () => { setIsInitialLoading(true); await initializeNetwork(toast, updateStateFromCache, setError); setIsInitialLoading(false); }; loadData(); // Set up periodic queries const { cleanup } = setupPeriodicQueries(isNetworkConnected, updateStateFromCache); return cleanup; }, [toast]); const getCellById = (id: string): Cell | undefined => { return cells.find(cell => cell.id === id); }; const getPostsByCell = (cellId: string): Post[] => { return posts.filter(post => post.cellId === cellId) .sort((a, b) => b.timestamp - a.timestamp); }; const getCommentsByPost = (postId: string): Comment[] => { return comments.filter(comment => comment.postId === postId) .sort((a, b) => a.timestamp - b.timestamp); }; const handleCreatePost = async (cellId: string, title: string, content: string): Promise => { setIsPostingPost(true); const result = await createPost( cellId, title, content, currentUser, isAuthenticated, toast, updateStateFromCache, messageSigning ); setIsPostingPost(false); return result; }; const handleCreateComment = async (postId: string, content: string): Promise => { setIsPostingComment(true); const result = await createComment( postId, content, currentUser, isAuthenticated, toast, updateStateFromCache, messageSigning ); setIsPostingComment(false); return result; }; const handleVotePost = async (postId: string, isUpvote: boolean): Promise => { setIsVoting(true); const result = await vote( postId, isUpvote, currentUser, isAuthenticated, toast, updateStateFromCache, messageSigning ); setIsVoting(false); return result; }; const handleVoteComment = async (commentId: string, isUpvote: boolean): Promise => { setIsVoting(true); const result = await vote( commentId, isUpvote, currentUser, isAuthenticated, toast, updateStateFromCache, messageSigning ); setIsVoting(false); return result; }; const handleCreateCell = async (name: string, description: string, icon: string): Promise => { setIsPostingCell(true); const result = await createCell( name, description, icon, currentUser, isAuthenticated, toast, updateStateFromCache, messageSigning ); setIsPostingCell(false); return result; }; return ( {children} ); } export const useForum = () => { const context = useContext(ForumContext); if (context === undefined) { throw new Error("useForum must be used within a ForumProvider"); } return context; };