diff --git a/components/AuthHeader.tsx b/components/AuthHeader.tsx
index 01f3d7f..7fce395 100644
--- a/components/AuthHeader.tsx
+++ b/components/AuthHeader.tsx
@@ -2,16 +2,27 @@ import Image from "next/image";
import Link from "next/link";
export default function AuthHeader() {
- return (
-
-
-
-
-
- SKILLSTACK
-
-
-
-
- );
+ const authenticated = false; // Replace with actual authentication logic
+
+ if (!authenticated) {
+ return (
+
+
+
+
+
+ SKILLSTACK
+
+
+
+
+ );
+ }
+
+ return null;
}
diff --git a/components/auth/login/LoginForm.tsx b/components/auth/login/LoginForm.tsx
new file mode 100644
index 0000000..6cf56a4
--- /dev/null
+++ b/components/auth/login/LoginForm.tsx
@@ -0,0 +1,101 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { useAuth } from "@/context/AuthContext";
+
+export default function LoginForm() {
+ const router = useRouter();
+ const { setUser } = useAuth();
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState
(null);
+
+ const onSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+ setError(null);
+ try {
+ const res = await fetch("/api/login", {
+ method: "POST",
+ credentials: "include",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email, password }),
+ });
+ if (!res.ok) {
+ const body = await res.json().catch(() => ({}));
+ throw new Error(body.message || "Login failed");
+ }
+ const user = await fetch("/api/me", { credentials: "include" }).then(
+ (r) => r.json()
+ );
+ setUser(user);
+ router.push("/dashboard");
+ } catch (err: any) {
+ setError(err.message);
+ setLoading(false);
+ }
+ };
+ return (
+ <>
+ Login
+ {error && (
+ {error}
+ )}
+
+ >
+ );
+}
diff --git a/components/auth/logout/LogoutButton.tsx b/components/auth/logout/LogoutButton.tsx
new file mode 100644
index 0000000..9a5e7a1
--- /dev/null
+++ b/components/auth/logout/LogoutButton.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { useAuth } from "@/context/AuthContext";
+
+export default function LogoutButton() {
+ const { logout } = useAuth();
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+
+ const handleLogout = async () => {
+ setLoading(true);
+ try {
+ await logout();
+ router.push("/auth");
+ } catch (err) {
+ console.error("Logout failed", err);
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/components/auth/signup/SignupForm.tsx b/components/auth/signup/SignupForm.tsx
new file mode 100644
index 0000000..0617b77
--- /dev/null
+++ b/components/auth/signup/SignupForm.tsx
@@ -0,0 +1,169 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { useAuth } from "@/context/AuthContext";
+
+export default function SignupForm() {
+ const router = useRouter();
+ const { setUser } = useAuth();
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [confirmPassword, setConfirmPassword] = useState("");
+ const [firstname, setFirstname] = useState("");
+ const [lastname, setLastname] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const onSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setError(null);
+ if (password !== confirmPassword) {
+ setError("Passwords do not match");
+ return;
+ }
+ setLoading(true);
+ try {
+ const res = await fetch("/api/signup", {
+ method: "POST",
+ credentials: "include",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email, password, firstname, lastname }),
+ });
+ if (!res.ok) {
+ const body = await res.json().catch(() => ({}));
+ throw new Error(body.message || "Signup failed");
+ }
+ const user = await fetch("/api/me", { credentials: "include" }).then(
+ (r) => r.json()
+ );
+ setUser(user);
+ router.push("/dashboard");
+ } catch (err: any) {
+ setError(err.message);
+ setLoading(false);
+ }
+ };
+
+ return (
+ <>
+ Register
+
+ {error && (
+ {error}
+ )}
+
+
+ >
+ );
+}
diff --git a/components/employee/login/LoginForm.tsx b/components/employee/login/LoginForm.tsx
deleted file mode 100644
index 7723afa..0000000
--- a/components/employee/login/LoginForm.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import Link from "next/link";
-
-export default function EmployeeLoginForm() {
- return (
- <>
- Welcome Back
-
- >
- );
-}
diff --git a/components/employee/signup/SignupForm.tsx b/components/employee/signup/SignupForm.tsx
deleted file mode 100644
index c2fd411..0000000
--- a/components/employee/signup/SignupForm.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import Link from "next/link";
-
-export default function EmployeeSignUpForm() {
- return (
- <>
-
- Create Your Account
-
-
- >
- );
-}
diff --git a/components/organization/login/LoginForm.tsx b/components/organization/login/LoginForm.tsx
deleted file mode 100644
index b9cb278..0000000
--- a/components/organization/login/LoginForm.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import Link from "next/link";
-
-export default function OrganizationLoginForm() {
- return (
- <>
-
- Organization Login
-
-
- >
- );
-}
diff --git a/components/organization/signup/SignupForm.tsx b/components/organization/signup/SignupForm.tsx
deleted file mode 100644
index 4a9bf13..0000000
--- a/components/organization/signup/SignupForm.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-export default function OrganizationSignUpForm() {
- return (
- <>
-
- Register Organization
-
-
- >
- );
-}
diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx
new file mode 100644
index 0000000..ff9a443
--- /dev/null
+++ b/context/AuthContext.tsx
@@ -0,0 +1,77 @@
+// context/AuthContext.tsx
+"use client";
+
+import {
+ createContext,
+ useContext,
+ useState,
+ useEffect,
+ ReactNode,
+} from "react";
+
+type OrgRole = "admin" | "member";
+
+export interface OrganisationMembership {
+ id: number;
+ organisationName: string;
+ role: OrgRole;
+}
+
+export interface User {
+ isLoggedIn: boolean;
+ userId?: number;
+ email?: string;
+ firstname?: string;
+ lastname?: string;
+ // now includes all the orgs this user belongs to, with their role
+ organisations?: OrganisationMembership[];
+}
+
+interface AuthCtx {
+ user: User;
+ setUser: (u: User) => void;
+ logout: () => Promise;
+}
+
+const AuthContext = createContext(undefined);
+
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [user, setUser] = useState({ isLoggedIn: false });
+
+ useEffect(() => {
+ // 1) Fetch the “whoami”
+ fetch("/api/me", { credentials: "include" })
+ .then((r) => r.json())
+ .then((u: User) => {
+ if (u.isLoggedIn) {
+ // 2) If logged in, also fetch their org memberships
+ fetch("/api/orgs/my", { credentials: "include" })
+ .then((r) => r.json())
+ .then((orgs: OrganisationMembership[]) =>
+ setUser({ ...u, organisations: orgs })
+ )
+ .catch(() => setUser(u));
+ } else {
+ setUser(u);
+ }
+ })
+ .catch(() => {});
+ }, []);
+
+ async function logout() {
+ await fetch("/api/logout", { method: "POST", credentials: "include" });
+ setUser({ isLoggedIn: false });
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useAuth() {
+ const ctx = useContext(AuthContext);
+ if (!ctx) throw new Error("useAuth must be inside AuthProvider");
+ return ctx;
+}
diff --git a/lib/auth.ts b/lib/auth.ts
new file mode 100644
index 0000000..d71c69c
--- /dev/null
+++ b/lib/auth.ts
@@ -0,0 +1,14 @@
+// lib/auth.ts
+import { cookies } from "next/headers";
+
+export async function getAuthUser() {
+ const cookieStore = await cookies();
+ const auth = cookieStore.get("auth");
+ if (!auth) return null;
+ try {
+ const user = JSON.parse(auth.value);
+ return user.isLoggedIn ? user : null;
+ } catch {
+ return null;
+ }
+}
diff --git a/next.config.ts b/next.config.ts
index e9ffa30..50e09c1 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,14 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
- /* config options here */
+ async rewrites() {
+ return [
+ {
+ source: "/api/:path*",
+ destination: "http://localhost:4000/api/:path*",
+ },
+ ];
+ },
};
export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
index 821bc11..473fec8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "skillstack",
"version": "0.1.0",
"dependencies": {
+ "cookie": "^1.0.2",
"next": "15.3.2",
"react": "^19.0.0",
"react-dom": "^19.0.0"
@@ -2284,6 +2285,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
diff --git a/package.json b/package.json
index debd18a..0447103 100644
--- a/package.json
+++ b/package.json
@@ -9,19 +9,20 @@
"lint": "next lint"
},
"dependencies": {
+ "cookie": "^1.0.2",
+ "next": "15.3.2",
"react": "^19.0.0",
- "react-dom": "^19.0.0",
- "next": "15.3.2"
+ "react-dom": "^19.0.0"
},
"devDependencies": {
- "typescript": "^5",
+ "@eslint/eslintrc": "^3",
+ "@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
- "@tailwindcss/postcss": "^4",
- "tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.3.2",
- "@eslint/eslintrc": "^3"
+ "tailwindcss": "^4",
+ "typescript": "^5"
}
}