feat: add SocketProvider and integrate real-time credits updates in NavPlan component
This commit is contained in:
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
109
src/contexts/SocketContext.tsx
Normal file
109
src/contexts/SocketContext.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user