From 8e1c5ba9cd04a54956346c601b0ead09b3819b1e Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 17:39:23 +0800 Subject: [PATCH 01/11] add onboarding page --- app/onboarding/employee/page.tsx | 1 + app/onboarding/organisation/page.tsx | 1 + app/onboarding/page.tsx | 9 +++++++++ context/AuthContext.tsx | 8 ++++---- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 app/onboarding/employee/page.tsx create mode 100644 app/onboarding/organisation/page.tsx create mode 100644 app/onboarding/page.tsx diff --git a/app/onboarding/employee/page.tsx b/app/onboarding/employee/page.tsx new file mode 100644 index 0000000..a80e2f9 --- /dev/null +++ b/app/onboarding/employee/page.tsx @@ -0,0 +1 @@ +// the employee onboarding page diff --git a/app/onboarding/organisation/page.tsx b/app/onboarding/organisation/page.tsx new file mode 100644 index 0000000..5bb9127 --- /dev/null +++ b/app/onboarding/organisation/page.tsx @@ -0,0 +1 @@ +// the organisation onboarding page diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx new file mode 100644 index 0000000..1bddfa0 --- /dev/null +++ b/app/onboarding/page.tsx @@ -0,0 +1,9 @@ +export default function OnboardingPage() { + return ( +
+

Onboarding

+

Welcome to the onboarding process!

+

Please follow the steps to get started.

