@teamvortexsoftware/vortex-react-native 0.0.8 → 0.0.10

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 (42) hide show
  1. package/dist/components/ShareButtons.js +170 -0
  2. package/dist/hooks/useThemeStyles.js +39 -0
  3. package/dist/hooks/useVortexInvite.js +281 -0
  4. package/dist/index.js +5 -0
  5. package/dist/shared/InvitationResult.js +2 -0
  6. package/dist/shared/api.js +90 -0
  7. package/dist/tests/TestVortexInvite.js +134 -0
  8. package/dist/types/components/ShareButtons.d.ts +27 -0
  9. package/dist/types/components/ShareButtons.d.ts.map +1 -0
  10. package/dist/types/hooks/useThemeStyles.d.ts +34 -0
  11. package/dist/types/hooks/useThemeStyles.d.ts.map +1 -0
  12. package/dist/types/hooks/useVortexInvite.d.ts +41 -0
  13. package/dist/types/hooks/useVortexInvite.d.ts.map +1 -0
  14. package/dist/types/index.d.ts +2 -0
  15. package/dist/types/index.d.ts.map +1 -0
  16. package/dist/types/shared/InvitationResult.d.ts +24 -0
  17. package/dist/types/shared/InvitationResult.d.ts.map +1 -0
  18. package/dist/types/shared/api.d.ts +14 -0
  19. package/dist/types/shared/api.d.ts.map +1 -0
  20. package/dist/types/tests/TestVortexInvite.d.ts +4 -0
  21. package/dist/types/tests/TestVortexInvite.d.ts.map +1 -0
  22. package/dist/types/utils/formUtils.d.ts +85 -0
  23. package/dist/types/utils/formUtils.d.ts.map +1 -0
  24. package/dist/types/utils/themeUtils.d.ts +35 -0
  25. package/dist/types/utils/themeUtils.d.ts.map +1 -0
  26. package/dist/types/vortexInvite.d.ts +25 -0
  27. package/dist/types/vortexInvite.d.ts.map +1 -0
  28. package/dist/utils/formUtils.js +174 -0
  29. package/dist/utils/themeUtils.js +55 -0
  30. package/dist/vortexInvite.js +172 -0
  31. package/package.json +6 -4
  32. package/eslint.config.mjs +0 -4
  33. package/plugin/withVortexReactNative.js +0 -41
  34. package/plugin.js +0 -2
  35. package/src/components/ShareButtons.tsx +0 -172
  36. package/src/hooks/useThemeStyles.ts +0 -42
  37. package/src/hooks/useVortexInvite.ts +0 -278
  38. package/src/index.tsx +0 -2
  39. package/src/shared/api.ts +0 -67
  40. package/src/utils/themeUtils.ts +0 -85
  41. package/src/vortexInvite.tsx +0 -188
  42. package/tsconfig.json +0 -16
