Skip to content

Commit b3100ea

Browse files
martin0heArmaanMehra
authored andcommitted
decoupling auth from scheduler navigation
making url params the single source of truth to support guest and auth flows
1 parent 862a75c commit b3100ea

20 files changed

Lines changed: 933 additions & 916 deletions

File tree

apps/searchneu/app/api/scheduler/saved-plans/route.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { verifyUser } from "@/lib/dal/audits";
2+
import { getTerm } from "@/lib/dal/terms";
23
import {
34
db,
4-
savedPlansT,
55
savedPlanCoursesT,
66
savedPlanSectionsT,
7+
savedPlansT,
78
} from "@/lib/db";
8-
import { eq, and } from "drizzle-orm";
9+
import { and, eq } from "drizzle-orm";
910
import { NextRequest } from "next/server";
10-
import { getTerm } from "@/lib/dal/terms";
1111

1212
interface SavePlanSection {
1313
sectionId: number;
@@ -55,6 +55,11 @@ export async function POST(req: NextRequest) {
5555
}
5656

5757
try {
58+
const term = await getTerm(body.term);
59+
if (!term) {
60+
return Response.json({ error: "term not found" }, { status: 400 });
61+
}
62+
5863
// Auto-generate name if not provided
5964
let planName = body.name;
6065
if (!planName) {
@@ -64,17 +69,12 @@ export async function POST(req: NextRequest) {
6469
.where(
6570
and(
6671
eq(savedPlansT.userId, user.id),
67-
eq(savedPlansT.termId, parseInt(body.term, 10)),
72+
eq(savedPlansT.termId, term.id),
6873
),
6974
);
7075
planName = `Plan ${existingPlans.length + 1}`;
7176
}
7277

73-
const term = await getTerm(body.term);
74-
if (!term) {
75-
return Response.json({ error: "term not found" }, { status: 400 });
76-
}
77-
7878
// Create the saved plan
7979
const [savedPlan] = await db
8080
.insert(savedPlansT)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export default function Loading() {
2+
return (
3+
<div className="bg-secondary flex h-full w-full gap-6 px-4 pt-4 xl:px-6">
4+
{/* Left sidebar skeleton */}
5+
<div className="bg-background h-[calc(100vh-72px)] w-75 shrink-0 animate-pulse rounded-lg p-6">
6+
<div className="mb-6 flex gap-4">
7+
<div className="bg-neu2 h-4 w-16 rounded" />
8+
<div className="bg-neu2 h-4 w-12 rounded" />
9+
</div>
10+
<div className="space-y-3">
11+
{Array.from({ length: 4 }).map((_, i) => (
12+
<div key={i} className="bg-neu2 h-16 rounded-lg" />
13+
))}
14+
</div>
15+
</div>
16+
17+
{/* Calendar skeleton */}
18+
<div className="flex min-w-0 flex-1 gap-6">
19+
<div className="min-w-0 flex-1">
20+
<div className="bg-neu2 mb-2 h-4 w-24 animate-pulse rounded" />
21+
<div className="bg-background h-[calc(100vh-100px)] animate-pulse rounded-lg" />
22+
</div>
23+
24+
{/* Right sidebar skeleton */}
25+
<div className="w-48 shrink-0 space-y-3">
26+
{Array.from({ length: 6 }).map((_, i) => (
27+
<div
28+
key={i}
29+
className="bg-background h-24 animate-pulse rounded-lg"
30+
/>
31+
))}
32+
</div>
33+
</div>
34+
</div>
35+
);
36+
}

apps/searchneu/app/scheduler/generator/page.tsx

Lines changed: 15 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,22 @@
11
import { SchedulerWrapper } from "@/components/scheduler/generator/SchedulerWrapper";
2-
import { getTerms } from "@/lib/dal/terms";
2+
import { auth } from "@/lib/auth/auth";
33
import { getCampuses } from "@/lib/dal/campuses";
44
import { getNupaths } from "@/lib/dal/nupaths";
5-
6-
import { db, nupathsT, savedPlansT } from "@/lib/db";
7-
import { auth } from "@/lib/auth/auth";
5+
import { getTerms } from "@/lib/dal/terms";
6+
import { db, nupathsT } from "@/lib/db";
87
import { headers } from "next/headers";
9-
import { notFound, redirect } from "next/navigation";
10-
import { eq, and } from "drizzle-orm";
11-
12-
export default async function Page({
13-
searchParams,
14-
}: {
15-
searchParams: Promise<{
16-
planId: string;
17-
}>;
18-
}) {
19-
const session = await auth.api.getSession({
20-
headers: await headers(),
21-
});
22-
23-
if (!session) {
24-
redirect("/");
25-
}
26-
27-
const params = await searchParams;
28-
const planId = params.planId ? parseInt(params.planId) : null;
29-
30-
if (!planId || isNaN(planId)) {
31-
return <div>Invalid or missing plan ID</div>;
32-
}
33-
34-
let plan;
35-
36-
try {
37-
plan = await db.query.savedPlansT.findFirst({
38-
where: and(
39-
eq(savedPlansT.id, planId),
40-
eq(savedPlansT.userId, session.user.id),
41-
),
42-
});
43-
} catch (error) {
44-
console.error("Error loading plan:", error);
45-
return notFound();
46-
}
47-
48-
if (!plan) {
49-
return notFound();
50-
}
51-
52-
// Fetch available NUPath options
53-
const nupathOptions = await db
54-
.selectDistinct({ short: nupathsT.short, name: nupathsT.name })
55-
.from(nupathsT)
56-
.then((c) => c.map((e) => ({ label: e.name, value: e.short })));
57-
58-
// Fetch terms from the db
59-
const terms = await getTerms();
60-
61-
// Fetch campuses for the mapping
62-
const campuses = await getCampuses();
638

64-
// Fetch nupaths for the mapping
65-
const nupaths = await getNupaths();
9+
export default async function Page() {
10+
const [session, nupathOptions, terms, campuses, nupaths] = await Promise.all([
11+
auth.api.getSession({ headers: await headers() }),
12+
db
13+
.selectDistinct({ short: nupathsT.short, name: nupathsT.name })
14+
.from(nupathsT)
15+
.then((c) => c.map((e) => ({ label: e.name, value: e.short }))),
16+
getTerms(),
17+
getCampuses(),
18+
getNupaths(),
19+
]);
6620

6721
return (
6822
<div className="bg-secondary h-full w-full px-4 pt-4 xl:px-6">
@@ -71,6 +25,7 @@ export default async function Page({
7125
terms={terms}
7226
campuses={campuses}
7327
nupaths={nupaths}
28+
isLoggedIn={!!session?.user?.id}
7429
/>
7530
</div>
7631
);
Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { getTerms } from "@/lib/dal/terms";
2-
import { getCampuses } from "@/lib/dal/campuses";
3-
import { getNupaths } from "@/lib/dal/nupaths";
41
import { DashboardClient } from "@/components/scheduler/dashboard/Dashboard";
52
import { auth } from "@/lib/auth/auth";
6-
import { headers } from "next/headers";
7-
import { redirect } from "next/navigation";
3+
import { getCampuses } from "@/lib/dal/campuses";
4+
import { getNupaths } from "@/lib/dal/nupaths";
5+
import { getTerms } from "@/lib/dal/terms";
86
import { unstable_cache } from "next/cache";
7+
import { headers } from "next/headers";
98

109
const cachedCampuses = unstable_cache(getCampuses, [], {
1110
revalidate: 3600,
@@ -18,23 +17,19 @@ const cachedNupaths = unstable_cache(getNupaths, [], {
1817
});
1918

2019
export default async function Dashboard() {
21-
const session = await auth.api.getSession({
22-
headers: await headers(),
23-
});
24-
25-
if (!session) {
26-
redirect("/");
27-
}
28-
2920
const terms = getTerms();
3021
const campuses = cachedCampuses();
3122
const nupaths = cachedNupaths();
23+
const session = await auth.api.getSession({
24+
headers: await headers(),
25+
});
3226

3327
return (
3428
<DashboardClient
3529
termsPromise={terms}
3630
campusesPromise={campuses}
3731
nupathsPromise={nupaths}
32+
isLoggedIn={!!session}
3833
/>
3934
);
4035
}

apps/searchneu/components/navigation/Footer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"use client";
22

33
import Link from "next/link";
4-
import { Logo } from "../icons/logo";
54
import { Footskie } from "../icons/Footskie";
5+
import { Logo } from "../icons/logo";
66

77
export function LinkColumn({
88
name,

apps/searchneu/components/navigation/Header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import Link from "next/link";
2-
import { Logo } from "../icons/logo";
3-
import { UserIcon } from "./UserMenu";
41
import { graduateFlag, roomsFlag } from "@/lib/flags";
2+
import Link from "next/link";
53
import { Suspense } from "react";
4+
import { Logo } from "../icons/logo";
65
import { NavBar } from "./NavBar";
76
import { MobileNav } from "./MobileNav";
87
import { auth } from "@/lib/auth/auth";
98
import { headers } from "next/headers";
9+
import { UserIcon } from "./UserMenu";
1010

1111
export async function Header() {
1212
const enableRoomsPage = roomsFlag();

apps/searchneu/components/navigation/NavBar.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
"use client";
2+
import { cn } from "@/lib/cn";
3+
import { FlagValues } from "flags/react";
24
import {
5+
Bell,
6+
BookMarked,
7+
Calendar,
38
CircleQuestionMark,
49
DoorOpen,
510
GraduationCapIcon,
6-
Bell,
7-
BookMarked,
811
} from "lucide-react";
9-
import { type ReactNode, use } from "react";
1012
import Link from "next/link";
1113
import { usePathname } from "next/navigation";
12-
import { FlagValues } from "flags/react";
14+
import { type ReactNode, use } from "react";
1315
import { SheetClose } from "../ui/sheet";
14-
import { SchedulerButton } from "./SchedulerButton";
15-
import { cn } from "@/lib/cn";
1616

1717
export function NavBar({
1818
flags,
@@ -70,7 +70,14 @@ export function NavBar({
7070
</Link>
7171
</LinkWrapper>
7272
<LinkWrapper mobileNav={closeable}>
73-
<SchedulerButton pathname={pathname} />
73+
<Link
74+
href="/scheduler"
75+
data-active={pathname === "/scheduler"}
76+
className="bg-neu1 data-[active=true]:border-neu3 flex w-full cursor-pointer items-center justify-center gap-2 rounded-full border-1 p-2 px-4 text-sm"
77+
>
78+
<Calendar className="size-4" />
79+
Scheduler
80+
</Link>
7481
</LinkWrapper>
7582
<LinkWrapper mobileNav={closeable}>
7683
<Link

apps/searchneu/components/navigation/SchedulerButton.tsx

Lines changed: 0 additions & 49 deletions
This file was deleted.

apps/searchneu/components/navigation/UserMenu.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
"use client";
22

3+
import { authClient } from "@/lib/auth/auth-client";
4+
import { useState } from "react";
5+
import { Iconskie } from "../icons/Iconskie";
36
import { SignIn } from "../SignIn";
7+
import { Avatar, AvatarFallback } from "../ui/avatar";
48
import { Button } from "../ui/button";
5-
import { useState } from "react";
69
import {
710
DropdownMenu,
811
DropdownMenuContent,
912
DropdownMenuItem,
1013
DropdownMenuTrigger,
1114
} from "../ui/dropdown-menu";
12-
import { Avatar, AvatarFallback } from "../ui/avatar";
13-
import { Iconskie } from "../icons/Iconskie";
14-
import { authClient } from "@/lib/auth/auth-client";
1515

1616
export function UserIcon() {
1717
const [showSI, setShowSI] = useState(false);
@@ -52,7 +52,10 @@ function UserMenu() {
5252
</DropdownMenuTrigger>
5353
<DropdownMenuContent>
5454
<DropdownMenuItem
55-
onClick={() => authClient.signOut()}
55+
onClick={async () => {
56+
await authClient.signOut();
57+
window.location.reload();
58+
}}
5659
variant="destructive"
5760
>
5861
Sign Out

0 commit comments

Comments
 (0)