Skip to content

Commit 56bb6c9

Browse files
committed
Merge branch 'main' into 421-guest-flow-for-scheduler-useplanpersistence-hook
2 parents 955b8c8 + 862a75c commit 56bb6c9

49 files changed

Lines changed: 6458 additions & 670 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Setup Node.js
2020
uses: actions/setup-node@v4
2121
with:
22-
node-version: 24
22+
node-version: 24.14.1
2323
cache: "pnpm"
2424

2525
- name: Install dependencies
@@ -42,7 +42,7 @@ jobs:
4242
- name: Setup Node.js
4343
uses: actions/setup-node@v4
4444
with:
45-
node-version: 24
45+
node-version: 24.14.1
4646
cache: "pnpm"
4747

4848
- name: Install dependencies
@@ -68,7 +68,7 @@ jobs:
6868
- name: Setup Node.js
6969
uses: actions/setup-node@v4
7070
with:
71-
node-version: 24
71+
node-version: 24.14.1
7272
cache: "pnpm"
7373

7474
- name: Install dependencies

apps/cli/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
"citty": "^0.2.2",
1616
"drizzle-orm": "^0.45.2",
1717
"picocolors": "^1.1.1",
18-
"yaml": "^2.8.2",
18+
"yaml": "^2.8.3",
1919
"zod": "^4.3.6"
2020
},
2121
"devDependencies": {
2222
"@sneu/eslint-config": "workspace:*",
2323
"@sneu/tsconfig": "workspace:*",
2424
"@types/node": "^25.5.0",
25-
"eslint": "^9.39.2",
25+
"eslint": "^9.39.4",
2626
"tsx": "^4.21.0",
2727
"typescript": "^6.0.2"
2828
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: 11 - graduate whiteboard
3+
---
4+
5+
lets graduate users select courses from their plan to fulfill section requirements
6+
7+
## WHY
8+
9+
overall direction of graduate needs to be more "manual"
10+
11+
## WHAT
12+
13+
added a new column in the audit plan table that maps section requirement name -> array of courses student has marked to fulfill this course
14+
each section req also has a status attribute that the user can update manually
15+
16+
## IMPLEMENTATION STEPS
17+
18+
1. Have database admin get Neon prod key
19+
2. Database admin runs `db:push`
20+
3. New column should create
21+
4. Check SearchNEU live site to ensure catalog still searches
22+
23+
## ROLLBACK STEPS
24+
25+
This is the HOW... if something goes wrong. How will you revert back to the original
26+
state? What tests will show when something goes wrong? ie:
27+
28+
1. IF GraduateNU audit table CRUD doesn't work
29+
2. Rollback to database snapshot
30+
3. Update Vercel prod DB URL
31+
4. Check SearchNEU live site to ensure graduate works
32+
33+
## APPENDIX

apps/docs/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
"dependencies": {
1414
"fumadocs-core": "16.7.9",
1515
"fumadocs-mdx": "14.2.11",
16-
"fumadocs-openapi": "^10.6.3",
16+
"fumadocs-openapi": "^10.6.7",
1717
"fumadocs-twoslash": "^3.1.15",
18-
"fumadocs-ui": "16.7.9",
18+
"fumadocs-ui": "16.7.11",
1919
"lucide-react": "^1.7.0",
2020
"next": "16.2.2",
2121
"react": "^19.2.4",
@@ -31,7 +31,7 @@
3131
"@types/node": "^25.5.0",
3232
"@types/react": "^19.2.14",
3333
"@types/react-dom": "^19.2.3",
34-
"eslint": "^9.39.2",
34+
"eslint": "^9.39.4",
3535
"json-schema-typed": "^8.0.2",
3636
"openapi-types": "^12.1.3",
3737
"postcss": "^8.5.8",

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,7 @@ export async function POST(req: NextRequest) {
6767
.select()
6868
.from(savedPlansT)
6969
.where(
70-
and(
71-
eq(savedPlansT.userId, user.id),
72-
eq(savedPlansT.termId, term.id),
73-
),
70+
and(eq(savedPlansT.userId, user.id), eq(savedPlansT.termId, term.id)),
7471
);
7572
planName = `Plan ${existingPlans.length + 1}`;
7673
}

apps/searchneu/app/faq/page.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,7 @@ export default async function Page() {
1919
<ScrollArea className="mb-4 w-full whitespace-nowrap">
2020
<div className="flex w-max space-x-4 p-4 pt-3">
2121
{howToData.howtos.map((howto) => (
22-
<HowToCard
23-
key={howto.id}
24-
img={howto.img}
25-
title={howto.title}
26-
description={howto.description}
27-
/>
22+
<HowToCard key={howto.id} howto={howto} />
2823
))}
2924
</div>
3025
<ScrollBar orientation="horizontal" />

apps/searchneu/app/globals.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@
9696
--scheduler-course-brown-stroke: #e0d3c9;
9797

9898
--font-lato: "Lato", sans-serif;
99+
100+
/* graduate colors */
101+
--graduate-navy-background: #1c3557;
102+
--graduate-medium-blue: #6080aa;
103+
--graduate-primary-text: #223454;
104+
--graduate-secondary-text: #c1cad9;
105+
--graduate-neutral-background: #e7ecf2;
106+
107+
--font-helvetica: "Helvetica Neue", sans-serif;
99108
}
100109

101110
@theme inline {

apps/searchneu/app/graduate/[planId]/page.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
Major,
1414
Minor,
1515
Requirement,
16+
Whiteboard,
17+
WhiteboardEntry,
1618
DEFAULT_CATALOG_YEAR,
1719
} from "@/lib/graduate/types";
1820
import { GraduateAPI } from "@/lib/graduate/graduateApiClient";
@@ -81,6 +83,20 @@ function applyNamesToSchedule(
8183
};
8284
}
8385

86+
/** Handle old format (string[]) and new format (WhiteboardEntry). */
87+
function normalizeWhiteboard(raw: unknown): Whiteboard {
88+
if (!raw || typeof raw !== "object") return {};
89+
const out: Whiteboard = {};
90+
for (const [key, val] of Object.entries(raw as Record<string, unknown>)) {
91+
if (Array.isArray(val)) {
92+
out[key] = { courses: val as string[], status: "not_started" };
93+
} else if (val && typeof val === "object" && "courses" in val) {
94+
out[key] = val as WhiteboardEntry;
95+
}
96+
}
97+
return out;
98+
}
99+
84100
async function hydratePlan(
85101
row: AuditPlanRow,
86102
): Promise<HydratedAuditPlan & { courseNames: Record<string, string> }> {
@@ -110,6 +126,7 @@ async function hydratePlan(
110126
minors,
111127
concentration: row.concentration,
112128
catalogYear: row.catalogYear ?? DEFAULT_CATALOG_YEAR,
129+
whiteboard: normalizeWhiteboard(row.whiteboard),
113130
createdAt: row.createdAt,
114131
updatedAt: row.updatedAt,
115132
courseNames,
@@ -138,7 +155,7 @@ export default async function PlanPage({
138155

139156
return (
140157
<div className="mx-auto flex flex-col gap-4 px-6">
141-
<HeaderClient plans={userPlans} currentPlan={hydrated} />
158+
<HeaderClient plans={userPlans} currentPlan={hydrated} isGuest={false} />
142159
<PlanClient plan={hydrated} courseNames={hydrated.courseNames} />
143160
</div>
144161
);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { GuestHeaderClient } from "@/components/graduate/GuestHeaderClient";
2+
import { GuestPlanClient } from "@/components/graduate/GuestPlanClient";
3+
import { getCourseNamesBatch } from "@/lib/dal/courses";
4+
import { GraduateAPI } from "@/lib/graduate/graduateApiClient";
5+
import { Requirement } from "@/lib/graduate/types";
6+
7+
function collectCourseKeys(reqs: Requirement[], out: Set<string>): void {
8+
for (const req of reqs) {
9+
if (req.type === "COURSE") {
10+
out.add(`${req.subject}-${req.classId}`);
11+
} else if (req.type === "AND" || req.type === "OR" || req.type === "XOM") {
12+
collectCourseKeys(req.courses, out);
13+
} else if (req.type === "SECTION") {
14+
collectCourseKeys(req.requirements, out);
15+
}
16+
}
17+
}
18+
19+
export default async function Page({
20+
searchParams,
21+
}: {
22+
searchParams: Promise<{
23+
majors?: string;
24+
minors?: string;
25+
catalogYear?: string;
26+
courses?: string;
27+
}>;
28+
}) {
29+
const params = await searchParams;
30+
const toArray = (v: string | string[] | undefined): string[] => {
31+
if (!v) return [];
32+
return Array.isArray(v) ? v.filter(Boolean) : [v];
33+
};
34+
35+
const majorNames = toArray(params.majors);
36+
const minorNames = toArray(params.minors);
37+
38+
const catalogYear = params.catalogYear ? Number(params.catalogYear) : null;
39+
const scheduleCourseKeys = params.courses
40+
? params.courses.split(",").filter(Boolean)
41+
: [];
42+
43+
let courseNames: Record<string, string> = {};
44+
45+
if (catalogYear) {
46+
const [majors, minors] = await Promise.all([
47+
Promise.all(
48+
majorNames.map((m) => GraduateAPI.majors.get(catalogYear, m)),
49+
),
50+
Promise.all(
51+
minorNames.map((m) => GraduateAPI.minors.get(catalogYear, m)),
52+
),
53+
]);
54+
55+
const keys = new Set<string>(scheduleCourseKeys);
56+
for (const m of [...majors, ...minors]) {
57+
for (const section of m.requirementSections) {
58+
collectCourseKeys(section.requirements, keys);
59+
}
60+
}
61+
62+
courseNames = await getCourseNamesBatch(keys);
63+
}
64+
65+
return (
66+
<div className="mx-auto flex flex-col gap-4 px-6">
67+
<GuestHeaderClient />
68+
<GuestPlanClient initialCourseNames={courseNames} />
69+
</div>
70+
);
71+
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import NewPlanModal from "@/components/graduate/modal/NewPlanModal";
2+
import { auth } from "@/lib/auth/auth";
3+
import { headers } from "next/headers";
24

3-
export default function Page() {
5+
export default async function Page() {
6+
const session = await auth.api.getSession({ headers: await headers() });
47
return (
58
<div>
6-
<NewPlanModal />
9+
<NewPlanModal isGuest={session == null} />
710
</div>
811
);
912
}

0 commit comments

Comments
 (0)