@teamvortexsoftware/vortex-react-native 0.0.13 → 1.0.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 (228) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1227 -11
  3. package/dist/InviteFormCore-D4HkMMo0.d.mts +721 -0
  4. package/dist/InviteFormCore-D9oUCbu7.d.ts +721 -0
  5. package/dist/VortexClient.js +192 -0
  6. package/dist/VortexClient.js.map +1 -0
  7. package/dist/VortexDeferredLinks.js +127 -0
  8. package/dist/VortexDeferredLinks.js.map +1 -0
  9. package/dist/clientInfo.js +45 -0
  10. package/dist/clientInfo.js.map +1 -0
  11. package/dist/components/ContactsPickerModal.js +182 -0
  12. package/dist/components/ContactsPickerModal.js.map +1 -0
  13. package/dist/components/InviteFormCore.js +2141 -0
  14. package/dist/components/InviteFormCore.js.map +1 -0
  15. package/dist/components/InviteFormMobile.js +463 -0
  16. package/dist/components/InviteFormMobile.js.map +1 -0
  17. package/dist/components/InviteFormWeb.js +295 -0
  18. package/dist/components/InviteFormWeb.js.map +1 -0
  19. package/dist/components/PlacedItemToolbar.js +147 -0
  20. package/dist/components/PlacedItemToolbar.js.map +1 -0
  21. package/dist/components/ShareButtons.js +1 -0
  22. package/dist/components/ShareButtons.js.map +1 -0
  23. package/dist/components/VrtxContactsImport.js +234 -0
  24. package/dist/components/VrtxContactsImport.js.map +1 -0
  25. package/dist/components/VrtxEmailInvitations.js +341 -0
  26. package/dist/components/VrtxEmailInvitations.js.map +1 -0
  27. package/dist/components/VrtxFindFriends.js +400 -0
  28. package/dist/components/VrtxFindFriends.js.map +1 -0
  29. package/dist/components/VrtxHeading.js +58 -0
  30. package/dist/components/VrtxHeading.js.map +1 -0
  31. package/dist/components/VrtxIncomingInvitations.js +657 -0
  32. package/dist/components/VrtxIncomingInvitations.js.map +1 -0
  33. package/dist/components/VrtxInvitationSuggestions.js +506 -0
  34. package/dist/components/VrtxInvitationSuggestions.js.map +1 -0
  35. package/dist/components/VrtxInviteContacts.js +512 -0
  36. package/dist/components/VrtxInviteContacts.js.map +1 -0
  37. package/dist/components/VrtxOutgoingInvitations.js +572 -0
  38. package/dist/components/VrtxOutgoingInvitations.js.map +1 -0
  39. package/dist/components/VrtxSearchBox.js +487 -0
  40. package/dist/components/VrtxSearchBox.js.map +1 -0
  41. package/dist/components/VrtxSelect.js +27 -0
  42. package/dist/components/VrtxSelect.js.map +1 -0
  43. package/dist/components/VrtxShareOptions.js +435 -0
  44. package/dist/components/VrtxShareOptions.js.map +1 -0
  45. package/dist/components/VrtxSubmit.js +132 -0
  46. package/dist/components/VrtxSubmit.js.map +1 -0
  47. package/dist/components/VrtxText.js +146 -0
  48. package/dist/components/VrtxText.js.map +1 -0
  49. package/dist/constants/mockData.d.mts +7 -0
  50. package/dist/constants/mockData.d.ts +7 -0
  51. package/dist/constants/mockData.js +48 -0
  52. package/dist/constants/mockData.js.map +1 -0
  53. package/dist/constants/mockData.mjs +22 -0
  54. package/dist/constants/mockData.mjs.map +1 -0
  55. package/dist/context/VortexModulesContext.js +135 -0
  56. package/dist/context/VortexModulesContext.js.map +1 -0
  57. package/dist/hooks/useInvitationFormLogic.d.mts +2 -0
  58. package/dist/hooks/useInvitationFormLogic.d.ts +2 -0
  59. package/dist/hooks/useInvitationFormLogic.js +300 -0
  60. package/dist/hooks/useInvitationFormLogic.js.map +1 -0
  61. package/dist/hooks/useInvitationFormLogic.mjs +276 -0
  62. package/dist/hooks/useInvitationFormLogic.mjs.map +1 -0
  63. package/dist/hooks/usePrefetchWidgetConfiguration.js +117 -0
  64. package/dist/hooks/usePrefetchWidgetConfiguration.js.map +1 -0
  65. package/dist/hooks/useThemeStyles.js +2 -0
  66. package/dist/hooks/useThemeStyles.js.map +1 -0
  67. package/dist/hooks/useVortexInvite.js +467 -56
  68. package/dist/hooks/useVortexInvite.js.map +1 -0
  69. package/dist/index-web.d.mts +93 -0
  70. package/dist/index-web.d.ts +93 -0
  71. package/dist/index-web.js +7397 -0
  72. package/dist/index-web.js.map +1 -0
  73. package/dist/index-web.mjs +7445 -0
  74. package/dist/index-web.mjs.map +1 -0
  75. package/dist/index.d.mts +656 -0
  76. package/dist/index.d.ts +656 -0
  77. package/dist/index.js +10205 -4
  78. package/dist/index.js.map +1 -0
  79. package/dist/index.mjs +10244 -0
  80. package/dist/index.mjs.map +1 -0
  81. package/dist/types/VortexClient.d.ts +106 -0
  82. package/dist/types/VortexClient.d.ts.map +1 -0
  83. package/dist/types/VortexDeferredLinks.d.ts +73 -0
  84. package/dist/types/VortexDeferredLinks.d.ts.map +1 -0
  85. package/dist/types/clientInfo.d.ts +5 -0
  86. package/dist/types/clientInfo.d.ts.map +1 -0
  87. package/dist/types/components/ContactsPickerModal.d.ts +18 -0
  88. package/dist/types/components/ContactsPickerModal.d.ts.map +1 -0
  89. package/dist/types/components/InviteFormCore.d.ts +166 -0
  90. package/dist/types/components/InviteFormCore.d.ts.map +1 -0
  91. package/dist/types/components/InviteFormMobile.d.ts +42 -0
  92. package/dist/types/components/InviteFormMobile.d.ts.map +1 -0
  93. package/dist/types/components/InviteFormWeb.d.ts +87 -0
  94. package/dist/types/components/InviteFormWeb.d.ts.map +1 -0
  95. package/dist/types/components/PlacedItemToolbar.d.ts +16 -0
  96. package/dist/types/components/PlacedItemToolbar.d.ts.map +1 -0
  97. package/dist/types/components/VrtxContactsImport.d.ts +14 -0
  98. package/dist/types/components/VrtxContactsImport.d.ts.map +1 -0
  99. package/dist/types/components/VrtxEmailInvitations.d.ts +31 -0
  100. package/dist/types/components/VrtxEmailInvitations.d.ts.map +1 -0
  101. package/dist/types/components/VrtxFindFriends.d.ts +25 -0
  102. package/dist/types/components/VrtxFindFriends.d.ts.map +1 -0
  103. package/dist/types/components/VrtxHeading.d.ts +6 -0
  104. package/dist/types/components/VrtxHeading.d.ts.map +1 -0
  105. package/dist/types/components/VrtxIncomingInvitations.d.ts +27 -0
  106. package/dist/types/components/VrtxIncomingInvitations.d.ts.map +1 -0
  107. package/dist/types/components/VrtxInvitationSuggestions.d.ts +25 -0
  108. package/dist/types/components/VrtxInvitationSuggestions.d.ts.map +1 -0
  109. package/dist/types/components/VrtxInviteContacts.d.ts +24 -0
  110. package/dist/types/components/VrtxInviteContacts.d.ts.map +1 -0
  111. package/dist/types/components/VrtxOutgoingInvitations.d.ts +27 -0
  112. package/dist/types/components/VrtxOutgoingInvitations.d.ts.map +1 -0
  113. package/dist/types/components/VrtxSearchBox.d.ts +28 -0
  114. package/dist/types/components/VrtxSearchBox.d.ts.map +1 -0
  115. package/dist/types/components/VrtxSelect.d.ts +6 -0
  116. package/dist/types/components/VrtxSelect.d.ts.map +1 -0
  117. package/dist/types/components/VrtxShareOptions.d.ts +41 -0
  118. package/dist/types/components/VrtxShareOptions.d.ts.map +1 -0
  119. package/dist/types/components/VrtxSubmit.d.ts +18 -0
  120. package/dist/types/components/VrtxSubmit.d.ts.map +1 -0
  121. package/dist/types/components/VrtxText.d.ts +8 -0
  122. package/dist/types/components/VrtxText.d.ts.map +1 -0
  123. package/dist/types/constants/mockData.d.ts +4 -0
  124. package/dist/types/constants/mockData.d.ts.map +1 -0
  125. package/dist/types/context/VortexModulesContext.d.ts +238 -0
  126. package/dist/types/context/VortexModulesContext.d.ts.map +1 -0
  127. package/dist/types/findFriends.js +10 -0
  128. package/dist/types/findFriends.js.map +1 -0
  129. package/dist/types/hooks/useInvitationFormLogic.d.ts +55 -0
  130. package/dist/types/hooks/useInvitationFormLogic.d.ts.map +1 -0
  131. package/dist/types/hooks/usePrefetchWidgetConfiguration.d.ts +39 -0
  132. package/dist/types/hooks/usePrefetchWidgetConfiguration.d.ts.map +1 -0
  133. package/dist/types/hooks/useThemeStyles.d.ts +1 -0
  134. package/dist/types/hooks/useThemeStyles.d.ts.map +1 -1
  135. package/dist/types/hooks/useVortexInvite.d.ts +48 -6
  136. package/dist/types/hooks/useVortexInvite.d.ts.map +1 -1
  137. package/dist/types/index-web.d.ts +23 -0
  138. package/dist/types/index-web.d.ts.map +1 -0
  139. package/dist/types/index.d.ts +21 -0
  140. package/dist/types/index.d.ts.map +1 -1
  141. package/dist/types/invitations.js +13 -0
  142. package/dist/types/invitations.js.map +1 -0
  143. package/dist/types/inviteContacts.js +14 -0
  144. package/dist/types/inviteContacts.js.map +1 -0
  145. package/dist/{shared/InvitationResult.js → types/platformOperations.js} +1 -0
  146. package/dist/types/platformOperations.js.map +1 -0
  147. package/dist/types/searchBox.js +11 -0
  148. package/dist/types/searchBox.js.map +1 -0
  149. package/dist/types/types/findFriends.d.ts +101 -0
  150. package/dist/types/types/findFriends.d.ts.map +1 -0
  151. package/dist/types/types/invitations.d.ts +301 -0
  152. package/dist/types/types/invitations.d.ts.map +1 -0
  153. package/dist/types/types/inviteContacts.d.ts +86 -0
  154. package/dist/types/types/inviteContacts.d.ts.map +1 -0
  155. package/dist/types/types/platformOperations.d.ts +185 -0
  156. package/dist/types/types/platformOperations.d.ts.map +1 -0
  157. package/dist/types/types/searchBox.d.ts +69 -0
  158. package/dist/types/types/searchBox.d.ts.map +1 -0
  159. package/dist/types/types/unfurlConfig.d.ts +34 -0
  160. package/dist/types/types/unfurlConfig.d.ts.map +1 -0
  161. package/dist/types/unfurlConfig.js +21 -0
  162. package/dist/types/unfurlConfig.js.map +1 -0
  163. package/dist/types/utils/analytics.d.ts +54 -0
  164. package/dist/types/utils/analytics.d.ts.map +1 -0
  165. package/dist/types/utils/configCache.d.ts +34 -0
  166. package/dist/types/utils/configCache.d.ts.map +1 -0
  167. package/dist/types/utils/contactUtils.d.ts +9 -0
  168. package/dist/types/utils/contactUtils.d.ts.map +1 -0
  169. package/dist/types/utils/featureWarnings.d.ts +56 -0
  170. package/dist/types/utils/featureWarnings.d.ts.map +1 -0
  171. package/dist/types/utils/formUtils.d.ts +11 -3
  172. package/dist/types/utils/formUtils.d.ts.map +1 -1
  173. package/dist/types/utils/gradientUtils.d.ts +67 -0
  174. package/dist/types/utils/gradientUtils.d.ts.map +1 -0
  175. package/dist/types/utils/invitationEvents.d.ts +21 -0
  176. package/dist/types/utils/invitationEvents.d.ts.map +1 -0
  177. package/dist/types/utils/moduleLoaders.d.ts +115 -0
  178. package/dist/types/utils/moduleLoaders.d.ts.map +1 -0
  179. package/dist/types/utils/moduleLoaders.web.d.ts +73 -0
  180. package/dist/types/utils/moduleLoaders.web.d.ts.map +1 -0
  181. package/dist/types/utils/nameUtils.d.ts +15 -0
  182. package/dist/types/utils/nameUtils.d.ts.map +1 -0
  183. package/dist/types/utils/themeUtils.d.ts +3 -1
  184. package/dist/types/utils/themeUtils.d.ts.map +1 -1
  185. package/dist/types/vortexInvite.d.ts +145 -5
  186. package/dist/types/vortexInvite.d.ts.map +1 -1
  187. package/dist/useInvitationFormLogic-Ct73M19B.d.mts +242 -0
  188. package/dist/useInvitationFormLogic-Ct73M19B.d.ts +242 -0
  189. package/dist/utils/analytics.js +92 -0
  190. package/dist/utils/analytics.js.map +1 -0
  191. package/dist/utils/configCache.js +68 -0
  192. package/dist/utils/configCache.js.map +1 -0
  193. package/dist/utils/contactUtils.d.mts +12 -0
  194. package/dist/utils/contactUtils.d.ts +12 -0
  195. package/dist/utils/contactUtils.js +37 -0
  196. package/dist/utils/contactUtils.js.map +1 -0
  197. package/dist/utils/contactUtils.mjs +12 -0
  198. package/dist/utils/contactUtils.mjs.map +1 -0
  199. package/dist/utils/featureWarnings.js +214 -0
  200. package/dist/utils/featureWarnings.js.map +1 -0
  201. package/dist/utils/formUtils.js +161 -51
  202. package/dist/utils/formUtils.js.map +1 -0
  203. package/dist/utils/gradientUtils.js +120 -0
  204. package/dist/utils/gradientUtils.js.map +1 -0
  205. package/dist/utils/invitationEvents.js +45 -0
  206. package/dist/utils/invitationEvents.js.map +1 -0
  207. package/dist/utils/moduleLoaders.js +275 -0
  208. package/dist/utils/moduleLoaders.js.map +1 -0
  209. package/dist/utils/moduleLoaders.web.js +72 -0
  210. package/dist/utils/moduleLoaders.web.js.map +1 -0
  211. package/dist/utils/nameUtils.js +51 -0
  212. package/dist/utils/nameUtils.js.map +1 -0
  213. package/dist/utils/themeUtils.js +117 -32
  214. package/dist/utils/themeUtils.js.map +1 -0
  215. package/dist/vortexInvite.js +78 -167
  216. package/dist/vortexInvite.js.map +1 -0
  217. package/package.json +69 -31
  218. package/dist/components/Clipboard.js +0 -64
  219. package/dist/shared/api.js +0 -90
  220. package/dist/tests/TestVortexInvite.js +0 -134
  221. package/dist/types/components/Clipboard.d.ts +0 -16
  222. package/dist/types/components/Clipboard.d.ts.map +0 -1
  223. package/dist/types/shared/InvitationResult.d.ts +0 -24
  224. package/dist/types/shared/InvitationResult.d.ts.map +0 -1
  225. package/dist/types/shared/api.d.ts +0 -14
  226. package/dist/types/shared/api.d.ts.map +0 -1
  227. package/dist/types/tests/TestVortexInvite.d.ts +0 -4
  228. package/dist/types/tests/TestVortexInvite.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,13 +1,1231 @@
