diff --git a/src/App.tsx b/src/App.tsx index 7cc4bd7..24516cf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,7 +17,8 @@ import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { AuthProvider } from "@/contexts/AuthContext"; -import { ForumProvider } from "@/contexts/ForumContext"; +import { ForumProvider, useForum } from "@/contexts/ForumContext"; +import { OfflineIndicator } from "@/components/ui/offline-indicator"; import CellPage from "./pages/CellPage"; import PostPage from "./pages/PostPage"; import NotFound from "./pages/NotFound"; @@ -26,6 +27,27 @@ import Dashboard from "./pages/Dashboard"; // Create a client const queryClient = new QueryClient(); +// Inner component that uses the Forum context +const AppContent = () => { + const { isNetworkConnected, isSyncing, outboxCount } = useForum(); + + return ( + <> + + } /> + } /> + } /> + } /> + + + + ); +}; + const App = () => ( @@ -34,12 +56,7 @@ const App = () => ( - - } /> - } /> - } /> - } /> - + diff --git a/src/components/PostDetail.tsx b/src/components/PostDetail.tsx index 82e8a93..6680097 100644 --- a/src/components/PostDetail.tsx +++ b/src/components/PostDetail.tsx @@ -9,6 +9,7 @@ import { formatDistanceToNow } from 'date-fns'; import { Comment } from '@/types'; import { CypherImage } from './ui/CypherImage'; import { Badge } from '@/components/ui/badge'; +import { PendingIndicator } from './ui/pending-indicator'; const PostDetail = () => { const { postId } = useParams<{ postId: string }>(); @@ -27,7 +28,8 @@ const PostDetail = () => { isRefreshing, refreshData, moderateComment, - moderateUser + moderateUser, + isMessagePending } = useForum(); const { currentUser, isAuthenticated, verificationStatus } = useAuth(); const [newComment, setNewComment] = useState(''); @@ -165,6 +167,7 @@ const PostDetail = () => { {post.authorAddress.slice(0, 6)}...{post.authorAddress.slice(-4)} + {isMessagePending(post.id) && } @@ -252,9 +255,12 @@ const PostDetail = () => { {comment.authorAddress.slice(0, 6)}...{comment.authorAddress.slice(-4)} - - {formatDistanceToNow(comment.timestamp, { addSuffix: true })} - +
+ + {formatDistanceToNow(comment.timestamp, { addSuffix: true })} + + {isMessagePending(comment.id) && } +

{comment.content}

{isCellAdmin && !comment.moderated && ( diff --git a/src/components/PostList.tsx b/src/components/PostList.tsx index 8a671cd..f518054 100644 --- a/src/components/PostList.tsx +++ b/src/components/PostList.tsx @@ -10,6 +10,7 @@ import { ArrowLeft, MessageSquare, MessageCircle, ArrowUp, ArrowDown, Clock, Ref import { formatDistanceToNow } from 'date-fns'; import { CypherImage } from './ui/CypherImage'; import { Badge } from '@/components/ui/badge'; +import { PendingIndicator } from './ui/pending-indicator'; const PostList = () => { const { cellId } = useParams<{ cellId: string }>(); @@ -26,7 +27,8 @@ const PostList = () => { isVoting, posts, moderatePost, - moderateUser + moderateUser, + isMessagePending } = useForum(); const { isAuthenticated, currentUser, verificationStatus } = useAuth(); const [newPostTitle, setNewPostTitle] = useState(''); @@ -259,6 +261,7 @@ const PostList = () => {
{formatDistanceToNow(post.timestamp, { addSuffix: true })} by {post.authorAddress.slice(0, 6)}...{post.authorAddress.slice(-4)} + {isMessagePending(post.id) && }
{isCellAdmin && !post.moderated && ( diff --git a/src/contexts/ForumContext.tsx b/src/contexts/ForumContext.tsx index 909ef7b..0caf25d 100644 --- a/src/contexts/ForumContext.tsx +++ b/src/contexts/ForumContext.tsx @@ -32,7 +32,11 @@ interface ForumContextType { isRefreshing: boolean; // Network status isNetworkConnected: boolean; + isSyncing: boolean; + outboxCount: number; + pendingMessageIds: string[]; error: string | null; + isMessagePending: (messageId: string) => boolean; getCellById: (id: string) => Cell | undefined; getPostsByCell: (cellId: string) => Post[]; getCommentsByPost: (postId: string) => Comment[]; @@ -75,11 +79,34 @@ export function ForumProvider({ children }: { children: React.ReactNode }) { const [isVoting, setIsVoting] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [isNetworkConnected, setIsNetworkConnected] = useState(false); + const [isSyncing, setIsSyncing] = useState(false); + const [outboxCount, setOutboxCount] = useState(0); + const [pendingMessageIds, setPendingMessageIds] = useState([]); const [error, setError] = useState(null); const { toast } = useToast(); const { currentUser, isAuthenticated, messageSigning } = useAuth(); + // Function to update outbox count and pending message IDs + const updateOutboxCount = async () => { + try { + const count = await messageManager.getOutboxCount(); + setOutboxCount(count); + + // Also update pending message IDs + const { db } = await import('@/lib/storage/db'); + const pendingIds = await db.getPendingMessageIds(); + setPendingMessageIds(pendingIds); + } catch (err) { + console.warn("Failed to get outbox count:", err); + } + }; + + // Helper function to check if a message is pending + const isMessagePending = (messageId: string) => { + return pendingMessageIds.includes(messageId); + }; + // Transform message cache data to the expected types const updateStateFromCache = () => { // Use the verifyMessage function from messageSigning if available @@ -129,13 +156,31 @@ export function ForumProvider({ children }: { children: React.ReactNode }) { // Monitor network connection status useEffect(() => { - const { unsubscribe } = monitorNetworkHealth(setIsNetworkConnected, toast); + const { unsubscribe } = monitorNetworkHealth( + (isConnected) => { + setIsNetworkConnected(isConnected); + if (isConnected) { + // When coming back online, sync will happen automatically + // but we should update the outbox count + updateOutboxCount(); + setIsSyncing(true); + // Reset syncing state after a short delay and update outbox again + setTimeout(async () => { + setIsSyncing(false); + await updateOutboxCount(); + }, 3000); + } + }, + toast + ); return unsubscribe; }, [toast]); useEffect(() => { const loadData = async () => { setIsInitialLoading(true); + // Load initial outbox count + await updateOutboxCount(); await initializeNetwork(toast, updateStateFromCache, setError); setIsInitialLoading(false); }; @@ -175,6 +220,8 @@ export function ForumProvider({ children }: { children: React.ReactNode }) { messageSigning ); setIsPostingPost(false); + // Update outbox count in case the message was queued offline + await updateOutboxCount(); return result; }; @@ -190,6 +237,8 @@ export function ForumProvider({ children }: { children: React.ReactNode }) { messageSigning ); setIsPostingComment(false); + // Update outbox count in case the message was queued offline + await updateOutboxCount(); return result; }; @@ -309,7 +358,11 @@ export function ForumProvider({ children }: { children: React.ReactNode }) { isVoting, isRefreshing, isNetworkConnected, + isSyncing, + outboxCount, + pendingMessageIds, error, + isMessagePending, getCellById, getPostsByCell, getCommentsByPost,