chore: refactor/split ForumContext for readability

This commit is contained in:
Danish Arora 2025-04-22 10:50:08 +05:30
parent 03d4ba38a0
commit 3414dfeae3
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
8 changed files with 578 additions and 494 deletions

View File

@ -0,0 +1,4 @@
## TODOs
- [ ] replace mock wallet connection/disconnection
- [ ] replace mock Ordinal verification (API)
- [ ]

View File

@ -46,8 +46,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const connectWallet = async () => {
setIsAuthenticating(true);
try {
// In a real app, this would connect to a Bitcoin wallet
// For now, we'll simulate a wallet connection with a mock address
//TODO: replace with actual wallet connection
const mockAddress = "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh";
// Create a new user object
@ -101,8 +100,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
setIsAuthenticating(true);
try {
// In a real app, this would verify Ordinal ownership
// For demo purposes, we'll simulate a successful verification
//TODO: replace with actual Ordinal verification
const updatedUser = {
...currentUser,
ordinalOwnership: true,

View File

@ -2,34 +2,18 @@ import React, { createContext, useContext, useState, useEffect } from 'react';
import { useToast } from '@/components/ui/use-toast';
import { Cell, Post, Comment } from '@/types';
import { useAuth } from './AuthContext';
import messageManager from '@/lib/waku';
import { CellMessage, CommentMessage, MessageType, PostMessage, VoteMessage } from '@/lib/waku/types';
import { v4 as uuidv4 } from 'uuid';
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>;
}
import {
getDataFromCache,
createPost,
createComment,
vote,
createCell,
refreshData as refreshNetworkData,
initializeNetwork,
setupPeriodicQueries,
monitorNetworkHealth,
ForumContextType
} from './forum';
const ForumContext = createContext<ForumContextType | undefined>(undefined);
@ -38,7 +22,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
const [posts, setPosts] = useState<Post[]>([]);
const [comments, setComments] = useState<Comment[]>([]);
// Replace single loading state with granular loading states
// Loading states
const [isInitialLoading, setIsInitialLoading] = useState(true);
const [isPostingCell, setIsPostingCell] = useState(false);
const [isPostingPost, setIsPostingPost] = useState(false);
@ -53,213 +37,40 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
const { currentUser, isAuthenticated } = useAuth();
const { toast } = useToast();
// Helper function to transform CellMessage to Cell
const transformCell = (cellMessage: CellMessage): Cell => {
return {
id: cellMessage.id,
name: cellMessage.name,
description: cellMessage.description,
icon: cellMessage.icon
};
};
// Helper function to transform PostMessage to Post with vote aggregation
const transformPost = (postMessage: PostMessage): Post => {
// Find all votes related to this post
const votes = Object.values(messageManager.messageCache.votes).filter(
vote => vote.targetId === postMessage.id
);
const upvotes = votes.filter(vote => vote.value === 1);
const downvotes = votes.filter(vote => vote.value === -1);
return {
id: postMessage.id,
cellId: postMessage.cellId,
authorAddress: postMessage.author,
title: postMessage.title,
content: postMessage.content,
timestamp: postMessage.timestamp,
upvotes: upvotes,
downvotes: downvotes
};
};
// Helper function to transform CommentMessage to Comment with vote aggregation
const transformComment = (commentMessage: CommentMessage): Comment => {
// Find all votes related to this comment
const votes = Object.values(messageManager.messageCache.votes).filter(
vote => vote.targetId === commentMessage.id
);
const upvotes = votes.filter(vote => vote.value === 1);
const downvotes = votes.filter(vote => vote.value === -1);
return {
id: commentMessage.id,
postId: commentMessage.postId,
authorAddress: commentMessage.author,
content: commentMessage.content,
timestamp: commentMessage.timestamp,
upvotes: upvotes,
downvotes: downvotes
};
};
// Function to update UI state from message cache
const updateStateFromCache = () => {
// Transform cells
const cellsArray = Object.values(messageManager.messageCache.cells).map(transformCell);
// Transform posts
const postsArray = Object.values(messageManager.messageCache.posts).map(transformPost);
// Transform comments
const commentsArray = Object.values(messageManager.messageCache.comments).map(transformComment);
setCells(cellsArray);
setPosts(postsArray);
setComments(commentsArray);
const data = getDataFromCache();
setCells(data.cells);
setPosts(data.posts);
setComments(data.comments);
};
// Function to refresh data from the network
const refreshData = async () => {
try {
setIsRefreshing(true);
toast({
title: "Refreshing data",
description: "Fetching latest messages from the network...",
});
// Try to connect if not already connected
if (!isNetworkConnected) {
try {
await messageManager.waitForRemotePeer(10000);
} catch (err) {
console.warn("Could not connect to peer during refresh:", err);
}
}
// Query historical messages from the store
await messageManager.queryStore();
// Update UI state from the cache
updateStateFromCache();
toast({
title: "Data refreshed",
description: "Your view has been updated with the latest messages.",
});
} catch (err) {
console.error("Error refreshing data:", err);
toast({
title: "Refresh failed",
description: "Could not fetch the latest messages. Please try again.",
variant: "destructive",
});
setError("Failed to refresh data. Please try again later.");
} finally {
setIsRefreshing(false);
}
const handleRefreshData = async () => {
setIsRefreshing(true);
await refreshNetworkData(isNetworkConnected, toast, updateStateFromCache, setError);
setIsRefreshing(false);
};
// Monitor network connection status
useEffect(() => {
// Initial status
setIsNetworkConnected(messageManager.isReady);
// Subscribe to health changes
const unsubscribe = messageManager.onHealthChange((isReady) => {
setIsNetworkConnected(isReady);
if (isReady) {
toast({
title: "Network connected",
description: "Connected to the Waku network",
});
} else {
toast({
title: "Network disconnected",
description: "Lost connection to the Waku network",
variant: "destructive",
});
}
});
return () => {
unsubscribe();
};
const { unsubscribe } = monitorNetworkHealth(setIsNetworkConnected, toast);
return unsubscribe;
}, [toast]);
useEffect(() => {
const loadData = async () => {
try {
setIsInitialLoading(true);
toast({
title: "Loading data",
description: "Connecting to the Waku network...",
});
// Wait for peer connection with timeout
try {
await messageManager.waitForRemotePeer(15000);
} catch (err) {
toast({
title: "Connection timeout",
description: "Could not connect to any peers. Some features may be unavailable.",
variant: "destructive",
});
console.warn("Timeout connecting to peer:", err);
}
// Query historical messages from the store
await messageManager.queryStore();
// Subscribe to new messages
await messageManager.subscribeToMessages();
// Update UI state from the cache
updateStateFromCache();
} catch (err) {
console.error("Error loading forum data:", err);
setError("Failed to load forum data. Please try again later.");
toast({
title: "Connection error",
description: "Failed to connect to Waku network. Please try refreshing.",
variant: "destructive",
});
} finally {
setIsInitialLoading(false);
}
setIsInitialLoading(true);
await initializeNetwork(toast, updateStateFromCache, setError);
setIsInitialLoading(false);
};
loadData();
// Set up a polling mechanism to refresh the UI every few seconds
// This is a temporary solution until we implement real-time updates with message callbacks
const uiRefreshInterval = setInterval(() => {
updateStateFromCache();
}, 5000);
// Set up regular network queries to fetch new messages
const networkQueryInterval = setInterval(async () => {
if (isNetworkConnected) {
try {
await messageManager.queryStore();
// No need to call updateStateFromCache() here as the UI refresh interval will handle that
} catch (err) {
console.warn("Error during scheduled network query:", err);
}
}
}, 3000);
// Set up periodic queries
const { cleanup } = setupPeriodicQueries(isNetworkConnected, updateStateFromCache);
return () => {
clearInterval(uiRefreshInterval);
clearInterval(networkQueryInterval);
// You might want to clean up subscriptions here
};
return cleanup;
}, [toast]);
const getCellById = (id: string): Cell | undefined => {
@ -276,280 +87,39 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
.sort((a, b) => a.timestamp - b.timestamp);
};
const createPost = async (cellId: string, title: string, content: string): Promise<Post | null> => {
if (!isAuthenticated || !currentUser) {
toast({
title: "Authentication Required",
description: "You need to verify Ordinal ownership to post.",
variant: "destructive",
});
return null;
}
try {
setIsPostingPost(true);
toast({
title: "Creating post",
description: "Sending your post to the network...",
});
const postId = uuidv4();
const postMessage: PostMessage = {
type: MessageType.POST,
id: postId,
cellId,
title,
content,
timestamp: Date.now(),
author: currentUser.address
};
// Send the message to the network
await messageManager.sendMessage(postMessage);
// Update UI (the cache is already updated in sendMessage)
updateStateFromCache();
toast({
title: "Post Created",
description: "Your post has been published successfully.",
});
// Return the transformed post
return transformPost(postMessage);
} catch (error) {
console.error("Error creating post:", error);
toast({
title: "Post Failed",
description: "Failed to create post. Please try again.",
variant: "destructive",
});
return null;
} finally {
setIsPostingPost(false);
}
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);
setIsPostingPost(false);
return result;
};
const createComment = async (postId: string, content: string): Promise<Comment | null> => {
if (!isAuthenticated || !currentUser) {
toast({
title: "Authentication Required",
description: "You need to verify Ordinal ownership to comment.",
variant: "destructive",
});
return null;
}
try {
setIsPostingComment(true);
toast({
title: "Posting comment",
description: "Sending your comment to the network...",
});
const commentId = uuidv4();
const commentMessage: CommentMessage = {
type: MessageType.COMMENT,
id: commentId,
postId,
content,
timestamp: Date.now(),
author: currentUser.address
};
// Send the message to the network
await messageManager.sendMessage(commentMessage);
// Update UI (the cache is already updated in sendMessage)
updateStateFromCache();
toast({
title: "Comment Added",
description: "Your comment has been published.",
});
// Return the transformed comment
return transformComment(commentMessage);
} catch (error) {
console.error("Error creating comment:", error);
toast({
title: "Comment Failed",
description: "Failed to add comment. Please try again.",
variant: "destructive",
});
return null;
} finally {
setIsPostingComment(false);
}
const handleCreateComment = async (postId: string, content: string): Promise<Comment | null> => {
setIsPostingComment(true);
const result = await createComment(postId, content, currentUser, isAuthenticated, toast, updateStateFromCache);
setIsPostingComment(false);
return result;
};
const votePost = async (postId: string, isUpvote: boolean): Promise<boolean> => {
if (!isAuthenticated || !currentUser) {
toast({
title: "Authentication Required",
description: "You need to verify Ordinal ownership to vote.",
variant: "destructive",
});
return false;
}
try {
setIsVoting(true);
const voteType = isUpvote ? "upvote" : "downvote";
toast({
title: `Sending ${voteType}`,
description: "Recording your vote on the network...",
});
const voteId = uuidv4();
const voteMessage: VoteMessage = {
type: MessageType.VOTE,
id: voteId,
targetId: postId,
value: isUpvote ? 1 : -1,
timestamp: Date.now(),
author: currentUser.address
};
// Send the vote message to the network
await messageManager.sendMessage(voteMessage);
// Update UI (the cache is already updated in sendMessage)
updateStateFromCache();
toast({
title: "Vote Recorded",
description: `Your ${voteType} has been registered.`,
});
return true;
} catch (error) {
console.error("Error voting on post:", error);
toast({
title: "Vote Failed",
description: "Failed to register your vote. Please try again.",
variant: "destructive",
});
return false;
} finally {
setIsVoting(false);
}
const handleVotePost = async (postId: string, isUpvote: boolean): Promise<boolean> => {
setIsVoting(true);
const result = await vote(postId, isUpvote, currentUser, isAuthenticated, toast, updateStateFromCache);
setIsVoting(false);
return result;
};
const voteComment = async (commentId: string, isUpvote: boolean): Promise<boolean> => {
if (!isAuthenticated || !currentUser) {
toast({
title: "Authentication Required",
description: "You need to verify Ordinal ownership to vote.",
variant: "destructive",
});
return false;
}
try {
setIsVoting(true);
const voteType = isUpvote ? "upvote" : "downvote";
toast({
title: `Sending ${voteType}`,
description: "Recording your vote on the network...",
});
const voteId = uuidv4();
const voteMessage: VoteMessage = {
type: MessageType.VOTE,
id: voteId,
targetId: commentId,
value: isUpvote ? 1 : -1,
timestamp: Date.now(),
author: currentUser.address
};
// Send the vote message to the network
await messageManager.sendMessage(voteMessage);
// Update UI (the cache is already updated in sendMessage)
updateStateFromCache();
toast({
title: "Vote Recorded",
description: `Your ${voteType} has been registered.`,
});
return true;
} catch (error) {
console.error("Error voting on comment:", error);
toast({
title: "Vote Failed",
description: "Failed to register your vote. Please try again.",
variant: "destructive",
});
return false;
} finally {
setIsVoting(false);
}
const handleVoteComment = async (commentId: string, isUpvote: boolean): Promise<boolean> => {
setIsVoting(true);
const result = await vote(commentId, isUpvote, currentUser, isAuthenticated, toast, updateStateFromCache);
setIsVoting(false);
return result;
};
const createCell = async (name: string, description: string, icon: string): Promise<Cell | null> => {
if (!isAuthenticated || !currentUser) {
toast({
title: "Authentication Required",
description: "You need to verify Ordinal ownership to create a cell.",
variant: "destructive",
});
return null;
}
try {
setIsPostingCell(true);
toast({
title: "Creating cell",
description: "Sending your cell to the network...",
});
const cellId = uuidv4();
const cellMessage: CellMessage = {
type: MessageType.CELL,
id: cellId,
name,
description,
icon,
timestamp: Date.now(),
author: currentUser.address
};
// Send the cell message to the network
await messageManager.sendMessage(cellMessage);
// Update UI (the cache is already updated in sendMessage)
updateStateFromCache();
toast({
title: "Cell Created",
description: "Your cell has been created successfully.",
});
return transformCell(cellMessage);
} catch (error) {
console.error("Error creating cell:", error);
toast({
title: "Cell Creation Failed",
description: "Failed to create cell. Please try again.",
variant: "destructive",
});
return null;
} finally {
setIsPostingCell(false);
}
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);
setIsPostingCell(false);
return result;
};
return (
@ -569,12 +139,12 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
getCellById,
getPostsByCell,
getCommentsByPost,
createPost,
createComment,
votePost,
voteComment,
createCell,
refreshData
createPost: handleCreatePost,
createComment: handleCreateComment,
votePost: handleVotePost,
voteComment: handleVoteComment,
createCell: handleCreateCell,
refreshData: handleRefreshData
}}
>
{children}

View File

@ -0,0 +1,250 @@
import { v4 as uuidv4 } from 'uuid';
import { CellMessage, CommentMessage, MessageType, PostMessage, VoteMessage } from '@/lib/waku/types';
import messageManager from '@/lib/waku';
import { Cell, Comment, Post, User } from '@/types';
import { transformCell, transformComment, transformPost } from './transformers';
type ToastFunction = (props: {
title: string;
description: string;
variant?: "default" | "destructive";
}) => void;
// Create a post
export const createPost = async (
cellId: string,
title: string,
content: string,
currentUser: User | null,
isAuthenticated: boolean,
toast: ToastFunction,
updateStateFromCache: () => void
): Promise<Post | null> => {
if (!isAuthenticated || !currentUser) {
toast({
title: "Authentication Required",
description: "You need to verify Ordinal ownership to post.",
variant: "destructive",
});
return null;
}
try {
toast({
title: "Creating post",
description: "Sending your post to the network...",
});
const postId = uuidv4();
const postMessage: PostMessage = {
type: MessageType.POST,
id: postId,
cellId,
title,
content,
timestamp: Date.now(),
author: currentUser.address
};
// Send the message to the network
await messageManager.sendMessage(postMessage);
// Update UI (the cache is already updated in sendMessage)
updateStateFromCache();
toast({
title: "Post Created",
description: "Your post has been published successfully.",
});
// Return the transformed post
return transformPost(postMessage);
} catch (error) {
console.error("Error creating post:", error);
toast({
title: "Post Failed",
description: "Failed to create post. Please try again.",
variant: "destructive",
});
return null;
}
};
// Create a comment
export const createComment = async (
postId: string,
content: string,
currentUser: User | null,
isAuthenticated: boolean,
toast: ToastFunction,
updateStateFromCache: () => void
): Promise<Comment | null> => {
if (!isAuthenticated || !currentUser) {
toast({
title: "Authentication Required",
description: "You need to verify Ordinal ownership to comment.",
variant: "destructive",
});
return null;
}
try {
toast({
title: "Posting comment",
description: "Sending your comment to the network...",
});
const commentId = uuidv4();
const commentMessage: CommentMessage = {
type: MessageType.COMMENT,
id: commentId,
postId,
content,
timestamp: Date.now(),
author: currentUser.address
};
// Send the message to the network
await messageManager.sendMessage(commentMessage);
// Update UI (the cache is already updated in sendMessage)
updateStateFromCache();
toast({
title: "Comment Added",
description: "Your comment has been published.",
});
// Return the transformed comment
return transformComment(commentMessage);
} catch (error) {
console.error("Error creating comment:", error);
toast({
title: "Comment Failed",
description: "Failed to add comment. Please try again.",
variant: "destructive",
});
return null;
}
};
// Vote on a post or comment
export const vote = async (
targetId: string,
isUpvote: boolean,
currentUser: User | null,
isAuthenticated: boolean,
toast: ToastFunction,
updateStateFromCache: () => void
): Promise<boolean> => {
if (!isAuthenticated || !currentUser) {
toast({
title: "Authentication Required",
description: "You need to verify Ordinal ownership to vote.",
variant: "destructive",
});
return false;
}
try {
const voteType = isUpvote ? "upvote" : "downvote";
toast({
title: `Sending ${voteType}`,
description: "Recording your vote on the network...",
});
const voteId = uuidv4();
const voteMessage: VoteMessage = {
type: MessageType.VOTE,
id: voteId,
targetId,
value: isUpvote ? 1 : -1,
timestamp: Date.now(),
author: currentUser.address
};
// Send the vote message to the network
await messageManager.sendMessage(voteMessage);
// Update UI (the cache is already updated in sendMessage)
updateStateFromCache();
toast({
title: "Vote Recorded",
description: `Your ${voteType} has been registered.`,
});
return true;
} catch (error) {
console.error("Error voting:", error);
toast({
title: "Vote Failed",
description: "Failed to register your vote. Please try again.",
variant: "destructive",
});
return false;
}
};
// Create a cell
export const createCell = async (
name: string,
description: string,
icon: string,
currentUser: User | null,
isAuthenticated: boolean,
toast: ToastFunction,
updateStateFromCache: () => void
): Promise<Cell | null> => {
if (!isAuthenticated || !currentUser) {
toast({
title: "Authentication Required",
description: "You need to verify Ordinal ownership to create a cell.",
variant: "destructive",
});
return null;
}
try {
toast({
title: "Creating cell",
description: "Sending your cell to the network...",
});
const cellId = uuidv4();
const cellMessage: CellMessage = {
type: MessageType.CELL,
id: cellId,
name,
description,
icon,
timestamp: Date.now(),
author: currentUser.address
};
// Send the cell message to the network
await messageManager.sendMessage(cellMessage);
// Update UI (the cache is already updated in sendMessage)
updateStateFromCache();
toast({
title: "Cell Created",
description: "Your cell has been created successfully.",
});
return transformCell(cellMessage);
} catch (error) {
console.error("Error creating cell:", error);
toast({
title: "Cell Creation Failed",
description: "Failed to create cell. Please try again.",
variant: "destructive",
});
return null;
}
};

View File

@ -0,0 +1,11 @@
// Export types
export * from './types';
// Export transformers
export * from './transformers';
// Export actions
export * from './actions';
// Export network functions
export * from './network';

View File

@ -0,0 +1,155 @@
import messageManager from '@/lib/waku';
type ToastFunction = (props: {
title: string;
description: string;
variant?: "default" | "destructive";
}) => void;
// Function to refresh data from the network
export const refreshData = async (
isNetworkConnected: boolean,
toast: ToastFunction,
updateStateFromCache: () => void,
setError: (error: string | null) => void
): Promise<void> => {
try {
toast({
title: "Refreshing data",
description: "Fetching latest messages from the network...",
});
// Try to connect if not already connected
if (!isNetworkConnected) {
try {
await messageManager.waitForRemotePeer(10000);
} catch (err) {
console.warn("Could not connect to peer during refresh:", err);
}
}
// Query historical messages from the store
await messageManager.queryStore();
// Update UI state from the cache
updateStateFromCache();
toast({
title: "Data refreshed",
description: "Your view has been updated with the latest messages.",
});
} catch (err) {
console.error("Error refreshing data:", err);
toast({
title: "Refresh failed",
description: "Could not fetch the latest messages. Please try again.",
variant: "destructive",
});
setError("Failed to refresh data. Please try again later.");
}
};
// Function to initialize data loading
export const initializeNetwork = async (
toast: ToastFunction,
updateStateFromCache: () => void,
setError: (error: string | null) => void
): Promise<void> => {
try {
toast({
title: "Loading data",
description: "Connecting to the Waku network...",
});
// Wait for peer connection with timeout
try {
await messageManager.waitForRemotePeer(15000);
} catch (err) {
toast({
title: "Connection timeout",
description: "Could not connect to any peers. Some features may be unavailable.",
variant: "destructive",
});
console.warn("Timeout connecting to peer:", err);
}
// Query historical messages from the store
await messageManager.queryStore();
// Subscribe to new messages
await messageManager.subscribeToMessages();
// Update UI state from the cache
updateStateFromCache();
} catch (err) {
console.error("Error loading forum data:", err);
setError("Failed to load forum data. Please try again later.");
toast({
title: "Connection error",
description: "Failed to connect to Waku network. Please try refreshing.",
variant: "destructive",
});
}
};
// Function to setup periodic network queries
export const setupPeriodicQueries = (
isNetworkConnected: boolean,
updateStateFromCache: () => void
): { cleanup: () => void } => {
// Set up a polling mechanism to refresh the UI every few seconds
// This is a temporary solution until we implement real-time updates with message callbacks
const uiRefreshInterval = setInterval(() => {
updateStateFromCache();
}, 5000);
// Set up regular network queries to fetch new messages
const networkQueryInterval = setInterval(async () => {
if (isNetworkConnected) {
try {
await messageManager.queryStore();
// No need to call updateStateFromCache() here as the UI refresh interval will handle that
} catch (err) {
console.warn("Error during scheduled network query:", err);
}
}
}, 3000);
// Return a cleanup function to clear the intervals
return {
cleanup: () => {
clearInterval(uiRefreshInterval);
clearInterval(networkQueryInterval);
}
};
};
// Function to monitor network health
export const monitorNetworkHealth = (
setIsNetworkConnected: (isConnected: boolean) => void,
toast: ToastFunction
): { unsubscribe: () => void } => {
// Initial status
setIsNetworkConnected(messageManager.isReady);
// Subscribe to health changes
const unsubscribe = messageManager.onHealthChange((isReady) => {
setIsNetworkConnected(isReady);
if (isReady) {
toast({
title: "Network connected",
description: "Connected to the Waku network",
});
} else {
toast({
title: "Network disconnected",
description: "Lost connection to the Waku network",
variant: "destructive",
});
}
});
return { unsubscribe };
};

View File

@ -0,0 +1,70 @@
import { Cell, Post, Comment } from '@/types';
import { CellMessage, CommentMessage, PostMessage } from '@/lib/waku/types';
import messageManager from '@/lib/waku';
// Helper function to transform CellMessage to Cell
export const transformCell = (cellMessage: CellMessage): Cell => {
return {
id: cellMessage.id,
name: cellMessage.name,
description: cellMessage.description,
icon: cellMessage.icon
};
};
// Helper function to transform PostMessage to Post with vote aggregation
export const transformPost = (postMessage: PostMessage): Post => {
// Find all votes related to this post
const votes = Object.values(messageManager.messageCache.votes).filter(
vote => vote.targetId === postMessage.id
);
const upvotes = votes.filter(vote => vote.value === 1);
const downvotes = votes.filter(vote => vote.value === -1);
return {
id: postMessage.id,
cellId: postMessage.cellId,
authorAddress: postMessage.author,
title: postMessage.title,
content: postMessage.content,
timestamp: postMessage.timestamp,
upvotes: upvotes,
downvotes: downvotes
};
};
// Helper function to transform CommentMessage to Comment with vote aggregation
export const transformComment = (commentMessage: CommentMessage): Comment => {
// Find all votes related to this comment
const votes = Object.values(messageManager.messageCache.votes).filter(
vote => vote.targetId === commentMessage.id
);
const upvotes = votes.filter(vote => vote.value === 1);
const downvotes = votes.filter(vote => vote.value === -1);
return {
id: commentMessage.id,
postId: commentMessage.postId,
authorAddress: commentMessage.author,
content: commentMessage.content,
timestamp: commentMessage.timestamp,
upvotes: upvotes,
downvotes: downvotes
};
};
// Function to update UI state from message cache
export const getDataFromCache = () => {
// Transform cells
const cells = Object.values(messageManager.messageCache.cells).map(transformCell);
// Transform posts
const posts = Object.values(messageManager.messageCache.posts).map(transformPost);
// Transform comments
const comments = Object.values(messageManager.messageCache.comments).map(transformComment);
return { cells, posts, comments };
};

View File

@ -0,0 +1,26 @@
import { Cell, Post, Comment } from '@/types';
export 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>;
}