OpChan/src/contexts/ForumContext.tsx

339 lines
9.1 KiB
TypeScript
Raw Normal View History

2025-04-15 16:28:03 +05:30
import React, { createContext, useContext, useState, useEffect } from 'react';
2025-04-27 15:54:24 +05:30
import { Cell, Post, Comment, OpchanMessage } from '@/types';
import { useToast } from '@/components/ui/use-toast';
import { useAuth } from '@/contexts/AuthContext';
import {
createPost,
createComment,
vote,
createCell,
moderatePost,
moderateComment,
moderateUser
} 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<Post | null>;
createComment: (postId: string, content: string) => Promise<Comment | null>;
votePost: (postId: string, isUpvote: boolean) => Promise<boolean>;
voteComment: (commentId: string, isUpvote: boolean) => Promise<boolean>;
createCell: (name: string, description: string, icon?: string) => Promise<Cell | null>;
refreshData: () => Promise<void>;
moderatePost: (
cellId: string,
postId: string,
reason: string | undefined,
cellOwner: string
) => Promise<boolean>;
moderateComment: (
cellId: string,
commentId: string,
reason: string | undefined,
cellOwner: string
) => Promise<boolean>;
moderateUser: (
cellId: string,
userAddress: string,
reason: string | undefined,
cellOwner: string
) => Promise<boolean>;
}
2025-04-15 16:28:03 +05:30
const ForumContext = createContext<ForumContextType | undefined>(undefined);
export function ForumProvider({ children }: { children: React.ReactNode }) {
const [cells, setCells] = useState<Cell[]>([]);
const [posts, setPosts] = useState<Post[]>([]);
const [comments, setComments] = useState<Comment[]>([]);
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);
2025-04-15 16:28:03 +05:30
const [error, setError] = useState<string | null>(null);
2025-04-15 16:28:03 +05:30
const { toast } = useToast();
const { currentUser, isAuthenticated, messageSigning } = useAuth();
// Transform message cache data to the expected types
const updateStateFromCache = () => {
2025-04-27 15:54:24 +05:30
// Use the verifyMessage function from messageSigning if available
const verifyFn = isAuthenticated && messageSigning ?
(message: OpchanMessage) => messageSigning.verifyMessage(message) :
undefined;
// Transform cells with verification
setCells(
2025-04-27 15:54:24 +05:30
Object.values(messageManager.messageCache.cells)
.map(cell => transformCell(cell, verifyFn))
.filter(cell => cell !== null) as Cell[]
);
2025-04-27 15:54:24 +05:30
// Transform posts with verification
setPosts(
2025-04-27 15:54:24 +05:30
Object.values(messageManager.messageCache.posts)
.map(post => transformPost(post, verifyFn))
.filter(post => post !== null) as Post[]
);
2025-04-27 15:54:24 +05:30
// Transform comments with verification
setComments(
2025-04-27 15:54:24 +05:30
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]);
2025-04-15 16:28:03 +05:30
useEffect(() => {
const loadData = async () => {
setIsInitialLoading(true);
await initializeNetwork(toast, updateStateFromCache, setError);
setIsInitialLoading(false);
2025-04-15 16:28:03 +05:30
};
loadData();
// Set up periodic queries
const { cleanup } = setupPeriodicQueries(isNetworkConnected, updateStateFromCache);
return cleanup;
}, [toast]);
2025-04-15 16:28:03 +05:30
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<Post | null> => {
setIsPostingPost(true);
const result = await createPost(
cellId,
title,
content,
currentUser,
isAuthenticated,
toast,
updateStateFromCache,
messageSigning
);
setIsPostingPost(false);
return result;
2025-04-15 16:28:03 +05:30
};
const handleCreateComment = async (postId: string, content: string): Promise<Comment | null> => {
setIsPostingComment(true);
const result = await createComment(
postId,
content,
currentUser,
isAuthenticated,
toast,
updateStateFromCache,
messageSigning
);
setIsPostingComment(false);
return result;
2025-04-15 16:28:03 +05:30
};
const handleVotePost = async (postId: string, isUpvote: boolean): Promise<boolean> => {
setIsVoting(true);
const result = await vote(
postId,
isUpvote,
currentUser,
isAuthenticated,
toast,
updateStateFromCache,
messageSigning
);
setIsVoting(false);
return result;
2025-04-15 16:28:03 +05:30
};
const handleVoteComment = async (commentId: string, isUpvote: boolean): Promise<boolean> => {
setIsVoting(true);
const result = await vote(
commentId,
isUpvote,
currentUser,
isAuthenticated,
toast,
updateStateFromCache,
messageSigning
);
setIsVoting(false);
return result;
2025-04-15 16:28:03 +05:30
};
const handleCreateCell = async (name: string, description: string, icon?: string): Promise<Cell | null> => {
setIsPostingCell(true);
const result = await createCell(
name,
description,
icon,
currentUser,
isAuthenticated,
toast,
updateStateFromCache,
messageSigning
);
setIsPostingCell(false);
return result;
2025-04-15 16:28:03 +05:30
};
const handleModeratePost = async (
cellId: string,
postId: string,
reason: string | undefined,
cellOwner: string
) => {
return moderatePost(
cellId,
postId,
reason,
currentUser,
isAuthenticated,
cellOwner,
toast,
updateStateFromCache,
messageSigning
);
};
const handleModerateComment = async (
cellId: string,
commentId: string,
reason: string | undefined,
cellOwner: string
) => {
return moderateComment(
cellId,
commentId,
reason,
currentUser,
isAuthenticated,
cellOwner,
toast,
updateStateFromCache,
messageSigning
);
};
const handleModerateUser = async (
cellId: string,
userAddress: string,
reason: string | undefined,
cellOwner: string
) => {
return moderateUser(
cellId,
userAddress,
reason,
currentUser,
isAuthenticated,
cellOwner,
toast,
updateStateFromCache,
messageSigning
);
};
2025-04-15 16:28:03 +05:30
return (
<ForumContext.Provider
value={{
cells,
posts,
comments,
isInitialLoading,
isPostingCell,
isPostingPost,
isPostingComment,
isVoting,
isRefreshing,
isNetworkConnected,
2025-04-15 16:28:03 +05:30
error,
getCellById,
getPostsByCell,
getCommentsByPost,
createPost: handleCreatePost,
createComment: handleCreateComment,
votePost: handleVotePost,
voteComment: handleVoteComment,
createCell: handleCreateCell,
refreshData: handleRefreshData,
moderatePost: handleModeratePost,
moderateComment: handleModerateComment,
moderateUser: handleModerateUser
2025-04-15 16:28:03 +05:30
}}
>
{children}
</ForumContext.Provider>
);
}
export const useForum = () => {
const context = useContext(ForumContext);
if (context === undefined) {
throw new Error("useForum must be used within a ForumProvider");
2025-04-15 16:28:03 +05:30
}
return context;
};