Skip to content
17 changes: 0 additions & 17 deletions app/api/me/route.ts

This file was deleted.

5 changes: 3 additions & 2 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { getAuthUser } from "@/lib/auth";
export default async function DashboardPage() {
const user = await getAuthUser();
if (!user) {
redirect("/auth");
return null;
}

return (
<div>
<h1>Welcome, {user.firstname || user.email}</h1>
<p>Enroll in an organisation to get started</p>
<p>Organisation: {user.organisation?.organisationname}</p>
<p>Role: {user.organisation.role}</p>
</div>
);
}
35 changes: 34 additions & 1 deletion app/layoutClient.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
"use client";

import { useAuth } from "@/context/AuthContext";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import HeaderNav from "@/components/HeaderNav";
import SideNav from "@/components/SideNav";
import OrgNav from "@/components/organisation/OrgNav";
import OnboardingNav from "@/components/onboarding/OnboardingNav";
import Footer from "@/components/Footer";

export default function LayoutClient({
Expand All @@ -12,10 +16,33 @@ export default function LayoutClient({
}) {
const { user } = useAuth();
const isLoggedIn = user && user.isLoggedIn;
const router = useRouter();
const role = user && user.hasCompletedOnboarding && user.organisation?.role;
const isAdmin = role === "admin";

const shouldRedirectToOnboarding = isLoggedIn && !user.hasCompletedOnboarding;

useEffect(() => {
if (shouldRedirectToOnboarding) {
router.push("/onboarding");
}
}, [shouldRedirectToOnboarding, router]);

useEffect(() => {
if (!isLoggedIn) {
router.push("/auth");
}
}, [isLoggedIn, router]);

return (
<div>
{isLoggedIn && (
{isLoggedIn && user.hasCompletedOnboarding && isAdmin && (
<div className="flex">
<OrgNav />
<main className="container mx-auto p-4">{children}</main>
</div>
)}
{isLoggedIn && user.hasCompletedOnboarding && !isAdmin && (
<div className="flex">
<SideNav />
<main className="container mx-auto p-4">{children}</main>
Expand All @@ -27,6 +54,12 @@ export default function LayoutClient({
<main className="container mx-auto p-4">{children}</main>
</div>
)}
{isLoggedIn && !user.hasCompletedOnboarding && (
<div>
<OnboardingNav />
<main className="container mx-auto p-4">{children}</main>
</div>
)}

<Footer />
</div>
Expand Down
160 changes: 160 additions & 0 deletions app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/context/AuthContext";

export default function OnboardingPage() {
const router = useRouter();
const { user, setUser } = useAuth();

const [role, setRole] = useState<"admin" | "employee">("employee");
const [orgName, setOrgName] = useState("");
const [orgId, setOrgId] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);

try {
if (role === "admin") {
// 1) Create the org + link you as admin
const create = await fetch("/api/orgs", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ organisationName: orgName }),
});
if (!create.ok) {
const body = await create.json().catch(() => ({}));
throw new Error(body.message || "Failed to create organization");
}
} else {
const addemp = await fetch("/api/orgs/addemployee", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ organisationId: orgId }),
});
if (!addemp.ok) {
const body = await addemp.json().catch(() => ({}));
throw new Error(body.message || "Failed to add employee");
}
}

// 2) Mark onboarding complete
const done = await fetch("/api/complete-onboarding", {
method: "POST",
credentials: "include",
});
if (!done.ok) throw new Error("Could not complete onboarding");
const updatedUser = await fetch("/api/me", {
credentials: "include",
}).then((r) => r.json());
setUser(updatedUser);
router.push(`/dashboard`);
} catch (err: any) {
setError(err.message);
setLoading(false);
}
};

return (
<div className="max-w-md mx-auto p-6">
<h1 className="text-2xl font-semibold mb-4">Onboarding</h1>
<p className="mb-6 text-gray-600">
Welcome, {user.firstname || user.email}! Let’s get you set up.
</p>

{/* Step 1: Choose role */}
<div className="flex space-x-4 mb-6">
<button
type="button"
className={`px-4 py-2 rounded ${
role === "employee"
? "bg-purple-600 text-white"
: "bg-gray-200 text-gray-700"
}`}
onClick={() => setRole("employee")}
>
Join Organization
</button>
<button
type="button"
className={`px-4 py-2 rounded ${
role === "admin"
? "bg-purple-600 text-white"
: "bg-gray-200 text-gray-700"
}`}
onClick={() => setRole("admin")}
>
Create Organization
</button>
</div>

{role === "admin" && (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="orgName"
className="block text-sm font-medium text-gray-700 mb-1"
>
Organization Name
</label>
<input
id="orgName"
type="text"
value={orgName}
onChange={(e) => setOrgName(e.target.value)}
required
className="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-purple-300"
/>
</div>

{error && <p className="text-red-600 text-sm">{error}</p>}

<button
type="submit"
disabled={loading}
className="w-full py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700 disabled:opacity-50 transition-colors"
>
{loading ? "Creating…" : "Create & Continue"}
</button>
</form>
)}
{role === "employee" && (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="orgName"
className="block text-sm font-medium text-gray-700 mb-1"
>
Organization Invite Code
</label>
<input
id="orgName"
type="text"
value={orgId}
onChange={(e) => setOrgId(e.target.value)}
required
className="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-purple-300"
/>
</div>

{error && <p className="text-red-600 text-sm">{error}</p>}

<button
type="submit"
disabled={loading}
className="w-full py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700 disabled:opacity-50 transition-colors"
>
{loading ? "Joining..." : "Join & Continue"}
</button>
</form>
)}
</div>
);
}
File renamed without changes.
2 changes: 1 addition & 1 deletion components/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const menuSections = [
heading: "Management",
items: [
{ label: "History", href: "/history", icon: ClockIcon },
{ label: "Organisations", href: "/organisations", icon: UsersIcon },
// { label: "Organisations", href: "/organisations", icon: UsersIcon },
{ label: "Settings", href: "/settings", icon: CogIcon },
],
},
Expand Down
2 changes: 1 addition & 1 deletion components/auth/signup/SignupForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function SignupForm() {
(r) => r.json()
);
setUser(user);
router.push("/dashboard");
router.push("/onboarding");
} catch (err: any) {
setError(err.message);
setLoading(false);
Expand Down
13 changes: 13 additions & 0 deletions components/onboarding/OnboardingNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import LogoutButton from "../auth/logout/LogoutButton";

export default function OnboardingNav() {
return (
<header className="bg-purple-100 p-4 shadow-sm">
<div className="container mx-auto flex justify-start items-center">
<div className="max-w-max">
<LogoutButton />
</div>
</div>
</header>
);
}
108 changes: 108 additions & 0 deletions components/organisation/OrgNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"use client";

import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import {
HomeIcon,
BookOpenIcon,
ChartBarIcon,
UsersIcon,
CogIcon,
} from "@heroicons/react/24/outline";
import LogoutButton from "../auth/logout/LogoutButton";
import { useAuth } from "@/context/AuthContext";

const menuSections = [
{
heading: "General",
items: [
{ label: "Dashboard", href: "/dashboard", icon: HomeIcon },
{ label: "Courses", href: "/courses", icon: BookOpenIcon },
{ label: "Reports", href: "/reports", icon: ChartBarIcon },
],
},
{
heading: "Management",
items: [
{ label: "My Organisation", href: "/organisation", icon: UsersIcon },
{ label: "Users", href: "/users", icon: UsersIcon },
{ label: "Settings", href: "/settings", icon: CogIcon },
],
},
];

export default function OrgNav() {
const path = usePathname();
const { user } = useAuth();
const firstName = user.firstname || user.email;
const lastName = user.lastname || user.email;
const avatarUrl = `https://avatar.iran.liara.run/username?username=${firstName}+${lastName}&background=f4d9b2&color=FF9800`;
const [imgSrc, setImgSrc] = useState(avatarUrl);

return (
<aside className="w-64 h-screen bg-white border-r border-gray-200 flex flex-col justify-between">
<div>
<Link
href="/"
className="flex items-center px-6 py-4 hover:bg-gray-50 transition-colors"
>
<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>

<nav className="mt-6 px-4 space-y-6">
{menuSections.map((section) => (
<div key={section.heading}>
<p className="text-xs uppercase text-gray-400 tracking-wide px-2 mb-2">
{section.heading}
</p>
<ul className="space-y-1">
{section.items.map(({ label, href, icon: Icon }) => {
const isActive = path === href;
return (
<li key={href}>
<Link
href={href}
className={`flex items-center px-2 py-2 rounded-md transition-colors
${
isActive
? "bg-purple-100 text-purple-700"
: "text-gray-700 hover:bg-gray-100 hover:text-purple-600"
}`}
>
<Icon
className={`h-5 w-5 flex-shrink-0 ${
isActive ? "text-purple-600" : "text-gray-400"
}`}
/>
<span className="ml-3 text-sm font-medium">
{label}
</span>
</Link>
</li>
);
})}
</ul>
</div>
))}
</nav>
</div>

<div className="px-6 py-4 flex flex-col items-center space-y-3">
<Image
src={imgSrc}
alt="Your avatar"
width={40}
height={40}
className="rounded-full"
onError={() => setImgSrc("/avatar-placeholder.jpg")}
/>
<LogoutButton />
</div>
</aside>
);
}
Loading