Skip to content

Commit 14a6ed0

Browse files
sjalkoteEthanL06
andauthored
feat: add referrer onboarding question (#126)
* feat: add referrer onboarding question (#123) * fix: update text for referral source question to improve clarity --------- Co-authored-by: Ethan <[email protected]>
1 parent acaf2a6 commit 14a6ed0

File tree

3 files changed

+135
-2
lines changed

3 files changed

+135
-2
lines changed

components/onboarding/OnboardingScreen.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ import FavoritesFeatureScreen from './screens/FavoritesFeatureScreen';
2424
import MapFeatureScreen from './screens/MapFeatureScreen';
2525
import MenusFeatureScreen from './screens/MenusFeatureScreen';
2626
import PermissionsScreen from './screens/PermissionsScreen';
27+
import ReferralSourceScreen from './screens/ReferralSourceScreen';
2728
import WelcomeScreen from './screens/WelcomeScreen';
2829

2930
const ONBOARDING_SCREENS = [
3031
ONBOARDING_STEPS.WELCOME,
3132
ONBOARDING_STEPS.DATA_COLLECTION,
33+
ONBOARDING_STEPS.REFERRAL_SOURCE,
3234
ONBOARDING_STEPS.FEATURES_MENUS,
3335
ONBOARDING_STEPS.FEATURES_MAP,
3436
ONBOARDING_STEPS.FEATURES_FAVORITES,
@@ -52,6 +54,8 @@ const OnboardingScreen = ({ isOnboardingComplete }: OnboardingScreenProps) => {
5254
const { currentStep, setCurrentStep, completeOnboarding } = useOnboardingStore();
5355
const [hasDataSelection, setHasDataSelection] = React.useState(false);
5456
const [selectedMotivations, setSelectedMotivations] = React.useState<string[]>([]);
57+
const [hasReferralSource, setHasReferralSource] = React.useState(false);
58+
const [referralSource, setReferralSource] = React.useState<string | null>(null);
5559
const [permissionStatus, setPermissionStatus] = React.useState<{
5660
location: string;
5761
notifications: string;
@@ -112,9 +116,10 @@ const OnboardingScreen = ({ isOnboardingComplete }: OnboardingScreenProps) => {
112116
analytics.capture('onboarding_completed', {
113117
selected_motivations: selectedMotivations,
114118
motivation_count: selectedMotivations.length,
119+
referral_source: referralSource || 'undetermined',
115120
location_permission: permissionStatus?.location || 'undetermined',
116121
notifications_permission: permissionStatus?.notifications || 'undetermined',
117-
onboarding_version: '1.0',
122+
onboarding_version: '1.1',
118123
});
119124

120125
completeOnboarding();
@@ -133,6 +138,15 @@ const OnboardingScreen = ({ isOnboardingComplete }: OnboardingScreenProps) => {
133138
onSelectionUpdate={setSelectedMotivations}
134139
/>
135140
);
141+
case ONBOARDING_STEPS.REFERRAL_SOURCE:
142+
return (
143+
<ReferralSourceScreen
144+
key={index}
145+
width={width}
146+
onSelectionChange={setHasReferralSource}
147+
onSelectionUpdate={setReferralSource}
148+
/>
149+
);
136150
case ONBOARDING_STEPS.FEATURES_MENUS:
137151
return <MenusFeatureScreen key={index} width={width} />;
138152
case ONBOARDING_STEPS.FEATURES_MAP:
@@ -244,7 +258,8 @@ const OnboardingScreen = ({ isOnboardingComplete }: OnboardingScreenProps) => {
244258
<Text className="py-2 text-center font-semibold text-white">
245259
{currentStep === ONBOARDING_SCREENS.length - 1
246260
? 'Finish'
247-
: currentStep === 1 && !hasDataSelection
261+
: (currentStep === 1 && !hasDataSelection) ||
262+
(currentStep === 2 && !hasReferralSource)
248263
? 'Skip'
249264
: 'Continue'}
250265
</Text>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import * as Haptics from 'expo-haptics';
2+
import {
3+
Book,
4+
Calendar,
5+
Eye,
6+
HelpCircle,
7+
Mail,
8+
Megaphone,
9+
Search,
10+
Users,
11+
} from 'lucide-react-native';
12+
import { useState } from 'react';
13+
import { Text, TouchableOpacity, View } from 'react-native';
14+
15+
import { useSettingsStore } from '~/store/useSettingsStore';
16+
import { COLORS } from '~/utils/colors';
17+
import { cn } from '~/utils/utils';
18+
19+
type Props = {
20+
width: number;
21+
onSelectionChange: (hasSelection: boolean) => void;
22+
onSelectionUpdate: (selectedTag: string | null) => void;
23+
};
24+
25+
const REFERRAL_SOURCES = [
26+
{ id: 'friend-family', label: 'Friend or family', icon: Users },
27+
{ id: 'social-media', label: 'Social media (TikTok, LinkedIn, etc.)', icon: Megaphone },
28+
{ id: 'advertisement', label: 'Advertisement (online or in-person)', icon: Mail },
29+
{ id: 'on-campus', label: 'Seen being used on campus', icon: Eye },
30+
{ id: 'search-engine', label: 'Search engine (Google, Bing, etc.)', icon: Search },
31+
{ id: 'class-professor', label: 'Professor or class', icon: Book },
32+
{ id: 'event-fair', label: 'At an event or fair', icon: Calendar },
33+
{ id: 'other', label: 'Other', icon: HelpCircle },
34+
];
35+
36+
const ReferralSourceScreen = ({ width, onSelectionChange, onSelectionUpdate }: Props) => {
37+
const [selectedTag, setSelectedTag] = useState<string | null>(null);
38+
const isDark = useSettingsStore((state) => state.isDarkMode);
39+
40+
const toggleTag = (tagId: string) => {
41+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
42+
43+
const newSelection = selectedTag === tagId ? null : tagId;
44+
45+
setSelectedTag(newSelection);
46+
onSelectionChange(newSelection !== null);
47+
onSelectionUpdate(newSelection);
48+
};
49+
50+
return (
51+
<View
52+
style={{ width }}
53+
className={cn('flex-1 px-6 py-8', isDark ? 'bg-neutral-900' : 'bg-white')}
54+
>
55+
<View className="mb-8">
56+
<Text
57+
className={cn(
58+
'mb-1 text-center font-bold text-3xl',
59+
isDark ? 'text-white' : 'text-gray-900',
60+
)}
61+
>
62+
How’d you hear about us?
63+
</Text>
64+
<Text className={cn('text-center text-lg', isDark ? 'text-gray-300' : 'text-gray-600')}>
65+
Select the option that best fits.
66+
</Text>
67+
</View>
68+
69+
<View className="flex-1">
70+
<View className="flex-row flex-wrap justify-between">
71+
{REFERRAL_SOURCES.map((tag) => (
72+
<TouchableOpacity
73+
activeOpacity={0.7}
74+
key={tag.id}
75+
onPress={() => toggleTag(tag.id)}
76+
className={cn(
77+
'mb-4 w-[48%] rounded-xl p-4',
78+
selectedTag === tag.id
79+
? 'border-ut-burnt-orange bg-ut-burnt-orange/10'
80+
: isDark
81+
? ' bg-neutral-800'
82+
: 'border border-gray-200 bg-white',
83+
)}
84+
>
85+
<View className="items-center">
86+
<tag.icon
87+
size={32}
88+
color={
89+
selectedTag === tag.id
90+
? COLORS['ut-burnt-orange']
91+
: isDark
92+
? COLORS['ut-grey-dark-mode']
93+
: COLORS['ut-grey']
94+
}
95+
/>
96+
<Text
97+
className={cn(
98+
'mt-2 text-center font-medium text-sm',
99+
selectedTag === tag.id
100+
? 'text-ut-burnt-orange'
101+
: isDark
102+
? 'text-gray-300'
103+
: 'text-gray-700',
104+
)}
105+
>
106+
{tag.label}
107+
</Text>
108+
</View>
109+
</TouchableOpacity>
110+
))}
111+
</View>
112+
</View>
113+
</View>
114+
);
115+
};
116+
117+
export default ReferralSourceScreen;

store/useOnboardingStore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface OnboardingState {
2424
export const ONBOARDING_STEPS = {
2525
WELCOME: 'welcome',
2626
DATA_COLLECTION: 'data_collection',
27+
REFERRAL_SOURCE: 'referral_source',
2728
FEATURES_MENUS: 'features_menus',
2829
FEATURES_MAP: 'features_map',
2930
FEATURES_FAVORITES: 'features_favorites',

0 commit comments

Comments
 (0)