11'use client' ;
22
33import { OnboardingStepInput } from '@/app/(app)/setup/components/OnboardingStepInput' ;
4+ import { AnimatedWrapper } from '@/components/animated-wrapper' ;
45import { LogoSpinner } from '@/components/logo-spinner' ;
56import { Button } from '@comp/ui/button' ;
6- import { Card , CardContent , CardFooter , CardHeader , CardTitle } from '@comp/ui/card' ;
77import { Form , FormControl , FormField , FormItem , FormMessage } from '@comp/ui/form' ;
88import type { Organization } from '@db' ;
9- import { ArrowLeft , ArrowRight } from 'lucide-react' ;
9+ import { AnimatePresence , motion } from 'framer-motion' ;
10+ import { Loader2 } from 'lucide-react' ;
1011import { useEffect , useMemo } from 'react' ;
12+ import Balancer from 'react-wrap-balancer' ;
1113import { usePostPaymentOnboarding } from '../hooks/usePostPaymentOnboarding' ;
1214
1315interface PostPaymentOnboardingProps {
@@ -51,6 +53,17 @@ export function PostPaymentOnboarding({
5153 ) ;
5254 } , [ ] ) ;
5355
56+ // Check if current step has valid input
57+ const currentStepValue = form . watch ( step ?. key ) ;
58+ const isCurrentStepValid = ( ( ) => {
59+ if ( ! step ) return false ;
60+ if ( step . key === 'frameworkIds' ) {
61+ return Array . isArray ( currentStepValue ) && currentStepValue . length > 0 ;
62+ }
63+ // For other fields, check if they have a value
64+ return Boolean ( currentStepValue ) && String ( currentStepValue ) . trim ( ) . length > 0 ;
65+ } ) ( ) ;
66+
5467 // Dispatch custom event for background animation when step changes
5568 useEffect ( ( ) => {
5669 if ( typeof window !== 'undefined' ) {
@@ -73,132 +86,166 @@ export function PostPaymentOnboarding({
7386 }
7487 } , [ stepIndex , isFinalizing , totalSteps ] ) ;
7588
76- return (
77- < div className = "scrollbar-hide flex min-h-[calc(100vh-50px)] flex-col items-center justify-center p-4" >
78- < div className = "relative w-full max-w-6xl" >
79- < Card className = "scrollbar-hide relative mx-auto flex w-full max-w-3xl flex-col bg-card/80 dark:bg-card/70 backdrop-blur-xl border border-border/50 shadow-2xl md:w-[680px] lg:w-[820px] xl:w-[920px]" >
80- { ( isLoading || isFinalizing ) && (
81- < div className = "absolute inset-0 z-50 flex items-center justify-center rounded-lg bg-background/80 backdrop-blur-sm" >
82- < LogoSpinner />
83- </ div >
84- ) }
85- < CardHeader className = "flex min-h-[140px] flex-col items-center justify-center pb-0" >
86- < div className = "flex flex-col items-center gap-2" >
87- < LogoSpinner />
88- < div className = "text-muted-foreground text-sm" >
89- Step { stepIndex + 1 } of { totalSteps }
90- </ div >
91- < CardTitle className = "flex min-h-[56px] items-center justify-center text-center leading-6" >
92- { step ?. question || '' }
93- </ CardTitle >
94- </ div >
95- </ CardHeader >
96- < CardContent className = "flex min-h-[150px] flex-1 flex-col overflow-y-auto" >
97- { ! isLoading && (
98- < Form { ...form } >
89+ return isFinalizing ? (
90+ < div className = "flex min-h-dvh items-center justify-center" >
91+ < LogoSpinner />
92+ </ div >
93+ ) : (
94+ < div className = "flex flex-1 flex-col md:px-18 md:py-12 px-4 py-6" >
95+ { /* Progress Stepper */ }
96+ < AnimatedWrapper
97+ delay = { 700 }
98+ animationKey = { `progress-${ step ?. key } ` }
99+ className = "mb-8 md:max-w-sm"
100+ >
101+ < div className = "w-full bg-muted h-1.5 rounded-full overflow-hidden" >
102+ < div
103+ className = "h-full bg-primary transition-all duration-300"
104+ style = { { width : `${ ( ( stepIndex + 1 ) / totalSteps ) * 100 } %` } }
105+ />
106+ </ div >
107+ </ AnimatedWrapper >
108+
109+ { /* Main Content */ }
110+ < div className = "flex-1 flex flex-col" >
111+ { /* Title */ }
112+ < div className = "mb-8" >
113+ < AnimatedWrapper delay = { 800 } animationKey = { `title-${ step ?. key } ` } >
114+ < h1 className = "text-2xl md:text-4xl font-bold text-foreground mb-2" >
115+ < Balancer > { step ?. question || '' } </ Balancer >
116+ </ h1 >
117+ </ AnimatedWrapper >
118+ < AnimatedWrapper delay = { 1000 } animationKey = { `subtitle-${ step ?. key } ` } >
119+ < p className = "text-md md:text-lg text-muted-foreground flex items-center flex-wrap" >
120+ < Balancer > Our AI will personalize the platform based on your answers.</ Balancer >
121+ </ p >
122+ </ AnimatedWrapper >
123+ </ div >
124+
125+ { /* Form Content */ }
126+ < div className = "flex-1" >
127+ { ! isLoading && step && (
128+ < AnimatedWrapper delay = { 1200 } animationKey = { `form-${ step . key } ` } >
129+ < Form { ...form } key = { step . key } >
99130 < form
100131 id = "onboarding-form"
101132 onSubmit = { form . handleSubmit ( onSubmit ) }
102- className = "mt-4 w-full"
133+ className = "w-full"
103134 autoComplete = "off"
104135 >
105- { steps . map ( ( s , idx ) => (
106- < div key = { s . key } style = { { display : idx === stepIndex ? 'block' : 'none' } } >
107- < FormField
108- name = { s . key }
109- render = { ( { field } ) => (
110- < FormItem >
111- < FormControl >
112- < OnboardingStepInput
113- currentStep = { s }
114- form = { form }
115- savedAnswers = { savedAnswers }
116- />
117- </ FormControl >
118- { s . key !== 'shipping' && (
119- < div className = "min-h-[20px]" >
120- < FormMessage />
121- </ div >
122- ) }
123- </ FormItem >
124- ) }
125- />
126- </ div >
127- ) ) }
136+ < FormField
137+ name = { step . key }
138+ render = { ( { field } ) => (
139+ < FormItem >
140+ < FormControl >
141+ < OnboardingStepInput
142+ currentStep = { step }
143+ form = { form }
144+ savedAnswers = { savedAnswers }
145+ />
146+ </ FormControl >
147+ < div className = "min-h-[20px]" >
148+ < FormMessage />
149+ </ div >
150+ </ FormItem >
151+ ) }
152+ />
128153 </ form >
129154 </ Form >
155+ </ AnimatedWrapper >
156+ ) }
157+ </ div >
158+
159+ { /* Action Buttons - Fixed at bottom */ }
160+ < div className = "flex items-center gap-2 justify-end" >
161+ < AnimatePresence >
162+ { stepIndex > 0 && (
163+ < motion . div
164+ key = "back"
165+ initial = { { opacity : 0 , x : 20 } }
166+ animate = { { opacity : 1 , x : 0 } }
167+ exit = { { opacity : 0 , x : 20 } }
168+ transition = { { duration : 0.25 } }
169+ >
170+ < Button
171+ type = "button"
172+ variant = "outline"
173+ className = "flex items-center gap-2"
174+ onClick = { handleBack }
175+ disabled = { isOnboarding || isLoading }
176+ >
177+ Previous
178+ </ Button >
179+ </ motion . div >
130180 ) }
131- </ CardContent >
132- < CardFooter className = "flex flex-col gap-4" >
133- < div className = "flex w-full items-center justify-between" >
181+ </ AnimatePresence >
182+ { isLocal && (
183+ < motion . div
184+ key = "complete-now"
185+ initial = { { opacity : 0 , x : 20 } }
186+ animate = { { opacity : 1 , x : 0 } }
187+ exit = { { opacity : 0 , x : 20 } }
188+ transition = { { duration : 0.25 , delay : 0.05 } }
189+ >
134190 < Button
135191 type = "button"
136- variant = "outline"
137- onClick = { handleBack }
138- disabled = { stepIndex === 0 || isOnboarding || isLoading }
139- className = "group transition-all hover:pr-3"
192+ variant = "secondary"
193+ onClick = { completeNow }
194+ disabled = { isOnboarding || isFinalizing || isLoading }
140195 >
141- < ArrowLeft className = "mr-2 h-4 w-4 transition-transform group-hover:-translate-x-0.5" />
142- Back
196+ Complete
143197 </ Button >
144-
145- < div className = "flex items-center gap-2" >
146- { isLocal && (
147- < Button
148- type = "button"
149- variant = "secondary"
150- onClick = { completeNow }
151- disabled = { isOnboarding || isFinalizing || isLoading }
152- className = "group transition-all"
153- >
154- Complete now
155- </ Button >
156- ) }
157- < Button
158- type = "submit"
159- form = "onboarding-form"
160- disabled = { isOnboarding || isFinalizing || isLoading }
161- className = "group transition-all hover:pl-3"
162- data-testid = "onboarding-next-button"
198+ </ motion . div >
199+ ) }
200+ < motion . div
201+ key = "next-finish"
202+ initial = { { opacity : 0 , x : 20 } }
203+ animate = { { opacity : 1 , x : 0 } }
204+ exit = { { opacity : 0 , x : 20 } }
205+ transition = { { duration : 0.25 , delay : 0.05 } }
206+ >
207+ { isLastStep ? (
208+ < Button
209+ type = "submit"
210+ form = "onboarding-form"
211+ className = "flex items-center gap-2"
212+ disabled = { ! isCurrentStepValid || isOnboarding || isFinalizing || isLoading }
213+ data-testid = "onboarding-next-button"
214+ >
215+ < motion . span
216+ key = "finish-label"
217+ initial = { { opacity : 0 , y : 10 } }
218+ animate = { { opacity : 1 , y : 0 } }
219+ exit = { { opacity : 0 , y : - 10 } }
220+ transition = { { duration : 0.2 } }
221+ className = "flex items-center gap-2"
163222 >
164- { isFinalizing ? (
165- 'Setting up...'
166- ) : isLastStep ? (
167- 'Complete Setup'
168- ) : (
169- < >
170- Next
171- < ArrowRight className = "ml-2 h-4 w-4 transition-transform group-hover:translate-x-0.5" />
172- </ >
173- ) }
174- </ Button >
175- </ div >
176- </ div >
177- < div className = "w-full border-t border-border/30 pt-3" >
178- < p className = "text-center text-xs text-muted-foreground/70" >
179- < span className = "inline-flex items-center justify-center gap-1.5 flex-wrap" >
180- < svg
181- className = "h-3.5 w-3.5 flex-shrink-0"
182- fill = "none"
183- stroke = "currentColor"
184- viewBox = "0 0 24 24"
185- xmlns = "http://www.w3.org/2000/svg"
186- >
187- < path
188- strokeLinecap = "round"
189- strokeLinejoin = "round"
190- strokeWidth = { 1.5 }
191- d = "M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"
192- />
193- </ svg >
194- < span className = "max-w-[280px] sm:max-w-none" >
195- AI personalizes your plan based on your answers
196- </ span >
197- </ span >
198- </ p >
199- </ div >
200- </ CardFooter >
201- </ Card >
223+ { isOnboarding && < Loader2 className = "h-4 w-4 animate-spin" /> }
224+ Complete
225+ </ motion . span >
226+ </ Button >
227+ ) : (
228+ < Button
229+ type = "submit"
230+ form = "onboarding-form"
231+ className = "flex items-center gap-2"
232+ disabled = { ! isCurrentStepValid || isOnboarding || isFinalizing || isLoading }
233+ data-testid = "onboarding-next-button"
234+ >
235+ < motion . span
236+ key = "next-label"
237+ initial = { { opacity : 0 , y : 10 } }
238+ animate = { { opacity : 1 , y : 0 } }
239+ exit = { { opacity : 0 , y : - 10 } }
240+ transition = { { duration : 0.2 } }
241+ className = "flex items-center"
242+ >
243+ Continue
244+ </ motion . span >
245+ </ Button >
246+ ) }
247+ </ motion . div >
248+ </ div >
202249 </ div >
203250 </ div >
204251 ) ;
0 commit comments