From b629b9a6e283cad53b9bdf3fa3a033354cc78819 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 23 Dec 2025 15:01:26 +0100 Subject: [PATCH 1/9] Testing new supabase structure --- package-lock.json | 155 ++++++++++++++++++++---------------- package.json | 3 +- src/actions/get-profile.ts | 19 +++++ src/actions/get-session.ts | 14 ++++ src/actions/login-server.ts | 18 +++++ src/app/auth/Login.tsx | 17 ++-- src/hooks/get-user.ts | 23 +++--- src/lib/supa.ts | 24 ++++++ 8 files changed, 179 insertions(+), 94 deletions(-) create mode 100644 src/actions/get-profile.ts create mode 100644 src/actions/get-session.ts create mode 100644 src/actions/login-server.ts create mode 100644 src/lib/supa.ts diff --git a/package-lock.json b/package-lock.json index 6a01c88..1bea6d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@supabase/ssr": "^0.8.0", "@supabase/supabase-js": "^2.75.0", "@types/axios": "^0.9.36", "@types/date-fns": "^2.5.3", @@ -53,7 +54,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/node": "^20.19.21", + "@types/node": "^20.19.27", "@types/pg": "^8.15.5", "@types/react": "^19", "@types/react-dom": "^19", @@ -1918,77 +1919,95 @@ "license": "MIT" }, "node_modules/@supabase/auth-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.75.0.tgz", - "integrity": "sha512-J8TkeqCOMCV4KwGKVoxmEBuDdHRwoInML2vJilthOo7awVCro2SM+tOcpljORwuBQ1vHUtV62Leit+5wlxrNtw==", + "version": "2.89.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.89.0.tgz", + "integrity": "sha512-wiWZdz8WMad8LQdJMWYDZ2SJtZP5MwMqzQq3ehtW2ngiI3UTgbKiFrvMUUS3KADiVlk4LiGfODB2mrYx7w2f8w==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15" + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/functions-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.75.0.tgz", - "integrity": "sha512-18yk07Moj/xtQ28zkqswxDavXC3vbOwt1hDuYM3/7xPnwwpKnsmPyZ7bQ5th4uqiJzQ135t74La9tuaxBR6e7w==", + "version": "2.89.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.89.0.tgz", + "integrity": "sha512-XEueaC5gMe5NufNYfBh9kPwJlP5M2f+Ogr8rvhmRDAZNHgY6mI35RCkYDijd92pMcNM7g8pUUJov93UGUnqfyw==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15" - } - }, - "node_modules/@supabase/node-fetch": { - "version": "2.6.15", - "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", - "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" + "tslib": "2.8.1" }, "engines": { - "node": "4.x || >=6.0.0" + "node": ">=20.0.0" } }, "node_modules/@supabase/postgrest-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.75.0.tgz", - "integrity": "sha512-YfBz4W/z7eYCFyuvHhfjOTTzRrQIvsMG2bVwJAKEVVUqGdzqfvyidXssLBG0Fqlql1zJFgtsPpK1n4meHrI7tg==", + "version": "2.89.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.89.0.tgz", + "integrity": "sha512-/b0fKrxV9i7RNOEXMno/I1862RsYhuUo+Q6m6z3ar1f4ulTMXnDfv0y4YYxK2POcgrOXQOgKYQx1eArybyNvtg==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15" + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/realtime-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.75.0.tgz", - "integrity": "sha512-B4Xxsf2NHd5cEnM6MGswOSPSsZKljkYXpvzKKmNxoUmNQOfB7D8HOa6NwHcUBSlxcjV+vIrYKcYXtavGJqeGrw==", + "version": "2.89.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.89.0.tgz", + "integrity": "sha512-aMOvfDb2a52u6PX6jrrjvACHXGV3zsOlWRzZsTIOAJa0hOVvRp01AwC1+nLTGUzxzezejrYeCX+KnnM1xHdl+w==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", + "tslib": "2.8.1", "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/ssr": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.8.0.tgz", + "integrity": "sha512-/PKk8kNFSs8QvvJ2vOww1mF5/c5W8y42duYtXvkOSe+yZKRgTTZywYG2l41pjhNomqESZCpZtXuWmYjFRMV+dw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.2" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.76.1" } }, "node_modules/@supabase/storage-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.75.0.tgz", - "integrity": "sha512-wpJMYdfFDckDiHQaTpK+Ib14N/O2o0AAWWhguKvmmMurB6Unx17GGmYp5rrrqCTf8S1qq4IfIxTXxS4hzrUySg==", + "version": "2.89.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.89.0.tgz", + "integrity": "sha512-6zKcXofk/M/4Eato7iqpRh+B+vnxeiTumCIP+Tz26xEqIiywzD9JxHq+udRrDuv6hXE+pmetvJd8n5wcf4MFRQ==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15" + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/supabase-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.75.0.tgz", - "integrity": "sha512-8UN/vATSgS2JFuJlMVr51L3eUDz+j1m7Ww63wlvHLKULzCDaVWYzvacCjBTLW/lX/vedI2LBI4Vg+01G9ufsJQ==", + "version": "2.89.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.89.0.tgz", + "integrity": "sha512-KlaRwSfFA0fD73PYVMHj5/iXFtQGCcX7PSx0FdQwYEEw9b2wqM7GxadY+5YwcmuEhalmjFB/YvqaoNVF+sWUlg==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.75.0", - "@supabase/functions-js": "2.75.0", - "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "2.75.0", - "@supabase/realtime-js": "2.75.0", - "@supabase/storage-js": "2.75.0" + "@supabase/auth-js": "2.89.0", + "@supabase/functions-js": "2.89.0", + "@supabase/postgrest-js": "2.89.0", + "@supabase/realtime-js": "2.89.0", + "@supabase/storage-js": "2.89.0" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@swc/helpers": { @@ -2353,9 +2372,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz", - "integrity": "sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==", + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2374,9 +2393,9 @@ } }, "node_modules/@types/phoenix": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", - "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", + "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", "license": "MIT" }, "node_modules/@types/react": { @@ -2687,6 +2706,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3140,6 +3172,15 @@ "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", "license": "MIT" }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/inline-style-parser": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", @@ -5221,12 +5262,6 @@ "node": ">=18" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -5463,22 +5498,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", diff --git a/package.json b/package.json index 8909f37..d89a45b 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@supabase/ssr": "^0.8.0", "@supabase/supabase-js": "^2.75.0", "@types/axios": "^0.9.36", "@types/date-fns": "^2.5.3", @@ -56,7 +57,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/node": "^20.19.21", + "@types/node": "^20.19.27", "@types/pg": "^8.15.5", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/src/actions/get-profile.ts b/src/actions/get-profile.ts new file mode 100644 index 0000000..168878d --- /dev/null +++ b/src/actions/get-profile.ts @@ -0,0 +1,19 @@ +"use server"; + +import createClient from "@/lib/supa"; + +export async function getProfileById(id: string) { + const supabase = await createClient(); + + const { error, data } = await supabase + .from("profiles") + .select("id, username, display_name, avatar_url, node_address") + .eq("id", id) + .maybeSingle(); + + if (error) { + throw error; + } + + return data; +} diff --git a/src/actions/get-session.ts b/src/actions/get-session.ts new file mode 100644 index 0000000..6b2bc78 --- /dev/null +++ b/src/actions/get-session.ts @@ -0,0 +1,14 @@ +"use server"; + +import createClient from "@/lib/supa"; + +export default async function getSession() { + const supabase = await createClient(); + const { error, data } = await supabase.auth.getSession(); + + if (error) { + throw error; + } + + return data.session?.user.id; +} diff --git a/src/actions/login-server.ts b/src/actions/login-server.ts new file mode 100644 index 0000000..c0d2063 --- /dev/null +++ b/src/actions/login-server.ts @@ -0,0 +1,18 @@ +"use server"; + +import createClient from "@/lib/supa"; + +export default async function loginServer(email: string, password: string) { + const supabase = await createClient(); + + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + + if (error) { + throw new Error(error.message); + } + + return data.user; +} diff --git a/src/app/auth/Login.tsx b/src/app/auth/Login.tsx index b5419d9..fb20e6c 100644 --- a/src/app/auth/Login.tsx +++ b/src/app/auth/Login.tsx @@ -2,32 +2,25 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import axios from "axios"; import { useState } from "react"; -import Cookies from "js-cookie"; import { useRouter } from "next/navigation"; import { CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import loginServer from "@/actions/login-server"; + +type Feedback = { kind: "error" | "info"; message: string }; export default function Login() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); - const [feedback, setFeedback] = useState< - undefined | { kind: "error" | "info"; message: string } - >(); + const [feedback, setFeedback] = useState(); const router = useRouter(); const login = async () => { - localStorage.removeItem("chat-messages"); - localStorage.removeItem("server-messages"); setFeedback(undefined); try { - const res = await axios.get("/api/user", { - params: { email, password }, - }); + const res = await loginServer(email, password); - Cookies.set("token", (res.data as any).token); - Cookies.set("refresh_token", (res.data as any).refresh_token); setFeedback({ kind: "info", message: "Login successful! Redirecting...", diff --git a/src/hooks/get-user.ts b/src/hooks/get-user.ts index 71f990f..98fe782 100644 --- a/src/hooks/get-user.ts +++ b/src/hooks/get-user.ts @@ -7,6 +7,8 @@ import { useEffect, useState } from "react"; import { get } from "@/lib/request"; import { toast } from "sonner"; import axios from "axios"; +import getSession from "@/actions/get-session"; +import { getProfileById } from "@/actions/get-profile"; export interface UserProfile extends ProfileSettings { id: string; @@ -19,20 +21,15 @@ export default function useUser() { useEffect(() => { async function fetchUser() { try { - setUser((await get("/api/profile", (r) => setUser(r.data))).data); - } catch (e: any) { - try { - console.log("Failed to obtain current user, refreshing token.."); - const refresh = await axios.put("/api/auth/"); - const { access_token, refresh_token } = refresh.data as any; - Cookies.set("token", access_token); - Cookies.set("refresh_token", refresh_token); - - setUser((await get("/api/profile", (r) => setUser(r.data))).data); - } catch (e: any) { - router.push("/auth?t=login"); - toast.error(`Error: ${e}`); + const user_id = await getSession(); + if (!user_id) { + throw new Error("Session user id is not valid"); } + + setUser(await getProfileById(user_id)); + } catch (e: any) { + router.push("/auth?t=login"); + toast.error(`Error: ${e}`); } } diff --git a/src/lib/supa.ts b/src/lib/supa.ts new file mode 100644 index 0000000..5c7f41e --- /dev/null +++ b/src/lib/supa.ts @@ -0,0 +1,24 @@ +import { cookies } from "next/headers"; +import { createServerClient } from "@supabase/ssr"; + +export default async function createClient() { + const cookieStore = await cookies(); + + return createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SERVICE_ROLE_KEY!, + { + cookies: { + get(name) { + return cookieStore.get(name)?.value; + }, + set(name, value) { + cookieStore.set(name, value); + }, + remove(name) { + cookieStore.delete(name); + }, + }, + } + ); +} From a39c4fa26d6e0dc5ae9466e484e6e93e012172e2 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 23 Dec 2025 15:24:59 +0100 Subject: [PATCH 2/9] Working supabase structure --- src/actions/get-profile.ts | 43 +++++++++- src/actions/get-session.ts | 2 +- src/actions/login-server.ts | 2 +- src/app/api/auth/route.ts | 83 ------------------- src/app/api/profile/route.ts | 115 --------------------------- src/app/api/user/route.ts | 22 ----- src/components/settings/Settings.tsx | 3 +- src/hooks/use-app.ts | 13 ++- src/lib/auth.ts | 5 +- src/lib/supa.ts | 24 ------ 10 files changed, 52 insertions(+), 260 deletions(-) delete mode 100644 src/app/api/profile/route.ts delete mode 100644 src/lib/supa.ts diff --git a/src/actions/get-profile.ts b/src/actions/get-profile.ts index 168878d..be1577c 100644 --- a/src/actions/get-profile.ts +++ b/src/actions/get-profile.ts @@ -1,8 +1,11 @@ "use server"; -import createClient from "@/lib/supa"; +import createClient from "@/actions/supa"; +import { ProfileSettings } from "@/types/settings"; +import getSession from "./get-session"; +import { UserProfile } from "@/hooks/get-user"; -export async function getProfileById(id: string) { +export async function getProfileById(id: string): Promise { const supabase = await createClient(); const { error, data } = await supabase @@ -17,3 +20,39 @@ export async function getProfileById(id: string) { return data; } + +export async function getProfileByUsername( + username: string +): Promise { + const supabase = await createClient(); + + const { error, data } = await supabase + .from("profiles") + .select("id, username, display_name, avatar_url, node_address") + .eq("username", username) + .maybeSingle(); + + if (error) { + throw error; + } + + return data; +} + +export async function updateProfile(profile: ProfileSettings) { + const supabase = await createClient(); + const user_id = await getSession(); + + if (!user_id) { + throw new Error("User id invalid"); + } + + const { error } = await supabase + .from("profiles") + .update(profile) + .eq("id", user_id); + + if (error) { + throw error; + } +} diff --git a/src/actions/get-session.ts b/src/actions/get-session.ts index 6b2bc78..1b48ff3 100644 --- a/src/actions/get-session.ts +++ b/src/actions/get-session.ts @@ -1,6 +1,6 @@ "use server"; -import createClient from "@/lib/supa"; +import createClient from "@/actions/supa"; export default async function getSession() { const supabase = await createClient(); diff --git a/src/actions/login-server.ts b/src/actions/login-server.ts index c0d2063..ea358c6 100644 --- a/src/actions/login-server.ts +++ b/src/actions/login-server.ts @@ -1,6 +1,6 @@ "use server"; -import createClient from "@/lib/supa"; +import createClient from "@/actions/supa"; export default async function loginServer(email: string, password: string) { const supabase = await createClient(); diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts index 55b1387..5be2a90 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -49,86 +49,3 @@ export async function GET(req: NextRequest) { return NextResponse.json({ message: "ok", ...auth }); } - -/** - * POST /api/auth - * Called by the client to request a temporary relay token for a DM server. - * - * Body: { server_id: string } - * Header: Authorization: Bearer - */ -export async function POST(req: NextRequest) { - const { server_id } = await req.json(); - const authHeader = req.cookies.get("token"); - - if (!server_id) - return NextResponse.json( - { message: "Missing server_id in the json body" }, - { status: StatusCodes.BAD_REQUEST } - ); - - if (!authHeader) - return NextResponse.json( - { message: "Missing Supabase Authorization header" }, - { status: StatusCodes.BAD_REQUEST } - ); - - const { - data: { user }, - error, - } = await supabase.auth.getUser(authHeader.value); - - if (error || !user) { - return NextResponse.json( - { message: "Invalid Supabase token" }, - { status: StatusCodes.UNAUTHORIZED } - ); - } - - const { data: relayToken, error: errorRelay } = await supabase - .from("server_auth") - .insert({ - user_id: user.id, - server_id, - }) - .select() - .maybeSingle(); - - if (!relayToken || errorRelay) - return NextResponse.json( - { message: "Failed to create relay token" }, - { status: StatusCodes.INTERNAL_SERVER_ERROR } - ); - - return NextResponse.json({ message: "ok", token: relayToken.key }); -} - -/** - * PUT /api/auth - * Called by the client to refresh the access token. - */ -export async function PUT(req: NextRequest) { - const refresh_token = req.cookies.get("refresh_token")?.value; - - if (!refresh_token) - return NextResponse.json( - { message: "Missing refresh_token cookie" }, - { status: StatusCodes.BAD_REQUEST } - ); - - const session = await supabase.auth.refreshSession({ refresh_token }); - - if (session.error) - return NextResponse.json( - { - message: session.error.message, - }, - { status: StatusCodes.INTERNAL_SERVER_ERROR } - ); - - return NextResponse.json({ - message: "ok", - access_token: session.data.session?.access_token, - refresh_token: session.data.session?.refresh_token, - }); -} diff --git a/src/app/api/profile/route.ts b/src/app/api/profile/route.ts deleted file mode 100644 index dcf9960..0000000 --- a/src/app/api/profile/route.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { StatusCodes } from "http-status-codes"; -import { NextRequest, NextResponse } from "next/server"; -import { supabase } from "../supa"; -import { ProfileSettings } from "@/types/settings"; - -/** - * POST /api/profile - * body { username, display_name, avatar_url } - */ -export async function POST(req: NextRequest) { - const url = new URL(req.url); - const token = req.cookies.get("token")?.value; - const { username, display_name, avatar_url, node_address } = - (await req.json()) as ProfileSettings; - - if (!token) - return NextResponse.json( - { message: "Token cookie not found" }, - { status: StatusCodes.BAD_REQUEST } - ); - - if ( - !username || - !display_name || - avatar_url === undefined || - avatar_url === null || - node_address === undefined || - node_address === null - ) - return NextResponse.json( - { - message: - "Invalid body, requires username, display_name, avatar_url, node_address", - }, - { status: StatusCodes.BAD_REQUEST } - ); - - const { - data: { user }, - } = await supabase.auth.getUser(token); - - if (!user) - return NextResponse.json( - { message: "Invalid token" }, - { status: StatusCodes.NOT_FOUND } - ); - - const { error } = await supabase - .from("profiles") - .update({ - username, - display_name, - avatar_url, - node_address, - }) - .eq("id", user.id); - - if (error) { - return NextResponse.json( - { message: error.message }, - { status: StatusCodes.INTERNAL_SERVER_ERROR } - ); - } - - return NextResponse.json({ message: "ok" }); -} - -/** - * GET /api/profile?id= - * or cookie-based lookup - */ -export async function GET(req: NextRequest) { - const url = new URL(req.url); - const id = url.searchParams.get("id"); - const username = url.searchParams.get("username"); - const token = req.cookies.get("token")?.value; - - if (!token && !id && !username) - return NextResponse.json( - { message: "Query arg should be token or id or username" }, - { status: StatusCodes.BAD_REQUEST } - ); - - const { data: profile, error } = id - ? await supabase - .from("profiles") - .select("id, username, display_name, avatar_url, node_address") - .eq("id", id) - .maybeSingle() - : username - ? await supabase - .from("profiles") - .select("id, username, display_name, avatar_url, node_address") - .eq("username", username) - .maybeSingle() - : await supabase - .from("profiles") - .select("id, username, display_name, avatar_url, node_address") - .eq("id", (await supabase.auth.getUser(token)).data.user?.id) - .maybeSingle(); - - if (error) - return NextResponse.json( - { message: error.message }, - { status: StatusCodes.INTERNAL_SERVER_ERROR } - ); - - if (!profile) - return NextResponse.json( - { message: "Profile not found" }, - { status: StatusCodes.NOT_FOUND } - ); - - return NextResponse.json({ message: "ok", ...profile }); -} diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts index 11e57fd..e6b81b2 100644 --- a/src/app/api/user/route.ts +++ b/src/app/api/user/route.ts @@ -98,25 +98,3 @@ export async function GET(req: NextRequest) { return res; } - -/** - * DELETE /api/auth - * Body: { username } - */ -export async function DELETE(req: NextRequest) { - const { username } = await req.json(); - - // Delete both profile + auth user - const { data: profile } = await supabase - .from("profiles") - .select("id") - .eq("username", username) - .maybeSingle(); - - if (profile) { - await supabase.auth.admin.deleteUser(profile.id); - await supabase.from("profiles").delete().eq("id", profile.id); - } - - return NextResponse.json({ message: "ok" }); -} diff --git a/src/components/settings/Settings.tsx b/src/components/settings/Settings.tsx index 78b02f2..1547981 100644 --- a/src/components/settings/Settings.tsx +++ b/src/components/settings/Settings.tsx @@ -14,6 +14,7 @@ import { useRouter } from "next/navigation"; import axios from "axios"; import { useClientSettings } from "@/hooks/use-settings"; import { useState } from "react"; +import { updateProfile } from "@/actions/get-profile"; export default function Settings({ tab, @@ -55,7 +56,7 @@ export default function Settings({ setSettings={setProfileSettings} schema={PROFILE_SCHEMA} onSave={async () => { - await axios.post("/api/profile", profileSettings); + await updateProfile(profileSettings); }} /> diff --git a/src/hooks/use-app.ts b/src/hooks/use-app.ts index 141ada0..6ca9fe3 100644 --- a/src/hooks/use-app.ts +++ b/src/hooks/use-app.ts @@ -8,6 +8,7 @@ import { get, Response } from "@/lib/request"; import { Channel, Server } from "@/types/types"; import Cookies from "js-cookie"; import { toast } from "sonner"; +import { getProfileById } from "@/actions/get-profile"; export default function useApp(): App { const node = useRef(null); @@ -58,17 +59,13 @@ export default function useApp(): App { getUserById: async (id: string) => { if (profiles[id] !== undefined) return profiles[id]; - const onResponse = (res: Response) => { + try { + const user = await getProfileById(id); setProfiles((prev) => { - prev[id] = res.data as UserProfile; + prev[id] = user; return prev; }); - }; - - try { - const user = await get(`/api/profile/?id=${id}`, onResponse); - onResponse(user); - return user.data; + return user; } catch { return null; } diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 4f95497..aa703b6 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -5,6 +5,7 @@ import { get } from "./request"; import { toast } from "sonner"; import { MessageStore } from "@/hooks/use-messages"; import { IndicatorContext, ServerMessage } from "@/types/protocol"; +import { authRelay } from "@/actions/auth"; type AuthParams = { id: string; @@ -30,9 +31,7 @@ export default async function auth({ const ip = ((await get(`/api/server/${id}`)).data as any).address as string; console.log("Authenticating with server at:", ip); - const server_auth = ( - (await axios.post("/api/auth", { server_id: id })).data as any - ).token; + const server_auth = await authRelay(id); var name = id; diff --git a/src/lib/supa.ts b/src/lib/supa.ts deleted file mode 100644 index 5c7f41e..0000000 --- a/src/lib/supa.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { cookies } from "next/headers"; -import { createServerClient } from "@supabase/ssr"; - -export default async function createClient() { - const cookieStore = await cookies(); - - return createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY!, - { - cookies: { - get(name) { - return cookieStore.get(name)?.value; - }, - set(name, value) { - cookieStore.set(name, value); - }, - remove(name) { - cookieStore.delete(name); - }, - }, - } - ); -} From 8d9b60def052aeb98be5a963ea3ee8ea12c6ee77 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 23 Dec 2025 15:25:07 +0100 Subject: [PATCH 3/9] Working supabase structure --- src/actions/auth.ts | 29 +++++++++++++++++++++++++++++ src/actions/supa.ts | 24 ++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/actions/auth.ts create mode 100644 src/actions/supa.ts diff --git a/src/actions/auth.ts b/src/actions/auth.ts new file mode 100644 index 0000000..3b78532 --- /dev/null +++ b/src/actions/auth.ts @@ -0,0 +1,29 @@ +"use server"; + +import createClient from "@/actions/supa"; +import getSession from "./get-session"; + +/** + * Request a temporary relay token for a DM node or server. + */ +export async function authRelay(server_id: string) { + const supabase = await createClient(); + + const user_id = await getSession(); + + if (!user_id) { + throw new Error("Invalid user id"); + } + + const { data: relayToken, error: errorRelay } = await supabase + .from("server_auth") + .insert({ user_id: user_id, server_id }) + .select("key") + .maybeSingle(); + + if (!relayToken || errorRelay) { + throw new Error("Failed to create relay token"); + } + + return relayToken.key as string; +} diff --git a/src/actions/supa.ts b/src/actions/supa.ts new file mode 100644 index 0000000..5c7f41e --- /dev/null +++ b/src/actions/supa.ts @@ -0,0 +1,24 @@ +import { cookies } from "next/headers"; +import { createServerClient } from "@supabase/ssr"; + +export default async function createClient() { + const cookieStore = await cookies(); + + return createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SERVICE_ROLE_KEY!, + { + cookies: { + get(name) { + return cookieStore.get(name)?.value; + }, + set(name, value) { + cookieStore.set(name, value); + }, + remove(name) { + cookieStore.delete(name); + }, + }, + } + ); +} From 6d4cbbf03b8ae31b8a8858e5ac48e394aecfc594 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 23 Dec 2025 15:31:44 +0100 Subject: [PATCH 4/9] Server supabase structure --- src/actions/get-server.ts | 19 +++++++++++++++++++ src/app/api/server/[id]/route.ts | 27 --------------------------- src/components/app/SidebarServers.tsx | 3 ++- src/hooks/use-app.ts | 7 +++---- src/lib/auth.ts | 5 ++--- 5 files changed, 26 insertions(+), 35 deletions(-) create mode 100644 src/actions/get-server.ts delete mode 100644 src/app/api/server/[id]/route.ts diff --git a/src/actions/get-server.ts b/src/actions/get-server.ts new file mode 100644 index 0000000..d9c32f3 --- /dev/null +++ b/src/actions/get-server.ts @@ -0,0 +1,19 @@ +"use server"; + +import createClient from "@/actions/supa"; + +export async function getServerById(id: string) { + const supabase = await createClient(); + + const { error, data: server } = await supabase + .from("servers") + .select("id, name, created_at, owner, address, icon_url") + .eq("id", id) + .maybeSingle(); + + if (error) throw error; + + if (!server) throw "Server is null"; + + return server; +} diff --git a/src/app/api/server/[id]/route.ts b/src/app/api/server/[id]/route.ts deleted file mode 100644 index edbe085..0000000 --- a/src/app/api/server/[id]/route.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { supabase } from "../../supa"; -import { StatusCodes } from "http-status-codes"; - -/** - * GET /api/server/ - */ -export async function GET( - req: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { - const { id } = await params; - - const { data: profile } = await supabase - .from("servers") - .select("id, name, created_at, owner, address, icon_url") - .eq("id", id) - .maybeSingle(); - - if (!profile) - return NextResponse.json( - { message: "not found" }, - { status: StatusCodes.NOT_FOUND } - ); - - return NextResponse.json({ message: "ok", ...profile }); -} diff --git a/src/components/app/SidebarServers.tsx b/src/components/app/SidebarServers.tsx index fb69aa1..c6a4502 100644 --- a/src/components/app/SidebarServers.tsx +++ b/src/components/app/SidebarServers.tsx @@ -23,6 +23,7 @@ import App from "@/types/app"; import ProfilePicture from "../ProfilePicture"; import SettingsDialog from "../settings/SettingsDialog"; import { useRouter } from "next/navigation"; +import { getServerById } from "@/actions/get-server"; export default function SidebarServers({ app }: { app: App }) { const [newServer, setNewServer] = useState(""); @@ -31,7 +32,7 @@ export default function SidebarServers({ app }: { app: App }) { const handleAddServer = async () => { if (!newServer || Object.hasOwn(app.servers, newServer)) return; try { - const server = (await get(`/api/server/${newServer}`)).data as Server; + const server = await getServerById(newServer); app.setServers((prev) => ({ ...prev, [server.id]: server })); Cookies.set("servers", Object.keys(app.servers).join(",")); setNewServer(""); diff --git a/src/hooks/use-app.ts b/src/hooks/use-app.ts index 6ca9fe3..58a04d3 100644 --- a/src/hooks/use-app.ts +++ b/src/hooks/use-app.ts @@ -9,6 +9,7 @@ import { Channel, Server } from "@/types/types"; import Cookies from "js-cookie"; import { toast } from "sonner"; import { getProfileById } from "@/actions/get-profile"; +import { getServerById } from "@/actions/get-server"; export default function useApp(): App { const node = useRef(null); @@ -30,11 +31,9 @@ export default function useApp(): App { .split(",") .map((x) => x.trim()) .filter((id) => id) - .map((id) => get(`/api/server/${id}`)) + .map((id) => getServerById(id)) ) - .then((o) => - setServers(Object.fromEntries(o.map((i) => [i.data.id, i.data]))) - ) + .then((o) => setServers(Object.fromEntries(o.map((i) => [i.id, i])))) .catch(toast.error); }, []); diff --git a/src/lib/auth.ts b/src/lib/auth.ts index aa703b6..db007dd 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,11 +1,10 @@ import { Message, Server } from "@/types/types"; -import axios from "axios"; import { Dispatch, RefObject, SetStateAction } from "react"; -import { get } from "./request"; import { toast } from "sonner"; import { MessageStore } from "@/hooks/use-messages"; import { IndicatorContext, ServerMessage } from "@/types/protocol"; import { authRelay } from "@/actions/auth"; +import { getServerById } from "@/actions/get-server"; type AuthParams = { id: string; @@ -28,7 +27,7 @@ export default async function auth({ if (!id) return; - const ip = ((await get(`/api/server/${id}`)).data as any).address as string; + const ip = (await getServerById(id)).address; console.log("Authenticating with server at:", ip); const server_auth = await authRelay(id); From 0c5817e42c513115052c901b653d8c1978ff5df1 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 23 Dec 2025 15:50:14 +0100 Subject: [PATCH 5/9] User supabase structure --- src/actions/login-server.ts | 18 ------ src/actions/user.ts | 78 +++++++++++++++++++++++++ src/app/api/user/route.ts | 100 --------------------------------- src/app/auth/CreateAccount.tsx | 15 +---- src/app/auth/Login.tsx | 4 +- 5 files changed, 83 insertions(+), 132 deletions(-) delete mode 100644 src/actions/login-server.ts create mode 100644 src/actions/user.ts delete mode 100644 src/app/api/user/route.ts diff --git a/src/actions/login-server.ts b/src/actions/login-server.ts deleted file mode 100644 index ea358c6..0000000 --- a/src/actions/login-server.ts +++ /dev/null @@ -1,18 +0,0 @@ -"use server"; - -import createClient from "@/actions/supa"; - -export default async function loginServer(email: string, password: string) { - const supabase = await createClient(); - - const { data, error } = await supabase.auth.signInWithPassword({ - email, - password, - }); - - if (error) { - throw new Error(error.message); - } - - return data.user; -} diff --git a/src/actions/user.ts b/src/actions/user.ts new file mode 100644 index 0000000..4d72e63 --- /dev/null +++ b/src/actions/user.ts @@ -0,0 +1,78 @@ +"use server"; + +import createClient from "@/actions/supa"; + +/** + * Register a new user + */ +export async function registerUser( + username: string, + email: string, + password: string +) { + if (!username || !email || !password) { + throw new Error("Missing required fields"); + } + + const supabase = await createClient(); + + // Check if username is already taken + const { data: existing } = await supabase + .from("profiles") + .select("username") + .eq("username", username) + .maybeSingle(); + + if (existing) { + throw new Error("Username already used"); + } + + // Sign up user + const { data: authData, error: authError } = await supabase.auth.signUp({ + email, + password, + }); + + if (authError || !authData.user) { + throw new Error(authError?.message ?? "User registration failed"); + } + + // Insert profile + await supabase.from("profiles").insert({ + id: authData.user.id, + username, + display_name: username, + avatar_url: "", + node_address: "", + }); + + return { + message: "User registered. Please check your email to verify your account.", + user_id: authData.user.id, + email_sent: true, + }; +} + +/** + * Login a user + */ +export async function loginUser(email: string, password: string) { + if (!email || !password) { + throw new Error("Email and password are required"); + } + + const supabase = await createClient(); + + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + + if (error || !data.session) { + throw new Error("Invalid credentials"); + } + + const { session } = data; + + return session.user?.id; +} diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts deleted file mode 100644 index e6b81b2..0000000 --- a/src/app/api/user/route.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { StatusCodes } from "http-status-codes"; -import { supabase } from "../supa"; - -/** - * POST /api/user - * Register new user - * Body: { username, email, password } - */ -export async function POST(req: NextRequest) { - const { username, email, password } = await req.json(); - - if (!email || !password || !username) - return NextResponse.json( - { message: "Missing required fields" }, - { status: StatusCodes.BAD_REQUEST } - ); - - const { data: existing } = await supabase - .from("profiles") - .select("username") - .eq("username", username) - .maybeSingle(); - - if (existing) - return NextResponse.json( - { message: "Username already used" }, - { status: StatusCodes.CONFLICT } - ); - - const { data: authData, error: authError } = await supabase.auth.signUp({ - email, - password, - }); - - if (authError) - return NextResponse.json( - { message: authError.message }, - { status: StatusCodes.INTERNAL_SERVER_ERROR } - ); - - if (!authData.user) - return NextResponse.json( - { message: "User is null" }, - { status: StatusCodes.INTERNAL_SERVER_ERROR } - ); - - await supabase.from("profiles").insert({ - id: authData.user.id, - username, - display_name: username, - avatar_url: "", - node_address: "", - }); - - return NextResponse.json({ - message: "User registered. Please check your email to verify your account.", - user_id: authData.user?.id, - email_sent: true, - }); -} - -/** - * GET /api/user - * Login user - * Params: { username, password } - */ -export async function GET(req: NextRequest) { - const params = new URL(req.url).searchParams; - const [email, password] = [params.get("email"), params.get("password")]; - - if (!email || !password) - return NextResponse.json( - { message: "Params email and password are required" }, - { status: StatusCodes.BAD_REQUEST } - ); - - const { data, error } = await supabase.auth.signInWithPassword({ - email, - password, - }); - - if (error) - return NextResponse.json( - { message: "Invalid credentials" }, - { status: StatusCodes.UNAUTHORIZED } - ); - - const { session } = data; - const res = NextResponse.json({ - message: "ok", - access_token: session?.access_token, - refresh_token: session?.refresh_token, - user_id: session?.user?.id, - }); - - res.cookies.set("token", session?.access_token ?? "", { httpOnly: true }); - - return res; -} diff --git a/src/app/auth/CreateAccount.tsx b/src/app/auth/CreateAccount.tsx index d1c63a8..6fa1286 100644 --- a/src/app/auth/CreateAccount.tsx +++ b/src/app/auth/CreateAccount.tsx @@ -10,6 +10,7 @@ import { Label } from "@/components/ui/label"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; import Cookies from "js-cookie"; +import { registerUser } from "@/actions/user"; export default function CreateAccount() { const [username, setUsername] = useState(""); @@ -58,8 +59,6 @@ export default function CreateAccount() { }; const signup = async () => { - localStorage.removeItem("chat-messages"); - localStorage.removeItem("server-messages"); setFeedback(undefined); const validationError = validateInput(); if (validationError) { @@ -69,16 +68,8 @@ export default function CreateAccount() { try { setLoading(true); - const { data } = await axios.post("/api/user", { - username, - password, - email, - }); - const response = data as { - message: string; - email_sent: boolean; - user_id: string; - }; + const response = await registerUser(username, email, password); + if (response.email_sent) { toast.info( "We sent you a verification email. Please verify to continue." diff --git a/src/app/auth/Login.tsx b/src/app/auth/Login.tsx index fb20e6c..d3fbc2f 100644 --- a/src/app/auth/Login.tsx +++ b/src/app/auth/Login.tsx @@ -5,7 +5,7 @@ import { Input } from "@/components/ui/input"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import loginServer from "@/actions/login-server"; +import { loginUser } from "@/actions/user"; type Feedback = { kind: "error" | "info"; message: string }; @@ -19,7 +19,7 @@ export default function Login() { const login = async () => { setFeedback(undefined); try { - const res = await loginServer(email, password); + await loginUser(email, password); setFeedback({ kind: "info", From baa18fe9f7942dbdcfc65e75cf11ba98930d637b Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 23 Dec 2025 15:57:21 +0100 Subject: [PATCH 6/9] Improved indicators --- src/app/chat/[id]/page.tsx | 4 +++- src/components/channel/ChannelView.tsx | 5 ++++- src/lib/auth.ts | 11 ++--------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx index 58a8bc3..351f38f 100644 --- a/src/app/chat/[id]/page.tsx +++ b/src/app/chat/[id]/page.tsx @@ -68,7 +68,9 @@ export default function DMs() { break; } }} - indicators={indicators} + indicators={indicators.filter( + (indicator) => indicator.indicator.params.user_id !== app.profile?.id + )} /> ) : ( diff --git a/src/components/channel/ChannelView.tsx b/src/components/channel/ChannelView.tsx index bd78cfa..8871f8f 100644 --- a/src/components/channel/ChannelView.tsx +++ b/src/components/channel/ChannelView.tsx @@ -33,7 +33,10 @@ export default function ChannelView({ while (serverRef.current?.CONNECTING) {} serverRef.current?.send(JSON.stringify(req)); }} - indicators={indicators} + indicators={indicators.filter( + (indicator) => + indicator.indicator.params.user_id !== app.profile?.id + )} /> ); diff --git a/src/lib/auth.ts b/src/lib/auth.ts index db007dd..a887b92 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -69,15 +69,8 @@ export default async function auth({ switch (msg.type) { case "authenticated": - // ws.send( - // JSON.stringify({ - // type: "load_chunk", - // params: { - // channel_id: "general", - // chunk_id: 0, - // }, - // }) - // ); + if (!onIndicator) break; + msg.params.indicators.forEach(onIndicator); break; case "message_create": From cd9612500c36ba006f70c4c45e26e29e41e912dc Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 23 Dec 2025 17:18:02 +0100 Subject: [PATCH 7/9] Fixed favicon error --- src/app/favicon.ico | Bin 105895 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/app/favicon.ico diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index 0cf8212c282a8dd11f4c6024a517d4544b359f7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105895 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 N|0 Date: Tue, 23 Dec 2025 17:21:01 +0100 Subject: [PATCH 8/9] Fixed unsafe warning --- src/actions/get-session.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/actions/get-session.ts b/src/actions/get-session.ts index 1b48ff3..1ef26f8 100644 --- a/src/actions/get-session.ts +++ b/src/actions/get-session.ts @@ -2,13 +2,15 @@ import createClient from "@/actions/supa"; -export default async function getSession() { +export default async function getSessionUserId() { const supabase = await createClient(); - const { error, data } = await supabase.auth.getSession(); + const { data, error } = await supabase.auth.getUser(); - if (error) { - throw error; - } + if (error) throw error; - return data.session?.user.id; + const user = data.user; + + if (!user) return null; + + return user.id; } From 67acee4b72881bfe96d63ca6850bf3867d3b1204 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 23 Dec 2025 18:15:27 +0100 Subject: [PATCH 9/9] Fixed a few bugs --- src/app/chat/[id]/page.tsx | 41 ++++++++++-------- src/components/app/AppLayout.tsx | 61 +++++++++++++++------------ src/components/channel/MessageBox.tsx | 22 +++++++--- 3 files changed, 72 insertions(+), 52 deletions(-) diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx index 351f38f..7053bb2 100644 --- a/src/app/chat/[id]/page.tsx +++ b/src/app/chat/[id]/page.tsx @@ -9,6 +9,7 @@ import auth from "@/lib/auth"; import useAsync from "@/hooks/use-async"; import { Button } from "@/components/ui/button"; import useIndicators from "@/hooks/use-indicators"; +import { useEffectOnceWhenReady } from "@/hooks/use-once"; export default function DMs() { const { id } = useParams<{ id: string }>(); @@ -18,25 +19,29 @@ export default function DMs() { const router = useRouter(); const { indicators, addIndicator } = useIndicators(); - useEffect(() => { - async function connectTargetNode() { - if (!target) return; - auth({ - id: target.node_address, - wsRef: targetNode, - messageStore: app.privateMessages, - onIndicator: addIndicator, + useEffectOnceWhenReady( + () => { + async function connectTargetNode() { + if (!target) return; + auth({ + id: target.node_address, + wsRef: targetNode, + messageStore: app.privateMessages, + onIndicator: addIndicator, + }); + } + + connectTargetNode(); + + app.setCurrentChannel({ + id: id, + name: target?.display_name || id, + kind: "text", }); - } - - connectTargetNode(); - - app.setCurrentChannel({ - id: id, - name: target?.display_name || id, - kind: "text", - }); - }, [target]); + }, + [target], + [(v) => v] + ); return loading || target ? ( diff --git a/src/components/app/AppLayout.tsx b/src/components/app/AppLayout.tsx index 77de524..164250e 100644 --- a/src/components/app/AppLayout.tsx +++ b/src/components/app/AppLayout.tsx @@ -13,6 +13,7 @@ import SidebarServers from "./SidebarServers"; import SidebarDMs from "./SidebarDMs"; import SidebarChannels from "./SidebarChannels"; import DMItem from "./DMItem"; +import { useEffectOnceWhenReady } from "@/hooks/use-once"; export default function AppLayout({ children, @@ -28,36 +29,42 @@ export default function AppLayout({ const router = useRouter(); const isMobile = useIsMobile(); - useEffect(() => { - if (!app.profile?.node_address) return; + useEffectOnceWhenReady( + () => { + if (!app.profile?.node_address) return; - auth({ - id: app.profile.node_address, - wsRef: app.node, - onNewMessage: (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", - } - ); + auth({ + id: app.profile.node_address, + wsRef: app.node, + onNewMessage: (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); - }, - messageStore: app.privateMessages, - }); + if (onNewMessage) onNewMessage(m); + }, + messageStore: app.privateMessages, + }); - app.setProfiles((prev) => { - if (app.profile) prev[app.profile.id] = app.profile; - return prev; - }); - }, [chatWith, app.profile]); + app.setProfiles((prev) => { + if (app.profile) prev[app.profile.id] = app.profile; + return prev; + }); + }, + [chatWith, app.profile], + [undefined, undefined] + ); return (
diff --git a/src/components/channel/MessageBox.tsx b/src/components/channel/MessageBox.tsx index 22c438f..110608c 100644 --- a/src/components/channel/MessageBox.tsx +++ b/src/components/channel/MessageBox.tsx @@ -27,6 +27,7 @@ import { } from "@/components/ui/context-menu"; import { ClientMessage, IndicatorContext } from "@/types/protocol"; import { AnimatePresence, motion } from "framer-motion"; +import AsyncValue from "../AsyncValue"; function MessageContainer({ message, @@ -429,13 +430,20 @@ export default function MessageBox({ exit={{ opacity: 0, y: -10 }} transition={{ duration: 0.3 }} > - {indicators - .map( - (indicator) => - app.profiles[indicator.indicator.params.user_id] - ?.display_name ?? null - ) - .join(", ")} + + await Promise.all( + indicators.map((indicator) => + app.getUserById(indicator.indicator.params.user_id) + ) + ) + } + render={({ value: indicators }) => + indicators + ?.map((indicator) => indicator?.display_name) + .join(", ") + } + /> {indicators.length > 0 && " is typing"} )}