diff --git a/app/AuthClientProvider.tsx b/app/AuthClientProvider.tsx new file mode 100644 index 0000000..e64460e --- /dev/null +++ b/app/AuthClientProvider.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter, usePathname } from "next/navigation"; +import { useAuth } from "@/context/AuthContext"; + +export default function AuthClientProvider({ + children, +}: { + children: React.ReactNode; +}) { + const router = useRouter(); + const pathname = usePathname(); + const { user, loading } = useAuth(); + + useEffect(() => { + if (loading) return; + if (pathname === "/" || pathname === "/auth") return; + if (!user.isLoggedIn) { + router.replace("/auth"); + return; + } + if (!user.hasCompletedOnboarding && pathname !== "/onboarding") { + router.replace("/onboarding"); + } + }, [loading, pathname, user, router]); + + return <>{children}; +} diff --git a/app/AuthClientServer.tsx b/app/AuthClientServer.tsx new file mode 100644 index 0000000..ddd2e38 --- /dev/null +++ b/app/AuthClientServer.tsx @@ -0,0 +1,10 @@ +import { ReactNode } from "react"; +import AuthClientProvider from "./AuthClientProvider"; + +export default function AuthServerProvider({ + children, +}: { + children: ReactNode; +}) { + return {children}; +} diff --git a/app/auth/page.tsx b/app/auth/page.tsx index 3d7e6c7..efa5091 100644 --- a/app/auth/page.tsx +++ b/app/auth/page.tsx @@ -1,4 +1,3 @@ -// app/auth/page.tsx import { redirect } from "next/navigation"; import { getAuthUser } from "@/lib/auth"; import AuthClient from "./AuthClient"; diff --git a/app/courses/[courseId]/edit/page.tsx b/app/courses/[courseId]/edit/page.tsx new file mode 100644 index 0000000..99b32c9 --- /dev/null +++ b/app/courses/[courseId]/edit/page.tsx @@ -0,0 +1,24 @@ +import { redirect } from "next/navigation"; +import { getAuthUser } from "@/lib/auth"; +import CourseForm from "@/components/organisation/courses/CourseForm"; + +export default async function EditCoursePage({ + params, +}: { + params: { courseId: string }; +}) { + const user = await getAuthUser(); + const { courseId } = await params; + if (user?.organisation?.role !== "admin") { + redirect(`/courses/${courseId}`); + } + + return ( +
+

+ Edit Course +