@@ -1,278 +0,0 @@
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';
8
-
9
- export type VortexActionType = 'invite' | 'share' | string;
10
-
11
- export interface VortexActionResult {
12
- type: VortexActionType;
13
- data: string;
14
- }
15
-
16
- export interface UseVortexInviteOptions {
17
- widgetId: string;
18
- environmentId: string;
19
- vortexApiHost: string;
20
- isLoading?: boolean;
21
- jwt?: string;
22
- onSuccess?: (result: VortexActionResult) => void;
23
- onError?: (error: Error, type: VortexActionType) => void;
24
- }
25
-
26
- export function useVortexInvite({
27
- widgetId,
28
- environmentId,
29
- vortexApiHost,
30
- isLoading = false,
31
- jwt,
32
- onSuccess,
33
- onError,
34
- }: UseVortexInviteOptions) {
35
- const vortexClient = useMemo(() => new VortexClient(vortexApiHost), [vortexApiHost]);
36
-
37
- const [widgetConfiguration, setWidgetConfiguration] = useState<WidgetConfiguration | undefined>();
38
- const [error, setError] = useState<{ message: string } | null>(null);
39
- const [fetching, setFetching] = useState(isLoading);
40
- const [loading, setLoading] = useState(true);
41
- const [email, setEmail] = useState('');
42
- const opacity = new Animated.Value(0.3);
43
-
44
- // Extract theme colors and feature flags using utility functions
45
- const themeColors = useMemo(() => extractThemeColors(widgetConfiguration), [widgetConfiguration]);
46
-
47
- // Get theme styles based on theme colors
48
- const themeStyles = useThemeStyles(themeColors);
49
-
50
- const options = useMemo(() => {
51
- return widgetConfiguration?.configuration?.props || {};
52
- }, [widgetConfiguration]);
53
-
54
- const has = useMemo(() => {
55
- const flags = extractFeatureFlags(widgetConfiguration);
56
- console.log('[Vortex] Invite has', flags);
57
- return flags;
58
- }, [widgetConfiguration]);
59
-
60
- // Log the host only once when the hook mounts
61
- useEffect(() => {
62
- if (vortexApiHost.indexOf('localhost') > -1) {
63
- console.warn('[Vortex] Invite Using localhost as host');
64
- }
65
- }, [vortexApiHost]);
66
-
67
- // Loading animation effect
68
- useEffect(() => {
69
- const animation = Animated.loop(
70
- Animated.sequence([
71
- Animated.timing(opacity, {
72
- toValue: 1,
73
- duration: 1000,
74
- useNativeDriver: false,
75
- }),
76
- Animated.timing(opacity, {
77
- toValue: 0.3,
78
- duration: 1000,
79
- useNativeDriver: false,
80
- }),
81
- ])
82
- );
83
- animation.start();
84
-
85
- setTimeout(() => {
86
- setLoading(false);
87
- animation.stop();
88
- }, 2000);
89
- }, []);
90
-
91
- // Fetch widget configuration if needed
92
- useEffect(() => {
93
- // Case: If `widgetConfiguration` is already set, do nothing
94
- if (widgetConfiguration) {
95
- console.log('[Vortex] Invite Already has widgetConfiguration, skipping fetch');
96
- return;
97
- }
98
- if (!jwt) {
99
- console.log('[Vortex] Invite JWT is required');
100
- return;
101
- }
102
-
103
- const fetchData = async () => {
104
- console.log('[Vortex] Invite Fetching Data...');
105
- setFetching(true);
106
- setLoading(true);
107
- setError(null);
108
-
109
- try {
110
- const result = await vortexClient.getWidgetConfiguration(widgetId, jwt, environmentId);
111
- if (result?.data?.widgetConfiguration) {
112
- setWidgetConfiguration(result.data.widgetConfiguration);
113
- console.log(
114
- '[Vortex] Invite Successfully fetched widgetConfiguration',
115
- JSON.stringify(result.data.widgetConfiguration)
116
- );
117
- } else {
118
- const error = result?.error || 'No configuration data found.';
119
- console.error(`[Vortex] Invite ${error}`);
120
- throw new Error(error);
121
- }
122
- } catch (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);
135
- setError({
136
- message: (err as Error).message || 'Something went wrong!',
137
- });
138
- } finally {
139
- setFetching(false);
140
- setLoading(false);
141
- }
142
- };
143
-
144
- fetchData();
145
- }, [widgetId, jwt, environmentId]);
146
-
147
- const handleInviteClick = async (sessionId: string, contentTokens?: Attributes) => {
148
- try {
149
- if (!widgetConfiguration?.id) {
150
- console.log('[Vortex Invite] Widget configuration ID is required');
151
- return;
152
- }
153
- // const url = `${host}/api/v1/no-auth/components/widget-configuration/${widgetConfigurationId}/invite`; // TODO should we use options.widgetHost?.value ?
154
- console.log('[Vortex] Invite Sending invitation', {
155
- email,
156
- widgetId,
157
- environmentId,
158
- });
159
- if (!jwt) {
160
- throw new Error('[Vortex Invite] JWT is required');
161
- }
162
- const payload = {
163
- ...contentTokens,
164
- email: {
165
- value: email,
166
- type: 'email',
167
- },
168
- };
169
- const body = await vortexClient.createInvite(
170
- jwt,
171
- widgetConfiguration?.id,
172
- environmentId,
173
- sessionId,
174
- payload
175
- );
176
- // const response = await fetch(url, {
177
- // method: "POST",
178
- // headers: {
179
- // "Content-Type": "application/json",
180
- // },
181
- // body: JSON.stringify({ to: [{ email }] }),
182
- // });
183
- // if (!response.ok) {
184
- // const body = await response.json();
185
- // console.error("[Vortex] Invite Error", body);
186
- // const error = body?.message || body?.error || "Something went wrong!";
187
- // onError?.(new Error(error), "invite");
188
- // return;
189
- // }
190
- // const body = await response.json();
191
- console.log('[Vortex] Invite Response', body);
192
- if (onSuccess) {
193
- onSuccess({
194
- type: 'invite',
195
- data: 'Email invite sent',
196
- });
197
- }
198
- } catch (error) {
199
- console.error('[Vortex]', error);
200
- if (onError) {
201
- onError(error instanceof Error ? error : new Error(String(error)), 'invite');
202
- }
203
- }
204
- };
205
-
206
- const getShareableLink = (): string | undefined => {
207
- const result =
208
- widgetConfiguration?.slug != null
209
- ? vortexClient.getShareableLinkFormatted(widgetConfiguration.slug)
210
- : undefined;
211
- return result;
212
- };
213
-
214
- const handleShareLink = async () => {
215
- try {
216
- const shareableLink = getShareableLink();
217
- if (!shareableLink) {
218
- throw new Error('No shareable link available');
219
- }
220
- await Share.share({
221
- message: shareableLink,
222
- title: 'Share Invite Link',
223
- });
224
- console.log('[Vortex] Link Shared', 'The invite link has been shared.');
225
- if (onSuccess) {
226
- onSuccess({
227
- type: 'share',
228
- data: 'Sharable link used',
229
- });
230
- }
231
- } catch (error) {
232
- console.error('[Vortex] Failed to share link:', error);
233
- if (onError) {
234
- onError(error instanceof Error ? error : new Error(String(error)), 'share');
235
- }
236
- }
237
- };
238
-
239
- const handleCopyLink = async () => {
240
- try {
241
- const shareableLink = getShareableLink();
242
- if (!shareableLink) {
243
- throw new Error('No shareable link available');
244
- }
245
- await Clipboard.setString(shareableLink);
246
- console.log('[Vortex] Link Copied to clipboard', 'The invite link has been copied.');
247
- if (onSuccess) {
248
- onSuccess({
249
- type: 'share',
250
- data: 'Sharable copied',
251
- });
252
- }
253
- } catch (error) {
254
- console.error('[Vortex] Failed to copy link:', error);
255
- if (onError) {
256
- onError(error instanceof Error ? error : new Error(String(error)), 'share');
257
- }
258
- }
259
- };
260
-
261
- return {
262
- widgetConfiguration,
263
- error,
264
- fetching,
265
- loading,
266
- email,
267
- setEmail,
268
- opacity,
269
- themeColors,
270
- themeStyles,
271
- has,
272
- options,
273
- handleInviteClick,
274
- handleShareLink,
275
- handleCopyLink,
276
- getShareableLink,
277
- };
278
- }
package/src/index.tsx DELETED
@@ -1,2 +0,0 @@
1
- export { VortexInvite, type VortexInviteProps, type Attributes } from './vortexInvite';
2
- export { type VortexActionResult, type VortexActionType } from './hooks/useVortexInvite';
package/src/shared/api.ts DELETED
@@ -1,67 +0,0 @@
1
- class VortexClient {
2
- private baseUrl: string;
3
-
4
- constructor(baseUrl: string) {
5
- // trim ending /
6
- this.baseUrl = baseUrl.replace(/\/+$/, '');
7
- }
8
-
9
- async getWidgetConfiguration(widgetId: string, jwt: string, environmentId: string) {
10
- const url = `${this.baseUrl}/api/v1/environment/${environmentId}/widgets/${widgetId}`;
11
- console.log('[VortexClient] getWidgetConfiguration', { url });
12
-
13
- const response = await fetch(url, {
14
- headers: { Authorization: `Bearer ${jwt}` },
15
- });
16
-
17
- if (!response.ok) {
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}`);
21
- }
22
-
23
- const data = await response.json();
24
- console.log('[VortexClient] getWidgetConfiguration', { data });
25
- return data;
26
- }
27
-
28
- async createInvite(
29
- jwt: string,
30
- widgetConfigurationId: string,
31
- environmentId: string,
32
- sessionId: string,
33
- payload: any
34
- ) {
35
- const response = await fetch(
36
- `${this.baseUrl}/api/v1/environment/${environmentId}/widget-configuration/${widgetConfigurationId}/invite`,
37
- {
38
- method: 'POST',
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- Authorization: `Bearer ${jwt}`,
42
- 'x-session-id': sessionId,
43
- },
44
- body: JSON.stringify({
45
- data: {
46
- payload,
47
- },
48
- }),
49
- }
50
- );
51
- if (!response.ok) {
52
- const body = await response.text();
53
- console.error('[VortexClient] createInvite', { status: response.status, body });
54
- throw new Error(`Error POSTing widget invite: ${response.status}`);
55
- }
56
-
57
- const data = await response.json();
58
- console.log('[VortexClient] createInvite', data);
59
- return data;
60
- }
61
-
62
- getShareableLinkFormatted(slug: string) {
63
- return `${this.baseUrl}/api/v1/no-auth/components/share/${slug}`;
64
- }
65
- }
66
-
67
- export default VortexClient;
@@ -1,85 +0,0 @@
1
- import { WidgetConfiguration } from "@teamvortexsoftware/vortex-core";
2
-
3
- export interface ThemeColors {
4
- containerBackground: string;
5
- containerForeground: string;
6
- containerBorder: string;
7
- primaryButtonBackground: string;
8
- primaryButtonForeground: string;
9
- primaryButtonBorder: string;
10
- secondaryButtonBackground: string;
11
- secondaryButtonForeground: string;
12
- secondaryButtonBorder: string;
13
- }
14
-
15
- export interface FeatureFlags {
16
- shareableLinks: boolean;
17
- emailInvitations: boolean;
18
- shareOptionsCopyLink: boolean;
19
- shareOptionsSms: boolean;
20
- shareOptionsFacebookMessenger: boolean;
21
- shareOptionsInstagramDms: boolean;
22
- shareOptionsLinkedInMessaging: boolean;
23
- shareOptionsTwitterDms: boolean;
24
- shareOptionsWhatsApp: boolean;
25
- shareOptionsNativeShareSheet: boolean;
26
- shareOptionsQrCode: boolean;
27
- }
28
-
29
- /**
30
- * Extracts theme colors from widget configuration
31
- */
32
- export function extractThemeColors(widgetConfiguration?: WidgetConfiguration): ThemeColors {
33
- const options = widgetConfiguration?.configuration?.props;
34
- const colors = options?.["vortex.theme.colors"]?.value || [];
35
- const colorMap: Record<string, string> = {};
36
-
37
- colors.forEach((color: any) => {
38
- if (color.key && color.value) {
39
- colorMap[color.key] = color.value;
40
- }
41
- });
42
-
43
- return {
44
- containerBackground: colorMap["--container-background"] || "#ffffff",
45
- containerForeground: colorMap["--container-foreground-color"] || "#666666",
46
- containerBorder: colorMap["--container-border-color"] || "#c4c4c4",
47
- primaryButtonBackground: colorMap["--primary-button-background"] || "#197af3",
48
- primaryButtonForeground: colorMap["--primary-button-foreground-color"] || "#ffffff",
49
- primaryButtonBorder: colorMap["--primary-button-border-color"] || "#000000",
50
- secondaryButtonBackground: colorMap["--secondary-button-background"] || "#dfdfdf",
51
- secondaryButtonForeground: colorMap["--secondary-button-foreground-color"] || "#000000",
52
- secondaryButtonBorder: colorMap["--secondary-button-border-color"] || "#c4c4c4",
53
- };
54
- }
55
-
56
- /**
57
- * Extracts feature flags from widget configuration
58
- */
59
- export function extractFeatureFlags(widgetConfiguration?: WidgetConfiguration): FeatureFlags {
60
- const features: string[] = Array.isArray(
61
- widgetConfiguration?.configuration?.props?.["vortex.components"]?.value,
62
- )
63
- ? widgetConfiguration?.configuration?.props?.["vortex.components"]?.value
64
- : [];
65
-
66
- const shareOptions: string[] = Array.isArray(
67
- widgetConfiguration?.configuration?.props?.["vortex.components.share.options"]?.value,
68
- )
69
- ? widgetConfiguration?.configuration?.props?.["vortex.components.share.options"]?.value
70
- : [];
71
-
72
- return {
73
- shareableLinks: features.includes("vortex.components.emailinvitations"),
74
- emailInvitations: features.includes("vortex.components.share"),
75
- shareOptionsCopyLink: shareOptions.includes("copyLink"),
76
- shareOptionsFacebookMessenger: shareOptions.includes("facebookMessenger"),
77
- shareOptionsInstagramDms: shareOptions.includes("instagramDms"),
78
- shareOptionsLinkedInMessaging: shareOptions.includes("linkedInMessaging"),
79
- shareOptionsNativeShareSheet: shareOptions.includes("nativeShareSheet"),
80
- shareOptionsQrCode: shareOptions.includes("qrCode"),
81
- shareOptionsSms: shareOptions.includes("sms"),
82
- shareOptionsTwitterDms: shareOptions.includes("twitterDms"),
83
- shareOptionsWhatsApp: shareOptions.includes("whatsApp"),
84
- };
85
- }
@@ -1,188 +0,0 @@
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();
15
-
16
- export interface VortexInviteProps {
17
- environmentId: string;
18
- widgetId: string;
19
- vortexApiHost: string;
20
- isLoading: boolean;
21
- jwt?: string;
22
- onSuccess?: (result: VortexActionResult) => void;
23
- onError?: (error: Error, type: VortexActionType) => void;
24
- contentTokens?: Attributes;
25
- // host: string;
26
- // widgetConfigurationId?: string;
27
- // widgetConfiguration?: WidgetConfiguration;
28
- // isLoading?: boolean;
29
- // onSuccess?: (result: VortexActionResult) => void;
30
- // onError?: (error: Error, type: VortexActionType) => void;
31
- }
32
-
33
- export function VortexInvite({
34
- widgetId,
35
- environmentId,
36
- vortexApiHost,
37
- isLoading = false,
38
- jwt,
39
- onSuccess,
40
- onError,
41
- contentTokens,
42
- }: VortexInviteProps) {
43
- const {
44
- error,
45
- fetching,
46
- loading,
47
- email,
48
- setEmail,
49
- opacity,
50
- themeColors,
51
- themeStyles,
52
- has,
53
- handleInviteClick,
54
- handleShareLink,
55
- handleCopyLink,
56
- getShareableLink,
57
- } = useVortexInvite({
58
- widgetId,
59
- environmentId,
60
- vortexApiHost,
61
- isLoading,
62
- jwt,
63
- onSuccess,
64
- onError,
65
- });
66
-
67
- if (error?.message) {
68
- return <Text data-testid="error">{error.message}</Text>;
69
- }
70
-
71
- if (fetching || loading) {
72
- return <Animated.View style={[styles.skeleton, { opacity }]} />;
73
- }
74
-
75
- // Theme styles are now provided by the hook
76
-
77
- return (
78
- <View style={[styles.container, themeStyles.containerStyles]}>
79
- {has?.emailInvitations && (
80
- <View style={styles.inviteContainer}>
81
- <Text style={[styles.introText, themeStyles.textStyles]}>Invite People</Text>
82
- <TextInput
83
- style={[styles.input, themeStyles.inputStyles]}
84
- placeholder="Enter email"
85
- placeholderTextColor={themeColors.containerForeground}
86
- value={email}
87
- onChangeText={setEmail}
88
- autoCapitalize="none"
89
- />
90
- <View style={styles.buttonContainer}>
91
- <Pressable
92
- style={[styles.submitButton, themeStyles.primaryButton]}
93
- onPress={() => handleInviteClick(sessionId, contentTokens)}
94
- >
95
- <Text style={[styles.submitButtonText, themeStyles.primaryButtonText]}>Invite</Text>
96
- </Pressable>
97
- </View>
98
- </View>
99
- )}
100
-
101
- {has?.shareableLinks && (
102
- <View style={styles.shareContainer}>
103
- {has?.emailInvitations && (
104
- <Text style={[styles.divider, themeStyles.textStyles]}>OR</Text>
105
- )}
106
-
107
- <View style={styles.shareButtonsContainer}>
108
- <ShareButtons
109
- has={has}
110
- themeColors={themeColors}
111
- themeStyles={themeStyles}
112
- handleShareLink={handleShareLink}
113
- handleCopyLink={handleCopyLink}
114
- shareableLink={getShareableLink()}
115
- />
116
- </View>
117
- </View>
118
- )}
119
- </View>
120
- );
121
- }
122
-
123
- const styles = StyleSheet.create({
124
- container: {
125
- padding: 15,
126
- display: 'flex',
127
- flexDirection: 'column',
128
- borderRadius: 8,
129
- borderWidth: 1,
130
- },
131
- inviteContainer: {
132
- display: 'flex',
133
- flexDirection: 'column',
134
- justifyContent: 'center',
135
- alignItems: 'center',
136
- marginTop: 8,
137
- },
138
- introText: {
139
- marginBottom: 8,
140
- fontSize: 24,
141
- fontWeight: 'bold',
142
- textAlign: 'center',
143
- },
144
- input: {
145
- width: '100%',
146
- borderWidth: 1,
147
- padding: 10,
148
- borderRadius: 5,
149
- marginBottom: 8,
150
- },
151
- buttonContainer: {
152
- marginTop: 16,
153
- marginBottom: 8,
154
- width: '100%',
155
- },
156
- submitButton: {
157
- padding: 12,
158
- borderRadius: 5,
159
- alignItems: 'center',
160
- borderWidth: 1,
161
- },
162
- submitButtonText: {
163
- fontWeight: '500',
164
- textTransform: 'none',
165
- },
166
- skeleton: {
167
- width: '100%',
168
- height: 50,
169
- backgroundColor: '#e0e0e0',
170
- borderRadius: 5,
171
- },
172
- shareContainer: {
173
- marginTop: 16,
174
- alignItems: 'center',
175
- },
176
- divider: {
177
- marginVertical: 10,
178
- fontSize: 16,
179
- fontWeight: 'bold',
180
- textAlign: 'center',
181
- },
182
- shareButtonsContainer: {
183
- flexDirection: 'row',
184
- flexWrap: 'wrap',
185
- justifyContent: 'center',
186
- },
187
- // Share button styles moved to ShareButtons.tsx
188
- });
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "extends": "@teamvortexsoftware/typescript-config/react-native-library",
3
- "include": ["src"],
4
- "exclude": ["node_modules", "dist"],
5
- "compilerOptions": {
6
- "outDir": "dist",
7
- "rootDir": "src",
8
- "declaration": true,
9
- "declarationDir": "dist/types",
10
- "emitDeclarationOnly": false,
11
- "moduleResolution": "node",
12
- "module": "CommonJS",
13
- "jsx": "react-native",
14
- "strict": true
15
- }
16
- }