Skip to content

Commit

Permalink
feat(mobile): track user auth events (#302)
Browse files Browse the repository at this point in the history
  • Loading branch information
duongdev authored Sep 11, 2024
1 parent 7b05e99 commit 94784a7
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 21 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"EXCHANGERATES",
"hono",
"openai",
"posthog",
"svix",
"tanstack"
]
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app/(app)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export default function TabLayout() {
const { user } = useUser()
const { width } = useWindowDimensions()
const { bottom } = useSafeAreaInsets()

const posthog = usePostHog()

useEffect(() => {
if (!user) {
posthog.reset()
return
}
posthog.identify(user?.id, {
Expand Down
37 changes: 33 additions & 4 deletions apps/mobile/app/(auth)/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,33 @@ import { Trans, t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { Link } from 'expo-router'
import { MailIcon } from 'lucide-react-native'
import { useState } from 'react'
import { usePostHog } from 'posthog-react-native'
import { useCallback, useState } from 'react'
import { Linking, ScrollView, View } from 'react-native'

type Strategy = 'email_code' | 'oauth_google' | 'oauth_apple'

export default function LoginScreen() {
const [withEmail, setWithEmail] = useState(false)
const { i18n } = useLingui()
const posthog = usePostHog()

const handleSignedUp = useCallback(
(strategy: Strategy, userId?: string) => {
posthog.identify(userId)
posthog.capture('user_signed_up', { strategy })
},
[posthog],
)

const handleSignedIn = useCallback(
(strategy: Strategy, userId?: string) => {
posthog.identify(userId)
posthog.capture('user_signed_up', { strategy })
},
[posthog],
)

return (
<ScrollView
className="bg-card"
Expand All @@ -37,14 +58,22 @@ export default function LoginScreen() {
</Trans>
<AuthIllustration className="my-16 h-[326px] text-primary" />
<View className="flex flex-col gap-3">
<AppleAuthButton />
<GoogleAuthButton />
<AppleAuthButton
onSignedUp={handleSignedUp}
onSignedIn={handleSignedIn}
/>
<GoogleAuthButton
onSignedUp={handleSignedUp}
onSignedIn={handleSignedIn}
/>
<Button variant="outline" onPress={() => setWithEmail(true)}>
<MailIcon className="h-5 w-5 text-primary" />
<Text>{t(i18n)`Continue with Email`}</Text>
</Button>
<Separator className="mx-auto my-3 w-[70%]" />
{withEmail && <AuthEmail />}
{withEmail && (
<AuthEmail onSignedUp={handleSignedUp} onSignedIn={handleSignedIn} />
)}
</View>
<View className="px-4">
<Trans>
Expand Down
4 changes: 3 additions & 1 deletion apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,13 @@ function RootLayout() {

return (
<PostHogProvider
autocapture
apiKey={process.env.EXPO_PUBLIC_POSTHOG_API_KEY!}
options={{
host: process.env.EXPO_PUBLIC_POSTHOG_HOST!,
disabled:
process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY?.includes('test'),
}}
autocapture
>
<ClerkProvider
publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!}
Expand Down
10 changes: 9 additions & 1 deletion apps/mobile/components/auth/auth-email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ import {
verifyEmailFormSchema,
} from './emailSchema'

export function AuthEmail() {
export function AuthEmail({
onSignedUp,
onSignedIn,
}: {
onSignedUp: (strategy: 'email_code', userId?: string) => void
onSignedIn: (strategy: 'email_code', userId?: string) => void
}) {
const { i18n } = useLingui()

const authEmailForm = useForm<EmailFormValues>({
Expand Down Expand Up @@ -108,6 +114,7 @@ export function AuthEmail() {
email: signUpAttempt.emailAddress!,
name: signUpAttempt.firstName ?? '',
})
onSignedUp('email_code', signUpAttempt.id)
} else {
console.error(signUpAttempt)
}
Expand All @@ -119,6 +126,7 @@ export function AuthEmail() {
if (signInAttempt.status === 'complete') {
await setActiveSignIn({ session: signInAttempt.createdSessionId })
// signed in
onSignedIn('email_code', signInAttempt.id)
} else {
console.error(signInAttempt)
}
Expand Down
54 changes: 41 additions & 13 deletions apps/mobile/components/auth/auth-social.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,45 @@ import { GoogleLogo } from '../svg-assets/google-logo'
import { Button } from '../ui/button'
import { Text } from '../ui/text'

type Strategy = 'oauth_google' | 'oauth_apple'

type AuthSocialProps = {
label: string
icon: React.ComponentType<SvgProps>
strategy: 'oauth_google' | 'oauth_apple'
strategy: Strategy
onSignedUp: (strategy: Strategy, userId?: string) => void
onSignedIn: (strategy: Strategy, userId?: string) => void
}

export function AuthSocial({ label, icon: Icon, strategy }: AuthSocialProps) {
export function AuthSocial({
label,
icon: Icon,
strategy,
onSignedIn,
onSignedUp,
}: AuthSocialProps) {
const { startOAuthFlow } = useOAuth({ strategy })

const onPress = async () => {
try {
const { createdSessionId, setActive, signUp } = await startOAuthFlow()
const { createdSessionId, setActive, signUp, signIn } =
await startOAuthFlow()

if (createdSessionId) {
setActive?.({ session: createdSessionId })
if (signUp?.createdUserId) {
setTimeout(
async () =>
await createUser({
email: signUp.emailAddress!,
name: signUp.firstName ?? '',
}),
1000,
)
setTimeout(async () => {
await createUser({
email: signUp.emailAddress!,
name: signUp.firstName ?? '',
})

onSignedUp(strategy, signUp.id)
}, 1000)
}
} else {
// Use signIn or signUp for next steps such as MFA
onSignedIn(strategy, signIn?.id)
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
} catch (err: any) {
Expand All @@ -53,24 +65,40 @@ export function AuthSocial({ label, icon: Icon, strategy }: AuthSocialProps) {
)
}

export function GoogleAuthButton() {
export function GoogleAuthButton({
onSignedUp,
onSignedIn,
}: {
onSignedUp: (strategy: Strategy, userId?: string) => void
onSignedIn: (strategy: Strategy, userId?: string) => void
}) {
const { i18n } = useLingui()
return (
<AuthSocial
label={t(i18n)`Sign in with Google`}
icon={GoogleLogo}
strategy="oauth_google"
onSignedIn={onSignedIn}
onSignedUp={onSignedUp}
/>
)
}

export function AppleAuthButton() {
export function AppleAuthButton({
onSignedUp,
onSignedIn,
}: {
onSignedUp: (strategy: Strategy, userId?: string) => void
onSignedIn: (strategy: Strategy, userId?: string) => void
}) {
const { i18n } = useLingui()
return (
<AuthSocial
label={t(i18n)`Sign in with Apple`}
icon={AppleLogo}
strategy="oauth_apple"
onSignedIn={onSignedIn}
onSignedUp={onSignedUp}
/>
)
}
8 changes: 7 additions & 1 deletion apps/mobile/mutations/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ export async function createUser(data: CreateUser) {
const deviceLanguage = getLocales()[0].languageCode ?? 'vi'
const deviceCurrency = getLocales()[0]?.currencyCode ?? 'VND'

await hc.v1.users.$post({
const res = await hc.v1.users.$post({
json: data,
header: {
'x-device-language': deviceLanguage,
'x-device-currency': deviceCurrency,
},
})

if (!res.ok) {
return null
}

return res.json()
}

0 comments on commit 94784a7

Please sign in to comment.