Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/courses/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function CoursesPage() {
return (
<div>
<h1>Courses</h1>
<p>List of courses will be displayed here.</p>
</div>
);
}
4 changes: 1 addition & 3 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { redirect } from "next/navigation";
import { getAuthUser } from "@/lib/auth";
import LogoutButton from "@/components/auth/logout/LogoutButton";

export default async function DashboardPage() {
const user = await getAuthUser();
Expand All @@ -11,8 +10,7 @@ export default async function DashboardPage() {
return (
<div>
<h1>Welcome, {user.firstname || user.email}</h1>
<p>Organisation: {user.organisationName}</p>
<LogoutButton />
<p>Enroll in an organisation to get started</p>
</div>
);
}
8 changes: 8 additions & 0 deletions app/history/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function HistoryPage() {
return (
<div>
<h1>History</h1>
<p>History page content goes here.</p>
</div>
);
}
9 changes: 3 additions & 6 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import AuthHeader from "@/components/AuthHeader";
import Footer from "@/components/Footer";
import { AuthProvider } from "@/context/AuthContext";
import LayoutClient from "./layoutClient";

const inter = Inter({ subsets: ["latin"] });

Expand All @@ -12,7 +11,7 @@ export const metadata: Metadata = {
description: "Organization and employee skill management platform",
};

export default function RootLayout({
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
Expand All @@ -21,9 +20,7 @@ export default function RootLayout({
<html lang="en">
<body className={`${inter.className} bg-white`}>
<AuthProvider>
<AuthHeader />
<main className="container mx-auto p-4">{children}</main>
<Footer />
<LayoutClient>{children}</LayoutClient>
</AuthProvider>
</body>
</html>
Expand Down
34 changes: 34 additions & 0 deletions app/layoutClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use client";

import { useAuth } from "@/context/AuthContext";
import HeaderNav from "@/components/HeaderNav";
import SideNav from "@/components/SideNav";
import Footer from "@/components/Footer";

export default function LayoutClient({
children,
}: {
children: React.ReactNode;
}) {
const { user } = useAuth();
const isLoggedIn = user && user.isLoggedIn;

return (
<div>
{isLoggedIn && (
<div className="flex">
<SideNav />
<main className="container mx-auto p-4">{children}</main>
</div>
)}
{!isLoggedIn && (
<div>
<HeaderNav />
<main className="container mx-auto p-4">{children}</main>
</div>
)}

<Footer />
</div>
);
}
27 changes: 27 additions & 0 deletions app/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default function Loading() {
return (
<div className="flex items-center justify-center h-screen bg-white">
<svg
className="animate-spin h-12 w-12 text-purple-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
/>
</svg>
<span className="ml-4 text-purple-600 font-medium">Loading...</span>
</div>
);
}
8 changes: 8 additions & 0 deletions app/organisations/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function OrganisationsPage() {
return (
<div>
<h1>Organisations</h1>
<p>List of organisations will be displayed here.</p>
</div>
);
}
8 changes: 8 additions & 0 deletions app/reports/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function ReportsPage() {
return (
<div>
<h1>Reports</h1>
<p>List of reports will be displayed here.</p>
</div>
);
}
8 changes: 8 additions & 0 deletions app/roadmap/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function RoadmapPage() {
return (
<div>
<h1>Roadmap</h1>
<p>Roadmap page content goes here.</p>
</div>
);
}
8 changes: 8 additions & 0 deletions app/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function SettingsPage() {
return (
<div>
<h1>Settings</h1>
<p>Settings page content goes here.</p>
</div>
);
}
28 changes: 0 additions & 28 deletions components/AuthHeader.tsx

This file was deleted.

17 changes: 17 additions & 0 deletions components/HeaderNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Image from "next/image";
import Link from "next/link";

export default function HeaderNav() {
return (
<header className="bg-purple-100 p-4 shadow-sm">
<div className="container mx-auto flex items-center">
<Link href="/" className="flex items-center">
<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>
</div>
</header>
);
}
111 changes: 111 additions & 0 deletions components/SideNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"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,
MapIcon,
ClockIcon,
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: "My Roadmap", href: "/roadmap", icon: MapIcon },
{ label: "Courses", href: "/courses", icon: BookOpenIcon },
{ label: "Reports", href: "/reports", icon: ChartBarIcon },
],
},
{
heading: "Management",
items: [
{ label: "History", href: "/history", icon: ClockIcon },
{ label: "Organisations", href: "/organisations", icon: UsersIcon },
{ label: "Settings", href: "/settings", icon: CogIcon },
],
},
];

export default function SideNav() {
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>
);
}
2 changes: 1 addition & 1 deletion components/auth/logout/LogoutButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function LogoutButton() {
<button
onClick={handleLogout}
disabled={loading}
className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:opacity-50 transition-colors"
className="w-full px-4 py-2 bg-purple-600 text-white rounded-full hover:bg-purple-700 disabled:opacity-50 transition-colors"
>
{loading ? "Logging out…" : "Logout"}
</button>
Expand Down
7 changes: 7 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ const nextConfig: NextConfig = {
},
];
},
images: {
remotePatterns: [
{
hostname: "avatar.iran.liara.run",
},
],
},
};

export default nextConfig;
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"cookie": "^1.0.2",
"next": "15.3.2",
"react": "^19.0.0",
Expand Down
Binary file added public/avatar-placeholder.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.