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';
|
2025-04-24 16:30:50 +05:30
|
|
|
import { useToast } from '@/components/ui/use-toast';
|
|
|
|
|
import { useAuth } from '@/contexts/AuthContext';
|
2025-04-22 10:50:08 +05:30
|
|
|
import {
|
|
|
|
|
createPost,
|
|
|
|
|
createComment,
|
|
|
|
|
vote,
|
2025-06-06 16:42:00 +05:30
|
|
|
createCell,
|
|
|
|
|
moderatePost,
|
2025-06-06 16:45:14 +05:30
|
|
|
moderateComment,
|
|
|
|
|
moderateUser
|
2025-04-24 16:30:50 +05:30
|
|
|
} from './forum/actions';
|
|
|
|
|
import {
|
2025-04-22 10:50:08 +05:30
|
|
|
setupPeriodicQueries,
|
2025-04-24 16:30:50 +05:30
|
|
|
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>;
|
2025-06-28 07:13:32 +05:30
|
|
|
createCell: (name: string, description: string, icon?: string) => Promise<Cell | null>;
|
2025-04-24 16:30:50 +05:30
|
|
|
refreshData: () => Promise<void>;
|
2025-06-06 16:42:00 +05:30
|
|
|
moderatePost: (
|
|
|
|
|
cellId: string,
|
|
|
|
|
postId: string,
|
|
|
|
|
reason: string | undefined,
|
|
|
|
|
cellOwner: string
|
|
|
|
|
) => Promise<boolean>;
|
|
|
|
|
moderateComment: (
|
|
|
|
|
cellId: string,
|
|
|
|
|
commentId: string,
|
|
|
|
|
reason: string | undefined,
|
|
|
|
|
cellOwner: string
|
|
|
|
|
) => Promise<boolean>;
|
2025-06-06 16:45:14 +05:30
|
|
|
moderateUser: (
|
|
|
|
|
cellId: string,
|
|
|
|
|
userAddress: string,
|
|
|
|
|
reason: string | undefined,
|
|
|
|
|
cellOwner: string
|
|
|
|
|
) => Promise<boolean>;
|
2025-04-24 16:30:50 +05:30
|
|
|
}
|
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[]>([]);
|
2025-04-22 10:39:32 +05:30
|
|
|
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-24 16:30:50 +05:30
|
|
|
|
2025-04-15 16:28:03 +05:30
|
|
|
const { toast } = useToast();
|
2025-04-24 16:30:50 +05:30
|
|
|
const { currentUser, isAuthenticated, messageSigning } = useAuth();
|
|
|
|
|
|
|
|
|
|
// Transform message cache data to the expected types
|
2025-04-22 10:39:32 +05:30
|
|
|
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
|
2025-04-24 16:30:50 +05:30
|
|
|
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-24 16:30:50 +05:30
|
|
|
);
|
|
|
|
|
|
2025-04-27 15:54:24 +05:30
|
|
|
// Transform posts with verification
|
2025-04-24 16:30:50 +05:30
|
|
|
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-24 16:30:50 +05:30
|
|
|
);
|
|
|
|
|
|
2025-04-27 15:54:24 +05:30
|
|
|
// Transform comments with verification
|
2025-04-24 16:30:50 +05:30
|
|
|
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[]
|
2025-04-24 16:30:50 +05:30
|
|
|
);
|
2025-04-22 10:39:32 +05:30
|
|
|
};
|
2025-04-24 16:30:50 +05:30
|
|
|
|
2025-04-22 10:50:08 +05:30
|
|
|
const handleRefreshData = async () => {
|
|
|
|
|
setIsRefreshing(true);
|
2025-04-24 16:30:50 +05:30
|
|
|
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);
|
|
|
|
|
}
|
2025-04-22 10:39:32 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Monitor network connection status
|
|
|
|
|
useEffect(() => {
|
2025-04-22 10:50:08 +05:30
|
|
|
const { unsubscribe } = monitorNetworkHealth(setIsNetworkConnected, toast);
|
|
|
|
|
return unsubscribe;
|
2025-04-22 10:39:32 +05:30
|
|
|
}, [toast]);
|
|
|
|
|
|
2025-04-15 16:28:03 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
const loadData = async () => {
|
2025-04-22 10:50:08 +05:30
|
|
|
setIsInitialLoading(true);
|
|
|
|
|
await initializeNetwork(toast, updateStateFromCache, setError);
|
|
|
|
|
setIsInitialLoading(false);
|
2025-04-15 16:28:03 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loadData();
|
2025-04-22 10:39:32 +05:30
|
|
|
|
2025-04-22 10:50:08 +05:30
|
|
|
// Set up periodic queries
|
|
|
|
|
const { cleanup } = setupPeriodicQueries(isNetworkConnected, updateStateFromCache);
|
2025-04-22 10:39:32 +05:30
|
|
|
|
2025-04-22 10:50:08 +05:30
|
|
|
return cleanup;
|
2025-04-22 10:39:32 +05:30
|
|
|
}, [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);
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-22 10:50:08 +05:30
|
|
|
const handleCreatePost = async (cellId: string, title: string, content: string): Promise<Post | null> => {
|
|
|
|
|
setIsPostingPost(true);
|
2025-04-24 16:30:50 +05:30
|
|
|
const result = await createPost(
|
|
|
|
|
cellId,
|
|
|
|
|
title,
|
|
|
|
|
content,
|
|
|
|
|
currentUser,
|
|
|
|
|
isAuthenticated,
|
|
|
|
|
toast,
|
|
|
|
|
updateStateFromCache,
|
|
|
|
|
messageSigning
|
|
|
|
|
);
|
2025-04-22 10:50:08 +05:30
|
|
|
setIsPostingPost(false);
|
|
|
|
|
return result;
|
2025-04-15 16:28:03 +05:30
|
|
|
};
|
|
|
|
|
|
2025-04-22 10:50:08 +05:30
|
|
|
const handleCreateComment = async (postId: string, content: string): Promise<Comment | null> => {
|
|
|
|
|
setIsPostingComment(true);
|
2025-04-24 16:30:50 +05:30
|
|
|
const result = await createComment(
|
|
|
|
|
postId,
|
|
|
|
|
content,
|
|
|
|
|
currentUser,
|
|
|
|
|
isAuthenticated,
|
|
|
|
|
toast,
|
|
|
|
|
updateStateFromCache,
|
|
|
|
|
messageSigning
|
|
|
|
|
);
|
2025-04-22 10:50:08 +05:30
|
|
|
setIsPostingComment(false);
|
|
|
|
|
return result;
|
2025-04-15 16:28:03 +05:30
|
|
|
};
|
|
|
|
|
|
2025-04-22 10:50:08 +05:30
|
|
|
const handleVotePost = async (postId: string, isUpvote: boolean): Promise<boolean> => {
|
|
|
|
|
setIsVoting(true);
|
2025-04-24 16:30:50 +05:30
|
|
|
const result = await vote(
|
|
|
|
|
postId,
|
|
|
|
|
isUpvote,
|
|
|
|
|
currentUser,
|
|
|
|
|
isAuthenticated,
|
|
|
|
|
toast,
|
|
|
|
|
updateStateFromCache,
|
|
|
|
|
messageSigning
|
|
|
|
|
);
|
2025-04-22 10:50:08 +05:30
|
|
|
setIsVoting(false);
|
|
|
|
|
return result;
|
2025-04-15 16:28:03 +05:30
|
|
|
};
|
|
|
|
|
|
2025-04-22 10:50:08 +05:30
|
|
|
const handleVoteComment = async (commentId: string, isUpvote: boolean): Promise<boolean> => {
|
|
|
|
|
setIsVoting(true);
|
2025-04-24 16:30:50 +05:30
|
|
|
const result = await vote(
|
|
|
|
|
commentId,
|
|
|
|
|
isUpvote,
|
|
|
|
|
currentUser,
|
|
|
|
|
isAuthenticated,
|
|
|
|
|
toast,
|
|
|
|
|
updateStateFromCache,
|
|
|
|
|
messageSigning
|
|
|
|
|
);
|
2025-04-22 10:50:08 +05:30
|
|
|
setIsVoting(false);
|
|
|
|
|
return result;
|
2025-04-15 16:28:03 +05:30
|
|
|
};
|
|
|
|
|
|
2025-06-28 07:13:32 +05:30
|
|
|
const handleCreateCell = async (name: string, description: string, icon?: string): Promise<Cell | null> => {
|
2025-04-22 10:50:08 +05:30
|
|
|
setIsPostingCell(true);
|
2025-04-24 16:30:50 +05:30
|
|
|
const result = await createCell(
|
|
|
|
|
name,
|
|
|
|
|
description,
|
|
|
|
|
icon,
|
|
|
|
|
currentUser,
|
|
|
|
|
isAuthenticated,
|
|
|
|
|
toast,
|
|
|
|
|
updateStateFromCache,
|
|
|
|
|
messageSigning
|
|
|
|
|
);
|
2025-04-22 10:50:08 +05:30
|
|
|
setIsPostingCell(false);
|
|
|
|
|
return result;
|
2025-04-15 16:28:03 +05:30
|
|
|
};
|
|
|
|
|
|
2025-06-06 16:45:14 +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,
|
2025-04-22 10:39:32 +05:30
|
|
|
isInitialLoading,
|
|
|
|
|
isPostingCell,
|
|
|
|
|
isPostingPost,
|
|
|
|
|
isPostingComment,
|
|
|
|
|
isVoting,
|
|
|
|
|
isRefreshing,
|
|
|
|
|
isNetworkConnected,
|
2025-04-15 16:28:03 +05:30
|
|
|
error,
|
|
|
|
|
getCellById,
|
|
|
|
|
getPostsByCell,
|
|
|
|
|
getCommentsByPost,
|
2025-04-22 10:50:08 +05:30
|
|
|
createPost: handleCreatePost,
|
|
|
|
|
createComment: handleCreateComment,
|
|
|
|
|
votePost: handleVotePost,
|
|
|
|
|
voteComment: handleVoteComment,
|
|
|
|
|
createCell: handleCreateCell,
|
2025-06-06 16:45:14 +05:30
|
|
|
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) {
|
2025-04-24 16:30:50 +05:30
|
|
|
throw new Error("useForum must be used within a ForumProvider");
|
2025-04-15 16:28:03 +05:30
|
|
|
}
|
|
|
|
|
return context;
|
|
|
|
|
};
|