feat: add SocketProvider and integrate real-time credits updates in NavPlan component

This commit is contained in:
JSC
2025-07-04 20:19:06 +02:00
parent 81cdbb9321
commit 3f0fc13a12
5 changed files with 154 additions and 2 deletions

View File

@@ -2,6 +2,7 @@ import { AppLayout } from '@/components/AppLayout'
import { ProtectedRoute } from '@/components/ProtectedRoute'
import { Button } from '@/components/ui/button'
import { AuthProvider } from '@/components/AuthProvider'
import { SocketProvider } from '@/contexts/SocketContext'
import { AccountPage } from '@/pages/AccountPage'
import { ActivityPage } from '@/pages/ActivityPage'
import { AdminUsersPage } from '@/pages/AdminUsersPage'
@@ -17,7 +18,8 @@ function App() {
return (
<ThemeProvider defaultTheme="dark" storageKey="theme">
<AuthProvider>
<Router>
<SocketProvider>
<Router>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
@@ -106,7 +108,8 @@ function App() {
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
</Router>
</Router>
</SocketProvider>
</AuthProvider>
</ThemeProvider>
)

View File

@@ -1,5 +1,6 @@
import type { User } from "@/services/auth"
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar"
import { useSocket } from "@/contexts/SocketContext"
import NumberFlow from '@number-flow/react'
import { useEffect, useState } from "react"
@@ -9,11 +10,28 @@ interface NavPlanProps {
export function NavPlan({ user }: NavPlanProps) {
const [credits, setCredits] = useState(0)
const { socket, isConnected } = useSocket()
useEffect(() => {
setCredits(user.credits)
}, [user])
// Listen for real-time credits updates
useEffect(() => {
if (!socket || !isConnected) return
const handleCreditsChanged = (data: { credits: number }) => {
setCredits(data.credits)
}
socket.on("credits_changed", handleCreditsChanged)
// Cleanup listener on unmount
return () => {
socket.off("credits_changed", handleCreditsChanged)
}
}, [socket, isConnected])
return (
<SidebarMenu>
<SidebarMenuItem>

View File

@@ -0,0 +1,109 @@
import { createContext, useContext, useEffect, useState } from "react";
import { io, Socket } from "socket.io-client";
import { useAuth } from "@/hooks/use-auth";
interface SocketContextType {
socket: Socket | null;
isConnected: boolean;
connect: () => void;
disconnect: () => void;
}
const SocketContext = createContext<SocketContextType>({
socket: null,
isConnected: false,
connect: () => {},
disconnect: () => {},
});
export const useSocket = () => {
const context = useContext(SocketContext);
if (!context) {
throw new Error("useSocket must be used within a SocketProvider");
}
return context;
};
interface SocketProviderProps {
children: React.ReactNode;
}
export const SocketProvider: React.FC<SocketProviderProps> = ({ children }) => {
const [socket, setSocket] = useState<Socket | null>(null);
const [isConnected, setIsConnected] = useState(false);
const { user, loading } = useAuth();
useEffect(() => {
// Create socket connection
const newSocket = io("http://localhost:5000", {
withCredentials: true, // Include cookies for authentication
autoConnect: false, // Don't connect automatically
transports: ["polling"], // Use polling only to avoid WebSocket issues
upgrade: false, // Disable WebSocket upgrade
});
// Set up event listeners
newSocket.on("connect", () => {
// Send authentication after connection
newSocket.emit("authenticate", {});
});
newSocket.on("auth_success", () => {
setIsConnected(true);
});
newSocket.on("auth_error", () => {
setIsConnected(false);
newSocket.disconnect();
});
newSocket.on("disconnect", () => {
setIsConnected(false);
});
newSocket.on("connect_error", () => {
setIsConnected(false);
});
setSocket(newSocket);
// Clean up on unmount
return () => {
newSocket.close();
};
}, []);
// Connect/disconnect based on authentication state
useEffect(() => {
if (!socket || loading) return;
if (user && !isConnected) {
socket.connect();
} else if (!user && isConnected) {
socket.disconnect();
}
}, [socket, user, loading, isConnected]);
const connect = () => {
if (socket && !socket.connected) {
socket.connect();
}
};
const disconnect = () => {
if (socket && socket.connected) {
socket.disconnect();
}
};
const value = {
socket,
isConnected,
connect,
disconnect,
};
return (
<SocketContext.Provider value={value}>{children}</SocketContext.Provider>
);
};