@teamvortexsoftware/vortex-react-native 0.0.4 → 0.0.6

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.4",
5
+ "version": "0.0.6",
6
6
  "publishConfig": {
7
7
  "access": "restricted"
8
8
  },
@@ -17,17 +17,19 @@
17
17
  "@typescript-eslint/parser": "^7.3.1",
18
18
  "eslint": "^9.24.0",
19
19
  "eslint-plugin-react-native": "^4.1.0",
20
- "tsup": "8.0.1",
21
20
  "typescript": "5.8.3",
22
21
  "typescript-eslint": "^8.30.1",
23
22
  "@teamvortexsoftware/eslint-config": "0.0.0",
24
23
  "@teamvortexsoftware/typescript-config": "0.0.0"
25
24
  },
26
25
  "dependencies": {
26
+ "@teamvortexsoftware/vortex-core": "file://../vortex-core",
27
+ "expo-random": "^14.0.1",
28
+ "expo-standard-web-crypto": "^2.1.4",
27
29
  "react": "18.3.1",
28
30
  "react-native": "0.76.9",
29
31
  "react-native-vector-icons": "^10.2.0",
30
- "@teamvortexsoftware/vortex-core": "0.0.1"
32
+ "uuid": "11.0.5"
31
33
  },
32
34
  "peerDependencies": {
33
35
  "react": "*",
@@ -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.debug("[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
 
@@ -95,48 +88,52 @@ export function useVortexInvite({
95
88
  }, 2000);
96
89
  }, []);
97
90
 
98
-
99
91
  // Fetch widget configuration if needed
