Skip to content

Commit 3666d79

Browse files
authored
Merge branch 'main' into mariano/ll-3
2 parents 74101fa + 768e695 commit 3666d79

File tree

17 files changed

+810
-384
lines changed

17 files changed

+810
-384
lines changed

apps/app/src/app/(app)/onboarding/[orgId]/layout.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { CheckoutCompleteDialog } from '@/components/dialogs/checkout-complete-dialog';
2-
import { OnboardingLayout } from '@/components/onboarding/OnboardingLayout';
2+
import { MinimalHeader } from '@/components/layout/MinimalHeader';
33
import { auth } from '@/utils/auth';
44
import { db } from '@db';
55
import { headers } from 'next/headers';
66
import { notFound } from 'next/navigation';
7+
import { OnboardingSidebar } from '../../setup/components/OnboardingSidebar';
78

89
interface OnboardingRouteLayoutProps {
910
children: React.ReactNode;
@@ -42,9 +43,24 @@ export default async function OnboardingRouteLayout({
4243
}
4344

4445
return (
45-
<OnboardingLayout variant="onboarding" currentOrganization={organization}>
46-
{children}
46+
<main className="flex min-h-dvh flex-col">
47+
<div className="flex flex-1 min-h-0">
48+
{/* Form Section - Left Side */}
49+
<div className="flex-1 flex flex-col">
50+
<MinimalHeader
51+
user={session.user}
52+
organizations={[]}
53+
currentOrganization={organization}
54+
/>
55+
{children}
56+
</div>
57+
58+
{/* Sidebar Section - Right Side, Hidden on Mobile */}
59+
<div className="hidden md:flex md:w-1/2 min-h-screen bg-[#FAFAFA] items-end justify-center py-16 px-8">
60+
<OnboardingSidebar className="w-full max-w-xl mx-auto h-1/2 mt-auto" />
61+
</div>
62+
</div>
4763
<CheckoutCompleteDialog orgId={organization.id} />
48-
</OnboardingLayout>
64+
</main>
4965
);
5066
}

apps/app/src/app/(app)/onboarding/[orgId]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default async function OnboardingPage({ params }: OnboardingPageProps) {
7777
Object.assign(initialData, {
7878
describe:
7979
initialData.describe ||
80-
'comp ai is a grc platform saas that gets companies compliant with soc2 iso and hipaa in days',
80+
'Bubba AI, Inc. is the company behind Comp AI - the fastest way to get SOC 2 compliant.',
8181
industry: initialData.industry || 'SaaS',
8282
teamSize: initialData.teamSize || '1-10',
8383
devices: initialData.devices || 'Personal laptops',

apps/app/src/app/(app)/onboarding/components/PostPaymentOnboarding.tsx

Lines changed: 162 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
'use client';
22

33
import { OnboardingStepInput } from '@/app/(app)/setup/components/OnboardingStepInput';
4+
import { AnimatedWrapper } from '@/components/animated-wrapper';
45
import { LogoSpinner } from '@/components/logo-spinner';
56
import { Button } from '@comp/ui/button';
6-
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@comp/ui/card';
77
import { Form, FormControl, FormField, FormItem, FormMessage } from '@comp/ui/form';
88
import type { Organization } from '@db';
9-
import { ArrowLeft, ArrowRight } from 'lucide-react';
9+
import { AnimatePresence, motion } from 'framer-motion';
10+
import { Loader2 } from 'lucide-react';
1011
import { useEffect, useMemo } from 'react';
12+
import Balancer from 'react-wrap-balancer';
1113
import { usePostPaymentOnboarding } from '../hooks/usePostPaymentOnboarding';
1214

1315
interface 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

Comments
 (0)