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" && ( +
+
+ + 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" + /> +
+ + {error &&

{error}

} + + +
+ )} + {role === "employee" && ( +
+
+ + 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" + /> +
+ + {error &&

{error}

} + + +
+ )} +
+ ); +} 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(() => {}); }, []);