Skip to content

Commit

Permalink
Stepper: calypso_signup_complete as in Start (#94706)
Browse files Browse the repository at this point in the history
* Use same tracking values as in start

* Pass destinationState to signup completion tracking

* Add newUser call

* Register in the store when it is a new account

* Better check

---------

Co-authored-by: escapemanuele <escapemanuele@gmail.com>
  • Loading branch information
renancarvalho and escapemanuele authored Sep 20, 2024
1 parent 66ff5bb commit 525322f
Show file tree
Hide file tree
Showing 11 changed files with 101 additions and 61 deletions.
8 changes: 4 additions & 4 deletions client/blocks/signup-form/passwordless.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,6 @@ class PasswordlessSignupForm extends Component {
const { flowName, queryArgs = {} } = this.props;
const { redirect_to, oauth2_client_id, oauth2_redirect } = queryArgs;

if ( this.props.onCreateAccountSuccess ) {
return this.props.onCreateAccountSuccess( userData );
}

recordRegistration( {
userData,
flow: flowName,
Expand All @@ -187,6 +183,10 @@ class PasswordlessSignupForm extends Component {
? { oauth2_client_id, oauth2_redirect }
: { redirect: redirect_to } ),
} );

if ( this.props.onCreateAccountSuccess ) {
return this.props.onCreateAccountSuccess( userData );
}
};

submitStep = ( data ) => {
Expand Down
3 changes: 3 additions & 0 deletions client/blocks/signup-form/signup-form-social-first.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface SignupFormSocialFirst {
extra: { first_name: string; last_name: string; username_hint: string };
} | null
) => void;
onCreateAccountSuccess?: ( data: AccountCreateReturn ) => void;
queryArgs: QueryArgs;
userEmail: string;
notice: JSX.Element | false;
Expand Down Expand Up @@ -69,6 +70,7 @@ const SignupFormSocialFirst = ( {
logInUrl,
socialServiceResponse,
handleSocialResponse,
onCreateAccountSuccess,
queryArgs,
userEmail,
notice,
Expand Down Expand Up @@ -175,6 +177,7 @@ const SignupFormSocialFirst = ( {
);
}
} }
onCreateAccountSuccess={ onCreateAccountSuccess }
{ ...gravatarProps }
/>
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { StepContainer } from '@automattic/onboarding';
import { Button } from '@wordpress/components';
import { useDispatch as useDataStoreDispatch } from '@wordpress/data';
import { useTranslate } from 'i18n-calypso';
import { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { AnyAction } from 'redux';
import { reloadProxy, requestAllBlogsAccess } from 'wpcom-proxy-request';
import SignupFormSocialFirst from 'calypso/blocks/signup-form/signup-form-social-first';
import FormattedHeader from 'calypso/components/formatted-header';
import { USER_STORE } from 'calypso/landing/stepper/stores';
import { recordTracksEvent } from 'calypso/lib/analytics/tracks';
import { login } from 'calypso/lib/paths';
import { AccountCreateReturn } from 'calypso/lib/signup/api/type';
Expand All @@ -30,6 +32,7 @@ const UserStepComponent: Step = function UserStep( {
const translate = useTranslate();
const isLoggedIn = useSelector( isUserLoggedIn );
const dispatch = useDispatch();
const { setIsNewUser } = useDataStoreDispatch( USER_STORE );
const { handleSocialResponse, notice, accountCreateResponse } = useHandleSocialResponse( flow );

const [ wpAccountCreateResponse, setWpAccountCreateResponse ] = useState< AccountCreateReturn >();
Expand Down Expand Up @@ -82,6 +85,7 @@ const UserStepComponent: Step = function UserStep( {
userEmail=""
notice={ notice }
isSocialFirst
onCreateAccountSuccess={ () => setIsNewUser( true ) }
/>
{ accountCreateResponse && 'bearer_token' in accountCreateResponse && (
<WpcomLoginForm
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// import config from '@automattic/calypso-config';
// import { initGoogleRecaptcha, recordGoogleRecaptchaAction } from 'calypso/lib/analytics/recaptcha';
import { useMutation } from '@tanstack/react-query';
import { useDispatch as useDataStoreDispatch } from '@wordpress/data';
import { USER_STORE } from 'calypso/landing/stepper/stores';
import { createAccount } from 'calypso/lib/signup/api/account';
import { useDispatch } from 'calypso/state';
import {
Expand All @@ -11,6 +13,7 @@ import {

export function useCreateAccountMutation() {
const dispatch = useDispatch();
const { setIsNewUser } = useDataStoreDispatch( USER_STORE );

return useMutation( {
mutationKey: [ 'create' ],
Expand All @@ -21,6 +24,9 @@ export function useCreateAccountMutation() {
} );
},
onSuccess: ( data ) => {
if ( 'isNewAccountCreated' in data && data.isNewAccountCreated ) {
setIsNewUser( true );
}
dispatch( {
type: SOCIAL_LOGIN_REQUEST_SUCCESS,
data: data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const ProcessingStep: React.FC< ProcessingStepProps > = function ( props ) {
if ( availableFlows[ flow ] ) {
availableFlows[ flow ]().then( ( flowExport ) => {
if ( flowExport.default.isSignupFlow ) {
recordSignupComplete();
recordSignupComplete( { ...destinationState } );
}
} );
}
Expand Down
113 changes: 62 additions & 51 deletions client/landing/stepper/hooks/use-record-signup-complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,79 @@ import { useSelect } from '@wordpress/data';
import { useCallback } from 'react';
import { USER_STORE, ONBOARD_STORE } from 'calypso/landing/stepper/stores';
import { SIGNUP_DOMAIN_ORIGIN, recordSignupComplete } from 'calypso/lib/analytics/signup';
import { useSelector } from 'calypso/state';
import isUserRegistrationDaysWithinRange from 'calypso/state/selectors/is-user-registration-days-within-range';
import { useSite } from './use-site';
import type { UserSelect, OnboardSelect } from '@automattic/data-stores';

export const useRecordSignupComplete = ( flow: string | null ) => {
const site = useSite();
const siteId = site?.ID || null;
const theme = site?.options?.theme_slug || '';
const { domainCartItem, planCartItem, siteCount, selectedDomain } = useSelect( ( select ) => {
return {
siteCount: ( select( USER_STORE ) as UserSelect ).getCurrentUser()?.site_count,
domainCartItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getDomainCartItem(),
planCartItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getPlanCartItem(),
selectedDomain: ( select( ONBOARD_STORE ) as OnboardSelect ).getSelectedDomain(),
};
}, [] );
const { domainCartItem, planCartItem, siteCount, selectedDomain, isNewUser } = useSelect(
( select ) => {
return {
siteCount: ( select( USER_STORE ) as UserSelect ).getCurrentUser()?.site_count,
domainCartItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getDomainCartItem(),
planCartItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getPlanCartItem(),
selectedDomain: ( select( ONBOARD_STORE ) as OnboardSelect ).getSelectedDomain(),
isNewUser: ( select( USER_STORE ) as UserSelect ).isNewUser(),
};
},
[]
);

return useCallback( () => {
// FIXME: once moving to the Stepper version of User step,
// wire the value of `isNewUser()` from the user store.
const isNewUser = ! siteCount;
const isNewishUser = useSelector( ( state ) =>
isUserRegistrationDaysWithinRange( state, null, 0, 7 )
);

// FIXME:
// currently it's impossible to derive this data since it requires
// the length of registration, so I use isNewUser here as an approximation
const isNew7DUserSite = isNewUser;
return useCallback(
( signupCompletionState: Record< string, unknown > ) => {
const siteSlug = site?.slug ?? signupCompletionState?.siteSlug;

// Domain product slugs can be a domain purchases like dotcom_domain or dotblog_domain or a mapping like domain_mapping
// When purchasing free subdomains the product_slugs is empty (since there is no actual produce being purchased)
// so we avoid capturing the product slug in these instances.
const domainProductSlug = domainCartItem?.product_slug ?? undefined;
const isNew7DUserSite = !! (
isNewUser ||
( isNewishUser && siteSlug && siteCount && siteCount <= 1 )
);

// Domain cart items can sometimes be included when free. So the selected domain is explicitly checked to see if it's free.
// For mappings and transfers this attribute should be empty but it needs to be checked.
const hasCartItems = !! ( domainProductSlug || planCartItem ); // see the function `dependenciesContainCartItem()
// Domain product slugs can be a domain purchases like dotcom_domain or dotblog_domain or a mapping like domain_mapping
// When purchasing free subdomains the product_slugs is empty (since there is no actual produce being purchased)
// so we avoid capturing the product slug in these instances.
const domainProductSlug = domainCartItem?.product_slug ?? undefined;

// When there is no plan put in the cart, `planCartItem` is `null` instead of `undefined` like domainCartItem.
// It worths a investigation of whether the both should behave the same.
const planProductSlug = planCartItem?.product_slug ?? undefined;
// To have a paid domain item it has to either be a paid domain or a different domain product like mapping or transfer.
const hasPaidDomainItem =
( selectedDomain && ! selectedDomain.is_free ) || !! domainProductSlug;
// Domain cart items can sometimes be included when free. So the selected domain is explicitly checked to see if it's free.
// For mappings and transfers this attribute should be empty but it needs to be checked.
const hasCartItems = !! ( domainProductSlug || planCartItem ); // see the function `dependenciesContainCartItem()

recordSignupComplete(
{
flow,
siteId,
isNewUser,
hasCartItems,
isNew7DUserSite,
theme,
intent: flow,
startingPoint: flow,
isBlankCanvas: theme?.includes( 'blank-canvas' ),
planProductSlug,
domainProductSlug,
isMapping:
hasPaidDomainItem && domainCartItem ? isDomainMapping( domainCartItem ) : undefined,
isTransfer:
hasPaidDomainItem && domainCartItem ? isDomainTransfer( domainCartItem ) : undefined,
signupDomainOrigin: SIGNUP_DOMAIN_ORIGIN.NOT_SET,
},
true
);
}, [ domainCartItem, flow, planCartItem, selectedDomain, siteCount, siteId, theme ] );
// When there is no plan put in the cart, `planCartItem` is `null` instead of `undefined` like domainCartItem.
// It worths a investigation of whether the both should behave the same.
const planProductSlug = planCartItem?.product_slug ?? undefined;
// To have a paid domain item it has to either be a paid domain or a different domain product like mapping or transfer.
const hasPaidDomainItem =
( selectedDomain && ! selectedDomain.is_free ) || !! domainProductSlug;

recordSignupComplete(
{
flow,
siteId: siteId ?? signupCompletionState?.siteId,
isNewUser,
hasCartItems,
isNew7DUserSite,
theme,
intent: flow,
startingPoint: flow,
isBlankCanvas: theme?.includes( 'blank-canvas' ),
planProductSlug,
domainProductSlug,
isMapping:
hasPaidDomainItem && domainCartItem ? isDomainMapping( domainCartItem ) : undefined,
isTransfer:
hasPaidDomainItem && domainCartItem ? isDomainTransfer( domainCartItem ) : undefined,
signupDomainOrigin: SIGNUP_DOMAIN_ORIGIN.NOT_SET,
},
true
);
},
[ domainCartItem, flow, planCartItem, selectedDomain, siteCount, siteId, theme ]
);
};
4 changes: 1 addition & 3 deletions client/lib/signup/api/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,7 @@ export async function createAccount( {
username,
signupType: service ? 'social' : 'default',
} );

return response;
}

return response;
return { ...response, isNewAccountCreated };
}
1 change: 1 addition & 0 deletions client/lib/signup/api/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type AccountCreationAPIResponse =
oauth2_redirect?: string;
marketing_price_group?: string;
created_account?: boolean;
isNewAccountCreated?: boolean;
}
| {
error: 'user_exists';
Expand Down
10 changes: 9 additions & 1 deletion packages/data-stores/src/user/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,25 @@ export function createActions() {
type: 'RECEIVE_CURRENT_USER_FAILED' as const,
} );

const setIsNewUser = ( isNewUser: boolean ) => ( {
type: 'SET_IS_NEW_USER' as const,
isNewUser,
} );

return {
receiveCurrentUser,
receiveCurrentUserFailed,
setIsNewUser,
};
}

export type ActionCreators = ReturnType< typeof createActions >;

export type Action =
| ReturnType<
ActionCreators[ 'receiveCurrentUser' ] | ActionCreators[ 'receiveCurrentUserFailed' ]
| ActionCreators[ 'receiveCurrentUser' ]
| ActionCreators[ 'receiveCurrentUserFailed' ]
| ActionCreators[ 'setIsNewUser' ]
>
// Type added so we can dispatch actions in tests, but has no runtime cost
| { type: 'TEST_ACTION' };
10 changes: 9 additions & 1 deletion packages/data-stores/src/user/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ export const currentUser: Reducer< CurrentUser | null | undefined, Action > = (
return state;
};

const reducer = combineReducers( { currentUser } );
const isNewUser: Reducer< boolean, Action > = ( state = false, action ) => {
switch ( action.type ) {
case 'SET_IS_NEW_USER':
return action.isNewUser;
}
return state;
};

const reducer = combineReducers( { currentUser, isNewUser } );
export type State = ReturnType< typeof reducer >;

export default reducer;
1 change: 1 addition & 0 deletions packages/data-stores/src/user/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const getState = ( state: State ) => state;

export const getCurrentUser = ( state: State ) => state.currentUser;
export const isCurrentUserLoggedIn = ( state: State ) => !! state.currentUser?.ID;
export const isNewUser = ( state: State ) => state.isNewUser;

0 comments on commit 525322f

Please sign in to comment.