diff --git a/apps/web/src/app/dashboard/components/sidebar/nav-items.tsx b/apps/web/src/app/dashboard/components/sidebar/nav-items.tsx index 65db3e50..9c8fdfaf 100644 --- a/apps/web/src/app/dashboard/components/sidebar/nav-items.tsx +++ b/apps/web/src/app/dashboard/components/sidebar/nav-items.tsx @@ -21,16 +21,25 @@ export function NavItems({ - {items.map((item) => ( - - - - - {item.title} - - - - ))} + {items.map((item) => { + const isExternal = item.url.startsWith("http"); + return ( + + + + + {item.title} + + + + ); + })} diff --git a/apps/web/src/app/dashboard/page.tsx b/apps/web/src/app/dashboard/page.tsx index 42b687e8..b24db84f 100644 --- a/apps/web/src/app/dashboard/page.tsx +++ b/apps/web/src/app/dashboard/page.tsx @@ -1,5 +1,8 @@ +import { redirect } from "next/navigation"; + const HomePage = () => { - return; + // TODO: homepage is not ready yet, hide if from MVP for now + redirect("/dashboard/register"); }; export default HomePage; diff --git a/apps/web/src/app/onboarding/component/onboarding-form.tsx b/apps/web/src/app/onboarding/component/onboarding-form.tsx index 84e49496..f881577f 100644 --- a/apps/web/src/app/onboarding/component/onboarding-form.tsx +++ b/apps/web/src/app/onboarding/component/onboarding-form.tsx @@ -2,6 +2,7 @@ import { api } from "@albert-plus/server/convex/_generated/api"; import type { Doc, Id } from "@albert-plus/server/convex/_generated/dataModel"; +import { useClerk, useUser } from "@clerk/nextjs"; import { useForm } from "@tanstack/react-form"; import { useConvexAuth, @@ -10,6 +11,7 @@ import { useQuery, } from "convex/react"; import type { FunctionArgs } from "convex/server"; +import { LogOutIcon } from "lucide-react"; import { useRouter } from "next/navigation"; import React, { Activity } from "react"; import { toast } from "sonner"; @@ -25,13 +27,16 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; import { FieldContent, + FieldDescription, FieldError, FieldGroup, FieldLabel, Field as UIField, } from "@/components/ui/field"; +import { Label } from "@/components/ui/label"; import MultipleSelector from "@/components/ui/multiselect"; import { Select, @@ -72,6 +77,8 @@ const onboardingFormSchema = z expectedGraduationDate: dateSchema, // User courses userCourses: z.array(z.object()).optional(), + // Final presentation invite + attendPresentation: z.boolean().optional(), }) .refine( (data) => { @@ -94,6 +101,8 @@ const onboardingFormSchema = z export function OnboardingForm() { const router = useRouter(); + const { user } = useUser(); + const { signOut } = useClerk(); const { isAuthenticated } = useConvexAuth(); const [isFileLoaded, setIsFileLoaded] = React.useState(false); const [currentStep, setCurrentStep] = React.useState<1 | 2>(1); @@ -101,6 +110,7 @@ export function OnboardingForm() { // actions const upsertStudent = useMutation(api.students.upsertCurrentStudent); const importUserCourses = useMutation(api.userCourses.importUserCourses); + const createInvite = useMutation(api.studentInvites.createInvite); // schools const schools = useQuery( @@ -183,6 +193,8 @@ export function OnboardingForm() { userCourses: undefined as | FunctionArgs["courses"] | undefined, + // final presentation + attendPresentation: false, }, validators: { onSubmit: ({ value }) => { @@ -215,6 +227,14 @@ export function OnboardingForm() { if (value.userCourses) { await importUserCourses({ courses: value.userCourses }); } + + if (value.attendPresentation && user) { + await createInvite({ + name: user.fullName || "Unknown", + email: + user.primaryEmailAddress?.emailAddress || "unknown@example.com", + }); + } } catch (error) { console.error("Error completing onboarding:", error); toast.error("Could not complete onboarding. Please try again."); @@ -255,37 +275,51 @@ export function OnboardingForm() { - - Degree Progress Report - - - - ? - - - -

- We do not store your degree progress report. Need help - finding it?{" "} - - View NYU's guide - -

-
-
-
- - Upload your degree progress report (PDF) so we can help you track - your academic progress and suggest courses. - +
+
+ + Degree Progress Report + + + + ? + + + +

+ We do not store your degree progress report. Need help + finding it?{" "} + + View NYU's guide + +

+
+
+
+ + Upload your degree progress report (PDF) so we can help you + track your academic progress and suggest courses. + +
+ +
- Academic Information - - Tell us about your academic background so we can personalize your - experience. - +
+
+ Academic Information + + Tell us about your academic background so we can personalize + your experience. + +
+ +
@@ -545,6 +593,55 @@ export function OnboardingForm() { {(field) => } + +
+
+
+

+ Tech@NYU Final Presentation RSVP +

+

+ The Tech@NYU Dev Team will showcase the Albert Plus + project during our final presentation between December 7 + and December 12, 2025. Let us know if you'd like to join + so we can send the exact date, time, and location details. +

+
+ {/* Final presentation invite */} + + {(field) => ( +
+ + + field.handleChange(checked === true) + } + aria-describedby={`${field.name}-description`} + /> + + + + We'll email you an RSVP as soon as the schedule is + finalized. + + + + +
+ )} +
+
+
diff --git a/apps/web/src/lib/config.ts b/apps/web/src/lib/config.ts index f993b6d6..777a45cb 100644 --- a/apps/web/src/lib/config.ts +++ b/apps/web/src/lib/config.ts @@ -10,7 +10,11 @@ const config = { ], navBottom: [ { title: "Settings", url: "#settings", icon: Settings }, - { title: "Feedback", url: "/feedback", icon: Send }, + { + title: "Feedback", + url: "https://techatnyu.featurebase.app/", + icon: Send, + }, ], }, }; diff --git a/packages/server/convex/_generated/api.d.ts b/packages/server/convex/_generated/api.d.ts index 0a32525f..4faa7a9c 100644 --- a/packages/server/convex/_generated/api.d.ts +++ b/packages/server/convex/_generated/api.d.ts @@ -21,10 +21,12 @@ import type * as schemas_courseOfferings from "../schemas/courseOfferings.js"; import type * as schemas_courses from "../schemas/courses.js"; import type * as schemas_programs from "../schemas/programs.js"; import type * as schemas_schools from "../schemas/schools.js"; +import type * as schemas_studentInvites from "../schemas/studentInvites.js"; import type * as schemas_students from "../schemas/students.js"; import type * as schools from "../schools.js"; import type * as scraper from "../scraper.js"; import type * as seed from "../seed.js"; +import type * as studentInvites from "../studentInvites.js"; import type * as students from "../students.js"; import type * as userCourseOfferings from "../userCourseOfferings.js"; import type * as userCourses from "../userCourses.js"; @@ -49,10 +51,12 @@ declare const fullApi: ApiFromModules<{ "schemas/courses": typeof schemas_courses; "schemas/programs": typeof schemas_programs; "schemas/schools": typeof schemas_schools; + "schemas/studentInvites": typeof schemas_studentInvites; "schemas/students": typeof schemas_students; schools: typeof schools; scraper: typeof scraper; seed: typeof seed; + studentInvites: typeof studentInvites; students: typeof students; userCourseOfferings: typeof userCourseOfferings; userCourses: typeof userCourses; diff --git a/packages/server/convex/schema.ts b/packages/server/convex/schema.ts index 39d65a13..b0761761 100644 --- a/packages/server/convex/schema.ts +++ b/packages/server/convex/schema.ts @@ -7,6 +7,7 @@ import { import { courses, prerequisites, userCourses } from "./schemas/courses"; import { programs, requirements } from "./schemas/programs"; import { schools } from "./schemas/schools"; +import { studentInvites } from "./schemas/studentInvites"; import { students } from "./schemas/students"; export default defineSchema({ @@ -49,4 +50,5 @@ export default defineSchema({ ]), students: defineTable(students).index("by_user_id", ["userId"]), schools: defineTable(schools).index("by_name_level", ["name", "level"]), + studentInvites: defineTable(studentInvites).index("by_user_id", ["userId"]), }); diff --git a/packages/server/convex/schemas/studentInvites.ts b/packages/server/convex/schemas/studentInvites.ts new file mode 100644 index 00000000..00d0edf0 --- /dev/null +++ b/packages/server/convex/schemas/studentInvites.ts @@ -0,0 +1,9 @@ +import { v } from "convex/values"; + +const studentInvites = { + userId: v.string(), + name: v.string(), + email: v.string(), +}; + +export { studentInvites }; diff --git a/packages/server/convex/studentInvites.ts b/packages/server/convex/studentInvites.ts new file mode 100644 index 00000000..1f902100 --- /dev/null +++ b/packages/server/convex/studentInvites.ts @@ -0,0 +1,36 @@ +import { ConvexError } from "convex/values"; +import { omit } from "convex-helpers"; +import { protectedMutation, protectedQuery } from "./helpers/auth"; +import { studentInvites } from "./schemas/studentInvites"; + +export const getCurrentUserInvite = protectedQuery({ + args: {}, + handler: async (ctx) => { + const invite = await ctx.db + .query("studentInvites") + .withIndex("by_user_id", (q) => q.eq("userId", ctx.user.subject)) + .unique(); + + return invite; + }, +}); + +export const createInvite = protectedMutation({ + args: omit(studentInvites, ["userId"]), + handler: async (ctx, args) => { + // Check if invite already exists + const existing = await ctx.db + .query("studentInvites") + .withIndex("by_user_id", (q) => q.eq("userId", ctx.user.subject)) + .unique(); + + if (existing) { + throw new ConvexError("Invite already exists for this user"); + } + + return await ctx.db.insert("studentInvites", { + ...args, + userId: ctx.user.subject, + }); + }, +});