Skip to content

Commit 40bf788

Browse files
capJavertclaude
andauthored
Feat/onboarding v2 ab test (#5835)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b127c02 commit 40bf788

17 files changed

Lines changed: 3972 additions & 27 deletions

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ function AuthOptionsInner({
159159
simplified = false,
160160
ignoreMessages = false,
161161
onboardingSignupButton,
162+
autoTriggerProvider,
163+
socialProviderScopes,
162164
}: AuthOptionsProps): ReactElement {
163165
const { displayToast } = useToastNotification();
164166
const { syncSettings } = useSettingsContext();
@@ -552,6 +554,7 @@ function AuthOptionsInner({
552554
provider.toLowerCase(),
553555
callbackURL,
554556
additionalData,
557+
socialProviderScopes,
555558
);
556559
if (!socialUrl) {
557560
logEvent({
@@ -605,6 +608,21 @@ function AuthOptionsInner({
605608
}, 500);
606609
};
607610

611+
const onProviderClickRef = useRef(onProviderClick);
612+
onProviderClickRef.current = onProviderClick;
613+
const autoTriggerFiredProvider = useRef<string | null>(null);
614+
615+
useEffect(() => {
616+
if (
617+
!autoTriggerProvider ||
618+
autoTriggerFiredProvider.current === autoTriggerProvider
619+
) {
620+
return;
621+
}
622+
autoTriggerFiredProvider.current = autoTriggerProvider;
623+
onProviderClickRef.current(autoTriggerProvider, false);
624+
}, [autoTriggerProvider]);
625+
608626
const onProviderMessage = async (e: MessageEvent) => {
609627
if (checkIsLoginMessage(e)) {
610628
return handleLoginMessage(e);

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import { providerMap } from './common';
55
import OrDivider from './OrDivider';
66
import { useLogContext } from '../../contexts/LogContext';
77
import type { AuthTriggersType } from '../../lib/auth';
8-
import { AuthEventNames } from '../../lib/auth';
8+
import { AuthEventNames, AuthTriggers } from '../../lib/auth';
99
import type { ButtonProps } from '../buttons/Button';
1010
import { Button, ButtonSize, ButtonVariant } from '../buttons/Button';
1111
import { isIOSNative } from '../../lib/func';
1212

1313
import { MemberAlready } from '../onboarding/MemberAlready';
1414
import SignupDisclaimer from './SignupDisclaimer';
1515
import { FunnelTargetId } from '../../features/onboarding/types/funnelEvents';
16+
import { useConditionalFeature } from '../../hooks/useConditionalFeature';
17+
import { featureOnboardingV2 } from '../../lib/featureManagement';
1618

1719
interface ClassName {
1820
onboardingSignup?: string;
@@ -105,6 +107,10 @@ export const OnboardingRegistrationForm = ({
105107
trigger,
106108
}: OnboardingRegistrationFormProps): ReactElement => {
107109
const { logEvent } = useLogContext();
110+
const { value: isOnboardingV2 } = useConditionalFeature({
111+
feature: featureOnboardingV2,
112+
shouldEvaluate: trigger === AuthTriggers.Onboarding,
113+
});
108114

109115
const trackOpenSignup = () => {
110116
logEvent({
@@ -157,8 +163,9 @@ export const OnboardingRegistrationForm = ({
157163
<MemberAlready
158164
onLogin={() => onExistingEmail?.('')}
159165
className={{
160-
container:
161-
'mx-auto mt-6 text-center text-text-secondary typo-callout',
166+
container: isOnboardingV2
167+
? 'mx-auto mt-6 w-full justify-center border-t border-border-subtlest-tertiary pt-6 text-center text-text-secondary typo-callout'
168+
: 'mx-auto mt-6 text-center text-text-secondary typo-callout',
162169
login: '!text-inherit',
163170
}}
164171
/>

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

Lines changed: 18 additions & 4 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';
@@ -82,6 +84,11 @@ const RegistrationForm = ({
8284
const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
8385
const [name, setName] = useState('');
8486
const isRecruiterOnboarding = trigger === AuthTriggers.RecruiterSelfServe;
87+
const { value: isOnboardingV2 } = useConditionalFeature({
88+
feature: featureOnboardingV2,
89+
shouldEvaluate: trigger === AuthTriggers.Onboarding,
90+
});
91+
const hideExperienceLevel = isRecruiterOnboarding || isOnboardingV2;
8592
const {
8693
username,
8794
setUsername,
@@ -163,7 +170,7 @@ const RegistrationForm = ({
163170
);
164171
delete values['cf-turnstile-response'];
165172

166-
const requiresExperienceLevel = !isRecruiterOnboarding;
173+
const requiresExperienceLevel = !hideExperienceLevel;
167174
if (
168175
!values['traits.name']?.length ||
169176
!values['traits.username']?.length ||
@@ -275,9 +282,16 @@ const RegistrationForm = ({
275282
variant={ButtonVariant.Secondary}
276283
/>
277284
<Typography
278-
className={classNames('mt-0.5 flex-1', onboardingGradientClasses)}
285+
className={
286+
isOnboardingV2
287+
? 'mt-0.5 flex-1 text-text-primary'
288+
: classNames('mt-0.5 flex-1', onboardingGradientClasses)
289+
}
279290
tag={TypographyTag.H2}
280-
type={TypographyType.Title1}
291+
type={
292+
isOnboardingV2 ? TypographyType.Title2 : TypographyType.Title1
293+
}
294+
bold={isOnboardingV2}
281295
>
282296
Join daily.dev
283297
</Typography>
@@ -396,7 +410,7 @@ const RegistrationForm = ({
396410
}
397411
rightIcon={usernameIcon}
398412
/>
399-
{!isRecruiterOnboarding && (
413+
{!hideExperienceLevel && (
400414
<ExperienceLevelDropdown
401415
className={{ container: 'w-full' }}
402416
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/components/auth/common.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const providerMap: ProviderMap = {
4242
value: 'google',
4343
},
4444
github: {
45-
icon: <GitHubIcon className="socialIcon" secondary />,
45+
icon: <GitHubIcon className="socialIcon" />,
4646
label: 'GitHub',
4747
value: 'github',
4848
},
@@ -130,4 +130,6 @@ export interface AuthOptionsProps {
130130
targetId?: string;
131131
ignoreMessages?: boolean;
132132
onboardingSignupButton?: ButtonProps<'button'>;
133+
autoTriggerProvider?: string;
134+
socialProviderScopes?: string[];
133135
}
Lines changed: 3 additions & 0 deletions
Loading

packages/shared/src/components/icons/GitHub/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import React from 'react';
33
import type { IconProps } from '../../Icon';
44
import Icon from '../../Icon';
55
import WhiteIcon from './white.svg';
6+
import FilledIcon from './filled.svg';
67

78
export const GitHubIcon = (props: IconProps): ReactElement => (
8-
<Icon {...props} IconPrimary={WhiteIcon} IconSecondary={WhiteIcon} />
9+
<Icon {...props} IconPrimary={WhiteIcon} IconSecondary={FilledIcon} />
910
);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { gql } from 'graphql-request';
2+
import { gqlClient } from './common';
3+
4+
export type OnboardingTagsResult = {
5+
includeTags: string[];
6+
};
7+
8+
export const GITHUB_PROFILE_TAGS_MUTATION = gql`
9+
mutation GitHubProfileTags {
10+
githubProfileTags {
11+
includeTags
12+
}
13+
}
14+
`;
15+
16+
export const ONBOARDING_PROFILE_TAGS_MUTATION = gql`
17+
mutation OnboardingProfileTags($prompt: String!) {
18+
onboardingProfileTags(prompt: $prompt) {
19+
includeTags
20+
}
21+
}
22+
`;
23+
24+
export const requestGitHubProfileTags = async (): Promise<string[]> => {
25+
const res = await gqlClient.request<{
26+
githubProfileTags: OnboardingTagsResult;
27+
}>(GITHUB_PROFILE_TAGS_MUTATION);
28+
return res.githubProfileTags.includeTags;
29+
};
30+
31+
export const requestOnboardingProfileTags = async (
32+
prompt: string,
33+
): Promise<string[]> => {
34+
const res = await gqlClient.request<{
35+
onboardingProfileTags: OnboardingTagsResult;
36+
}>(ONBOARDING_PROFILE_TAGS_MUTATION, { prompt });
37+
return res.onboardingProfileTags.includeTags;
38+
};

packages/shared/src/lib/betterAuth.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ const getBetterAuthSocialRedirect = async (
149149
provider: string,
150150
callbackURL: string,
151151
additionalData?: SocialAdditionalData,
152+
scopes?: string[],
152153
): Promise<BetterAuthSocialRedirectResponse> => {
153154
const absoluteCallbackURL = callbackURL.startsWith('http')
154155
? callbackURL
@@ -167,6 +168,7 @@ const getBetterAuthSocialRedirect = async (
167168
errorCallbackURL: absoluteCallbackURL,
168169
disableRedirect: true,
169170
...(additionalData && { additionalData }),
171+
...(scopes?.length && { scopes }),
170172
},
171173
'Failed to get social auth URL',
172174
);
@@ -182,12 +184,14 @@ export const getBetterAuthSocialRedirectData = (
182184
provider: string,
183185
callbackURL: string,
184186
additionalData?: SocialAdditionalData,
187+
scopes?: string[],
185188
): Promise<BetterAuthSocialRedirectResponse> =>
186189
getBetterAuthSocialRedirect(
187190
'sign-in/social',
188191
provider,
189192
callbackURL,
190193
additionalData,
194+
scopes,
191195
);
192196

193197
export const getBetterAuthSocialUrl = (

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://api.daily.dev/mobile';
3637
export const timezoneSettingsUrl = 'https://r.daily.dev/timezone';
3738
export const isDevelopment = process.env.NODE_ENV === 'development';
3839
export const isProductionAPI =

0 commit comments

Comments
 (0)