@entropy-softworks/ui 2026.9.10 → 2026.910.1
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 +2 -2
- package/lib/components/error-boundary.d.ts.map +1 -1
- package/lib/components/error-boundary.jsx +3 -1
- package/lib/components/error-boundary.jsx.map +1 -1
- package/lib/components/password-generator.d.ts.map +1 -1
- package/lib/components/password-generator.jsx +15 -5
- package/lib/components/password-generator.jsx.map +1 -1
- package/lib/components/settings-row.d.ts.map +1 -1
- package/lib/components/settings-row.jsx +3 -1
- package/lib/components/settings-row.jsx.map +1 -1
- package/lib/hooks/usePasswordGenerator.d.ts.map +1 -1
- package/lib/hooks/usePasswordGenerator.js +1 -1
- package/lib/hooks/usePasswordGenerator.js.map +1 -1
- package/lib/screens/auth/AccountPickerScreen.d.ts.map +1 -1
- package/lib/screens/auth/AccountPickerScreen.jsx +1 -3
- package/lib/screens/auth/AccountPickerScreen.jsx.map +1 -1
- package/lib/screens/auth/AuthScreen.d.ts +18 -1
- package/lib/screens/auth/AuthScreen.d.ts.map +1 -1
- package/lib/screens/auth/AuthScreen.jsx +30 -1
- package/lib/screens/auth/AuthScreen.jsx.map +1 -1
- package/lib/screens/auth/ConsentScreen.jsx.map +1 -1
- package/lib/screens/auth/ErrorScreen.jsx.map +1 -1
- package/lib/screens/auth/ExpiredLinkScreen.d.ts.map +1 -1
- package/lib/screens/auth/ExpiredLinkScreen.jsx +1 -1
- package/lib/screens/auth/ExpiredLinkScreen.jsx.map +1 -1
- package/lib/screens/auth/ForgotPasswordScreen.d.ts.map +1 -1
- package/lib/screens/auth/ForgotPasswordScreen.jsx.map +1 -1
- package/lib/screens/auth/ImportDataScreen.jsx.map +1 -1
- package/lib/screens/auth/LoginScreen.d.ts.map +1 -1
- package/lib/screens/auth/LoginScreen.jsx.map +1 -1
- package/lib/screens/auth/MfaChallengeScreen.d.ts.map +1 -1
- package/lib/screens/auth/MfaChallengeScreen.jsx.map +1 -1
- package/lib/screens/auth/PasskeyEnrollScreen.d.ts.map +1 -1
- package/lib/screens/auth/PasskeyEnrollScreen.jsx +3 -5
- package/lib/screens/auth/PasskeyEnrollScreen.jsx.map +1 -1
- package/lib/screens/auth/PrivacyPolicyScreen.d.ts.map +1 -1
- package/lib/screens/auth/PrivacyPolicyScreen.jsx.map +1 -1
- package/lib/screens/auth/ResetPasswordScreen.d.ts.map +1 -1
- package/lib/screens/auth/ResetPasswordScreen.jsx.map +1 -1
- package/lib/screens/auth/ServerConfigScreen.d.ts.map +1 -1
- package/lib/screens/auth/ServerConfigScreen.jsx +15 -5
- package/lib/screens/auth/ServerConfigScreen.jsx.map +1 -1
- package/lib/screens/auth/TermsOfServiceScreen.d.ts.map +1 -1
- package/lib/screens/auth/TermsOfServiceScreen.jsx.map +1 -1
- package/lib/screens/auth/WelcomeScreen.d.ts.map +1 -1
- package/lib/screens/auth/WelcomeScreen.jsx.map +1 -1
- package/package.json +1 -1
- package/src/components/error-boundary.tsx +3 -1
- package/src/components/password-generator.tsx +15 -5
- package/src/components/settings-row.tsx +3 -1
- package/src/hooks/usePasswordGenerator.ts +8 -1
- package/src/screens/auth/AccountPickerScreen.tsx +2 -4
- package/src/screens/auth/AuthScreen.tsx +52 -0
- package/src/screens/auth/ConsentScreen.tsx +2 -2
- package/src/screens/auth/ErrorScreen.tsx +2 -2
- package/src/screens/auth/ExpiredLinkScreen.tsx +3 -9
- package/src/screens/auth/ForgotPasswordScreen.tsx +6 -4
- package/src/screens/auth/ImportDataScreen.tsx +1 -1
- package/src/screens/auth/LoginScreen.tsx +13 -6
- package/src/screens/auth/MfaChallengeScreen.tsx +4 -1
- package/src/screens/auth/PasskeyEnrollScreen.tsx +5 -7
- package/src/screens/auth/PrivacyPolicyScreen.tsx +9 -3
- package/src/screens/auth/ResetPasswordScreen.tsx +6 -4
- package/src/screens/auth/ServerConfigScreen.tsx +19 -6
- package/src/screens/auth/TermsOfServiceScreen.tsx +9 -3
- package/src/screens/auth/WelcomeScreen.tsx +11 -4
- package/src/services/telemetry.ts +3 -3
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
// layout — see components/password-generator.tsx's own doc comment for
|
|
7
7
|
// why that stayed a separate render rather than one component branching
|
|
8
8
|
// on Platform.OS internally. User-requested, 2026-08-17.
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type Dispatch,
|
|
11
|
+
type SetStateAction,
|
|
12
|
+
useCallback,
|
|
13
|
+
useEffect,
|
|
14
|
+
useRef,
|
|
15
|
+
useState,
|
|
16
|
+
} from "react";
|
|
10
17
|
import { Platform } from "react-native";
|
|
11
18
|
import * as Haptics from "expo-haptics";
|
|
12
19
|
import Toast from "react-native-toast-message";
|
|
@@ -75,9 +75,7 @@ export function AccountPickerScreen({
|
|
|
75
75
|
accessibilityLabel={`Continue as ${a.username}`}
|
|
76
76
|
className="rounded-md border border-border px-3 py-3"
|
|
77
77
|
>
|
|
78
|
-
<Text className="text-sm text-foreground">
|
|
79
|
-
{a.displayName || a.username}
|
|
80
|
-
</Text>
|
|
78
|
+
<Text className="text-sm text-foreground">{a.displayName || a.username}</Text>
|
|
81
79
|
<MutedText className="text-xs">{a.email}</MutedText>
|
|
82
80
|
</Pressable>
|
|
83
81
|
))
|
|
@@ -89,7 +87,7 @@ export function AccountPickerScreen({
|
|
|
89
87
|
className="w-full"
|
|
90
88
|
variant="outline"
|
|
91
89
|
telemetryId="auth.account_picker.use_another"
|
|
92
|
-
|
|
90
|
+
onPress={onUseAnother}
|
|
93
91
|
accessibilityLabel="Use another account"
|
|
94
92
|
accessibilityRole="button"
|
|
95
93
|
>
|
|
@@ -279,6 +279,23 @@ export interface AuthScreenProps {
|
|
|
279
279
|
* hidden too, since there is no stage behind it.
|
|
280
280
|
*/
|
|
281
281
|
showWelcome?: boolean;
|
|
282
|
+
/**
|
|
283
|
+
* Page heading, rendered ABOVE the card, e.g.
|
|
284
|
+
* "Log into your Entropy account to continue."
|
|
285
|
+
*
|
|
286
|
+
* Undefined renders no heading, which is the right default for an app whose
|
|
287
|
+
* own login the user reached deliberately -- the surrounding brand already
|
|
288
|
+
* says where they are. Set it on an identity provider, where the user is
|
|
289
|
+
* frequently redirected in from a relying application and this is the only
|
|
290
|
+
* thing on screen that can say whose credentials to type.
|
|
291
|
+
*
|
|
292
|
+
* It used to render inside the card header, where it sat over the
|
|
293
|
+
* Sign In / Sign Up toggle and read as a label for the selected mode. Above
|
|
294
|
+
* the card it is the page's heading and the card is purely the form.
|
|
295
|
+
*/
|
|
296
|
+
title?: string;
|
|
297
|
+
/** Optional line under `title`. Ignored unless `title` is set. */
|
|
298
|
+
subtitle?: string;
|
|
282
299
|
}
|
|
283
300
|
|
|
284
301
|
/**
|
|
@@ -314,6 +331,8 @@ export function AuthScreen({
|
|
|
314
331
|
onSignUp,
|
|
315
332
|
signUpSuccessMessage = "Account created. Check your email to verify the address, then sign in.",
|
|
316
333
|
showWelcome = true,
|
|
334
|
+
title,
|
|
335
|
+
subtitle,
|
|
317
336
|
}: AuthScreenProps = {}) {
|
|
318
337
|
const {
|
|
319
338
|
login: authLogin,
|
|
@@ -1058,6 +1077,34 @@ export function AuthScreen({
|
|
|
1058
1077
|
{cardBackdrop}
|
|
1059
1078
|
</View>
|
|
1060
1079
|
) : null}
|
|
1080
|
+
{/* ── WHOSE login this is, ABOVE the card ──────────────
|
|
1081
|
+
Without it the card is a pair of unlabelled fields under
|
|
1082
|
+
a Sign In / Sign Up toggle: fine when the user arrived
|
|
1083
|
+
from the app they are signing into, actively confusing
|
|
1084
|
+
when an OIDC redirect dropped them here from somewhere
|
|
1085
|
+
else -- the case an identity provider is FOR.
|
|
1086
|
+
|
|
1087
|
+
Above the card rather than in its header, which is where
|
|
1088
|
+
this started. In the header it sat directly over the
|
|
1089
|
+
mode toggle and read as a label for the selected mode
|
|
1090
|
+
("Log in to Entropy" above a Sign In / Sign Up switch
|
|
1091
|
+
restates the switch). Out here it is unambiguously the
|
|
1092
|
+
page's own heading, the card below it is purely the
|
|
1093
|
+
form, and the brand backdrop has something to sit
|
|
1094
|
+
behind.
|
|
1095
|
+
|
|
1096
|
+
Width is pinned to the card's 360px so the two are one
|
|
1097
|
+
column, and the text is centred to match. */}
|
|
1098
|
+
{title ? (
|
|
1099
|
+
<View className="w-full max-w-[360px] gap-1 pb-5">
|
|
1100
|
+
<Heading className="text-center text-xl">{title}</Heading>
|
|
1101
|
+
{subtitle ? (
|
|
1102
|
+
<MutedText className="text-center text-sm leading-5">
|
|
1103
|
+
{subtitle}
|
|
1104
|
+
</MutedText>
|
|
1105
|
+
) : null}
|
|
1106
|
+
</View>
|
|
1107
|
+
) : null}
|
|
1061
1108
|
<Card className="w-full max-w-[360px]">
|
|
1062
1109
|
{displayStage === "forgot" ? (
|
|
1063
1110
|
<>
|
|
@@ -1139,6 +1186,11 @@ export function AuthScreen({
|
|
|
1139
1186
|
that otherwise all collects above the footer.
|
|
1140
1187
|
Applies to both modes — the header is shared. */}
|
|
1141
1188
|
<CardHeader className="gap-5 pb-3 pt-5">
|
|
1189
|
+
{/* The heading moved OUT of the card -- see the
|
|
1190
|
+
block above <Card>. Inside here it read as a
|
|
1191
|
+
label for whichever toggle mode was selected;
|
|
1192
|
+
above the card it reads as the page saying whose
|
|
1193
|
+
login this is, which is what it is for. */}
|
|
1142
1194
|
{signUpEnabled ? (
|
|
1143
1195
|
<SegmentedToggle<Mode>
|
|
1144
1196
|
value={mode}
|
|
@@ -93,7 +93,7 @@ export function ConsentScreen({
|
|
|
93
93
|
<Button
|
|
94
94
|
className="w-full"
|
|
95
95
|
telemetryId="auth.consent.allow"
|
|
96
|
-
|
|
96
|
+
onPress={onAllow}
|
|
97
97
|
isLoading={isLoading}
|
|
98
98
|
accessibilityLabel={`Allow ${clientName}`}
|
|
99
99
|
accessibilityRole="button"
|
|
@@ -104,7 +104,7 @@ export function ConsentScreen({
|
|
|
104
104
|
className="w-full"
|
|
105
105
|
variant="outline"
|
|
106
106
|
telemetryId="auth.consent.deny"
|
|
107
|
-
|
|
107
|
+
onPress={onDeny}
|
|
108
108
|
accessibilityLabel="Deny access"
|
|
109
109
|
accessibilityRole="button"
|
|
110
110
|
>
|
|
@@ -68,7 +68,7 @@ export function ErrorScreen({
|
|
|
68
68
|
<Button
|
|
69
69
|
className="w-full"
|
|
70
70
|
telemetryId="auth.error.retry"
|
|
71
|
-
|
|
71
|
+
onPress={onRetry}
|
|
72
72
|
accessibilityLabel="Try again"
|
|
73
73
|
accessibilityRole="button"
|
|
74
74
|
>
|
|
@@ -80,7 +80,7 @@ export function ErrorScreen({
|
|
|
80
80
|
className="w-full"
|
|
81
81
|
variant="outline"
|
|
82
82
|
telemetryId="auth.error.home"
|
|
83
|
-
|
|
83
|
+
onPress={onHome}
|
|
84
84
|
accessibilityLabel={homeLabel}
|
|
85
85
|
accessibilityRole="button"
|
|
86
86
|
>
|
|
@@ -4,13 +4,7 @@ import { SafeAreaView } from "react-native-safe-area-context";
|
|
|
4
4
|
import { StatusBar } from "expo-status-bar";
|
|
5
5
|
import { useColorScheme } from "nativewind";
|
|
6
6
|
import { Button } from "../../components/button";
|
|
7
|
-
import {
|
|
8
|
-
Card,
|
|
9
|
-
CardDescription,
|
|
10
|
-
CardFooter,
|
|
11
|
-
CardHeader,
|
|
12
|
-
CardTitle,
|
|
13
|
-
} from "../../components/card";
|
|
7
|
+
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "../../components/card";
|
|
14
8
|
|
|
15
9
|
export interface ExpiredLinkScreenProps {
|
|
16
10
|
title?: string;
|
|
@@ -59,7 +53,7 @@ export function ExpiredLinkScreen({
|
|
|
59
53
|
<Button
|
|
60
54
|
className="w-full"
|
|
61
55
|
telemetryId="auth.expired_link.request_new"
|
|
62
|
-
|
|
56
|
+
onPress={onRequestNew}
|
|
63
57
|
accessibilityLabel={requestLabel}
|
|
64
58
|
accessibilityRole="button"
|
|
65
59
|
>
|
|
@@ -71,7 +65,7 @@ export function ExpiredLinkScreen({
|
|
|
71
65
|
className="w-full"
|
|
72
66
|
variant="outline"
|
|
73
67
|
telemetryId="auth.expired_link.home"
|
|
74
|
-
|
|
68
|
+
onPress={onHome}
|
|
75
69
|
accessibilityLabel={homeLabel}
|
|
76
70
|
accessibilityRole="button"
|
|
77
71
|
>
|
|
@@ -81,7 +81,7 @@ export function ForgotPasswordScreen() {
|
|
|
81
81
|
<IconButton
|
|
82
82
|
variant="ghost"
|
|
83
83
|
telemetryId="auth.forgot_password.back"
|
|
84
|
-
|
|
84
|
+
onPress={() => router.back()}
|
|
85
85
|
accessibilityLabel="Go back"
|
|
86
86
|
accessibilityRole="button"
|
|
87
87
|
>
|
|
@@ -123,7 +123,7 @@ export function ForgotPasswordScreen() {
|
|
|
123
123
|
<Button
|
|
124
124
|
className="w-full"
|
|
125
125
|
telemetryId="auth.forgot_password.submit"
|
|
126
|
-
|
|
126
|
+
onPress={handleSubmit}
|
|
127
127
|
isLoading={isLoading}
|
|
128
128
|
accessibilityLabel="Reset your password"
|
|
129
129
|
accessibilityRole="button"
|
|
@@ -135,7 +135,9 @@ export function ForgotPasswordScreen() {
|
|
|
135
135
|
Remember your password?{" "}
|
|
136
136
|
<Text
|
|
137
137
|
className="text-sm text-foreground underline"
|
|
138
|
-
onPress={withClickTracking("auth.forgot_password.push", () =>
|
|
138
|
+
onPress={withClickTracking("auth.forgot_password.push", () =>
|
|
139
|
+
router.push(routes.login)
|
|
140
|
+
)}
|
|
139
141
|
accessibilityRole="link"
|
|
140
142
|
accessibilityLabel="Sign in"
|
|
141
143
|
>
|
|
@@ -149,7 +151,7 @@ export function ForgotPasswordScreen() {
|
|
|
149
151
|
<Button
|
|
150
152
|
className="w-full"
|
|
151
153
|
telemetryId="auth.forgot_password.push_2"
|
|
152
|
-
|
|
154
|
+
onPress={() =>
|
|
153
155
|
router.push({
|
|
154
156
|
pathname: routes.resetPassword,
|
|
155
157
|
params: { email },
|
|
@@ -96,7 +96,7 @@ export function ImportDataScreen({ renderImport }: ImportDataScreenProps = {}) {
|
|
|
96
96
|
<Button
|
|
97
97
|
className="w-full"
|
|
98
98
|
telemetryId="auth.import_data.complete_and_navigate"
|
|
99
|
-
|
|
99
|
+
onPress={completeAndNavigate}
|
|
100
100
|
accessibilityLabel={`Continue to ${appName}`}
|
|
101
101
|
accessibilityRole="button"
|
|
102
102
|
testID="import-continue"
|
|
@@ -282,7 +282,7 @@ export function LoginScreen({
|
|
|
282
282
|
<IconButton
|
|
283
283
|
variant="ghost"
|
|
284
284
|
telemetryId="auth.login.push"
|
|
285
|
-
|
|
285
|
+
onPress={() => router.push(routes.welcome)}
|
|
286
286
|
className="self-start"
|
|
287
287
|
accessibilityLabel="Go back"
|
|
288
288
|
accessibilityRole="button"
|
|
@@ -318,7 +318,7 @@ export function LoginScreen({
|
|
|
318
318
|
variant="outline"
|
|
319
319
|
className="w-full"
|
|
320
320
|
telemetryId="auth.login.passkey_login"
|
|
321
|
-
|
|
321
|
+
onPress={handlePasskeyLogin}
|
|
322
322
|
isLoading={passkeyStep === "passkey-loading"}
|
|
323
323
|
accessibilityLabel="Sign in with passkey"
|
|
324
324
|
accessibilityRole="button"
|
|
@@ -378,7 +378,9 @@ export function LoginScreen({
|
|
|
378
378
|
<View className="items-end">
|
|
379
379
|
<Text
|
|
380
380
|
className="font-mono text-sm underline"
|
|
381
|
-
onPress={withClickTracking("auth.login.push_2", () =>
|
|
381
|
+
onPress={withClickTracking("auth.login.push_2", () =>
|
|
382
|
+
router.push(routes.forgotPassword)
|
|
383
|
+
)}
|
|
382
384
|
accessibilityRole="link"
|
|
383
385
|
accessibilityLabel="Forgot password"
|
|
384
386
|
>
|
|
@@ -392,7 +394,9 @@ export function LoginScreen({
|
|
|
392
394
|
Don't have an account?{" "}
|
|
393
395
|
<Text
|
|
394
396
|
className="text-sm text-foreground underline"
|
|
395
|
-
onPress={withClickTracking("auth.login.push_3", () =>
|
|
397
|
+
onPress={withClickTracking("auth.login.push_3", () =>
|
|
398
|
+
router.push(routes.register)
|
|
399
|
+
)}
|
|
396
400
|
accessibilityRole="link"
|
|
397
401
|
accessibilityLabel="Sign up"
|
|
398
402
|
>
|
|
@@ -405,7 +409,7 @@ export function LoginScreen({
|
|
|
405
409
|
<Button
|
|
406
410
|
className="w-full"
|
|
407
411
|
telemetryId="auth.login.login"
|
|
408
|
-
|
|
412
|
+
onPress={handleLogin}
|
|
409
413
|
isLoading={isLoading}
|
|
410
414
|
accessibilityLabel="Sign in"
|
|
411
415
|
accessibilityRole="button"
|
|
@@ -473,7 +477,10 @@ export function LoginScreen({
|
|
|
473
477
|
<DialogFooter style={{ justifyContent: "center" }}>
|
|
474
478
|
<DialogButton
|
|
475
479
|
variant="primary"
|
|
476
|
-
onPress={withClickTracking(
|
|
480
|
+
onPress={withClickTracking(
|
|
481
|
+
"auth.login.action",
|
|
482
|
+
isLoading ? () => {} : handleUnlockAfterPasskey
|
|
483
|
+
)}
|
|
477
484
|
accessibilityLabel="Unlock"
|
|
478
485
|
accessibilityRole="button"
|
|
479
486
|
>
|
|
@@ -195,7 +195,10 @@ export function MfaChallengeScreen() {
|
|
|
195
195
|
</Pressable>
|
|
196
196
|
|
|
197
197
|
<Pressable
|
|
198
|
-
onPress={withClickTracking(
|
|
198
|
+
onPress={withClickTracking(
|
|
199
|
+
"auth.mfa_challenge.toggle_recovery_mode",
|
|
200
|
+
toggleRecoveryMode
|
|
201
|
+
)}
|
|
199
202
|
className="mt-5 items-center"
|
|
200
203
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
201
204
|
accessibilityRole="button"
|
|
@@ -50,9 +50,8 @@ export function PasskeyEnrollScreen({
|
|
|
50
50
|
<CardHeader>
|
|
51
51
|
<CardTitle>Add a passkey</CardTitle>
|
|
52
52
|
<CardDescription>
|
|
53
|
-
Sign in faster and more securely next time with a passkey — your
|
|
54
|
-
|
|
55
|
-
to remember or phish.
|
|
53
|
+
Sign in faster and more securely next time with a passkey — your device unlocks it
|
|
54
|
+
with your fingerprint, face, or PIN. No password to remember or phish.
|
|
56
55
|
</CardDescription>
|
|
57
56
|
</CardHeader>
|
|
58
57
|
|
|
@@ -61,8 +60,7 @@ export function PasskeyEnrollScreen({
|
|
|
61
60
|
<Text className="text-sm text-destructive dark:text-red-900">{error}</Text>
|
|
62
61
|
) : (
|
|
63
62
|
<MutedText className="text-sm">
|
|
64
|
-
You can always add or remove passkeys later in your account
|
|
65
|
-
settings.
|
|
63
|
+
You can always add or remove passkeys later in your account settings.
|
|
66
64
|
</MutedText>
|
|
67
65
|
)}
|
|
68
66
|
</CardContent>
|
|
@@ -71,7 +69,7 @@ export function PasskeyEnrollScreen({
|
|
|
71
69
|
<Button
|
|
72
70
|
className="w-full"
|
|
73
71
|
telemetryId="auth.passkey_enroll.enroll"
|
|
74
|
-
|
|
72
|
+
onPress={onEnroll}
|
|
75
73
|
isLoading={isLoading}
|
|
76
74
|
accessibilityLabel="Add a passkey"
|
|
77
75
|
accessibilityRole="button"
|
|
@@ -82,7 +80,7 @@ export function PasskeyEnrollScreen({
|
|
|
82
80
|
className="w-full"
|
|
83
81
|
variant="ghost"
|
|
84
82
|
telemetryId="auth.passkey_enroll.skip"
|
|
85
|
-
|
|
83
|
+
onPress={onSkip}
|
|
86
84
|
accessibilityLabel="Not now"
|
|
87
85
|
accessibilityRole="button"
|
|
88
86
|
>
|
|
@@ -62,8 +62,12 @@ export function PrivacyPolicyScreen({
|
|
|
62
62
|
<View className="flex-1" style={{ backgroundColor: isDark ? "#000000" : "#FFFFFF" }}>
|
|
63
63
|
<View className="px-6 pt-4 pb-6">
|
|
64
64
|
<View className="flex-row items-center justify-between mb-6">
|
|
65
|
-
<IconButton
|
|
66
|
-
|
|
65
|
+
<IconButton
|
|
66
|
+
variant="ghost"
|
|
67
|
+
size="sm"
|
|
68
|
+
telemetryId="auth.privacy_policy.back"
|
|
69
|
+
onPress={() => router.back()}
|
|
70
|
+
>
|
|
67
71
|
<Ionicons name="arrow-back" size={20} color={isDark ? "#FFFFFF" : "#000000"} />
|
|
68
72
|
</IconButton>
|
|
69
73
|
|
|
@@ -191,7 +195,9 @@ export function PrivacyPolicyScreen({
|
|
|
191
195
|
<Text
|
|
192
196
|
className="font-mono text-sm underline"
|
|
193
197
|
style={{ color: isDark ? "#6BA3E5" : "#4A90E2" }}
|
|
194
|
-
onPress={withClickTracking("auth.privacy_policy.open_u_r_l", () =>
|
|
198
|
+
onPress={withClickTracking("auth.privacy_policy.open_u_r_l", () =>
|
|
199
|
+
Linking.openURL(`mailto:${supportEmail}`)
|
|
200
|
+
)}
|
|
195
201
|
>
|
|
196
202
|
{supportEmail}
|
|
197
203
|
</Text>
|
|
@@ -230,7 +230,7 @@ export function ResetPasswordScreen({ encryptionWarning }: ResetPasswordScreenPr
|
|
|
230
230
|
<IconButton
|
|
231
231
|
variant="ghost"
|
|
232
232
|
telemetryId="auth.reset_password.step"
|
|
233
|
-
|
|
233
|
+
onPress={() => (step === "password" ? setStep("code") : router.back())}
|
|
234
234
|
accessibilityLabel="Go back"
|
|
235
235
|
accessibilityRole="button"
|
|
236
236
|
>
|
|
@@ -273,7 +273,7 @@ export function ResetPasswordScreen({ encryptionWarning }: ResetPasswordScreenPr
|
|
|
273
273
|
<Button
|
|
274
274
|
className="w-full"
|
|
275
275
|
telemetryId="auth.reset_password.code_submit"
|
|
276
|
-
|
|
276
|
+
onPress={handleCodeSubmit}
|
|
277
277
|
isLoading={isLoading}
|
|
278
278
|
accessibilityLabel="Continue"
|
|
279
279
|
accessibilityRole="button"
|
|
@@ -285,7 +285,9 @@ export function ResetPasswordScreen({ encryptionWarning }: ResetPasswordScreenPr
|
|
|
285
285
|
Back to{" "}
|
|
286
286
|
<Text
|
|
287
287
|
className="text-sm text-foreground underline"
|
|
288
|
-
onPress={withClickTracking("auth.reset_password.push", () =>
|
|
288
|
+
onPress={withClickTracking("auth.reset_password.push", () =>
|
|
289
|
+
router.push(routes.login)
|
|
290
|
+
)}
|
|
289
291
|
accessibilityRole="link"
|
|
290
292
|
accessibilityLabel="Sign in"
|
|
291
293
|
>
|
|
@@ -351,7 +353,7 @@ export function ResetPasswordScreen({ encryptionWarning }: ResetPasswordScreenPr
|
|
|
351
353
|
<Button
|
|
352
354
|
className="w-full"
|
|
353
355
|
telemetryId="auth.reset_password.password_submit"
|
|
354
|
-
|
|
356
|
+
onPress={handlePasswordSubmit}
|
|
355
357
|
isLoading={isLoading}
|
|
356
358
|
accessibilityLabel="Reset password"
|
|
357
359
|
accessibilityRole="button"
|
|
@@ -251,7 +251,9 @@ export function ServerConfigScreen({
|
|
|
251
251
|
>
|
|
252
252
|
<Ionicons name="close" size={24} color={isDark ? "white" : "black"} />
|
|
253
253
|
</Pressable>
|
|
254
|
-
<Text className="text-lg font-semibold text-foreground dark:text-white">
|
|
254
|
+
<Text className="text-lg font-semibold text-foreground dark:text-white">
|
|
255
|
+
Server Configuration
|
|
256
|
+
</Text>
|
|
255
257
|
<View className="h-10 w-10" />
|
|
256
258
|
</View>
|
|
257
259
|
|
|
@@ -275,7 +277,9 @@ export function ServerConfigScreen({
|
|
|
275
277
|
</View>
|
|
276
278
|
|
|
277
279
|
<View className="mb-6">
|
|
278
|
-
<Text className="mb-2 text-sm font-medium text-foreground dark:text-white">
|
|
280
|
+
<Text className="mb-2 text-sm font-medium text-foreground dark:text-white">
|
|
281
|
+
Server URL
|
|
282
|
+
</Text>
|
|
279
283
|
<View className="flex-row items-center rounded-lg border border-border dark:border-neutral-800 bg-background dark:bg-black px-3 py-3">
|
|
280
284
|
<Ionicons
|
|
281
285
|
name="globe-outline"
|
|
@@ -345,7 +349,10 @@ export function ServerConfigScreen({
|
|
|
345
349
|
</Pressable>
|
|
346
350
|
|
|
347
351
|
<Pressable
|
|
348
|
-
onPress={withClickTracking(
|
|
352
|
+
onPress={withClickTracking(
|
|
353
|
+
"auth.server_config.clear_configuration",
|
|
354
|
+
handleClearConfiguration
|
|
355
|
+
)}
|
|
349
356
|
accessibilityRole="button"
|
|
350
357
|
accessibilityLabel="Clear all settings"
|
|
351
358
|
className="active:scale-98 mb-3 w-full rounded-lg border border-destructive dark:border-red-900 bg-background dark:bg-black px-6 py-3"
|
|
@@ -370,12 +377,18 @@ export function ServerConfigScreen({
|
|
|
370
377
|
</Pressable>
|
|
371
378
|
|
|
372
379
|
<View className="rounded-lg bg-card dark:bg-neutral-900/50 p-4">
|
|
373
|
-
<Text className="mb-3 text-sm font-semibold text-foreground dark:text-white">
|
|
380
|
+
<Text className="mb-3 text-sm font-semibold text-foreground dark:text-white">
|
|
381
|
+
Need Help?
|
|
382
|
+
</Text>
|
|
374
383
|
<View className="space-y-2">
|
|
375
384
|
{bullets.map((bullet, idx) => (
|
|
376
385
|
<View key={idx} className="flex-row items-start">
|
|
377
|
-
<Text className="mr-2 text-sm text-muted-foreground dark:text-neutral-400"
|
|
378
|
-
|
|
386
|
+
<Text className="mr-2 text-sm text-muted-foreground dark:text-neutral-400">
|
|
387
|
+
•
|
|
388
|
+
</Text>
|
|
389
|
+
<Text className="flex-1 text-sm text-muted-foreground dark:text-neutral-400">
|
|
390
|
+
{bullet}
|
|
391
|
+
</Text>
|
|
379
392
|
</View>
|
|
380
393
|
))}
|
|
381
394
|
</View>
|
|
@@ -74,8 +74,12 @@ export function TermsOfServiceScreen({
|
|
|
74
74
|
<View className="flex-1" style={{ backgroundColor: isDark ? "#000000" : "#FFFFFF" }}>
|
|
75
75
|
<View className="px-6 pt-4 pb-6">
|
|
76
76
|
<View className="flex-row items-center justify-between mb-6">
|
|
77
|
-
<IconButton
|
|
78
|
-
|
|
77
|
+
<IconButton
|
|
78
|
+
variant="ghost"
|
|
79
|
+
size="sm"
|
|
80
|
+
telemetryId="auth.terms_of_service.back"
|
|
81
|
+
onPress={() => router.back()}
|
|
82
|
+
>
|
|
79
83
|
<Ionicons name="arrow-back" size={20} color={isDark ? "#FFFFFF" : "#000000"} />
|
|
80
84
|
</IconButton>
|
|
81
85
|
|
|
@@ -168,7 +172,9 @@ export function TermsOfServiceScreen({
|
|
|
168
172
|
<Text
|
|
169
173
|
className="font-mono text-sm underline"
|
|
170
174
|
style={{ color: isDark ? "#6BA3E5" : "#4A90E2" }}
|
|
171
|
-
onPress={withClickTracking("auth.terms_of_service.open_u_r_l", () =>
|
|
175
|
+
onPress={withClickTracking("auth.terms_of_service.open_u_r_l", () =>
|
|
176
|
+
Linking.openURL(`mailto:${supportEmail}`)
|
|
177
|
+
)}
|
|
172
178
|
>
|
|
173
179
|
{supportEmail}
|
|
174
180
|
</Text>
|
|
@@ -50,12 +50,19 @@ export function WelcomeScreen({ decoration, topAccessory }: WelcomeScreenProps)
|
|
|
50
50
|
) : null}
|
|
51
51
|
|
|
52
52
|
<View className="w-full max-w-[360px] gap-3">
|
|
53
|
-
<Button
|
|
54
|
-
|
|
53
|
+
<Button
|
|
54
|
+
className="w-full"
|
|
55
|
+
telemetryId="auth.welcome.push"
|
|
56
|
+
onPress={() => router.push(routes.login)}
|
|
57
|
+
>
|
|
55
58
|
Sign In
|
|
56
59
|
</Button>
|
|
57
|
-
<Button
|
|
58
|
-
|
|
60
|
+
<Button
|
|
61
|
+
variant="outline"
|
|
62
|
+
className="w-full"
|
|
63
|
+
telemetryId="auth.welcome.push_2"
|
|
64
|
+
onPress={() => router.push(routes.register)}
|
|
65
|
+
>
|
|
59
66
|
Create Account
|
|
60
67
|
</Button>
|
|
61
68
|
</View>
|
|
@@ -70,11 +70,11 @@ export function trackClick(element: string, route?: string): void {
|
|
|
70
70
|
*/
|
|
71
71
|
export function withClickTracking<F extends (...args: never[]) => unknown>(
|
|
72
72
|
element: string | undefined,
|
|
73
|
-
handler: F
|
|
73
|
+
handler: F
|
|
74
74
|
): F;
|
|
75
75
|
export function withClickTracking<F extends (...args: never[]) => unknown>(
|
|
76
76
|
element: string | undefined,
|
|
77
|
-
handler: F | null | undefined
|
|
77
|
+
handler: F | null | undefined
|
|
78
78
|
): F | undefined;
|
|
79
79
|
/**
|
|
80
80
|
* Wrap a press handler so activating the element is reported first.
|
|
@@ -88,7 +88,7 @@ export function withClickTracking<F extends (...args: never[]) => unknown>(
|
|
|
88
88
|
*/
|
|
89
89
|
export function withClickTracking(
|
|
90
90
|
element: string | undefined,
|
|
91
|
-
handler: ((...args: never[]) => unknown) | null | undefined
|
|
91
|
+
handler: ((...args: never[]) => unknown) | null | undefined
|
|
92
92
|
): ((...args: never[]) => unknown) | undefined {
|
|
93
93
|
// Untagged controls keep their original handler rather than gaining a
|
|
94
94
|
// closure per render for nothing.
|