1
- # @teamvortexsoftware/vortex-react-native
1
+ # Vortex React Native SDK
2
2
 
3
- React Native components for Vortex applications.
3
+ Configuration-driven invitation forms for React Native apps.
4
+
5
+ ## Overview
6
+
7
+ The Vortex React Native SDK provides a React Native component for rendering customizable invitation forms in your React Native applications. The SDK fetches configuration from the [Vortex](https://vortexsoftware.com) platform and dynamically renders the appropriate UI.
8
+
9
+ ## Requirements
10
+
11
+ - React 19+
12
+ - React Native 0.79.0+
4
13
 
5
14
  ## Installation
6
15
 
7
16
  ```bash
17
+ # Using pnpm (recommended)
8
18
  pnpm add @teamvortexsoftware/vortex-react-native
19
+
20
+ # Using npm
21
+ npm install @teamvortexsoftware/vortex-react-native
22
+
23
+ # Using yarn
24
+ yarn add @teamvortexsoftware/vortex-react-native
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### Basic Usage
30
+
31
+ ```tsx
32
+ import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';
33
+ import { useState, useEffect } from 'react';
34
+
35
+ function MyInviteScreen() {
36
+ const [jwt, setJwt] = useState<string | null>(null);
37
+
38
+ useEffect(() => {
39
+ // Generate JWT for authenticated user
40
+ const generateJwt = async () => {
41
+ try {
42
+ const response = await fetch('https://your-api.com/api/vortex/jwt', {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/json' },
45
+ });
46
+ const data = await response.json();
47
+ setJwt(data.jwt);
48
+ } catch (error) {
49
+ console.error('Failed to generate JWT:', error);
50
+ }
51
+ };
52
+
53
+ generateJwt();
54
+ }, []);
55
+
56
+ if (!jwt) {
57
+ return <Text>Loading...</Text>;
58
+ }
59
+
60
+ return (
61
+ <VortexInvite
62
+ componentId="my-component-id"
63
+ jwt={jwt}
64
+ scope="team-123"
65
+ scopeType="team"
66
+ onInvite={(data) => {
67
+ console.log('Invitation sent:', data);
68
+ }}
69
+ onError={(error) => {
70
+ console.error('Invitation error:', error);
71
+ }}
72
+ />
73
+ );
74
+ }
75
+ ```
76
+
77
+ ### With Group Context
78
+
79
+ ```tsx
80
+ import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';
81
+
82
+ function TeamInviteScreen({ teamId, teamName }: { teamId: string; teamName: string }) {
83
+ const { jwt, error } = useVortexAuth();
84
+
85
+ if (error) {
86
+ return <Text>Error: {error.message}</Text>;
87
+ }
88
+
89
+ return (
90
+ <VortexInvite
91
+ componentId={process.env.EXPO_PUBLIC_COMPONENT_ID}
92
+ jwt={jwt || ''}
93
+ scope={teamId}
94
+ scopeType="team"
95
+ onInvite={(result) => {
96
+ console.log('Invitations sent:', result);
97
+ }}
98
+ />
99
+ );
100
+ }
101
+ ```
102
+
103
+ ### Complete Example with Modal
104
+
105
+ ```tsx
106
+ import { useState, useEffect } from 'react';
107
+ import { Modal, View, Button, StyleSheet } from 'react-native';
108
+ import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';
109
+
110
+ function InviteModal({
111
+ visible,
112
+ onClose,
113
+ workspaceId,
114
+ }: {
115
+ visible: boolean;
116
+ onClose: () => void;
117
+ workspaceId: string;
118
+ }) {
119
+ const [jwt, setJwt] = useState<string>('');
120
+
121
+ useEffect(() => {
122
+ if (visible) {
123
+ // Generate JWT when modal opens
124
+ fetchJwt().then(setJwt);
125
+ }
126
+ }, [visible]);
127
+
128
+ return (
129
+ <Modal visible={visible} animationType="slide">
130
+ <View style={styles.container}>
131
+ <View style={styles.header}>
132
+ <Button title="Close" onPress={onClose} />
133
+ </View>
134
+
135
+ <VortexInvite
136
+ componentId={process.env.EXPO_PUBLIC_COMPONENT_ID}
137
+ jwt={jwt}
138
+ scope={workspaceId}
139
+ scopeType="workspace"
140
+ onInvite={(result) => {
141
+ console.log('Invitations sent successfully');
142
+ onClose();
143
+ }}
144
+ onError={(error) => {
145
+ console.error('Failed to send invitations:', error);
146
+ }}
147
+ />
148
+ </View>
149
+ </Modal>
150
+ );
151
+ }
152
+
153
+ const styles = StyleSheet.create({
154
+ container: {
155
+ flex: 1,
156
+ backgroundColor: '#fff',
157
+ },
158
+ header: {
159
+ padding: 16,
160
+ borderBottomWidth: 1,
161
+ borderBottomColor: '#e0e0e0',
162
+ },
163
+ });
164
+ ```
165
+
166
+ ### Invite Contacts
167
+
168
+ The Invite Contacts component displays a list of contacts that can be invited via SMS. Unlike the Contact Import feature (which fetches contacts from the device), Invite Contacts receives a pre-populated list of contacts from your app.
169
+
170
+ **Basic Usage:**
171
+
172
+ ```tsx
173
+ import { VortexInvite, InviteContactsConfig, InviteContactsContact } from '@teamvortexsoftware/vortex-react-native';
174
+
175
+ const contacts: InviteContactsContact[] = [
176
+ { name: "Alice Johnson", phoneNumber: "+1 (555) 123-4567" },
177
+ { name: "Bob Smith", phoneNumber: "+1 (555) 234-5678" },
178
+ { name: "Carol Davis", phoneNumber: "+1 (555) 345-6789" }
179
+ ];
180
+
181
+ <VortexInvite
182
+ componentId="your-component-id"
183
+ jwt={jwt}
184
+ inviteContactsConfig={{ contacts }}
185
+ onInvite={(result) => {
186
+ console.log('Invitations sent:', result);
187
+ }}
188
+ />
189
+ ```
190
+
191
+ **How It Works:**
192
+
193
+ 1. The component shows an "Invite your contacts" entry with a contact count
194
+ 2. Tapping it navigates to a searchable list of contacts
195
+ 3. Each contact has an "Invite" button that:
196
+ - Creates an SMS invitation via the Vortex API
197
+ - Opens the device's SMS app (on supported devices)
198
+ - Pre-fills the message with the invitation link
199
+
200
+ **InviteContactsContact Properties:**
201
+
202
+ ```typescript
203
+ // Simple usage - just name and phone number (ID is auto-generated)
204
+ {
205
+ name: string; // Display name
206
+ phoneNumber: string; // Phone number for SMS
207
+ avatarUrl?: string; // Optional avatar image URL
208
+ metadata?: Record<string, any>; // Optional custom metadata
209
+ }
210
+
211
+ // Full usage - with custom ID
212
+ {
213
+ id: string; // Custom unique identifier
214
+ name: string; // Display name
215
+ phoneNumber: string; // Phone number for SMS
216
+ avatarUrl?: string; // Optional avatar image URL
217
+ metadata?: Record<string, any>; // Optional custom metadata
218
+ }
219
+ ```
220
+
221
+ **InviteContactsConfig Properties:**
222
+
223
+ ```typescript
224
+ {
225
+ contacts: InviteContactsContact[]; // List of contacts to display
226
+ }
227
+ ```
228
+
229
+ ### Find Friends
230
+
231
+ The Find Friends component displays a list of contacts provided by your app. Each contact has a "Connect" button that creates an invitation with `targetType: internalId`.
232
+
233
+ **Basic Usage:**
234
+
235
+ ```tsx
236
+ import { VortexInvite, FindFriendsConfig, FindFriendsContact } from '@teamvortexsoftware/vortex-react-native';
237
+
238
+ <VortexInvite
239
+ componentId="your-component-id"
240
+ jwt={jwt}
241
+ findFriendsConfig={{
242
+ contacts: [
243
+ { internalId: "user-123", name: "Alice Johnson", subtitle: "@alice" },
244
+ { internalId: "user-456", name: "Bob Smith", subtitle: "@bob" }
245
+ ],
246
+ onInvitationCreated: (contact) => {
247
+ // Called after an invitation is successfully created
248
+ console.log('Invitation created for', contact.name);
249
+ }
250
+ }}
251
+ />
252
+ ```
253
+
254
+ **How It Works:**
255
+
256
+ 1. Your app provides a list of contacts with internal IDs (users already in your platform)
257
+ 2. The component displays them with a "Connect" button (text configurable via widget config)
258
+ 3. When the user taps "Connect", the SDK creates an invitation via the Vortex API with `targetType: internalId`
259
+ 4. The `onInvitationCreated` callback is called after a successful invitation
260
+ 5. The section is hidden when there are no contacts to display
261
+
262
+ **FindFriendsContact Properties:**
263
+
264
+ ```typescript
265
+ {
266
+ internalId: string; // Required: ID in your platform
267
+ name: string; // Required: Display name
268
+ subtitle?: string; // Optional: Secondary text (e.g., username)
269
+ avatarUrl?: string; // Optional: Avatar image URL
270
+ metadata?: Record<string, any>; // Optional: Custom metadata
271
+ }
272
+ ```
273
+
274
+ **FindFriendsConfig Properties:**
275
+
276
+ ```typescript
277
+ {
278
+ contacts: FindFriendsContact[]; // Required: List of contacts to display
279
+ onInvitationCreated?: (contact: FindFriendsContact) => void; // Optional: Called after successful invitation
280
+ }
281
+ ```
282
+
283
+ ### Invitation Suggestions
284
+
285
+ The Invitation Suggestions component displays a list of suggested contacts provided by your app. Each contact has an "Invite" button and a dismiss (X) button. When the user taps "Invite", an invitation with `targetType: internalId` is created. The dismiss button removes the suggestion from the list.
286
+
287
+ **Basic Usage:**
288
+
289
+ ```tsx
290
+ import { VortexInvite, InvitationSuggestionsConfig, InvitationSuggestionContact } from '@teamvortexsoftware/vortex-react-native';
291
+
292
+ <VortexInvite
293
+ componentId="your-component-id"
294
+ jwt={jwt}
295
+ invitationSuggestionsConfig={{
296
+ contacts: [
297
+ {
298
+ internalId: "user-123",
299
+ name: "Alice Johnson",
300
+ subtitle: "@alice",
301
+ avatarUrl: "https://example.com/alice-avatar.jpg"
302
+ },
303
+ {
304
+ internalId: "user-456",
305
+ name: "Bob Smith",
306
+ subtitle: "@bob"
307
+ }
308
+ ],
309
+ onDismiss: (contact) => {
310
+ // Called when user taps the X button
311
+ console.log('Dismissed suggestion for', contact.name);
312
+ },
313
+ onInvitationCreated: (contact) => {
314
+ console.log('Invitation created for', contact.name);
315
+ },
316
+ onInvitationFailed: (contact, error) => {
317
+ console.error('Failed to invite', contact.name, error);
318
+ }
319
+ }}
320
+ />
321
+ ```
322
+
323
+ **How It Works:**
324
+
325
+ 1. Your app provides a list of suggested contacts with internal IDs
326
+ 2. The component displays them with an "Invite" button and a dismiss (X) button
327
+ 3. When the user taps "Invite", the SDK creates an invitation via the Vortex API with `targetType: internalId`
328
+ 4. The `onInvitationCreated` or `onInvitationFailed` callback is called based on the result
329
+ 5. When the user taps the X button, the `onDismiss` callback is called and the contact is removed from the list
330
+
331
+ **InvitationSuggestionContact Properties:**
332
+
333
+ ```typescript
334
+ {
335
+ internalId: string; // Required: ID in your platform
336
+ name: string; // Required: Display name
337
+ subtitle?: string; // Optional: Secondary text (e.g., username)
338
+ avatarUrl?: string; // Optional: Avatar image URL (rendered instead of initials if provided)
339
+ metadata?: Record<string, any>; // Optional: Custom metadata
340
+ }
341
+ ```
342
+
343
+ **InvitationSuggestionsConfig Properties:**
344
+
345
+ ```typescript
346
+ {
347
+ contacts: InvitationSuggestionContact[]; // Required: List of contacts to display
348
+ onDismiss: (contact: InvitationSuggestionContact) => void; // Required: Called when user dismisses a suggestion
349
+ onInvitationCreated?: (contact: InvitationSuggestionContact) => void; // Called after successful invitation
350
+ onInvitationFailed?: (contact: InvitationSuggestionContact, error: Error) => void; // Called on failure
351
+ }
352
+ ```
353
+
354
+ ### Search Box
355
+
356
+ The Search Box component displays a search input with a search button. When the user taps the search button, your app's `onSearch` callback is invoked to return matching contacts. The results are rendered below the search box with "Connect" buttons, identical to the Find Friends component.
357
+
358
+ **Basic Usage:**
359
+
360
+ ```tsx
361
+ import { VortexInvite, SearchBoxConfig, SearchBoxContact } from '@teamvortexsoftware/vortex-react-native';
362
+
363
+ <VortexInvite
364
+ componentId="your-component-id"
365
+ jwt={jwt}
366
+ searchBoxConfig={{
367
+ onSearch: async (query) => {
368
+ // Return matching contacts from your backend or local data
369
+ const results = await myAPI.searchUsers(query);
370
+ return results.map(user => ({
371
+ internalId: user.id,
372
+ name: user.displayName,
373
+ subtitle: `@${user.username}`,
374
+ avatarUrl: user.avatarUrl
375
+ }));
376
+ },
377
+ onInvitationCreated: (contact) => {
378
+ console.log('Invitation created for', contact.name);
379
+ }
380
+ }}
381
+ />
382
+ ```
383
+
384
+ **How It Works:**
385
+
386
+ 1. The component renders a search text field with a configurable placeholder and a search button
387
+ 2. An optional title can be displayed above the search box (configured in the widget editor)
388
+ 3. When the user taps the search button, your `onSearch` callback is called with the query string
389
+ 4. Your callback returns a list of `SearchBoxContact` objects
390
+ 5. The matching contacts are rendered below the search box with a "Connect" button next to each
391
+ 6. If the search returns no results, a configurable "no results" message is displayed
392
+ 7. When the user taps "Connect", the SDK creates an invitation via the Vortex API with `targetType: internalId` and the contact is removed from the list (identical to Find Friends behavior)
393
+
394
+ **SearchBoxConfig Properties:**
395
+
396
+ ```typescript
397
+ {
398
+ onSearch: (query: string) => Promise<SearchBoxContact[]>; // Required: Search callback
399
+ onInvitationCreated?: (contact: SearchBoxContact) => void; // Optional: Called after successful invitation
400
+ }
401
+ ```
402
+
403
+ > **Note:** The placeholder text, connect button text, no-results message, and title are all configurable from the widget editor.
404
+
405
+ ### Incoming Invitations
406
+
407
+ The Incoming Invitations component displays invitations the user has received, with Accept and Delete actions.
408
+
409
+ **Basic Usage:**
410
+
411
+ ```tsx
412
+ import { VortexInvite, IncomingInvitationsConfig } from '@teamvortexsoftware/vortex-react-native';
413
+
414
+ <VortexInvite
415
+ componentId="your-component-id"
416
+ jwt={jwt}
417
+ incomingInvitationsConfig={{
418
+ onAccept: async (invitation) => {
419
+ // Handle acceptance (return true to proceed with API call)
420
+ await myAPI.acceptInvitation(invitation.id);
421
+ return true;
422
+ },
423
+ onDelete: async (invitation) => {
424
+ // Handle deletion (return true to proceed with API call)
425
+ return true;
426
+ }
427
+ }}
428
+ />
429
+ ```
430
+
431
+ **With Internal Invitations:**
432
+
433
+ You can merge your app's invitations with Vortex API invitations:
434
+
435
+ ```tsx
436
+ <VortexInvite
437
+ componentId="your-component-id"
438
+ jwt={jwt}
439
+ incomingInvitationsConfig={{
440
+ internalInvitations: [
441
+ {
442
+ id: "internal-1",
443
+ name: "Alice Johnson",
444
+ subtitle: "alice@example.com",
445
+ avatarUrl: "https://example.com/avatar.jpg"
446
+ }
447
+ ],
448
+ onAccept: async (invitation) => {
449
+ if (invitation.isVortexInvitation) {
450
+ // Vortex invitation: return true to let SDK call the Vortex API
451
+ return true;
452
+ } else {
453
+ // Internal/app invitation: handle it yourself
454
+ await myAPI.acceptInvitation(invitation.id);
455
+ return true; // Return true to remove from list
456
+ }
457
+ },
458
+ onDelete: async (invitation) => {
459
+ if (invitation.isVortexInvitation) {
460
+ return true; // Let SDK handle the API call
461
+ } else {
462
+ await myAPI.deleteInvitation(invitation.id);
463
+ return true; // Return true to remove from list
464
+ }
465
+ }
466
+ }}
467
+ />
468
+ ```
469
+
470
+ **Identifying Invitation Source:**
471
+
472
+ Use the `isVortexInvitation` property to determine where an invitation came from:
473
+ - `true`: Fetched from the Vortex API — the SDK will handle accept/delete API calls
474
+ - `false`: Provided by your app via `internalInvitations` — your app must handle the action
475
+
476
+ **IncomingInvitationsConfig Properties:**
477
+
478
+ ```typescript
479
+ {
480
+ internalInvitations?: IncomingInvitationItem[]; // App-provided invitations (isVortexInvitation = false)
481
+ onAccept?: (invitation: IncomingInvitationItem) => Promise<boolean>; // Called when user accepts
482
+ onDelete?: (invitation: IncomingInvitationItem) => Promise<boolean>; // Called when user deletes
483
+ }
484
+ ```
485
+
486
+ **Callback Return Values:**
487
+
488
+ | Invitation Source | Return `true` | Return `false` |
489
+ |-------------------|---------------|----------------|
490
+ | Vortex (`isVortexInvitation === true`) | SDK calls Vortex API, removes from list | Cancels the action |
491
+ | Internal (`isVortexInvitation === false`) | Removes from list (no API call) | Keeps in list |
492
+
493
+ ### Outgoing Invitations
494
+
495
+ The Outgoing Invitations component displays invitations the user has sent, with a Cancel action.
496
+
497
+ **Basic Usage:**
498
+
499
+ ```tsx
500
+ import { VortexInvite, OutgoingInvitationsConfig } from '@teamvortexsoftware/vortex-react-native';
501
+
502
+ <VortexInvite
503
+ componentId="your-component-id"
504
+ jwt={jwt}
505
+ outgoingInvitationsConfig={{
506
+ onCancel: async (invitation) => {
507
+ // Handle cancellation (return true to proceed with API call)
508
+ return true;
509
+ }
510
+ }}
511
+ />
512
+ ```
513
+
514
+ **With Internal Invitations:**
515
+
516
+ You can merge your app's outgoing invitations with Vortex API invitations:
517
+
518
+ ```tsx
519
+ <VortexInvite
520
+ componentId="your-component-id"
521
+ jwt={jwt}
522
+ outgoingInvitationsConfig={{
523
+ internalInvitations: [
524
+ {
525
+ id: "internal-1",
526
+ name: "Bob Smith",
527
+ subtitle: "user-12345", // e.g., user ID or other identifier
528
+ avatarUrl: "https://example.com/avatar.jpg"
529
+ }
530
+ ],
531
+ onCancel: async (invitation) => {
532
+ if (invitation.isVortexInvitation) {
533
+ // Vortex invitation: return true to let SDK call the Vortex API
534
+ return true;
535
+ } else {
536
+ // Internal/app invitation: handle it yourself
537
+ await myAPI.cancelInvitation(invitation.id);
538
+ return true; // Return true to remove from list
539
+ }
540
+ }
541
+ }}
542
+ />
543
+ ```
544
+
545
+ **Identifying Invitation Source:**
546
+
547
+ Use the `isVortexInvitation` property to determine where an invitation came from:
548
+ - `true`: Fetched from the Vortex API — the SDK will handle cancel API calls
549
+ - `false`: Provided by your app via `internalInvitations` — your app must handle the action
550
+
551
+ **OutgoingInvitationsConfig Properties:**
552
+
553
+ ```typescript
554
+ {
555
+ internalInvitations?: OutgoingInvitationItem[]; // App-provided invitations (isVortexInvitation = false)
556
+ onCancel?: (invitation: OutgoingInvitationItem) => Promise<boolean>; // Called when user cancels
557
+ }
558
+ ```
559
+
560
+ **Callback Return Values:**
561
+
562
+ | Invitation Source | Return `true` | Return `false` |
563
+ |-------------------|---------------|----------------|
564
+ | Vortex (`isVortexInvitation === true`) | SDK calls Vortex API, removes from list | Cancels the action |
565
+ | Internal (`isVortexInvitation === false`) | Removes from list (no API call) | Keeps in list |
566
+
567
+ ## Prefetch for Instant Rendering
568
+
569
+ The SDK supports prefetching widget configurations to eliminate loading delays when opening the invite form. This uses a **stale-while-revalidate** pattern: cached configurations are shown immediately while fresh data is fetched in the background.
570
+
571
+ ### Basic Prefetch Example
572
+
573
+ ```tsx
574
+ import { VortexInvite, usePrefetchWidgetConfiguration } from '@teamvortexsoftware/vortex-react-native';
575
+ import { useState, useEffect } from 'react';
576
+
577
+ function InviteModal({ onClose, groupId }) {
578
+ const [jwt, setJwt] = useState<string | undefined>();
579
+
580
+ // Fetch JWT on mount
581
+ useEffect(() => {
582
+ fetchJwt().then(setJwt);
583
+ }, []);
584
+
585
+ // Prefetch widget configuration as soon as JWT is available
586
+ const { widgetConfiguration } = usePrefetchWidgetConfiguration({
587
+ componentId: process.env.EXPO_PUBLIC_COMPONENT_ID,
588
+ vortexApiUrl: 'https://client-api.vortexsoftware.com',
589
+ jwt,
590
+ enabled: !!jwt, // Only prefetch when JWT is ready
591
+ });
592
+
593
+ return (
594
+ <VortexInvite
595
+ componentId={process.env.EXPO_PUBLIC_COMPONENT_ID}
596
+ jwt={jwt}
597
+ widgetConfiguration={widgetConfiguration} // Pass prefetched config
598
+ scope={groupId}
599
+ scopeType="team"
600
+ onClose={onClose}
601
+ />
602
+ );
603
+ }
604
+ ```
605
+
606
+ ### How It Works
607
+
608
+ 1. **Without Prefetching** (default behavior):
609
+ - Component mounts → Shows loading spinner
610
+ - Fetches configuration → Renders form
611
+
612
+ 2. **With Prefetching** (optimized):
613
+ - Parent prefetches configuration early (when JWT becomes available)
614
+ - Component mounts → **Renders form immediately** (no loading spinner!)
615
+ - Fetches latest configuration in background
616
+ - Updates form if configuration changed
617
+
618
+ ### Benefits
619
+
620
+ ✅ **Instant rendering** - No loading spinner when modal/screen is shown
621
+ ✅ **Always fresh** - Component still fetches latest configuration in background
622
+ ✅ **Automatic updates** - Form updates if configuration changes on server
623
+ ✅ **Backward compatible** - Works perfectly without prefetching (shows loading spinner as before)
624
+
625
+ ## Optional Features
626
+
627
+ The SDK supports optional native features that enhance the user experience. These features require additional libraries that you can install based on your needs.
628
+
629
+ ### Why Optional?
630
+
631
+ These features use native code that must be explicitly installed in your app. The SDK gracefully degrades when these libraries aren't installed:
632
+
633
+ - **Gradients** → Falls back to solid colors
634
+ - **Haptics** → Silently skipped
635
+ - **QR Codes** → Shows placeholder message
636
+
637
+ ### Configuration
638
+
639
+ Specify which libraries you've installed via the unified `modules` prop on `VortexInvite`:
640
+
641
+ ```tsx
642
+ <VortexInvite
643
+ componentId="your-component-id"
644
+ jwt={jwt}
645
+ // Optional feature modules (unified configuration)
646
+ modules={{
647
+ gradient: 'expo-linear-gradient',
648
+ haptics: 'expo-haptics',
649
+ qrCode: 'react-native-qrcode-svg',
650
+ }}
651
+ />
652
+ ```
653
+
654
+ ### Available Features
655
+
656
+ | Feature | Property | Options | Description |
657
+ |---------|----------|---------|-------------|
658
+ | Gradients | `modules.gradient` | `'expo-linear-gradient'` or `'react-native-linear-gradient'` | Gradient backgrounds on buttons |
659
+ | Haptics | `modules.haptics` | `'expo-haptics'` | Haptic feedback on button presses |
660
+ | QR Codes | `modules.qrCode` | `'react-native-qrcode-svg'` or `'react-qr-code'` | QR code display for shareable links |
661
+ | Clipboard | `modules.clipboard` | `'expo-clipboard'` or `'@react-native-clipboard/clipboard'` | Enhanced clipboard operations |
662
+ | Sharing | `modules.sharing` | `'expo-sharing'` | Native share sheet |
663
+
664
+ ### Installation by Project Type
665
+
666
+ #### Expo Projects (Recommended)
667
+
668
+ ```bash
669
+ # Gradients
670
+ npx expo install expo-linear-gradient
671
+
672
+ # Haptics
673
+ npx expo install expo-haptics
674
+
675
+ # QR Codes (requires both packages)
676
+ npx expo install react-native-qrcode-svg react-native-svg
677
+
678
+ # Clipboard
679
+ npx expo install expo-clipboard
680
+
681
+ # Sharing
682
+ npx expo install expo-sharing
683
+ ```
684
+
685
+ Then configure:
686
+
687
+ ```tsx
688
+ <VortexInvite
689
+ componentId="your-component-id"
690
+ jwt={jwt}
691
+ modules={{
692
+ gradient: 'expo-linear-gradient',
693
+ haptics: 'expo-haptics',
694
+ qrCode: 'react-native-qrcode-svg',
695
+ clipboard: 'expo-clipboard',
696
+ sharing: 'expo-sharing',
697
+ }}
698
+ />
699
+ ```
700
+
701
+ #### Bare React Native Projects
702
+
703
+ ```bash
704
+ # Gradients
705
+ npm install react-native-linear-gradient
706
+ cd ios && pod install
707
+
708
+ # QR Codes (requires both packages)
709
+ npm install react-native-qrcode-svg react-native-svg
710
+ cd ios && pod install
711
+
712
+ # Clipboard
713
+ npm install @react-native-clipboard/clipboard
714
+ cd ios && pod install
715
+ ```
716
+
717
+ Then configure:
718
+
719
+ ```tsx
720
+ <VortexInvite
721
+ componentId="your-component-id"
722
+ jwt={jwt}
723
+ modules={{
724
+ gradient: 'react-native-linear-gradient',
725
+ qrCode: 'react-native-qrcode-svg',
726
+ clipboard: '@react-native-clipboard/clipboard',
727
+ }}
728
+ />
729
+ ```
730
+
731
+ ### Helpful Warnings
732
+
733
+ In development mode, the SDK will print helpful warnings when:
734
+ - A feature is detected in your widget configuration but no module is specified
735
+ - A module is specified but not installed
736
+
737
+ Example warning:
738
+ ```
739
+ [Vortex] ⚠️ Gradient backgrounds detected in widget configuration, but no gradient module specified.
740
+
741
+ To enable gradients on native platforms, add the modules prop to <VortexInvite>:
742
+
743
+ For Expo projects:
744
+ <VortexInvite modules={{ gradient: 'expo-linear-gradient' }} ... />
745
+ Then run: npx expo install expo-linear-gradient
746
+ ```
747
+
748
+ ---
749
+
750
+ ## Deferred Deep Linking
751
+
752
+ Deferred deep linking allows your app to retrieve invitation context even when a user installs the app after clicking an invitation link. When a user clicks an invitation link but doesn't have the app installed, they're redirected to the App Store or Play Store. After installation, the SDK can match the device fingerprint to retrieve the original invitation context.
753
+
754
+ ### Basic Usage
755
+
756
+ Call `VortexDeferredLinks.retrieveDeferredDeepLink` when the user signs in or when the app session is restored:
757
+
758
+ ```tsx
759
+ import { VortexDeferredLinks } from '@teamvortexsoftware/vortex-react-native';
760
+
761
+ // In your authentication manager or app initialization
762
+ async function onUserSignedIn(vortexJwt: string) {
763
+ try {
764
+ const result = await VortexDeferredLinks.retrieveDeferredDeepLink({ jwt: vortexJwt });
765
+
766
+ if (result.matched && result.deepLink) {
767
+ console.log('Found pending invitation!');
768
+ console.log('Invitation ID:', result.invitationId);
769
+ console.log('Deep Link:', result.deepLink);
770
+ console.log('Metadata:', result.metadata);
771
+ // Handle the invitation (e.g., show UI, auto-join group, navigate to specific screen)
772
+ }
773
+ } catch (error) {
774
+ console.error('Deferred link check failed:', error);
775
+ }
776
+ }
777
+ ```
778
+
779
+ ### Response Types
780
+
781
+ **MatchFingerprintResponse:**
782
+ ```typescript
783
+ interface MatchFingerprintResponse {
784
+ matched: boolean; // Whether a matching invitation was found
785
+ invitationId?: string; // The original invitation ID
786
+ deepLink?: string; // Deep link URL to handle
787
+ metadata?: Record<string, any>; // Additional metadata
788
+ }
789
+ ```
790
+
791
+ ### Best Practices
792
+
793
+ 1. **Call on authentication**: Check for deferred deep links immediately after user sign-in or session restore
794
+ 2. **Use Vortex JWT**: The endpoint requires a Vortex JWT token (not your app's auth token)
795
+ 3. **Handle once**: Clear or mark the invitation as handled after processing to avoid showing it repeatedly
796
+ 4. **Graceful degradation**: The check may fail (network issues, no match found) - handle errors gracefully
797
+
798
+ ### Example Integration with Navigation
799
+
800
+ ```tsx
801
+ import { useEffect } from 'react';
802
+ import { VortexDeferredLinks } from '@teamvortexsoftware/vortex-react-native';
803
+
804
+ function App() {
805
+ const [jwt, setJwt] = useState<string | null>(null);
806
+ const navigation = useNavigation();
807
+
808
+ useEffect(() => {
809
+ if (jwt) {
810
+ checkForPendingInvitations();
811
+ }
812
+ }, [jwt]);
813
+
814
+ async function checkForPendingInvitations() {
815
+ try {
816
+ const result = await VortexDeferredLinks.retrieveDeferredDeepLink({ jwt });
817
+
818
+ if (result.matched && result.deepLink) {
819
+ // Parse deep link and navigate
820
+ const params = parseDeepLink(result.deepLink);
821
+ navigation.navigate('TeamScreen', { teamId: params.teamId });
822
+ }
823
+ } catch (error) {
824
+ // Log error but don't block the user
825
+ console.error('Deferred deep link check failed:', error);
826
+ }
827
+ }
828
+
829
+ return <YourApp />;
830
+ }
831
+ ```
832
+
833
+ ## Unfurl Configuration
834
+
835
+ When invitation links are shared on social platforms (iMessage, WhatsApp, Facebook, Twitter, etc.), the `UnfurlConfig` allows you to customize the Open Graph metadata that appears in the link preview card.
836
+
837
+ ### Usage
838
+
839
+ ```tsx
840
+ <VortexInvite
841
+ componentId="your-component-id"
842
+ jwt={jwt}
843
+ scope={teamId}
844
+ scopeType="team"
845
+ unfurlConfig={{
846
+ title: "Join our team!",
847
+ description: "You've been invited to collaborate on an exciting project",
848
+ image: "https://example.com/preview-image.png",
849
+ siteName: "My App",
850
+ type: "website"
851
+ }}
852
+ onInvite={(result) => {
853
+ console.log('Invitations sent:', result);
854
+ }}
855
+ />
856
+ ```
857
+
858
+ ### UnfurlConfig Properties
859
+
860
+ | Property | Type | Description |
861
+ |----------|------|-------------|
862
+ | `title` | `string?` | The title shown in the link preview (`og:title`) |
863
+ | `description` | `string?` | The description shown in the link preview (`og:description`) |
864
+ | `image` | `string?` | URL to the image shown in the link preview (`og:image`). Must be a valid URL. |
865
+ | `siteName` | `string?` | The site name shown in the link preview (`og:site_name`) |
866
+ | `type` | `string?` | The Open Graph type (`og:type`). Defaults to "website" if not provided. |
867
+
868
+ ### Priority Chain
869
+
870
+ The backend uses this priority for unfurl values:
871
+ 1. **Invitation metadata** (values from `UnfurlConfig`) - highest priority
872
+ 2. **Domain profile unfurlData** - fallback from account settings in Vortex dashboard
873
+ 3. **Hardcoded defaults** - last resort
874
+
875
+ ## Features
876
+
877
+ **Invitation Methods:**
878
+ - Email invitations with validation
879
+ - Copy shareable link to clipboard
880
+ - Native share sheet integration
881
+ - SMS sharing
882
+ - QR code generation
883
+ - WhatsApp, LINE, and other social messaging integrations
884
+
885
+ **Contact Import:**
886
+ - Device Contacts integration (iOS/Android)
887
+ - Google Contacts integration (via Google Sign-In)
888
+
889
+ **Advanced Components:**
890
+ - **Invite Contacts** - Display a list of contacts for SMS invitation
891
+ - **Find Friends** - Display contacts with "Connect" buttons for internal invitations
892
+ - **Invitation Suggestions** - Show suggested contacts with invite/dismiss actions
893
+ - **Search Box** - Search and invite users from your platform
894
+ - **Incoming Invitations** - Display and manage received invitations
895
+ - **Outgoing Invitations** - Display and manage sent invitations
896
+
897
+ **Core Capabilities:**
898
+ - Dynamic form rendering from server configuration
899
+ - JWT authentication
900
+ - Group/team/workspace context support
901
+ - Real-time loading states and error handling
902
+ - Customizable UI based on widget configuration
903
+ - Deferred deep linking via fingerprint matching
904
+ - Prefetching for instant rendering
905
+ - Open Graph unfurl metadata customization
906
+
907
+ **Optional Native Features:**
908
+ - Linear gradients (Expo or React Native)
909
+ - Haptic feedback (Expo)
910
+ - QR code generation (react-native-qrcode-svg or react-qr-code)
911
+ - Enhanced clipboard (Expo or React Native Community)
912
+ - Native share sheet (Expo)
913
+
914
+ ---
915
+
916
+ ## API Reference
917
+
918
+ ### VortexInvite
919
+
920
+ The main React Native component for rendering invitation forms.
921
+
922
+ **Props:**
923
+
924
+ | Prop | Type | Required | Description |
925
+ |------|------|----------|-------------|
926
+ | `componentId` | `string` | ✅ | Unique identifier for the Vortex component |
927
+ | `jwt` | `string` | ✅ | JWT token for authenticated user |
928
+ | `scope` | `string` | ✅ | Scope identifier (e.g., team ID, workspace ID) |
929
+ | `scopeType` | `string` | ✅ | Type of scope (e.g., "team", "workspace", "project") |
930
+ | `widgetConfiguration` | `WidgetConfiguration` | ❌ | Pre-fetched widget configuration |
931
+ | `onInvite` | `(result: any) => void` | ❌ | Callback when invitations are successfully sent |
932
+ | `onError` | `(error: Error) => void` | ❌ | Callback when an error occurs |
933
+ | `onClose` | `() => void` | ❌ | Callback when the component is closed |
934
+ | `templateVariables` | `Record<string, string>` | ❌ | Variables for invitation templates |
935
+ | `analyticsSegmentation` | `Record<string, any>` | ❌ | Analytics tracking data |
936
+ | `modules` | `VortexModules` | ❌ | Unified configuration for optional native libraries (see below) |
937
+ | `inviteContactsConfig` | `InviteContactsConfig` | ❌ | Configuration for Invite Contacts feature |
938
+ | `findFriendsConfig` | `FindFriendsConfig` | ❌ | Configuration for Find Friends feature |
939
+ | `invitationSuggestionsConfig` | `InvitationSuggestionsConfig` | ❌ | Configuration for Invitation Suggestions feature |
940
+ | `searchBoxConfig` | `SearchBoxConfig` | ❌ | Configuration for Search Box feature |
941
+ | `incomingInvitationsConfig` | `IncomingInvitationsConfig` | ❌ | Configuration for Incoming Invitations feature |
942
+ | `outgoingInvitationsConfig` | `OutgoingInvitationsConfig` | ❌ | Configuration for Outgoing Invitations feature |
943
+ | `unfurlConfig` | `UnfurlConfig` | ❌ | Configuration for Open Graph unfurl metadata |
944
+
945
+ **`modules` Object Properties:**
946
+
947
+ | Property | Type | Description |
948
+ |----------|------|-------------|
949
+ | `gradient` | `'expo-linear-gradient'` \| `'react-native-linear-gradient'` | Gradient library for button backgrounds |
950
+ | `haptics` | `'expo-haptics'` | Haptics library for touch feedback |
951
+ | `qrCode` | `'react-native-qrcode-svg'` \| `'react-qr-code'` | QR code library |
952
+ | `clipboard` | `'expo-clipboard'` \| `'@react-native-clipboard/clipboard'` | Clipboard library |
953
+ | `sharing` | `'expo-sharing'` | Sharing library |
954
+
955
+ ### usePrefetchWidgetConfiguration
956
+
957
+ Hook for prefetching widget configuration.
958
+
959
+ **Options:**
960
+
961
+ | Option | Type | Required | Description |
962
+ |--------|------|----------|-------------|
963
+ | `componentId` | `string` | ✅ | Your widget component ID |
964
+ | `vortexApiUrl` | `string` | ✅ | Vortex API URL |
965
+ | `jwt` | `string` | ❌ | JWT token (or use `user`) |
966
+ | `user` | `string \| object` | ❌ | User data for unsigned JWT |
967
+ | `enabled` | `boolean` | ❌ | Conditionally enable/disable prefetching (default: true) |
968
+
969
+ **Return Value:**
970
+
971
+ | Property | Type | Description |
972
+ |----------|------|-------------|
973
+ | `widgetConfiguration` | `WidgetConfiguration \| undefined` | The prefetched configuration |
974
+ | `isLoading` | `boolean` | True while fetching |
975
+ | `error` | `Error \| null` | Error if fetch failed |
976
+
977
+ ### VortexDeferredLinks
978
+
979
+ Class for handling deferred deep linking - track invitation attribution even when the app wasn't installed at the time the invitation link was clicked.
980
+
981
+ **Methods:**
982
+
983
+ | Method | Parameters | Returns | Description |
984
+ |--------|------------|---------|-------------|
985
+ | `collectDeviceFingerprint()` | none | `DeviceFingerprint` | Collects device fingerprint data for matching |
986
+ | `retrieveDeferredDeepLink(options)` | `RetrieveDeferredDeepLinkOptions` | `Promise<MatchFingerprintResponse>` | Retrieves deferred deep link by matching fingerprint |
987
+
988
+ **Example:**
989
+
990
+ ```tsx
991
+ import { VortexDeferredLinks } from '@teamvortexsoftware/vortex-react-native';
992
+
993
+ // On app first launch after install
994
+ async function checkDeferredDeepLink(jwt: string) {
995
+ const result = await VortexDeferredLinks.retrieveDeferredDeepLink({ jwt });
996
+ if (result.matched && result.deepLink) {
997
+ console.log('User came from invitation:', result.invitationId);
998
+ // Navigate to appropriate screen based on deepLink
999
+ }
1000
+ }
1001
+ ```
1002
+
1003
+ **Types:**
1004
+
1005
+ ```typescript
1006
+ interface DeviceFingerprint {
1007
+ platform: 'ios' | 'android';
1008
+ osVersion: string;
1009
+ deviceModel: string;
1010
+ deviceBrand: string;
1011
+ timezone: string;
1012
+ language: string;
1013
+ screenWidth: number;
1014
+ screenHeight: number;
1015
+ carrierName?: string;
1016
+ totalMemory?: number;
1017
+ }
1018
+
1019
+ interface MatchFingerprintResponse {
1020
+ matched: boolean;
1021
+ invitationId?: string;
1022
+ deepLink?: string;
1023
+ metadata?: Record<string, any>;
1024
+ }
1025
+
1026
+ interface RetrieveDeferredDeepLinkOptions {
1027
+ jwt: string;
1028
+ baseURL?: string; // Defaults to production
1029
+ }
9
1030
  ```
10
1031
 
1032
+ ### Type Definitions
1033
+
1034
+ **InviteContactsConfig:**
1035
+ ```typescript
1036
+ interface InviteContactsConfig {
1037
+ contacts: InviteContactsContact[];
1038
+ }
1039
+
1040
+ interface InviteContactsContact {
1041
+ id?: string;
1042
+ name: string;
1043
+ phoneNumber: string;
1044
+ avatarUrl?: string;
1045
+ metadata?: Record<string, any>;
1046
+ }
1047
+ ```
1048
+
1049
+ **FindFriendsConfig:**
1050
+ ```typescript
1051
+ interface FindFriendsConfig {
1052
+ contacts: FindFriendsContact[];
1053
+ onInvitationCreated?: (contact: FindFriendsContact) => void;
1054
+ }
1055
+
1056
+ interface FindFriendsContact {
1057
+ internalId: string;
1058
+ name: string;
1059
+ subtitle?: string;
1060
+ avatarUrl?: string;
1061
+ metadata?: Record<string, any>;
1062
+ }
1063
+ ```
1064
+
1065
+ **InvitationSuggestionsConfig:**
1066
+ ```typescript
1067
+ interface InvitationSuggestionsConfig {
1068
+ contacts: InvitationSuggestionContact[];
1069
+ onDismiss: (contact: InvitationSuggestionContact) => void;
1070
+ onInvitationCreated?: (contact: InvitationSuggestionContact) => void;
1071
+ onInvitationFailed?: (contact: InvitationSuggestionContact, error: Error) => void;
1072
+ }
1073
+
1074
+ interface InvitationSuggestionContact {
1075
+ internalId: string;
1076
+ name: string;
1077
+ subtitle?: string;
1078
+ avatarUrl?: string;
1079
+ metadata?: Record<string, any>;
1080
+ }
1081
+ ```
1082
+
1083
+ **SearchBoxConfig:**
1084
+ ```typescript
1085
+ interface SearchBoxConfig {
1086
+ onSearch: (query: string) => Promise<SearchBoxContact[]>;
1087
+ onInvitationCreated?: (contact: SearchBoxContact) => void;
1088
+ }
1089
+
1090
+ interface SearchBoxContact {
1091
+ internalId: string;
1092
+ name: string;
1093
+ subtitle?: string;
1094
+ avatarUrl?: string;
1095
+ metadata?: Record<string, any>;
1096
+ }
1097
+ ```
1098
+
1099
+ **IncomingInvitationsConfig:**
1100
+ ```typescript
1101
+ interface IncomingInvitationsConfig {
1102
+ internalInvitations?: IncomingInvitationItem[];
1103
+ onAccept?: (invitation: IncomingInvitationItem) => Promise<boolean>;
1104
+ onDelete?: (invitation: IncomingInvitationItem) => Promise<boolean>;
1105
+ }
1106
+
1107
+ interface IncomingInvitationItem {
1108
+ id: string;
1109
+ name: string;
1110
+ subtitle?: string;
1111
+ avatarUrl?: string;
1112
+ isVortexInvitation: boolean;
1113
+ metadata?: Record<string, any>;
1114
+ }
1115
+ ```
1116
+
1117
+ **OutgoingInvitationsConfig:**
1118
+ ```typescript
1119
+ interface OutgoingInvitationsConfig {
1120
+ internalInvitations?: OutgoingInvitationItem[];
1121
+ onCancel?: (invitation: OutgoingInvitationItem) => Promise<boolean>;
1122
+ }
1123
+
1124
+ interface OutgoingInvitationItem {
1125
+ id: string;
1126
+ name: string;
1127
+ subtitle?: string;
1128
+ avatarUrl?: string;
1129
+ isVortexInvitation: boolean;
1130
+ metadata?: Record<string, any>;
1131
+ }
1132
+ ```
1133
+
1134
+ **UnfurlConfig:**
1135
+ ```typescript
1136
+ interface UnfurlConfig {
1137
+ title?: string;
1138
+ description?: string;
1139
+ image?: string;
1140
+ siteName?: string;
1141
+ type?: string;
1142
+ }
1143
+ ```
1144
+
1145
+ ## Authentication
1146
+
1147
+ Your backend should generate JWTs for authenticated users:
1148
+
1149
+ ### Node.js Example
1150
+
1151
+ ```typescript
1152
+ import { generateJwt } from '@teamvortexsoftware/vortex-node-22-sdk';
1153
+
1154
+ const jwt = generateJwt({
1155
+ user: {
1156
+ id: user.id,
1157
+ email: user.email,
1158
+ adminScopes: user.isAdmin ? ['autojoin'] : [],
1159
+ },
1160
+ apiKey: process.env.VORTEX_API_KEY,
1161
+ });
1162
+ ```
1163
+
1164
+ ### Express/Fastify Example
1165
+
1166
+ ```typescript
1167
+ app.post('/api/vortex/jwt', async (req, res) => {
1168
+ const user = await getCurrentUser(req);
1169
+
1170
+ const jwt = generateJwt({
1171
+ user: {
1172
+ id: user.id,
1173
+ email: user.email,
1174
+ adminScopes: user.isAutojoinAdmin ? ['autojoin'] : [],
1175
+ },
1176
+ apiKey: process.env.VORTEX_API_KEY,
1177
+ });
1178
+
1179
+ res.json({ jwt });
1180
+ });
1181
+ ```
1182
+
1183
+ ## Configuration
1184
+
1185
+ The SDK automatically fetches and caches widget configuration from your Vortex backend. The configuration determines:
1186
+
1187
+ - Available invitation methods
1188
+ - Form fields and validation
1189
+ - UI theme and styling
1190
+ - Enabled features
1191
+
1192
+ ### Environment Variables
1193
+
1194
+ Set up your environment variables in `.env`:
1195
+
1196
+ ```bash
1197
+ EXPO_PUBLIC_COMPONENT_ID=your-component-id-here
1198
+ ```
1199
+
1200
+ ## Error Handling
1201
+
1202
+ The SDK provides error callbacks for handling failures:
1203
+
1204
+ ```tsx
1205
+ <VortexInvite
1206
+ componentId="my-component-id"
1207
+ jwt={jwt}
1208
+ scope={teamId}
1209
+ scopeType="team"
1210
+ onError={(error) => {
1211
+ // Handle specific error types
1212
+ if (error.message.includes('network')) {
1213
+ Alert.alert('Network Error', 'Please check your connection and try again.');
1214
+ } else if (error.message.includes('unauthorized')) {
1215
+ Alert.alert('Authentication Error', 'Please log in again.');
1216
+ } else {
1217
+ Alert.alert('Error', error.message);
1218
+ }
1219
+ }}
1220
+ />
1221
+ ```
1222
+
1223
+ Common error scenarios:
1224
+ - **Network errors** - Connection issues or API unavailable
1225
+ - **Authentication errors** - Invalid or expired JWT
1226
+ - **Configuration errors** - Invalid component ID or missing configuration
1227
+ - **Validation errors** - Invalid form input (e.g., malformed email)
1228
+
11
1229
  ## Development
12
1230
 
13
1231
  ```bash
@@ -27,16 +1245,14 @@ pnpm run lint
27
1245
  pnpm run dev
28
1246
  ```
29
1247
 
30
- ## Usage
1248
+ ## License
31
1249
 
32
- ```tsx
33
- import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';
1250
+ Apache License 2.0 - see [LICENSE](LICENSE) file for details.
34
1251
 
35
- // Your component code here
36
- ```
1252
+ ## Support
37
1253
 
38
- ## Requirements
1254
+ For questions or issues:
39
1255
 
40
- - React 18.3.1 or higher
41
- - React Native 0.74.5 or higher
42
- - pnpm 10.11.0 or higher
1256
+ - 📚 Documentation: https://docs.vortexsoftware.com
1257
+ - 📧 Email: support@vortexsoftware.com
1258
+ - 🐛 GitHub Issues: https://github.com/teamvortexsoftware/vortex/issues