feat: integrate offline indicators in app components

This commit is contained in:
Ashis Kumar Naik 2025-07-02 08:36:31 +05:30
parent 52a28deb96
commit 5a1ddd43ad
4 changed files with 92 additions and 13 deletions

View File

@ -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 (
<>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/cell/:cellId" element={<CellPage />} />
<Route path="/post/:postId" element={<PostPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
<OfflineIndicator
isNetworkConnected={isNetworkConnected}
isSyncing={isSyncing}
outboxCount={outboxCount}
/>
</>
);
};
const App = () => (
<QueryClientProvider client={queryClient}>
<Router>
@ -34,12 +56,7 @@ const App = () => (
<TooltipProvider>
<Toaster />
<Sonner />
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/cell/:cellId" element={<CellPage />} />
<Route path="/post/:postId" element={<PostPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
<AppContent />
</TooltipProvider>
</ForumProvider>
</AuthProvider>

View File

@ -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 = () => {
<span className="truncate max-w-[150px]">
{post.authorAddress.slice(0, 6)}...{post.authorAddress.slice(-4)}
</span>
{isMessagePending(post.id) && <PendingIndicator />}
</div>
</div>
</div>
@ -252,9 +255,12 @@ const PostDetail = () => {
{comment.authorAddress.slice(0, 6)}...{comment.authorAddress.slice(-4)}
</span>
</div>
<span className="text-xs text-muted-foreground">
{formatDistanceToNow(comment.timestamp, { addSuffix: true })}
</span>
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">
{formatDistanceToNow(comment.timestamp, { addSuffix: true })}
</span>
{isMessagePending(comment.id) && <PendingIndicator />}
</div>
</div>
<p className="text-sm break-words">{comment.content}</p>
{isCellAdmin && !comment.moderated && (

View File

@ -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 = () => {
<div className="flex items-center gap-4 text-xs text-cyber-neutral">
<span>{formatDistanceToNow(post.timestamp, { addSuffix: true })}</span>
<span>by {post.authorAddress.slice(0, 6)}...{post.authorAddress.slice(-4)}</span>
{isMessagePending(post.id) && <PendingIndicator />}
</div>
</Link>
{isCellAdmin && !post.moderated && (

View File

@ -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<string[]>([]);
const [error, setError] = useState<string | null>(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,