+ +
+ ); +} diff --git a/app/courses/[courseId]/layout.tsx b/app/courses/[courseId]/layout.tsx new file mode 100644 index 0000000..5c27e2b --- /dev/null +++ b/app/courses/[courseId]/layout.tsx @@ -0,0 +1,50 @@ +import { ReactNode } from "react"; +import Link from "next/link"; +import { getAuthUser } from "@/lib/auth"; + +export default async function CourseLayout({ + children, + params, +}: { + children: ReactNode; + params: { courseId: string }; +}) { + const user = await getAuthUser(); + const isAdmin = user?.organisation?.role === "admin"; + const { courseId } = await params; + + const response = { + id: courseId, + name: "Sample Course", // Replace with actual API call to fetch course details + }; + return ( +
+ + + {isAdmin && ( +
+ + Edit Course Details + + + Add new module + +
+ )} + + {children} +
+ ); +} diff --git a/app/courses/[courseId]/modules/[moduleId]/edit/page.tsx b/app/courses/[courseId]/modules/[moduleId]/edit/page.tsx new file mode 100644 index 0000000..dd6b95e --- /dev/null +++ b/app/courses/[courseId]/modules/[moduleId]/edit/page.tsx @@ -0,0 +1,27 @@ +import { redirect } from "next/navigation"; +import { getAuthUser } from "@/lib/auth"; +import ModuleForm from "@/components/organisation/courses/ModuleForm"; + +export default async function EditModulePage({ + params, +}: { + params: { courseId: string; moduleId: string }; +}) { + const user = await getAuthUser(); + const { courseId, moduleId } = await params; + if (!courseId || !moduleId) { + redirect("/courses"); + } + if (user?.organisation?.role !== "admin") { + redirect(`/${courseId}/modules`); + } + + return ( +
+

+ Edit Module +

+ +
+ ); +} diff --git a/app/courses/[courseId]/modules/[moduleId]/page.tsx b/app/courses/[courseId]/modules/[moduleId]/page.tsx new file mode 100644 index 0000000..cc8e5b2 --- /dev/null +++ b/app/courses/[courseId]/modules/[moduleId]/page.tsx @@ -0,0 +1,18 @@ +// app/courses/[courseId]/modules/[moduleId]/page.tsx +import ModuleDetail, { + ModuleDetailData, +} from "@/components/organisation/courses/ModuleDetail"; + +export default async function ModulePage({ + params, +}: { + params: { courseId: string; moduleId: string }; +}) { + const { moduleId } = await params; + + return ( +
+ +
+ ); +} diff --git a/app/courses/[courseId]/modules/new/page.tsx b/app/courses/[courseId]/modules/new/page.tsx new file mode 100644 index 0000000..e11a5c1 --- /dev/null +++ b/app/courses/[courseId]/modules/new/page.tsx @@ -0,0 +1,27 @@ +import { redirect } from "next/navigation"; +import { getAuthUser } from "@/lib/auth"; +import ModuleForm from "@/components/organisation/courses/ModuleForm"; + +export default async function NewModulePage({ + params, +}: { + params: { courseId: string }; +}) { + const user = await getAuthUser(); + const { courseId } = await params; + if (!courseId) { + redirect("/courses"); + } + if (user?.organisation?.role !== "admin") { + redirect(`/${courseId}/modules`); + } + + return ( +
+

+ Create New Module +

+ +
+ ); +} diff --git a/app/courses/[courseId]/page.tsx b/app/courses/[courseId]/page.tsx new file mode 100644 index 0000000..3f8f466 --- /dev/null +++ b/app/courses/[courseId]/page.tsx @@ -0,0 +1,14 @@ +import ModuleList from "@/components/organisation/courses/ModuleList"; +import { getAuthUser } from "@/lib/auth"; + +export default async function CoursePage({ + params, +}: { + params: { courseId: string }; +}) { + const user = await getAuthUser(); + const isAdmin = user?.organisation?.role === "admin"; + const { courseId } = await params; + + return ; +} diff --git a/app/courses/layout.tsx b/app/courses/layout.tsx new file mode 100644 index 0000000..675d669 --- /dev/null +++ b/app/courses/layout.tsx @@ -0,0 +1,11 @@ +// app/courses/layout.tsx +import { ReactNode } from "react"; + +export default function CoursesLayout({ children }: { children: ReactNode }) { + return ( +
+

Courses

+ {children} +
+ ); +} diff --git a/app/courses/new/page.tsx b/app/courses/new/page.tsx new file mode 100644 index 0000000..f47e241 --- /dev/null +++ b/app/courses/new/page.tsx @@ -0,0 +1,20 @@ +// app/courses/new/page.tsx +import { redirect } from "next/navigation"; +import { getAuthUser } from "@/lib/auth"; +import CourseForm from "@/components/organisation/courses/CourseForm"; + +export default async function NewCoursePage() { + const user = await getAuthUser(); + if (user?.organisation?.role !== "admin") { + redirect("/courses"); + } + + return ( +
+

+ Create New Course +

