@teamvortexsoftware/vortex-react-native 0.0.5 → 0.0.7

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.
package/README.md CHANGED
@@ -39,4 +39,4 @@ import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';
39
39
 
40
40
  - React 18.3.1 or higher
41
41
  - React Native 0.74.5 or higher
42
- - pnpm 10.8.1 or higher
42
+ - pnpm 10.10.0 or higher
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@teamvortexsoftware/vortex-react-native",
3
3
  "description": "",
4
4
  "author": "@teamvortexsoftware",
5
- "version": "0.0.5",
5
+ "version": "0.0.7",
6
6
  "publishConfig": {
7
7
  "access": "restricted"
8
8
  },
@@ -13,20 +13,19 @@
13
13
  "@eslint/js": "^9.24.0",
14
14
  "@types/react": "18.2.14",
15
15
  "@types/react-native-vector-icons": "^6.4.18",
16
- "@typescript-eslint/eslint-plugin": "^7.3.1",
17
- "@typescript-eslint/parser": "^7.3.1",
18
16
  "eslint": "^9.24.0",
19
17
  "eslint-plugin-react-native": "^4.1.0",
20
- "tsup": "8.0.1",
21
18
  "typescript": "5.8.3",
22
19
  "typescript-eslint": "^8.30.1",
23
20
  "@teamvortexsoftware/eslint-config": "0.0.0",
24
21
  "@teamvortexsoftware/typescript-config": "0.0.0"
25
22
  },
26
23
  "dependencies": {
24
+ "expo-standard-web-crypto": "^2.1.4",
27
25
  "react": "18.3.1",
28
26
  "react-native": "0.76.9",
29
27
  "react-native-vector-icons": "^10.2.0",
28
+ "uuid": "11.0.5",
30
29
  "@teamvortexsoftware/vortex-core": "0.0.1"
31
30
  },
