mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
417 lines
12 KiB
TypeScript
417 lines
12 KiB
TypeScript
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<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>;
|
|
}
|
|
|
|
const ForumContext = createContext<ForumContextType | undefined>(undefined);
|
|
|
|
export { ForumContext };
|
|
|
|
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);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [userVerificationStatus, setUserVerificationStatus] = useState<UserVerificationStatus>({});
|
|
|
|
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<string>();
|
|
|
|
// 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<string, string | undefined>(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<Post | null> => {
|
|
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<Comment | null> => {
|
|
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<boolean> => {
|
|
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<boolean> => {
|
|
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<Cell | null> => {
|
|
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 (
|
|
<ForumContext.Provider
|
|
value={{
|
|
cells,
|
|
posts,
|
|
comments,
|
|
userVerificationStatus,
|
|
isInitialLoading,
|
|
isPostingCell,
|
|
isPostingPost,
|
|
isPostingComment,
|
|
isVoting,
|
|
isRefreshing,
|
|
isNetworkConnected,
|
|
error,
|
|
getCellById,
|
|
getPostsByCell,
|
|
getCommentsByPost,
|
|
createPost: handleCreatePost,
|
|
createComment: handleCreateComment,
|
|
votePost: handleVotePost,
|
|
voteComment: handleVoteComment,
|
|
createCell: handleCreateCell,
|
|
refreshData: handleRefreshData,
|
|
moderatePost: handleModeratePost,
|
|
moderateComment: handleModerateComment,
|
|
moderateUser: handleModerateUser
|
|
}}
|
|
>
|
|
{children}
|
|
</ForumContext.Provider>
|
|
);
|
|
}
|
|
|
|
|