yaver-feedback-react-native 0.9.0 → 0.9.3

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 (144) hide show
  1. package/README.md +102 -1
  2. package/app.plugin.js +126 -9
  3. package/dist/AuthOverlay.d.ts +1 -14
  4. package/dist/AuthOverlay.js +9 -62
  5. package/dist/BlackBox.js +9 -1
  6. package/dist/DeployPanel.js +65 -0
  7. package/dist/Discovery.js +13 -8
  8. package/dist/DogfoodRuntime.d.ts +124 -0
  9. package/dist/DogfoodRuntime.js +273 -0
  10. package/dist/FeedbackModal.js +505 -65
  11. package/dist/LoginScreen.d.ts +1 -5
  12. package/dist/LoginScreen.js +2 -6
  13. package/dist/MachinePickerScreen.d.ts +1 -3
  14. package/dist/MachinePickerScreen.js +6 -18
  15. package/dist/P2PClient.d.ts +178 -2
  16. package/dist/P2PClient.js +493 -3
  17. package/dist/P2PDogfoodDriver.d.ts +12 -0
  18. package/dist/P2PDogfoodDriver.js +118 -0
  19. package/dist/PairDeviceModal.d.ts +2 -3
  20. package/dist/ShakeDetector.js +2 -0
  21. package/dist/VibeChatScreen.d.ts +11 -1
  22. package/dist/VibeChatScreen.js +200 -41
  23. package/dist/YaverFeedback.d.ts +132 -0
  24. package/dist/YaverFeedback.js +633 -65
  25. package/dist/YaverModeBadge.d.ts +22 -0
  26. package/dist/YaverModeBadge.js +219 -0
  27. package/dist/__tests__/AuthDevices.test.js +1 -48
  28. package/dist/__tests__/BlackBox.relayPassword.test.d.ts +1 -0
  29. package/dist/__tests__/BlackBox.relayPassword.test.js +105 -0
  30. package/dist/__tests__/BlackBoxAutoStart.test.d.ts +1 -0
  31. package/dist/__tests__/BlackBoxAutoStart.test.js +91 -0
  32. package/dist/__tests__/BlackBoxAutoStartColdStart.test.d.ts +1 -0
  33. package/dist/__tests__/BlackBoxAutoStartColdStart.test.js +156 -0
  34. package/dist/__tests__/BrowserLaneIcon.test.d.ts +3 -0
  35. package/dist/__tests__/BrowserLaneIcon.test.js +80 -0
  36. package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
  37. package/dist/__tests__/DogfoodRuntime.test.js +116 -0
  38. package/dist/__tests__/P2PClient.test.js +2 -2
  39. package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
  40. package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
  41. package/dist/__tests__/ReportIdentity.test.d.ts +25 -0
  42. package/dist/__tests__/ReportIdentity.test.js +180 -0
  43. package/dist/__tests__/SDKToken.test.js +1 -1
  44. package/dist/__tests__/ShakeToggle.test.d.ts +1 -0
  45. package/dist/__tests__/ShakeToggle.test.js +121 -0
  46. package/dist/__tests__/YaverFeedback.test.js +13 -3
  47. package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
  48. package/dist/__tests__/deviceDogfood.test.js +86 -0
  49. package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
  50. package/dist/__tests__/dogfoodPolicy.test.js +33 -0
  51. package/dist/__tests__/pickTargetDevice.test.d.ts +1 -0
  52. package/dist/__tests__/pickTargetDevice.test.js +89 -0
  53. package/dist/__tests__/reloadActions.test.d.ts +1 -0
  54. package/dist/__tests__/reloadActions.test.js +129 -0
  55. package/dist/__tests__/reloadActionsParity.test.d.ts +1 -0
  56. package/dist/__tests__/reloadActionsParity.test.js +42 -0
  57. package/dist/__tests__/types.test.js +5 -5
  58. package/dist/_core/ansi.d.ts +117 -0
  59. package/dist/_core/ansi.js +468 -0
  60. package/dist/_core/ansi.test.d.ts +1 -0
  61. package/dist/_core/ansi.test.js +225 -0
  62. package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
  63. package/dist/_core/buildFeedbackPrompt.js +18 -72
  64. package/dist/_core/constants.d.ts +19 -6
  65. package/dist/_core/constants.js +20 -7
  66. package/dist/_core/device.d.ts +1 -5
  67. package/dist/_core/device.js +5 -14
  68. package/dist/_core/endpoints.d.ts +0 -7
  69. package/dist/_core/endpoints.js +0 -7
  70. package/dist/_core/index.d.ts +4 -0
  71. package/dist/_core/index.js +4 -0
  72. package/dist/_core/remoteless.d.ts +44 -0
  73. package/dist/_core/remoteless.js +75 -0
  74. package/dist/_core/trace.d.ts +47 -0
  75. package/dist/_core/trace.js +38 -0
  76. package/dist/_core/trace.test.d.ts +1 -0
  77. package/dist/_core/trace.test.js +60 -0
  78. package/dist/auth.d.ts +3 -60
  79. package/dist/auth.js +6 -88
  80. package/dist/deviceDogfood.d.ts +53 -0
  81. package/dist/deviceDogfood.js +137 -0
  82. package/dist/dogfoodPolicy.d.ts +25 -0
  83. package/dist/dogfoodPolicy.js +24 -0
  84. package/dist/index.d.ts +17 -4
  85. package/dist/index.js +33 -7
  86. package/dist/reloadActions.d.ts +88 -0
  87. package/dist/reloadActions.js +200 -0
  88. package/dist/storeShots.d.ts +67 -0
  89. package/dist/storeShots.js +137 -0
  90. package/dist/types.d.ts +208 -8
  91. package/package.json +13 -4
  92. package/src/AuthOverlay.tsx +20 -106
  93. package/src/BlackBox.ts +10 -1
  94. package/src/DeployPanel.tsx +74 -0
  95. package/src/Discovery.ts +13 -8
  96. package/src/DogfoodRuntime.ts +373 -0
  97. package/src/FeedbackModal.tsx +619 -79
  98. package/src/LoginScreen.tsx +2 -22
  99. package/src/MachinePickerScreen.tsx +6 -21
  100. package/src/P2PClient.ts +580 -4
  101. package/src/P2PDogfoodDriver.ts +132 -0
  102. package/src/PairDeviceModal.tsx +2 -3
  103. package/src/ShakeDetector.ts +1 -0
  104. package/src/VibeChatScreen.tsx +232 -42
  105. package/src/YaverFeedback.ts +631 -68
  106. package/src/YaverModeBadge.tsx +234 -0
  107. package/src/__tests__/AuthDevices.test.ts +1 -52
  108. package/src/__tests__/BlackBox.relayPassword.test.ts +129 -0
  109. package/src/__tests__/BlackBoxAutoStart.test.ts +111 -0
  110. package/src/__tests__/BlackBoxAutoStartColdStart.test.ts +191 -0
  111. package/src/__tests__/BrowserLaneIcon.test.ts +85 -0
  112. package/src/__tests__/DogfoodRuntime.test.ts +135 -0
  113. package/src/__tests__/P2PClient.test.ts +2 -2
  114. package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
  115. package/src/__tests__/ReportIdentity.test.ts +212 -0
  116. package/src/__tests__/SDKToken.test.ts +1 -1
  117. package/src/__tests__/ShakeToggle.test.ts +153 -0
  118. package/src/__tests__/YaverFeedback.test.ts +15 -3
  119. package/src/__tests__/deviceDogfood.test.ts +77 -0
  120. package/src/__tests__/dogfoodPolicy.test.ts +34 -0
  121. package/src/__tests__/pickTargetDevice.test.ts +101 -0
  122. package/src/__tests__/reloadActions.test.ts +171 -0
  123. package/src/__tests__/reloadActionsParity.test.ts +49 -0
  124. package/src/__tests__/types.test.ts +5 -5
  125. package/src/_core/ansi.test.ts +250 -0
  126. package/src/_core/ansi.ts +475 -0
  127. package/src/_core/buildFeedbackPrompt.ts +18 -95
  128. package/src/_core/constants.ts +20 -6
  129. package/src/_core/device.ts +5 -16
  130. package/src/_core/endpoints.ts +0 -7
  131. package/src/_core/index.ts +4 -0
  132. package/src/_core/remoteless.ts +110 -0
  133. package/src/_core/trace.test.ts +58 -0
  134. package/src/_core/trace.ts +75 -0
  135. package/src/auth.ts +7 -156
  136. package/src/deviceDogfood.ts +171 -0
  137. package/src/dogfoodPolicy.ts +40 -0
  138. package/src/index.ts +61 -11
  139. package/src/reloadActions.ts +273 -0
  140. package/src/storeShots.ts +189 -0
  141. package/src/types.ts +209 -8
  142. package/dist/GuestOnboardingScreen.d.ts +0 -8
  143. package/dist/GuestOnboardingScreen.js +0 -282
  144. package/src/GuestOnboardingScreen.tsx +0 -307
