diff --git a/src/app/server/[ip]/page.tsx b/src/app/server/[ip]/page.tsx index 9e0991a..1ad9a43 100644 --- a/src/app/server/[ip]/page.tsx +++ b/src/app/server/[ip]/page.tsx @@ -1,34 +1,45 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import { useParams } from "next/navigation"; import MessageBox from "@/components/MessageBox"; import AppSidebar from "@/components/Sidebar"; -import { Message, Server as ServerType } from "@/types/types"; -import { UserProfile } from "@/hooks/get-user"; -import { StringMap } from "@/types/typeUtils"; +import { Server as ServerType } from "@/types/types"; import auth from "@/lib/auth"; -import { useMessages } from "@/hooks/use-messages"; +import { useServerMessages } from "@/hooks/use-messages"; import useApp from "@/hooks/use-app"; +import { useEffectOnceWhenReady } from "@/hooks/use-once"; export default function Server() { const { ip } = useParams<{ ip: string }>(); const serverRef = useRef(null); const [server, setServer] = useState(); const app = useApp(); + const { messages, addMessage } = useServerMessages(); - useEffect(() => { - if (!ip) return; - auth(ip, serverRef, setServer, app.addMessage); - }, [ip]); + useEffectOnceWhenReady( + () => { + if (!ip) return; + const msgs = messages[ip]; + auth( + ip, + serverRef, + setServer, + (m) => addMessage(ip, m), + () => {}, + msgs ? msgs[msgs.length - 1]?.id : undefined + ); + }, + [ip, messages], + [undefined, (v) => v[ip]] + ); return ( m.channel_id === "general")} + messages={messages[ip]?.filter((m) => m.channel_id === "general") || []} sendMessage={(message) => { serverRef.current?.send( JSON.stringify({ diff --git a/src/hooks/use-app.ts b/src/hooks/use-app.ts index 801203e..fa4af8d 100644 --- a/src/hooks/use-app.ts +++ b/src/hooks/use-app.ts @@ -7,8 +7,7 @@ import useUser, { UserProfile } from "./get-user"; export default function useApp(): App { const node = useRef(null); const [profiles, setProfiles] = useState>({}); - const messages = useMessages((s) => s.messages); - const addMessage = useMessages((s) => s.addMessage); + const { messages, addMessage } = useMessages(); const [sidebarOpen, setSidebarOpen] = useState(true); const profile = useUser(); diff --git a/src/hooks/use-messages.ts b/src/hooks/use-messages.ts index 6d5ab5e..77b7756 100644 --- a/src/hooks/use-messages.ts +++ b/src/hooks/use-messages.ts @@ -9,6 +9,13 @@ interface MessageStore { clearMessages: () => void; } +type ServerMessageStore = { + messages: Record; // server_id → messages + setMessages: (serverId: string, msgs: Message[]) => void; + addMessage: (serverId: string, msg: Message) => void; + clearMessages: (serverId?: string) => void; +}; + export const useMessages = create()( persist( (set) => ({ @@ -25,3 +32,44 @@ export const useMessages = create()( { name: "chat-messages" } ) ); + +export const useServerMessages = create()( + persist( + (set) => ({ + messages: {}, + + setMessages: (serverId, msgs) => + set((state) => ({ + messages: { ...state.messages, [serverId]: msgs }, + })), + + addMessage: (serverId, msg) => + set((state) => { + const serverMsgs = state.messages[serverId] || []; + + // avoid duplicates + if (serverMsgs.some((m) => m.id === msg.id)) return state; + + return { + messages: { + ...state.messages, + [serverId]: [...serverMsgs, msg], + }, + }; + }), + + clearMessages: (serverId) => + set((state) => + serverId + ? { + messages: { + ...state.messages, + [serverId]: [], + }, + } + : { messages: {} } + ), + }), + { name: "server-messages" } + ) +); diff --git a/src/hooks/use-once.ts b/src/hooks/use-once.ts new file mode 100644 index 0000000..4dcc853 --- /dev/null +++ b/src/hooks/use-once.ts @@ -0,0 +1,18 @@ +import { useEffect, useRef } from "react"; + +export function useEffectOnceWhenReady( + effect: () => void, + deps: any[], + testers: (undefined | ((m: any) => boolean))[] +) { + const hasRun = useRef(false); + + useEffect(() => { + const allReady = deps.every( + (d, i) => d !== undefined && d !== null && (!testers[i] || testers[i](d)) + ); + if (!allReady || hasRun.current) return; + hasRun.current = true; + effect(); + }, deps); +}