Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/restrict-pr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Restrict PRs to Deployment Branch
name: Restrict PRs to Main Branch

on:
pull_request:
Expand Down
17 changes: 17 additions & 0 deletions app/api/me/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";

export async function GET() {
const cookieStore = await cookies();
const auth = cookieStore.get("auth");
if (!auth) {
return NextResponse.json({ isLoggedIn: false });
}

try {
const user = JSON.parse(auth.value);
return NextResponse.json(user);
} catch {
return NextResponse.json({ isLoggedIn: false });
}
}
39 changes: 39 additions & 0 deletions app/auth/AuthClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";
import { useState } from "react";
import LoginForm from "@/components/auth/login/LoginForm";
import SignUpForm from "@/components/auth/signup/SignupForm";

export default function AuthClient() {
const [activeTab, setActiveTab] = useState<"signup" | "login">("signup");

return (
<div className="min-h-screen flex items-center justify-center p-8 bg-white">
<div className="w-full max-w-md shadow-lg rounded-lg p-8 border border-gray-100">
<div className="flex mb-8 justify-center border-b">
<button
className={`px-6 py-3 ${
activeTab === "signup"
? "border-b-2 border-purple-600 text-purple-800"
: "text-gray-500 hover:text-gray-700"
}`}
onClick={() => setActiveTab("signup")}
>
Sign Up
</button>
<button
className={`px-6 py-3 ${
activeTab === "login"
? "border-b-2 border-purple-600 text-purple-800"
: "text-gray-500 hover:text-gray-700"
}`}
onClick={() => setActiveTab("login")}
>
Login
</button>
</div>

{activeTab === "signup" ? <SignUpForm /> : <LoginForm />}
</div>
</div>
);
}
101 changes: 11 additions & 90 deletions app/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -1,91 +1,12 @@
"use client";

import { useState } from "react";
import EmployeeSignUpForm from "@/components/employee/signup/SignupForm";
import EmployeeLoginForm from "@/components/employee/login/LoginForm";
import OrganizationSignUpForm from "@/components/organization/signup/SignupForm";
import OrganizationLoginForm from "@/components/organization/login/LoginForm";