@@ -1,282 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.YaverGuestOnboardingScreen = void 0;
37
- const react_1 = __importStar(require("react"));
38
- const react_native_1 = require("react-native");
39
- const auth_1 = require("./auth");
40
- const YaverGuestOnboardingScreen = ({ token, initialInviteCode, onContinue, onCancel, }) => {
41
- const [code, setCode] = (0, react_1.useState)((initialInviteCode ?? '').toUpperCase());
42
- const [preview, setPreview] = (0, react_1.useState)(null);
43
- const [hosts, setHosts] = (0, react_1.useState)({ pending: [], active: [] });
44
- const [loading, setLoading] = (0, react_1.useState)(true);
45
- const [busy, setBusy] = (0, react_1.useState)(false);
46
- const [error, setError] = (0, react_1.useState)(null);
47
- const cleanedCode = (0, react_1.useMemo)(() => code.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 6), [code]);
48
- const loadHosts = (0, react_1.useCallback)(async () => {
49
- setLoading(true);
50
- setError(null);
51
- try {
52
- const result = await (0, auth_1.fetchGuestHosts)(token);
53
- setHosts(result);
54
- }
55
- catch (err) {
56
- setError(err instanceof Error ? err.message : String(err));
57
- }
58
- finally {
59
- setLoading(false);
60
- }
61
- }, [token]);
62
- (0, react_1.useEffect)(() => {
63
- void loadHosts();
64
- }, [loadHosts]);
65
- (0, react_1.useEffect)(() => {
66
- let cancelled = false;
67
- if (cleanedCode.length !== 6) {
68
- setPreview(null);
69
- return;
70
- }
71
- (async () => {
72
- try {
73
- const result = await (0, auth_1.findInviteByCode)(token, cleanedCode);
74
- if (!cancelled) {
75
- setPreview(result);
76
- if (!result) {
77
- setError('Invite code not found or expired.');
78
- }
79
- else {
80
- setError(null);
81
- }
82
- }
83
- }
84
- catch (err) {
85
- if (!cancelled) {
86
- setPreview(null);
87
- setError(err instanceof Error ? err.message : String(err));
88
- }
89
- }
90
- })();
91
- return () => {
92
- cancelled = true;
93
- };
94
- }, [cleanedCode, token]);
95
- const handleAcceptCode = (0, react_1.useCallback)(async () => {
96
- if (cleanedCode.length !== 6) {
97
- setError('Enter the 6-character invite code from the host.');
98
- return;
99
- }
100
- setBusy(true);
101
- setError(null);
102
- try {
103
- await (0, auth_1.acceptGuestByCode)(token, cleanedCode, preview?.proposedDeviceIds);
104
- await loadHosts();
105
- onContinue();
106
- }
107
- catch (err) {
108
- setError(err instanceof Error ? err.message : String(err));
109
- }
110
- finally {
111
- setBusy(false);
112
- }
113
- }, [cleanedCode, loadHosts, onContinue, preview?.proposedDeviceIds, token]);
114
- const handleAcceptPending = (0, react_1.useCallback)(async (hostUserId) => {
115
- setBusy(true);
116
- setError(null);
117
- try {
118
- await (0, auth_1.acceptGuestInvitation)(token, hostUserId);
119
- await loadHosts();
120
- onContinue();
121
- }
122
- catch (err) {
123
- setError(err instanceof Error ? err.message : String(err));
124
- }
125
- finally {
126
- setBusy(false);
127
- }
128
- }, [loadHosts, onContinue, token]);
129
- return (<react_native_1.SafeAreaView style={styles.container}>
130
- <react_native_1.ScrollView contentContainerStyle={styles.content} keyboardShouldPersistTaps="handled">
131
- <react_native_1.View style={styles.header}>
132
- <react_native_1.View>
133
- <react_native_1.Text style={styles.title}>Guest Access</react_native_1.Text>
134
- <react_native_1.Text style={styles.subtitle}>
135
- Join a host&apos;s repo-scoped Feedback SDK access without leaving the app.
136
- </react_native_1.Text>
137
- </react_native_1.View>
138
- {onCancel && (<react_native_1.Pressable onPress={onCancel} style={styles.skipBtn}>
139
- <react_native_1.Text style={styles.skipText}>Skip</react_native_1.Text>
140
- </react_native_1.Pressable>)}
141
- </react_native_1.View>
142
-
143
- <react_native_1.View style={styles.card}>
144
- <react_native_1.Text style={styles.cardTitle}>Have an invite code?</react_native_1.Text>
145
- <react_native_1.Text style={styles.cardText}>
146
- Enter the 6-character code from the host. After redemption you can pick the shared machine directly here.
147
- </react_native_1.Text>
148
- <react_native_1.TextInput style={styles.input} value={cleanedCode} onChangeText={setCode} placeholder="ABC123" placeholderTextColor="#667085" autoCapitalize="characters" autoCorrect={false} maxLength={6}/>
149
- {preview && (<react_native_1.View style={styles.previewBox}>
150
- <react_native_1.Text style={styles.previewTitle}>{preview.hostName}</react_native_1.Text>
151
- <react_native_1.Text style={styles.previewMeta}>{preview.hostEmail}</react_native_1.Text>
152
- <react_native_1.Text style={styles.previewMeta}>
153
- {preview.hostDevices.length > 0
154
- ? `${preview.hostDevices.length} host machine${preview.hostDevices.length === 1 ? '' : 's'} shared`
155
- : 'Host machine list will appear after acceptance'}
156
- </react_native_1.Text>
157
- </react_native_1.View>)}
158
- <react_native_1.Pressable onPress={() => void handleAcceptCode()} disabled={busy || cleanedCode.length !== 6} style={({ pressed }) => [
159
- styles.primaryBtn,
160
- (pressed || busy || cleanedCode.length !== 6) && styles.primaryBtnPressed,
161
- ]}>
162
- {busy ? <react_native_1.ActivityIndicator color="#fff"/> : <react_native_1.Text style={styles.primaryBtnText}>Redeem Code</react_native_1.Text>}
163
- </react_native_1.Pressable>
164
- </react_native_1.View>
165
-
166
- <react_native_1.View style={styles.card}>
167
- <react_native_1.Text style={styles.cardTitle}>Pending host invites</react_native_1.Text>
168
- <react_native_1.Text style={styles.cardText}>
169
- If a host invited your email directly, it should appear here after you sign up.
170
- </react_native_1.Text>
171
- {loading ? (<react_native_1.ActivityIndicator color="#98a2b3" style={{ marginTop: 12 }}/>) : hosts.pending.length > 0 ? (hosts.pending.map((invite) => (<react_native_1.View key={`${invite.hostUserId}:${invite.createdAt}`} style={styles.inviteRow}>
172
- <react_native_1.View style={{ flex: 1 }}>
173
- <react_native_1.Text style={styles.inviteName}>{invite.hostName}</react_native_1.Text>
174
- <react_native_1.Text style={styles.inviteMeta}>{invite.hostEmail}</react_native_1.Text>
175
- </react_native_1.View>
176
- <react_native_1.Pressable onPress={() => void handleAcceptPending(invite.hostUserId)} disabled={busy} style={({ pressed }) => [
177
- styles.secondaryBtn,
178
- (pressed || busy) && styles.secondaryBtnPressed,
179
- ]}>
180
- <react_native_1.Text style={styles.secondaryBtnText}>Accept</react_native_1.Text>
181
- </react_native_1.Pressable>
182
- </react_native_1.View>))) : (<react_native_1.Text style={styles.emptyText}>No pending host invites on this account yet.</react_native_1.Text>)}
183
- </react_native_1.View>
184
-
185
- <react_native_1.View style={styles.cardMuted}>
186
- <react_native_1.Text style={styles.cardTitle}>No machine yet?</react_native_1.Text>
187
- <react_native_1.Text style={styles.cardText}>
188
- That is fine. You can still sign up here and redeem the host&apos;s guest access. Shared machines will appear once the host has one running. Later you can install Yaver on your own machine too.
189
- </react_native_1.Text>
190
- </react_native_1.View>
191
-
192
- {error ? <react_native_1.Text style={styles.error}>{error}</react_native_1.Text> : null}
193
-
194
- <react_native_1.Pressable onPress={onContinue} style={({ pressed }) => [styles.linkBtn, pressed && { opacity: 0.7 }]}>
195
- <react_native_1.Text style={styles.linkBtnText}>Continue to machine picker</react_native_1.Text>
196
- </react_native_1.Pressable>
197
- </react_native_1.ScrollView>
198
- </react_native_1.SafeAreaView>);
199
- };
200
- exports.YaverGuestOnboardingScreen = YaverGuestOnboardingScreen;
201
- const styles = react_native_1.StyleSheet.create({
202
- container: { flex: 1, backgroundColor: '#0f172a' },
203
- content: { padding: 20, gap: 16 },
204
- header: {
205
- flexDirection: 'row',
206
- justifyContent: 'space-between',
207
- alignItems: 'flex-start',
208
- marginBottom: 8,
209
- },
210
- title: { color: '#f8fafc', fontSize: 24, fontWeight: '700' },
211
- subtitle: { color: '#94a3b8', fontSize: 14, marginTop: 6, maxWidth: 280 },
212
- skipBtn: { paddingVertical: 6, paddingHorizontal: 10 },
213
- skipText: { color: '#cbd5e1', fontSize: 13, fontWeight: '600' },
214
- card: {
215
- backgroundColor: 'rgba(15,23,42,0.82)',
216
- borderWidth: 1,
217
- borderColor: 'rgba(148,163,184,0.18)',
218
- borderRadius: 16,
219
- padding: 16,
220
- },
221
- cardMuted: {
222
- backgroundColor: 'rgba(30,41,59,0.9)',
223
- borderRadius: 16,
224
- padding: 16,
225
- },
226
- cardTitle: { color: '#e2e8f0', fontSize: 16, fontWeight: '700' },
227
- cardText: { color: '#94a3b8', fontSize: 13, lineHeight: 20, marginTop: 8 },
228
- input: {
229
- marginTop: 14,
230
- borderWidth: 1,
231
- borderColor: 'rgba(148,163,184,0.28)',
232
- borderRadius: 12,
233
- paddingHorizontal: 14,
234
- paddingVertical: 12,
235
- backgroundColor: 'rgba(2,6,23,0.88)',
236
- color: '#f8fafc',
237
- fontSize: 18,
238
- letterSpacing: 3,
239
- textAlign: 'center',
240
- },
241
- previewBox: {
242
- marginTop: 12,
243
- padding: 12,
244
- borderRadius: 12,
245
- backgroundColor: 'rgba(30,41,59,0.75)',
246
- },
247
- previewTitle: { color: '#f8fafc', fontSize: 15, fontWeight: '600' },
248
- previewMeta: { color: '#94a3b8', fontSize: 12, marginTop: 4 },
249
- primaryBtn: {
250
- marginTop: 14,
251
- backgroundColor: '#2563eb',
252
- borderRadius: 12,
253
- minHeight: 46,
254
- alignItems: 'center',
255
- justifyContent: 'center',
256
- },
257
- primaryBtnPressed: { opacity: 0.6 },
258
- primaryBtnText: { color: '#fff', fontSize: 15, fontWeight: '700' },
259
- inviteRow: {
260
- flexDirection: 'row',
261
- alignItems: 'center',
262
- gap: 12,
263
- marginTop: 12,
264
- paddingTop: 12,
265
- borderTopWidth: 1,
266
- borderTopColor: 'rgba(148,163,184,0.12)',
267
- },
268
- inviteName: { color: '#f8fafc', fontSize: 14, fontWeight: '600' },
269
- inviteMeta: { color: '#94a3b8', fontSize: 12, marginTop: 4 },
270
- secondaryBtn: {
271
- paddingHorizontal: 12,
272
- paddingVertical: 10,
273
- borderRadius: 10,
274
- backgroundColor: 'rgba(37,99,235,0.16)',
275
- },
276
- secondaryBtnPressed: { opacity: 0.6 },
277
- secondaryBtnText: { color: '#bfdbfe', fontSize: 13, fontWeight: '700' },
278
- emptyText: { color: '#64748b', fontSize: 13, marginTop: 12 },
279
- error: { color: '#f87171', fontSize: 13, marginTop: 4 },
280
- linkBtn: { alignItems: 'center', paddingVertical: 8 },
281
- linkBtnText: { color: '#cbd5e1', fontSize: 14, fontWeight: '600' },
282
- });
@@ -1,307 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import {
3
- ActivityIndicator,
4
- Pressable,
5
- SafeAreaView,
6
- ScrollView,
7
- StyleSheet,
8
- Text,
9
- TextInput,
10
- View,
11
- } from 'react-native';
12
- import {
13
- acceptGuestByCode,
14
- acceptGuestInvitation,
15
- fetchGuestHosts,
16
- findInviteByCode,
17
- type GuestHostsResponse,
18
- type InvitationPreview,
19
- } from './auth';
20
-
21
- export interface YaverGuestOnboardingScreenProps {
22
- token: string;
23
- initialInviteCode?: string;
24
- onContinue: () => void;
25
- onCancel?: () => void;
26
- }
27
-
28
- export const YaverGuestOnboardingScreen: React.FC<YaverGuestOnboardingScreenProps> = ({
29
- token,
30
- initialInviteCode,
31
- onContinue,
32
- onCancel,
33
- }) => {
34
- const [code, setCode] = useState((initialInviteCode ?? '').toUpperCase());
35
- const [preview, setPreview] = useState<InvitationPreview | null>(null);
36
- const [hosts, setHosts] = useState<GuestHostsResponse>({ pending: [], active: [] });
37
- const [loading, setLoading] = useState(true);
38
- const [busy, setBusy] = useState(false);
39
- const [error, setError] = useState<string | null>(null);
40
- const cleanedCode = useMemo(() => code.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 6), [code]);
41
-
42
- const loadHosts = useCallback(async () => {
43
- setLoading(true);
44
- setError(null);
45
- try {
46
- const result = await fetchGuestHosts(token);
47
- setHosts(result);
48
- } catch (err) {
49
- setError(err instanceof Error ? err.message : String(err));
50
- } finally {
51
- setLoading(false);
52
- }
53
- }, [token]);
54
-
55
- useEffect(() => {
56
- void loadHosts();
57
- }, [loadHosts]);
58
-
59
- useEffect(() => {
60
- let cancelled = false;
61
- if (cleanedCode.length !== 6) {
62
- setPreview(null);
63
- return;
64
- }
65
- (async () => {
66
- try {
67
- const result = await findInviteByCode(token, cleanedCode);
68
- if (!cancelled) {
69
- setPreview(result);
70
- if (!result) {
71
- setError('Invite code not found or expired.');
72
- } else {
73
- setError(null);
74
- }
75
- }
76
- } catch (err) {
77
- if (!cancelled) {
78
- setPreview(null);
79
- setError(err instanceof Error ? err.message : String(err));
80
- }
81
- }
82
- })();
83
- return () => {
84
- cancelled = true;
85
- };
86
- }, [cleanedCode, token]);
87
-
88
- const handleAcceptCode = useCallback(async () => {
89
- if (cleanedCode.length !== 6) {
90
- setError('Enter the 6-character invite code from the host.');
91
- return;
92
- }
93
- setBusy(true);
94
- setError(null);
95
- try {
96
- await acceptGuestByCode(token, cleanedCode, preview?.proposedDeviceIds);
97
- await loadHosts();
98
- onContinue();
99
- } catch (err) {
100
- setError(err instanceof Error ? err.message : String(err));
101
- } finally {
102
- setBusy(false);
103
- }
104
- }, [cleanedCode, loadHosts, onContinue, preview?.proposedDeviceIds, token]);
105
-
106
- const handleAcceptPending = useCallback(async (hostUserId: string) => {
107
- setBusy(true);
108
- setError(null);
109
- try {
110
- await acceptGuestInvitation(token, hostUserId);
111
- await loadHosts();
112
- onContinue();
113
- } catch (err) {
114
- setError(err instanceof Error ? err.message : String(err));
115
- } finally {
116
- setBusy(false);
117
- }
118
- }, [loadHosts, onContinue, token]);
119
-
120
- return (
121
- <SafeAreaView style={styles.container}>
122
- <ScrollView contentContainerStyle={styles.content} keyboardShouldPersistTaps="handled">
123
- <View style={styles.header}>
124
- <View>
125
- <Text style={styles.title}>Guest Access</Text>
126
- <Text style={styles.subtitle}>
127
- Join a host&apos;s repo-scoped Feedback SDK access without leaving the app.
128
- </Text>
129
- </View>
130
- {onCancel && (
131
- <Pressable onPress={onCancel} style={styles.skipBtn}>
132
- <Text style={styles.skipText}>Skip</Text>
133
- </Pressable>
134
- )}
135
- </View>
136
-
137
- <View style={styles.card}>
138
- <Text style={styles.cardTitle}>Have an invite code?</Text>
139
- <Text style={styles.cardText}>
140
- Enter the 6-character code from the host. After redemption you can pick the shared machine directly here.
141
- </Text>
142
- <TextInput
143
- style={styles.input}
144
- value={cleanedCode}
145
- onChangeText={setCode}
146
- placeholder="ABC123"
147
- placeholderTextColor="#667085"
148
- autoCapitalize="characters"
149
- autoCorrect={false}
150
- maxLength={6}
151
- />
152
- {preview && (
153
- <View style={styles.previewBox}>
154
- <Text style={styles.previewTitle}>{preview.hostName}</Text>
155
- <Text style={styles.previewMeta}>{preview.hostEmail}</Text>
156
- <Text style={styles.previewMeta}>
157
- {preview.hostDevices.length > 0
158
- ? `${preview.hostDevices.length} host machine${preview.hostDevices.length === 1 ? '' : 's'} shared`
159
- : 'Host machine list will appear after acceptance'}
160
- </Text>
161
- </View>
162
- )}
163
- <Pressable
164
- onPress={() => void handleAcceptCode()}
165
- disabled={busy || cleanedCode.length !== 6}
166
- style={({ pressed }) => [
167
- styles.primaryBtn,
168
- (pressed || busy || cleanedCode.length !== 6) && styles.primaryBtnPressed,
169
- ]}
170
- >
171
- {busy ? <ActivityIndicator color="#fff" /> : <Text style={styles.primaryBtnText}>Redeem Code</Text>}
172
- </Pressable>
173
- </View>
174
-
175
- <View style={styles.card}>
176
- <Text style={styles.cardTitle}>Pending host invites</Text>
177
- <Text style={styles.cardText}>
178
- If a host invited your email directly, it should appear here after you sign up.
179
- </Text>
180
- {loading ? (
181
- <ActivityIndicator color="#98a2b3" style={{ marginTop: 12 }} />
182
- ) : hosts.pending.length > 0 ? (
183
- hosts.pending.map((invite) => (
184
- <View key={`${invite.hostUserId}:${invite.createdAt}`} style={styles.inviteRow}>
185
- <View style={{ flex: 1 }}>
186
- <Text style={styles.inviteName}>{invite.hostName}</Text>
187
- <Text style={styles.inviteMeta}>{invite.hostEmail}</Text>
188
- </View>
189
- <Pressable
190
- onPress={() => void handleAcceptPending(invite.hostUserId)}
191
- disabled={busy}
192
- style={({ pressed }) => [
193
- styles.secondaryBtn,
194
- (pressed || busy) && styles.secondaryBtnPressed,
195
- ]}
196
- >
197
- <Text style={styles.secondaryBtnText}>Accept</Text>
198
- </Pressable>
199
- </View>
200
- ))
201
- ) : (
202
- <Text style={styles.emptyText}>No pending host invites on this account yet.</Text>
203
- )}
204
- </View>
205
-
206
- <View style={styles.cardMuted}>
207
- <Text style={styles.cardTitle}>No machine yet?</Text>
208
- <Text style={styles.cardText}>
209
- That is fine. You can still sign up here and redeem the host&apos;s guest access. Shared machines will appear once the host has one running. Later you can install Yaver on your own machine too.
210
- </Text>
211
- </View>
212
-
213
- {error ? <Text style={styles.error}>{error}</Text> : null}
214
-
215
- <Pressable
216
- onPress={onContinue}
217
- style={({ pressed }) => [styles.linkBtn, pressed && { opacity: 0.7 }]}
218
- >
219
- <Text style={styles.linkBtnText}>Continue to machine picker</Text>
220
- </Pressable>
221
- </ScrollView>
222
- </SafeAreaView>
223
- );
224
- };
225
-
226
- const styles = StyleSheet.create({
227
- container: { flex: 1, backgroundColor: '#0f172a' },
228
- content: { padding: 20, gap: 16 },
229
- header: {
230
- flexDirection: 'row',
231
- justifyContent: 'space-between',
232
- alignItems: 'flex-start',
233
- marginBottom: 8,
234
- },
235
- title: { color: '#f8fafc', fontSize: 24, fontWeight: '700' },
236
- subtitle: { color: '#94a3b8', fontSize: 14, marginTop: 6, maxWidth: 280 },
237
- skipBtn: { paddingVertical: 6, paddingHorizontal: 10 },
238
- skipText: { color: '#cbd5e1', fontSize: 13, fontWeight: '600' },
239
- card: {
240
- backgroundColor: 'rgba(15,23,42,0.82)',
241
- borderWidth: 1,
242
- borderColor: 'rgba(148,163,184,0.18)',
243
- borderRadius: 16,
244
- padding: 16,
245
- },
246
- cardMuted: {
247
- backgroundColor: 'rgba(30,41,59,0.9)',
248
- borderRadius: 16,
249
- padding: 16,
250
- },
251
- cardTitle: { color: '#e2e8f0', fontSize: 16, fontWeight: '700' },
252
- cardText: { color: '#94a3b8', fontSize: 13, lineHeight: 20, marginTop: 8 },
253
- input: {
254
- marginTop: 14,
255
- borderWidth: 1,
256
- borderColor: 'rgba(148,163,184,0.28)',
257
- borderRadius: 12,
258
- paddingHorizontal: 14,
259
- paddingVertical: 12,
260
- backgroundColor: 'rgba(2,6,23,0.88)',
261
- color: '#f8fafc',
262
- fontSize: 18,
263
- letterSpacing: 3,
264
- textAlign: 'center',
265
- },
266
- previewBox: {
267
- marginTop: 12,
268
- padding: 12,
269
- borderRadius: 12,
270
- backgroundColor: 'rgba(30,41,59,0.75)',
271
- },
272
- previewTitle: { color: '#f8fafc', fontSize: 15, fontWeight: '600' },
273
- previewMeta: { color: '#94a3b8', fontSize: 12, marginTop: 4 },
274
- primaryBtn: {
275
- marginTop: 14,
276
- backgroundColor: '#2563eb',
277
- borderRadius: 12,
278
- minHeight: 46,
279
- alignItems: 'center',
280
- justifyContent: 'center',
281
- },
282
- primaryBtnPressed: { opacity: 0.6 },
283
- primaryBtnText: { color: '#fff', fontSize: 15, fontWeight: '700' },
284
- inviteRow: {
285
- flexDirection: 'row',
286
- alignItems: 'center',
287
- gap: 12,
288
- marginTop: 12,
289
- paddingTop: 12,
290
- borderTopWidth: 1,
291
- borderTopColor: 'rgba(148,163,184,0.12)',
292
- },
293
- inviteName: { color: '#f8fafc', fontSize: 14, fontWeight: '600' },
294
- inviteMeta: { color: '#94a3b8', fontSize: 12, marginTop: 4 },
295
- secondaryBtn: {
296
- paddingHorizontal: 12,
297
- paddingVertical: 10,
298
- borderRadius: 10,
299
- backgroundColor: 'rgba(37,99,235,0.16)',
300
- },
301
- secondaryBtnPressed: { opacity: 0.6 },
302
- secondaryBtnText: { color: '#bfdbfe', fontSize: 13, fontWeight: '700' },
303
- emptyText: { color: '#64748b', fontSize: 13, marginTop: 12 },
304
- error: { color: '#f87171', fontSize: 13, marginTop: 4 },
305
- linkBtn: { alignItems: 'center', paddingVertical: 8 },
306
- linkBtnText: { color: '#cbd5e1', fontSize: 14, fontWeight: '600' },
307
- });