diff --git a/app/api/me/route.ts b/app/api/me/route.ts
deleted file mode 100644
index 83b6282..0000000
--- a/app/api/me/route.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-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 });
- }
-}
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
index 71abac0..486d7a8 100644
--- a/app/dashboard/page.tsx
+++ b/app/dashboard/page.tsx
@@ -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 (
Welcome, {user.firstname || user.email}
-
Enroll in an organisation to get started
+
Organisation: {user.organisation?.organisationname}
+
Role: {user.organisation.role}
);
}
diff --git a/app/layoutClient.tsx b/app/layoutClient.tsx
index 9781077..f780c94 100644
--- a/app/layoutClient.tsx
+++ b/app/layoutClient.tsx
@@ -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({
@@ -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 (
- {isLoggedIn && (
+ {isLoggedIn && user.hasCompletedOnboarding && isAdmin && (
+
+
+ {children}
+
+ )}
+ {isLoggedIn && user.hasCompletedOnboarding && !isAdmin && (
{children}
@@ -27,6 +54,12 @@ export default function LayoutClient({
{children}
)}
+ {isLoggedIn && !user.hasCompletedOnboarding && (
+
+
+ {children}
+
+ )}
diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx
new file mode 100644
index 0000000..293187c
--- /dev/null
+++ b/app/onboarding/page.tsx
@@ -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(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 (
+
+
Onboarding
+
+ Welcome, {user.firstname || user.email}! Let’s get you set up.
+
+
+ {/* Step 1: Choose role */}
+
+
+
+
+
+ {role === "admin" && (
+
+ )}
+ {role === "employee" && (
+
+ )}
+
+ );
+}
diff --git a/app/organisations/page.tsx b/app/organisation/page.tsx
similarity index 100%
rename from app/organisations/page.tsx
rename to app/organisation/page.tsx
diff --git a/components/SideNav.tsx b/components/SideNav.tsx
index 020edf5..1b6783d 100644
--- a/components/SideNav.tsx
+++ b/components/SideNav.tsx
@@ -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 },
],
},
diff --git a/components/auth/signup/SignupForm.tsx b/components/auth/signup/SignupForm.tsx
index 0617b77..055e5c4 100644
--- a/components/auth/signup/SignupForm.tsx
+++ b/components/auth/signup/SignupForm.tsx
@@ -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);
diff --git a/components/onboarding/OnboardingNav.tsx b/components/onboarding/OnboardingNav.tsx
new file mode 100644
index 0000000..7e1ab4e
--- /dev/null
+++ b/components/onboarding/OnboardingNav.tsx
@@ -0,0 +1,13 @@
+import LogoutButton from "../auth/logout/LogoutButton";
+
+export default function OnboardingNav() {
+ return (
+
+ );
+}
diff --git a/components/organisation/OrgNav.tsx b/components/organisation/OrgNav.tsx
new file mode 100644
index 0000000..393472a
--- /dev/null
+++ b/components/organisation/OrgNav.tsx
@@ -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 (
+
+ );
+}
diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx
index ff9a443..b5901ec 100644
--- a/context/AuthContext.tsx
+++ b/context/AuthContext.tsx
@@ -9,7 +9,7 @@ import {
ReactNode,
} from "react";
-type OrgRole = "admin" | "member";
+type OrgRole = "admin" | "employee";
export interface OrganisationMembership {
id: number;
@@ -23,8 +23,8 @@ export interface User {
email?: string;
firstname?: string;
lastname?: string;
- // now includes all the orgs this user belongs to, with their role
- organisations?: OrganisationMembership[];
+ organisation?: OrganisationMembership;
+ hasCompletedOnboarding?: boolean | null;
}
interface AuthCtx {
@@ -42,19 +42,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// 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);
- }
- })
+ .then((u: User) => setUser(u))
.catch(() => {});
}, []);