From 6868894a1bb73c7896dbd807e7d9907e141bc2be Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 16 Oct 2025 21:47:56 +0200 Subject: [PATCH 1/7] Simple UI fix --- src/components/MessageBox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.tsx index d1cc086..9c72b28 100644 --- a/src/components/MessageBox.tsx +++ b/src/components/MessageBox.tsx @@ -156,12 +156,12 @@ export default function MessageBox({ )} -
+
{messages.toReversed().map((msg) => ( ))}
-
+
Date: Fri, 17 Oct 2025 10:34:57 +0200 Subject: [PATCH 2/7] Better auth --- src/app/api/user/route.ts | 55 +++----- src/app/api/verify/route.ts | 40 ++++++ src/components/Sidebar.tsx | 4 +- src/components/auth/CreateAccount.tsx | 184 +++++++++++++++++--------- src/components/auth/Login.tsx | 16 +-- src/hooks/get-user.ts | 6 - 6 files changed, 186 insertions(+), 119 deletions(-) create mode 100644 src/app/api/verify/route.ts diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts index 40f25fe..11e57fd 100644 --- a/src/app/api/user/route.ts +++ b/src/app/api/user/route.ts @@ -5,10 +5,10 @@ import { supabase } from "../supa"; /** * POST /api/user * Register new user - * Body: { name, username, email, password } + * Body: { username, email, password } */ export async function POST(req: NextRequest) { - const { name, username, email, password } = await req.json(); + const { username, email, password } = await req.json(); if (!email || !password || !username) return NextResponse.json( @@ -28,29 +28,35 @@ export async function POST(req: NextRequest) { { status: StatusCodes.CONFLICT } ); - const { data: authData, error: authError } = - await supabase.auth.admin.createUser({ - email, - password, - email_confirm: true, - user_metadata: { name, username }, - }); + const { data: authData, error: authError } = await supabase.auth.signUp({ + email, + password, + }); if (authError) return NextResponse.json( { message: authError.message }, - { status: StatusCodes.BAD_REQUEST } + { 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, - name, username, + display_name: username, + avatar_url: "", + node_address: "", }); return NextResponse.json({ - message: "ok", - user_id: authData.user.id, + message: "User registered. Please check your email to verify your account.", + user_id: authData.user?.id, + email_sent: true, }); } @@ -61,30 +67,14 @@ export async function POST(req: NextRequest) { */ export async function GET(req: NextRequest) { const params = new URL(req.url).searchParams; - const [username, password] = [params.get("username"), params.get("password")]; + const [email, password] = [params.get("email"), params.get("password")]; - if (!username || !password) + if (!email || !password) return NextResponse.json( - { message: "Params username and password are required" }, + { message: "Params email and password are required" }, { status: StatusCodes.BAD_REQUEST } ); - let email = username; - if (!username.includes("@")) { - const { data } = await supabase - .from("profiles") - .select("email") - .eq("email", username) - .maybeSingle(); - email = data?.email; - } - - if (!email) - return NextResponse.json( - { message: "Invalid username" }, - { status: StatusCodes.UNAUTHORIZED } - ); - const { data, error } = await supabase.auth.signInWithPassword({ email, password, @@ -104,7 +94,6 @@ export async function GET(req: NextRequest) { user_id: session?.user?.id, }); - // Optionally set cookie res.cookies.set("token", session?.access_token ?? "", { httpOnly: true }); return res; diff --git a/src/app/api/verify/route.ts b/src/app/api/verify/route.ts new file mode 100644 index 0000000..223f423 --- /dev/null +++ b/src/app/api/verify/route.ts @@ -0,0 +1,40 @@ +import { NextRequest, NextResponse } from "next/server"; +import { supabase } from "../supa"; +import { StatusCodes } from "http-status-codes"; + +export async function GET(req: NextRequest) { + const email = req.nextUrl.searchParams.get("email"); + const password = req.nextUrl.searchParams.get("password"); + + if (!email || !password) + return NextResponse.json( + { message: "Missing email or password" }, + { status: StatusCodes.BAD_REQUEST } + ); + + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + + if (error) + return NextResponse.json( + { message: error.message }, + { status: StatusCodes.INTERNAL_SERVER_ERROR } + ); + + if (!data) + return NextResponse.json( + { verified: false, message: "User not found" }, + { status: StatusCodes.NOT_FOUND } + ); + + const res = NextResponse.json({ + verified: !!data.user.email_confirmed_at, + user_id: data.user.id, + access_token: data.session.access_token, + refresh_token: data.session.refresh_token, + }); + + return res; +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index bd5fefa..66e0830 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -106,9 +106,7 @@ export default function AppSidebar({ ]; setServers(updatedServers); - Cookies.set("servers", updatedServers.map((x) => x.join("@")).join(","), { - expires: 7, - }); + Cookies.set("servers", updatedServers.map((x) => x.join("@")).join(",")); setNewServer({ name: "", ip: "" }); }; diff --git a/src/components/auth/CreateAccount.tsx b/src/components/auth/CreateAccount.tsx index e0055de..7a0e77d 100644 --- a/src/components/auth/CreateAccount.tsx +++ b/src/components/auth/CreateAccount.tsx @@ -3,12 +3,13 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import axios from "axios"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import Cookies from "js-cookie"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { CardContent, CardHeader, CardTitle } from "../ui/card"; import { Label } from "../ui/label"; +import { toast } from "sonner"; export default function CreateAccount() { const [username, setUsername] = useState(""); @@ -20,9 +21,40 @@ export default function CreateAccount() { const [feedback, setFeedback] = useState< undefined | { kind: "error" | "info"; message: string } >(); + const [verify, setVerify] = useState(); const router = useRouter(); + // Verify interval + useEffect(() => { + if (verify) { + const i = setInterval(async () => { + const { data } = await axios.get("/api/verify", { + params: { email, password }, + }); + const response = data as { + verified: boolean; + user_id: string; + access_token?: string; + refresh_token?: string; + }; + if (response.verified) { + clearInterval(i); + setFeedback({ + message: "Email verified, redirecting...", + kind: "info", + }); + if (response.access_token) { + Cookies.set("token", response.access_token); + setTimeout(() => router.push("/chat"), 500); + } else { + console.log(response); + } + } + }, 8000); + } + }, [verify]); + const validateInput = (): | { kind: "error" | "info"; message: string } | undefined => { @@ -36,7 +68,7 @@ export default function CreateAccount() { return { kind: "error", message: - "Username must be 3–20 characters and only letters, numbers, or underscores.", + "Username must be 3-20 characters and only letters, numbers, or underscores.", }; } if (password.length < 6) { @@ -67,11 +99,25 @@ export default function CreateAccount() { try { setLoading(true); - const res = await axios.post("/api/user", { username, password, email }); - Cookies.set("token", (res.data as any).token); - - setFeedback({ kind: "info", message: "Account created! Redirecting..." }); - setTimeout(() => router.push("/chat"), 1000); + const { data } = await axios.post("/api/user", { + username, + password, + email, + }); + const response = data as { + message: string; + email_sent: boolean; + user_id: string; + }; + if (response.email_sent) { + toast.info( + "We sent you a verification email. Please verify to continue." + ); + setFeedback(undefined); + setVerify(response.user_id); + } else { + setFeedback({ message: response.message, kind: "error" }); + } } catch (err: any) { setFeedback({ kind: "error", @@ -87,69 +133,77 @@ export default function CreateAccount() { return ( <> - Create an Account + + {verify ? "Verify" : "Create an Account"} + - - Email - setEmail(e.target.value)} - placeholder="john@example.com" - type="email" - /> - - - Username - setUsername(e.target.value)} - placeholder="john_doe" - /> - - - Password - setPassword(e.target.value)} - type="password" - /> - - - Confirm Password - setConfirmPassword(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") signup(); - }} - type="password" - /> - + {verify ? ( + `We sent you a verification email at (${email}). Please verify to continue.` + ) : ( + <> + + Email + setEmail(e.target.value)} + placeholder="john@example.com" + type="email" + /> + + + Username + setUsername(e.target.value)} + placeholder="john_doe" + /> + + + Password + setPassword(e.target.value)} + type="password" + /> + + + Confirm Password + setConfirmPassword(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") signup(); + }} + type="password" + /> + - + - {feedback && ( -
- {feedback.message} -
+ {feedback && ( +
+ {feedback.message} +
+ )} + + + )} - -
); diff --git a/src/components/auth/Login.tsx b/src/components/auth/Login.tsx index 72075c9..ed410b3 100644 --- a/src/components/auth/Login.tsx +++ b/src/components/auth/Login.tsx @@ -6,18 +6,10 @@ import axios from "axios"; import { useState } from "react"; import Cookies from "js-cookie"; import { useRouter } from "next/navigation"; -import { Label } from "@/components/ui/label"; -import Link from "next/link"; -import { - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "../ui/card"; +import { CardContent, CardHeader, CardTitle } from "../ui/card"; export default function Login() { - const [username, setUsername] = useState(""); + const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [feedback, setFeedback] = useState< undefined | { kind: "error" | "info"; message: string } @@ -29,7 +21,7 @@ export default function Login() { setFeedback(undefined); try { const res = await axios.get("/api/user", { - params: { username, password }, + params: { email, password }, }); Cookies.set("token", (res.data as any).token); @@ -57,7 +49,7 @@ export default function Login() { Email - setUsername(e.target.value)} type="email" /> + setEmail(e.target.value)} type="email" /> Password diff --git a/src/hooks/get-user.ts b/src/hooks/get-user.ts index 9379a1e..c3770c5 100644 --- a/src/hooks/get-user.ts +++ b/src/hooks/get-user.ts @@ -17,12 +17,6 @@ export default function useUser() { useEffect(() => { async function fetchUser() { - const token = Cookies.get("token"); - if (!token) { - router.push("/login"); - return; - } - try { setUser((await get("/api/profile")).data); } catch (e: any) { From 56e8857c7c2ffc9845edd9f26d3d6c0438eb9350 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Fri, 17 Oct 2025 11:08:58 +0200 Subject: [PATCH 3/7] Better verify --- src/app/verify/page.tsx | 74 +++++++++++ src/components/auth/CreateAccount.tsx | 172 +++++++++++--------------- 2 files changed, 143 insertions(+), 103 deletions(-) create mode 100644 src/app/verify/page.tsx diff --git a/src/app/verify/page.tsx b/src/app/verify/page.tsx new file mode 100644 index 0000000..4d63ff3 --- /dev/null +++ b/src/app/verify/page.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import Cookies from "js-cookie"; +import axios from "axios"; + +export default function Verify() { + const [email, setEmail] = useState(); + const router = useRouter(); + const [verified, setVerified] = useState(false); + + useEffect(() => { + const email = Cookies.get("verify_temp_email"); + setEmail(email); + const password = Cookies.get("verify_temp_password"); + + const i = setInterval(async () => { + const { data } = await axios.get("/api/verify", { + params: { email, password }, + }); + const response = data as { + verified: boolean; + user_id: string; + access_token?: string; + refresh_token?: string; + }; + if (response.verified) { + clearInterval(i); + if (response.access_token) { + setVerified(true); + toast.info("Email verified, redirecting..."); + Cookies.set("token", response.access_token); + Cookies.remove("verify_temp_email"); + Cookies.remove("verify_temp_password"); + setTimeout(() => router.push("/chat"), 500); + } else { + console.log(response); + } + } + }, 8000); + }, []); + + return ( +
+ + + Verify Email + + {verified ? ( + "Successfully verified" + ) : ( + <> + We sent you a verification email at {email}. Please verify to + continue. + + )} + + + + Current status: {verified ? "Verified" : "Unverified"} + + +
+ ); +} diff --git a/src/components/auth/CreateAccount.tsx b/src/components/auth/CreateAccount.tsx index 7a0e77d..2552658 100644 --- a/src/components/auth/CreateAccount.tsx +++ b/src/components/auth/CreateAccount.tsx @@ -3,13 +3,13 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import axios from "axios"; -import { useEffect, useState } from "react"; -import Cookies from "js-cookie"; -import { useRouter } from "next/navigation"; +import { useState } from "react"; import Link from "next/link"; import { CardContent, CardHeader, CardTitle } from "../ui/card"; import { Label } from "../ui/label"; import { toast } from "sonner"; +import { useRouter } from "next/navigation"; +import Cookies from "js-cookie"; export default function CreateAccount() { const [username, setUsername] = useState(""); @@ -21,40 +21,8 @@ export default function CreateAccount() { const [feedback, setFeedback] = useState< undefined | { kind: "error" | "info"; message: string } >(); - const [verify, setVerify] = useState(); - const router = useRouter(); - // Verify interval - useEffect(() => { - if (verify) { - const i = setInterval(async () => { - const { data } = await axios.get("/api/verify", { - params: { email, password }, - }); - const response = data as { - verified: boolean; - user_id: string; - access_token?: string; - refresh_token?: string; - }; - if (response.verified) { - clearInterval(i); - setFeedback({ - message: "Email verified, redirecting...", - kind: "info", - }); - if (response.access_token) { - Cookies.set("token", response.access_token); - setTimeout(() => router.push("/chat"), 500); - } else { - console.log(response); - } - } - }, 8000); - } - }, [verify]); - const validateInput = (): | { kind: "error" | "info"; message: string } | undefined => { @@ -113,8 +81,14 @@ export default function CreateAccount() { toast.info( "We sent you a verification email. Please verify to continue." ); - setFeedback(undefined); - setVerify(response.user_id); + setFeedback({ + message: + "We sent you a verification email. Please verify to continue.", + kind: "info", + }); + Cookies.set("verify_temp_email", email); + Cookies.set("verify_temp_password", password); + router.push(`/verify`); } else { setFeedback({ message: response.message, kind: "error" }); } @@ -133,77 +107,69 @@ export default function CreateAccount() { return ( <> - - {verify ? "Verify" : "Create an Account"} - + Create an Account - {verify ? ( - `We sent you a verification email at (${email}). Please verify to continue.` - ) : ( - <> - - Email - setEmail(e.target.value)} - placeholder="john@example.com" - type="email" - /> - - - Username - setUsername(e.target.value)} - placeholder="john_doe" - /> - - - Password - setPassword(e.target.value)} - type="password" - /> - - - Confirm Password - setConfirmPassword(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") signup(); - }} - type="password" - /> - + + Email + setEmail(e.target.value)} + placeholder="john@example.com" + type="email" + /> + + + Username + setUsername(e.target.value)} + placeholder="john_doe" + /> + + + Password + setPassword(e.target.value)} + type="password" + /> + + + Confirm Password + setConfirmPassword(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") signup(); + }} + type="password" + /> + - + - {feedback && ( -
- {feedback.message} -
- )} - - - + {feedback && ( +
+ {feedback.message} +
)} + +
); From 8f43b7958b19126bd2f8d310729d3a6a79912802 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Fri, 17 Oct 2025 11:36:28 +0200 Subject: [PATCH 4/7] Fixed settings stuff --- src/app/api/profile/route.ts | 9 ++++++- src/components/Sidebar.tsx | 23 +++++++++++++---- src/components/settings/Settings.tsx | 10 ++++---- src/components/settings/SettingsDialog.tsx | 14 +++------- src/hooks/use-app.ts | 2 +- src/hooks/use-settings.ts | 30 +--------------------- src/types/app.ts | 4 +-- 7 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/app/api/profile/route.ts b/src/app/api/profile/route.ts index e45c823..46238aa 100644 --- a/src/app/api/profile/route.ts +++ b/src/app/api/profile/route.ts @@ -19,7 +19,14 @@ export async function POST(req: NextRequest) { { status: StatusCodes.BAD_REQUEST } ); - if (!username || !display_name || !avatar_url) + if ( + !username || + !display_name || + avatar_url === undefined || + avatar_url === null || + node_address === undefined || + node_address === null + ) return NextResponse.json( { message: diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 66e0830..9d1a313 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -36,6 +36,7 @@ import Link from "next/link"; import { get, Response } from "@/lib/request"; import { cn } from "@/lib/utils"; import App from "@/types/app"; +import { ProfileSettings } from "@/types/settings"; export default function AppSidebar({ children, @@ -201,7 +202,7 @@ export default function AppSidebar({
- +
@@ -243,6 +244,7 @@ export default function AppSidebar({ ?.avatar_url || "" } key={id} + app={app} /> ))} @@ -256,6 +258,7 @@ export default function AppSidebar({ id={app.profile?.id || "me"} avatar={app.profile?.avatar_url || ""} status="online" + app={app} settings /> @@ -283,12 +286,14 @@ function DMItem({ avatar, status, settings, + app, }: { name: string; id: string; avatar: string; status: string; settings?: boolean; + app: App; }) { return ( )} @@ -332,10 +338,12 @@ function ChannelIcon({ kind }: { kind: "text" | "voice" }) { function getUser( id: string, - userList: StringMap, - setUserList: React.Dispatch>> + userList: StringMap, + setUserList: React.Dispatch< + React.SetStateAction> + > ) { - if (userList[id]) return userList[id]; + if (userList[id] !== undefined) return userList[id]; const onResponse = (res: Response) => { setUserList((prev) => { @@ -346,5 +354,10 @@ function getUser( get(`/api/profile/?id=${id}`, onResponse) ?.then(onResponse) - .catch((e) => {}); + .catch((e) => { + setUserList((prev) => { + prev[id] = null; + return prev; + }); + }); } diff --git a/src/components/settings/Settings.tsx b/src/components/settings/Settings.tsx index 6842abf..78b02f2 100644 --- a/src/components/settings/Settings.tsx +++ b/src/components/settings/Settings.tsx @@ -1,6 +1,5 @@ "use client"; -import { useClientSettings } from "@/hooks/use-settings"; import { setClientSettings } from "@/lib/clientSettings"; import SettingSchema from "./SettingSchema"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -11,19 +10,20 @@ import { CLIENT_SCHEMA, ProfileSettings, } from "@/types/settings"; -import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import axios from "axios"; +import { useClientSettings } from "@/hooks/use-settings"; +import { useState } from "react"; export default function Settings({ tab, - profileSettings, - setProfileSettings, + profileSettings: ps, }: { tab?: string; profileSettings: ProfileSettings; - setProfileSettings: React.Dispatch>; }) { const [settings, setSettings] = useClientSettings(); + const [profileSettings, setProfileSettings] = useState(ps); const router = useRouter(); return ( diff --git a/src/components/settings/SettingsDialog.tsx b/src/components/settings/SettingsDialog.tsx index 4d98415..6ed0225 100644 --- a/src/components/settings/SettingsDialog.tsx +++ b/src/components/settings/SettingsDialog.tsx @@ -3,7 +3,6 @@ import { Button } from "../ui/button"; import { Dialog, DialogContent, - DialogDescription, DialogHeader, DialogTitle, DialogTrigger, @@ -11,18 +10,17 @@ import { import { cn } from "@/lib/utils"; import Settings from "./Settings"; import { ProfileSettings } from "@/types/settings"; -import { useProfileSettings } from "@/hooks/use-settings"; +import { useState } from "react"; export default function SettingsDialog({ className, tab, + profile, }: { className?: string; tab?: string; - profile?: ProfileSettings; + profile: ProfileSettings; }) { - const [profileSettings, setProfileSettings] = useProfileSettings(); - return ( @@ -41,11 +39,7 @@ export default function SettingsDialog({
- +
diff --git a/src/hooks/use-app.ts b/src/hooks/use-app.ts index fa4af8d..7285484 100644 --- a/src/hooks/use-app.ts +++ b/src/hooks/use-app.ts @@ -6,7 +6,7 @@ import useUser, { UserProfile } from "./get-user"; export default function useApp(): App { const node = useRef(null); - const [profiles, setProfiles] = useState>({}); + const [profiles, setProfiles] = useState>({}); const { messages, addMessage } = useMessages(); const [sidebarOpen, setSidebarOpen] = useState(true); const profile = useUser(); diff --git a/src/hooks/use-settings.ts b/src/hooks/use-settings.ts index 21bf861..a4e51f5 100644 --- a/src/hooks/use-settings.ts +++ b/src/hooks/use-settings.ts @@ -1,8 +1,7 @@ "use client"; import { defaultSettings, getClientSettings } from "@/lib/clientSettings"; -import { ProfileSettings, ClientSettings } from "@/types/settings"; -import axios from "axios"; +import { ClientSettings } from "@/types/settings"; import { Dispatch, SetStateAction, useEffect, useState } from "react"; export function useClientSettings(): [ @@ -17,30 +16,3 @@ export function useClientSettings(): [ return [settings, setSettings]; } - -export function useProfileSettings(): [ - ProfileSettings, - Dispatch> -] { - const [settings, setSettings] = useState({ - node_address: "", - username: "", - display_name: "", - avatar_url: "", - }); - - useEffect(() => { - async function fetchProfileSettings() { - try { - const res = await axios.get("/api/profile"); - setSettings(res.data as ProfileSettings); - } catch (error) { - console.error("Failed to fetch profile settings:", error); - } - } - - fetchProfileSettings(); - }, []); - - return [settings, setSettings]; -} diff --git a/src/types/app.ts b/src/types/app.ts index 3ddf70b..d6bcd04 100644 --- a/src/types/app.ts +++ b/src/types/app.ts @@ -9,8 +9,8 @@ export default interface App { node: RefObject; profile: UserProfile | null; - profiles: StringMap; - setProfiles: SetState>; + profiles: StringMap; + setProfiles: SetState>; messages: Message[]; addMessage: (v: Message) => void; From f417e92d81d6714afd18096ec2f6c34762754981 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Fri, 17 Oct 2025 11:49:14 +0200 Subject: [PATCH 5/7] Slightly better ui fixes --- src/components/MessageBox.tsx | 8 +++++--- src/components/ProfilePicture.tsx | 2 +- src/components/Sidebar.tsx | 2 +- src/hooks/is-mobile.ts | 20 ++++++++++++++++++++ src/hooks/use-app.ts | 2 +- 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 src/hooks/is-mobile.ts diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.tsx index 9c72b28..05b0e43 100644 --- a/src/components/MessageBox.tsx +++ b/src/components/MessageBox.tsx @@ -19,6 +19,7 @@ import { UserProfile } from "@/hooks/get-user"; import axios from "axios"; import ProfilePicture from "./ProfilePicture"; import App from "@/types/app"; +import { useIsMobile } from "@/hooks/use-mobile"; function MessageContainer({ message, app }: { message: Message; app: App }) { useEffect(() => { @@ -41,14 +42,14 @@ function MessageContainer({ message, app }: { message: Message; app: App }) { url={app.profiles[message.from]?.avatar_url} name={String( app.profiles[message.from] - ? app.profiles[message.from].display_name + ? app.profiles[message.from]?.display_name : message.from )} />
{app.profiles[message.from] - ? app.profiles[message.from].display_name + ? app.profiles[message.from]?.display_name : message.from}

@@ -142,13 +143,14 @@ export default function MessageBox({ messages: Message[]; }) { const [text, setText] = useState(""); + const isMobile = useIsMobile(); return (

{channelName && (
app.setSidebarOpen(true)} + onClick={() => isMobile && app.setSidebarOpen(true)} className="cursor-pointer flex" > diff --git a/src/components/ProfilePicture.tsx b/src/components/ProfilePicture.tsx index bd77b6a..7a17ac9 100644 --- a/src/components/ProfilePicture.tsx +++ b/src/components/ProfilePicture.tsx @@ -5,7 +5,7 @@ export default function ProfilePicture({ url, }: { name: string; - url: string; + url: string | undefined; }) { return ( diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 9d1a313..d7a27b5 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -269,7 +269,7 @@ export default function AppSidebar({ className="w-full h-full overflow-x-hidden" onClick={() => app.setSidebarOpen(false)} > -
+
{children}
diff --git a/src/hooks/is-mobile.ts b/src/hooks/is-mobile.ts new file mode 100644 index 0000000..d477190 --- /dev/null +++ b/src/hooks/is-mobile.ts @@ -0,0 +1,20 @@ +import { useEffect, useState } from "react"; + +export function useIsMobile() { + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const mediaQuery = window.matchMedia("(max-width: 767px)"); + const handleChange = () => setIsMobile(mediaQuery.matches); + + // Set initial value + handleChange(); + + // Listen for changes + mediaQuery.addEventListener("change", handleChange); + + return () => mediaQuery.removeEventListener("change", handleChange); + }, []); + + return isMobile; +} diff --git a/src/hooks/use-app.ts b/src/hooks/use-app.ts index 7285484..2ff6cf3 100644 --- a/src/hooks/use-app.ts +++ b/src/hooks/use-app.ts @@ -8,7 +8,7 @@ export default function useApp(): App { const node = useRef(null); const [profiles, setProfiles] = useState>({}); const { messages, addMessage } = useMessages(); - const [sidebarOpen, setSidebarOpen] = useState(true); + const [sidebarOpen, setSidebarOpen] = useState(false); const profile = useUser(); return { From ef0a7bcfcee9a745cf7d669767dce24a1efa38a5 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Fri, 17 Oct 2025 12:37:55 +0200 Subject: [PATCH 6/7] Message input multiline --- src/app/chat/page.tsx | 6 ++- src/components/MessageBox.tsx | 79 ++++++++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index 546180b..dd9e76f 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -2,16 +2,20 @@ import AppSidebar from "@/components/Sidebar"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useIsMobile } from "@/hooks/is-mobile"; import useApp from "@/hooks/use-app"; export default function Chat() { const app = useApp(); + const isMobile = useIsMobile(); return ( - Direct Messages + isMobile && app.setSidebarOpen(true)}> + Direct Messages + diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.tsx index 05b0e43..64d11ff 100644 --- a/src/components/MessageBox.tsx +++ b/src/components/MessageBox.tsx @@ -10,8 +10,8 @@ import { DropdownMenuContent, DropdownMenuTrigger, } from "./ui/dropdown-menu"; -import { ChevronLeftIcon, SmilePlusIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import { ChevronLeftIcon, SendIcon, SmilePlusIcon } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; import { format, isToday, isYesterday } from "date-fns"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -20,6 +20,7 @@ import axios from "axios"; import ProfilePicture from "./ProfilePicture"; import App from "@/types/app"; import { useIsMobile } from "@/hooks/use-mobile"; +import { Textarea } from "./ui/textarea"; function MessageContainer({ message, app }: { message: Message; app: App }) { useEffect(() => { @@ -144,6 +145,7 @@ export default function MessageBox({ }) { const [text, setText] = useState(""); const isMobile = useIsMobile(); + const inputRef = useRef(null); return (
@@ -164,21 +166,64 @@ export default function MessageBox({ ))}