Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions app/(protected)/(tabs)/card/activate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { CardActivationStep } from '@/components/Card/CardActivationStep';
import PageLayout from '@/components/PageLayout';
import { Text } from '@/components/ui/text';
import { path } from '@/constants/path';
import { TRACKING_EVENTS } from '@/constants/tracking-events';
import { track } from '@/lib/analytics';
import { useCardStatus } from '@/hooks/useCardStatus';
import { useCardSteps } from '@/hooks/useCardSteps';
import { useCountryCheck } from '@/hooks/useCountryCheck';
Expand Down Expand Up @@ -59,6 +61,19 @@ export default function ActivateMobile() {
Array.isArray(cardsEndorsement?.requirements?.pending) &&
cardsEndorsement.requirements.pending.length > 0;

// Track card activate page view (on mount only)
React.useEffect(() => {
track(TRACKING_EVENTS.CARD_ACTIVATE_PAGE_VIEWED, {
card_status: cardStatus,
kyc_status: _kycStatus,
is_card_pending: isCardPending,
is_card_blocked: isCardBlocked,
is_under_review: isUnderReview,
country_confirmed: countryConfirmed === 'true',
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// If the card is already active, skip the activation flow
React.useEffect(() => {
if (cardStatus === CardStatus.ACTIVE || cardStatus === CardStatus.FROZEN) {
Expand Down
25 changes: 24 additions & 1 deletion app/(protected)/(tabs)/kyc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import PageLayout from '@/components/PageLayout';
import { Text } from '@/components/ui/text';
import { TRACKING_EVENTS } from '@/constants/tracking-events';
import { track } from '@/lib/analytics';
import { getAttributionChannel } from '@/lib/attribution';
import { useAttributionStore } from '@/store/useAttributionStore';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { ArrowLeft } from 'lucide-react-native';
import type { ClientOptions } from 'persona';
Expand Down Expand Up @@ -130,7 +132,22 @@ export default function Kyc({ onSuccess }: KycParams = {}) {
templateVersionId: (options as any).templateVersionId ?? null,
host: (options as any).host ?? null,
onLoad: null,
onEvent: null,
onEvent: (name: string, meta: any) => {
// Track Persona KYC step progression
if (name === 'start') {
track(TRACKING_EVENTS.KYC_STEP_STARTED, {
step_name: meta?.name || 'unknown',
template_id: options.templateId,
inquiry_id: options.inquiryId,
});
} else if (name === 'complete') {
track(TRACKING_EVENTS.KYC_STEP_COMPLETED, {
step_name: meta?.name || 'unknown',
template_id: options.templateId,
inquiry_id: options.inquiryId,
});
}
},
onReady: () => {
track(TRACKING_EVENTS.KYC_LINK_SDK_READY, {
templateId: options.templateId,
Expand All @@ -139,10 +156,16 @@ export default function Kyc({ onSuccess }: KycParams = {}) {
setLoading(false);
},
onComplete: ({ inquiryId: completedInquiryId, status }) => {
// Capture attribution for KYC conversion tracking
const attributionData = useAttributionStore.getState().getAttributionForEvent();
const attributionChannel = getAttributionChannel(attributionData);

track(TRACKING_EVENTS.KYC_LINK_COMPLETED, {
inquiryId: completedInquiryId,
status,
hasRedirectUri: !!redirectUri,
...attributionData,
attribution_channel: attributionChannel,
});
onSuccess?.();

Expand Down
21 changes: 20 additions & 1 deletion app/(protected)/(tabs)/user-kyc-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import PageLayout from '@/components/PageLayout';
import { Text } from '@/components/ui/text';
import { zodResolver } from '@hookform/resolvers/zod';
import { ArrowLeft } from 'lucide-react-native';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { Pressable, View } from 'react-native';
import { z } from 'zod';

import { UserInfoFooter, UserInfoForm, UserInfoHeader } from '@/components/UserKyc';
import { KycMode, type UserInfoFormData, userInfoSchema } from '@/components/UserKyc/types';
import { TRACKING_EVENTS } from '@/constants/tracking-events';
import { track } from '@/lib/analytics';
import { createKycLink } from '@/lib/api';
import { startKycFlow } from '@/lib/utils/kyc';
import { useKycStore } from '@/store/useKycStore';
Expand All @@ -30,6 +32,14 @@ export default function UserKycInfo() {

const { redirectUri, kycMode } = params;

// Track page view on mount
useEffect(() => {
track(TRACKING_EVENTS.USER_KYC_INFO_PAGE_VIEWED, {
kyc_mode: kycMode || 'unknown',
has_redirect_uri: !!redirectUri,
});
}, [kycMode, redirectUri]);

const schema = userInfoSchema.superRefine((data, ctx) => {
if ((kycMode as KycMode) === KycMode.CARD && data.agreedToEsign !== true) {
ctx.addIssue({
Expand Down Expand Up @@ -63,6 +73,15 @@ export default function UserKycInfo() {
const onSubmit = async (data: UserInfoFormData) => {
setIsLoading(true);

// Track form submission
track(TRACKING_EVENTS.USER_KYC_INFO_FORM_STARTED, {
kyc_mode: kycMode || 'unknown',
has_email: !!data.email,
has_full_name: !!data.fullName,
agreed_to_terms: data.agreedToTerms,
agreed_to_esign: data.agreedToEsign,
});

const redirectUrl = getRedirectUrl();
console.warn('redirectUrl', redirectUrl);

Expand Down
19 changes: 9 additions & 10 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import WhatsNewModal from '@/components/WhatsNewModal';
import WithdrawModalProvider from '@/components/Withdraw/WithdrawModalProvider';
import '@/global.css';
import { infoClient } from '@/graphql/clients';
import { useAttributionInitialization } from '@/hooks/useAttributionInitialization';
import { useWhatsNew } from '@/hooks/useWhatsNew';
import { initAnalytics, trackScreen } from '@/lib/analytics';
import { config } from '@/lib/wagmi';
Expand Down Expand Up @@ -183,6 +184,9 @@ export default Sentry.wrap(function RootLayout() {
const [splashScreenHidden, setSplashScreenHidden] = useState(false);
const { whatsNew, isVisible, closeWhatsNew } = useWhatsNew();

// Initialize attribution tracking automatically (handles web and mobile)
useAttributionInitialization();

const [loaded, error] = useFonts({
MonaSans_200ExtraLight,
MonaSans_300Light,
Expand Down Expand Up @@ -243,10 +247,12 @@ export default Sentry.wrap(function RootLayout() {
}
}, [appIsReady, splashScreenHidden]);

// Track screen views on all platforms (web, iOS, Android)
// trackScreen() handles platform-specific routing internally:
// - Amplitude: tracks on all platforms
// - Firebase: tracks on web only
useEffect(() => {
if (Platform.OS === 'web') {
trackScreen(pathname, params);
}
trackScreen(pathname, params);
}, [pathname, params]);

useEffect(() => {
Expand Down Expand Up @@ -295,13 +301,6 @@ export default Sentry.wrap(function RootLayout() {
animation: 'none',
}}
/>
<Stack.Screen
name="register"
options={{
headerShown: false,
animation: 'none',
}}
/>
<Stack.Screen
name="welcome"
options={{
Expand Down
Loading
Loading