From d8787210985980f0ba07891c1bcd6fd0d6e80e9b Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 18 Oct 2025 11:09:43 +0200 Subject: [PATCH 1/5] Chat bug fix and top name implementation --- src/app/chat/[id]/page.tsx | 27 ++++++++++++++------------- src/components/MessageBox.tsx | 13 ++++++++++--- src/components/ProfilePicture.tsx | 5 ++++- src/hooks/use-async.ts | 16 ++++++++++++++++ src/lib/auth.ts | 2 ++ 5 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 src/hooks/use-async.ts diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx index db8929c..aaaf2b7 100644 --- a/src/app/chat/[id]/page.tsx +++ b/src/app/chat/[id]/page.tsx @@ -6,34 +6,35 @@ import { useParams } from "next/navigation"; import useApp from "@/hooks/use-app"; import { useEffect, useRef } from "react"; import auth from "@/lib/auth"; +import useAsync from "@/hooks/use-async"; export default function DMs() { const { id } = useParams<{ id: string }>(); const app = useApp(); const targetNode = useRef(null); + const { value: target } = useAsync(() => app.getUserById(id)); useEffect(() => { async function connectTargetNode() { - const target = await app.getUserById(id); - if (target) { - auth( - target.node_address, - targetNode, - () => {}, - app.addMessage, - () => {} - ); - } + if (!target) return; + auth( + target.node_address, + targetNode, + () => {}, + app.addMessage, + () => {} + ); } - connectTargetNode().then(() => {}); - }, [id]); + connectTargetNode(); + }, [target]); return ( (m.channel_id === id && m.from === app.profile?.id) || diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.tsx index 50d3bb6..010e271 100644 --- a/src/components/MessageBox.tsx +++ b/src/components/MessageBox.tsx @@ -120,11 +120,13 @@ export default function MessageBox({ app, sendMessage, channelName, + channelIcon, messages, }: { app: App; sendMessage: (m: string) => void; channelName?: string; + channelIcon?: string; messages: Message[]; }) { const [text, setText] = useState(""); @@ -136,10 +138,15 @@ export default function MessageBox({
app.setSidebarOpen(true)} - className="cursor-pointer flex" + className="cursor-pointer flex gap-2" > - - {channelName} + + + {channelIcon && ( + + )} + {channelName} +
)} diff --git a/src/components/ProfilePicture.tsx b/src/components/ProfilePicture.tsx index 7a17ac9..7228443 100644 --- a/src/components/ProfilePicture.tsx +++ b/src/components/ProfilePicture.tsx @@ -1,14 +1,17 @@ +import { cn } from "@/lib/utils"; import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"; export default function ProfilePicture({ name, url, + className, }: { name: string; url: string | undefined; + className?: string; }) { return ( - + {name[0]} diff --git a/src/hooks/use-async.ts b/src/hooks/use-async.ts new file mode 100644 index 0000000..7742870 --- /dev/null +++ b/src/hooks/use-async.ts @@ -0,0 +1,16 @@ +import { useEffect, useState } from "react"; + +export default function useAsync(func: () => Promise) { + const [loading, setLoading] = useState(true); + const [value, setValue] = useState(); + const [error, setError] = useState(); + + useEffect(() => { + func() + .then(setValue) + .catch(setError) + .finally(() => setLoading(false)); + }, []); + + return { value, loading, error }; +} diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 9e330bc..5b7bafa 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -13,6 +13,8 @@ export default async function auth( ) { console.log("Authenticating with server id:", id); + if (!id) return; + const ip = ((await get(`/api/server/${id}`)).data as any).address as string; console.log("Authenticating with server at:", ip); From 99af75ea25310d0c3c5e58add2b4a1251915271d Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 18 Oct 2025 14:10:06 +0200 Subject: [PATCH 2/5] Sidebar UI, server stuff --- src/app/api/server/[id]/route.ts | 2 +- src/components/Sidebar.tsx | 96 +++++++++++--------------------- src/hooks/use-app.ts | 24 +++++++- src/types/app.ts | 5 +- src/types/types.ts | 1 + 5 files changed, 61 insertions(+), 67 deletions(-) diff --git a/src/app/api/server/[id]/route.ts b/src/app/api/server/[id]/route.ts index f695cac..edbe085 100644 --- a/src/app/api/server/[id]/route.ts +++ b/src/app/api/server/[id]/route.ts @@ -13,7 +13,7 @@ export async function GET( const { data: profile } = await supabase .from("servers") - .select("id, created_at, owner, address") + .select("id, name, created_at, owner, address, icon_url") .eq("id", id) .maybeSingle(); diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 1b887b7..50f66ae 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -14,7 +14,6 @@ import { Dialog, DialogClose, DialogContent, - DialogDescription, DialogFooter, DialogHeader, DialogTitle, @@ -37,6 +36,7 @@ import App from "@/types/app"; import { ProfileSettings } from "@/types/settings"; import axios from "axios"; import { useIsMobile } from "@/hooks/is-mobile"; +import { get } from "@/lib/request"; export default function AppSidebar({ children, @@ -51,8 +51,7 @@ export default function AppSidebar({ server?: Server | undefined; app: App; }>) { - const [servers, setServers] = useState<[string, string][]>([]); - const [newServer, setNewServer] = useState({ name: "", ip: "" }); + const [newServer, setNewServer] = useState(""); const [query, setQuery] = useState(""); const router = useRouter(); const isMobile = useIsMobile(); @@ -82,15 +81,7 @@ export default function AppSidebar({ load(); }, [dms, query]); - useEffect(() => { - const stored = Cookies.get("servers"); - if (!stored) return; - const parsed = stored - .split(",") - .map((x) => x.trim().split("@")) - .filter(([ip, name]) => ip && name) as [string, string][]; - setServers(parsed); - }, []); + useEffect(() => {}, []); useEffect(() => { if (!app.profile?.node_address) return; @@ -123,16 +114,20 @@ export default function AppSidebar({ }); }, [chatWith, app.profile]); - const handleAddServer = () => { - if (!newServer.ip) return; - const updatedServers = [ - ...servers, - [newServer.ip, newServer.name] as [string, string], - ]; + const handleAddServer = async () => { + if (!newServer || Object.hasOwn(app.servers, newServer)) return; - setServers(updatedServers); - Cookies.set("servers", updatedServers.map((x) => x.join("@")).join(",")); - setNewServer({ name: "", ip: "" }); + try { + const server = (await get(`/api/server/${newServer}`)).data as Server; + app.setServers((prev) => ({ + ...prev, + [server.id]: server, + })); + Cookies.set("servers", Object.keys(app.servers).join(",")); + setNewServer(""); + } catch (e: any) { + toast.error(e); + } }; return ( @@ -143,7 +138,7 @@ export default function AppSidebar({ app.sidebarOpen && isMobile ? "w-5xl md:w-md" : "w-0 md:w-md" )} > -
+
- {servers.map( - ([ip, name]) => - ip && ( - + {Object.values(app.servers).map( + (srv) => + srv && ( + + + ) )} @@ -173,41 +164,18 @@ export default function AppSidebar({ - Add server - - Add a new server to your server list. - + Join a server
- + - setNewServer((prev) => ({ - ...prev, - name: e.target.value, - })) - } - placeholder="Voxa Server" - /> -
-
- - - setNewServer((prev) => ({ - ...prev, - ip: e.target.value, - })) - } - placeholder="192.168.1.5" + id="id-1" + name="id" + value={newServer} + onChange={(e) => setNewServer(e.target.value)} + placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
@@ -218,7 +186,7 @@ export default function AppSidebar({ @@ -231,7 +199,7 @@ export default function AppSidebar({
{/* Right column */} -
+
{server ? (

{server.name}

diff --git a/src/hooks/use-app.ts b/src/hooks/use-app.ts index ec226ec..b90180d 100644 --- a/src/hooks/use-app.ts +++ b/src/hooks/use-app.ts @@ -1,10 +1,13 @@ import App from "@/types/app"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useMessages } from "./use-messages"; import { StringMap } from "@/types/typeUtils"; import useUser, { UserProfile } from "./get-user"; import { useClientSettings } from "./use-settings"; import { get, Response } from "@/lib/request"; +import { Server } from "@/types/types"; +import Cookies from "js-cookie"; +import { toast } from "sonner"; export default function useApp(): App { const node = useRef(null); @@ -14,8 +17,27 @@ export default function useApp(): App { const profile = useUser(); const [clientSettings, setClientSettings] = useClientSettings(); const [dms, setDms] = useState([]); + const [servers, setServers] = useState>({}); + + useEffect(() => { + const stored = Cookies.get("servers"); + if (!stored) return; + Promise.all( + stored + .split(",") + .map((x) => x.trim()) + .filter((id) => id) + .map((id) => get(`/api/server/${id}`)) + ) + .then((o) => + setServers(Object.fromEntries(o.map((i) => [i.data.id, i.data]))) + ) + .catch(toast.error); + }, []); return { + servers, + setServers, dms, setDms, node, diff --git a/src/types/app.ts b/src/types/app.ts index 2624abb..45f7ceb 100644 --- a/src/types/app.ts +++ b/src/types/app.ts @@ -1,7 +1,7 @@ import { Dispatch, RefObject, SetStateAction } from "react"; import { StringMap } from "./typeUtils"; import { UserProfile } from "@/hooks/get-user"; -import { Message } from "./types"; +import { Message, Server } from "./types"; import { ClientSettings } from "./settings"; export type SetState = Dispatch>; @@ -13,6 +13,9 @@ export default interface App { clientSettings: ClientSettings; setClientSettings: SetState; + servers: StringMap; + setServers: SetState>; + profiles: StringMap; setProfiles: SetState>; diff --git a/src/types/types.ts b/src/types/types.ts index 9bb3c32..6301fa3 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -15,5 +15,6 @@ export interface Message { export interface Server { id: string; name: string; + icon_url: string; channels: Channel[]; } From 9483a7065b4e44078a395938b15cff5a5b2fb7c7 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 18 Oct 2025 14:19:57 +0200 Subject: [PATCH 3/5] Sidebar and app layout structure --- src/app/chat/[id]/page.tsx | 6 +- src/app/chat/page.tsx | 6 +- src/app/server/[ip]/page.tsx | 6 +- src/components/Sidebar.tsx | 345 ------------------------- src/components/app/AppLayout.tsx | 101 ++++++++ src/components/app/DMItem.tsx | 43 +++ src/components/app/SidebarChannels.tsx | 42 +++ src/components/app/SidebarDMs.tsx | 105 ++++++++ src/components/app/SidebarServers.tsx | 98 +++++++ 9 files changed, 398 insertions(+), 354 deletions(-) delete mode 100644 src/components/Sidebar.tsx create mode 100644 src/components/app/AppLayout.tsx create mode 100644 src/components/app/DMItem.tsx create mode 100644 src/components/app/SidebarChannels.tsx create mode 100644 src/components/app/SidebarDMs.tsx create mode 100644 src/components/app/SidebarServers.tsx diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx index aaaf2b7..340d749 100644 --- a/src/app/chat/[id]/page.tsx +++ b/src/app/chat/[id]/page.tsx @@ -1,7 +1,7 @@ "use client"; import MessageBox from "@/components/MessageBox"; -import AppSidebar from "@/components/Sidebar"; +import AppLayout from "@/components/app/AppLayout"; import { useParams } from "next/navigation"; import useApp from "@/hooks/use-app"; import { useEffect, useRef } from "react"; @@ -30,7 +30,7 @@ export default function DMs() { }, [target]); return ( - + - + ); } diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index a35b042..600e224 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -2,7 +2,7 @@ import ProfilePicture from "@/components/ProfilePicture"; import SettingsDialog from "@/components/settings/SettingsDialog"; -import AppSidebar from "@/components/Sidebar"; +import AppLayout from "@/components/app/AppLayout"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; @@ -28,7 +28,7 @@ export default function ChatHub() { const activeServers: Server[] = []; return ( - +
-
+ ); } diff --git a/src/app/server/[ip]/page.tsx b/src/app/server/[ip]/page.tsx index 1ad9a43..e847af2 100644 --- a/src/app/server/[ip]/page.tsx +++ b/src/app/server/[ip]/page.tsx @@ -3,7 +3,7 @@ import { useRef, useState } from "react"; import { useParams } from "next/navigation"; import MessageBox from "@/components/MessageBox"; -import AppSidebar from "@/components/Sidebar"; +import AppLayout from "@/components/app/AppLayout"; import { Server as ServerType } from "@/types/types"; import auth from "@/lib/auth"; import { useServerMessages } from "@/hooks/use-messages"; @@ -35,7 +35,7 @@ export default function Server() { ); return ( - + - + ); } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx deleted file mode 100644 index 50f66ae..0000000 --- a/src/components/Sidebar.tsx +++ /dev/null @@ -1,345 +0,0 @@ -"use client"; - -import { - HashIcon, - MessageCircle, - Plus, - Search, - Volume2Icon, -} from "lucide-react"; -import { Server, Channel, Message } from "@/types/types"; -import React, { useEffect, useMemo, useState } from "react"; -import Cookies from "js-cookie"; -import { - Dialog, - DialogClose, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "./ui/dialog"; -import { Input } from "./ui/input"; -import { Label } from "./ui/label"; -import { Button } from "./ui/button"; -import { Card } from "./ui/card"; -import { ScrollArea } from "./ui/scroll-area"; -import { useRouter } from "next/navigation"; -import { UserProfile } from "@/hooks/get-user"; -import ProfilePicture from "./ProfilePicture"; -import auth from "@/lib/auth"; -import { toast } from "sonner"; -import SettingsDialog from "./settings/SettingsDialog"; -import Link from "next/link"; -import { cn } from "@/lib/utils"; -import App from "@/types/app"; -import { ProfileSettings } from "@/types/settings"; -import axios from "axios"; -import { useIsMobile } from "@/hooks/is-mobile"; -import { get } from "@/lib/request"; - -export default function AppSidebar({ - children, - chatWith, - onNewMessage, - server, - app, -}: Readonly<{ - onNewMessage?: (msg: Message) => void; - chatWith?: string; - children?: React.ReactNode; - server?: Server | undefined; - app: App; -}>) { - const [newServer, setNewServer] = useState(""); - const [query, setQuery] = useState(""); - const router = useRouter(); - const isMobile = useIsMobile(); - - const dms = useMemo(() => { - return Array.from( - new Set( - app.messages - ?.flatMap((item) => [item.from, item.channel_id]) - .filter((item) => item !== app.profile?.id) || [] - ) - ); - }, [app.messages, app.profile?.id]); - - useEffect(() => { - const load = async () => { - const users = await Promise.all(dms.map((id) => app.getUserById(id))); - app.setDms( - users.filter( - (user) => - user !== null && - (user?.username.toLowerCase().includes(query.toLowerCase()) || - user?.id.toLowerCase().includes(query.toLowerCase())) - ) as UserProfile[] - ); - }; - load(); - }, [dms, query]); - - useEffect(() => {}, []); - - useEffect(() => { - if (!app.profile?.node_address) return; - auth( - app.profile?.node_address, - app.node, - () => {}, - app.addMessage, - (m) => { - if (m.from !== chatWith && m.from !== app.profile?.id) - toast( - `New message from ${app.profiles[m.from]?.display_name || m.from}`, - { - description: m.contents.slice(0, 80), - action: { - label: "View", - onClick: () => router.push(`/chat/${m.from}`), - }, - position: "top-right", - } - ); - - if (onNewMessage) onNewMessage(m); - }, - app.messages ? app.messages[app.messages.length - 1]?.id : undefined - ); - app.setProfiles((prev) => { - if (app.profile) prev[app.profile.id] = app.profile; - return prev; - }); - }, [chatWith, app.profile]); - - const handleAddServer = async () => { - if (!newServer || Object.hasOwn(app.servers, newServer)) return; - - try { - const server = (await get(`/api/server/${newServer}`)).data as Server; - app.setServers((prev) => ({ - ...prev, - [server.id]: server, - })); - Cookies.set("servers", Object.keys(app.servers).join(",")); - setNewServer(""); - } catch (e: any) { - toast.error(e); - } - }; - - return ( -
-
-
- - - {Object.values(app.servers).map( - (srv) => - srv && ( - - - - ) - )} - - - - - - - - - Join a server - - -
-
- - setNewServer(e.target.value)} - placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - /> -
-
- - - - - - - - - -
-
- -
- -
-
- - {/* Right column */} -
- {server ? ( -
-

{server.name}

-
- ) : ( -
-
- setQuery(e.target.value)} - /> - -
-
- )} - -
- {server ? ( - <> - {server.channels.map((channel) => ( - - ))} - - ) : ( - <> - {app.dms.map((user) => ( - - ))} - {app.dms.length === 0 && query.trim() !== "" ? ( -
- -
- ) : null} - - )} -
-
- -
- -
-
-
- {app.sidebarOpen && isMobile ? ( -
app.setSidebarOpen(false)} - > -
- {children} -
-
- ) : ( - children - )} -
- ); -} - -function DMItem({ - name, - id, - avatar, - status, - settings, - app, -}: { - name: string; - id: string; - avatar: string; - status: string; - settings?: boolean; - app: App; -}) { - return ( - - -
- {name} - {status} -
- {settings && ( - - )} - - ); -} - -function ChannelProp(channel: Channel) { - return ( - - - {channel.name} - - ); -} - -function ChannelIcon({ kind }: { kind: "text" | "voice" }) { - switch (kind) { - case "text": - return ; - case "voice": - return ; - default: - return null; - } -} diff --git a/src/components/app/AppLayout.tsx b/src/components/app/AppLayout.tsx new file mode 100644 index 0000000..c65f969 --- /dev/null +++ b/src/components/app/AppLayout.tsx @@ -0,0 +1,101 @@ +"use client"; + +import React, { useEffect, useMemo, useState } from "react"; +import { cn } from "@/lib/utils"; +import { useRouter } from "next/navigation"; +import { get } from "@/lib/request"; +import Cookies from "js-cookie"; +import { toast } from "sonner"; +import auth from "@/lib/auth"; +import { useIsMobile } from "@/hooks/is-mobile"; +import App from "@/types/app"; +import { Server, Message } from "@/types/types"; + +import SidebarServers from "./SidebarServers"; +import SidebarDMs from "./SidebarDMs"; +import SidebarChannels from "./SidebarChannels"; + +export default function AppLayout({ + children, + chatWith, + onNewMessage, + server, + app, +}: { + onNewMessage?: (msg: Message) => void; + chatWith?: string; + children?: React.ReactNode; + server?: Server; + app: App; +}) { + const [query, setQuery] = useState(""); + const router = useRouter(); + const isMobile = useIsMobile(); + + useEffect(() => { + if (!app.profile?.node_address) return; + + auth( + app.profile.node_address, + app.node, + () => {}, + app.addMessage, + (m) => { + if (m.from !== chatWith && m.from !== app.profile?.id) + toast( + `New message from ${app.profiles[m.from]?.display_name || m.from}`, + { + description: m.contents.slice(0, 80), + action: { + label: "View", + onClick: () => router.push(`/chat/${m.from}`), + }, + position: "top-right", + } + ); + + if (onNewMessage) onNewMessage(m); + }, + app.messages?.[app.messages.length - 1]?.id + ); + + app.setProfiles((prev) => { + if (app.profile) prev[app.profile.id] = app.profile; + return prev; + }); + }, [chatWith, app.profile]); + + return ( +
+
+ {/* Left column — servers */} + + + {/* Right column — DMs or server channels */} + {server ? ( + + ) : ( + + )} +
+ + {app.sidebarOpen && isMobile ? ( +
app.setSidebarOpen(false)} + > +
+ {children} +
+
+ ) : ( + children + )} +
+ ); +} diff --git a/src/components/app/DMItem.tsx b/src/components/app/DMItem.tsx new file mode 100644 index 0000000..535efbf --- /dev/null +++ b/src/components/app/DMItem.tsx @@ -0,0 +1,43 @@ +"use client"; + +import Link from "next/link"; +import ProfilePicture from "../ProfilePicture"; +import SettingsDialog from "../settings/SettingsDialog"; +import App from "@/types/app"; +import { ProfileSettings } from "@/types/settings"; + +export default function DMItem({ + name, + id, + avatar, + status, + settings, + app, +}: { + name: string; + id: string; + avatar: string; + status: string; + settings?: boolean; + app: App; +}) { + return ( + + +
+ {name} + {status} +
+ {settings && ( + + )} + + ); +} diff --git a/src/components/app/SidebarChannels.tsx b/src/components/app/SidebarChannels.tsx new file mode 100644 index 0000000..875740e --- /dev/null +++ b/src/components/app/SidebarChannels.tsx @@ -0,0 +1,42 @@ +"use client"; +import { ScrollArea } from "../ui/scroll-area"; +import { Server, Channel } from "@/types/types"; +import { Card } from "../ui/card"; +import { HashIcon, Volume2Icon } from "lucide-react"; + +export function ChannelIcon({ kind }: { kind: "text" | "voice" }) { + switch (kind) { + case "text": + return ; + case "voice": + return ; + default: + return null; + } +} + +export function ChannelItem({ channel }: { channel: Channel }) { + return ( + + + {channel.name} + + ); +} + +export default function SidebarChannels({ server }: { server: Server }) { + return ( +
+
+

{server.name}

+
+ +
+ {server.channels.map((channel: Channel) => ( + + ))} +
+
+
+ ); +} diff --git a/src/components/app/SidebarDMs.tsx b/src/components/app/SidebarDMs.tsx new file mode 100644 index 0000000..fd9832e --- /dev/null +++ b/src/components/app/SidebarDMs.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { Input } from "../ui/input"; +import { Search, Plus } from "lucide-react"; +import { ScrollArea } from "../ui/scroll-area"; +import { Button } from "../ui/button"; +import { useRouter } from "next/navigation"; +import axios from "axios"; +import DMItem from "./DMItem"; +import App from "@/types/app"; +import { UserProfile } from "@/hooks/get-user"; +import { useEffect } from "react"; + +export default function SidebarDMs({ + app, + query, + setQuery, +}: { + app: App; + query: string; + setQuery: (v: string) => void; +}) { + const router = useRouter(); + + useEffect(() => { + const load = async () => { + const users = await Promise.all( + app.messages + .flatMap((m) => [m.from, m.channel_id]) + .filter((id) => id !== app.profile?.id) + .map((id) => app.getUserById(id)) + ); + app.setDms( + users.filter( + (user) => + user && + (user.username.toLowerCase().includes(query.toLowerCase()) || + user.id.toLowerCase().includes(query.toLowerCase())) + ) as UserProfile[] + ); + }; + load(); + }, [query]); + + return ( +
+
+
+ setQuery(e.target.value)} + /> + +
+
+ + +
+ {app.dms.map((user) => ( + + ))} + + {app.dms.length === 0 && query.trim() !== "" && ( +
+ +
+ )} +
+
+ +
+ +
+
+ ); +} diff --git a/src/components/app/SidebarServers.tsx b/src/components/app/SidebarServers.tsx new file mode 100644 index 0000000..3b4b0e8 --- /dev/null +++ b/src/components/app/SidebarServers.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { Plus, MessageCircle } from "lucide-react"; +import Link from "next/link"; +import Cookies from "js-cookie"; +import { Button } from "../ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../ui/dialog"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { toast } from "sonner"; +import { useState } from "react"; +import { get } from "@/lib/request"; +import { Server } from "@/types/types"; +import App from "@/types/app"; +import ProfilePicture from "../ProfilePicture"; +import SettingsDialog from "../settings/SettingsDialog"; +import { useRouter } from "next/navigation"; + +export default function SidebarServers({ app }: { app: App }) { + const [newServer, setNewServer] = useState(""); + const router = useRouter(); + + const handleAddServer = async () => { + if (!newServer || Object.hasOwn(app.servers, newServer)) return; + try { + const server = (await get(`/api/server/${newServer}`)).data as Server; + app.setServers((prev) => ({ ...prev, [server.id]: server })); + Cookies.set("servers", Object.keys(app.servers).join(",")); + setNewServer(""); + } catch (e: any) { + toast.error(e); + } + }; + + return ( +
+ + + {Object.values(app.servers).map( + (srv) => + srv && ( + + + + ) + )} + + + + + + + + Join a server + + +
+ + setNewServer(e.target.value)} + placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + /> +
+ + + + + + + + + +
+
+ +
+ +
+
+ ); +} From ec9421e0ed85bdeb58a4ab5100079850d689bc84 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 18 Oct 2025 14:56:26 +0200 Subject: [PATCH 4/5] Dynamic logo --- src/app/page.tsx | 8 +++++--- src/components/Logo.tsx | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/components/Logo.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index e7fa34a..7b0679f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,6 +6,7 @@ import { Input } from "@/components/ui/input"; import { MessageCircle, Users, Shield } from "lucide-react"; import { motion } from "framer-motion"; import { useRouter } from "next/navigation"; +import Logo from "@/components/Logo"; export default function LandingPage() { const router = useRouter(); @@ -14,13 +15,14 @@ export default function LandingPage() {
{/* Header */}
- + > + +