Sidebar UI, server stuff

This commit is contained in:
2025-10-18 14:10:06 +02:00
parent d878721098
commit 99af75ea25
5 changed files with 61 additions and 67 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ export async function GET(
const { data: profile } = await supabase const { data: profile } = await supabase
.from("servers") .from("servers")
.select("id, created_at, owner, address") .select("id, name, created_at, owner, address, icon_url")
.eq("id", id) .eq("id", id)
.maybeSingle(); .maybeSingle();
+32 -64
View File
@@ -14,7 +14,6 @@ import {
Dialog, Dialog,
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogDescription,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
@@ -37,6 +36,7 @@ import App from "@/types/app";
import { ProfileSettings } from "@/types/settings"; import { ProfileSettings } from "@/types/settings";
import axios from "axios"; import axios from "axios";
import { useIsMobile } from "@/hooks/is-mobile"; import { useIsMobile } from "@/hooks/is-mobile";
import { get } from "@/lib/request";
export default function AppSidebar({ export default function AppSidebar({
children, children,
@@ -51,8 +51,7 @@ export default function AppSidebar({
server?: Server | undefined; server?: Server | undefined;
app: App; app: App;
}>) { }>) {
const [servers, setServers] = useState<[string, string][]>([]); const [newServer, setNewServer] = useState("");
const [newServer, setNewServer] = useState({ name: "", ip: "" });
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const router = useRouter(); const router = useRouter();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
@@ -82,15 +81,7 @@ export default function AppSidebar({
load(); load();
}, [dms, query]); }, [dms, query]);
useEffect(() => { 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; if (!app.profile?.node_address) return;
@@ -123,16 +114,20 @@ export default function AppSidebar({
}); });
}, [chatWith, app.profile]); }, [chatWith, app.profile]);
const handleAddServer = () => { const handleAddServer = async () => {
if (!newServer.ip) return; if (!newServer || Object.hasOwn(app.servers, newServer)) return;
const updatedServers = [
...servers,
[newServer.ip, newServer.name] as [string, string],
];
setServers(updatedServers); try {
Cookies.set("servers", updatedServers.map((x) => x.join("@")).join(",")); const server = (await get(`/api/server/${newServer}`)).data as Server;
setNewServer({ name: "", ip: "" }); app.setServers((prev) => ({
...prev,
[server.id]: server,
}));
Cookies.set("servers", Object.keys(app.servers).join(","));
setNewServer("");
} catch (e: any) {
toast.error(e);
}
}; };
return ( return (
@@ -143,7 +138,7 @@ export default function AppSidebar({
app.sidebarOpen && isMobile ? "w-5xl md:w-md" : "w-0 md:w-md" app.sidebarOpen && isMobile ? "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 border-r">
<Button <Button
onClick={() => router.push(`/chat`)} onClick={() => router.push(`/chat`)}
className="bg-transparent text-accent-foreground hover:text-accent" className="bg-transparent text-accent-foreground hover:text-accent"
@@ -151,16 +146,12 @@ export default function AppSidebar({
<MessageCircle className="h-6 w-6" /> <MessageCircle className="h-6 w-6" />
</Button> </Button>
{servers.map( {Object.values(app.servers).map(
([ip, name]) => (srv) =>
ip && ( srv && (
<Button <Link href={`/server/${srv.id}`} key={srv.id} className="">
onClick={() => router.push(`/server/${ip}`)} <ProfilePicture name={srv.name} url={srv.icon_url} />
key={ip} </Link>
className="bg-transparent text-accent-foreground hover:text-accent"
>
<HashIcon className="h-6 w-6" />
</Button>
) )
)} )}
@@ -173,41 +164,18 @@ export default function AppSidebar({
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Add server</DialogTitle> <DialogTitle>Join a server</DialogTitle>
<DialogDescription>
Add a new server to your server list.
</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="grid gap-4"> <div className="grid gap-4">
<div className="grid gap-3"> <div className="grid gap-3">
<Label htmlFor="name-1">Name</Label> <Label htmlFor="id-1">Server ID</Label>
<Input <Input
id="name-1" id="id-1"
name="name" name="id"
value={newServer.name} value={newServer}
onChange={(e) => onChange={(e) => setNewServer(e.target.value)}
setNewServer((prev) => ({ placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
...prev,
name: e.target.value,
}))
}
placeholder="Voxa Server"
/>
</div>
<div className="grid gap-3">
<Label htmlFor="ip-1">Server Ip</Label>
<Input
id="ip-1"
name="ip"
value={newServer.ip}
onChange={(e) =>
setNewServer((prev) => ({
...prev,
ip: e.target.value,
}))
}
placeholder="192.168.1.5"
/> />
</div> </div>
</div> </div>
@@ -218,7 +186,7 @@ export default function AppSidebar({
</DialogClose> </DialogClose>
<DialogClose asChild> <DialogClose asChild>
<Button type="button" onClick={handleAddServer}> <Button type="button" onClick={handleAddServer}>
Add server Join server
</Button> </Button>
</DialogClose> </DialogClose>
</DialogFooter> </DialogFooter>
@@ -231,7 +199,7 @@ export default function AppSidebar({
</div> </div>
{/* Right column */} {/* Right column */}
<div className="w-full flex flex-col bg-card"> <div className="w-full flex flex-col bg-card/50">
{server ? ( {server ? (
<div className="p-3 border-b flex items-center gap-2"> <div className="p-3 border-b flex items-center gap-2">
<h2>{server.name}</h2> <h2>{server.name}</h2>
+23 -1
View File
@@ -1,10 +1,13 @@
import App from "@/types/app"; import App from "@/types/app";
import { useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useMessages } from "./use-messages"; import { useMessages } from "./use-messages";
import { StringMap } from "@/types/typeUtils"; import { StringMap } from "@/types/typeUtils";
import useUser, { UserProfile } from "./get-user"; import useUser, { UserProfile } from "./get-user";
import { useClientSettings } from "./use-settings"; import { useClientSettings } from "./use-settings";
import { get, Response } from "@/lib/request"; 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 { export default function useApp(): App {
const node = useRef<WebSocket | null>(null); const node = useRef<WebSocket | null>(null);
@@ -14,8 +17,27 @@ export default function useApp(): App {
const profile = useUser(); const profile = useUser();
const [clientSettings, setClientSettings] = useClientSettings(); const [clientSettings, setClientSettings] = useClientSettings();
const [dms, setDms] = useState<UserProfile[]>([]); const [dms, setDms] = useState<UserProfile[]>([]);
const [servers, setServers] = useState<StringMap<Server>>({});
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 { return {
servers,
setServers,
dms, dms,
setDms, setDms,
node, node,
+4 -1
View File
@@ -1,7 +1,7 @@
import { Dispatch, RefObject, SetStateAction } from "react"; import { Dispatch, RefObject, SetStateAction } from "react";
import { StringMap } from "./typeUtils"; import { StringMap } from "./typeUtils";
import { UserProfile } from "@/hooks/get-user"; import { UserProfile } from "@/hooks/get-user";
import { Message } from "./types"; import { Message, Server } from "./types";
import { ClientSettings } from "./settings"; import { ClientSettings } from "./settings";
export type SetState<T> = Dispatch<SetStateAction<T>>; export type SetState<T> = Dispatch<SetStateAction<T>>;
@@ -13,6 +13,9 @@ export default interface App {
clientSettings: ClientSettings; clientSettings: ClientSettings;
setClientSettings: SetState<ClientSettings>; setClientSettings: SetState<ClientSettings>;
servers: StringMap<Server>;
setServers: SetState<StringMap<Server>>;
profiles: StringMap<UserProfile | null>; profiles: StringMap<UserProfile | null>;
setProfiles: SetState<StringMap<UserProfile | null>>; setProfiles: SetState<StringMap<UserProfile | null>>;
+1
View File
@@ -15,5 +15,6 @@ export interface Message {
export interface Server { export interface Server {
id: string; id: string;
name: string; name: string;
icon_url: string;
channels: Channel[]; channels: Channel[];
} }