From 5f9bb4c87707deeaff1ad28f6719918ba7dd43ba Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 12 Oct 2025 14:56:56 +0200 Subject: [PATCH] Working profile system --- src/app/api/profile/route.ts | 54 ++++++++++++++++++++++++++++++- src/app/settings/page.tsx | 21 ++++++------ src/components/MessageBox.tsx | 5 +-- src/components/ProfilePicture.tsx | 10 ++++-- src/components/auth/Login.tsx | 2 +- src/hooks/get-user.ts | 5 +-- src/hooks/use-settings.ts | 31 ++++++++++++++---- src/types/settings.ts | 14 +++++--- 8 files changed, 115 insertions(+), 27 deletions(-) diff --git a/src/app/api/profile/route.ts b/src/app/api/profile/route.ts index 195ef7f..f40a4ff 100644 --- a/src/app/api/profile/route.ts +++ b/src/app/api/profile/route.ts @@ -1,6 +1,58 @@ 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 } = + (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) + return NextResponse.json( + { message: "Invalid body, requires username, display_name, avatar_url" }, + { 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, + }) + .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= @@ -33,7 +85,7 @@ export async function GET(req: NextRequest) { const { data: profile } = await supabase .from("profiles") - .select("id, username, name, email") + .select("id, username, display_name, avatar_url") .eq("id", user_id) .maybeSingle(); diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index f5f32bb..b559245 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -1,19 +1,20 @@ "use client"; -import { useAccountSettings, useClientSettings } from "@/hooks/use-settings"; +import { useProfileSettings, useClientSettings } from "@/hooks/use-settings"; import { setClientSettings } from "@/lib/clientSettings"; import Settings from "./settings"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card } from "@/components/ui/card"; import { CardContent } from "@/components/ui/card"; -import { ACCOUNT_SCHEMA, CLIENT_SCHEMA } from "@/types/settings"; +import { PROFILE_SCHEMA, CLIENT_SCHEMA } from "@/types/settings"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { ChevronLeftCircle } from "lucide-react"; +import axios from "axios"; export default function SettingsPage() { const [settings, setSettings] = useClientSettings(); - const [accountSettings, setAccountSettings] = useAccountSettings(); + const [profileSettings, setProfileSettings] = useProfileSettings(); const router = useRouter(); return ( @@ -28,7 +29,7 @@ export default function SettingsPage() { Client Settings - Account Settings + Profile Settings @@ -45,14 +46,16 @@ export default function SettingsPage() { - + {}} + settings={profileSettings} + setSettings={setProfileSettings} + schema={PROFILE_SCHEMA} + onSave={async () => { + await axios.post("/api/profile", profileSettings); + }} /> diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.tsx index 6018ad9..3e61d9b 100644 --- a/src/components/MessageBox.tsx +++ b/src/components/MessageBox.tsx @@ -44,16 +44,17 @@ function MessageContainer({
{/*
*/}
{userList[message.from] - ? userList[message.from].username + ? userList[message.from].display_name : message.from}

diff --git a/src/components/ProfilePicture.tsx b/src/components/ProfilePicture.tsx index 389d354..bd77b6a 100644 --- a/src/components/ProfilePicture.tsx +++ b/src/components/ProfilePicture.tsx @@ -1,9 +1,15 @@ import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"; -export default function ProfilePicture({ name }: { name: string }) { +export default function ProfilePicture({ + name, + url, +}: { + name: string; + url: string; +}) { return ( - + {name[0]} ); diff --git a/src/components/auth/Login.tsx b/src/components/auth/Login.tsx index aed855d..72075c9 100644 --- a/src/components/auth/Login.tsx +++ b/src/components/auth/Login.tsx @@ -56,7 +56,7 @@ export default function Login() { - Username or email + Email setUsername(e.target.value)} type="email" /> diff --git a/src/hooks/get-user.ts b/src/hooks/get-user.ts index dcb7952..b3613e4 100644 --- a/src/hooks/get-user.ts +++ b/src/hooks/get-user.ts @@ -6,9 +6,10 @@ import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; export interface User { - id: number; + id: string; username: string; - email: string; + display_name: string; + avatar_url: string; } export default function useUser() { diff --git a/src/hooks/use-settings.ts b/src/hooks/use-settings.ts index eb17b47..c6b50e4 100644 --- a/src/hooks/use-settings.ts +++ b/src/hooks/use-settings.ts @@ -1,7 +1,7 @@ "use client"; import { defaultSettings, getClientSettings } from "@/lib/clientSettings"; -import { AccountSettings, ClientSettings } from "@/types/settings"; +import { ProfileSettings, ClientSettings } from "@/types/settings"; import { Dispatch, SetStateAction, useEffect, useState } from "react"; export function useClientSettings(): [ @@ -17,13 +17,32 @@ export function useClientSettings(): [ return [settings, setSettings]; } -export function useAccountSettings(): [ - AccountSettings, - Dispatch> +export function useProfileSettings(): [ + ProfileSettings, + Dispatch> ] { - const [settings, setSettings] = useState({ - node_ip: "node0.voxa.org", + const [settings, setSettings] = useState({ + node_address: "", + username: "", + display_name: "", + avatar_url: "", }); + useEffect(() => { + async function fetchProfileSettings() { + try { + const res = await fetch("/api/profile"); + if (res.ok) { + const data = (await res.json()) as ProfileSettings; + setSettings(data); + } + } catch (error) { + console.error("Failed to fetch profile settings:", error); + } + } + + fetchProfileSettings(); + }, []); + return [settings, setSettings]; } diff --git a/src/types/settings.ts b/src/types/settings.ts index 973f305..37d4374 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -4,16 +4,22 @@ export interface ClientSettings { theme: "light" | "dark"; } -export interface AccountSettings { - node_ip: string; +export interface ProfileSettings { + username: string; + display_name: string; + avatar_url: string; + node_address: string; } export const CLIENT_SCHEMA: SettingsSchema = { theme: ["light", "dark"], }; -export const ACCOUNT_SCHEMA: SettingsSchema = { - node_ip: new RegexSetting( +export const PROFILE_SCHEMA: SettingsSchema = { + username: "string", + display_name: "string", + avatar_url: "string", + node_address: new RegexSetting( "node2.voxa.org", /^(?:(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|(?:\d{1,3}\.){3}\d{1,3})$/ ),