@squad-sports/react-native 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/package.json +46 -0
  4. package/src/SquadExperience.tsx +200 -0
  5. package/src/SquadProvider.tsx +232 -0
  6. package/src/SquadSportsSDK.ts +286 -0
  7. package/src/__tests__/DeepLinkHandler.test.ts +101 -0
  8. package/src/__tests__/ErrorBoundary.test.tsx +161 -0
  9. package/src/__tests__/EventProcessor.test.ts +241 -0
  10. package/src/__tests__/PushNotificationHandler.test.ts +91 -0
  11. package/src/__tests__/SecureStorage.test.ts +62 -0
  12. package/src/__tests__/SquadSportsSDK.test.ts +278 -0
  13. package/src/__tests__/VerificationCooldown.test.ts +153 -0
  14. package/src/components/ErrorBoundary.tsx +129 -0
  15. package/src/components/SOTD/SOTDComponents.tsx +101 -0
  16. package/src/components/audio/AudioPlayerRow.tsx +189 -0
  17. package/src/components/audio/recording/AudioRecording.tsx +232 -0
  18. package/src/components/communities/CommunityComponents.tsx +78 -0
  19. package/src/components/dialogs/AllDialogs.tsx +123 -0
  20. package/src/components/dialogs/ConfirmDialog.tsx +77 -0
  21. package/src/components/dialogs/PermissionDialog.tsx +132 -0
  22. package/src/components/dragAndDrop/DragAndDrop.tsx +56 -0
  23. package/src/components/events/EventComponents.tsx +93 -0
  24. package/src/components/feed/ChatBannerCard.tsx +94 -0
  25. package/src/components/feed/FeedLoadingSkeleton.tsx +43 -0
  26. package/src/components/feed/FreestyleCard.tsx +119 -0
  27. package/src/components/feed/InterstitialOverlay.tsx +190 -0
  28. package/src/components/feed/PollCard.tsx +158 -0
  29. package/src/components/feed/SponsoredContentCard.tsx +118 -0
  30. package/src/components/freestyle/FreestyleComponents.tsx +148 -0
  31. package/src/components/index.ts +42 -0
  32. package/src/components/message/MessageCard.tsx +166 -0
  33. package/src/components/message/MessageComponents.tsx +143 -0
  34. package/src/components/poll/PollComponents.tsx +226 -0
  35. package/src/components/sentinels/Sentinels.tsx +175 -0
  36. package/src/components/squad/FeedSquad.tsx +54 -0
  37. package/src/components/toasts/Toasts.tsx +88 -0
  38. package/src/components/ux/RootUXComponents.tsx +157 -0
  39. package/src/components/ux/action-sheet/ActionSheet.tsx +67 -0
  40. package/src/components/ux/bottom-sheet/BottomSheetComponents.tsx +93 -0
  41. package/src/components/ux/bottom-sheet/SquadBottomSheet.tsx +119 -0
  42. package/src/components/ux/buttons/Button.tsx +37 -0
  43. package/src/components/ux/buttons/InfoButton.tsx +24 -0
  44. package/src/components/ux/buttons/XButton.tsx +27 -0
  45. package/src/components/ux/carousel/Carousel.tsx +134 -0
  46. package/src/components/ux/emoji-picker/EmojiReactionPicker.tsx +139 -0
  47. package/src/components/ux/errors/ErrorHint.tsx +46 -0
  48. package/src/components/ux/inputs/CodeInput.tsx +121 -0
  49. package/src/components/ux/inputs/DatePicker.tsx +76 -0
  50. package/src/components/ux/inputs/MaskedTextInput.tsx +95 -0
  51. package/src/components/ux/inputs/PhoneNumberInput.tsx +90 -0
  52. package/src/components/ux/inputs/SearchTextInput.tsx +70 -0
  53. package/src/components/ux/inputs/TextInput.tsx +58 -0
  54. package/src/components/ux/layout/AvoidKeyboardScreen.tsx +58 -0
  55. package/src/components/ux/layout/BlurOverlay.tsx +26 -0
  56. package/src/components/ux/layout/CrossFade.tsx +30 -0
  57. package/src/components/ux/layout/DismissKeyboardOnBlur.tsx +14 -0
  58. package/src/components/ux/layout/LoadingOverlay.tsx +60 -0
  59. package/src/components/ux/layout/NetworkBanner.tsx +64 -0
  60. package/src/components/ux/layout/PermissionsCTAContent.tsx +54 -0
  61. package/src/components/ux/layout/RefreshControl.tsx +7 -0
  62. package/src/components/ux/layout/Screen.tsx +31 -0
  63. package/src/components/ux/layout/ScreenHeader.tsx +89 -0
  64. package/src/components/ux/layout/TabBar.tsx +39 -0
  65. package/src/components/ux/layout/Toast.tsx +116 -0
  66. package/src/components/ux/layout/TransparentOverlay.tsx +21 -0
  67. package/src/components/ux/navigation/BackButton.tsx +29 -0
  68. package/src/components/ux/navigation/LinkButton.tsx +21 -0
  69. package/src/components/ux/navigation/UrlButton.tsx +25 -0
  70. package/src/components/ux/scroll/RefreshableFlatList.tsx +35 -0
  71. package/src/components/ux/shapes/Shapes.tsx +23 -0
  72. package/src/components/ux/text/Typography.tsx +28 -0
  73. package/src/components/ux/user-image/UserImage.tsx +75 -0
  74. package/src/components/ux/user-image/UserImageVariants.tsx +104 -0
  75. package/src/components/wallet/WalletComponents.tsx +116 -0
  76. package/src/contexts/AuthContext.tsx +45 -0
  77. package/src/contexts/EventProcessorContext.tsx +41 -0
  78. package/src/contexts/PlayerQueueContext.tsx +95 -0
  79. package/src/hooks/useAuth.ts +23 -0
  80. package/src/hooks/useDataRefresh.ts +30 -0
  81. package/src/hooks/useEventProcessor.ts +6 -0
  82. package/src/hooks/useImageOptimization.ts +59 -0
  83. package/src/hooks/useOnboardingStepGuard.ts +36 -0
  84. package/src/hooks/usePendingNavigation.ts +26 -0
  85. package/src/hooks/useSquadData.ts +84 -0
  86. package/src/hooks/useUserCreated.ts +25 -0
  87. package/src/hooks/useUserUpdate.ts +25 -0
  88. package/src/hooks/useViewabilityTracker.ts +40 -0
  89. package/src/index.ts +109 -0
  90. package/src/navigation/SquadNavigator.tsx +262 -0
  91. package/src/realtime/DeepLinkHandler.ts +113 -0
  92. package/src/realtime/EventProcessor.ts +313 -0
  93. package/src/realtime/NetworkMonitor.ts +84 -0
  94. package/src/realtime/OfflineQueue.ts +133 -0
  95. package/src/realtime/PushNotificationHandler.ts +125 -0
  96. package/src/realtime/useRealtimeSync.ts +84 -0
  97. package/src/screens/auth/EmailVerificationScreen.tsx +201 -0
  98. package/src/screens/auth/EnterCodeScreen.tsx +253 -0
  99. package/src/screens/auth/EnterEmailScreen.tsx +234 -0
  100. package/src/screens/auth/LandingScreen.tsx +90 -0
  101. package/src/screens/auth/LoginScreen.tsx +126 -0
  102. package/src/screens/events/EventScreen.tsx +163 -0
  103. package/src/screens/freestyle/CommunityFreestyleScreen.tsx +82 -0
  104. package/src/screens/freestyle/FreestyleCreationScreen.tsx +255 -0
  105. package/src/screens/freestyle/FreestyleListensScreen.tsx +44 -0
  106. package/src/screens/freestyle/FreestyleReactionsScreen.tsx +44 -0
  107. package/src/screens/freestyle/FreestyleScreen.tsx +64 -0
  108. package/src/screens/home/HomeScreen.tsx +365 -0
  109. package/src/screens/home/slivers/SquadCircle.tsx +77 -0
  110. package/src/screens/invite/InvitationQrCodeScreen.tsx +114 -0
  111. package/src/screens/invite/InviteScreen.tsx +175 -0
  112. package/src/screens/messaging/MessagingScreen.tsx +360 -0
  113. package/src/screens/onboarding/OnboardingAccountSetupScreen.tsx +221 -0
  114. package/src/screens/onboarding/OnboardingTeamSelectScreen.tsx +215 -0
  115. package/src/screens/onboarding/OnboardingWelcomeScreen.tsx +93 -0
  116. package/src/screens/polls/PollResponseScreen.tsx +229 -0
  117. package/src/screens/polls/PollSummationScreen.tsx +78 -0
  118. package/src/screens/profile/ProfileScreen.tsx +234 -0
  119. package/src/screens/settings/BlockedUsersScreen.tsx +139 -0
  120. package/src/screens/settings/DeleteAccountScreen.tsx +182 -0
  121. package/src/screens/settings/EditProfileScreen.tsx +154 -0
  122. package/src/screens/settings/NetworkStatusScreen.tsx +59 -0
  123. package/src/screens/settings/SettingsScreen.tsx +194 -0
  124. package/src/screens/squad-line/AddCallTitleScreen.tsx +293 -0
  125. package/src/screens/wallet/WalletScreen.tsx +174 -0
  126. package/src/services/AuthStateManager.ts +93 -0
  127. package/src/services/NavigationService.ts +40 -0
  128. package/src/services/UserDataManager.ts +59 -0
  129. package/src/services/UserUpdateService.ts +31 -0
  130. package/src/services/VerificationStateManager.ts +41 -0
  131. package/src/squad-line/CallScreen.tsx +158 -0
  132. package/src/squad-line/IncomingCallOverlay.tsx +113 -0
  133. package/src/squad-line/SquadLineClient.ts +327 -0
  134. package/src/squad-line/useSquadLine.ts +80 -0
  135. package/src/state/audio.ts +38 -0
  136. package/src/state/client.ts +26 -0
  137. package/src/state/communities.ts +45 -0
  138. package/src/state/contacts.ts +28 -0
  139. package/src/state/device-info.ts +22 -0
  140. package/src/state/events.ts +16 -0
  141. package/src/state/features.ts +57 -0
  142. package/src/state/index.ts +121 -0
  143. package/src/state/invitations.ts +16 -0
  144. package/src/state/modal-keys.ts +63 -0
  145. package/src/state/modal-queue.ts +104 -0
  146. package/src/state/navigation.ts +34 -0
  147. package/src/state/permissions.ts +43 -0
  148. package/src/state/session.ts +223 -0
  149. package/src/state/squaddie-of-the-day.ts +21 -0
  150. package/src/state/sync/crdt.ts +70 -0
  151. package/src/state/sync/dependable.ts +213 -0
  152. package/src/state/sync/feed-v2.ts +42 -0
  153. package/src/state/sync/messages.ts +44 -0
  154. package/src/state/sync/offline-support.ts +42 -0
  155. package/src/state/sync/polls.ts +37 -0
  156. package/src/state/sync/refresh.ts +24 -0
  157. package/src/state/sync/squad-v2.ts +25 -0
  158. package/src/state/ui.ts +36 -0
  159. package/src/state/user.ts +46 -0
  160. package/src/state/wallet.ts +26 -0
  161. package/src/storage/SecureStorage.ts +77 -0
  162. package/src/theme/ThemeContext.tsx +159 -0
  163. package/src/types/modules.d.ts +165 -0