export default function Auth() {
const [activeTab, setActiveTab] = useState("signup");
const [accountType, setAccountType] = useState("employee");

return (
<div className="min-h-screen flex flex-col bg-white">
<div className="flex-1 flex flex-col items-center justify-center p-8">
<div className="w-full max-w-md shadow-lg rounded-lg p-8 border border-gray-100">
{/* Account Type Tabs */}
<div className="flex mb-8 justify-center border-b">
<button
className={`px-6 py-3 transition-colors ${
accountType === "employee"
? "border-b-2 border-purple-600 text-purple-800 font-medium"
: "text-gray-500 hover:text-gray-700"
}`}
onClick={() => setAccountType("employee")}
>
Employee
</button>
<button
className={`px-6 py-3 transition-colors ${
accountType === "organization"
? "border-b-2 border-purple-600 text-purple-800 font-medium"
: "text-gray-500 hover:text-gray-700"
}`}
onClick={() => setAccountType("organization")}
>
Organization
</button>
</div>

{/* Login/Signup Toggle */}
<div className="flex mb-8 justify-center bg-gray-100 rounded-full p-1">
<button
className={`px-6 py-2 rounded-full transition-all ${
activeTab === "signup"
? "bg-white text-purple-800 font-medium shadow-sm"
: "text-gray-600 hover:text-gray-800"
}`}
onClick={() => setActiveTab("signup")}
>
Sign Up
</button>
<button
className={`px-6 py-2 rounded-full transition-all ${
activeTab === "login"
? "bg-white text-purple-800 font-medium shadow-sm"
: "text-gray-600 hover:text-gray-800"
}`}
onClick={() => setActiveTab("login")}
>
Log In
</button>
</div>

{/* Forms */}
<div className="mt-6">
{/* Employee Sign Up Form */}
{activeTab === "signup" && accountType === "employee" && (
<EmployeeSignUpForm />
)}

{/* Employee Login Form */}
{activeTab === "login" && accountType === "employee" && (
<EmployeeLoginForm />
)}

{/* Organization Sign Up Form */}
{activeTab === "signup" && accountType === "organization" && (
<OrganizationSignUpForm />
)}

{/* Organization Login Form */}
{activeTab === "login" && accountType === "organization" && (
<OrganizationLoginForm />
)}
</div>
</div>
</div>
</div>
);
// app/auth/page.tsx
import { redirect } from "next/navigation";
import { getAuthUser } from "@/lib/auth";
import AuthClient from "./AuthClient";

export default async function AuthPage() {
const user = await getAuthUser();
if (user) {
redirect(`/dashboard`);
}
return <AuthClient />;
}
25 changes: 13 additions & 12 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
export default function Dashboard() {
import { redirect } from "next/navigation";
import { getAuthUser } from "@/lib/auth";
import LogoutButton from "@/components/auth/logout/LogoutButton";

export default async function DashboardPage() {
const user = await getAuthUser();
if (!user) {
redirect("/auth");
}

return (
<div>
<h1 className="text-2xl font-bold text-center mb-6">Dashboard</h1>
<div className="flex justify-center">
<div className="bg-gray-200 p-4 rounded-lg shadow-md w-full max-w-md">
<p className="text-gray-700">Welcome to your dashboard!</p>
</div>
</div>
<div className="flex justify-center mt-4">
<div className="bg-gray-200 p-4 rounded-lg shadow-md w-full max-w-md">
<p className="text-gray-700">Here you can manage your account.</p>
</div>
</div>
<h1>Welcome, {user.firstname || user.email}</h1>
<p>Organisation: {user.organisationName}</p>
<LogoutButton />
</div>
);
}
9 changes: 6 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Inter } from "next/font/google";
import "./globals.css";
import AuthHeader from "@/components/AuthHeader";
import Footer from "@/components/Footer";
import { AuthProvider } from "@/context/AuthContext";

const inter = Inter({ subsets: ["latin"] });

Expand All @@ -19,9 +20,11 @@ export default function RootLayout({
return (
<html lang="en">
<body className={`${inter.className} bg-white`}>
<AuthHeader />
<main className="container mx-auto p-4">{children}</main>
<Footer />
<AuthProvider>
<AuthHeader />
<main className="container mx-auto p-4">{children}</main>
<Footer />
</AuthProvider>
</body>
</html>
);
Expand Down
9 changes: 7 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import Image from "next/image";
import Link from "next/link";
import { getAuthUser } from "@/lib/auth";
import { redirect } from "next/navigation";

export default function Home() {
export default async function Home() {
const user = await getAuthUser();
if (user) {
redirect("/dashboard");
}
return (
<div className="min-h-screen flex flex-col">
<div className="flex-1 flex flex-col items-center justify-center p-8 bg-white">
Expand Down
35 changes: 23 additions & 12 deletions components/AuthHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ import Image from "next/image";
import Link from "next/link";

export default function AuthHeader() {
return (
<header className="bg-purple-100 p-4 shadow-sm">
<div className="container mx-auto flex items-center">
<Link href="/" className="flex items-center">
<Image src="/logo.svg" alt="SkillStack Logo" width={28} height={28} />
<span className="ml-2 text-purple-800 font-semibold text-lg">
SKILLSTACK
</span>
</Link>
</div>
</header>
);
const authenticated = false; // Replace with actual authentication logic

if (!authenticated) {
return (
<header className="bg-purple-100 p-4 shadow-sm">
<div className="container mx-auto flex items-center">
<Link href="/" className="flex items-center">
<Image
src="/logo.svg"
alt="SkillStack Logo"
width={28}
height={28}
/>
<span className="ml-2 text-purple-800 font-semibold text-lg">
SKILLSTACK
</span>
</Link>
</div>
</header>
);
}

return null;
}
101 changes: 101 additions & 0 deletions components/auth/login/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -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<string | null>(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 (
<>
<h1 className="text-2xl font-bold text-center mb-6">Login</h1>
{error && (
<div className="text-red-600 text-sm mb-4 text-center">{error}</div>
)}
<form className="space-y-4" onSubmit={onSubmit}>
<div>
<label
htmlFor="email"
className="block text-sm font-medium mb-1 text-gray-700"
>
Email
</label>
<input
type="email"
id="email"
value={email}
className="w-full border border-gray-300 rounded-md p-2 focus:ring-2 focus:ring-purple-300 focus:border-purple-500 outline-none transition-all"
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>

<div>
<label
htmlFor="password"
className="block text-sm font-medium mb-1 text-gray-700"
>
Password
</label>
<input
type="password"
id="password"
className="w-full border border-gray-300 rounded-md p-2 focus:ring-2 focus:ring-purple-300 focus:border-purple-500 outline-none transition-all"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>

<button
type="submit"
className="w-full bg-purple-600 text-white py-2 rounded-md hover:bg-purple-700 transition-colors font-medium disabled:opacity-50"
disabled={loading}
>
{loading ? "Signing in…" : "Sign In"}
</button>

<div className="text-center mt-2">
<Link
href="/organization/forgot-password"
className="text-sm text-purple-600 hover:underline"
>
Forgot password?
</Link>
</div>
</form>
</>
);
}
Loading