Merge pull request #26 from kleo-dev/ui-ux-stuff

UI ux stuff
This commit is contained in:
Klesti Selimaj
2025-10-22 12:36:59 +02:00
committed by GitHub
16 changed files with 153 additions and 58 deletions
@@ -5,8 +5,8 @@ import { Input } from "@/components/ui/input";
import axios from "axios"; import axios from "axios";
import { useState } from "react"; import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { CardContent, CardHeader, CardTitle } from "../ui/card"; import { CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "../ui/label"; import { Label } from "@/components/ui/label";
import { toast } from "sonner"; import { toast } from "sonner";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
@@ -115,6 +115,7 @@ export default function CreateAccount() {
<span> <span>
Email Email
<Input <Input
autoComplete="email"
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="[email protected]" placeholder="[email protected]"
type="email" type="email"
@@ -123,6 +124,7 @@ export default function CreateAccount() {
<span> <span>
Username Username
<Input <Input
autoComplete="username"
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
placeholder="john_doe" placeholder="john_doe"
/> />
@@ -130,6 +132,7 @@ export default function CreateAccount() {
<span> <span>
Password Password
<Input <Input
autoComplete="new-password"
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
type="password" type="password"
/> />
@@ -137,6 +140,7 @@ export default function CreateAccount() {
<span> <span>
Confirm Password Confirm Password
<Input <Input
autoComplete="new-password"
onChange={(e) => setConfirmPassword(e.target.value)} onChange={(e) => setConfirmPassword(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") signup(); if (e.key === "Enter") signup();
@@ -6,7 +6,7 @@ import axios from "axios";
import { useState } from "react"; import { useState } from "react";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { CardContent, CardHeader, CardTitle } from "../ui/card"; import { CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function Login() { export default function Login() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
@@ -51,11 +51,16 @@ export default function Login() {
<CardContent className="gap-3 flex flex-col"> <CardContent className="gap-3 flex flex-col">
<span> <span>
Email Email
<Input onChange={(e) => setEmail(e.target.value)} type="email" /> <Input
autoComplete="username"
onChange={(e) => setEmail(e.target.value)}
type="email"
/>
</span> </span>
<span> <span>
Password Password
<Input <Input
autoComplete="current-password"
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") login(); if (e.key === "Enter") login();
@@ -1,14 +1,19 @@
"use client"; "use client";
import { Card } from "../ui/card"; import { Card } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import CreateAccount from "./CreateAccount"; import CreateAccount from "./CreateAccount";
import Login from "./Login"; import Login from "./Login";
import { useSearchParams } from "next/navigation";
export default function Auth() {
const params = useSearchParams();
const pt = params.get("t");
const tab = pt === "create" || pt === "login" ? pt : "create";
export default function Auth({ kind }: { kind: "login" | "create" }) {
return ( return (
<div className="h-screen flex items-center"> <div className="h-screen flex items-center">
<Tabs defaultValue={kind} className="mx-auto"> <Tabs defaultValue={tab} className="mx-auto">
<TabsList className="mx-auto"> <TabsList className="mx-auto">
<TabsTrigger value="login">Login</TabsTrigger> <TabsTrigger value="login">Login</TabsTrigger>
<TabsTrigger value="create">Create Account</TabsTrigger> <TabsTrigger value="create">Create Account</TabsTrigger>
+56
View File
@@ -0,0 +1,56 @@
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuItem,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { PlusCircle } from "lucide-react";
import { UserProfile } from "@/hooks/get-user";
import { useRouter } from "next/navigation";
import axios from "axios";
export default function StartNewDM() {
const [username, setUsername] = useState("");
const router = useRouter();
const handleStartDM = async () => {
const res = await axios.get("/api/profile", {
params: { username },
});
const user = res.data as UserProfile;
router.push(`/chat/${user.id}`);
setUsername("");
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="gap-2">
<PlusCircle className="w-4 h-4" />
Start New DM
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 p-3 space-y-2">
<Input
type="text"
placeholder="Enter username"
value={username}
onChange={(e) => setUsername(e.target.value)}
autoComplete="off"
spellCheck={false}
autoCorrect="off"
autoCapitalize="none"
/>
<Button onClick={handleStartDM} className="w-full">
Start
</Button>
</DropdownMenuContent>
</DropdownMenu>
);
}
+2 -4
View File
@@ -19,6 +19,7 @@ import {
ChevronRight, ChevronRight,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import StartNewDM from "./StartNewDM";
export default function ChatHub() { export default function ChatHub() {
const app = useApp(); const app = useApp();
@@ -122,10 +123,7 @@ export default function ChatHub() {
<div> <div>
<h2 className="text-xl font-semibold mb-4">Quick Actions</h2> <h2 className="text-xl font-semibold mb-4">Quick Actions</h2>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
<Button variant="outline" className="gap-2"> <StartNewDM />
<PlusCircle className="w-4 h-4" />
Start New DM
</Button>
<Button variant="outline" className="gap-2"> <Button variant="outline" className="gap-2">
<Users className="w-4 h-4" /> <Users className="w-4 h-4" />
Create Server Create Server
+35 -1
View File
@@ -102,7 +102,7 @@
--chart-3: oklch(0.769 0.188 70.08); --chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9); --chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439); --chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0); --sidebar: oklch(0.145 0 0);
--sidebar-foreground: oklch(0.985 0 0); --sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.985 0 0);
@@ -112,6 +112,40 @@
--sidebar-ring: oklch(0.556 0 0); --sidebar-ring: oklch(0.556 0 0);
} }
.black {
--background: oklch(0.050 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.150 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.150 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.150 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.215 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.200 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(0.3 0 0 / 70%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.150 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.150 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base { @layer base {
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
-7
View File
@@ -1,7 +0,0 @@
"use client";
import Auth from "@/components/auth/Auth";
export default function Login() {
return <Auth kind="login" />;
}
+5 -3
View File
@@ -24,10 +24,12 @@ export default function LandingPage() {
<Logo /> <Logo />
</motion.div> </motion.div>
<div className="flex gap-4"> <div className="flex gap-4">
<Button variant="ghost" onClick={() => router.push("/login")}> <Button variant="ghost" onClick={() => router.push("/auth?t=login")}>
Login Login
</Button> </Button>
<Button onClick={() => router.push("/signup")}>Get Started</Button> <Button onClick={() => router.push("/auth?t=create")}>
Get Started
</Button>
</div> </div>
</header> </header>
@@ -46,7 +48,7 @@ export default function LandingPage() {
with real-time messaging, secure channels, and seamless collaboration. with real-time messaging, secure channels, and seamless collaboration.
</p> </p>
<div className="flex gap-3"> <div className="flex gap-3">
<Button size="lg" onClick={() => router.push("/signup")}> <Button size="lg" onClick={() => router.push("/auth?t=create")}>
Get Started Get Started
</Button> </Button>
<Button <Button
-7
View File
@@ -1,7 +0,0 @@
"use client";
import Auth from "@/components/auth/Auth";
export default function SignUp() {
return <Auth kind="create" />;
}
+24 -8
View File
@@ -12,6 +12,7 @@ import { Server, Message } from "@/types/types";
import SidebarServers from "./SidebarServers"; import SidebarServers from "./SidebarServers";
import SidebarDMs from "./SidebarDMs"; import SidebarDMs from "./SidebarDMs";
import SidebarChannels from "./SidebarChannels"; import SidebarChannels from "./SidebarChannels";
import DMItem from "./DMItem";
export default function AppLayout({ export default function AppLayout({
children, children,
@@ -70,15 +71,30 @@ export default function AppLayout({
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"
)} )}
> >
{/* Left column — servers */} <div className="flex-shrink-0">
<SidebarServers app={app} /> <SidebarServers app={app} />
</div>
{/* Right column — DMs or server channels */} <div className="flex-1 min-w-0 flex flex-col bg-sidebar">
{server ? ( <div className="flex-1 min-h-0">
<SidebarChannels server={server} /> {server ? (
) : ( <SidebarChannels server={server} />
<SidebarDMs app={app} /> ) : (
)} <SidebarDMs app={app} />
)}
</div>
<footer className="mt-auto pb-3 px-3">
<DMItem
name={app.profile?.display_name || "Loading.."}
id={app.profile?.id || "me"}
avatar={app.profile?.avatar_url || ""}
status="online"
app={app}
settings
/>
</footer>
</div>
</div> </div>
{app.sidebarOpen && isMobile ? ( {app.sidebarOpen && isMobile ? (
+1 -1
View File
@@ -26,7 +26,7 @@ export function ChannelItem({ channel }: { channel: Channel }) {
export default function SidebarChannels({ server }: { server: Server }) { export default function SidebarChannels({ server }: { server: Server }) {
return ( return (
<div className="w-full flex flex-col bg-card/50"> <div className="w-full flex flex-col bg-sidebar">
<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>
</div> </div>
+2 -13
View File
@@ -46,7 +46,7 @@ export default function SidebarDMs({ app }: { app: App }) {
}, [query, app.messages, app.profile]); }, [query, app.messages, app.profile]);
return ( return (
<div className="w-full flex flex-col bg-card/50"> <div className="w-full flex flex-col bg-sidebar">
<div className="p-3 border-b flex items-center"> <div className="p-3 border-b flex items-center">
<div className="relative w-full"> <div className="relative w-full">
<Input <Input
@@ -76,7 +76,7 @@ export default function SidebarDMs({ app }: { app: App }) {
<div className="px-3 py-2"> <div className="px-3 py-2">
<Button <Button
variant="ghost" variant="ghost"
className="w-full justify-start gap-2 text-sm" className="w-full justify-start gap-2 text-sm truncate"
onClick={async () => { onClick={async () => {
const res = await axios.get("/api/profile", { const res = await axios.get("/api/profile", {
params: { username: query }, params: { username: query },
@@ -92,17 +92,6 @@ export default function SidebarDMs({ app }: { app: App }) {
)} )}
</div> </div>
</ScrollArea> </ScrollArea>
<footer className="mt-auto pb-3 px-3">
<DMItem
name={app.profile?.display_name || "Loading.."}
id={app.profile?.id || "me"}
avatar={app.profile?.avatar_url || ""}
status="online"
app={app}
settings
/>
</footer>
</div> </div>
); );
} }
+1 -1
View File
@@ -41,7 +41,7 @@ export default function SidebarServers({ app }: { app: App }) {
}; };
return ( return (
<div className="w-16 flex flex-col items-center gap-4 py-4 border-r"> <div className="w-16 flex flex-col items-center gap-4 py-4 border-r h-full">
<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"
+2 -2
View File
@@ -19,8 +19,8 @@ export default function useUser() {
async function fetchUser() { async function fetchUser() {
try { try {
setUser((await get("/api/profile", (r) => setUser(r.data))).data); setUser((await get("/api/profile", (r) => setUser(r.data))).data);
} catch (e: any) { } catch (e: any) {
router.push("/login"); router.push("/auth?t=login");
toast.error(`Error: ${e}`); toast.error(`Error: ${e}`);
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@ import { ClientSettings } from "@/types/settings";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
export const defaultSettings: ClientSettings = { export const defaultSettings: ClientSettings = {
theme: "dark", theme: "black",
}; };
export function parseClientSettings(s?: string): ClientSettings { export function parseClientSettings(s?: string): ClientSettings {
+2 -2
View File
@@ -4,7 +4,7 @@ import {
} from "@/components/settings/SettingSchema"; } from "@/components/settings/SettingSchema";
export interface ClientSettings { export interface ClientSettings {
theme: "light" | "dark"; theme: "light" | "dark" | "black";
} }
export interface ProfileSettings { export interface ProfileSettings {
@@ -15,7 +15,7 @@ export interface ProfileSettings {
} }
export const CLIENT_SCHEMA: SettingsSchema = { export const CLIENT_SCHEMA: SettingsSchema = {
theme: ["light", "dark"], theme: ["light", "dark", "black"],
}; };
export const PROFILE_SCHEMA: SettingsSchema = { export const PROFILE_SCHEMA: SettingsSchema = {