diff --git a/README.md b/README.md
index 38eea22..759008b 100644
--- a/README.md
+++ b/README.md
@@ -27,8 +27,10 @@ The project uses Next.js Route Groups without a `src/` directory to maintain a c
- `components/` — Modular UI components (Chat, Navigation, Sidebars)
- `db/` — Database schema definitions, migrations, and Drizzle configuration (`drizzle.config.ts`)
- `lib/` — Centralized core logic folder containing:
- - `store/` — State management stores
+ - `stores/` — State management stores
+ - `schemas/` — Zod validation schemas
- `types/` — Global TypeScript interfaces and type definitions
+ - `services/` — Business logic layers and external API integration services
- `public/` — Static assets (images, icons, fonts)
## 🎯 Current Status
diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx
index 0512816..a19ade6 100644
--- a/app/(app)/layout.tsx
+++ b/app/(app)/layout.tsx
@@ -5,7 +5,7 @@
"use client";
-import { useSidebarStore } from "@/lib/store/useSidebarStore";
+import { useSidebarStore } from "@/lib/stores/useSidebarStore";
import { ServerSidebar } from "@/components/sidebar/ServerSidebar";
import { ChannelSidebar } from "@/components/sidebar/ChannelSidebar";
import { MemberSidebar } from "@/components/sidebar/MemberSidebar";
diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx
new file mode 100644
index 0000000..5817d11
--- /dev/null
+++ b/app/(auth)/layout.tsx
@@ -0,0 +1,16 @@
+/**
+ * @file app/(auth)/layout.tsx
+ * @description Layout component wrapping authentication views with a centered container structure and fixed bottom footer.
+ */
+
+export default function AuthLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx
new file mode 100644
index 0000000..6ba79f3
--- /dev/null
+++ b/app/(auth)/login/page.tsx
@@ -0,0 +1,144 @@
+/**
+ * @file app/(auth)/login/page.tsx
+ * @description Client component rendering the login page, handling form submission, authentication requests, error messaging, and redirection.
+ */
+
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import Link from "next/link";
+import Image from "next/image";
+import { loginUser } from "@/lib/services/auth.service";
+
+/**
+ * Renders the user login interface and manages authentication form state.
+ *
+ * @returns {JSX.Element} The rendered login page component.
+ */
+export default function LoginPage() {
+ const router = useRouter();
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ /**
+ * Form submit handler extracting credentials from FormData and delegating to handleSignIn.
+ *
+ * @param {React.SubmitEvent} e - The form submission event.
+ */
+ const handleSubmit = async (e: React.SubmitEvent) => {
+ e.preventDefault();
+ const formData = new FormData(e.currentTarget);
+ const email = formData.get("email") as string;
+ const password = formData.get("password") as string;
+ await handleSignIn(email, password);
+ };
+
+ /**
+ * Authenticates the user with provided credentials and redirects upon success.
+ *
+ * @param {string} email - The user's email address.
+ * @param {string} password - The user's password.
+ */
+ const handleSignIn = async (email: string, password: string) => {
+ setError(null);
+ setLoading(true);
+
+ const result = await loginUser(email, password);
+
+ if (result.error) {
+ setError(result.error);
+ setLoading(false);
+ } else {
+ router.push("/");
+ }
+ };
+
+ return (
+
+
+ {/* Header / Logo */}
+
+
+
+
+
Welcome back!
+
+ We're looking forward to seeing you again.
+
+
+
+ {/* Error Message */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Form */}
+
+
+ {/* Footer Link */}
+
+ Don't have an account yet?{" "}
+
+ Register
+
+
+
+
+ );
+}
diff --git a/app/(auth)/register/page.tsx b/app/(auth)/register/page.tsx
new file mode 100644
index 0000000..26e860f
--- /dev/null
+++ b/app/(auth)/register/page.tsx
@@ -0,0 +1,171 @@
+/**
+ * @file app/(auth)/register/page.tsx
+ * @description Client component rendering the registration page, handling form submissions, account creation logic, error handling, and navigation.
+ */
+
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import Link from "next/link";
+import Image from "next/image";
+import { registerUser } from "@/lib/services/auth.service";
+
+/**
+ * Renders the user registration interface and manages sign-up form state.
+ *
+ * @returns {JSX.Element} The rendered registration page component.
+ */
+export default function RegisterPage() {
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const router = useRouter();
+
+ /**
+ * Handles user registration form submission, validates form data via auth service,
+ * and navigates to summary page on successful registration.
+ *
+ * @param {React.SubmitEvent} event - The form submission event.
+ * @returns {Promise}
+ */
+ async function handleSubmit(event: React.SubmitEvent) {
+ event.preventDefault();
+ setError(null);
+ setLoading(true);
+
+ const formData = new FormData(event.currentTarget);
+ const payload = Object.fromEntries(formData.entries());
+
+ const result = await registerUser(payload);
+
+ if (result.error) {
+ setError(result.error);
+ setLoading(false);
+ } else {
+ router.push("/");
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+ Create an Account
+
+
+ Get started with Waveform now.
+
+
+
+ {/* Error Message */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Form */}
+
+
+
+ Already have an account?{" "}
+
+ Sign In
+
+
+
+
+ );
+}
diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts
new file mode 100644
index 0000000..745a9f6
--- /dev/null
+++ b/app/api/auth/register/route.ts
@@ -0,0 +1,72 @@
+/**
+ * @file app/api/auth/register/route.ts
+ * @description API route handler for user registration, validating request payloads, checking duplicate accounts, hashing passwords, and storing new user records.
+ */
+
+import { NextResponse } from "next/server";
+import { db } from "@/db";
+import { users } from "@/db/schema";
+import { hashPassword } from "@/lib/password";
+import { registerSchema } from "@/lib/schemas/auth.schema";
+import { eq, or } from "drizzle-orm";
+
+/**
+ * Handles HTTP POST requests for registering a new user.
+ *
+ * @param {Request} req - The incoming HTTP request object containing the JSON registration payload.
+ * @returns {Promise} JSON response indicating successful creation (201), validation failure (400), conflict (409), or server error (500).
+ */
+export async function POST(req: Request) {
+ try {
+ const body = await req.json();
+ const validationResult = registerSchema.safeParse(body);
+
+ if (!validationResult.success) {
+ return NextResponse.json(
+ { message: validationResult.error.issues[0].message },
+ { status: 400 },
+ );
+ }
+
+ const { username, email, password } = validationResult.data;
+
+ // Check whether the email address or username already exists
+ const [existingUser] = await db
+ .select()
+ .from(users)
+ .where(or(eq(users.email, email), eq(users.username, username)))
+ .limit(1);
+
+ if (existingUser) {
+ if (existingUser.email === email) {
+ return NextResponse.json(
+ { message: "This email address is already in use." },
+ { status: 409 },
+ );
+ }
+
+ if (existingUser.username === username) {
+ return NextResponse.json(
+ { message: "This username is already taken." },
+ { status: 409 },
+ );
+ }
+ }
+
+ // Hash a Password & Create a User
+ const hashedPassword = await hashPassword(password);
+
+ await db.insert(users).values({
+ username,
+ email,
+ password: hashedPassword,
+ });
+
+ return NextResponse.json({ success: true }, { status: 201 });
+ } catch {
+ return NextResponse.json(
+ { message: "Fehler beim Erstellen des Benutzers." },
+ { status: 500 },
+ );
+ }
+}
diff --git a/app/globals.css b/app/globals.css
index 6799211..62c1b9a 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -7,6 +7,8 @@
--color-accent: hsl(235 86% 65%);
--color-accent-hover: hsl(235 86% 58%);
+ --color-destructive: hsl(0 84% 60%);
+
--color-foreground: hsl(210 11% 93%);
--color-muted: hsl(215 8% 55%);
diff --git a/app/layout.tsx b/app/layout.tsx
index a440573..72ff832 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -3,6 +3,7 @@
* @description Root layout component that wraps the entire application, providing global CSS styles, base HTML structure, and page metadata.
*/
+import { SessionProvider } from "next-auth/react";
import "./globals.css";
/**
@@ -27,7 +28,9 @@ export default function RootLayout({
}) {
return (
- {children}
+
+ {children}
+
);
}
diff --git a/components/chat/ChatHeader.tsx b/components/chat/ChatHeader.tsx
index 4561a1f..9492958 100644
--- a/components/chat/ChatHeader.tsx
+++ b/components/chat/ChatHeader.tsx
@@ -6,7 +6,7 @@
"use client";
import { Menu, Hash, Users } from "lucide-react";
-import { useSidebarStore } from "@/lib/store/useSidebarStore";
+import { useSidebarStore } from "@/lib/stores/useSidebarStore";
/**
* Renders the top navigation header for the chat view, providing toggle triggers for mobile navigation and member list sidebars.
diff --git a/components/sidebar/UserProfile.tsx b/components/sidebar/UserProfile.tsx
index 0150ad2..0fcb811 100644
--- a/components/sidebar/UserProfile.tsx
+++ b/components/sidebar/UserProfile.tsx
@@ -1,31 +1,42 @@
/**
* @file components/sidebar/UserProfile.tsx
- * @description User profile component rendered at the bottom of the sidebar, displaying user avatar, status indicator, username, and settings action trigger.
+ * @description User profile component displaying current session info, status, settings, and logout action.
*/
-import { Settings } from "lucide-react";
+"use client";
+
+import { LogOut, Settings } from "lucide-react";
+import { signOut, useSession } from "next-auth/react";
/**
- * Renders the user profile card with user information, online status, and quick access settings.
+ * Renders the user profile card with user information, online status, settings, and logout trigger.
*
* @returns {JSX.Element} The rendered user profile footer element.
*/
export function UserProfile() {
+ const { data: session } = useSession();
+
+ const handleLogout = () => {
+ signOut({ callbackUrl: "/login" });
+ };
+
+ const username = session?.user?.name || "User";
+ const userInitial = username.charAt(0).toUpperCase();
+
return (
);
diff --git a/lib/schemas/auth.schema.ts b/lib/schemas/auth.schema.ts
new file mode 100644
index 0000000..67f7c45
--- /dev/null
+++ b/lib/schemas/auth.schema.ts
@@ -0,0 +1,44 @@
+/**
+ * @file app/lib/schemas/auth.schema.ts
+ * @description Zod validation schemas for authentication forms, including login and user registration data constraints.
+ */
+
+import { z } from "zod";
+
+/**
+ * Zod validation schema for user login credentials.
+ */
+export const loginSchema = z.object({
+ email: z
+ .string()
+ .max(255, "Email is too long")
+ .pipe(z.email("Please provide a valid email address")),
+ password: z
+ .string()
+ .min(1, "Password is required")
+ .max(72, "Password is too long"),
+});
+
+/**
+ * Zod validation schema for new user registration payloads, including password confirmation matching.
+ */
+export const registerSchema = z
+ .object({
+ username: z
+ .string()
+ .min(1, "Username is required")
+ .max(50, "Username is too long"),
+ email: z
+ .string()
+ .max(255, "Email is too long")
+ .pipe(z.email("Please provide a valid email address")),
+ password: z
+ .string()
+ .min(8, "Password must be at least 8 characters long")
+ .max(72, "Password is too long (max 72 characters)"),
+ confirmPassword: z.string(),
+ })
+ .refine((data) => data.password === data.confirmPassword, {
+ message: "Passwords do not match",
+ path: ["confirmPassword"],
+ });
diff --git a/lib/services/auth.service.ts b/lib/services/auth.service.ts
new file mode 100644
index 0000000..cf44e1a
--- /dev/null
+++ b/lib/services/auth.service.ts
@@ -0,0 +1,134 @@
+/**
+ * @file app/lib/services/auth.service.ts
+ * @description Authentication service providing helper functions for sign-in, and registration.
+ */
+
+import { loginSchema, registerSchema } from "@/lib/schemas/auth.schema";
+import { signIn } from "next-auth/react";
+
+/**
+ * Authenticates a user using email and password credentials, performing client-side validation first.
+ *
+ * @async
+ * @param {string} email - The user's email address.
+ * @param {string} password - The user's account password.
+ * @returns {Promise<{ success?: boolean; error?: string }>} An object indicating sign-in success or an error message.
+ */
+export async function loginUser(email: string, password: string) {
+ const validationResult = loginSchema.safeParse({ email, password });
+
+ if (!validationResult.success) {
+ return { error: validationResult.error.issues[0].message };
+ }
+
+ try {
+ const res = await signIn("credentials", {
+ email: validationResult.data.email,
+ password: validationResult.data.password,
+ redirect: false,
+ });
+
+ if (res?.error) {
+ if (res.error === "CredentialsSignin") {
+ return { error: "Incorrect email address or password." };
+ }
+ return { error: "An unexpected error has occurred." };
+ }
+
+ return { success: true };
+ } catch (err: any) {
+ if (
+ err?.message?.includes("fetch") ||
+ err?.name === "TypeError" ||
+ err?.message?.includes("network")
+ ) {
+ return { error: "Server error: API endpoint not available" };
+ }
+ return { error: err?.message || "An unexpected error has occurred." };
+ }
+}
+
+/**
+ * Registers a new user account by validating the form data and submitting it to the backend registration route.
+ *
+ * @async
+ * @param {Record} payload - The user registration form payload containing credentials and user details.
+ * @returns {Promise<{ success?: boolean; error?: string }>} An object indicating successful registration or an error message.
+ */
+export async function registerUser(payload: Record) {
+ const validationResult = registerSchema.safeParse(payload);
+
+ if (!validationResult.success) {
+ return { error: validationResult.error.issues[0].message };
+ }
+
+ try {
+ const response = await fetch("/api/auth/register", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(validationResult.data),
+ });
+
+ const contentType = response.headers.get("content-type");
+ let data;
+
+ if (contentType && contentType.includes("application/json")) {
+ try {
+ data = await response.json();
+ } catch (jsonErr) {
+ return { error: "Server error: Invalid response format" };
+ }
+ } else {
+ throw new Error("Server error: API endpoint not available");
+ }
+
+ if (!response.ok) {
+ return { error: data.message || "Something went wrong." };
+ }
+
+ const loginResult = await autoLogin(
+ validationResult.data.email,
+ validationResult.data.password,
+ );
+
+ if (!loginResult.success) {
+ return { error: loginResult.error };
+ }
+
+ return { success: true };
+ } catch (err: any) {
+ return { error: err.message || "Something went wrong." };
+ }
+}
+
+/**
+ * Performs client-side automatic login using user credentials.
+ *
+ * @async
+ * @param {string} email - The user's email address.
+ * @param {string} password - The user's password.
+ * @returns {Promise<{ success: boolean; error?: string }>} The result of the sign-in operation.
+ */
+export async function autoLogin(email: string, password: string) {
+ try {
+ const signInResult = await signIn("credentials", {
+ email,
+ password,
+ redirect: false,
+ });
+
+ if (signInResult?.error) {
+ return {
+ success: false,
+ error: "Automatic sign-in failed. Please log in manually.",
+ };
+ }
+
+ return { success: true };
+ } catch (err: any) {
+ return {
+ success: false,
+ error: err?.message || "An unexpected error occurred during auto-login.",
+ };
+ }
+}
diff --git a/lib/store/useSidebarStore.ts b/lib/stores/useSidebarStore.ts
similarity index 97%
rename from lib/store/useSidebarStore.ts
rename to lib/stores/useSidebarStore.ts
index 4426ccb..ffb176e 100644
--- a/lib/store/useSidebarStore.ts
+++ b/lib/stores/useSidebarStore.ts
@@ -1,5 +1,5 @@
/**
- * @file lib/store/useSidebarStore.ts
+ * @file lib/stores/useSidebarStore.ts
* @description Condition state management store for controlling the visibility and mutual exclusion of navigation and members sidebars.
*/