22
33import { api } from "@albert-plus/server/convex/_generated/api" ;
44import type { Doc , Id } from "@albert-plus/server/convex/_generated/dataModel" ;
5+ import { useClerk , useUser } from "@clerk/nextjs" ;
56import { useForm } from "@tanstack/react-form" ;
67import {
78 useConvexAuth ,
@@ -10,6 +11,7 @@ import {
1011 useQuery ,
1112} from "convex/react" ;
1213import type { FunctionArgs } from "convex/server" ;
14+ import { LogOutIcon } from "lucide-react" ;
1315import { useRouter } from "next/navigation" ;
1416import React , { Activity } from "react" ;
1517import { toast } from "sonner" ;
@@ -25,13 +27,16 @@ import {
2527 CardHeader ,
2628 CardTitle ,
2729} from "@/components/ui/card" ;
30+ import { Checkbox } from "@/components/ui/checkbox" ;
2831import {
2932 FieldContent ,
33+ FieldDescription ,
3034 FieldError ,
3135 FieldGroup ,
3236 FieldLabel ,
3337 Field as UIField ,
3438} from "@/components/ui/field" ;
39+ import { Label } from "@/components/ui/label" ;
3540import MultipleSelector from "@/components/ui/multiselect" ;
3641import {
3742 Select ,
@@ -72,6 +77,8 @@ const onboardingFormSchema = z
7277 expectedGraduationDate : dateSchema ,
7378 // User courses
7479 userCourses : z . array ( z . object ( ) ) . optional ( ) ,
80+ // Final presentation invite
81+ attendPresentation : z . boolean ( ) . optional ( ) ,
7582 } )
7683 . refine (
7784 ( data ) => {
@@ -94,13 +101,16 @@ const onboardingFormSchema = z
94101
95102export function OnboardingForm ( ) {
96103 const router = useRouter ( ) ;
104+ const { user } = useUser ( ) ;
105+ const { signOut } = useClerk ( ) ;
97106 const { isAuthenticated } = useConvexAuth ( ) ;
98107 const [ isFileLoaded , setIsFileLoaded ] = React . useState ( false ) ;
99108 const [ currentStep , setCurrentStep ] = React . useState < 1 | 2 > ( 1 ) ;
100109
101110 // actions
102111 const upsertStudent = useMutation ( api . students . upsertCurrentStudent ) ;
103112 const importUserCourses = useMutation ( api . userCourses . importUserCourses ) ;
113+ const createInvite = useMutation ( api . studentInvites . createInvite ) ;
104114
105115 // schools
106116 const schools = useQuery (
@@ -183,6 +193,8 @@ export function OnboardingForm() {
183193 userCourses : undefined as
184194 | FunctionArgs < typeof api . userCourses . importUserCourses > [ "courses" ]
185195 | undefined ,
196+ // final presentation
197+ attendPresentation : false ,
186198 } ,
187199 validators : {
188200 onSubmit : ( { value } ) => {
@@ -215,6 +227,14 @@ export function OnboardingForm() {
215227 if ( value . userCourses ) {
216228 await importUserCourses ( { courses : value . userCourses } ) ;
217229 }
230+
231+ if ( value . attendPresentation && user ) {
232+ await createInvite ( {
233+ name : user . fullName || "Unknown" ,
234+ email :
235+ user . primaryEmailAddress ?. emailAddress || "[email protected] " , 236+ } ) ;
237+ }
218238 } catch ( error ) {
219239 console . error ( "Error completing onboarding:" , error ) ;
220240 toast . error ( "Could not complete onboarding. Please try again." ) ;
@@ -255,37 +275,51 @@ export function OnboardingForm() {
255275 < Activity mode = { currentStep === 1 ? "visible" : "hidden" } >
256276 < Card >
257277 < CardHeader >
258- < CardTitle className = "text-2xl flex items-center gap-2" >
259- Degree Progress Report
260- < Tooltip >
261- < TooltipTrigger asChild >
262- < Badge
263- variant = "outline"
264- className = "cursor-help size-5 rounded-full p-0 text-xs hover:bg-muted"
265- >
266- ?
267- </ Badge >
268- </ TooltipTrigger >
269- < TooltipContent className = "max-w-xs" >
270- < p >
271- We do not store your degree progress report. Need help
272- finding it?{ " " }
273- < a
274- href = "https://www.nyu.edu/students/student-information-and-resources/registration-records-and-graduation/registration/tracking-degree-progress.html"
275- target = "_blank"
276- rel = "noopener noreferrer"
277- className = "underline"
278- >
279- View NYU's guide
280- </ a >
281- </ p >
282- </ TooltipContent >
283- </ Tooltip >
284- </ CardTitle >
285- < CardDescription >
286- Upload your degree progress report (PDF) so we can help you track
287- your academic progress and suggest courses.
288- </ CardDescription >
278+ < div className = "flex items-start justify-between gap-4" >
279+ < div className = "flex-1" >
280+ < CardTitle className = "text-2xl flex items-center gap-2" >
281+ Degree Progress Report
282+ < Tooltip >
283+ < TooltipTrigger asChild >
284+ < Badge
285+ variant = "outline"
286+ className = "cursor-help size-5 rounded-full p-0 text-xs hover:bg-muted"
287+ >
288+ ?
289+ </ Badge >
290+ </ TooltipTrigger >
291+ < TooltipContent className = "max-w-xs" >
292+ < p >
293+ We do not store your degree progress report. Need help
294+ finding it?{ " " }
295+ < a
296+ href = "https://www.nyu.edu/students/student-information-and-resources/registration-records-and-graduation/registration/tracking-degree-progress.html"
297+ target = "_blank"
298+ rel = "noopener noreferrer"
299+ className = "underline"
300+ >
301+ View NYU's guide
302+ </ a >
303+ </ p >
304+ </ TooltipContent >
305+ </ Tooltip >
306+ </ CardTitle >
307+ < CardDescription >
308+ Upload your degree progress report (PDF) so we can help you
309+ track your academic progress and suggest courses.
310+ </ CardDescription >
311+ </ div >
312+ < Button
313+ type = "button"
314+ variant = "ghost"
315+ size = "sm"
316+ onClick = { ( ) => signOut ( { redirectUrl : "/" } ) }
317+ className = "gap-2 text-muted-foreground hover:text-foreground"
318+ >
319+ < LogOutIcon className = "size-4" />
320+ Sign out
321+ </ Button >
322+ </ div >
289323 </ CardHeader >
290324 < CardContent >
291325 < DegreeProgreeUpload
@@ -337,11 +371,25 @@ export function OnboardingForm() {
337371 < Activity mode = { currentStep === 2 ? "visible" : "hidden" } >
338372 < Card >
339373 < CardHeader >
340- < CardTitle className = "text-2xl" > Academic Information</ CardTitle >
341- < CardDescription >
342- Tell us about your academic background so we can personalize your
343- experience.
344- </ CardDescription >
374+ < div className = "flex items-start justify-between gap-4" >
375+ < div className = "flex-1" >
376+ < CardTitle className = "text-2xl" > Academic Information</ CardTitle >
377+ < CardDescription >
378+ Tell us about your academic background so we can personalize
379+ your experience.
380+ </ CardDescription >
381+ </ div >
382+ < Button
383+ type = "button"
384+ variant = "ghost"
385+ size = "sm"
386+ onClick = { ( ) => signOut ( { redirectUrl : "/" } ) }
387+ className = "gap-2 text-muted-foreground hover:text-foreground"
388+ >
389+ < LogOutIcon className = "size-4" />
390+ Sign out
391+ </ Button >
392+ </ div >
345393 </ CardHeader >
346394 < CardContent >
347395 < FieldGroup >
@@ -545,6 +593,55 @@ export function OnboardingForm() {
545593 { ( field ) => < FieldError errors = { field . state . meta . errors } /> }
546594 </ form . Field >
547595 </ FieldGroup >
596+
597+ < div className = "rounded-lg border border-border/40 bg-muted/5 p-5" >
598+ < div className = "space-y-4" >
599+ < div className = "space-y-1" >
600+ < h3 className = "text-base font-semibold" >
601+ Tech@NYU Final Presentation RSVP
602+ </ h3 >
603+ < p className = "text-sm text-muted-foreground" >
604+ The Tech@NYU Dev Team will showcase the Albert Plus
605+ project during our final presentation between December 7
606+ and December 12, 2025. Let us know if you'd like to join
607+ so we can send the exact date, time, and location details.
608+ </ p >
609+ </ div >
610+ { /* Final presentation invite */ }
611+ < form . Field name = "attendPresentation" >
612+ { ( field ) => (
613+ < div className = "space-y-2" >
614+ < UIField
615+ orientation = "horizontal"
616+ className = "items-start gap-3"
617+ >
618+ < Checkbox
619+ id = { field . name }
620+ checked = { Boolean ( field . state . value ) }
621+ onCheckedChange = { ( checked ) =>
622+ field . handleChange ( checked === true )
623+ }
624+ aria-describedby = { `${ field . name } -description` }
625+ />
626+ < FieldContent >
627+ < Label
628+ htmlFor = { field . name }
629+ className = "text-sm font-medium leading-snug"
630+ >
631+ I plan to attend the final presentation
632+ </ Label >
633+ < FieldDescription id = { `${ field . name } -description` } >
634+ We'll email you an RSVP as soon as the schedule is
635+ finalized.
636+ </ FieldDescription >
637+ </ FieldContent >
638+ </ UIField >
639+ < FieldError errors = { field . state . meta . errors } />
640+ </ div >
641+ ) }
642+ </ form . Field >
643+ </ div >
644+ </ div >
548645 </ FieldGroup >
549646 </ CardContent >
550647 < CardFooter >
0 commit comments