Skip to content

Commit 61823dc

Browse files
capJavertclaude
andcommitted
feat(onboarding): add onboarding v2 behind feature flag
Port onboarding v2 redesign from feat/redesign-onboarding branch as a self-contained component, served on /onboarding behind the onboarding_v2 GrowthBook feature flag. No shared layout components modified. - Add featureOnboardingV2 flag and mobileAppUrl constant - Create OnboardingV2 component with hero, tag clouds, feed preview, auth flow - Wire A/B test in onboarding page via dynamic import + feature flag - Hide experience level in auth forms when flag is on (gated on both trigger + flag) - Wrap feed preview with local SearchProvider/FeedLayoutProvider Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 08d8be0 commit 61823dc

7 files changed

Lines changed: 4692 additions & 20 deletions

File tree

packages/shared/src/components/auth/OnboardingRegistrationForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export const OnboardingRegistrationForm = ({
158158
onLogin={() => onExistingEmail?.('')}
159159
className={{
160160
container:
161-
'mx-auto mt-6 text-center text-text-secondary typo-callout',
161+
'mx-auto mt-6 w-full justify-center border-t border-border-subtlest-tertiary pt-6 text-center text-text-secondary typo-callout',
162162
login: '!text-inherit',
163163
}}
164164
/>

packages/shared/src/components/auth/RegistrationForm.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type {
1010
RegistrationParameters,
1111
} from '../../lib/auth';
1212
import { AuthEventNames, AuthTriggers } from '../../lib/auth';
13+
import { useConditionalFeature } from '../../hooks/useConditionalFeature';
14+
import { featureOnboardingV2 } from '../../lib/featureManagement';
1315
import { formToJson } from '../../lib/form';
1416
import { Button, ButtonVariant, ButtonSize } from '../buttons/Button';
1517
import { PasswordField } from '../fields/PasswordField';
@@ -33,7 +35,6 @@ import {
3335
TypographyTag,
3436
TypographyType,
3537
} from '../typography/Typography';
36-
import { onboardingGradientClasses } from '../onboarding/common';
3738
import { useAuthData } from '../../contexts/AuthDataContext';
3839
import { authAtom } from '../../features/onboarding/store/onboarding.store';
3940
import { FunnelTargetId } from '../../features/onboarding/types/funnelEvents';
@@ -82,6 +83,11 @@ const RegistrationForm = ({
8283
const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
8384
const [name, setName] = useState('');
8485
const isRecruiterOnboarding = trigger === AuthTriggers.RecruiterSelfServe;
86+
const { value: isOnboardingV2 } = useConditionalFeature({
87+
feature: featureOnboardingV2,
88+
shouldEvaluate: trigger === AuthTriggers.Onboarding,
89+
});
90+
const hideExperienceLevel = isRecruiterOnboarding || isOnboardingV2;
8591
const {
8692
username,
8793
setUsername,
@@ -163,7 +169,7 @@ const RegistrationForm = ({
163169
);
164170
delete values['cf-turnstile-response'];
165171

166-
const requiresExperienceLevel = !isRecruiterOnboarding;
172+
const requiresExperienceLevel = !hideExperienceLevel;
167173
if (
168174
!values['traits.name']?.length ||
169175
!values['traits.username']?.length ||
@@ -275,9 +281,10 @@ const RegistrationForm = ({
275281
variant={ButtonVariant.Secondary}
276282
/>
277283
<Typography
278-
className={classNames('mt-0.5 flex-1', onboardingGradientClasses)}
284+
className="mt-0.5 flex-1 text-text-primary"
279285
tag={TypographyTag.H2}
280-
type={TypographyType.Title1}
286+
type={TypographyType.Title2}
287+
bold
281288
>
282289
Join daily.dev
283290
</Typography>
@@ -396,7 +403,7 @@ const RegistrationForm = ({
396403
}
397404
rightIcon={usernameIcon}
398405
/>
399-
{!isRecruiterOnboarding && (
406+
{!hideExperienceLevel && (
400407
<ExperienceLevelDropdown
401408
className={{ container: 'w-full' }}
402409
name="traits.experienceLevel"

packages/shared/src/components/auth/SocialRegistrationForm.tsx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import type {
55
AuthTriggersType,
66
SocialRegistrationParameters,
77
} from '../../lib/auth';
8-
import { AuthEventNames } from '../../lib/auth';
8+
import { AuthEventNames, AuthTriggers } from '../../lib/auth';
9+
import { useConditionalFeature } from '../../hooks/useConditionalFeature';
10+
import { featureOnboardingV2 } from '../../lib/featureManagement';
911
import { formToJson } from '../../lib/form';
1012
import { Button, ButtonVariant } from '../buttons/Button';
1113
import ImageInput from '../fields/ImageInput';
@@ -57,9 +59,15 @@ export const SocialRegistrationForm = ({
5759
onSignup,
5860
isLoading,
5961
simplified,
62+
trigger,
6063
}: SocialRegistrationFormProps): ReactElement => {
6164
const { logEvent } = useLogContext();
6265
const { user } = useContext(AuthContext);
66+
const { value: isOnboardingV2 } = useConditionalFeature({
67+
feature: featureOnboardingV2,
68+
shouldEvaluate: trigger === AuthTriggers.Onboarding,
69+
});
70+
const hideExperienceLevel = isOnboardingV2;
6371
const [nameHint, setNameHint] = useState<string>(null);
6472
const [usernameHint, setUsernameHint] = useState<string>(null);
6573
const [experienceLevelHint, setExperienceLevelHint] = useState<string>(null);
@@ -118,7 +126,7 @@ export const SocialRegistrationForm = ({
118126
return;
119127
}
120128

121-
if (!values.experienceLevel?.length) {
129+
if (!hideExperienceLevel && !values.experienceLevel?.length) {
122130
logError('Experience level not provided');
123131
setExperienceLevelHint('Please select your experience level');
124132
return;
@@ -229,18 +237,20 @@ export const SocialRegistrationForm = ({
229237
}
230238
rightIcon={isLoadingUsername ? <Loader /> : null}
231239
/>
232-
<ExperienceLevelDropdown
233-
className={{ container: 'w-full' }}
234-
name="experienceLevel"
235-
onChange={() => {
236-
if (experienceLevelHint) {
237-
setExperienceLevelHint(null);
238-
}
239-
}}
240-
valid={experienceLevelHint === null}
241-
hint={experienceLevelHint}
242-
saveHintSpace
243-
/>
240+
{!hideExperienceLevel && (
241+
<ExperienceLevelDropdown
242+
className={{ container: 'w-full' }}
243+
name="experienceLevel"
244+
onChange={() => {
245+
if (experienceLevelHint) {
246+
setExperienceLevelHint(null);
247+
}
248+
}}
249+
valid={experienceLevelHint === null}
250+
hint={experienceLevelHint}
251+
saveHintSpace
252+
/>
253+
)}
244254
<span className="border-b border-border-subtlest-tertiary pb-4 text-text-secondary typo-subhead">
245255
Your email will be used to send you product and community updates
246256
</span>

packages/shared/src/lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const slackIntegration = 'https://r.daily.dev/slack';
3333
export const statusPage = 'https://r.daily.dev/status';
3434
export const businessWebsiteUrl = 'https://r.daily.dev/business';
3535
export const appsUrl = 'https://daily.dev/apps';
36+
export const mobileAppUrl = 'https://app.daily.dev';
3637
export const timezoneSettingsUrl = 'https://r.daily.dev/timezone';
3738
export const isDevelopment = process.env.NODE_ENV === 'development';
3839
export const isProductionAPI =

packages/shared/src/lib/featureManagement.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ export const sharedPostPreviewFeature = new Feature(
151151
false,
152152
);
153153

154+
export const featureOnboardingV2 = new Feature('onboarding_v2', true);
155+
154156
export const featureUpvoteCountThreshold = new Feature<{
155157
threshold: number;
156158
belowThresholdLabel: string;

0 commit comments

Comments
 (0)