Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,578 changes: 1,298 additions & 280 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"react-markdown": "^10.1.0",
"react-redux": "^9.2.0",
"remark-gfm": "^4.0.1",
"socket.io-client": "^4.8.3",
"tailwind-merge": "^3.3.1",
"zod": "^4.1.11"
},
Expand Down
25 changes: 25 additions & 0 deletions src/components/examples/SocketStatusIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';

import React from 'react';
import { useSocketConnection } from '@/hooks/useSocket';
import { Wifi, WifiOff } from 'lucide-react';

export function SocketStatusIndicator() {
const { isConnected } = useSocketConnection();

return (
<div className="flex items-center space-x-2 px-3 py-1 rounded-full text-xs font-medium">
{isConnected ? (
<>
<Wifi className="w-4 h-4 text-green-500" />
<span className="text-green-500">Connected</span>
</>
) : (
<>
<WifiOff className="w-4 h-4 text-red-500" />
<span className="text-red-500">Disconnected</span>
</>
)}
</div>
);
}
60 changes: 38 additions & 22 deletions src/components/providers/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Toaster } from 'react-hot-toast';
import { store } from '@/store';
import { initializeAuth } from '@/store/slices/authSlice';
import { initializeUI } from '@/store/slices/uiSlice';
import { SocketProvider } from './SocketProvider';

interface ProvidersProps {
children: React.ReactNode;
Expand All @@ -21,31 +22,46 @@ export function Providers({ children }: ProvidersProps) {

return (
<Provider store={store}>
{children}
<Toaster
position="top-right"
toastOptions={{
duration: 4000,
style: {
background: '#363636',
color: '#fff',
<SocketProvider
config={{
url: process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3001',
options: {
transports: ['websocket', 'polling'],
autoConnect: true,
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5,
timeout: 20000,
},
success: {
duration: 3000,
iconTheme: {
primary: '#10B981',
secondary: '#fff',
}}
autoConnect={true}
>
{children}
<Toaster
position="top-right"
toastOptions={{
duration: 4000,
style: {
background: '#363636',
color: '#fff',
},
},
error: {
duration: 5000,
iconTheme: {
primary: '#EF4444',
secondary: '#fff',
success: {
duration: 3000,
iconTheme: {
primary: '#10B981',
secondary: '#fff',
},
},
},
}}
/>
error: {
duration: 5000,
iconTheme: {
primary: '#EF4444',
secondary: '#fff',
},
},
}}
/>
</SocketProvider>
</Provider>
);
}
113 changes: 113 additions & 0 deletions src/components/providers/SocketProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
'use client';

import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { Socket } from 'socket.io-client';
import { SocketManager, SocketConfig, initializeSocketManager } from '@/services/socketManager';

interface SocketContextType {
socket: Socket | null;
socketManager: SocketManager | null;
isConnected: boolean;
connect: () => void;
disconnect: () => void;
emit: (event: string, data?: any) => void;
on: (event: string, callback: (...args: any[]) => void) => void;
off: (event: string, callback?: (...args: any[]) => void) => void;
once: (event: string, callback: (...args: any[]) => void) => void;
}

const SocketContext = createContext<SocketContextType | undefined>(undefined);

interface SocketProviderProps {
children: ReactNode;
config: SocketConfig;
autoConnect?: boolean;
}

export function SocketProvider({ children, config, autoConnect = true }: SocketProviderProps) {
const [socketManager, setSocketManager] = useState<SocketManager | null>(null);
const [socket, setSocket] = useState<Socket | null>(null);
const [isConnected, setIsConnected] = useState(false);

useEffect(() => {
const manager = initializeSocketManager(config);
setSocketManager(manager);

if (autoConnect) {
const socketInstance = manager.connect();
setSocket(socketInstance);
setIsConnected(socketInstance.connected);

// Update connection status
socketInstance.on('connect', () => setIsConnected(true));
socketInstance.on('disconnect', () => setIsConnected(false));
}

return () => {
if (autoConnect) {
manager.disconnect();
}
};
}, [config, autoConnect]);

const connect = () => {
if (socketManager) {
const socketInstance = socketManager.connect();
setSocket(socketInstance);
setIsConnected(socketInstance.connected);

socketInstance.on('connect', () => setIsConnected(true));
socketInstance.on('disconnect', () => setIsConnected(false));
}
};

const disconnect = () => {
if (socketManager) {
socketManager.disconnect();
setSocket(null);
setIsConnected(false);
}
};

const emit = (event: string, data?: any) => {
socketManager?.emit(event, data);
};

const on = (event: string, callback: (...args: any[]) => void) => {
socketManager?.on(event, callback);
};

const off = (event: string, callback?: (...args: any[]) => void) => {
socketManager?.off(event, callback);
};

const once = (event: string, callback: (...args: any[]) => void) => {
socketManager?.once(event, callback);
};

const value: SocketContextType = {
socket,
socketManager,
isConnected,
connect,
disconnect,
emit,
on,
off,
once,
};

return (
<SocketContext.Provider value={value}>
{children}
</SocketContext.Provider>
);
}

export function useSocket(): SocketContextType {
const context = useContext(SocketContext);
if (context === undefined) {
throw new Error('useSocket must be used within a SocketProvider');
}
return context;
}
43 changes: 43 additions & 0 deletions src/hooks/useSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect } from 'react';
import { useSocket as useSocketContext } from '@/components/providers/SocketProvider';
import type { Socket } from 'socket.io-client';

export function useSocket() {
const context = useSocketContext();

return {
socket: context.socket,
socketManager: context.socketManager,
isConnected: context.isConnected,
connect: context.connect,
disconnect: context.disconnect,
emit: context.emit,
on: context.on,
off: context.off,
once: context.once,
};
}

export function useSocketEvent(event: string, callback: (...args: any[]) => void) {
const { socket, on, off } = useSocket();

useEffect(() => {
if (socket) {
on(event, callback);

return () => {
off(event, callback);
};
}
}, [socket, event, callback, on, off]);
}

export function useSocketConnection() {
const { isConnected, connect, disconnect } = useSocket();

return {
isConnected,
connect,
disconnect,
};
}
Loading
Loading