diff options
author | Samuel Newman <mozzius@protonmail.com> | 2025-03-27 20:17:07 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-27 20:17:07 +0200 |
commit | 5ceaee57938892157491ae2941d05f90c1d74149 (patch) | |
tree | d5c2df5937570fd4f3393ecf431e37c6675d80c7 /src/screens/Signup | |
parent | 7d1ebf6a027085ddc10a7dad2075d5e52d314233 (diff) | |
download | voidsky-5ceaee57938892157491ae2941d05f90c1d74149.tar.zst |
Instrument signup (#8037)
Diffstat (limited to 'src/screens/Signup')
-rw-r--r-- | src/screens/Signup/StepCaptcha/index.tsx | 5 | ||||
-rw-r--r-- | src/screens/Signup/StepHandle.tsx | 26 | ||||
-rw-r--r-- | src/screens/Signup/StepInfo/index.tsx | 15 | ||||
-rw-r--r-- | src/screens/Signup/index.tsx | 28 | ||||
-rw-r--r-- | src/screens/Signup/state.ts | 90 |
5 files changed, 128 insertions, 36 deletions
diff --git a/src/screens/Signup/StepCaptcha/index.tsx b/src/screens/Signup/StepCaptcha/index.tsx index 85558c5f5..388deecaf 100644 --- a/src/screens/Signup/StepCaptcha/index.tsx +++ b/src/screens/Signup/StepCaptcha/index.tsx @@ -4,7 +4,6 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {nanoid} from 'nanoid/non-secure' -import {logEvent} from '#/lib/statsig/statsig' import {createFullHandle} from '#/lib/strings/handles' import {logger} from '#/logger' import {ScreenTransition} from '#/screens/Login/ScreenTransition' @@ -40,7 +39,7 @@ export function StepCaptcha() { const onSuccess = React.useCallback( (code: string) => { setCompleted(true) - logEvent('signup:captchaSuccess', {}) + logger.metric('signup:captchaSuccess', {}, {statsig: true}) dispatch({ type: 'submit', task: {verificationCode: code, mutableProcessed: false}, @@ -55,7 +54,7 @@ export function StepCaptcha() { type: 'setError', value: _(msg`Error receiving captcha response.`), }) - logEvent('signup:captchaFailure', {}) + logger.metric('signup:captchaFailure', {}, {statsig: true}) logger.error('Signup Flow Error', { registrationHandle: state.handle, error, diff --git a/src/screens/Signup/StepHandle.tsx b/src/screens/Signup/StepHandle.tsx index 9192ba5a2..03fa28772 100644 --- a/src/screens/Signup/StepHandle.tsx +++ b/src/screens/Signup/StepHandle.tsx @@ -3,12 +3,12 @@ import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {logEvent} from '#/lib/statsig/statsig' import { createFullHandle, MAX_SERVICE_HANDLE_LENGTH, validateServiceHandle, } from '#/lib/strings/handles' +import {logger} from '#/logger' import {useAgent} from '#/state/session' import {ScreenTransition} from '#/screens/Login/ScreenTransition' import {useSignupContext} from '#/screens/Signup/state' @@ -53,7 +53,9 @@ export function StepHandle() { dispatch({ type: 'setError', value: _(msg`That handle is already taken.`), + field: 'handle', }) + logger.metric('signup:handleTaken', {}) return } } catch (e) { @@ -62,11 +64,15 @@ export function StepHandle() { dispatch({type: 'setIsLoading', value: false}) } - logEvent('signup:nextPressed', { - activeStep: state.activeStep, - phoneVerificationRequired: - state.serviceDescription?.phoneVerificationRequired, - }) + logger.metric( + 'signup:nextPressed', + { + activeStep: state.activeStep, + phoneVerificationRequired: + state.serviceDescription?.phoneVerificationRequired, + }, + {statsig: true}, + ) // phoneVerificationRequired is actually whether a captcha is required if (!state.serviceDescription?.phoneVerificationRequired) { dispatch({ @@ -92,9 +98,11 @@ export function StepHandle() { value: handle, }) dispatch({type: 'prev'}) - logEvent('signup:backPressed', { - activeStep: state.activeStep, - }) + logger.metric( + 'signup:backPressed', + {activeStep: state.activeStep}, + {statsig: true}, + ) }, [dispatch, state.activeStep]) const validCheck = validateServiceHandle(draftValue, state.userDomain) diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx index a19a3ad4a..f24cd0e45 100644 --- a/src/screens/Signup/StepInfo/index.tsx +++ b/src/screens/Signup/StepInfo/index.tsx @@ -1,11 +1,10 @@ import React, {useRef} from 'react' -import {TextInput, View} from 'react-native' +import {type TextInput, View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import * as EmailValidator from 'email-validator' import type tldts from 'tldts' -import {logEvent} from '#/lib/statsig/statsig' import {isEmailMaybeInvalid} from '#/lib/strings/email' import {logger} from '#/logger' import {ScreenTransition} from '#/screens/Login/ScreenTransition' @@ -13,7 +12,7 @@ import {is13, is18, useSignupContext} from '#/screens/Signup/state' import {Policies} from '#/screens/Signup/StepInfo/Policies' import {atoms as a, native} from '#/alf' import * as DateField from '#/components/forms/DateField' -import {DateFieldRef} from '#/components/forms/DateField/types' +import {type DateFieldRef} from '#/components/forms/DateField/types' import {FormError} from '#/components/forms/FormError' import {HostingProvider} from '#/components/forms/HostingProvider' import * as TextField from '#/components/forms/TextField' @@ -134,9 +133,13 @@ export function StepInfo({ dispatch({type: 'setEmail', value: email}) dispatch({type: 'setPassword', value: password}) dispatch({type: 'next'}) - logEvent('signup:nextPressed', { - activeStep: state.activeStep, - }) + logger.metric( + 'signup:nextPressed', + { + activeStep: state.activeStep, + }, + {statsig: true}, + ) } return ( diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx index e82d0da1c..c98040010 100644 --- a/src/screens/Signup/index.tsx +++ b/src/screens/Signup/index.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import {View} from 'react-native' +import {useEffect, useReducer, useState} from 'react' +import {AppState, type AppStateStatus, View} from 'react-native' import Animated, {FadeIn, LayoutAnimationConfig} from 'react-native-reanimated' import {AppBskyGraphStarterpack} from '@atproto/api' import {msg, Trans} from '@lingui/macro' @@ -31,7 +31,7 @@ import * as bsky from '#/types/bsky' export function Signup({onPressBack}: {onPressBack: () => void}) { const {_} = useLingui() const t = useTheme() - const [state, dispatch] = React.useReducer(reducer, initialState) + const [state, dispatch] = useReducer(reducer, initialState) const {gtMobile} = useBreakpoints() const submit = useSubmitSignup() @@ -44,7 +44,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { uri: activeStarterPack?.uri, }) - const [isFetchedAtMount] = React.useState(starterPack != null) + const [isFetchedAtMount] = useState(starterPack != null) const showStarterPackCard = activeStarterPack?.uri && !isFetchingStarterPack && starterPack @@ -55,7 +55,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { refetch, } = useServiceQuery(state.serviceUrl) - React.useEffect(() => { + useEffect(() => { if (isFetching) { dispatch({type: 'setIsLoading', value: true}) } else if (!isFetching) { @@ -63,7 +63,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { } }, [isFetching]) - React.useEffect(() => { + useEffect(() => { if (isError) { dispatch({type: 'setServiceDescription', value: undefined}) dispatch({ @@ -78,7 +78,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { } }, [_, serviceInfo, isError]) - React.useEffect(() => { + useEffect(() => { if (state.pendingSubmit) { if (!state.pendingSubmit.mutableProcessed) { state.pendingSubmit.mutableProcessed = true @@ -87,6 +87,20 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { } }, [state, dispatch, submit]) + // Track app backgrounding during signup + useEffect(() => { + const subscription = AppState.addEventListener( + 'change', + (nextAppState: AppStateStatus) => { + if (nextAppState === 'background') { + dispatch({type: 'incrementBackgroundCount'}) + } + }, + ) + + return () => subscription.remove() + }, []) + return ( <SignupContext.Provider value={{state, dispatch}}> <LoggedOutLayout diff --git a/src/screens/Signup/state.ts b/src/screens/Signup/state.ts index 3daf36a9b..48ea4ccd9 100644 --- a/src/screens/Signup/state.ts +++ b/src/screens/Signup/state.ts @@ -2,7 +2,7 @@ import React, {useCallback} from 'react' import {LayoutAnimation} from 'react-native' import { ComAtprotoServerCreateAccount, - ComAtprotoServerDescribeServer, + type ComAtprotoServerDescribeServer, } from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -56,6 +56,11 @@ export type SignupState = { isLoading: boolean pendingSubmit: null | SubmitTask + + // Tracking + signupStartTime: number + fieldErrors: Record<ErrorField, number> + backgroundCount: number } export type SignupAction = @@ -74,6 +79,7 @@ export type SignupAction = | {type: 'clearError'} | {type: 'setIsLoading'; value: boolean} | {type: 'submit'; task: SubmitTask} + | {type: 'incrementBackgroundCount'} export const initialState: SignupState = { hasPrev: false, @@ -93,6 +99,17 @@ export const initialState: SignupState = { isLoading: false, pendingSubmit: null, + + // Tracking + signupStartTime: Date.now(), + fieldErrors: { + 'invite-code': 0, + email: 0, + handle: 0, + password: 0, + 'date-of-birth': 0, + }, + backgroundCount: 0, } export function is13(date: Date) { @@ -169,6 +186,23 @@ export function reducer(s: SignupState, a: SignupAction): SignupState { case 'setError': { next.error = a.value next.errorField = a.field + + // Track field errors + if (a.field) { + next.fieldErrors[a.field] = (next.fieldErrors[a.field] || 0) + 1 + + // Log the field error + logger.metric( + 'signup:fieldError', + { + field: a.field, + errorCount: next.fieldErrors[a.field], + errorMessage: a.value, + activeStep: next.activeStep, + }, + {statsig: true}, + ) + } break } case 'clearError': { @@ -180,6 +214,20 @@ export function reducer(s: SignupState, a: SignupAction): SignupState { next.pendingSubmit = a.task break } + case 'incrementBackgroundCount': { + next.backgroundCount = s.backgroundCount + 1 + + // Log background/foreground event during signup + logger.metric( + 'signup:backgrounded', + { + activeStep: next.activeStep, + backgroundCount: next.backgroundCount, + }, + {statsig: true}, + ) + break + } } next.hasPrev = next.activeStep !== SignupStep.INFO @@ -212,6 +260,7 @@ export function useSubmitSignup() { return dispatch({ type: 'setError', value: _(msg`Please enter your email.`), + field: 'email', }) } if (!EmailValidator.validate(state.email)) { @@ -219,6 +268,7 @@ export function useSubmitSignup() { return dispatch({ type: 'setError', value: _(msg`Your email appears to be invalid.`), + field: 'email', }) } if (!state.password) { @@ -226,6 +276,7 @@ export function useSubmitSignup() { return dispatch({ type: 'setError', value: _(msg`Please choose your password.`), + field: 'password', }) } if (!state.handle) { @@ -233,6 +284,7 @@ export function useSubmitSignup() { return dispatch({ type: 'setError', value: _(msg`Please choose your handle.`), + field: 'handle', }) } if ( @@ -253,15 +305,26 @@ export function useSubmitSignup() { dispatch({type: 'setIsLoading', value: true}) try { - await createAccount({ - service: state.serviceUrl, - email: state.email, - handle: createFullHandle(state.handle, state.userDomain), - password: state.password, - birthDate: state.dateOfBirth, - inviteCode: state.inviteCode.trim(), - verificationCode: state.pendingSubmit?.verificationCode, - }) + await createAccount( + { + service: state.serviceUrl, + email: state.email, + handle: createFullHandle(state.handle, state.userDomain), + password: state.password, + birthDate: state.dateOfBirth, + inviteCode: state.inviteCode.trim(), + verificationCode: state.pendingSubmit?.verificationCode, + }, + { + signupDuration: Date.now() - state.signupStartTime, + fieldErrorsTotal: Object.values(state.fieldErrors).reduce( + (a, b) => a + b, + 0, + ), + backgroundCount: state.backgroundCount, + }, + ) + /* * Must happen last so that if the user has multiple tabs open and * createAccount fails, one tab is not stuck in onboarding — Eric @@ -275,6 +338,7 @@ export function useSubmitSignup() { value: _( msg`Invite code not accepted. Check that you input it correctly and try again.`, ), + field: 'invite-code', }) dispatch({type: 'setStep', value: SignupStep.INFO}) return @@ -284,7 +348,11 @@ export function useSubmitSignup() { const isHandleError = error.toLowerCase().includes('handle') dispatch({type: 'setIsLoading', value: false}) - dispatch({type: 'setError', value: error}) + dispatch({ + type: 'setError', + value: error, + field: isHandleError ? 'handle' : undefined, + }) dispatch({type: 'setStep', value: isHandleError ? 2 : 1}) logger.error('Signup Flow Error', { |