@@ -0,0 +1,221 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput,
6
+ StyleSheet,
7
+ Pressable,
8
+ Alert,
9
+ } from 'react-native';
10
+ import { useNavigation } from '@react-navigation/native';
11
+ import * as ImagePicker from 'expo-image-picker';
12
+
13
+ import { useApiClient, useSquadSDK } from '../../SquadProvider';
14
+ import { useTheme, Colors } from '../../theme/ThemeContext';
15
+ import Button from '../../components/ux/buttons/Button';
16
+ import ScreenHeader from '../../components/ux/layout/ScreenHeader';
17
+ import UserImage from '../../components/ux/user-image/UserImage';
18
+ import AvoidKeyboardScreen from '../../components/ux/layout/AvoidKeyboardScreen';
19
+ import { BodyRegular, TitleMedium } from '../../components/ux/text/Typography';
20
+ import { ErrorHint } from '../../components/ux/errors/ErrorHint';
21
+
22
+ export function OnboardingAccountSetupScreen() {
23
+ const navigation = useNavigation();
24
+ const apiClient = useApiClient();
25
+ const sdk = useSquadSDK();
26
+ const { theme } = useTheme();
27
+
28
+ const [displayName, setDisplayName] = useState('');
29
+ const [imageUri, setImageUri] = useState<string | null>(null);
30
+ const [isLoading, setIsLoading] = useState(false);
31
+ const [error, setError] = useState<string | null>(null);
32
+
33
+ const pickImage = useCallback(async () => {
34
+ try {
35
+ const permResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
36
+ if (!permResult.granted) {
37
+ Alert.alert('Permission needed', 'Please grant photo library access to set your profile photo.');
38
+ return;
39
+ }
40
+
41
+ const result = await ImagePicker.launchImageLibraryAsync({
42
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
43
+ allowsEditing: true,
44
+ aspect: [1, 1],
45
+ quality: 0.8,
46
+ });
47
+
48
+ if (!result.canceled && result.assets[0]) {
49
+ setImageUri(result.assets[0].uri);
50
+ }
51
+ } catch {
52
+ setError('Failed to select image');
53
+ }
54
+ }, []);
55
+
56
+ const handleComplete = useCallback(async () => {
57
+ if (!displayName.trim()) {
58
+ setError('Please enter your name');
59
+ return;
60
+ }
61
+
62
+ setIsLoading(true);
63
+ setError(null);
64
+
65
+ try {
66
+ // Update user profile with display name
67
+ const user = await apiClient.getLoggedInUser();
68
+ if (user) {
69
+ const updatedUser = user.clone();
70
+ updatedUser.displayName = displayName.trim();
71
+ updatedUser.status = 'active';
72
+ await apiClient.updateLoggedInUser(updatedUser);
73
+
74
+ // Upload profile image if selected
75
+ if (imageUri) {
76
+ const response = await fetch(imageUri);
77
+ const blob = await response.blob();
78
+ const buffer = new Uint8Array(await (blob as any).arrayBuffer());
79
+ await apiClient.uploadUserImage(user.id ?? '', buffer, 'image/jpeg');
80
+ }
81
+ }
82
+
83
+ // Navigation will automatically route to main flow
84
+ // since the user now has displayName and community
85
+ } catch (err) {
86
+ setError('Failed to save profile. Please try again.');
87
+ } finally {
88
+ setIsLoading(false);
89
+ }
90
+ }, [displayName, imageUri, apiClient]);
91
+
92
+ const buttonDisabled = !displayName.trim() || isLoading;
93
+
94
+ return (
95
+ <AvoidKeyboardScreen>
96
+ <ScreenHeader title="Set Up Profile" showBack={true} />
97
+
98
+ <View style={styles.content}>
99
+ <Pressable onPress={pickImage} style={styles.avatarContainer}>
100
+ <UserImage
101
+ imageUrl={imageUri}
102
+ displayName={displayName || '?'}
103
+ size={100}
104
+ />
105
+ <View style={[styles.editBadge, { backgroundColor: theme.buttonColor }]}>
106
+ <Text style={styles.editBadgeText}>+</Text>
107
+ </View>
108
+ </Pressable>
109
+
110
+ <BodyRegular style={styles.photoHint}>Tap to add a photo</BodyRegular>
111
+
112
+ <View style={styles.inputContainer}>
113
+ <TitleMedium style={styles.label}>What should we call you?</TitleMedium>
114
+ <TextInput
115
+ value={displayName}
116
+ onChangeText={(text) => {
117
+ setDisplayName(text);
118
+ setError(null);
119
+ }}
120
+ placeholder="Your name"
121
+ placeholderTextColor={Colors.gray6}
122
+ style={styles.textInput}
123
+ autoCapitalize="words"
124
+ maxLength={30}
125
+ returnKeyType="done"
126
+ onSubmitEditing={handleComplete}
127
+ />
128
+ <ErrorHint hint={error} />
129
+ </View>
130
+ </View>
131
+
132
+ <View style={styles.footer}>
133
+ <Button
134
+ style={[
135
+ styles.button,
136
+ { backgroundColor: buttonDisabled ? Colors.gray2 : theme.buttonColor },
137
+ ]}
138
+ onPress={handleComplete}
139
+ disabled={buttonDisabled}
140
+ >
141
+ <Text
142
+ style={[
143
+ styles.buttonText,
144
+ { color: buttonDisabled ? Colors.gray6 : theme.buttonText },
145
+ ]}
146
+ >
147
+ {isLoading ? 'Saving...' : 'Complete Setup'}
148
+ </Text>
149
+ </Button>
150
+ </View>
151
+ </AvoidKeyboardScreen>
152
+ );
153
+ }
154
+
155
+ const styles = StyleSheet.create({
156
+ content: {
157
+ flex: 1,
158
+ alignItems: 'center',
159
+ paddingHorizontal: 24,
160
+ paddingTop: 24,
161
+ },
162
+ avatarContainer: {
163
+ position: 'relative',
164
+ marginBottom: 8,
165
+ },
166
+ editBadge: {
167
+ position: 'absolute',
168
+ bottom: 0,
169
+ right: 0,
170
+ width: 32,
171
+ height: 32,
172
+ borderRadius: 16,
173
+ justifyContent: 'center',
174
+ alignItems: 'center',
175
+ borderWidth: 3,
176
+ borderColor: Colors.black,
177
+ },
178
+ editBadgeText: {
179
+ color: Colors.white,
180
+ fontSize: 18,
181
+ fontWeight: '700',
182
+ marginTop: -2,
183
+ },
184
+ photoHint: {
185
+ color: Colors.gray6,
186
+ marginBottom: 32,
187
+ },
188
+ inputContainer: {
189
+ width: '100%',
190
+ maxWidth: 400,
191
+ },
192
+ label: {
193
+ color: Colors.white,
194
+ marginBottom: 12,
195
+ },
196
+ textInput: {
197
+ color: Colors.white,
198
+ fontSize: 18,
199
+ fontWeight: '500',
200
+ paddingVertical: 16,
201
+ paddingHorizontal: 16,
202
+ borderWidth: 1,
203
+ borderColor: Colors.gray5,
204
+ borderRadius: 8,
205
+ backgroundColor: Colors.black,
206
+ },
207
+ footer: {
208
+ paddingHorizontal: 24,
209
+ paddingBottom: 32,
210
+ },
211
+ button: {
212
+ height: 56,
213
+ borderRadius: 28,
214
+ justifyContent: 'center',
215
+ alignItems: 'center',
216
+ },
217
+ buttonText: {
218
+ fontSize: 16,
219
+ fontWeight: '600',
220
+ },
221
+ });
@@ -0,0 +1,215 @@
1
+ import React, { useEffect, useState, useCallback } from 'react';
2
+ import { View, Text, StyleSheet, FlatList, Pressable, ActivityIndicator } from 'react-native';
3
+ import { useNavigation } from '@react-navigation/native';
4
+ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
5
+
6
+ import type { RootStackParamList } from '../../navigation/SquadNavigator';
7
+ import { useApiClient, useSquadSDK } from '../../SquadProvider';
8
+ import { useTheme, Colors } from '../../theme/ThemeContext';
9
+ import Button from '../../components/ux/buttons/Button';
10
+ import ScreenHeader from '../../components/ux/layout/ScreenHeader';
11
+ import { BodyRegular, TitleSmall } from '../../components/ux/text/Typography';
12
+
13
+ type Nav = NativeStackNavigationProp<RootStackParamList, 'OnboardingTeamSelect'>;
14
+
15
+ interface CommunityItem {
16
+ id: string;
17
+ name: string;
18
+ color?: string;
19
+ }
20
+
21
+ export function OnboardingTeamSelectScreen() {
22
+ const navigation = useNavigation<Nav>();
23
+ const sdk = useSquadSDK();
24
+ const apiClient = useApiClient();
25
+ const { theme } = useTheme();
26
+
27
+ const [communities, setCommunities] = useState<CommunityItem[]>([]);
28
+ const [selected, setSelected] = useState<string | null>(null);
29
+ const [loading, setLoading] = useState(true);
30
+
31
+ const partnerCommunityId = sdk.config.partnerAuth?.communityId;
32
+
33
+ useEffect(() => {
34
+ // If partner has a fixed community, auto-select and skip
35
+ if (partnerCommunityId) {
36
+ (async () => {
37
+ try {
38
+ await apiClient.updateUserCommunity({ id: partnerCommunityId });
39
+ navigation.navigate('OnboardingAccountSetup');
40
+ } catch (err) {
41
+ console.error('[TeamSelect] Error auto-setting partner community:', err);
42
+ setLoading(false);
43
+ }
44
+ })();
45
+ return;
46
+ }
47
+
48
+ const fetchCommunities = async () => {
49
+ try {
50
+ const result = await apiClient.fetchAllCommunities();
51
+ if (result?.communities) {
52
+ setCommunities(
53
+ result.communities.map(c => ({
54
+ id: c.id ?? '',
55
+ name: c.name,
56
+ color: c.color,
57
+ })),
58
+ );
59
+ }
60
+ } catch (err) {
61
+ console.error('[TeamSelect] Error fetching communities:', err);
62
+ } finally {
63
+ setLoading(false);
64
+ }
65
+ };
66
+ fetchCommunities();
67
+ }, [apiClient, partnerCommunityId, navigation]);
68
+
69
+ const handleSelect = useCallback((id: string) => {
70
+ setSelected(id);
71
+ }, []);
72
+
73
+ const handleContinue = useCallback(async () => {
74
+ if (!selected) return;
75
+
76
+ try {
77
+ await apiClient.updateUserCommunity({ id: selected });
78
+ navigation.navigate('OnboardingAccountSetup');
79
+ } catch (err) {
80
+ console.error('[TeamSelect] Error setting community:', err);
81
+ }
82
+ }, [selected, apiClient, navigation]);
83
+
84
+ const renderItem = useCallback(
85
+ ({ item }: { item: CommunityItem }) => (
86
+ <Pressable
87
+ style={[
88
+ styles.communityItem,
89
+ selected === item.id && styles.communityItemSelected,
90
+ selected === item.id && { borderColor: item.color ?? theme.buttonColor },
91
+ ]}
92
+ onPress={() => handleSelect(item.id)}
93
+ >
94
+ <View
95
+ style={[
96
+ styles.colorDot,
97
+ { backgroundColor: item.color ?? Colors.purple1 },
98
+ ]}
99
+ />
100
+ <TitleSmall style={styles.communityName}>{item.name}</TitleSmall>
101
+ {selected === item.id && (
102
+ <Text style={[styles.check, { color: item.color ?? theme.buttonColor }]}>
103
+ {'v'}
104
+ </Text>
105
+ )}
106
+ </Pressable>
107
+ ),
108
+ [selected, theme.buttonColor, handleSelect],
109
+ );
110
+
111
+ return (
112
+ <View style={[styles.container, { backgroundColor: theme.screenBackground }]}>
113
+ <ScreenHeader title="Pick Your Team" />
114
+
115
+ <View style={styles.content}>
116
+ <BodyRegular style={styles.subtitle}>
117
+ Choose your team to join their community
118
+ </BodyRegular>
119
+
120
+ {loading ? (
121
+ <ActivityIndicator color={Colors.white} style={styles.loader} />
122
+ ) : (
123
+ <FlatList
124
+ data={communities}
125
+ keyExtractor={item => item.id}
126
+ renderItem={renderItem}
127
+ contentContainerStyle={styles.list}
128
+ showsVerticalScrollIndicator={false}
129
+ />
130
+ )}
131
+ </View>
132
+
133
+ <View style={styles.footer}>
134
+ <Button
135
+ style={[
136
+ styles.button,
137
+ { backgroundColor: selected ? theme.buttonColor : Colors.gray2 },
138
+ ]}
139
+ onPress={handleContinue}
140
+ disabled={!selected}
141
+ >
142
+ <Text
143
+ style={[
144
+ styles.buttonText,
145
+ { color: selected ? theme.buttonText : Colors.gray6 },
146
+ ]}
147
+ >
148
+ Continue
149
+ </Text>
150
+ </Button>
151
+ </View>
152
+ </View>
153
+ );
154
+ }
155
+
156
+ const styles = StyleSheet.create({
157
+ container: {
158
+ flex: 1,
159
+ },
160
+ content: {
161
+ flex: 1,
162
+ paddingHorizontal: 24,
163
+ },
164
+ subtitle: {
165
+ color: Colors.gray6,
166
+ marginBottom: 24,
167
+ },
168
+ loader: {
169
+ marginTop: 48,
170
+ },
171
+ list: {
172
+ gap: 8,
173
+ paddingBottom: 24,
174
+ },
175
+ communityItem: {
176
+ flexDirection: 'row',
177
+ alignItems: 'center',
178
+ padding: 16,
179
+ backgroundColor: Colors.gray2,
180
+ borderRadius: 12,
181
+ borderWidth: 2,
182
+ borderColor: 'transparent',
183
+ },
184
+ communityItemSelected: {
185
+ backgroundColor: 'rgba(110, 130, 231, 0.1)',
186
+ },
187
+ colorDot: {
188
+ width: 32,
189
+ height: 32,
190
+ borderRadius: 16,
191
+ marginRight: 12,
192
+ },
193
+ communityName: {
194
+ color: Colors.white,
195
+ flex: 1,
196
+ },
197
+ check: {
198
+ fontSize: 18,
199
+ fontWeight: '700',
200
+ },
201
+ footer: {
202
+ paddingHorizontal: 24,
203
+ paddingBottom: 32,
204
+ },
205
+ button: {
206
+ height: 56,
207
+ borderRadius: 28,
208
+ justifyContent: 'center',
209
+ alignItems: 'center',
210
+ },
211
+ buttonText: {
212
+ fontSize: 16,
213
+ fontWeight: '600',
214
+ },
215
+ });
@@ -0,0 +1,93 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ import { useNavigation } from '@react-navigation/native';
4
+ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
5
+
6
+ import type { RootStackParamList } from '../../navigation/SquadNavigator';
7
+ import { useTheme, Colors } from '../../theme/ThemeContext';
8
+ import Button from '../../components/ux/buttons/Button';
9
+ import { TitleRegular, BodyRegular } from '../../components/ux/text/Typography';
10
+
11
+ type Nav = NativeStackNavigationProp<RootStackParamList, 'OnboardingWelcome'>;
12
+
13
+ export function OnboardingWelcomeScreen() {
14
+ const navigation = useNavigation<Nav>();
15
+ const { theme } = useTheme();
16
+
17
+ return (
18
+ <View style={[styles.container, { backgroundColor: theme.screenBackground }]}>
19
+ <View style={styles.content}>
20
+ <View style={[styles.circle, { borderColor: theme.buttonColor }]}>
21
+ <Text style={styles.circleEmoji}>{'\\uD83C\\uDFC8'}</Text>
22
+ </View>
23
+
24
+ <TitleRegular style={styles.title}>
25
+ Welcome to Squad
26
+ </TitleRegular>
27
+
28
+ <BodyRegular style={styles.subtitle}>
29
+ Connect with your squad, share freestyles, and experience sports together.
30
+ </BodyRegular>
31
+ </View>
32
+
33
+ <View style={styles.footer}>
34
+ <Button
35
+ style={[styles.button, { backgroundColor: theme.buttonColor }]}
36
+ onPress={() => navigation.navigate('OnboardingTeamSelect')}
37
+ >
38
+ <Text style={[styles.buttonText, { color: theme.buttonText }]}>
39
+ Get Started
40
+ </Text>
41
+ </Button>
42
+ </View>
43
+ </View>
44
+ );
45
+ }
46
+
47
+ const styles = StyleSheet.create({
48
+ container: {
49
+ flex: 1,
50
+ paddingHorizontal: 24,
51
+ paddingBottom: 48,
52
+ },
53
+ content: {
54
+ flex: 1,
55
+ justifyContent: 'center',
56
+ alignItems: 'center',
57
+ },
58
+ circle: {
59
+ width: 120,
60
+ height: 120,
61
+ borderRadius: 60,
62
+ borderWidth: 3,
63
+ justifyContent: 'center',
64
+ alignItems: 'center',
65
+ marginBottom: 32,
66
+ },
67
+ circleEmoji: {
68
+ fontSize: 48,
69
+ },
70
+ title: {
71
+ color: Colors.white,
72
+ textAlign: 'center',
73
+ marginBottom: 12,
74
+ },
75
+ subtitle: {
76
+ color: Colors.gray6,
77
+ textAlign: 'center',
78
+ maxWidth: 280,
79
+ },
80
+ footer: {
81
+ gap: 12,
82
+ },
83
+ button: {
84
+ height: 56,
85
+ borderRadius: 28,
86
+ justifyContent: 'center',
87
+ alignItems: 'center',
88
+ },
89
+ buttonText: {
90
+ fontSize: 16,
91
+ fontWeight: '600',
92
+ },
93
+ });