diff --git a/app/layout.tsx b/app/layout.tsx index 2c151ba..d8123b9 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,8 +4,9 @@ import { Open_Sans } from 'next/font/google' import { ClerkProvider } from '@clerk/nextjs' import { cn } from '@/lib/utils' -import { ThemeProvider } from '@/components/providors/theme-providor' -import { ModalProvidor } from '@/components/providors/modal-providor' +import { ThemeProvider } from '@/components/providers/theme-provider' +import { ModalProvider } from '@/components/providers/modal-provider' +import { SocketProvider } from '@/components/providers/socket-provider' const font = Open_Sans({ subsets: ['latin'] }) @@ -32,8 +33,10 @@ export default function RootLayout({ enableSystem={false} storageKey="discord-theme" > - + + {children} + diff --git a/components/chat/chat-header.tsx b/components/chat/chat-header.tsx index cb1408b..2e59ee7 100644 --- a/components/chat/chat-header.tsx +++ b/components/chat/chat-header.tsx @@ -1,6 +1,7 @@ import { Hash, Menu } from "lucide-react" import { MobileToggle } from "@/components/mobile-toggle"; -import { UserAvatar } from "../user-avatar"; +import { UserAvatar } from "@/components/user-avatar"; +import { SocketIndicator } from "@/components/socket-indicator"; interface ChatHeaderProps { serverId: string; @@ -31,6 +32,9 @@ export const ChatHeader = ({

{name}

+
+ +
) } \ No newline at end of file diff --git a/components/providors/modal-providor.tsx b/components/providers/modal-provider.tsx similarity index 97% rename from components/providors/modal-providor.tsx rename to components/providers/modal-provider.tsx index c06b285..505e22d 100644 --- a/components/providors/modal-providor.tsx +++ b/components/providers/modal-provider.tsx @@ -12,7 +12,7 @@ import { DeleteServerModal } from "@/components/modals/delete-server-modal"; import { DeleteChannelModal } from "@/components/modals/delete-channel-modal"; import { EditChannelModal } from "@/components/modals/edit-channel-modal"; -export const ModalProvidor = () => { +export const ModalProvider = () => { const [isMounted, setIsMounted] = useState(false); useEffect (() => { diff --git a/components/providers/socket-provider.tsx b/components/providers/socket-provider.tsx new file mode 100644 index 0000000..3f31ffb --- /dev/null +++ b/components/providers/socket-provider.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { + createContext, + useContext, + useEffect, + useState +} from "react"; +import { io as ClientIO } from "socket.io-client"; + +type SocketContextType = { + socket: any | null; + isConnected: boolean; +}; + +const SocketContext = createContext({ + socket: null, + isConnected: false, +}); + +export const useSocket = () => { + return useContext(SocketContext); +}; + +export const SocketProvider = ({ + children +}: { + children: React.ReactNode +}) => { + const [socket, setSocket] = useState(null); + const [isConnected, setIsConnected] = useState(false); + + useEffect(() => { + const socketInstance = new (ClientIO as any)(process.env.NEXT_PUBLIC_SITE_URL!, { + path: "/api/socket/io", + addTrailingSlash: false, + }); + + socketInstance.on("connect", () => { + setIsConnected(true); + }); + + socketInstance.on("disconnect", () => { + setIsConnected(false); + }); + + setSocket(socketInstance); + + return () => { + socketInstance.disconnect(); + } + }, []); + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/components/providors/theme-providor.tsx b/components/providers/theme-provider.tsx similarity index 100% rename from components/providors/theme-providor.tsx rename to components/providers/theme-provider.tsx diff --git a/components/socket-indicator.tsx b/components/socket-indicator.tsx new file mode 100644 index 0000000..a535358 --- /dev/null +++ b/components/socket-indicator.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useSocket } from "@/components/providers/socket-provider"; +import { Badge } from "@/components/ui/badge"; + +export const SocketIndicator = () => { + const { isConnected } = useSocket(); + + if (!isConnected) { + return ( + + Fallback: Polling every 1s + + ) + } + + return ( + + Live: Real-time Updates + + ) +} \ No newline at end of file diff --git a/pages/api/socket/io.ts b/pages/api/socket/io.ts new file mode 100644 index 0000000..621e100 --- /dev/null +++ b/pages/api/socket/io.ts @@ -0,0 +1,26 @@ +import { Server as NetServer } from 'http'; +import { NextApiRequest } from 'next'; +import { Server as ServerIO } from 'socket.io'; + +import { NextApiResponseServerIo } from '@/types'; + +export const config = { + api: { + bodyParser: false, + } +} + +const ioHandler = (req: NextApiRequest, res: NextApiResponseServerIo) => { + if (!res.socket.server.io) { + const path = "/api/socket/io" + const httpServer: NetServer = res.socket.server as any; + const io = new ServerIO(httpServer, { + path: path, + addTrailingSlash: false, + }); + res.socket.server.io = io; + } + res.end(); +} + +export default ioHandler; \ No newline at end of file diff --git a/types.ts b/types.ts index ff3ad09..c1037f9 100644 --- a/types.ts +++ b/types.ts @@ -1,5 +1,16 @@ +import { Server as NetServer, Socket } from 'net'; +import { NextApiResponse } from 'next'; +import { Server as SocketIOServer } from 'socket.io'; import { Server, Member, Profile } from '@prisma/client'; export type ServerWithMembersWithProfiles = Server & { members: (Member & { profile: Profile })[]; +} + +export type NextApiResponseServerIo = NextApiResponse & { + socket: Socket & { + server: NetServer & { + io: SocketIOServer; + } + } } \ No newline at end of file