Skip to content

Commit cc96608

Browse files
authored
feat(web): refine onboarding flow (#97)
1 parent f5a505f commit cc96608

File tree

10 files changed

+456
-277
lines changed

10 files changed

+456
-277
lines changed

apps/web/src/app/onboarding/component/onboarding-form.tsx

Lines changed: 302 additions & 255 deletions
Large diffs are not rendered by default.

apps/web/src/app/onboarding/component/school-combobox.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"use client";
2-
31
import type { Doc, Id } from "@albert-plus/server/convex/_generated/dataModel";
42
import { CheckIcon, ChevronDownIcon } from "lucide-react";
53
import { useId, useState } from "react";
@@ -35,7 +33,7 @@ export function SchoolCombobox({
3533
value,
3634
onValueChange,
3735
disabled = false,
38-
placeholder = "Select your school or college",
36+
placeholder = "Select your school",
3937
id: providedId,
4038
}: SchoolComboboxProps) {
4139
const generatedId = useId();

apps/web/src/modules/report-parsing/components/degree-progress-upload.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,23 @@ export default function DegreeProgreeUpload({
6969
removeFile(fileData.id);
7070
return;
7171
}
72-
73-
try {
74-
const startingTerm = await extractStartingTerm(file);
75-
console.log(startingTerm);
76-
77-
setStartingTerm(startingTerm);
78-
} catch (e) {
79-
console.warn("Could not find starting term:", e);
80-
}
8172
} catch (err) {
8273
console.error("Error verifying PDF:", err);
8374
toast.error("Could not verify the PDF file.");
8475
removeFile(fileData.id);
8576
return;
8677
}
8778

79+
// try extract starting term
80+
try {
81+
const startingTerm = await extractStartingTerm(file);
82+
console.log(startingTerm);
83+
84+
setStartingTerm(startingTerm);
85+
} catch (e) {
86+
console.warn("Could not find starting term:", e);
87+
}
88+
8889
// Extract and parse course history
8990
try {
9091
const historyText = await extractCourseHistory(file);
@@ -113,6 +114,7 @@ export default function DegreeProgreeUpload({
113114
// wait for modal close before clearing state
114115
// otherwise the modal will flicker with empty data
115116
setTimeout(() => {
117+
setStartingTerm(null);
116118
setParsedCourses([]);
117119
setFileName(files[0].file.name);
118120
if (files[0]) {
@@ -129,6 +131,7 @@ export default function DegreeProgreeUpload({
129131

130132
const handleCancel = () => {
131133
setIsModalOpen(false);
134+
setStartingTerm(null);
132135
setParsedCourses([]);
133136
// Remove the uploaded file
134137
if (files[0]) {

apps/web/src/utils/term.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,14 @@ export function buildAcademicTimeline(
120120
totalYears: academicYear,
121121
};
122122
}
123+
124+
export function getTermAfterSemesters(
125+
start: TermYear,
126+
semesters: number,
127+
): TermYear {
128+
let current = start;
129+
for (let i = 0; i < semesters; i++) {
130+
current = getNextTerm(current);
131+
}
132+
return current;
133+
}

packages/server/convex/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,5 @@ export default defineSchema({
4848
"userId",
4949
]),
5050
students: defineTable(students).index("by_user_id", ["userId"]),
51-
schools: defineTable(schools),
51+
schools: defineTable(schools).index("by_name_level", ["name", "level"]),
5252
});
Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { v } from "convex/values";
22

3+
const semester = v.object({
4+
year: v.number(),
5+
term: v.union(
6+
v.literal("spring"),
7+
v.literal("summer"),
8+
v.literal("fall"),
9+
v.literal("j-term"),
10+
),
11+
});
12+
313
const students = {
414
userId: v.string(),
515
programs: v.array(v.id("programs")),
616
school: v.id("schools"),
7-
startingDate: v.object({
8-
year: v.number(),
9-
term: v.union(v.literal("spring"), v.literal("fall")),
10-
}),
11-
expectedGraduationDate: v.object({
12-
year: v.number(),
13-
term: v.union(v.literal("spring"), v.literal("fall")),
14-
}),
17+
startingDate: semester,
18+
expectedGraduationDate: semester,
1519
};
1620

1721
export { students };

packages/server/convex/schools.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1+
import { v } from "convex/values";
12
import { query } from "./_generated/server";
3+
import { schoolName } from "./schemas/schools";
24

35
export const getSchools = query({
46
args: {},
57
handler: async (ctx) => {
68
return await ctx.db.query("schools").collect();
79
},
810
});
11+
12+
export const getSchoolByNameAndLevel = query({
13+
args: {
14+
name: schoolName,
15+
level: v.union(v.literal("undergraduate"), v.literal("graduate")),
16+
},
17+
handler: async (ctx, args) => {
18+
return await ctx.db
19+
.query("schools")
20+
.withIndex("by_name_level", (q) =>
21+
q.eq("name", args.name).eq("level", args.level),
22+
)
23+
.unique();
24+
},
25+
});

packages/server/convex/seed.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,32 @@ export const seedAll = internalMutation({
7373
programUrl: v.string(),
7474
}),
7575
),
76+
students: v.array(
77+
v.object({
78+
userId: v.string(),
79+
programs: v.array(v.string()),
80+
school: schoolName,
81+
schoolLevel: v.union(v.literal("undergraduate"), v.literal("graduate")),
82+
startingDate: v.object({
83+
year: v.number(),
84+
term: v.union(
85+
v.literal("spring"),
86+
v.literal("summer"),
87+
v.literal("fall"),
88+
v.literal("j-term"),
89+
),
90+
}),
91+
expectedGraduationDate: v.object({
92+
year: v.number(),
93+
term: v.union(
94+
v.literal("spring"),
95+
v.literal("summer"),
96+
v.literal("fall"),
97+
v.literal("j-term"),
98+
),
99+
}),
100+
}),
101+
),
76102
courses: v.array(
77103
v.object({
78104
code: v.string(),
@@ -363,7 +389,55 @@ export const seedAll = internalMutation({
363389
}
364390
}
365391

366-
// 7. Seed user courses
392+
// 7. Seed students
393+
console.log("👨 Seeding students...");
394+
for (const student of args.students) {
395+
// Get school ID by both name and level
396+
const school = await ctx.db
397+
.query("schools")
398+
.withIndex("by_name_level", (q) =>
399+
q.eq("name", student.school).eq("level", student.schoolLevel),
400+
)
401+
.unique();
402+
if (!school) {
403+
console.warn(
404+
`School not found: ${student.school} (${student.schoolLevel})`,
405+
);
406+
continue;
407+
}
408+
409+
// Convert program names to IDs
410+
const programIds: Id<"programs">[] = [];
411+
for (const programName of student.programs) {
412+
const programId = programMap.get(programName);
413+
if (programId) {
414+
programIds.push(programId);
415+
} else {
416+
console.warn(`Program not found: ${programName}`);
417+
}
418+
}
419+
420+
const existing = await ctx.db
421+
.query("students")
422+
.withIndex("by_user_id", (q) => q.eq("userId", student.userId))
423+
.first();
424+
425+
const studentData = {
426+
userId: student.userId,
427+
programs: programIds,
428+
school: school._id,
429+
startingDate: student.startingDate,
430+
expectedGraduationDate: student.expectedGraduationDate,
431+
};
432+
433+
if (existing) {
434+
await ctx.db.patch(existing._id, studentData);
435+
} else {
436+
await ctx.db.insert("students", studentData);
437+
}
438+
}
439+
440+
// 8. Seed user courses
367441
console.log("📚 Seeding user courses...");
368442
for (const userCourse of args.userCourses) {
369443
const existing = await ctx.db

packages/server/seed/seed.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@ async function seedDatabase() {
3535
const courseOfferings = await readJSON("courseOfferings.json");
3636
const prerequisites = await readJSON("prerequisites.json");
3737
const requirements = await readJSON("requirements.json");
38+
const studentsData = await readJSON("students.json");
3839
const userCoursesData = await readJSON("userCourses.json");
3940
const userCourseOfferingsData = await readJSON("userCourseOfferings.json");
4041

42+
// biome-ignore lint/suspicious/noExplicitAny: JSON data doesn't have types
43+
const students = studentsData.map((student: any) => ({
44+
...student,
45+
userId: TEST_USER_ID,
46+
}));
47+
4148
// biome-ignore lint/suspicious/noExplicitAny: JSON data doesn't have types
4249
const userCourses = userCoursesData.map((course: any) => ({
4350
...course,
@@ -60,6 +67,7 @@ async function seedDatabase() {
6067
console.log(` - ${courseOfferings.length} course offerings`);
6168
console.log(` - ${prerequisites.length} prerequisites`);
6269
console.log(` - ${requirements.length} requirements`);
70+
console.log(` - ${students.length} students`);
6371
console.log(` - ${userCourses.length} user courses`);
6472
console.log(` - ${userCourseOfferings.length} user course offerings`);
6573
console.log(`\n🔑 Using TEST_USER_ID: ${TEST_USER_ID}\n`);
@@ -91,6 +99,7 @@ async function seedDatabase() {
9199
courseOfferings,
92100
prerequisites,
93101
requirements,
102+
students,
94103
userCourses,
95104
userCourseOfferings,
96105
});

packages/server/seed/students.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[
2+
{
3+
"userId": "user_test_123",
4+
"programs": ["Computer Science (BS)"],
5+
"school": "College of Arts and Science",
6+
"schoolLevel": "undergraduate",
7+
"startingDate": {
8+
"year": 2023,
9+
"term": "fall"
10+
},
11+
"expectedGraduationDate": {
12+
"year": 2027,
13+
"term": "spring"
14+
}
15+
}
16+
]

0 commit comments

Comments
 (0)