import React, { createContext, useState, useEffect, useCallback, useMemo } from 'react'; import { Cell, Post, Comment, OpchanMessage, User, EVerificationStatus } from '@/types/forum'; import { useToast } from '@/components/ui/use-toast'; import { useAuth } from '@/contexts/useAuth'; import { createPost, createComment, vote, createCell, moderatePost, moderateComment, moderateUser } from '@/lib/forum/actions'; import { setupPeriodicQueries, monitorNetworkHealth, initializeNetwork } from '@/lib/waku/network'; import messageManager from '@/lib/waku'; import { getDataFromCache } from '@/lib/forum/transformers'; import { RelevanceCalculator } from '@/lib/forum/relevance'; import { UserVerificationStatus } from '@/types/forum'; import { CryptoService, AuthService } from '@/lib/services'; import { getEnsName } from '@wagmi/core'; import { config } from '@/lib/identity/wallets/appkit'; interface ForumContextType { cells: Cell[]; posts: Post[]; comments: Comment[]; // User verification status for display userVerificationStatus: UserVerificationStatus; // 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; moderatePost: ( cellId: string, postId: string, reason: string | undefined, cellOwner: string ) => Promise; moderateComment: ( cellId: string, commentId: string, reason: string | undefined, cellOwner: string ) => Promise; moderateUser: ( cellId: string, userAddress: string, reason: string | undefined, cellOwner: string ) => Promise; } const ForumContext = createContext(undefined); export { ForumContext }; 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 [userVerificationStatus, setUserVerificationStatus] = useState({}); const { toast } = useToast(); const { currentUser, isAuthenticated } = useAuth(); const cryptoService = useMemo(() => new CryptoService(), []); const authService = useMemo(() => new AuthService(cryptoService), [cryptoService]); // Transform message cache data to the expected types const updateStateFromCache = useCallback(() => { // Use the verifyMessage function from cryptoService if available const verifyFn = isAuthenticated ? (message: OpchanMessage) => cryptoService.verifyMessage(message) : undefined; // Build user verification status for relevance calculation const relevanceCalculator = new RelevanceCalculator(); const allUsers: User[] = []; // Collect all unique users from posts, comments, and votes const userAddresses = new Set(); // Add users from posts Object.values(messageManager.messageCache.posts).forEach(post => { userAddresses.add(post.author); }); // Add users from comments Object.values(messageManager.messageCache.comments).forEach(comment => { userAddresses.add(comment.author); }); // Add users from votes Object.values(messageManager.messageCache.votes).forEach(vote => { userAddresses.add(vote.author); }); // Create user objects for verification status Array.from(userAddresses).forEach(address => { // Check if this address matches the current user's address if (currentUser && currentUser.address === address) { // Use the current user's actual verification status allUsers.push({ address, walletType: currentUser.walletType, verificationStatus: currentUser.verificationStatus, ensOwnership: currentUser.ensOwnership, ensName: currentUser.ensName, ensAvatar: currentUser.ensAvatar, ordinalOwnership: currentUser.ordinalOwnership, lastChecked: currentUser.lastChecked }); } else { // Create generic user object for other addresses allUsers.push({ address, walletType: address.startsWith('0x') ? 'ethereum' : 'bitcoin', verificationStatus: EVerificationStatus.UNVERIFIED }); } }); const initialStatus = relevanceCalculator.buildUserVerificationStatus(allUsers); // Transform data with relevance calculation (initial pass) const { cells, posts, comments } = getDataFromCache(verifyFn, initialStatus); setCells(cells); setPosts(posts); setComments(comments); setUserVerificationStatus(initialStatus); // Enrich: resolve ENS for ethereum addresses asynchronously and update (async () => { const targets = allUsers.filter(u => u.walletType === 'ethereum' && !u.ensOwnership); if (targets.length === 0) return; const lookups = await Promise.all(targets.map(async (u) => { try { const name = await getEnsName(config, { address: u.address as `0x${string}` }); return { address: u.address, ensName: name || undefined }; } catch { return { address: u.address, ensName: undefined }; } })); const ensByAddress = new Map(lookups.map(l => [l.address, l.ensName])); const enrichedUsers: User[] = allUsers.map(u => { const ensName = ensByAddress.get(u.address); if (ensName) { return { ...u, walletType: 'ethereum', ensOwnership: true, ensName, verificationStatus: 'verified-owner' } as User; } return u; }); const enrichedStatus = relevanceCalculator.buildUserVerificationStatus(enrichedUsers); const transformed = getDataFromCache(verifyFn, enrichedStatus); setCells(transformed.cells); setPosts(transformed.posts); setComments(transformed.comments); setUserVerificationStatus(enrichedStatus); })(); }, [cryptoService, isAuthenticated, currentUser]); 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; }, [isNetworkConnected, toast, updateStateFromCache]); 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, authService ); setIsPostingPost(false); return result; }; const handleCreateComment = async (postId: string, content: string): Promise => { setIsPostingComment(true); const result = await createComment( postId, content, currentUser, isAuthenticated, toast, updateStateFromCache, authService ); setIsPostingComment(false); return result; }; const handleVotePost = async (postId: string, isUpvote: boolean): Promise => { setIsVoting(true); const result = await vote( postId, isUpvote, currentUser, isAuthenticated, toast, updateStateFromCache, authService ); setIsVoting(false); return result; }; const handleVoteComment = async (commentId: string, isUpvote: boolean): Promise => { setIsVoting(true); const result = await vote( commentId, isUpvote, currentUser, isAuthenticated, toast, updateStateFromCache, authService ); 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, authService ); setIsPostingCell(false); return result; }; const handleModeratePost = async ( cellId: string, postId: string, reason: string | undefined, cellOwner: string ) => { return moderatePost( cellId, postId, reason, currentUser, isAuthenticated, cellOwner, toast, updateStateFromCache, authService ); }; const handleModerateComment = async ( cellId: string, commentId: string, reason: string | undefined, cellOwner: string ) => { return moderateComment( cellId, commentId, reason, currentUser, isAuthenticated, cellOwner, toast, updateStateFromCache, authService ); }; const handleModerateUser = async ( cellId: string, userAddress: string, reason: string | undefined, cellOwner: string ) => { return moderateUser( cellId, userAddress, reason, currentUser, isAuthenticated, cellOwner, toast, updateStateFromCache, authService ); }; return ( {children} ); }