Added state structure #19

This commit is contained in:
2025-10-15 21:01:04 +02:00
parent 68ec582d3b
commit cc4812ef3c
7 changed files with 98 additions and 135 deletions
+11 -30
View File
@@ -1,49 +1,30 @@
"use client"; "use client";
import { useRef, useState } from "react";
import MessageBox from "@/components/MessageBox"; import MessageBox from "@/components/MessageBox";
import AppSidebar from "@/components/Sidebar"; import AppSidebar from "@/components/Sidebar";
import { Message } from "@/types/types";
import { UserProfile } from "@/hooks/get-user";
import { StringMap } from "@/types/typeUtils";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useMessages } from "@/hooks/use-messages"; import useApp from "@/hooks/use-app";
export default function DMs() { export default function DMs() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const wsRef = useRef<WebSocket | null>(null); const app = useApp();
const messages = useMessages((s) => s.messages);
const addMessage = useMessages((s) => s.addMessage);
const [userList, setUserList] = useState<StringMap<UserProfile>>({});
const [user, setUser] = useState<UserProfile | undefined>();
const [sidebarOpen, setSidebarOpen] = useState(false);
return ( return (
<AppSidebar <AppSidebar app={app}>
open={sidebarOpen}
setOpen={setSidebarOpen}
addMessage={addMessage}
wsRef={wsRef}
userList={userList}
setUserList={setUserList}
chatWith={id}
setUser={setUser}
messages={messages}
>
<MessageBox <MessageBox
toggleSidebar={() => setSidebarOpen(true)} toggleSidebar={() => app.setSidebarOpen(true)}
channelName={userList[id]?.display_name || id} channelName={app.profiles[id]?.display_name || id}
userList={userList} userList={app.profiles}
setUserList={setUserList} setUserList={app.setProfiles}
messages={messages messages={app.messages
.filter( .filter(
(m) => (m) =>
(m.channel_id === id && m.from === user?.id) || (m.channel_id === id && m.from === app.profile?.id) ||
(m.from === id && m.channel_id === user?.id) (m.from === id && m.channel_id === app.profile?.id)
) )
.toReversed()} .toReversed()}
sendMessage={(message) => { sendMessage={(message) => {
wsRef.current?.send( app.node.current?.send(
JSON.stringify({ JSON.stringify({
type: "send_message", type: "send_message",
params: { params: {
+4 -43
View File
@@ -1,59 +1,20 @@
"use client"; "use client";
import { useRef, useState } from "react";
import AppSidebar from "@/components/Sidebar"; import AppSidebar from "@/components/Sidebar";
import { UserProfile } from "@/hooks/get-user";
import { StringMap } from "@/types/typeUtils";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import ProfilePicture from "@/components/ProfilePicture"; import useApp from "@/hooks/use-app";
import { Message } from "@/types/types";
import { useMessages } from "@/hooks/use-messages";
export default function Chat() { export default function Chat() {
const wsRef = useRef<WebSocket | null>(null); const app = useApp();
const [userList, setUserList] = useState<StringMap<UserProfile>>({});
const messages = useMessages((s) => s.messages);
const addMessage = useMessages((s) => s.addMessage);
const [sidebarOpen, setSidebarOpen] = useState(true);
return ( return (
<AppSidebar <AppSidebar app={app}>
open={sidebarOpen}
setOpen={setSidebarOpen}
wsRef={wsRef}
userList={userList}
setUserList={setUserList}
addMessage={addMessage}
messages={messages}
>
<Card className="bg-background w-full"> <Card className="bg-background w-full">
<CardHeader> <CardHeader>
<CardTitle>Direct Messages</CardTitle> <CardTitle>Direct Messages</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent></CardContent>
{/* <DMItem name="Alice" status="online" avatar="" /> */}
</CardContent>
</Card> </Card>
</AppSidebar> </AppSidebar>
); );
} }
function DMItem({
name,
avatar,
status,
}: {
name: string;
avatar: string;
status: string;
}) {
return (
<Card className="p-2 flex flex-row items-center gap-2 cursor-pointer hover:bg-accent">
<ProfilePicture name={name} url={avatar} />
<div className="flex flex-col">
<span className="text-sm font-medium">{name}</span>
<span className="text-xs text-muted-foreground">{status}</span>
</div>
</Card>
);
}
+10 -21
View File
@@ -9,42 +9,31 @@ import { UserProfile } from "@/hooks/get-user";
import { StringMap } from "@/types/typeUtils"; import { StringMap } from "@/types/typeUtils";
import auth from "@/lib/auth"; import auth from "@/lib/auth";
import { useMessages } from "@/hooks/use-messages"; import { useMessages } from "@/hooks/use-messages";
import useApp from "@/hooks/use-app";
export default function Server() { export default function Server() {
const { ip } = useParams<{ ip: string }>(); const { ip } = useParams<{ ip: string }>();
const wsServerRef = useRef<WebSocket | null>(null); const serverRef = useRef<WebSocket | null>(null);
const wsNodeRef = useRef<WebSocket | null>(null);
const [server, setServer] = useState<undefined | ServerType>(); const [server, setServer] = useState<undefined | ServerType>();
const [userList, setUserList] = useState<StringMap<UserProfile>>({}); const app = useApp();
const messages = useMessages((s) => s.messages);
const addMessage = useMessages((s) => s.addMessage);
const [sidebarOpen, setSidebarOpen] = useState(false);
useEffect(() => { useEffect(() => {
if (!ip) return; if (!ip) return;
auth(ip, wsServerRef, setServer, addMessage); auth(ip, serverRef, setServer, app.addMessage);
}, [ip]); }, [ip]);
return ( return (
<AppSidebar <AppSidebar app={app} server={server}>
open={sidebarOpen}
setOpen={setSidebarOpen}
wsRef={wsNodeRef}
addMessage={() => {}}
userList={userList}
setUserList={setUserList}
server={server}
>
<MessageBox <MessageBox
toggleSidebar={() => setSidebarOpen(true)} toggleSidebar={() => app.setSidebarOpen(true)}
channelName="General" channelName="General"
userList={userList} userList={app.profiles}
setUserList={setUserList} setUserList={app.setProfiles}
messages={messages messages={app.messages
.filter((m) => m.channel_id === "general") .filter((m) => m.channel_id === "general")
.toReversed()} .toReversed()}
sendMessage={(message) => { sendMessage={(message) => {
wsServerRef.current?.send( serverRef.current?.send(
JSON.stringify({ JSON.stringify({
type: "send_message", type: "send_message",
params: { params: {
+2 -2
View File
@@ -156,7 +156,7 @@ export default function MessageBox({
const [text, setText] = useState(""); const [text, setText] = useState("");
return ( return (
<div className="h-full w-full flex flex-col pb-5 pl-5 gap-5"> <div className="h-full w-full max-h-svh flex flex-col pb-5 pl-5 gap-5">
{channelName && ( {channelName && (
<header className="h-12 py-4 flex items-center border-b text-sm font-semibold"> <header className="h-12 py-4 flex items-center border-b text-sm font-semibold">
<span onClick={toggleSidebar} className="cursor-pointer flex"> <span onClick={toggleSidebar} className="cursor-pointer flex">
@@ -165,7 +165,7 @@ export default function MessageBox({
</span> </span>
</header> </header>
)} )}
<div className="w-full h-max flex-1 overflow-y-scroll flex gap-2 flex-col-reverse pr-5"> <div className="w-full h-full flex-1 overflow-y-scroll flex gap-2 flex-col-reverse pr-5">
{messages.map((msg) => ( {messages.map((msg) => (
<MessageContainer <MessageContainer
key={msg.id} key={msg.id}
+26 -39
View File
@@ -26,53 +26,39 @@ import { Button } from "./ui/button";
import { Card } from "./ui/card"; import { Card } from "./ui/card";
import { ScrollArea } from "./ui/scroll-area"; import { ScrollArea } from "./ui/scroll-area";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import useUser, { UserProfile } from "@/hooks/get-user"; import { UserProfile } from "@/hooks/get-user";
import ProfilePicture from "./ProfilePicture"; import ProfilePicture from "./ProfilePicture";
import auth from "@/lib/auth"; import auth from "@/lib/auth";
import { toast } from "sonner"; import { toast } from "sonner";
import { StringMap } from "@/types/typeUtils"; import { StringMap } from "@/types/typeUtils";
import SettingsDialog from "./settings/SettingsDialog"; import SettingsDialog from "./settings/SettingsDialog";
import Link from "next/link"; import Link from "next/link";
import axios from "axios";
import { get, Response } from "@/lib/request"; import { get, Response } from "@/lib/request";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import App from "@/types/app";
export default function AppSidebar({ export default function AppSidebar({
children, children,
chatWith, chatWith,
wsRef,
onNewMessage, onNewMessage,
setUser,
server, server,
messages, app,
addMessage,
open,
setOpen,
}: Readonly<{ }: Readonly<{
wsRef: React.RefObject<WebSocket | null>;
addMessage: (msg: Message) => void;
messages?: Message[];
onNewMessage?: (msg: Message) => void; onNewMessage?: (msg: Message) => void;
userList: StringMap<UserProfile>;
setUserList: React.Dispatch<React.SetStateAction<StringMap<UserProfile>>>;
chatWith?: string; chatWith?: string;
children?: React.ReactNode; children?: React.ReactNode;
setUser?: React.Dispatch<React.SetStateAction<UserProfile | undefined>>;
server?: Server | undefined; server?: Server | undefined;
open: boolean; app: App;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
}>) { }>) {
const [servers, setServers] = useState<[string, string][]>([]); const [servers, setServers] = useState<[string, string][]>([]);
const [newServer, setNewServer] = useState({ name: "", ip: "" }); const [newServer, setNewServer] = useState({ name: "", ip: "" });
const router = useRouter(); const router = useRouter();
const user = useUser();
const [userList, setUserList] = useState<StringMap<UserProfile>>({});
const dms = Array.from( const dms = Array.from(
new Set( new Set(
messages app.messages
?.flatMap((item) => [item.from, item.channel_id]) ?.flatMap((item) => [item.from, item.channel_id])
.filter((item) => item !== user?.id) || [] .filter((item) => item !== app.profile?.id) || []
) )
); );
@@ -82,17 +68,16 @@ export default function AppSidebar({
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!user?.node_address) return; if (!app.profile?.node_address) return;
if (setUser) setUser(user);
auth( auth(
user?.node_address, app.profile?.node_address,
wsRef, app.node,
() => {}, () => {},
addMessage, app.addMessage,
(m) => { (m) => {
if (m.from !== chatWith && m.from !== user.id) if (m.from !== chatWith && m.from !== app.profile?.id)
toast( toast(
`New message from ${userList[m.from]?.display_name || m.from}`, `New message from ${app.profiles[m.from]?.display_name || m.from}`,
{ {
description: m.contents.slice(0, 80), description: m.contents.slice(0, 80),
action: { action: {
@@ -105,13 +90,13 @@ export default function AppSidebar({
if (onNewMessage) onNewMessage(m); if (onNewMessage) onNewMessage(m);
}, },
messages ? messages[messages.length - 1]?.id : undefined app.messages ? app.messages[app.messages.length - 1]?.id : undefined
); );
setUserList((prev) => { app.setProfiles((prev) => {
prev[user.id] = user; if (app.profile) prev[app.profile.id] = app.profile;
return prev; return prev;
}); });
}, [chatWith, user]); }, [chatWith, app.profile]);
const handleAddServer = () => { const handleAddServer = () => {
if (!newServer.ip) return; if (!newServer.ip) return;
@@ -132,7 +117,7 @@ export default function AppSidebar({
<div <div
className={cn( className={cn(
"border-r transition-all duration-300 ease-in-out overflow-hidden flex", "border-r transition-all duration-300 ease-in-out overflow-hidden flex",
open ? "w-5xl md:w-md" : "w-0 md:w-md" app.sidebarOpen ? "w-5xl md:w-md" : "w-0 md:w-md"
)} )}
> >
<div className="w-16 flex flex-col items-center gap-4 py-4 bg-muted border-r"> <div className="w-16 flex flex-col items-center gap-4 py-4 bg-muted border-r">
@@ -250,12 +235,14 @@ export default function AppSidebar({
{dms.map((id) => ( {dms.map((id) => (
<DMItem <DMItem
name={ name={
getUser(id, userList, setUserList)?.display_name || id getUser(id, app.profiles, app.setProfiles)
?.display_name || id
} }
id={id} id={id}
status="online" status="online"
avatar={ avatar={
getUser(id, userList, setUserList)?.avatar_url || "" getUser(id, app.profiles, app.setProfiles)
?.avatar_url || ""
} }
key={id} key={id}
/> />
@@ -267,19 +254,19 @@ export default function AppSidebar({
<footer className="mt-auto pb-3 pl-3 pr-3"> <footer className="mt-auto pb-3 pl-3 pr-3">
<DMItem <DMItem
name={user?.display_name || "Loading.."} name={app.profile?.display_name || "Loading.."}
id={user?.id || "me"} id={app.profile?.id || "me"}
avatar={user?.avatar_url || ""} avatar={app.profile?.avatar_url || ""}
status="online" status="online"
settings settings
/> />
</footer> </footer>
</div> </div>
</div> </div>
{open ? ( {app.sidebarOpen ? (
<div <div
className="w-screen h-full overflow-x-hidden" className="w-screen h-full overflow-x-hidden"
onClick={() => setOpen(false)} onClick={() => app.setSidebarOpen(false)}
> >
<div className="overflow-x-hidden w-screen min-w-max flex-shrink-0"> <div className="overflow-x-hidden w-screen min-w-max flex-shrink-0">
{children} {children}
+25
View File
@@ -0,0 +1,25 @@
import App from "@/types/app";
import { useRef, useState } from "react";
import { useMessages } from "./use-messages";
import { StringMap } from "@/types/typeUtils";
import useUser, { UserProfile } from "./get-user";
export default function useApp(): App {
const node = useRef<WebSocket | null>(null);
const [profiles, setProfiles] = useState<StringMap<UserProfile>>({});
const messages = useMessages((s) => s.messages);
const addMessage = useMessages((s) => s.addMessage);
const [sidebarOpen, setSidebarOpen] = useState(true);
const profile = useUser();
return {
node,
profile,
profiles,
setProfiles,
messages,
addMessage,
sidebarOpen,
setSidebarOpen,
};
}
+20
View File
@@ -0,0 +1,20 @@
import { Dispatch, RefObject, SetStateAction } from "react";
import { StringMap } from "./typeUtils";
import { UserProfile } from "@/hooks/get-user";
import { Message } from "./types";
export type SetState<T> = Dispatch<SetStateAction<T>>;
export default interface App {
node: RefObject<WebSocket | null>;
profile: UserProfile | null;
profiles: StringMap<UserProfile>;
setProfiles: SetState<StringMap<UserProfile>>;
messages: Message[];
addMessage: (v: Message) => void;
sidebarOpen: boolean;
setSidebarOpen: SetState<boolean>;
}