+
+ ); +} diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx index ff9a443..04fa09b 100644 --- a/context/AuthContext.tsx +++ b/context/AuthContext.tsx @@ -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; } interface AuthCtx { @@ -47,8 +47,8 @@ export function AuthProvider({ children }: { children: ReactNode }) { // 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 }) + .then((org: OrganisationMembership) => + setUser({ ...u, organisation: org }) ) .catch(() => setUser(u)); } else { From a83344097c4ba3f9405c13bd93d883d4a0ba9f45 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 17:39:29 +0800 Subject: [PATCH 02/11] remove unused api --- app/api/me/route.ts | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 app/api/me/route.ts 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 }); - } -} From 0a40f866f75e57ecc38600c6b17835c6311a370a Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 17:40:01 +0800 Subject: [PATCH 03/11] redirect to onboarding or auth accordingly --- app/dashboard/page.tsx | 2 +- app/layoutClient.tsx | 26 ++++++++++++++++++++++++- components/auth/signup/SignupForm.tsx | 2 +- components/onboarding/OnboardingNav.tsx | 13 +++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 components/onboarding/OnboardingNav.tsx diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 71abac0..9538ad2 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -4,7 +4,7 @@ import { getAuthUser } from "@/lib/auth"; export default async function DashboardPage() { const user = await getAuthUser(); if (!user) { - redirect("/auth"); + return null; } return ( diff --git a/app/layoutClient.tsx b/app/layoutClient.tsx index 9781077..e8b06aa 100644 --- a/app/layoutClient.tsx +++ b/app/layoutClient.tsx @@ -1,8 +1,11 @@ "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 OnboardingNav from "@/components/onboarding/OnboardingNav"; import Footer from "@/components/Footer"; export default function LayoutClient({ @@ -12,10 +15,25 @@ export default function LayoutClient({ }) { const { user } = useAuth(); const isLoggedIn = user && user.isLoggedIn; + const router = useRouter(); + + 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 && (
{children}
@@ -27,6 +45,12 @@ export default function LayoutClient({
{children}
)} + {isLoggedIn && !user.hasCompletedOnboarding && ( +
+ +
{children}
+
+ )}
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 ( +
+
+
+ +
+
+
+ ); +} From 9ae909fac5e803a7f832eee3f9e2b701d8c917af Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 19:06:35 +0800 Subject: [PATCH 04/11] add organisation admin navbar --- app/layoutClient.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/layoutClient.tsx b/app/layoutClient.tsx index e8b06aa..f780c94 100644 --- a/app/layoutClient.tsx +++ b/app/layoutClient.tsx @@ -5,6 +5,7 @@ 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"; @@ -16,6 +17,8 @@ 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; @@ -33,7 +36,13 @@ export default function LayoutClient({ return (
- {isLoggedIn && user.hasCompletedOnboarding && ( + {isLoggedIn && user.hasCompletedOnboarding && isAdmin && ( +
+ +
{children}
+
+ )} + {isLoggedIn && user.hasCompletedOnboarding && !isAdmin && (
{children}
From 297fd3487b93d4262f916dd036e0c3cbdc68bfbf Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 19:06:45 +0800 Subject: [PATCH 05/11] update dashboard to show more info --- app/dashboard/page.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 9538ad2..486d7a8 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -10,7 +10,8 @@ export default async function DashboardPage() { return (

Welcome, {user.firstname || user.email}

-

Enroll in an organisation to get started

+

Organisation: {user.organisation?.organisationname}

+

Role: {user.organisation.role}

); } From 96d58c14561b8d0e810453d43695e8f700516751 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 19:06:56 +0800 Subject: [PATCH 06/11] update sidenav for employees --- components/SideNav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }, ], }, From d3085308bc4225b714f8f8b31dd98f820bc1cdeb Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 19:07:01 +0800 Subject: [PATCH 07/11] Add orgnav --- components/organisation/OrgNav.tsx | 108 +++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 components/organisation/OrgNav.tsx diff --git a/components/organisation/OrgNav.tsx b/components/organisation/OrgNav.tsx new file mode 100644 index 0000000..33557a9 --- /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 Organisations", href: "/organisations", 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 ( + + ); +} From baa2e63d68cc87ed0522d1d04fd8340a987393cd Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 19:07:12 +0800 Subject: [PATCH 08/11] Update authcontext --- context/AuthContext.tsx | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx index 04fa09b..ddb4024 100644 --- a/context/AuthContext.tsx +++ b/context/AuthContext.tsx @@ -24,7 +24,7 @@ export interface User { firstname?: string; lastname?: string; organisation?: OrganisationMembership; - hasCompletedOnboarding?: boolean; + 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((org: OrganisationMembership) => - setUser({ ...u, organisation: org }) - ) - .catch(() => setUser(u)); - } else { - setUser(u); - } - }) + .then((u: User) => setUser(u)) .catch(() => {}); }, []); From d27d756d1dbb6a9cb68d9e2ac7ebe6e34b3b8a45 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 19:07:22 +0800 Subject: [PATCH 09/11] Update onboarding page for organisations --- app/onboarding/page.tsx | 132 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 4 deletions(-) diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx index 1bddfa0..e17b07a 100644 --- a/app/onboarding/page.tsx +++ b/app/onboarding/page.tsx @@ -1,9 +1,133 @@ +"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 [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 { + // TODO: employee join flow + // await fetch('/api/orgs/join', { ... }) + } + + // 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(`/${orgName}/dashboard`); + } catch (err: any) { + setError(err.message); + setLoading(false); + } + }; + return ( -
-

Onboarding

-

Welcome to the onboarding process!

-

Please follow the steps to get started.

+
+

Onboarding

+

+ Welcome, {user.firstname || user.email}! Let’s get you set up. +

+ + {/* Step 1: Choose role */} +
+ + +
+ + {/* Step 2: Admin form */} + {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}

} + + +
+ )} + + {/* Step 2: Employee stub */} + {role === "employee" && ( +
+ {/* You can replace this with your “join org” form later */} +

+ As an employee, you’ll be added to an existing organization. (Form + coming soon.) +

+
+ )}
); } From 37061dadae33c5e95ce7ce46d3b30fd37b2f0162 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 22 May 2025 19:21:49 +0800 Subject: [PATCH 10/11] Add employee --- app/onboarding/page.tsx | 83 +++++++++++++++++++++-------------------- context/AuthContext.tsx | 2 +- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx index e17b07a..fb1ee08 100644 --- a/app/onboarding/page.tsx +++ b/app/onboarding/page.tsx @@ -32,8 +32,16 @@ export default function OnboardingPage() { throw new Error(body.message || "Failed to create organization"); } } else { - // TODO: employee join flow - // await fetch('/api/orgs/join', { ... }) + const addemp = await fetch("/api/orgs/addemployee", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ organisationName: orgName }), + }); + if (!addemp.ok) { + const body = await addemp.json().catch(() => ({})); + throw new Error(body.message || "Failed to add employee"); + } } // 2) Mark onboarding complete @@ -87,47 +95,40 @@ export default function OnboardingPage() {
{/* Step 2: Admin form */} - {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}

} - - -
- )} - - {/* Step 2: Employee stub */} - {role === "employee" && ( -
- {/* You can replace this with your “join org” form later */} -

- As an employee, you’ll be added to an existing organization. (Form - coming soon.) -

+ Organization Name + + 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}

} + + +
); } diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx index ddb4024..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; From d736ac88f7317fb2dd5ce1a287cc2b3fbf29e273 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Sat, 24 May 2025 19:25:37 +0800 Subject: [PATCH 11/11] Update employee onboarding --- app/onboarding/employee/page.tsx | 1 - app/onboarding/organisation/page.tsx | 1 - app/onboarding/page.tsx | 94 +++++++++++++------- app/{organisations => organisation}/page.tsx | 0 components/organisation/OrgNav.tsx | 2 +- 5 files changed, 61 insertions(+), 37 deletions(-) delete mode 100644 app/onboarding/employee/page.tsx delete mode 100644 app/onboarding/organisation/page.tsx rename app/{organisations => organisation}/page.tsx (100%) diff --git a/app/onboarding/employee/page.tsx b/app/onboarding/employee/page.tsx deleted file mode 100644 index a80e2f9..0000000 --- a/app/onboarding/employee/page.tsx +++ /dev/null @@ -1 +0,0 @@ -// the employee onboarding page diff --git a/app/onboarding/organisation/page.tsx b/app/onboarding/organisation/page.tsx deleted file mode 100644 index 5bb9127..0000000 --- a/app/onboarding/organisation/page.tsx +++ /dev/null @@ -1 +0,0 @@ -// the organisation onboarding page diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx index fb1ee08..293187c 100644 --- a/app/onboarding/page.tsx +++ b/app/onboarding/page.tsx @@ -10,6 +10,7 @@ export default function OnboardingPage() { 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); @@ -36,7 +37,7 @@ export default function OnboardingPage() { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ organisationName: orgName }), + body: JSON.stringify({ organisationId: orgId }), }); if (!addemp.ok) { const body = await addemp.json().catch(() => ({})); @@ -54,7 +55,7 @@ export default function OnboardingPage() { credentials: "include", }).then((r) => r.json()); setUser(updatedUser); - router.push(`/${orgName}/dashboard`); + router.push(`/dashboard`); } catch (err: any) { setError(err.message); setLoading(false); @@ -94,41 +95,66 @@ export default function OnboardingPage() {
- {/* Step 2: Admin form */} -
-
-
+ {loading ? "Creating…" : "Create & Continue"} + +
+ )} + {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}

} + {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/organisation/OrgNav.tsx b/components/organisation/OrgNav.tsx index 33557a9..393472a 100644 --- a/components/organisation/OrgNav.tsx +++ b/components/organisation/OrgNav.tsx @@ -26,7 +26,7 @@ const menuSections = [ { heading: "Management", items: [ - { label: "My Organisations", href: "/organisations", icon: UsersIcon }, + { label: "My Organisation", href: "/organisation", icon: UsersIcon }, { label: "Users", href: "/users", icon: UsersIcon }, { label: "Settings", href: "/settings", icon: CogIcon }, ],