Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .cursorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
.env.local
.env
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"lodash": "^4.17.23",
"lucide-react": "^0.553.0",
"next": "^16.0.8",
"next-auth": "5.0.0-beta.30",
"next-auth": "^4.24.13",
"radix-ui": "^1.4.3",
"react": "19.2.0",
"react-dom": "19.2.0",
Expand Down
176 changes: 92 additions & 84 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ NEXT_PUBLIC_API_URL = http://localhost:8000
NEXTAUTH_URL=http://localhost:3000

# NEXTAUTH_SECRET is required by the Auth.js library to handle sessions, to generate a secret you can run this command (openssl rand -base64 32) or just set to a random string like "mysecret"
AUTH_SECRET=
NEXTAUTH_SECRET=

# These are all values that can be found in the teams chat provided by UTS
AUTH_MICROSOFT_ENTRA_ID_ID=
AUTH_MICROSOFT_ENTRA_ID_SECRET=
AUTH_MICROSOFT_ENTRA_ID_TENANT_ID=
# These are all values that can be found in the Auth0 client, in the future will be provided by UTS
AUTH_CLIENT_SECRET=
AUTH_CLIENT_ID=
AUTH_ISSUER=
8 changes: 6 additions & 2 deletions src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
import { handlers } from "@/auth";
export const { GET, POST } = handlers;
import NextAuth from "next-auth";
import { authOptions } from "@/lib/auth";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };
25 changes: 8 additions & 17 deletions src/app/auth/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,25 @@
"use client";

import { useEffect, Suspense } from "react";
import { useEffect } from "react";
import { signIn } from "next-auth/react";
import { useSearchParams } from "next/navigation";

function SignInContent() {
export default function SignInPage() {
const searchParams = useSearchParams();

const redirectTo = searchParams.get("callbackUrl") || "/";
// If there is no callbackUrl (like if the user went directly to /auth/signin), redirect to home "/"
const callbackUrl = searchParams.get("callbackUrl") || "/";

useEffect(() => {
signIn("microsoft-entra-id", { redirectTo });
}, [redirectTo]);
// "auth0" is the provider ID. This might be changed in the future when we swap to Azure Active Directory / Entra (whatever Microsoft calls it these days)
signIn("auth0", { callbackUrl });
}, [callbackUrl]);

return (
<div className="flex h-screen items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold">Redirecting to Login...</h1>
<p className="text-muted-foreground">
Please wait while we connect to McMaster SSO
</p>
<p>Please wait while we send you to SSO</p>
</div>
</div>
);
}