32
31
  "peerDependencies": {
@@ -1,11 +1,12 @@
1
- import { WidgetConfiguration } from "@teamvortexsoftware/vortex-core";
2
- import { useEffect, useMemo, useState } from "react";
3
- import { Animated, Share, Clipboard } from "react-native";
4
- import VortexClient from "../shared/api";
5
- import { extractFeatureFlags, extractThemeColors } from "../utils/themeUtils";
6
- import { useThemeStyles } from "./useThemeStyles";
1
+ import { WidgetConfiguration } from '@teamvortexsoftware/vortex-core';
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { Animated, Share, Clipboard } from 'react-native';
4
+ import VortexClient from '../shared/api';
5
+ import { extractFeatureFlags, extractThemeColors } from '../utils/themeUtils';
6
+ import { useThemeStyles } from './useThemeStyles';
7
+ import { Attributes } from '../vortexInvite';
7
8
 
8
- export type VortexActionType = "invite" | "share" | string;
9
+ export type VortexActionType = 'invite' | 'share' | string;
9
10
 
10
11
  export interface VortexActionResult {
11
12
  type: VortexActionType;
@@ -31,25 +32,17 @@ export function useVortexInvite({
31
32
  onSuccess,
32
33
  onError,
33
34
  }: UseVortexInviteOptions) {
34
- const vortexClient = useMemo(
35
- () => new VortexClient(vortexApiHost),
36
- [vortexApiHost],
37
- );
35
+ const vortexClient = useMemo(() => new VortexClient(vortexApiHost), [vortexApiHost]);
38
36
 
39
- const [widgetConfiguration, setWidgetConfiguration] = useState<
40
- WidgetConfiguration | undefined
41
- >();
37
+ const [widgetConfiguration, setWidgetConfiguration] = useState<WidgetConfiguration | undefined>();
42
38
  const [error, setError] = useState<{ message: string } | null>(null);
43
39
  const [fetching, setFetching] = useState(isLoading);
44
40
  const [loading, setLoading] = useState(true);
45
- const [email, setEmail] = useState("");
41
+ const [email, setEmail] = useState('');
46
42
  const opacity = new Animated.Value(0.3);
47
43
 
48
44
  // Extract theme colors and feature flags using utility functions
49
- const themeColors = useMemo(
50
- () => extractThemeColors(widgetConfiguration),
51
- [widgetConfiguration],
52
- );
45
+ const themeColors = useMemo(() => extractThemeColors(widgetConfiguration), [widgetConfiguration]);
53
46
 
54
47
  // Get theme styles based on theme colors
55
48
  const themeStyles = useThemeStyles(themeColors);
@@ -60,14 +53,14 @@ export function useVortexInvite({
60
53
 
61
54
  const has = useMemo(() => {
62
55
  const flags = extractFeatureFlags(widgetConfiguration);
63
- console.log("[Vortex] Invite has", flags);
56
+ console.log('[Vortex] Invite has', flags);
64
57
  return flags;
65
58
  }, [widgetConfiguration]);
66
59
 
67
60
  // Log the host only once when the hook mounts
68
61
  useEffect(() => {
69
- if (vortexApiHost.indexOf("localhost") > -1) {
70
- console.warn("[Vortex] Invite Using localhost as host");
62
+ if (vortexApiHost.indexOf('localhost') > -1) {
63
+ console.warn('[Vortex] Invite Using localhost as host');
71
64
  }
72
65
  }, [vortexApiHost]);
73
66
 
@@ -85,7 +78,7 @@ export function useVortexInvite({
85
78
  duration: 1000,
86
79
  useNativeDriver: false,
87
80
  }),
88
- ]),
81
+ ])
89
82
  );
90
83
  animation.start();
91
84
 
@@ -99,54 +92,48 @@ export function useVortexInvite({
99
92
  useEffect(() => {
100
93
  // Case: If `widgetConfiguration` is already set, do nothing
101
94
  if (widgetConfiguration) {
102
- console.log(
103
- "[Vortex] Invite Already has widgetConfiguration, skipping fetch",
104
- );
95
+ console.log('[Vortex] Invite Already has widgetConfiguration, skipping fetch');
105
96
  return;
106
97
  }
107
98
  if (!jwt) {
108
- console.log("[Vortex] Invite JWT is required");
99
+ console.log('[Vortex] Invite JWT is required');
109
100
  return;
110
101
  }
111
102
 
112
103
  const fetchData = async () => {
113
- console.log("[Vortex] Invite Fetching Data...");
104
+ console.log('[Vortex] Invite Fetching Data...');
114
105
  setFetching(true);
115
106
  setLoading(true);
116
107
  setError(null);
117
108
 
118
109
  try {
119
- const result = await vortexClient.getWidgetConfiguration(
120
- widgetId,
121
- jwt,
122
- environmentId,
123
- );
110
+ const result = await vortexClient.getWidgetConfiguration(widgetId, jwt, environmentId);
124
111
  if (result?.data?.widgetConfiguration) {
125
112
  setWidgetConfiguration(result.data.widgetConfiguration);
126
113
  console.log(
127
- "[Vortex] Invite Successfully fetched widgetConfiguration",
128
- JSON.stringify(result.data.widgetConfiguration),
114
+ '[Vortex] Invite Successfully fetched widgetConfiguration',
115
+ JSON.stringify(result.data.widgetConfiguration)
129
116
  );
130
117
  } else {
131
- const error = result?.error || "No configuration data found.";
118
+ const error = result?.error || 'No configuration data found.';
132
119
  console.error(`[Vortex] Invite ${error}`);
133
120
  throw new Error(error);
134
121
  }
135
122
  } catch (err) {
136
123
  if (err instanceof Error) {
137
124
  const fetchError = err as any;
138
- console.error("[Vortex] Invite Error Details:", {
125
+ console.error('[Vortex] Invite Error Details:', {
139
126
  message: fetchError.message,
140
- status: fetchError.status || "unknown",
127
+ status: fetchError.status || 'unknown',
141
128
  statusText: fetchError.statusText,
142
129
  body: fetchError.body,
143
130
  stack: fetchError.stack,
144
131
  });
145
132
  }
146
133
 
147
- console.error("[Vortex] Invite Error", err);
134
+ console.error('[Vortex] Invite Error', err);
148
135
  setError({
149
- message: (err as Error).message || "Something went wrong!",
136
+ message: (err as Error).message || 'Something went wrong!',
150
137
  });
151
138
  } finally {
152
139
  setFetching(false);
@@ -157,30 +144,34 @@ export function useVortexInvite({
157
144
  fetchData();
158
145
  }, [widgetId, jwt, environmentId]);
159
146
 
160
- const handleInviteClick = async () => {
147
+ const handleInviteClick = async (sessionId: string, contentTokens?: Attributes) => {
161
148
  try {
162
149
  if (!widgetConfiguration?.id) {
163
- console.log("[Vortex Invite] Widget configuration ID is required");
150
+ console.log('[Vortex Invite] Widget configuration ID is required');
164
151
  return;
165
152
  }
166
153
  // const url = `${host}/api/v1/no-auth/components/widget-configuration/${widgetConfigurationId}/invite`; // TODO should we use options.widgetHost?.value ?
167
- console.log("[Vortex] Invite Sending invitation", {
154
+ console.log('[Vortex] Invite Sending invitation', {
168
155
  email,
169
156
  widgetId,
170
157
  environmentId,
171
158
  });
172
159
  if (!jwt) {
173
- throw new Error("[Vortex Invite] JWT is required");
160
+ throw new Error('[Vortex Invite] JWT is required');
174
161
  }
162
+ const payload = {
163
+ ...contentTokens,
164
+ email: {
165
+ value: email,
166
+ type: 'email',
167
+ },
168
+ };
175
169
  const body = await vortexClient.createInvite(
176
170
  jwt,
177
171
  widgetConfiguration?.id,
178
172
  environmentId,
179
- {
180
- email: {
181
- value: email,
182
- },
183
- },
173
+ sessionId,
174
+ payload
184
175
  );
185
176
  // const response = await fetch(url, {
186
177
  // method: "POST",
@@ -197,20 +188,17 @@ export function useVortexInvite({
197
188
  // return;
198
189
  // }
199
190
  // const body = await response.json();
200
- console.log("[Vortex] Invite Response", body);
191
+ console.log('[Vortex] Invite Response', body);
201
192
  if (onSuccess) {
202
193
  onSuccess({
203
- type: "invite",
204
- data: "Email invite sent",
194
+ type: 'invite',
195
+ data: 'Email invite sent',
205
196
  });
206
197
  }
207
198
  } catch (error) {
208
- console.error("[Vortex]", error);
199
+ console.error('[Vortex]', error);
209
200
  if (onError) {
210
- onError(
211
- error instanceof Error ? error : new Error(String(error)),
212
- "invite",
213
- );
201
+ onError(error instanceof Error ? error : new Error(String(error)), 'invite');
214
202
  }
215
203
  }
216
204
  };
@@ -227,26 +215,23 @@ export function useVortexInvite({
227
215
  try {
228
216
  const shareableLink = getShareableLink();
229
217
  if (!shareableLink) {
230
- throw new Error("No shareable link available");
218
+ throw new Error('No shareable link available');
231
219
  }
232
220
  await Share.share({
233
221
  message: shareableLink,
234
- title: "Share Invite Link",
222
+ title: 'Share Invite Link',
235
223
  });
236
- console.log("[Vortex] Link Shared", "The invite link has been shared.");
224
+ console.log('[Vortex] Link Shared', 'The invite link has been shared.');
237
225
  if (onSuccess) {
238
226
  onSuccess({
239
- type: "share",
240
- data: "Sharable link used",
227
+ type: 'share',
228
+ data: 'Sharable link used',
241
229
  });
242
230
  }
243
231
  } catch (error) {
244
- console.error("[Vortex] Failed to share link:", error);
232
+ console.error('[Vortex] Failed to share link:', error);
245
233
  if (onError) {
246
- onError(
247
- error instanceof Error ? error : new Error(String(error)),
248
- "share",
249
- );
234
+ onError(error instanceof Error ? error : new Error(String(error)), 'share');
250
235
  }
251
236
  }
252
237
  };
@@ -255,26 +240,20 @@ export function useVortexInvite({
255
240
  try {
256
241
  const shareableLink = getShareableLink();
257
242
  if (!shareableLink) {
258
- throw new Error("No shareable link available");
243
+ throw new Error('No shareable link available');
259
244
  }
260
245
  await Clipboard.setString(shareableLink);
261
- console.log(
262
- "[Vortex] Link Copied to clipboard",
263
- "The invite link has been copied.",
264
- );
246
+ console.log('[Vortex] Link Copied to clipboard', 'The invite link has been copied.');
265
247
  if (onSuccess) {
266
248
  onSuccess({
267
- type: "share",
268
- data: "Sharable copied",
249
+ type: 'share',
250
+ data: 'Sharable copied',
269
251
  });
270
252
  }
271
253
  } catch (error) {
272
- console.error("[Vortex] Failed to copy link:", error);
254
+ console.error('[Vortex] Failed to copy link:', error);
273
255
  if (onError) {
274
- onError(
275
- error instanceof Error ? error : new Error(String(error)),
276
- "share",
277
- );
256
+ onError(error instanceof Error ? error : new Error(String(error)), 'share');
278
257
  }
279
258
  }
280
259
  };
package/src/index.tsx CHANGED
@@ -1,2 +1,2 @@
1
- export { VortexInvite, type VortexInviteProps } from "./vortexInvite";
2
- export { type VortexActionResult, type VortexActionType } from "./hooks/useVortexInvite";
1
+ export { VortexInvite, type VortexInviteProps, type Attributes } from './vortexInvite';
2
+ export { type VortexActionResult, type VortexActionType } from './hooks/useVortexInvite';
package/src/shared/api.ts CHANGED
@@ -3,31 +3,25 @@ class VortexClient {
3
3
 
4
4
  constructor(baseUrl: string) {
5
5
  // trim ending /
6
- this.baseUrl = baseUrl.replace(/\/+$/, "");
6
+ this.baseUrl = baseUrl.replace(/\/+$/, '');
7
7
  }
8
8
 
9
- async getWidgetConfiguration(
10
- widgetId: string,
11
- jwt: string,
12
- environmentId: string,
13
- ) {
9
+ async getWidgetConfiguration(widgetId: string, jwt: string, environmentId: string) {
14
10
  const url = `${this.baseUrl}/api/v1/environment/${environmentId}/widgets/${widgetId}`;
15
- console.log("[VortexClient] getWidgetConfiguration", { url });
16
-
11
+ console.log('[VortexClient] getWidgetConfiguration', { url });
12
+
17
13
  const response = await fetch(url, {
18
14
  headers: { Authorization: `Bearer ${jwt}` },
19
15
  });
20
16
 
21
17
  if (!response.ok) {
22
18
  const body = await response.text();
23
- console.error("[VortexClient] getWidgetConfiguration", { status: response.status, body });
24
- throw new Error(
25
- `Error fetching widget configuration from ${url}: ${response.status}`,
26
- );
19
+ console.error('[VortexClient] getWidgetConfiguration', { status: response.status, body });
20
+ throw new Error(`Error fetching widget configuration from ${url}: ${response.status}`);
27
21
  }
28
22
 
29
23
  const data = await response.json();
30
- console.log("[VortexClient] getWidgetConfiguration", { data });
24
+ console.log('[VortexClient] getWidgetConfiguration', { data });
31
25
  return data;
32
26
  }
33
27
 
@@ -35,33 +29,33 @@ class VortexClient {
35
29
  jwt: string,
36
30
  widgetConfigurationId: string,
37
31
  environmentId: string,
38
- payload: any,
32
+ sessionId: string,
33
+ payload: any
39
34
  ) {
40
-
41
-
42
35
  const response = await fetch(
43
36
  `${this.baseUrl}/api/v1/environment/${environmentId}/widget-configuration/${widgetConfigurationId}/invite`,
44
37
  {
45
- method: "POST",
38
+ method: 'POST',
46
39
  headers: {
47
- "Content-Type": "application/json",
40
+ 'Content-Type': 'application/json',
48
41
  Authorization: `Bearer ${jwt}`,
42
+ 'x-session-id': sessionId,
49
43
  },
50
44
  body: JSON.stringify({
51
45
  data: {
52
46
  payload,
53
47
  },
54
48
  }),
55
- },
49
+ }
56
50
  );
57
51
  if (!response.ok) {
58
52
  const body = await response.text();
59
- console.error("[VortexClient] createInvite", { status: response.status, body });
53
+ console.error('[VortexClient] createInvite', { status: response.status, body });
60
54
  throw new Error(`Error POSTing widget invite: ${response.status}`);
61
55
  }
62
56
 
63
57
  const data = await response.json();
64
- console.log("[VortexClient] createInvite", data);
58
+ console.log('[VortexClient] createInvite', data);
65
59
  return data;
66
60
  }
67
61
 
@@ -1,18 +1,17 @@
1
- import React from "react";
2
- import {
3
- Animated,
4
- Pressable,
5
- StyleSheet,
6
- Text,
7
- TextInput,
8
- View,
9
- } from "react-native";
10
- import {
11
- useVortexInvite,
12
- VortexActionResult,
13
- VortexActionType,
14
- } from "./hooks/useVortexInvite";
15
- import { ShareButtons } from "./components/ShareButtons";
1
+ import React from 'react';
2
+ import { Animated, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
3
+ import { useVortexInvite, VortexActionResult, VortexActionType } from './hooks/useVortexInvite';
4
+ import { ShareButtons } from './components/ShareButtons';
5
+ import { polyfillWebCrypto } from 'expo-standard-web-crypto';
6
+ import { v4 } from 'uuid';
7
+
8
+ export interface Attributes {
9
+ [key: string]: { value: string | string[]; type: string; role?: string };
10
+ }
11
+
12
+ polyfillWebCrypto();
13
+ // TODO from main app that uses this library
14
+ const sessionId = v4();
16
15
 
17
16
  export interface VortexInviteProps {
18
17
  environmentId: string;
@@ -22,6 +21,7 @@ export interface VortexInviteProps {
22
21
  jwt?: string;
23
22
  onSuccess?: (result: VortexActionResult) => void;
24
23
  onError?: (error: Error, type: VortexActionType) => void;
24
+ contentTokens?: Attributes;
25
25
  // host: string;
26
26
  // widgetConfigurationId?: string;
27
27
  // widgetConfiguration?: WidgetConfiguration;
@@ -38,6 +38,7 @@ export function VortexInvite({
38
38
  jwt,
39
39
  onSuccess,
40
40
  onError,
41
+ contentTokens,
41
42
  }: VortexInviteProps) {
42
43
  const {
43
44
  error,
@@ -77,9 +78,7 @@ export function VortexInvite({
77
78
  <View style={[styles.container, themeStyles.containerStyles]}>
78
79
  {has?.emailInvitations && (
79
80
  <View style={styles.inviteContainer}>
80
- <Text style={[styles.introText, themeStyles.textStyles]}>
81
- Invite People
82
- </Text>
81
+ <Text style={[styles.introText, themeStyles.textStyles]}>Invite People</Text>
83
82
  <TextInput
84
83
  style={[styles.input, themeStyles.inputStyles]}
85
84
  placeholder="Enter email"
@@ -91,13 +90,9 @@ export function VortexInvite({
91
90
  <View style={styles.buttonContainer}>
92
91
  <Pressable
93
92
  style={[styles.submitButton, themeStyles.primaryButton]}
94
- onPress={handleInviteClick}
93
+ onPress={() => handleInviteClick(sessionId, contentTokens)}
95
94
  >
96
- <Text
97
- style={[styles.submitButtonText, themeStyles.primaryButtonText]}
98
- >
99
- Invite
100
- </Text>
95
+ <Text style={[styles.submitButtonText, themeStyles.primaryButtonText]}>Invite</Text>
101
96
  </Pressable>
102
97
  </View>
103
98
  </View>
@@ -128,26 +123,26 @@ export function VortexInvite({
128
123
  const styles = StyleSheet.create({
129
124
  container: {
130
125
  padding: 15,
131
- display: "flex",
132
- flexDirection: "column",
126
+ display: 'flex',
127
+ flexDirection: 'column',
133
128
  borderRadius: 8,
134
129
  borderWidth: 1,
135
130
  },
136
131
  inviteContainer: {
137
- display: "flex",
138
- flexDirection: "column",
139
- justifyContent: "center",
140
- alignItems: "center",
132
+ display: 'flex',
133
+ flexDirection: 'column',
134
+ justifyContent: 'center',
135
+ alignItems: 'center',
141
136
  marginTop: 8,
142
137
  },
143
138
  introText: {
144
139
  marginBottom: 8,
145
140
  fontSize: 24,
146
- fontWeight: "bold",
147
- textAlign: "center",
141
+ fontWeight: 'bold',
142
+ textAlign: 'center',
148
143
  },
149
144
  input: {
150
- width: "100%",
145
+ width: '100%',
151
146
  borderWidth: 1,
152
147
  padding: 10,
153
148
  borderRadius: 5,
@@ -156,38 +151,38 @@ const styles = StyleSheet.create({
156
151
  buttonContainer: {
157
152
  marginTop: 16,
158
153
  marginBottom: 8,
159
- width: "100%",
154
+ width: '100%',
160
155
  },
161
156
  submitButton: {
162
157
  padding: 12,
163
158
  borderRadius: 5,
164
- alignItems: "center",
159
+ alignItems: 'center',
165
160
  borderWidth: 1,
166
161
  },
167
162
  submitButtonText: {
168
- fontWeight: "500",
169
- textTransform: "none",
163
+ fontWeight: '500',
164
+ textTransform: 'none',
170
165
  },
171
166
  skeleton: {
172
- width: "100%",
167
+ width: '100%',
173
168
  height: 50,
174
- backgroundColor: "#e0e0e0",
169
+ backgroundColor: '#e0e0e0',
175
170
  borderRadius: 5,
176
171
  },
177
172
  shareContainer: {
178
173
  marginTop: 16,
179
- alignItems: "center",
174
+ alignItems: 'center',
180
175
  },
181
176
  divider: {
182
177
  marginVertical: 10,
183
178
  fontSize: 16,
184
- fontWeight: "bold",
185
- textAlign: "center",
179
+ fontWeight: 'bold',
180
+ textAlign: 'center',
186
181
  },
187
182
  shareButtonsContainer: {
188
- flexDirection: "row",
189
- flexWrap: "wrap",
190
- justifyContent: "center",
183
+ flexDirection: 'row',
184
+ flexWrap: 'wrap',
185
+ justifyContent: 'center',
191
186
  },
192
187
  // Share button styles moved to ShareButtons.tsx
193
188
  });