yaver-feedback-react-native 0.8.2 → 0.8.4
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/dist/AuthOverlay.js +32 -2
- package/dist/Discovery.js +2 -0
- package/dist/FeedbackModal.js +48 -3
- package/dist/GuestOnboardingScreen.d.ts +8 -0
- package/dist/GuestOnboardingScreen.js +282 -0
- package/dist/LoginScreen.d.ts +5 -1
- package/dist/LoginScreen.js +24 -7
- package/dist/MachinePickerScreen.js +45 -7
- package/dist/P2PClient.d.ts +13 -0
- package/dist/P2PClient.js +22 -0
- package/dist/YaverFeedback.d.ts +2 -0
- package/dist/YaverFeedback.js +46 -4
- package/dist/auth.d.ts +58 -0
- package/dist/auth.js +116 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7 -1
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
- package/src/AuthOverlay.tsx +46 -2
- package/src/Discovery.ts +2 -0
- package/src/FeedbackModal.tsx +51 -2
- package/src/GuestOnboardingScreen.tsx +307 -0
- package/src/LoginScreen.tsx +40 -7
- package/src/MachinePickerScreen.tsx +48 -7
- package/src/P2PClient.ts +33 -0
- package/src/YaverFeedback.ts +50 -4
- package/src/auth.ts +183 -0
- package/src/index.ts +11 -0
- package/src/types.ts +7 -0
package/dist/AuthOverlay.js
CHANGED
|
@@ -38,6 +38,7 @@ const react_1 = __importStar(require("react"));
|
|
|
38
38
|
const react_native_1 = require("react-native");
|
|
39
39
|
const LoginScreen_1 = require("./LoginScreen");
|
|
40
40
|
const MachinePickerScreen_1 = require("./MachinePickerScreen");
|
|
41
|
+
const GuestOnboardingScreen_1 = require("./GuestOnboardingScreen");
|
|
41
42
|
const YaverFeedback_1 = require("./YaverFeedback");
|
|
42
43
|
const auth_1 = require("./auth");
|
|
43
44
|
/**
|
|
@@ -56,8 +57,10 @@ const auth_1 = require("./auth");
|
|
|
56
57
|
*/
|
|
57
58
|
const AuthOverlay = () => {
|
|
58
59
|
const [loginVisible, setLoginVisible] = (0, react_1.useState)(false);
|
|
60
|
+
const [guestVisible, setGuestVisible] = (0, react_1.useState)(false);
|
|
59
61
|
const [pickerVisible, setPickerVisible] = (0, react_1.useState)(false);
|
|
60
62
|
const [token, setToken] = (0, react_1.useState)(null);
|
|
63
|
+
const [pendingInviteCode, setPendingInviteCode] = (0, react_1.useState)(null);
|
|
61
64
|
(0, react_1.useEffect)(() => {
|
|
62
65
|
let mounted = true;
|
|
63
66
|
(async () => {
|
|
@@ -79,26 +82,53 @@ const AuthOverlay = () => {
|
|
|
79
82
|
pickerSub.remove();
|
|
80
83
|
};
|
|
81
84
|
}, []);
|
|
82
|
-
const
|
|
85
|
+
const continueAfterAuth = async (newToken, inviteCode) => {
|
|
83
86
|
setToken(newToken);
|
|
84
87
|
await YaverFeedback_1.YaverFeedback.setAuthToken(newToken);
|
|
88
|
+
const devices = await (0, auth_1.listReachableDevices)(newToken).catch(() => ({ owned: [], shared: [] }));
|
|
85
89
|
setLoginVisible(false);
|
|
90
|
+
const cleanedInviteCode = (inviteCode ?? '').trim().toUpperCase();
|
|
91
|
+
if (cleanedInviteCode) {
|
|
92
|
+
setPendingInviteCode(cleanedInviteCode);
|
|
93
|
+
setGuestVisible(true);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (devices.owned.length === 0 && devices.shared.length === 0) {
|
|
97
|
+
setGuestVisible(true);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
86
100
|
setPickerVisible(true);
|
|
87
101
|
};
|
|
102
|
+
const handleLoggedIn = async (newToken, opts) => {
|
|
103
|
+
await continueAfterAuth(newToken, opts?.inviteCode);
|
|
104
|
+
};
|
|
88
105
|
const handleDevicePicked = async (device) => {
|
|
89
106
|
await YaverFeedback_1.YaverFeedback.setPreferredDevice(device.deviceId);
|
|
90
107
|
setPickerVisible(false);
|
|
108
|
+
setGuestVisible(false);
|
|
91
109
|
// Continue straight into the feedback flow the user originally triggered.
|
|
92
110
|
react_native_1.DeviceEventEmitter.emit('yaverFeedback:startReport');
|
|
93
111
|
};
|
|
94
112
|
return (<>
|
|
95
113
|
<react_native_1.Modal visible={loginVisible} animationType="slide" presentationStyle="fullScreen" onRequestClose={() => setLoginVisible(false)}>
|
|
96
|
-
<LoginScreen_1.YaverLoginScreen onLoggedIn={handleLoggedIn} onCancel={() => setLoginVisible(false)}/>
|
|
114
|
+
<LoginScreen_1.YaverLoginScreen onLoggedIn={handleLoggedIn} onCancel={() => setLoginVisible(false)} initialInviteCode={pendingInviteCode ?? YaverFeedback_1.YaverFeedback.getConfig()?.guestInviteCode}/>
|
|
97
115
|
</react_native_1.Modal>
|
|
98
116
|
|
|
99
117
|
<react_native_1.Modal visible={pickerVisible && !!token} animationType="slide" presentationStyle="fullScreen" onRequestClose={() => setPickerVisible(false)}>
|
|
100
118
|
{token && (<MachinePickerScreen_1.YaverMachinePickerScreen token={token} currentDeviceId={YaverFeedback_1.YaverFeedback.getConfig()?.preferredDeviceId} onPick={handleDevicePicked} onCancel={() => setPickerVisible(false)}/>)}
|
|
101
119
|
</react_native_1.Modal>
|
|
120
|
+
|
|
121
|
+
<react_native_1.Modal visible={guestVisible && !!token} animationType="slide" presentationStyle="fullScreen" onRequestClose={() => setGuestVisible(false)}>
|
|
122
|
+
{token && (<GuestOnboardingScreen_1.YaverGuestOnboardingScreen token={token} initialInviteCode={pendingInviteCode ?? YaverFeedback_1.YaverFeedback.getConfig()?.guestInviteCode} onContinue={() => {
|
|
123
|
+
setGuestVisible(false);
|
|
124
|
+
setPendingInviteCode(null);
|
|
125
|
+
setPickerVisible(true);
|
|
126
|
+
}} onCancel={() => {
|
|
127
|
+
setGuestVisible(false);
|
|
128
|
+
setPendingInviteCode(null);
|
|
129
|
+
setPickerVisible(true);
|
|
130
|
+
}}/>)}
|
|
131
|
+
</react_native_1.Modal>
|
|
102
132
|
</>);
|
|
103
133
|
};
|
|
104
134
|
exports.AuthOverlay = AuthOverlay;
|
package/dist/Discovery.js
CHANGED
|
@@ -174,8 +174,10 @@ class YaverDiscovery {
|
|
|
174
174
|
runnerDown: !!d.runnerDown,
|
|
175
175
|
lastHeartbeat: d.lastHeartbeat ?? 0,
|
|
176
176
|
isGuest: !!d.isGuest,
|
|
177
|
+
hostUserId: d.hostUserId,
|
|
177
178
|
hostName: d.hostName,
|
|
178
179
|
hostEmail: d.hostEmail,
|
|
180
|
+
hostUserIdString: d.hostUserIdString,
|
|
179
181
|
accessScope: d.accessScope ?? 'owner',
|
|
180
182
|
quicHost: d.quicHost ?? d.host ?? '',
|
|
181
183
|
quicPort: d.quicPort ?? 0,
|
package/dist/FeedbackModal.js
CHANGED
|
@@ -63,6 +63,7 @@ const FeedbackModal = () => {
|
|
|
63
63
|
const [vibePrompt, setVibePrompt] = (0, react_1.useState)('');
|
|
64
64
|
const [lastVibeTaskId, setLastVibeTaskId] = (0, react_1.useState)(null);
|
|
65
65
|
const [quickIconColorPreset, setQuickIconColorPreset] = (0, react_1.useState)(null);
|
|
66
|
+
const [keyboardInset, setKeyboardInset] = (0, react_1.useState)(0);
|
|
66
67
|
const [machineCard, setMachineCard] = (0, react_1.useState)({
|
|
67
68
|
device: null,
|
|
68
69
|
reachable: null,
|
|
@@ -231,6 +232,24 @@ const FeedbackModal = () => {
|
|
|
231
232
|
}, 5000);
|
|
232
233
|
return () => clearInterval(interval);
|
|
233
234
|
}, [loadSelectedMachine, visible]);
|
|
235
|
+
(0, react_1.useEffect)(() => {
|
|
236
|
+
if (!visible) {
|
|
237
|
+
setKeyboardInset(0);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const showEvent = react_native_1.Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
241
|
+
const hideEvent = react_native_1.Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
242
|
+
const showSub = react_native_1.Keyboard.addListener(showEvent, (event) => {
|
|
243
|
+
setKeyboardInset(event.endCoordinates?.height ?? 0);
|
|
244
|
+
});
|
|
245
|
+
const hideSub = react_native_1.Keyboard.addListener(hideEvent, () => {
|
|
246
|
+
setKeyboardInset(0);
|
|
247
|
+
});
|
|
248
|
+
return () => {
|
|
249
|
+
showSub.remove();
|
|
250
|
+
hideSub.remove();
|
|
251
|
+
};
|
|
252
|
+
}, [visible]);
|
|
234
253
|
const closeSoon = (0, react_1.useCallback)((delayMs = 1200) => {
|
|
235
254
|
setTimeout(() => {
|
|
236
255
|
if (mountedRef.current)
|
|
@@ -437,8 +456,29 @@ const FeedbackModal = () => {
|
|
|
437
456
|
// user types what they want, hits Send, sees the task id back. If
|
|
438
457
|
// left blank, we default to "pick the next small improvement"
|
|
439
458
|
// so a one-tap workflow still works for lazy days.
|
|
440
|
-
const handleVibingButton = (0, react_1.useCallback)(() => {
|
|
459
|
+
const handleVibingButton = (0, react_1.useCallback)(async () => {
|
|
441
460
|
if (!showVibeInput) {
|
|
461
|
+
const client = YaverFeedback_1.YaverFeedback.getP2PClient();
|
|
462
|
+
if (!client) {
|
|
463
|
+
setError('Not connected to the agent yet.');
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
setError(null);
|
|
467
|
+
try {
|
|
468
|
+
const eligibility = await client.getVibingEligibility();
|
|
469
|
+
if (!eligibility.canVibe) {
|
|
470
|
+
const message = eligibility.guidance && eligibility.guidance.trim()
|
|
471
|
+
? `${eligibility.reason ?? 'Vibe coding is unavailable.'} ${eligibility.guidance}`
|
|
472
|
+
: eligibility.reason ?? 'Vibe coding is unavailable.';
|
|
473
|
+
setError(message);
|
|
474
|
+
setToast('Vibe coding unavailable for this project.');
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
catch (err) {
|
|
479
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
442
482
|
setShowVibeInput(true);
|
|
443
483
|
return;
|
|
444
484
|
}
|
|
@@ -494,12 +534,17 @@ const FeedbackModal = () => {
|
|
|
494
534
|
<QuickActionIcon_1.QuickActionIcon />
|
|
495
535
|
{visible && (<react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={handleClose}>
|
|
496
536
|
<react_native_1.Pressable style={styles.overlay} onPress={handleClose}>
|
|
497
|
-
<react_native_1.KeyboardAvoidingView behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.kbAvoider} pointerEvents="box-none">
|
|
537
|
+
<react_native_1.KeyboardAvoidingView behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={react_native_1.Platform.OS === 'ios' ? 12 : 0} style={styles.kbAvoider} pointerEvents="box-none">
|
|
498
538
|
<react_native_1.Pressable style={styles.modal} onPress={(e) => {
|
|
499
539
|
e.stopPropagation();
|
|
500
540
|
react_native_1.Keyboard.dismiss();
|
|
501
541
|
}}>
|
|
502
|
-
<react_native_1.ScrollView style={styles.scroll} contentContainerStyle={
|
|
542
|
+
<react_native_1.ScrollView style={styles.scroll} contentContainerStyle={[
|
|
543
|
+
styles.scrollContent,
|
|
544
|
+
showVibeInput && keyboardInset > 0
|
|
545
|
+
? { paddingBottom: 8 + keyboardInset }
|
|
546
|
+
: null,
|
|
547
|
+
]} keyboardShouldPersistTaps="handled" keyboardDismissMode={react_native_1.Platform.OS === 'ios' ? 'interactive' : 'on-drag'}>
|
|
503
548
|
<react_native_1.View style={styles.header}>
|
|
504
549
|
<react_native_1.Text style={styles.title}>Send Feedback</react_native_1.Text>
|
|
505
550
|
<react_native_1.Pressable onPress={handleClose} hitSlop={12} style={styles.closeBtn} accessibilityRole="button" accessibilityLabel="Close">
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface YaverGuestOnboardingScreenProps {
|
|
3
|
+
token: string;
|
|
4
|
+
initialInviteCode?: string;
|
|
5
|
+
onContinue: () => void;
|
|
6
|
+
onCancel?: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const YaverGuestOnboardingScreen: React.FC<YaverGuestOnboardingScreenProps>;
|
|
@@ -0,0 +1,282 @@
|
|
|
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'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'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
|
+
});
|
package/dist/LoginScreen.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export interface YaverLoginScreenProps {
|
|
3
3
|
/** Invoked once a session token is issued and the user is loaded. */
|
|
4
|
-
onLoggedIn: (token: string
|
|
4
|
+
onLoggedIn: (token: string, opts?: {
|
|
5
|
+
inviteCode?: string;
|
|
6
|
+
}) => void;
|
|
5
7
|
/** Optional cancel button shown in header. */
|
|
6
8
|
onCancel?: () => void;
|
|
9
|
+
/** Optional prefilled guest invite code from config / deep link. */
|
|
10
|
+
initialInviteCode?: string;
|
|
7
11
|
}
|
|
8
12
|
/**
|
|
9
13
|
* Full-screen in-SDK login. Mirrors the Yaver mobile app login UX: native
|
package/dist/LoginScreen.js
CHANGED
|
@@ -91,7 +91,7 @@ const iconStyles = react_native_1.StyleSheet.create({
|
|
|
91
91
|
* Apple Sign-In on iOS, in-app browser OAuth for Google/GitHub/GitLab/
|
|
92
92
|
* Microsoft (no codes, no leaving the app), and inline email/password.
|
|
93
93
|
*/
|
|
94
|
-
const YaverLoginScreen = ({ onLoggedIn, onCancel, }) => {
|
|
94
|
+
const YaverLoginScreen = ({ onLoggedIn, onCancel, initialInviteCode, }) => {
|
|
95
95
|
const [busyProvider, setBusyProvider] = (0, react_1.useState)(null);
|
|
96
96
|
const [showEmailForm, setShowEmailForm] = (0, react_1.useState)(false);
|
|
97
97
|
const [isSignUp, setIsSignUp] = (0, react_1.useState)(false);
|
|
@@ -99,6 +99,7 @@ const YaverLoginScreen = ({ onLoggedIn, onCancel, }) => {
|
|
|
99
99
|
const [email, setEmail] = (0, react_1.useState)('');
|
|
100
100
|
const [password, setPassword] = (0, react_1.useState)('');
|
|
101
101
|
const [confirmPassword, setConfirmPassword] = (0, react_1.useState)('');
|
|
102
|
+
const [inviteCode, setInviteCode] = (0, react_1.useState)((initialInviteCode ?? '').toUpperCase());
|
|
102
103
|
const [emailBusy, setEmailBusy] = (0, react_1.useState)(false);
|
|
103
104
|
const [emailError, setEmailError] = (0, react_1.useState)('');
|
|
104
105
|
const finish = async (token) => {
|
|
@@ -106,7 +107,8 @@ const YaverLoginScreen = ({ onLoggedIn, onCancel, }) => {
|
|
|
106
107
|
await (0, auth_1.saveToken)(token);
|
|
107
108
|
if (user)
|
|
108
109
|
await (0, auth_1.saveUser)(user);
|
|
109
|
-
|
|
110
|
+
const cleanedInviteCode = inviteCode.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 6);
|
|
111
|
+
onLoggedIn(token, cleanedInviteCode ? { inviteCode: cleanedInviteCode } : undefined);
|
|
110
112
|
};
|
|
111
113
|
const handleApple = async () => {
|
|
112
114
|
setBusyProvider('apple');
|
|
@@ -177,14 +179,18 @@ const YaverLoginScreen = ({ onLoggedIn, onCancel, }) => {
|
|
|
177
179
|
return (<react_native_1.SafeAreaView style={styles.safeArea}>
|
|
178
180
|
<react_native_1.KeyboardAvoidingView style={{ flex: 1 }} behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : undefined}>
|
|
179
181
|
<react_native_1.ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled">
|
|
180
|
-
<react_native_1.View style={styles.
|
|
181
|
-
<react_native_1.
|
|
182
|
-
<react_native_1.Text style={styles.subtitle}>Sign in to send feedback</react_native_1.Text>
|
|
182
|
+
<react_native_1.View style={styles.topBar}>
|
|
183
|
+
<react_native_1.View style={styles.topBarSpacer}/>
|
|
183
184
|
{onCancel && (<react_native_1.Pressable onPress={onCancel} style={styles.cancel}>
|
|
184
185
|
<react_native_1.Text style={styles.cancelText}>Cancel</react_native_1.Text>
|
|
185
186
|
</react_native_1.Pressable>)}
|
|
186
187
|
</react_native_1.View>
|
|
187
188
|
|
|
189
|
+
<react_native_1.View style={styles.header}>
|
|
190
|
+
<react_native_1.Text style={styles.logo}>Yaver</react_native_1.Text>
|
|
191
|
+
<react_native_1.Text style={styles.subtitle}>Sign in to send feedback</react_native_1.Text>
|
|
192
|
+
</react_native_1.View>
|
|
193
|
+
|
|
188
194
|
<react_native_1.View style={styles.buttons}>
|
|
189
195
|
{react_native_1.Platform.OS === 'ios'
|
|
190
196
|
? renderProvider('apple', 'Continue with Apple', handleApple)
|
|
@@ -212,6 +218,7 @@ const YaverLoginScreen = ({ onLoggedIn, onCancel, }) => {
|
|
|
212
218
|
<react_native_1.TextInput style={styles.input} placeholder="Email" placeholderTextColor="#666" value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" autoCorrect={false}/>
|
|
213
219
|
<react_native_1.TextInput style={styles.input} placeholder="Password" placeholderTextColor="#666" value={password} onChangeText={setPassword} secureTextEntry autoCapitalize="none"/>
|
|
214
220
|
{isSignUp && (<react_native_1.TextInput style={styles.input} placeholder="Confirm Password" placeholderTextColor="#666" value={confirmPassword} onChangeText={setConfirmPassword} secureTextEntry/>)}
|
|
221
|
+
{isSignUp && (<react_native_1.TextInput style={styles.input} placeholder="Invite Code (optional)" placeholderTextColor="#666" value={inviteCode} onChangeText={(value) => setInviteCode(value.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 6))} autoCapitalize="characters" autoCorrect={false} maxLength={6}/>)}
|
|
215
222
|
|
|
216
223
|
{emailError ? (<react_native_1.Text style={styles.errorText}>{emailError}</react_native_1.Text>) : null}
|
|
217
224
|
|
|
@@ -249,11 +256,21 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
249
256
|
paddingHorizontal: 24,
|
|
250
257
|
justifyContent: 'center',
|
|
251
258
|
},
|
|
259
|
+
topBar: {
|
|
260
|
+
minHeight: 32,
|
|
261
|
+
marginBottom: 24,
|
|
262
|
+
flexDirection: 'row',
|
|
263
|
+
alignItems: 'center',
|
|
264
|
+
justifyContent: 'space-between',
|
|
265
|
+
},
|
|
266
|
+
topBarSpacer: {
|
|
267
|
+
width: 56,
|
|
268
|
+
},
|
|
252
269
|
header: { alignItems: 'center', marginBottom: 40 },
|
|
253
270
|
logo: { fontSize: 44, fontWeight: '800', color: '#e0e0e0', letterSpacing: -1 },
|
|
254
271
|
subtitle: { fontSize: 15, color: '#9ca3af', marginTop: 6 },
|
|
255
|
-
cancel: {
|
|
256
|
-
cancelText: { color: '#9ca3af', fontSize: 14 },
|
|
272
|
+
cancel: { minWidth: 56, alignItems: 'flex-end', paddingVertical: 8 },
|
|
273
|
+
cancelText: { color: '#9ca3af', fontSize: 14, fontWeight: '500' },
|
|
257
274
|
buttons: { gap: 12 },
|
|
258
275
|
button: {
|
|
259
276
|
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
@@ -53,6 +53,7 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
|
|
|
53
53
|
const [error, setError] = (0, react_1.useState)(null);
|
|
54
54
|
const [list, setList] = (0, react_1.useState)({ owned: [], shared: [] });
|
|
55
55
|
const [pairingDevice, setPairingDevice] = (0, react_1.useState)(null);
|
|
56
|
+
const [reachability, setReachability] = (0, react_1.useState)({});
|
|
56
57
|
const load = (0, react_1.useCallback)(async (silent = false) => {
|
|
57
58
|
if (!silent)
|
|
58
59
|
setLoading(true);
|
|
@@ -60,8 +61,25 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
|
|
|
60
61
|
try {
|
|
61
62
|
const result = await (0, auth_1.listReachableDevices)(token);
|
|
62
63
|
setList(result);
|
|
64
|
+
setReachability({});
|
|
65
|
+
void (async () => {
|
|
66
|
+
const devices = [...result.owned, ...result.shared];
|
|
67
|
+
const settled = await Promise.allSettled(devices.map(async (device) => ({
|
|
68
|
+
deviceId: device.deviceId,
|
|
69
|
+
result: await (0, auth_1.probeDeviceReachability)(device),
|
|
70
|
+
})));
|
|
71
|
+
setReachability((prev) => {
|
|
72
|
+
const next = { ...prev };
|
|
73
|
+
for (const entry of settled) {
|
|
74
|
+
if (entry.status === 'fulfilled') {
|
|
75
|
+
next[entry.value.deviceId] = entry.value.result;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return next;
|
|
79
|
+
});
|
|
80
|
+
})();
|
|
63
81
|
if (result.owned.length === 0 && result.shared.length === 0) {
|
|
64
|
-
setError('
|
|
82
|
+
setError('No machines found yet. If you do not have your own computer, redeem a host invite code first. Otherwise run `yaver auth` + `yaver serve` on your machine.');
|
|
65
83
|
}
|
|
66
84
|
}
|
|
67
85
|
catch (err) {
|
|
@@ -85,11 +103,18 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
|
|
|
85
103
|
setPairingDevice(device);
|
|
86
104
|
return;
|
|
87
105
|
}
|
|
106
|
+
const direct = await (0, auth_1.probeDeviceReachability)(device);
|
|
107
|
+
if (!direct.reachable && !device.needsAuth) {
|
|
108
|
+
setError('Selected machine is not responding. Start `yaver serve` on it and try again.');
|
|
109
|
+
setReachability((prev) => ({ ...prev, [device.deviceId]: direct }));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
88
112
|
await (0, auth_1.saveSelectedDeviceId)(device.deviceId);
|
|
89
113
|
onPick(device);
|
|
90
114
|
};
|
|
91
115
|
const renderDevice = (device) => {
|
|
92
116
|
const selected = device.deviceId === currentDeviceId;
|
|
117
|
+
const probe = reachability[device.deviceId];
|
|
93
118
|
// Trust Convex's `isOnline` — the backend already gates it on a
|
|
94
119
|
// fresh 90 s heartbeat (see backend/convex/devices.ts
|
|
95
120
|
// deriveIsOnline). Re-checking on the client produced false
|
|
@@ -100,20 +125,33 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
|
|
|
100
125
|
// healthy — a separate concern from "can I reach this machine?"
|
|
101
126
|
// Mobile app surfaces runner issues via a separate badge, not
|
|
102
127
|
// this dot. Picker's job is reachability, nothing more.
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
const effectivelyReachable = probe?.reachable === true;
|
|
129
|
+
const explicitlyOffline = probe?.reachable === false;
|
|
130
|
+
const healthColor = device.needsAuth
|
|
131
|
+
? '#f59e0b'
|
|
132
|
+
: effectivelyReachable
|
|
133
|
+
? '#22c55e'
|
|
134
|
+
: explicitlyOffline || !device.isOnline
|
|
135
|
+
? '#ef4444'
|
|
136
|
+
: '#22c55e';
|
|
108
137
|
// Derive a single short status phrase the user can act on.
|
|
109
138
|
let statusLine = device.platform;
|
|
110
|
-
if (
|
|
139
|
+
if (probe === undefined) {
|
|
140
|
+
statusLine = 'Checking connection…';
|
|
141
|
+
}
|
|
142
|
+
else if (!device.isOnline && effectivelyReachable) {
|
|
143
|
+
statusLine = 'Reachable now — waiting for cloud status to refresh';
|
|
144
|
+
}
|
|
145
|
+
else if (!device.isOnline) {
|
|
111
146
|
statusLine = 'Offline — start `yaver serve` on the Mac';
|
|
112
147
|
}
|
|
113
148
|
else if (device.needsAuth) {
|
|
114
149
|
statusLine =
|
|
115
150
|
'Needs pairing — open the Yaver app to adopt this machine';
|
|
116
151
|
}
|
|
152
|
+
else if (explicitlyOffline) {
|
|
153
|
+
statusLine = 'Agent not responding on this machine';
|
|
154
|
+
}
|
|
117
155
|
else if (device.runnerDown) {
|
|
118
156
|
statusLine = 'Runner down — restart the coding agent on the Mac';
|
|
119
157
|
}
|