+ +
+ ); +} diff --git a/app/courses/page.tsx b/app/courses/page.tsx index 29a7f48..7ab03f0 100644 --- a/app/courses/page.tsx +++ b/app/courses/page.tsx @@ -1,8 +1,38 @@ +"use client"; + +import CourseList from "@/components/organisation/courses/CourseList"; +import { Course } from "@/components/organisation/courses/CourseCard"; +import { useAuth } from "@/context/AuthContext"; +import { useState, useEffect } from "react"; + export default function CoursesPage() { - return ( -
-

Courses

-

List of courses will be displayed here.

-
- ); + const { user } = useAuth(); + const [courses, setCourses] = useState([]); + + if (!user || !user.hasCompletedOnboarding) { + return null; + } + + const isAdmin = user?.organisation?.role === "admin"; + + useEffect(() => { + // Fetch courses from API or database + async function fetchCourses() { + // Replace with actual API call + const fetchedCourses = await fetch("/api/courses", { + credentials: "include", + }) + .then((response) => response.json()) + .then((data) => data["courses"] || []) + .catch((error) => { + console.error("Failed to fetch courses:", error); + return []; + }); + setCourses(fetchedCourses); + } + fetchCourses(); + }, []); + + // 3) Render the client component that will show/hide admin buttons + return ; } diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 486d7a8..da6092a 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,9 +1,8 @@ -import { redirect } from "next/navigation"; import { getAuthUser } from "@/lib/auth"; export default async function DashboardPage() { const user = await getAuthUser(); - if (!user) { + if (!user || !user.hasCompletedOnboarding) { return null; } @@ -12,6 +11,7 @@ export default async function DashboardPage() {

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

Organisation: {user.organisation?.organisationname}

Role: {user.organisation.role}

+

Dashboard is currently in progress.

); } diff --git a/app/layout.tsx b/app/layout.tsx index c0e726d..f02a20c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,6 +3,7 @@ import { Inter } from "next/font/google"; import "./globals.css"; import { AuthProvider } from "@/context/AuthContext"; import LayoutClient from "./layoutClient"; +import AuthServerProvider from "./AuthClientServer"; const inter = Inter({ subsets: ["latin"] }); @@ -20,7 +21,9 @@ export default async function RootLayout({ - {children} + + {children} + diff --git a/app/layoutClient.tsx b/app/layoutClient.tsx index f780c94..47e4591 100644 --- a/app/layoutClient.tsx +++ b/app/layoutClient.tsx @@ -1,8 +1,6 @@ "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"; @@ -16,24 +14,9 @@ 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 && user.hasCompletedOnboarding && isAdmin && ( diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx index 293187c..db0a5c9 100644 --- a/app/onboarding/page.tsx +++ b/app/onboarding/page.tsx @@ -8,9 +8,13 @@ export default function OnboardingPage() { const router = useRouter(); const { user, setUser } = useAuth(); + if (!user || !user.isLoggedIn) { + return null; + } + const [role, setRole] = useState<"admin" | "employee">("employee"); const [orgName, setOrgName] = useState(""); - const [orgId, setOrgId] = useState(""); + const [orgInvite, setOrgInvite] = useState(""); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); @@ -37,7 +41,9 @@ export default function OnboardingPage() { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ organisationId: orgId }), + body: JSON.stringify({ + inviteCode: orgInvite, + }), }); if (!addemp.ok) { const body = await addemp.json().catch(() => ({})); @@ -45,7 +51,6 @@ export default function OnboardingPage() { } } - // 2) Mark onboarding complete const done = await fetch("/api/complete-onboarding", { method: "POST", credentials: "include", @@ -137,8 +142,8 @@ export default function OnboardingPage() { setOrgId(e.target.value)} + value={orgInvite} + onChange={(e) => setOrgInvite(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" /> diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 2d5771f..a6b1030 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -1,8 +1,35 @@ -export default function SettingsPage() { +import { getAuthUser } from "@/lib/auth"; +import OrgSettings from "@/components/organisation/settings/OrgSettings"; + +export default async function SettingsPage() { + const user = await getAuthUser(); + if (!user || !user.hasCompletedOnboarding) { + return null; + } + const organisation = user.organisation; + const role = organisation.role; + const isAdmin = role === "admin"; + const isMember = role === "employee"; + + if (isAdmin) { + return ( +
+ +
+ ); + } + + if (isMember) { + return ( +
+

Member Settings

+
+ ); + } + return (
-

Settings

-

Settings page content goes here.

+

Page should not reach here

); } diff --git a/app/users/layout.tsx b/app/users/layout.tsx new file mode 100644 index 0000000..1c4aaa3 --- /dev/null +++ b/app/users/layout.tsx @@ -0,0 +1,10 @@ +import { ReactNode } from "react"; + +export default function UsersLayout({ children }: { children: ReactNode }) { + return ( +
+

Users

+ {children} +
+ ); +} diff --git a/app/users/page.tsx b/app/users/page.tsx new file mode 100644 index 0000000..f287a12 --- /dev/null +++ b/app/users/page.tsx @@ -0,0 +1,17 @@ +import { getAuthUser } from "@/lib/auth"; +import { redirect } from "next/navigation"; +import UsersList from "@/components/organisation/users/UsersList"; + +export default async function UsersPage() { + const user = await getAuthUser(); + if (!user || !user.hasCompletedOnboarding) { + return null; + } + const isAdmin = user?.organisation?.role === "admin"; + if (!isAdmin) { + redirect("/dashboard"); + return null; + } + + return ; +} diff --git a/components/SideNav.tsx b/components/SideNav.tsx index 1b6783d..6d38d41 100644 --- a/components/SideNav.tsx +++ b/components/SideNav.tsx @@ -45,7 +45,7 @@ export default function SideNav() { const [imgSrc, setImgSrc] = useState(avatarUrl); return ( -