diff --git a/frontend/common/stores/account-store.js b/frontend/common/stores/account-store.js index 2deb58f93073..0d4e733e852a 100644 --- a/frontend/common/stores/account-store.js +++ b/frontend/common/stores/account-store.js @@ -17,6 +17,8 @@ import { hidePylon, identifyChatUser } from 'common/loadChat' import { service } from 'common/service' import { getBuildVersion } from 'common/services/useBuildVersion' import { createOnboardingSupportOptIn } from 'common/services/useOnboardingSupportOptIn' +import flagsmith from '@flagsmith/flagsmith' +import isFreeEmailDomain from 'common/utils/isFreeEmailDomain' const controller = { acceptInvite: (id) => { @@ -244,6 +246,18 @@ const controller = { } data.setToken(Project.cookieAuthEnabled ? 'true' : res.key) + if (res.is_new_user) { + try { + flagsmith.trackEvent('new_signup', { + metadata: { + invite: !!API.getInvite(), + signup_method: type, + }, + }) + } catch (e) { + // never let analytics break the login flow + } + } return controller.onLogin() }) .catch((e) => API.ajaxHandler(store, e)) @@ -265,6 +279,20 @@ const controller = { .then(async (res) => { data.setToken(Project.cookieAuthEnabled ? 'true' : res.key) API.trackEvent(Constants.events.REGISTER) + const freeEmailDomain = isFreeEmailDomain(user.email) + try { + flagsmith.trackEvent('new_signup', { + metadata: { + ...(freeEmailDomain && { domain: user.email.split('@')[1] }), + free_email_domain: freeEmailDomain, + invite: !!API.getInvite(), + signup_method: 'email', + utm_source: user.utm_data?.utm_source, + }, + }) + } catch (e) { + // never let analytics break the signup flow + } if (API.getReferrer()) { API.trackEvent( Constants.events.REFERRER_REGISTERED(API.getReferrer().utm_source), diff --git a/frontend/common/useSignupExperiment.ts b/frontend/common/useSignupExperiment.ts new file mode 100644 index 000000000000..443ce00221b3 --- /dev/null +++ b/frontend/common/useSignupExperiment.ts @@ -0,0 +1,36 @@ +import { useEffect, useRef, useState } from 'react' +import flagsmith from '@flagsmith/flagsmith' +import isFreeEmailDomain from './utils/isFreeEmailDomain' +import { storageGet, storageSet } from './safeLocalStorage' + +// The experiment only starts (identify + exposure) once the visitor types a +// free email domain — corporate-email visitors never enter the experiment. +export default function useSignupExperiment( + useEnvironmentFlag: boolean, + email: string, +) { + const [variant, setVariant] = useState() + const started = useRef(false) + + useEffect(() => { + if (useEnvironmentFlag || started.current || !isFreeEmailDomain(email)) { + return + } + started.current = true + const identifyAndExpose = async () => { + const id = + storageGet('signup_anonymous_id') || + (crypto.randomUUID?.() ?? `${Date.now()}-${Math.random()}`) + storageSet('signup_anonymous_id', id) + // @ts-expect-error transient is missing from the SDK's identify type + await flagsmith.identify(id, {}, true) + const flag = flagsmith.getExperimentFlag('signup_corporate_only') + setVariant(flag?.enabled ? flag.variant : undefined) + } + identifyAndExpose().catch(() => { + started.current = false + }) + }, [useEnvironmentFlag, email]) + + return variant +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4577b744f36e..2906ede9d66a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,7 +25,7 @@ "@dnd-kit/abstract": "^0.3.2", "@dnd-kit/helpers": "^0.3.2", "@dnd-kit/react": "^0.3.2", - "@flagsmith/flagsmith": "11.0.0-internal.11", + "@flagsmith/flagsmith": "12.1.0", "@ionic/react": "^7.5.3", "@react-oauth/google": "^0.2.8", "@reduxjs/toolkit": "1.9.1", @@ -3234,9 +3234,9 @@ } }, "node_modules/@flagsmith/flagsmith": { - "version": "11.0.0-internal.11", - "resolved": "https://registry.npmjs.org/@flagsmith/flagsmith/-/flagsmith-11.0.0-internal.11.tgz", - "integrity": "sha512-u2Mprn3GLz53TUEo1rpfK/18BMQbwf9Z5mKhs9jnUnRDjqh23Ud+83AwSPNjif5EjgXbrEg4p76gY8k/E/1RoA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@flagsmith/flagsmith/-/flagsmith-12.1.0.tgz", + "integrity": "sha512-xcT1J4gHWLYYXv+0jUNVSFM3ZPtKPdSe6n0ruAjwbqaGmTXL+B6W66b46J78SLJcCCgOlAuoYNYgGa/trPCBdw==", "license": "BSD-3-Clause" }, "node_modules/@floating-ui/core": { diff --git a/frontend/package.json b/frontend/package.json index 419e9ff0fc6f..e88a766e2eaf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -63,7 +63,7 @@ "@dnd-kit/abstract": "^0.3.2", "@dnd-kit/helpers": "^0.3.2", "@dnd-kit/react": "^0.3.2", - "@flagsmith/flagsmith": "11.0.0-internal.11", + "@flagsmith/flagsmith": "12.1.0", "@ionic/react": "^7.5.3", "@react-oauth/google": "^0.2.8", "@reduxjs/toolkit": "1.9.1", diff --git a/frontend/web/components/pages/HomePage.tsx b/frontend/web/components/pages/HomePage.tsx index debcf25d64f3..a7883751157b 100644 --- a/frontend/web/components/pages/HomePage.tsx +++ b/frontend/web/components/pages/HomePage.tsx @@ -31,6 +31,7 @@ import AccountStore from 'common/stores/account-store' import { LoginRequest, RegisterRequest } from 'common/types/requests' import { useGetBuildVersionQuery } from 'common/services/useBuildVersion' import { useUTMs } from 'common/useUTMs' +import useSignupExperiment from 'common/useSignupExperiment' const HomePage: React.FC = () => { const history = useHistory() @@ -180,6 +181,13 @@ const HomePage: React.FC = () => { currentLocation.indexOf('signup') !== -1) const disableSignup = preventSignup && isSignup const preventEmailPassword = Project.preventEmailPassword + + const signupVariant = useSignupExperiment( + !isSignup || isInvite || !!preventEmailPassword || !!AccountStore.getUser(), + email, + ) + const blockGenericEmailDomain = + signupVariant === 'signup_corporate_only' && isFreeEmailDomain(email) const disableForgotPassword = Project.preventForgotPassword const oauths: React.ReactNode[] = [] const disableOauthRegister = Utils.getFlagsmithHasFeature( @@ -627,12 +635,16 @@ const HomePage: React.FC = () => { name='email' id='email' /> - {isFreeEmailDomain(email) && ( - - Signing up with a work email makes it easier - for co-workers to join your Flagsmith - organisation. - + {blockGenericEmailDomain ? ( + + ) : ( + isFreeEmailDomain(email) && ( + + Signing up with a work email makes it + easier for co-workers to join your + Flagsmith organisation. + + ) )} { disabled={ isLoading || isSaving || - !allRequirementsMet + !allRequirementsMet || + !firstName.trim() || + !lastName.trim() || + blockGenericEmailDomain } className='px-4 mt-3 full-width' type='submit'