Skip to content

feat(funnels): enable funnels on onboarding #4489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 97 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
77f5ba3
feat: refactor cookie handling and add onboarding boot data hook
ilasw May 13, 2025
3bb380b
feat: add initial organic registration funnel step and component
ilasw May 13, 2025
510da23
feat: add organic registration step to funnel background
ilasw May 14, 2025
43d69c8
feat: add storybook stories for FunnelOrganicRegistration component
ilasw May 14, 2025
845ef9c
refactor: update onboarding props and Organic signup UI
ilasw May 14, 2025
5148a1e
refactor: update return type of withIsActiveGuard
ilasw May 14, 2025
f938ee8
refactor: replace useFeature with props for AuthOptions
ilasw May 15, 2025
5e64be3
feat: add image support and auth options
ilasw May 15, 2025
296cbc6
refactor: removed unused components, logic, and dependencies
ilasw May 15, 2025
28c98fb
refactor: better mock for useRouter
ilasw May 15, 2025
c2ba914
feat: removed reorder experiment and unnecessary code dependencies
ilasw May 15, 2025
959a8a2
feat: add OnboardingHeader into FunnelOrganicRegistration
ilasw May 15, 2025
0c2219d
refactor: remove onboardingVisual feature
ilasw May 15, 2025
85ad504
refactor: migrate onboarding auth state to Jotai and make login link …
ilasw May 15, 2025
cf6bff1
refactor: cleaned unused onboarding logic and improved store update w…
ilasw May 15, 2025
6095d12
fix: apply SEO on Page and renamed OnboardingPage to Onboarding
ilasw May 15, 2025
83e9c9d
refactor: extracted onboarding auth logic into custom hook
ilasw May 15, 2025
2e1278a
refactor: simplified OnboardingHeader component and removed unused On…
ilasw May 16, 2025
179945a
refactor: add onComplete function
ilasw May 16, 2025
fc8a203
refactor: updated funnelStore imports
ilasw May 16, 2025
289ad8f
refactor: simplified OnboardingHeader and removed click handle on logo
ilasw May 16, 2025
4709629
feat: go back on default on forgot pw is already default display
ilasw May 16, 2025
0b4e8cc
refactor: added logging for successful registration debug
ilasw May 16, 2025
74fa3b1
feat: added onboarding action handling
ilasw May 16, 2025
3e1ec5a
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 16, 2025
75dffe5
refactor: fixed import path in StepHeadline
ilasw May 16, 2025
7c4d073
fix: lint
ilasw May 16, 2025
0c95838
feat: trigger profile fetch on register and added onTransition for or…
ilasw May 16, 2025
089d61e
feat: add stepper in onboarding page and full-width step support
ilasw May 19, 2025
da6c7b2
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 19, 2025
de26896
fix: auth state on signup
ilasw May 21, 2025
b571fb3
fix: return null early if user is logged in or auth is not ready
ilasw May 21, 2025
29aa06d
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 21, 2025
c13aeb2
fix: adjusted sign back logic
ilasw May 21, 2025
614abb5
refactor: added featureKey parameter to switch between onboarding and…
ilasw May 21, 2025
5fbcff4
refactor: updated onboarding containers for desktop
ilasw May 21, 2025
e2ab8c4
refactor: set user logged in if is logged on onboarding funnel
ilasw May 21, 2025
92bbfa9
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 21, 2025
66de0f3
fix: removed unnecessary placeholder text
ilasw May 21, 2025
dde01b4
refactor: cleaned code and simplified import paths in onboarding comp…
ilasw May 21, 2025
8d6e053
refactor: simplified rendering conditions
ilasw May 21, 2025
10d49f1
fix: adjusted container max-width
ilasw May 22, 2025
eb15ddd
fix: simplified onboarding auth logic and fixed showing login instead…
ilasw May 22, 2025
9765c7f
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 22, 2025
8e8736d
refactor: updated image prop
ilasw May 22, 2025
6e34833
refactor: rollback onForgotPasswordBack
ilasw May 22, 2025
8c6808c
fix: add default
ilasw May 22, 2025
e7ce855
feat: updated story
ilasw May 22, 2025
f91d17c
feat: updated story
ilasw May 22, 2025
d46e3f8
chore: added dompurify dependency to storybook
ilasw May 22, 2025
78feef9
fix: lint
ilasw May 22, 2025
e54b8e1
test: updated input selectors in RegistrationForm
ilasw May 22, 2025
321fce9
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 22, 2025
42a3793
fix: removed default id from onboarding funnel
ilasw May 22, 2025
1d167dd
refactor: removed unnecessary fragment
ilasw May 23, 2025
7271a92
refactor: cleaned (won) experiment for onboarding reordering
ilasw May 23, 2025
82ea3c0
fix: added footer links in authenticating onboarding screen
ilasw May 23, 2025
1d2b62b
refactor: updated useEffect dependencies in onboarding
ilasw May 23, 2025
62181a8
chore: removed unused var
ilasw May 23, 2025
933bdd2
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 23, 2025
19f2346
fix: import path
ilasw May 23, 2025
cdfe238
chore: move PlusCards components
ilasw May 23, 2025
0f376ec
feat: added funnel tracking for signup provider buttons
ilasw May 23, 2025
376b52a
feat: added funnel tracking for signup provider buttons
ilasw May 23, 2025
dfb52f2
feat: added funnel tracking attribute to onboarding header logo
ilasw May 23, 2025
44b1db0
feat: added funnel tracking attribute to onboarding header logo
ilasw May 23, 2025
df523d1
feat: added PlusCards step to funnel types
ilasw May 23, 2025
1c90606
feat: added FunnelPlusCards step
ilasw May 23, 2025
87bb85a
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 23, 2025
de03b8e
fix: lint unused parameter
ilasw May 23, 2025
4fcbaf9
fix: updated default featureKey and URL path for onboarding boot API
ilasw May 26, 2025
9adfbe3
fix: vertical center for pricing cards
ilasw May 26, 2025
bd48739
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 27, 2025
cd873c3
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 27, 2025
1258318
fix: typo in featureKey
ilasw May 27, 2025
60cf684
fix: add initialStepId on load
ilasw May 27, 2025
d66f8c6
chore: add test console.log
ilasw May 27, 2025
205c14e
feat: implement onboarding completion redirect logic
ilasw May 28, 2025
280a7e8
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 28, 2025
d8781d3
refactor: added isOnboardingActionsReady check
ilasw May 28, 2025
c2a7f8c
refactor: added withIsActiveGuard to FunnelPlusCards component
ilasw May 28, 2025
a535eb6
refactor: updated onboarding social auth flow
ilasw May 28, 2025
7254e1d
refactor: removed id and stepId params from onboarding redirects
ilasw May 28, 2025
1551ae1
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 28, 2025
43c3595
feat: added selected tags on funnel step transition
ilasw May 28, 2025
f646d02
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 28, 2025
1e191b2
chore: added log
ilasw May 28, 2025
70506ab
fix: update boot default featureKey
ilasw May 29, 2025
23591ff
Merge branch 'main' of https://github.com/dailydotdev/apps into MI-89…
ilasw May 29, 2025
3de8c02
fix: disable funnel pricing when pricing step is not available
ilasw May 29, 2025
6fedd5a
feat: updated onboarding flow to handle action completion
ilasw May 29, 2025
ec11b1a
feat: updated onboarding redirection
ilasw May 29, 2025
e769a5a
fix: nothing show
ilasw May 29, 2025
b3ec982
fix: isOnboardingActionsReady is always false for guest users
ilasw May 29, 2025
2ae5ce3
fix: funnelPricing is array, useProductPricing will never be enabled
ilasw May 29, 2025
6f2b14a
fix: plus cards as full-width step
ilasw May 29, 2025
7168f68
fix: plus cards not centered
ilasw May 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 14 additions & 23 deletions packages/shared/src/components/auth/AuthOptionsInner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ import { labels } from '../../lib';
import usePersistentState from '../../hooks/usePersistentState';
import { IconSize } from '../Icon';
import { MailIcon } from '../icons';
import { useFeature } from '../GrowthBookProvider';
import { featureOnboardingReorder } from '../../lib/featureManagement';
import { usePixelsContext } from '../../contexts/PixelsContext';
import { useAuthData } from '../../contexts/AuthDataContext';