export default function SignInPage() {
return (
<Suspense fallback={<div>Loading...</div>}>
<SignInContent />
</Suspense>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,15 @@ function QuestionTestPage({ params: paramsPromise }: QuestionTestPageProps) {
};

const updateContinueActions = () => {
getActiveTestSession(authFetch).then((session) => {
setContinueActions({
use_skipped_questions: session.skipped_questions.length > 0,
getActiveTestSession(authFetch)
.then((session) => {
setContinueActions({
use_skipped_questions: session.skipped_questions.length > 0,
});
})
.catch(() => {
setContinueActions({ use_skipped_questions: false });
});
});
};

useEffect(() => {
Expand Down
14 changes: 7 additions & 7 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Metadata } from "next";
import { Inter, Poppins } from "next/font/google";
import "@/styles/globals.css";
import "@/styles/globals.css";
import "../styles/globals.css";

import { SessionProvider } from "next-auth/react";
import { auth } from "@/auth";
import { authOptions } from "@/lib/auth";
import { getServerSession } from "next-auth";
import { Providers } from "./providers";

const inter = Inter({
variable: "--font-inter",
Expand All @@ -27,17 +27,17 @@ export default async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await auth();
const session = await getServerSession(authOptions);

return (
<html lang="en" className="h-full">
<SessionProvider session={session}>
<Providers session={session}>
<body
className={`${inter.variable} ${poppins.variable} antialiased h-full`}
>
{children}
</body>
</SessionProvider>
</Providers>
</html>
);
}
23 changes: 22 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
"use client";

import Image from "next/image";
import { useRouter } from "next/navigation";
import { MacFastHeader } from "@/components/ui/custom/macfast-header";
import CourseCard from "@/components/ui/custom/course-card";
import { useUserCourses } from "@/hooks/useUserCourses";
import { useAuthFetch } from "@/hooks/useFetchWithAuth";
import { getResumeTarget } from "@/lib/resume-api";
import ErrorMessage from "@/components/ui/custom/error-message";

export default function Home() {
const router = useRouter();
const authFetch = useAuthFetch();
const { courses: userCourses, isLoading, error } = useUserCourses();

const handleResume = async (courseCode: string) => {
try {
const target = await getResumeTarget(courseCode, authFetch);
const url = `/courses/${encodeURIComponent(target.course_code)}/${encodeURIComponent(target.unit_name)}/${encodeURIComponent(target.subtopic_name)}/test`;
router.push(url);
} catch {
// Fallback to course page if resume fails (e.g. when backend not ready)
router.push(`/courses/${encodeURIComponent(courseCode)}/coursepage`);
}
};

console.log("Error:", error);

if (error && (error as any).status === 403) {
Expand Down Expand Up @@ -42,7 +58,12 @@ export default function Home() {

<div className="grid grid-cols-1 gap-6 pb-10 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-5">
{userCourses.map((course, index) => (
<CourseCard key={index} course={course} progress={50} />
<CourseCard
key={index}
course={course}
progress={50}
onResume={handleResume}
/>
))}
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions src/app/providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"use client";
import { SessionProvider } from "next-auth/react";

export function Providers({ children, session }: any) {
return <SessionProvider session={session}>{children}</SessionProvider>;
}
88 changes: 0 additions & 88 deletions src/auth.ts

This file was deleted.

23 changes: 19 additions & 4 deletions src/components/ui/custom/course-card.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import {
Card,
CardContent,
Expand All @@ -22,9 +24,11 @@ export type Course = {
type CourseCardProps = {
course: Course;
progress: number;
/** Called when user clicks Resume; if provided, Resume uses this instead of a static link. */
onResume?: (courseCode: string) => void;
};

function CourseCard({ course, progress }: CourseCardProps) {
function CourseCard({ course, progress, onResume }: CourseCardProps) {
return (
<Card className="group relative flex w-full flex-col overflow-hidden border-light-gray bg-white transition-all hover:-translate-y-1 hover:shadow-lg">
<div className="h-40 bg-gradient-to-br from-slate-50 to-slate-200 p-4 transition-colors group-hover:from-text-gold group-hover:to-text-maroon">
Expand Down Expand Up @@ -71,9 +75,20 @@ function CourseCard({ course, progress }: CourseCardProps) {
<Button variant="secondary" className="flex-1 text-xs font-bold">
<Link href={`/courses/${course.code}/coursepage`}>Details</Link>
</Button>
<Button className="flex-1 gap-2 text-xs shadow-sm font-bold">
Resume <ArrowRight className="h-3 w-3" />
</Button>
{onResume ? (
<Button
className="flex-1 gap-2 text-xs shadow-sm font-bold"
onClick={() => onResume(course.code)}
>
Resume <ArrowRight className="h-3 w-3" />
</Button>
) : (
<Button className="flex-1 gap-2 text-xs shadow-sm font-bold" asChild>
<Link href={`/courses/${course.code}/coursepage`}>
Resume <ArrowRight className="h-3 w-3" />
</Link>
</Button>
)}
</CardFooter>
</Card>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/ui/custom/macfast-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export function MacFastHeader() {
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-red-600 focus:text-red-600 cursor-pointer"
onClick={() => signOut({ redirectTo: "/" })}
onClick={() => signOut({ callbackUrl: "/" })}
>
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
Expand All @@ -148,7 +148,7 @@ export function MacFastHeader() {
</DropdownMenu>
) : (
<Button
onClick={() => signIn("microsoft-entra-id")}
onClick={() => signIn("auth0")}
variant="secondary"
className="ml-2 font-semibold shadow-sm"
>
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useFetchWithAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";

export function useAuthFetch() {
const { data: session } = useSession();
const token = session?.id_token;
const token = session?.accessToken;

const authFetch = useCallback(
async (endpoint: string, options: RequestInit = {}) => {
Expand Down
7 changes: 4 additions & 3 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { auth } from "@/auth";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { useAuthFetch } from "@/hooks/useFetchWithAuth";

const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
Expand All @@ -21,8 +22,8 @@ export async function fetchWithAuth(
endpoint: string,
options: RequestInit = {},
) {
const session = await auth();
const token = session?.id_token;
const session = await getServerSession(authOptions);
const token = session?.accessToken;

console.log(token);

Expand Down
Loading