@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,80 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { useApiClient } from '../SquadProvider';
3
+ import { SquadLineClient, type CallState, type CallInfo } from './SquadLineClient';
4
+
5
+ /**
6
+ * React hook for Squad Line voice calling.
7
+ * Wraps SquadLineClient for easy consumption in components.
8
+ */
9
+ export function useSquadLine() {
10
+ const apiClient = useApiClient();
11
+ const [callState, setCallState] = useState<CallState>('idle');
12
+ const [currentCall, setCurrentCall] = useState<CallInfo | null>(null);
13
+ const [isMuted, setIsMuted] = useState(false);
14
+ const [isSpeakerOn, setIsSpeakerOn] = useState(false);
15
+ const [incomingCall, setIncomingCall] = useState<CallInfo | null>(null);
16
+
17
+ const client = SquadLineClient.getInstance(apiClient);
18
+
19
+ useEffect(() => {
20
+ const onCallStateChanged = (state: CallState) => setCallState(state);
21
+ const onMuteChanged = (muted: boolean) => setIsMuted(muted);
22
+ const onSpeakerChanged = (speaker: boolean) => setIsSpeakerOn(speaker);
23
+ const onIncomingCall = (info: CallInfo) => setIncomingCall(info);
24
+ const onConnected = (info: CallInfo) => {
25
+ setCurrentCall(info);
26
+ setIncomingCall(null);
27
+ };
28
+ const onDisconnected = () => {
29
+ setCurrentCall(null);
30
+ setIncomingCall(null);
31
+ };
32
+
33
+ client.on('callStateChanged', onCallStateChanged);
34
+ client.on('muteChanged', onMuteChanged);
35
+ client.on('speakerChanged', onSpeakerChanged);
36
+ client.on('incomingCall', onIncomingCall);
37
+ client.on('connected', onConnected);
38
+ client.on('disconnected', onDisconnected);
39
+
40
+ return () => {
41
+ client.off('callStateChanged', onCallStateChanged);
42
+ client.off('muteChanged', onMuteChanged);
43
+ client.off('speakerChanged', onSpeakerChanged);
44
+ client.off('incomingCall', onIncomingCall);
45
+ client.off('connected', onConnected);
46
+ client.off('disconnected', onDisconnected);
47
+ };
48
+ }, [client]);
49
+
50
+ const makeCall = useCallback(
51
+ (connectionId: string, title: string, calleeIdentity?: string) =>
52
+ client.makeCall(connectionId, title, calleeIdentity),
53
+ [client],
54
+ );
55
+
56
+ const endCall = useCallback(() => client.endCall(), [client]);
57
+ const acceptCall = useCallback(() => client.acceptCall(), [client]);
58
+ const rejectCall = useCallback(() => client.rejectCall(), [client]);
59
+ const toggleMute = useCallback(() => client.toggleMute(), [client]);
60
+ const toggleSpeaker = useCallback(() => client.toggleSpeaker(), [client]);
61
+ const sendReaction = useCallback(
62
+ (emoji: string, imageUrl?: string) => client.sendReaction(emoji, imageUrl),
63
+ [client],
64
+ );
65
+
66
+ return {
67
+ callState,
68
+ currentCall,
69
+ incomingCall,
70
+ isMuted,
71
+ isSpeakerOn,
72
+ makeCall,
73
+ endCall,
74
+ acceptCall,
75
+ rejectCall,
76
+ toggleMute,
77
+ toggleSpeaker,
78
+ sendReaction,
79
+ };
80
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Audio playback/recording atoms.
3
+ * Ported from squad-demo/src/atoms/audio.ts.
4
+ */
5
+ import { atom, atomFamily } from 'recoil';
6
+
7
+ export type AudioStatus = {
8
+ isPlaying: boolean;
9
+ isLoaded: boolean;
10
+ durationMs: number;
11
+ positionMs: number;
12
+ };
13
+
14
+ export const playerForRecorder = atomFamily<unknown | null, string>({
15
+ key: 'squad-sdk:audio:recorder:player',
16
+ default: null,
17
+ });
18
+
19
+ export const statusForAudioDevice = atomFamily<AudioStatus | null, string | null>({
20
+ key: 'squad-sdk:audio:device:status',
21
+ default: null,
22
+ });
23
+
24
+ export const positionForAudioPlayer = atomFamily<number, string | null>({
25
+ key: 'squad-sdk:audio:device:progress',
26
+ default: 0,
27
+ });
28
+
29
+ // Global audio state
30
+ export const isAudioRecording = atom<boolean>({
31
+ key: 'squad-sdk:audio:isRecording',
32
+ default: false,
33
+ });
34
+
35
+ export const activeAudioPlayerId = atom<string | null>({
36
+ key: 'squad-sdk:audio:activePlayerId',
37
+ default: null,
38
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * API client selectors.
3
+ * Ported from squad-demo/src/atoms/client.ts.
4
+ */
5
+ import { selector } from 'recoil';
6
+ import { reActiveAccessToken } from './session';
7
+ import { configApp } from './features';
8
+
9
+ // Preferred API environment
10
+ export const rePreferredApiEnvironment = selector<string>({
11
+ key: 'squad-sdk:client:preferredApiEnvironment',
12
+ get: ({ get }) => {
13
+ const config = get(configApp);
14
+ return config.apiEnvironments[0]?.squadApiBaseUrl ?? 'https://api-release.withyoursquad.com';
15
+ },
16
+ });
17
+
18
+ // Client selector that creates API client from current token + environment
19
+ export const reAnyClient = selector<{ baseUrl: string; accessToken: string | null }>({
20
+ key: 'squad-sdk:client:any',
21
+ get: ({ get }) => {
22
+ const accessToken = get(reActiveAccessToken);
23
+ const baseUrl = get(rePreferredApiEnvironment);
24
+ return { baseUrl, accessToken };
25
+ },
26
+ });
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Community atoms.
3
+ * Ported from squad-demo/src/atoms/communities.ts + community-reactions.ts + community-selection.ts + onboarding-community.ts.
4
+ */
5
+ import { atom, selector, selectorFamily } from 'recoil';
6
+ import type { Community, Communities } from '@squad-sports/core';
7
+
8
+ export const reAllCommunities = atom<Community[]>({
9
+ key: 'squad-sdk:communities:all',
10
+ default: [],
11
+ });
12
+
13
+ export const reAllCommunitiesByGroups = atom<unknown | undefined>({
14
+ key: 'squad-sdk:communities:byGroup',
15
+ default: undefined,
16
+ });
17
+
18
+ export const reCommunityById = selectorFamily<Community | null, string>({
19
+ key: 'squad-sdk:communities:byId',
20
+ get: (id) => ({ get }) => {
21
+ return get(reAllCommunities).find(c => c.id === id) ?? null;
22
+ },
23
+ });
24
+
25
+ export const reCommunitiesRefresh = atom<number>({
26
+ key: 'squad-sdk:communities:refresh',
27
+ default: 0,
28
+ });
29
+
30
+ export const reUserCommunity = atom<Community | null>({
31
+ key: 'squad-sdk:communities:userCommunity',
32
+ default: null,
33
+ });
34
+
35
+ // Community reactions
36
+ export const reCommunityReactions = atom<Map<string, unknown[]>>({
37
+ key: 'squad-sdk:communities:reactions',
38
+ default: new Map(),
39
+ });
40
+
41
+ // Onboarding community selection
42
+ export const reOnboardingSelectedCommunity = atom<Community | null>({
43
+ key: 'squad-sdk:onboarding:selectedCommunity',
44
+ default: null,
45
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Device contacts atoms.
3
+ * Ported from squad-demo/src/atoms/contacts.ts.
4
+ */
5
+ import { atom } from 'recoil';
6
+
7
+ export interface DeviceContact {
8
+ id: string;
9
+ name: string;
10
+ phoneNumbers: string[];
11
+ emails: string[];
12
+ imageUri?: string;
13
+ }
14
+
15
+ export const deviceContacts = atom<DeviceContact[]>({
16
+ key: 'squad-sdk:contacts:device',
17
+ default: [],
18
+ });
19
+
20
+ export const contactsLoaded = atom<boolean>({
21
+ key: 'squad-sdk:contacts:loaded',
22
+ default: false,
23
+ });
24
+
25
+ export const contactsOnSquad = atom<string[]>({
26
+ key: 'squad-sdk:contacts:onSquad',
27
+ default: [],
28
+ });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Device info atoms.
3
+ * Ported from squad-demo/src/atoms/device-info.ts.
4
+ */
5
+ import { atom } from 'recoil';
6
+
7
+ export const reDeviceInfo = atom<{
8
+ platform: string;
9
+ osVersion: string;
10
+ appVersion: string;
11
+ deviceModel: string;
12
+ pushToken: string | null;
13
+ }>({
14
+ key: 'squad-sdk:device:info',
15
+ default: {
16
+ platform: '',
17
+ osVersion: '',
18
+ appVersion: '',
19
+ deviceModel: '',
20
+ pushToken: null,
21
+ },
22
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Event atoms.
3
+ * Ported from squad-demo/src/atoms/events.ts.
4
+ */
5
+ import { atom } from 'recoil';
6
+ import type { Events } from '@squad-sports/core';
7
+
8
+ export const reActiveEvents = atom<Events | null>({
9
+ key: 'squad-sdk:events:active',
10
+ default: null,
11
+ });
12
+
13
+ export const reEventsRefresh = atom<number>({
14
+ key: 'squad-sdk:events:refresh',
15
+ default: 0,
16
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Feature flag and config atoms.
3
+ * Ported from squad-demo/src/atoms/features.ts + config-app.ts + config-squad.ts + developer-access.ts + app-store.ts.
4
+ */
5
+ import { atom, selector } from 'recoil';
6
+
7
+ // Feature flags
8
+ export const featureFlags = atom<Record<string, boolean>>({
9
+ key: 'squad-sdk:features:flags',
10
+ default: {
11
+ squadLine: true,
12
+ freestyle: true,
13
+ messaging: true,
14
+ polls: true,
15
+ events: true,
16
+ wallet: true,
17
+ },
18
+ });
19
+
20
+ // App config
21
+ export interface ApiEnvironment {
22
+ squadApiBaseUrl: string;
23
+ name: string;
24
+ }
25
+
26
+ export const configApp = atom<{
27
+ apiEnvironments: ApiEnvironment[];
28
+ }>({
29
+ key: 'squad-sdk:config:app',
30
+ default: {
31
+ apiEnvironments: [{ squadApiBaseUrl: 'https://api-release.withyoursquad.com', name: 'production' }],
32
+ },
33
+ });
34
+
35
+ // Developer access
36
+ export const developerAccessEnabled = atom<boolean>({
37
+ key: 'squad-sdk:developer:access',
38
+ default: false,
39
+ });
40
+
41
+ // App store version
42
+ export const reLatestAppStoreVersion = atom<string | null>({
43
+ key: 'squad-sdk:appStore:latestVersion',
44
+ default: null,
45
+ });
46
+
47
+ // Session count
48
+ export const sessionCount = atom<number>({
49
+ key: 'squad-sdk:session:count',
50
+ default: 0,
51
+ });
52
+
53
+ // Prerelease
54
+ export const rePrereleaseEnabled = atom<boolean>({
55
+ key: 'squad-sdk:features:prerelease',
56
+ default: false,
57
+ });
@@ -0,0 +1,121 @@
1
+ /**
2
+ * State management — all Recoil atoms.
3
+ * Complete port from squad-demo/src/atoms/.
4
+ */
5
+
6
+ // CRDT
7
+ export { compileCrdt, compileCrdtSingle } from './sync/crdt';
8
+ export type { CrdtArray, CrdtArrayItem, CrdtArrayOperation } from './sync/crdt';
9
+
10
+ // Dependable
11
+ export { DependableAtom, DependableSelector, DependableAtomFamily } from './sync/dependable';
12
+
13
+ // Refresh
14
+ export { reLastRefreshed, reRefresh, reRefreshing } from './sync/refresh';
15
+
16
+ // Offline
17
+ export { isOnlineAtom, pendingActionsAtom, offlineCacheAtom, shouldUseOfflineDataSelector } from './sync/offline-support';
18
+ export type { QueuedOfflineAction, OfflineActionType } from './sync/offline-support';
19
+
20
+ // Session / Auth
21
+ export {
22
+ reActivePhoneNumber, reActiveEmail, reActiveCode,
23
+ reActiveAccessToken, reActiveUserId, reUserRefresh,
24
+ reAuthHydrated, rePendingNavigation, reAttemptedVerifications,
25
+ pruneExpiredAttemptedVerifications, ATTEMPTED_VERIFICATION_WINDOW_MS,
26
+ } from './session';
27
+ export type { AttemptedVerification } from './session';
28
+
29
+ // Permissions
30
+ export {
31
+ microphonePermissions, contactsPermissions, cameraPermissions,
32
+ imagesPermissions, notificationsPermissions,
33
+ } from './permissions';
34
+ export type { PermissionStatus } from './permissions';
35
+
36
+ // Modal queue
37
+ export { default as modalQueue, ModalQueueItem, ModalType, topModalAtom } from './modal-queue';
38
+ export { DialogKey, ToastKey, BottomSheetKey } from './modal-keys';
39
+ export type { ModalKey, DialogProps, ToastProps, BottomSheetProps } from './modal-keys';
40
+ export type { ModalQueueOptions } from './modal-queue';
41
+
42
+ // Communities
43
+ export {
44
+ reAllCommunities, reAllCommunitiesByGroups, reCommunityById,
45
+ reCommunitiesRefresh, reUserCommunity, reCommunityReactions,
46
+ reOnboardingSelectedCommunity,
47
+ } from './communities';
48
+
49
+ // Navigation
50
+ export { currentStack, navigationState, reOnboardingStep, rePostOnboardingLoadingActive } from './navigation';
51
+ export type { ScreenName, OnboardingStep } from './navigation';
52
+
53
+ // Audio
54
+ export {
55
+ playerForRecorder, statusForAudioDevice, positionForAudioPlayer,
56
+ isAudioRecording, activeAudioPlayerId,
57
+ } from './audio';
58
+ export type { AudioStatus } from './audio';
59
+
60
+ // UI
61
+ export {
62
+ loadingOverlayVisible, loadingOverlayText,
63
+ aKeyboardOpen, networkBannerVisible, networkStateAtom,
64
+ } from './ui';
65
+
66
+ // Features / Config
67
+ export {
68
+ featureFlags, configApp, developerAccessEnabled,
69
+ reLatestAppStoreVersion, sessionCount, rePrereleaseEnabled,
70
+ } from './features';
71
+ export type { ApiEnvironment } from './features';
72
+
73
+ // Contacts
74
+ export { deviceContacts, contactsLoaded, contactsOnSquad } from './contacts';
75
+ export type { DeviceContact } from './contacts';
76
+
77
+ // Wallet
78
+ export { reWallet, reCoupons, reBrands, reWalletRefresh } from './wallet';
79
+
80
+ // SOTD
81
+ export { reSquaddieOfTheDay, reSOTDAnimationShown, reSOTDIntroShown } from './squaddie-of-the-day';
82
+
83
+ // User
84
+ export {
85
+ reUserCache, reLoggedInUserLoaded, userUpdates,
86
+ reUser, reUserTag, reSophiaUser,
87
+ } from './user';
88
+
89
+ // Client
90
+ export { rePreferredApiEnvironment, reAnyClient } from './client';
91
+
92
+ // Sync — Squad
93
+ export { reInitialConnections, reConnectionUpdates, reConnectionWithUser } from './sync/squad-v2';
94
+
95
+ // Sync — Feed
96
+ export {
97
+ reInitialFeed, feedUpdates, feedExpiryTickAtom,
98
+ freestyleReactionUpdates, reFreestylePrompts, reFreestyleCreating,
99
+ } from './sync/feed-v2';
100
+
101
+ // Sync — Messages
102
+ export {
103
+ reInitialConversationState, reConnectionMessages, reMessageReaction,
104
+ reMessageSendStatus, reMessagePrompts,
105
+ } from './sync/messages';
106
+ export type { FailedMessageInfo } from './sync/messages';
107
+
108
+ // Sync — Polls
109
+ export {
110
+ reInitialPollFeed, rePollUpdates, rePollResponseUpdates,
111
+ rePollResponseReactionsUpdates, rePollNudgesUpdates,
112
+ } from './sync/polls';
113
+
114
+ // Events
115
+ export { reActiveEvents, reEventsRefresh } from './events';
116
+
117
+ // Invitations
118
+ export { reInvitationCode, reInvitationCodeLoaded } from './invitations';
119
+
120
+ // Device info
121
+ export { reDeviceInfo } from './device-info';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Invitation atoms.
3
+ * Ported from squad-demo/src/atoms/invitations.ts + live/invitation-code.ts.
4
+ */
5
+ import { atom } from 'recoil';
6
+ import type { InvitationCode } from '@squad-sports/core';
7
+
8
+ export const reInvitationCode = atom<InvitationCode | null>({
9
+ key: 'squad-sdk:invitations:code',
10
+ default: null,
11
+ });
12
+
13
+ export const reInvitationCodeLoaded = atom<boolean>({
14
+ key: 'squad-sdk:invitations:loaded',
15
+ default: false,
16
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Modal key definitions for dialogs, toasts, and bottom sheets.
3
+ * Ported from squad-demo/src/atoms/modal-keys.ts.
4
+ */
5
+
6
+ export enum DialogKey {
7
+ blockConfirmation = 'block-confirmation',
8
+ deleteConfirmation = 'delete-confirmation',
9
+ flagConfirmation = 'flag-confirmation',
10
+ micPermissions = 'mic-permissions',
11
+ cameraPermissions = 'camera-permissions',
12
+ imagesPermissions = 'images-permissions',
13
+ notificationPermissions = 'notification-permissions',
14
+ unblockConfirmation = 'unblock-confirmation',
15
+ SOTDIntroduction = 'sotd-introduction',
16
+ SOTDBlocked = 'sotd-blocked',
17
+ progressCongratulation = 'progress-congratulation',
18
+ collectEmail = 'collect-email',
19
+ noConnection = 'no-connection',
20
+ removeFromSquad = 'remove-from-squad',
21
+ }
22
+
23
+ export enum ToastKey {
24
+ success = 'success',
25
+ error = 'error',
26
+ info = 'info',
27
+ busy = 'busy',
28
+ networkBanner = 'network-banner',
29
+ }
30
+
31
+ export enum BottomSheetKey {
32
+ squadLineInvitation = 'squad-line-invitation',
33
+ help = 'help',
34
+ quickTip = 'quick-tip',
35
+ sophiaIntroduction = 'sophia-introduction',
36
+ SOTD = 'SOTD',
37
+ upgradeHardLock = 'upgrade-hard-lock',
38
+ upgradeSoftLock = 'upgrade-soft-lock',
39
+ addFreestyleReaction = 'add-freestyle-reaction',
40
+ selectCommunity = 'select-community',
41
+ removeOrBlock = 'remove-or-block',
42
+ addPollReaction = 'add-poll-reaction',
43
+ filterPollSummation = 'filter-poll-summation',
44
+ pollReactions = 'poll-reactions',
45
+ inviteFeatureIntroduction = 'invite-feature-introduction',
46
+ sotdSelectingFriend = 'SOTD-selecting-friend',
47
+ errorInfo = 'error-info',
48
+ inviteContact = 'invite-contact',
49
+ inviteErrorSquadMaxed = 'invite-error-squad-maxed',
50
+ inviteErrorOtherSquadMaxed = 'invite-error-other-squad-maxed',
51
+ requestItemMenu = 'request-item-menu',
52
+ sotdIntroduction = 'sotd-introduction-selecting',
53
+ selectTeam = 'select-team',
54
+ viewAllAttendees = 'events-view-all-attendees',
55
+ redeemWalletCode = 'redeem-wallet-code',
56
+ couponShareSheet = 'coupon-share-sheet',
57
+ couponInternalShareSheet = 'coupon-internal-share-sheet',
58
+ }
59
+
60
+ export type DialogProps = Record<string, any>;
61
+ export type ToastProps = Record<string, any>;
62
+ export type BottomSheetProps = Record<string, any> & { onDismiss?: () => void };
63
+ export type ModalKey = DialogKey | ToastKey | BottomSheetKey;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Modal queue system for managing dialog/toast/bottom sheet display.
3
+ * Ported from squad-demo/src/atoms/modal-queue.ts.
4
+ */
5
+ import EventEmitter from 'eventemitter3';
6
+ import { atomFamily } from 'recoil';
7
+ import { type ModalKey, type DialogProps, DialogKey, ToastKey, BottomSheetKey } from './modal-keys';
8
+
9
+ export const enum ModalType {
10
+ Dialog = 'dialog',
11
+ Toast = 'toast',
12
+ BottomSheet = 'bottomSheet',
13
+ }
14
+
15
+ export type ModalQueueOptions = Partial<{
16
+ animationType: 'none' | 'slide' | 'fade';
17
+ dismissable: boolean;
18
+ }>;
19
+
20
+ export class ModalQueueItem {
21
+ key: ModalKey;
22
+ keyType: ModalType;
23
+ options: ModalQueueOptions;
24
+ private props?: DialogProps;
25
+
26
+ constructor(key: ModalKey, props?: DialogProps, options: ModalQueueOptions = {}) {
27
+ this.key = key;
28
+ this.options = options;
29
+ this.props = props;
30
+ this.keyType = ModalQueueItem.getTypeForKey(key);
31
+ }
32
+
33
+ get isDismissable(): boolean {
34
+ if (typeof this.options.dismissable === 'boolean') return this.options.dismissable;
35
+ return this.keyType === ModalType.BottomSheet;
36
+ }
37
+
38
+ getProps<T extends DialogProps>(): T {
39
+ return (this.props ?? {}) as T;
40
+ }
41
+
42
+ static getTypeForKey(key: ModalKey): ModalType {
43
+ if (Object.values(DialogKey).includes(key as DialogKey)) return ModalType.Dialog;
44
+ if (Object.values(ToastKey).includes(key as ToastKey)) return ModalType.Toast;
45
+ return ModalType.BottomSheet;
46
+ }
47
+ }
48
+
49
+ class ModalQueue {
50
+ private queue: ModalQueueItem[] = [];
51
+ private emitter = new EventEmitter();
52
+
53
+ push(key: ModalKey, props?: DialogProps, options?: ModalQueueOptions): void {
54
+ const item = new ModalQueueItem(key, props, options);
55
+ this.queue.push(item);
56
+ this.emitter.emit('change', this.queue);
57
+ }
58
+
59
+ pop(): ModalQueueItem | undefined {
60
+ const item = this.queue.shift();
61
+ this.emitter.emit('change', this.queue);
62
+ return item;
63
+ }
64
+
65
+ peek(): ModalQueueItem | undefined {
66
+ return this.queue[0];
67
+ }
68
+
69
+ dismiss(key?: ModalKey): void {
70
+ if (key) {
71
+ this.queue = this.queue.filter(item => item.key !== key);
72
+ } else {
73
+ this.queue.shift();
74
+ }
75
+ this.emitter.emit('change', this.queue);
76
+ }
77
+
78
+ clear(): void {
79
+ this.queue = [];
80
+ this.emitter.emit('change', this.queue);
81
+ }
82
+
83
+ get length(): number {
84
+ return this.queue.length;
85
+ }
86
+
87
+ get isEmpty(): boolean {
88
+ return this.queue.length === 0;
89
+ }
90
+
91
+ onChange(callback: (queue: ModalQueueItem[]) => void): () => void {
92
+ this.emitter.on('change', callback);
93
+ return () => this.emitter.off('change', callback);
94
+ }
95
+ }
96
+
97
+ const modalQueue = new ModalQueue();
98
+ export default modalQueue;
99
+
100
+ // Recoil atom for top modal (used by renderers)
101
+ export const topModalAtom = atomFamily<ModalQueueItem | null, string>({
102
+ key: 'squad-sdk:modal:top',
103
+ default: null,
104
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Navigation atoms.
3
+ * Ported from squad-demo/src/atoms/navigation.ts + onboarding.ts + post-onboarding.ts.
4
+ */
5
+ import { atom } from 'recoil';
6
+
7
+ export type ScreenName = string;
8
+
9
+ export const currentStack = atom<ScreenName[]>({
10
+ key: 'squad-sdk:navigation:currentStack',
11
+ default: [],
12
+ });
13
+
14
+ export const navigationState = atom<'auth' | 'onboarding' | 'main'>({
15
+ key: 'squad-sdk:navigation:state',
16
+ default: 'auth',
17
+ });
18
+
19
+ // Onboarding step tracking
20
+ export type OnboardingStep =
21
+ | 'welcome'
22
+ | 'teamSelect'
23
+ | 'accountSetup'
24
+ | 'complete';
25
+
26
+ export const reOnboardingStep = atom<OnboardingStep>({
27
+ key: 'squad-sdk:onboarding:step',
28
+ default: 'welcome',
29
+ });
30
+
31
+ export const rePostOnboardingLoadingActive = atom<boolean>({
32
+ key: 'squad-sdk:onboarding:postLoadingActive',
33
+ default: false,
34
+ });