Expand All @@ -61,12 +59,6 @@ const OnboardingRegistrationForm = dynamic(() =>
).then((mod) => mod.OnboardingRegistrationForm),
);

const OnboardingRegistrationFormExperiment = dynamic(() =>
import(
/* webpackChunkName: "onboardingRegistrationFormExperiment" */ './OnboardingRegistrationForm'
).then((mod) => mod.OnboardingRegistrationFormExperiment),
);

const AuthSignBack = dynamic(() =>
import(/* webpackChunkName: "authSignBack" */ './AuthSignBack').then(
(mod) => mod.AuthSignBack,
Expand Down Expand Up @@ -130,8 +122,8 @@ function AuthOptionsInner({
);
const { refetchBoot, user, isFunnel } = useAuthContext();
const router = useRouter();
const isOnboardingPage = !!router?.pathname?.startsWith('/onboarding');
const isReorderExperiment = useFeature(featureOnboardingReorder);
const isOnboardingOrFunnel =
!!router?.pathname?.startsWith('/onboarding') || isFunnel;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe put this condition within the context itself so everything that uses this will get the same values.

const [flow, setFlow] = useState('');
const [activeDisplay, setActiveDisplay] = useState(() =>
storage.getItem(SIGNIN_METHOD_KEY) && !forceDefaultDisplay
Expand Down Expand Up @@ -206,7 +198,7 @@ function AuthOptionsInner({
onRedirect: (redirect) => {
windowPopup.current.location.href = redirect;
},
keepSession: isFunnel,
keepSession: isOnboardingOrFunnel,
});

const {
Expand All @@ -223,12 +215,14 @@ function AuthOptionsInner({
return displayToast(labels.auth.error.generic);
},
});
const onProfileSuccess = async (options: { redirect?: string } = {}) => {
const onProfileSuccess = async (
options: { redirect?: string; setSignBack?: boolean } = {},
) => {
Comment on lines +218 to +220
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm now using this onProfileSuccess after the signup with email.
This was calling the onSignBackLogin that created redirection bugs within the funnel. Added an option to handle it.

setIsRegistration(true);
const { redirect } = options;
const { redirect, setSignBack = true } = options;
const { data } = await refetchBoot();

if (data.user) {
if (data.user && setSignBack) {
const provider = chosenProvider || 'password';
onSignBackLogin(data.user as LoggedUser, provider as SignBackProvider);
}
Expand Down Expand Up @@ -268,7 +262,7 @@ function AuthOptionsInner({
if (!isNativeAuthSupported(provider)) {
windowPopup.current = window.open();
}
setChosenProvider(provider);
await setChosenProvider(provider);
await onSocialRegistration(provider);
onAuthStateUpdate?.({ isLoading: true });
};
Expand Down Expand Up @@ -345,6 +339,7 @@ function AuthOptionsInner({
return onSuccessfulLogin?.();
}

onAuthStateUpdate({ defaultDisplay: AuthDisplay.SocialRegistration });
return onSetActiveDisplay(AuthDisplay.SocialRegistration);
};

Expand All @@ -363,11 +358,12 @@ function AuthOptionsInner({
await syncSettings();
};

const onRegister = (params: RegistrationFormValues) => {
validateRegistration({
const onRegister = async (params: RegistrationFormValues) => {
await validateRegistration({
...params,
method: 'password',
});
await onProfileSuccess({ setSignBack: false });
};

const onForgotPassword = (withEmail?: string) => {
Expand All @@ -389,11 +385,6 @@ function AuthOptionsInner({
onPasswordLogin(params);
};

const RegistrationFormComponent =
isReorderExperiment && isOnboardingPage
? OnboardingRegistrationFormExperiment
: OnboardingRegistrationForm;

return (
<div
className={classNames(
Expand Down Expand Up @@ -477,7 +468,7 @@ function AuthOptionsInner({
/>
</Tab>
<Tab label={AuthDisplay.OnboardingSignup}>
<RegistrationFormComponent
<OnboardingRegistrationForm
onContinueWithEmail={() => {
onAuthStateUpdate({
isAuthenticating: true,
Expand Down
13 changes: 0 additions & 13 deletions packages/shared/src/components/auth/AuthenticationBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import classed from '../../lib/classed';
import { OnboardingHeadline } from './OnboardingHeadline';
import AuthOptions from './AuthOptions';
import { AuthTriggers } from '../../lib/auth';
import { MemberAlready } from '../onboarding/MemberAlready';
import { useAuthContext } from '../../contexts/AuthContext';
import { authGradientBg, BottomBannerContainer } from '../banners';
import { ButtonVariant } from '../buttons/common';
Expand Down Expand Up @@ -69,18 +68,6 @@ export function AuthenticationBanner({
variant: ButtonVariant.Primary,
}}
/>
<MemberAlready
onLogin={() =>
showLogin({
trigger: AuthTriggers.Onboarding,
options: { isLogin: true },
})
}
className={{
container: 'justify-center text-text-secondary typo-callout',
login: 'font-bold',
}}
/>
</Section>
</div>
</BottomBannerContainer>
Expand Down
139 changes: 3 additions & 136 deletions packages/shared/src/components/auth/OnboardingRegistrationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ReactElement } from 'react';
import React, { useEffect } from 'react';
import classNames from 'classnames';
import type { AuthFormProps } from './common';
import { providerMap } from './common';
import OrDivider from './OrDivider';
Expand All @@ -9,16 +8,11 @@ import type { AuthTriggersType } from '../../lib/auth';
import { AuthEventNames } from '../../lib/auth';
import type { ButtonProps } from '../buttons/Button';
import { Button, ButtonSize, ButtonVariant } from '../buttons/Button';
import AuthForm from './AuthForm';
import { TextField } from '../fields/TextField';
import { MailIcon } from '../icons';
import { IconSize } from '../Icon';
import Alert, { AlertParagraph, AlertType } from '../widgets/Alert';
import { isIOSNative } from '../../lib/func';

import { useCheckExistingEmail } from '../../hooks';
import { MemberAlready } from '../onboarding/MemberAlready';
import SignupDisclaimer from './SignupDisclaimer';
import { FunnelTargetId } from '../../features/onboarding/types/funnelEvents';

interface ClassName {
onboardingSignup?: string;
Expand Down Expand Up @@ -101,135 +95,6 @@ const getSignupProviders = () => {
};

export const OnboardingRegistrationForm = ({
onSignup,
onExistingEmail,
onProviderClick,
targetId,
isReady,
trigger,
className,
onboardingSignupButton,
}: OnboardingRegistrationFormProps): ReactElement => {
const { logEvent } = useLogContext();
const { email, onEmailCheck } = useCheckExistingEmail({
onBeforeEmailCheck: () => {
logEvent({
event_name: 'click',
target_type: AuthEventNames.SignUpProvider,
target_id: 'email',
extra: JSON.stringify({ trigger }),
});
},
onValidEmail: onSignup,
});
const onboardingSignupButtonProps = {
size: ButtonSize.Large,
variant: ButtonVariant.Primary,
...onboardingSignupButton,
};

const onSocialClick = (provider: string) => {
onProviderClick?.(provider, email.alreadyExists);
};

useEffect(() => {
logEvent({
event_name: email.alreadyExists
? AuthEventNames.OpenLogin
: AuthEventNames.OpenSignup,
extra: JSON.stringify({ trigger }),
target_id: targetId,
});
// @NOTE see https://dailydotdev.atlassian.net/l/cp/dK9h1zoM
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [email.alreadyExists]);

return (
<>
<AuthForm
className={classNames('mb-8 gap-8', className?.onboardingForm)}
onSubmit={onEmailCheck}
aria-label={email.alreadyExists ? 'Login form' : 'Signup form'}
>
<TextField
leftIcon={
<MailIcon aria-hidden role="presentation" size={IconSize.Small} />
}
required
inputId="email"
label="Email"
type="email"
name="email"
focused
pressed
className={{ container: 'bg-overlay-active-salt' }}
/>

{email.alreadyExists && (
<>
<Alert type={AlertType.Error} flexDirection="flex-row">
<AlertParagraph className="!mt-0 flex-1">
Email is taken. Existing user?{' '}
<button
type="button"
onClick={() => {
onExistingEmail(email.value);
}}
className="font-bold underline"
>
Log in.
</button>
</AlertParagraph>
</Alert>
</>
)}

<Button
className="w-full"
loading={!isReady || email.isCheckPending}
type="submit"
variant={ButtonVariant.Primary}
>
Sign up - Free forever ➔
</Button>
</AuthForm>
<OrDivider
className={{
container: classNames('mb-8', className?.onboardingDivider),
text: 'text-text-tertiary',
}}
label="Or sign up with"
aria-hidden
/>
<div
aria-label="Social login buttons"
className={classNames(
'flex flex-col gap-8 pb-8',
className.onboardingSignup,
)}
role="list"
>
{getSignupProviders().map((provider) => (
<Button
aria-label={`${email.alreadyExists ? 'Login' : 'Signup'} using ${
provider.label
}`}
icon={provider.icon}
key={provider.value}
loading={!isReady}
onClick={() => onSocialClick(provider.value)}
type="button"
{...onboardingSignupButtonProps}
>
{provider.label}
</Button>
))}
</div>
</>
);
};

export const OnboardingRegistrationFormExperiment = ({
isReady,
onContinueWithEmail,
onExistingEmail,
Expand Down Expand Up @@ -266,6 +131,7 @@ export const OnboardingRegistrationFormExperiment = ({
<Button
aria-label={`Continue using ${provider.label}`}
className="w-full"
data-funnel-track={FunnelTargetId.SignupProvider}
icon={provider.icon}
loading={!isReady}
onClick={() => onProviderClick?.(provider.value, false)}
Expand Down Expand Up @@ -297,6 +163,7 @@ export const OnboardingRegistrationFormExperiment = ({
<Button
aria-label="Signup using email"
className="mb-8"
data-funnel-track={FunnelTargetId.SignupProvider}
onClick={() => {
trackOpenSignup();
onContinueWithEmail?.();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {
mockRegistrationFlow,
} from '../../../__tests__/fixture/auth';
import { AuthContextProvider } from '../../contexts/AuthContext';
import type { AuthOptionsProps } from './AuthOptions';
import AuthOptions from './AuthOptions';
import SettingsContext from '../../contexts/SettingsContext';
import { mockGraphQL } from '../../../__tests__/helpers/graphql';
import { GET_USERNAME_SUGGESTION } from '../../graphql/users';
import { AuthTriggers } from '../../lib/auth';
import type { AuthOptionsProps } from './common';

const user = null;

Expand Down Expand Up @@ -92,11 +92,11 @@ const renderRegistration = async (
},
});
await screen.findByTestId('registration_form');
const nameInput = screen.getByLabelText('Name');
const nameInput = screen.getByPlaceholderText('Name');
fireEvent.input(screen.getByPlaceholderText('Enter a username'), {
target: { value: username },
});
fireEvent.input(screen.getByLabelText('Name'), {
fireEvent.input(screen.getByPlaceholderText('Name'), {
target: { value: name },
});
simulateTextboxInput(nameInput as HTMLTextAreaElement, name);
Expand Down
Loading