100
92
  useEffect(() => {
101
93
  // Case: If `widgetConfiguration` is already set, do nothing
102
94
  if (widgetConfiguration) {
103
- console.debug(
104
- "[Vortex] Invite Already has widgetConfiguration, skipping fetch",
105
- );
95
+ console.log('[Vortex] Invite Already has widgetConfiguration, skipping fetch');
106
96
  return;
107
97
  }
108
98
  if (!jwt) {
109
- console.debug("[Vortex] Invite JWT is required");
99
+ console.log('[Vortex] Invite JWT is required');
110
100
  return;
111
101
  }
112
102
 
113
103
  const fetchData = async () => {
114
- console.debug("[Vortex] Invite Fetching Data...");
104
+ console.log('[Vortex] Invite Fetching Data...');
115
105
  setFetching(true);
116
106
  setLoading(true);
117
107
  setError(null);
118
108
 
119
109
  try {
120
- const result = await vortexClient.getWidgetConfiguration(
121
- widgetId,
122
- jwt,
123
- environmentId,
124
- );
110
+ const result = await vortexClient.getWidgetConfiguration(widgetId, jwt, environmentId);
125
111
  if (result?.data?.widgetConfiguration) {
126
112
  setWidgetConfiguration(result.data.widgetConfiguration);
127
- console.debug(
128
- "[Vortex] Invite Successfully fetched widgetConfiguration",
129
- JSON.stringify(result.data.widgetConfiguration),
113
+ console.log(
114
+ '[Vortex] Invite Successfully fetched widgetConfiguration',
115
+ JSON.stringify(result.data.widgetConfiguration)
130
116
  );
131
117
  } else {
132
- const error = result?.error || "No configuration data found.";
118
+ const error = result?.error || 'No configuration data found.';
133
119
  console.error(`[Vortex] Invite ${error}`);
134
120
  throw new Error(error);
135
121
  }
136
122
  } catch (err) {
137
- console.error("[Vortex] Invite Error", err);
123
+ if (err instanceof Error) {
124
+ const fetchError = err as any;
125
+ console.error('[Vortex] Invite Error Details:', {
126
+ message: fetchError.message,
127
+ status: fetchError.status || 'unknown',
128
+ statusText: fetchError.statusText,
129
+ body: fetchError.body,
130
+ stack: fetchError.stack,
131
+ });
132
+ }
133
+
134
+ console.error('[Vortex] Invite Error', err);
138
135
  setError({
139
- message: (err as Error).message || "Something went wrong!",
136
+ message: (err as Error).message || 'Something went wrong!',
140
137
  });
141
138
  } finally {
142
139
  setFetching(false);
@@ -147,30 +144,34 @@ export function useVortexInvite({
147
144
  fetchData();
148
145
  }, [widgetId, jwt, environmentId]);
149
146
 
150
- const handleInviteClick = async () => {
147
+ const handleInviteClick = async (sessionId: string, contentTokens?: Attributes) => {
151
148
  try {
152
149
  if (!widgetConfiguration?.id) {
153
- console.debug("[Vortex Invite] Widget configuration ID is required");
150
+ console.log('[Vortex Invite] Widget configuration ID is required');
154
151
  return;
155
152
  }
156
153
  // const url = `${host}/api/v1/no-auth/components/widget-configuration/${widgetConfigurationId}/invite`; // TODO should we use options.widgetHost?.value ?
157
- console.debug("[Vortex] Invite Sending invitation", {
154
+ console.log('[Vortex] Invite Sending invitation', {
158
155
  email,
159
156
  widgetId,
160
157
  environmentId,
161
158
  });
162
159
  if (!jwt) {
163
- throw new Error("[Vortex Invite] JWT is required");
160
+ throw new Error('[Vortex Invite] JWT is required');
164
161
  }
162
+ const payload = {
163
+ ...contentTokens,
164
+ email: {
165
+ value: email,
166
+ type: 'email',
167
+ },
168
+ };
165
169
  const body = await vortexClient.createInvite(
166
170
  jwt,
167
171
  widgetConfiguration?.id,
168
172
  environmentId,
169
- {
170
- email: {
171
- value: email,
172
- },
173
- },
173
+ sessionId,
174
+ payload
174
175
  );
175
176
  // const response = await fetch(url, {
176
177
  // method: "POST",
@@ -187,20 +188,17 @@ export function useVortexInvite({
187
188
  // return;
188
189
  // }
189
190
  // const body = await response.json();
190
- console.log("[Vortex] Invite Response", body);
191
+ console.log('[Vortex] Invite Response', body);
191
192
  if (onSuccess) {
192
193
  onSuccess({
193
- type: "invite",
194
- data: "Email invite sent",
194
+ type: 'invite',
195
+ data: 'Email invite sent',
195
196
  });
196
197
  }
197
198
  } catch (error) {
198
- console.error("[Vortex]", error);
199
+ console.error('[Vortex]', error);
199
200
  if (onError) {
200
- onError(
201
- error instanceof Error ? error : new Error(String(error)),
202
- "invite",
203
- );
201
+ onError(error instanceof Error ? error : new Error(String(error)), 'invite');
204
202
  }
205
203
  }
206
204
  };
@@ -217,26 +215,23 @@ export function useVortexInvite({
217
215
  try {
218
216
  const shareableLink = getShareableLink();
219
217
  if (!shareableLink) {
220
- throw new Error("No shareable link available");
218
+ throw new Error('No shareable link available');
221
219
  }
222
220
  await Share.share({
223
221
  message: shareableLink,
224
- title: "Share Invite Link",
222
+ title: 'Share Invite Link',
225
223
  });
226
- console.debug("[Vortex] Link Shared", "The invite link has been shared.");
224
+ console.log('[Vortex] Link Shared', 'The invite link has been shared.');
227
225
  if (onSuccess) {
228
226
  onSuccess({
229
- type: "share",
230
- data: "Sharable link used",
227
+ type: 'share',
228
+ data: 'Sharable link used',
231
229
  });
232
230
  }
233
231
  } catch (error) {
234
- console.error("[Vortex] Failed to share link:", error);
232
+ console.error('[Vortex] Failed to share link:', error);
235
233
  if (onError) {
236
- onError(
237
- error instanceof Error ? error : new Error(String(error)),
238
- "share",
239
- );
234
+ onError(error instanceof Error ? error : new Error(String(error)), 'share');
240
235
  }
241
236
  }
242
237
  };
@@ -245,26 +240,20 @@ export function useVortexInvite({
245
240
  try {
246
241
  const shareableLink = getShareableLink();
247
242
  if (!shareableLink) {
248
- throw new Error("No shareable link available");
243
+ throw new Error('No shareable link available');
249
244
  }
250
245
  await Clipboard.setString(shareableLink);
251
- console.debug(
252
- "[Vortex] Link Copied to clipboard",
253
- "The invite link has been copied.",
254
- );
246
+ console.log('[Vortex] Link Copied to clipboard', 'The invite link has been copied.');
255
247
  if (onSuccess) {
256
248
  onSuccess({
257
- type: "share",
258
- data: "Sharable copied",
249
+ type: 'share',
250
+ data: 'Sharable copied',
259
251
  });
260
252
  }
261
253
  } catch (error) {
262
- console.error("[Vortex] Failed to copy link:", error);
254
+ console.error('[Vortex] Failed to copy link:', error);
263
255
  if (onError) {
264
- onError(
265
- error instanceof Error ? error : new Error(String(error)),
266
- "share",
267
- );
256
+ onError(error instanceof Error ? error : new Error(String(error)), 'share');
268
257
  }
269
258
  }
270
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,29 +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.debug("[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
- throw new Error(
23
- `Error fetching widget configuration: ${response.status}`,
24
- );
18
+ const body = await response.text();
19
+ console.error('[VortexClient] getWidgetConfiguration', { status: response.status, body });
20
+ throw new Error(`Error fetching widget configuration from ${url}: ${response.status}`);
25
21
  }
26
22
 
27
23
  const data = await response.json();
28
- console.debug("[VortexClient] getWidgetConfiguration", { data });
24
+ console.log('[VortexClient] getWidgetConfiguration', { data });
29
25
  return data;
30
26
  }
31
27
 
@@ -33,33 +29,33 @@ class VortexClient {
33
29
  jwt: string,
34
30
  widgetConfigurationId: string,
35
31
  environmentId: string,
36
- payload: any,
32
+ sessionId: string,
33
+ payload: any
37
34
  ) {
38
-
39
-
40
35
  const response = await fetch(
41
36
  `${this.baseUrl}/api/v1/environment/${environmentId}/widget-configuration/${widgetConfigurationId}/invite`,
42
37
  {
43
- method: "POST",
38
+ method: 'POST',
44
39
  headers: {
45
- "Content-Type": "application/json",
40
+ 'Content-Type': 'application/json',
46
41
  Authorization: `Bearer ${jwt}`,
42
+ 'x-session-id': sessionId,
47
43
  },
48
44
  body: JSON.stringify({
49
45
  data: {
50
46
  payload,
51
47
  },
52
48
  }),
53
- },
49
+ }
54
50
  );
55
51
  if (!response.ok) {
56
52
  const body = await response.text();
57
- console.error("[VortexClient] createInvite", { status: response.status, body });
53
+ console.error('[VortexClient] createInvite', { status: response.status, body });
58
54
  throw new Error(`Error POSTing widget invite: ${response.status}`);
59
55
  }
60
56
 
61
57
  const data = await response.json();
62
- console.log("[VortexClient] createInvite", data);
58
+ console.log('[VortexClient] createInvite', data);
63
59
  return data;
64
60
  }
65
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
  });