@oxy.so/services 1.1.0 → 2.0.0
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 +1 -1
- package/lib/commonjs/ui/components/BenefitList.js +93 -0
- package/lib/commonjs/ui/components/BenefitList.js.map +1 -0
- package/lib/commonjs/ui/components/FollowTargetButton.js +1 -1
- package/lib/commonjs/ui/components/FollowTargetButton.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAuthChooser.js +95 -140
- package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +8 -1
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/SignInEntryView.js +35 -25
- package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/SignInRequestView.js +2 -3
- package/lib/commonjs/ui/components/authChooser/SignInRequestView.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/SignUpView.js +12 -127
- package/lib/commonjs/ui/components/authChooser/SignUpView.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/signInFailureMessage.js +40 -0
- package/lib/commonjs/ui/components/authChooser/signInFailureMessage.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/types.js +4 -6
- package/lib/commonjs/ui/components/authChooser/types.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +1 -17
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +15 -2
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/passkeyFlow.js +2 -28
- package/lib/commonjs/ui/context/passkeyFlow.js.map +1 -1
- package/lib/commonjs/ui/hooks/accountDialogSnapshot.js +3 -1
- package/lib/commonjs/ui/hooks/accountDialogSnapshot.js.map +1 -1
- package/lib/commonjs/ui/hooks/useProfileEditing.js +14 -0
- package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountVerificationScreen.js +10 -10
- package/lib/commonjs/ui/screens/AccountVerificationScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +225 -9
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LearnMoreUsernamesScreen.js +6 -6
- package/lib/commonjs/ui/screens/LearnMoreUsernamesScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +10 -0
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +5 -5
- package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/trust/TrustAboutScreen.js +13 -13
- package/lib/commonjs/ui/screens/trust/TrustAboutScreen.js.map +1 -1
- package/lib/module/ui/components/BenefitList.js +89 -0
- package/lib/module/ui/components/BenefitList.js.map +1 -0
- package/lib/module/ui/components/FollowTargetButton.js +1 -1
- package/lib/module/ui/components/FollowTargetButton.js.map +1 -1
- package/lib/module/ui/components/OxyAuthChooser.js +97 -142
- package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/module/ui/components/authChooser/AccountsMenuView.js +8 -1
- package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -1
- package/lib/module/ui/components/authChooser/SignInEntryView.js +35 -25
- package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -1
- package/lib/module/ui/components/authChooser/SignInRequestView.js +2 -3
- package/lib/module/ui/components/authChooser/SignInRequestView.js.map +1 -1
- package/lib/module/ui/components/authChooser/SignUpView.js +14 -129
- package/lib/module/ui/components/authChooser/SignUpView.js.map +1 -1
- package/lib/module/ui/components/authChooser/signInFailureMessage.js +36 -0
- package/lib/module/ui/components/authChooser/signInFailureMessage.js.map +1 -0
- package/lib/module/ui/components/authChooser/types.js +4 -6
- package/lib/module/ui/components/authChooser/types.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +2 -18
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +15 -2
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/passkeyFlow.js +2 -27
- package/lib/module/ui/context/passkeyFlow.js.map +1 -1
- package/lib/module/ui/hooks/accountDialogSnapshot.js +3 -1
- package/lib/module/ui/hooks/accountDialogSnapshot.js.map +1 -1
- package/lib/module/ui/hooks/useProfileEditing.js +14 -0
- package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/module/ui/screens/AccountVerificationScreen.js +6 -6
- package/lib/module/ui/screens/AccountVerificationScreen.js.map +1 -1
- package/lib/module/ui/screens/EditProfileFieldScreen.js +225 -9
- package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/module/ui/screens/LearnMoreUsernamesScreen.js +6 -6
- package/lib/module/ui/screens/LearnMoreUsernamesScreen.js.map +1 -1
- package/lib/module/ui/screens/ManageAccountScreen.js +12 -2
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js +1 -1
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
- package/lib/module/ui/screens/trust/TrustAboutScreen.js +7 -7
- package/lib/module/ui/screens/trust/TrustAboutScreen.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/BenefitList.d.ts +25 -0
- package/lib/typescript/commonjs/ui/components/BenefitList.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +3 -4
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts +11 -12
- package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts +10 -13
- package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/signInFailureMessage.d.ts +16 -0
- package/lib/typescript/commonjs/ui/components/authChooser/signInFailureMessage.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +11 -13
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +3 -2
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +20 -10
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/passkeyFlow.d.ts +2 -21
- package/lib/typescript/commonjs/ui/context/passkeyFlow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +3 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts +1 -0
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/BenefitList.d.ts +25 -0
- package/lib/typescript/module/ui/components/BenefitList.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +3 -4
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts +11 -12
- package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts +10 -13
- package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/signInFailureMessage.d.ts +16 -0
- package/lib/typescript/module/ui/components/authChooser/signInFailureMessage.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/types.d.ts +11 -13
- package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +3 -2
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +20 -10
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/passkeyFlow.d.ts +2 -21
- package/lib/typescript/module/ui/context/passkeyFlow.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +3 -0
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts +1 -0
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +1 -1
- package/src/ui/components/BenefitList.tsx +111 -0
- package/src/ui/components/FollowTargetButton.tsx +1 -1
- package/src/ui/components/OxyAuthChooser.tsx +106 -156
- package/src/ui/components/authChooser/AccountsMenuView.tsx +7 -0
- package/src/ui/components/authChooser/SignInEntryView.tsx +41 -44
- package/src/ui/components/authChooser/SignInRequestView.tsx +1 -4
- package/src/ui/components/authChooser/SignUpView.tsx +16 -114
- package/src/ui/components/authChooser/signInFailureMessage.ts +39 -0
- package/src/ui/components/authChooser/types.ts +11 -13
- package/src/ui/context/OxyContext.tsx +2 -20
- package/src/ui/context/__tests__/passkeyFlow.test.ts +2 -65
- package/src/ui/context/hooks/useAuthOperations.ts +10 -5
- package/src/ui/context/oxyContextTypes.ts +16 -8
- package/src/ui/context/passkeyFlow.ts +2 -39
- package/src/ui/hooks/accountDialogSnapshot.ts +2 -0
- package/src/ui/hooks/useProfileEditing.ts +15 -0
- package/src/ui/screens/AccountVerificationScreen.tsx +6 -6
- package/src/ui/screens/EditProfileFieldScreen.tsx +202 -7
- package/src/ui/screens/LearnMoreUsernamesScreen.tsx +6 -6
- package/src/ui/screens/ManageAccountScreen.tsx +12 -0
- package/src/ui/screens/PremiumSubscriptionScreen.tsx +1 -1
- package/src/ui/screens/trust/TrustAboutScreen.tsx +7 -7
- package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js +0 -50
- package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js.map +0 -1
- package/lib/module/ui/components/authChooser/useUsernameAvailability.js +0 -46
- package/lib/module/ui/components/authChooser/useUsernameAvailability.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts +0 -16
- package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts +0 -16
- package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts.map +0 -1
- package/src/ui/components/authChooser/useUsernameAvailability.ts +0 -60
|
@@ -1,37 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Account creation (`signup` view).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* Web: the account AND its self-custody identity are created at the identity
|
|
5
|
+
* origin (`id.oxy.so`), in the same window a passkey sign-in uses — never on
|
|
6
|
+
* this page, whatever its origin (one identity, two carriers). The sign-in
|
|
7
|
+
* entry's "create an account" link opens that window directly; this view is
|
|
8
|
+
* what `openAccountDialog('signup')` lands on, and offers the same one action.
|
|
9
|
+
*
|
|
10
|
+
* Native: Commons owns identity creation. Deep-link straight in when Commons is
|
|
11
|
+
* installed, else lead with the "Get Commons" acquisition CTA.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type React from 'react';
|
|
15
|
-
import {
|
|
16
|
-
import { TextInput, View } from 'react-native';
|
|
15
|
+
import { View } from 'react-native';
|
|
17
16
|
import { Button } from '@oxy.so/bloom/button';
|
|
18
17
|
import { Text } from '@oxy.so/bloom/typography';
|
|
19
|
-
import type { AccountDialogSnapshot
|
|
20
|
-
import {
|
|
21
|
-
import { Dividerish, SubtleLink } from './primitives';
|
|
18
|
+
import type { AccountDialogSnapshot } from '@oxy.so/core';
|
|
19
|
+
import { SubtleLink } from './primitives';
|
|
22
20
|
import { authChooserStyles as styles } from './styles';
|
|
23
21
|
import type { PasskeyMode, Theme, Translate } from './types';
|
|
24
|
-
import { useUsernameAvailability, type UsernameStatus } from './useUsernameAvailability';
|
|
25
22
|
|
|
26
23
|
interface SignUpViewProps {
|
|
27
24
|
snapshot: AccountDialogSnapshot;
|
|
28
25
|
theme: Theme;
|
|
29
26
|
t: Translate;
|
|
30
|
-
oxyServices: OxyServices;
|
|
31
27
|
passkeyMode: PasskeyMode;
|
|
32
|
-
|
|
33
|
-
createPending: boolean;
|
|
34
|
-
/** Open the auth.oxy.so hub popup (b2) — the 'hub' mode's only path. */
|
|
28
|
+
/** Open the identity-origin window — web's only creation path. */
|
|
35
29
|
onOpenHub: () => void;
|
|
36
30
|
/** Deep-link into Commons' own identity-creation screen (installed only). */
|
|
37
31
|
onCreateIdentityInCommons: () => void;
|
|
@@ -44,22 +38,13 @@ const SignUpView: React.FC<SignUpViewProps> = ({
|
|
|
44
38
|
snapshot,
|
|
45
39
|
theme,
|
|
46
40
|
t,
|
|
47
|
-
oxyServices,
|
|
48
41
|
passkeyMode,
|
|
49
|
-
onCreateWithPasskey,
|
|
50
|
-
createPending,
|
|
51
42
|
onOpenHub,
|
|
52
43
|
onCreateIdentityInCommons,
|
|
53
44
|
onGetCommons,
|
|
54
45
|
onBackToSignIn,
|
|
55
46
|
}) => {
|
|
56
|
-
|
|
57
|
-
const { status: usernameStatus, check: checkUsername } = useUsernameAvailability(oxyServices, t);
|
|
58
|
-
const canSubmit = username.trim().length >= 3 && usernameStatus === 'available' && !createPending;
|
|
59
|
-
|
|
60
|
-
// Native — Commons owns identity creation. Deep-link straight in when it is
|
|
61
|
-
// installed, else lead with the same "Get Commons" acquisition CTA.
|
|
62
|
-
if (!isWebBrowser()) {
|
|
47
|
+
if (passkeyMode === 'none') {
|
|
63
48
|
const commonsInstalled = snapshot.commonsAvailability === 'available';
|
|
64
49
|
return (
|
|
65
50
|
<View style={styles.centeredBlock}>
|
|
@@ -75,100 +60,17 @@ const SignUpView: React.FC<SignUpViewProps> = ({
|
|
|
75
60
|
);
|
|
76
61
|
}
|
|
77
62
|
|
|
78
|
-
// Web, first-party Oxy origin. Commons leads with a "Get Commons" CTA; passkey
|
|
79
|
-
// creation is the de-emphasized alternative underneath for someone who doesn't
|
|
80
|
-
// want to install anything — a secondary button introduced by its own "No
|
|
81
|
-
// Commons?" framing, the same subordination the sign-in surfaces use.
|
|
82
|
-
if (passkeyMode === 'direct') {
|
|
83
|
-
return (
|
|
84
|
-
<View style={styles.signInBlock}>
|
|
85
|
-
<Text style={[styles.qrHeadline, { color: theme.colors.text }]}>
|
|
86
|
-
{t('signup.commonsHeadline')}
|
|
87
|
-
</Text>
|
|
88
|
-
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
89
|
-
{t('signup.commonsExplainer')}
|
|
90
|
-
</Text>
|
|
91
|
-
<Button variant="primary" onPress={onGetCommons} style={styles.primaryButton}>
|
|
92
|
-
{t('accountSwitcher.getCommons')}
|
|
93
|
-
</Button>
|
|
94
|
-
|
|
95
|
-
<Dividerish theme={theme} label={t('signin.or')} />
|
|
96
|
-
|
|
97
|
-
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
98
|
-
{t('signup.passkeyAlternative')}
|
|
99
|
-
</Text>
|
|
100
|
-
<TextInput
|
|
101
|
-
style={[styles.usernameInput, { borderColor: theme.colors.border, color: theme.colors.text }]}
|
|
102
|
-
placeholder={t('signup.usernamePlaceholder')}
|
|
103
|
-
placeholderTextColor={theme.colors.textSecondary}
|
|
104
|
-
value={username}
|
|
105
|
-
autoCapitalize="none"
|
|
106
|
-
autoCorrect={false}
|
|
107
|
-
autoComplete="username"
|
|
108
|
-
onChangeText={(value) => {
|
|
109
|
-
setUsername(value);
|
|
110
|
-
checkUsername(value.trim());
|
|
111
|
-
}}
|
|
112
|
-
testID="signup-username-input"
|
|
113
|
-
/>
|
|
114
|
-
<UsernameStatusText status={usernameStatus} theme={theme} t={t} />
|
|
115
|
-
<Button
|
|
116
|
-
variant="secondary"
|
|
117
|
-
onPress={() => onCreateWithPasskey(username.trim())}
|
|
118
|
-
disabled={!canSubmit}
|
|
119
|
-
style={styles.secondaryButton}
|
|
120
|
-
testID="signup-create-button"
|
|
121
|
-
>
|
|
122
|
-
{createPending ? t('signup.creating') : t('signup.createAccount')}
|
|
123
|
-
</Button>
|
|
124
|
-
<SubtleLink label={t('signup.backToSignInLink')} theme={theme} onPress={onBackToSignIn} />
|
|
125
|
-
</View>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Web, non-Oxy origin (b2): a passkey ceremony cannot run locally here — the
|
|
130
|
-
// same WebAuthn RP-ID boundary the sign-in surfaces hit — so this opens the
|
|
131
|
-
// auth.oxy.so hub popup. Completing EITHER a sign-in OR a fresh sign-up there
|
|
132
|
-
// relays the resulting session back through the SAME poll/socket engine.
|
|
133
63
|
return (
|
|
134
64
|
<View style={styles.centeredBlock}>
|
|
135
65
|
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
136
|
-
{t('
|
|
66
|
+
{t('accountSwitcher.passkeyHint')}
|
|
137
67
|
</Text>
|
|
138
|
-
<Button variant="primary" onPress={onOpenHub} style={styles.primaryButton}>
|
|
139
|
-
{t('signup.
|
|
68
|
+
<Button variant="primary" onPress={onOpenHub} style={styles.primaryButton} testID="signup-open-identity">
|
|
69
|
+
{t('signup.createAccount')}
|
|
140
70
|
</Button>
|
|
141
71
|
<SubtleLink label={t('signup.backToSignInLink')} theme={theme} onPress={onBackToSignIn} />
|
|
142
72
|
</View>
|
|
143
73
|
);
|
|
144
74
|
};
|
|
145
75
|
|
|
146
|
-
/** The inline availability verdict. Network FAILURES are toasted, never shown here. */
|
|
147
|
-
const UsernameStatusText: React.FC<{ status: UsernameStatus; theme: Theme; t: Translate }> = ({
|
|
148
|
-
status,
|
|
149
|
-
theme,
|
|
150
|
-
t,
|
|
151
|
-
}) => {
|
|
152
|
-
if (status === 'idle') return null;
|
|
153
|
-
if (status === 'checking') {
|
|
154
|
-
return (
|
|
155
|
-
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
156
|
-
{t('signup.usernameChecking')}
|
|
157
|
-
</Text>
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
if (status === 'available') {
|
|
161
|
-
return (
|
|
162
|
-
<Text style={[styles.mutedText, { color: theme.colors.success }]}>
|
|
163
|
-
{t('signup.usernameAvailable')}
|
|
164
|
-
</Text>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
return (
|
|
168
|
-
<Text style={[styles.mutedText, { color: theme.colors.error }]}>
|
|
169
|
-
{t('signup.usernameTaken')}
|
|
170
|
-
</Text>
|
|
171
|
-
);
|
|
172
|
-
};
|
|
173
|
-
|
|
174
76
|
export default SignUpView;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The localized toast for a failed sign-in attempt.
|
|
3
|
+
*
|
|
4
|
+
* One responsibility: turn the controller's machine-readable
|
|
5
|
+
* `signIn.failure` into copy. The controller ships no user-facing prose — its
|
|
6
|
+
* raw `signIn.error` is English or a server string, kept for diagnostics only.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SignInFailureReason } from '@oxy.so/core';
|
|
10
|
+
import type { Translate } from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The toast for `failure`, or `null` when there is nothing to report: a
|
|
14
|
+
* `'cancelled'` attempt is the user's own choice (they closed the window), and
|
|
15
|
+
* the surface already offers "Try again" without scolding them for it.
|
|
16
|
+
*/
|
|
17
|
+
export function signInFailureMessage(
|
|
18
|
+
failure: SignInFailureReason | null,
|
|
19
|
+
t: Translate,
|
|
20
|
+
): string | null {
|
|
21
|
+
switch (failure) {
|
|
22
|
+
case 'cancelled':
|
|
23
|
+
return null;
|
|
24
|
+
case 'denied':
|
|
25
|
+
return t('accountSwitcher.signInFailures.denied');
|
|
26
|
+
case 'expired':
|
|
27
|
+
return t('accountSwitcher.signInFailures.expired');
|
|
28
|
+
case 'network':
|
|
29
|
+
return t('accountSwitcher.signInFailures.network');
|
|
30
|
+
case 'not-configured':
|
|
31
|
+
return t('accountSwitcher.signInFailures.notConfigured');
|
|
32
|
+
case 'unsupported-flow':
|
|
33
|
+
return t('accountSwitcher.signInFailures.unsupportedFlow');
|
|
34
|
+
case 'claim-failed':
|
|
35
|
+
return t('accountSwitcher.signInFailures.claimFailed');
|
|
36
|
+
default:
|
|
37
|
+
return t('accountSwitcher.signInFailures.generic');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -95,6 +95,11 @@ export interface AccountsMenuActions {
|
|
|
95
95
|
onPrivacy: () => void;
|
|
96
96
|
onTerms: () => void;
|
|
97
97
|
onSignOut: () => void;
|
|
98
|
+
/**
|
|
99
|
+
* Open the person's identity at the identity origin (recovery phrase,
|
|
100
|
+
* recovery, account deletion). Web only — `undefined` hides the row.
|
|
101
|
+
*/
|
|
102
|
+
onOpenIdentity?: () => void;
|
|
98
103
|
customItems: readonly {
|
|
99
104
|
key: string;
|
|
100
105
|
label: string;
|
|
@@ -104,15 +109,13 @@ export interface AccountsMenuActions {
|
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
/**
|
|
107
|
-
* Where a
|
|
112
|
+
* Where a passkey sign-in or sign-up can run from the current surface.
|
|
108
113
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* `'
|
|
112
|
-
* `'hub'` = any other web origin; it runs in the auth.oxy.so popup instead.
|
|
113
|
-
* `'none'` = native, where Commons owns identity.
|
|
114
|
+
* `'hub'` = web: it runs at the identity origin (`id.oxy.so`) in a popup, on
|
|
115
|
+
* every web origin, first-party or not.
|
|
116
|
+
* `'none'` = native, where Commons owns identity.
|
|
114
117
|
*/
|
|
115
|
-
export type PasskeyMode = '
|
|
118
|
+
export type PasskeyMode = 'hub' | 'none';
|
|
116
119
|
|
|
117
120
|
/**
|
|
118
121
|
* Everything that is NOT the sign-in surface's one primary action.
|
|
@@ -125,17 +128,12 @@ export type PasskeyMode = 'direct' | 'hub' | 'none';
|
|
|
125
128
|
export interface SignInAlternatives {
|
|
126
129
|
/** Whether a WebAuthn ceremony is reachable from this origin at all. */
|
|
127
130
|
passkeyAvailable: boolean;
|
|
128
|
-
/**
|
|
129
|
-
* `true` while a DIRECT ceremony is in flight. The hub-popup variant reports
|
|
130
|
-
* its progress through `snapshot.signIn` instead, so it never sets this.
|
|
131
|
-
*/
|
|
132
|
-
passkeyPending: boolean;
|
|
133
131
|
onSignInWithPasskey: () => void;
|
|
134
132
|
/** Fall back to the cross-device QR handoff (restarts the request). */
|
|
135
133
|
onShowQr: () => void;
|
|
136
134
|
/** Open the Commons store listing / landing page for this platform. */
|
|
137
135
|
onGetCommons: () => void;
|
|
138
|
-
/**
|
|
136
|
+
/** Create an account: the identity-origin window on web, the Commons flow on native. */
|
|
139
137
|
onCreateAccount: () => void;
|
|
140
138
|
}
|
|
141
139
|
|
|
@@ -95,7 +95,7 @@ import { DEFAULT_SESSION_VALIDITY_MS } from './oxyContextHelpers';
|
|
|
95
95
|
// never dereferenced at module scope here.
|
|
96
96
|
import { useFollow } from '../hooks/useFollow';
|
|
97
97
|
import { commitDeviceSetAndResolve } from './commitSessionFlow';
|
|
98
|
-
import { runPasskeyLogin,
|
|
98
|
+
import { runPasskeyLogin, runPasskeyAdd } from './passkeyFlow';
|
|
99
99
|
import {
|
|
100
100
|
isPasskeySupported,
|
|
101
101
|
runRegistrationCeremony,
|
|
@@ -104,7 +104,7 @@ import {
|
|
|
104
104
|
import { queryKeys } from '../hooks/queries/queryKeys';
|
|
105
105
|
import { useOxyAccountGraph } from './useOxyAccountGraph';
|
|
106
106
|
|
|
107
|
-
export type { OxyContextState, OxyRuntimeProviderProps } from './oxyContextTypes';
|
|
107
|
+
export type { LogoutResult, OxyContextState, OxyRuntimeProviderProps } from './oxyContextTypes';
|
|
108
108
|
|
|
109
109
|
const OxyRuntimeContext = createContext<OxyContextState | null>(null);
|
|
110
110
|
|
|
@@ -959,22 +959,6 @@ export const OxyRuntimeProvider: React.FC<OxyRuntimeProviderProps> = ({
|
|
|
959
959
|
[oxyServices, authStore, commitSession],
|
|
960
960
|
);
|
|
961
961
|
|
|
962
|
-
// Create a brand-new account whose first auth method is a passkey.
|
|
963
|
-
const registerWithPasskey = useCallback(
|
|
964
|
-
async (params: { username: string; deviceName?: string }): Promise<void> => {
|
|
965
|
-
await runPasskeyRegister({
|
|
966
|
-
isSupported: isPasskeySupported,
|
|
967
|
-
getRegisterOptions: (username) => oxyServices.webauthnRegisterOptions(username),
|
|
968
|
-
runCeremony: runRegistrationCeremony,
|
|
969
|
-
registerVerify: (response, envelope) => oxyServices.webauthnRegisterVerify(response, envelope),
|
|
970
|
-
commit: (input) => commitSession(input, { activate: true }),
|
|
971
|
-
username: params.username,
|
|
972
|
-
deviceName: params.deviceName,
|
|
973
|
-
});
|
|
974
|
-
},
|
|
975
|
-
[oxyServices, commitSession],
|
|
976
|
-
);
|
|
977
|
-
|
|
978
962
|
// Add a passkey to the already-signed-in account (bearer present). No new
|
|
979
963
|
// session is committed — just refresh the linked auth-methods list.
|
|
980
964
|
const addPasskey = useCallback(
|
|
@@ -1265,7 +1249,6 @@ export const OxyRuntimeProvider: React.FC<OxyRuntimeProviderProps> = ({
|
|
|
1265
1249
|
getPublicKey,
|
|
1266
1250
|
signIn,
|
|
1267
1251
|
signInWithPasskey,
|
|
1268
|
-
registerWithPasskey,
|
|
1269
1252
|
addPasskey,
|
|
1270
1253
|
removePasskey,
|
|
1271
1254
|
revokeSuspiciousSignIn,
|
|
@@ -1323,7 +1306,6 @@ export const OxyRuntimeProvider: React.FC<OxyRuntimeProviderProps> = ({
|
|
|
1323
1306
|
getPublicKey,
|
|
1324
1307
|
signIn,
|
|
1325
1308
|
signInWithPasskey,
|
|
1326
|
-
registerWithPasskey,
|
|
1327
1309
|
addPasskey,
|
|
1328
1310
|
removePasskey,
|
|
1329
1311
|
revokeSuspiciousSignIn,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `passkeyFlow` — the pure, deps-injected passkey (WebAuthn) orchestration that
|
|
3
|
-
* backs `useOxy().signInWithPasskey` / `
|
|
3
|
+
* backs `useOxy().signInWithPasskey` / `addPasskey`.
|
|
4
4
|
*
|
|
5
5
|
* These tests assert the fixed `options → ceremony → verify → commit` ordering,
|
|
6
6
|
* the exact `commitSession` input projected from a session-arm login result, the
|
|
@@ -12,11 +12,9 @@
|
|
|
12
12
|
import type { LoginSessionResult } from '@oxy.so/contracts';
|
|
13
13
|
import {
|
|
14
14
|
runPasskeyLogin,
|
|
15
|
-
|
|
16
|
-
runPasskeyAdd,
|
|
15
|
+
runPasskeyAdd,
|
|
17
16
|
PASSKEY_UNSUPPORTED_MESSAGE,
|
|
18
17
|
type RunPasskeyLoginDeps,
|
|
19
|
-
type RunPasskeyRegisterDeps,
|
|
20
18
|
type RunPasskeyAddDeps,
|
|
21
19
|
type PasskeyRegisterVerifyResult,
|
|
22
20
|
} from '../passkeyFlow';
|
|
@@ -122,67 +120,6 @@ describe('runPasskeyLogin', () => {
|
|
|
122
120
|
});
|
|
123
121
|
});
|
|
124
122
|
|
|
125
|
-
describe('runPasskeyRegister', () => {
|
|
126
|
-
const buildDeps = (
|
|
127
|
-
order: string[],
|
|
128
|
-
overrides: Partial<RunPasskeyRegisterDeps> = {},
|
|
129
|
-
): RunPasskeyRegisterDeps => ({
|
|
130
|
-
isSupported: jest.fn(() => true),
|
|
131
|
-
getRegisterOptions: jest.fn(async (username) => {
|
|
132
|
-
order.push('getRegisterOptions');
|
|
133
|
-
expect(username).toBe('newuser');
|
|
134
|
-
return { challenge: 'reg-opts' };
|
|
135
|
-
}),
|
|
136
|
-
runCeremony: jest.fn(async () => {
|
|
137
|
-
order.push('runCeremony');
|
|
138
|
-
return { id: 'attestation' };
|
|
139
|
-
}),
|
|
140
|
-
registerVerify: jest.fn(async () => {
|
|
141
|
-
order.push('registerVerify');
|
|
142
|
-
return sessionResult;
|
|
143
|
-
}),
|
|
144
|
-
commit: jest.fn(async () => {
|
|
145
|
-
order.push('commit');
|
|
146
|
-
}),
|
|
147
|
-
username: 'newuser',
|
|
148
|
-
deviceName: 'My Laptop',
|
|
149
|
-
...overrides,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('creates the account (signup branch) and commits the minted session', async () => {
|
|
153
|
-
const order: string[] = [];
|
|
154
|
-
const deps = buildDeps(order);
|
|
155
|
-
|
|
156
|
-
await runPasskeyRegister(deps);
|
|
157
|
-
|
|
158
|
-
expect(order).toEqual(['getRegisterOptions', 'runCeremony', 'registerVerify', 'commit']);
|
|
159
|
-
expect(deps.registerVerify).toHaveBeenCalledWith(
|
|
160
|
-
{ id: 'attestation' },
|
|
161
|
-
{ username: 'newuser', deviceName: 'My Laptop' },
|
|
162
|
-
);
|
|
163
|
-
expect(deps.commit).toHaveBeenCalledWith(expectedCommitInput);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('throws (no commit) when verify returns a link branch instead of a session', async () => {
|
|
167
|
-
const order: string[] = [];
|
|
168
|
-
const deps = buildDeps(order, {
|
|
169
|
-
registerVerify: jest.fn(async () => linkResult),
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
await expect(runPasskeyRegister(deps)).rejects.toThrow(/did not establish a session/i);
|
|
173
|
-
expect(deps.commit).not.toHaveBeenCalled();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('throws and touches nothing when passkeys are unsupported', async () => {
|
|
177
|
-
const order: string[] = [];
|
|
178
|
-
const deps = buildDeps(order, { isSupported: jest.fn(() => false) });
|
|
179
|
-
|
|
180
|
-
await expect(runPasskeyRegister(deps)).rejects.toThrow(PASSKEY_UNSUPPORTED_MESSAGE);
|
|
181
|
-
expect(deps.getRegisterOptions).not.toHaveBeenCalled();
|
|
182
|
-
expect(deps.commit).not.toHaveBeenCalled();
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
123
|
describe('runPasskeyAdd', () => {
|
|
187
124
|
const buildDeps = (
|
|
188
125
|
order: string[],
|
|
@@ -2,6 +2,7 @@ import { useCallback } from 'react';
|
|
|
2
2
|
import type { ApiError, AuthStateStore, IdentityBinding, SessionClient, User } from '@oxy.so/core';
|
|
3
3
|
import type { ClientSession, SessionLoginResponse } from '@oxy.so/core';
|
|
4
4
|
import type { OxyRuntime } from '../../runtime';
|
|
5
|
+
import type { LogoutResult } from '../oxyContextTypes';
|
|
5
6
|
import { DeviceManager } from '@oxy.so/core';
|
|
6
7
|
import { fetchSessionsWithFallback } from '../../utils/sessionHelpers';
|
|
7
8
|
import { handleAuthError, isInvalidSessionError } from '../../utils/errorHandlers';
|
|
@@ -56,8 +57,8 @@ export interface UseAuthOperationsOptions {
|
|
|
56
57
|
export interface UseAuthOperationsResult {
|
|
57
58
|
/** Sign in with existing public key */
|
|
58
59
|
signIn: (publicKey: string, deviceName?: string) => Promise<User>;
|
|
59
|
-
/** Logout from current session */
|
|
60
|
-
logout: (targetSessionId?: string) => Promise<
|
|
60
|
+
/** Logout from current session. Never rejects — see {@link LogoutResult}. */
|
|
61
|
+
logout: (targetSessionId?: string) => Promise<LogoutResult>;
|
|
61
62
|
/** Logout from all sessions */
|
|
62
63
|
logoutAll: () => Promise<void>;
|
|
63
64
|
}
|
|
@@ -292,9 +293,9 @@ export const useAuthOperations = ({
|
|
|
292
293
|
* Logout from session
|
|
293
294
|
*/
|
|
294
295
|
const logout = useCallback(
|
|
295
|
-
async (targetSessionId?: string): Promise<
|
|
296
|
+
async (targetSessionId?: string): Promise<LogoutResult> => {
|
|
296
297
|
const activeSessionId = runtime.getSnapshot().activeSessionId;
|
|
297
|
-
if (!activeSessionId) return;
|
|
298
|
+
if (!activeSessionId) return { status: 'signed-out' };
|
|
298
299
|
|
|
299
300
|
const sessionToLogout = targetSessionId || activeSessionId;
|
|
300
301
|
|
|
@@ -326,6 +327,7 @@ export const useAuthOperations = ({
|
|
|
326
327
|
clearPersistedAuthSafe(store, logger);
|
|
327
328
|
await clearSessionState();
|
|
328
329
|
}
|
|
330
|
+
return { status: 'signed-out' };
|
|
329
331
|
} catch (error) {
|
|
330
332
|
const isInvalid = isInvalidSessionError(error);
|
|
331
333
|
|
|
@@ -337,7 +339,7 @@ export const useAuthOperations = ({
|
|
|
337
339
|
// The active session is invalid → full sign-out; clear persisted state.
|
|
338
340
|
clearPersistedAuthSafe(store, logger);
|
|
339
341
|
await clearSessionState();
|
|
340
|
-
return;
|
|
342
|
+
return { status: 'signed-out' };
|
|
341
343
|
}
|
|
342
344
|
|
|
343
345
|
handleAuthError(error, {
|
|
@@ -348,6 +350,9 @@ export const useAuthOperations = ({
|
|
|
348
350
|
logger,
|
|
349
351
|
status: isInvalid ? 401 : undefined,
|
|
350
352
|
});
|
|
353
|
+
// Nothing was revoked and the session is still installed here. Say so,
|
|
354
|
+
// rather than letting a caller close its surface as if it had worked.
|
|
355
|
+
return { status: 'failed', error };
|
|
351
356
|
}
|
|
352
357
|
},
|
|
353
358
|
[
|
|
@@ -11,6 +11,19 @@ import type {
|
|
|
11
11
|
import type { WebAuthMode, WebOAuthSignInResult } from '../oauth/types';
|
|
12
12
|
import type { StorageInterface } from '../utils/storageHelpers';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The outcome of `logout()`. It never rejects: a caller reads this instead.
|
|
16
|
+
*
|
|
17
|
+
* - `signed-out` — the server confirmed the sign-out, or the session was
|
|
18
|
+
* already invalid and was cleaned up locally. Either way, this device no
|
|
19
|
+
* longer holds it.
|
|
20
|
+
* - `failed` — the revocation did not go through (typically no connection).
|
|
21
|
+
* The session is still installed on this device, and nothing about the
|
|
22
|
+
* outcome may be presented as revoked. The error is also reported through
|
|
23
|
+
* the provider's `onError`, as before.
|
|
24
|
+
*/
|
|
25
|
+
export type LogoutResult = { status: 'signed-out' } | { status: 'failed'; error: unknown };
|
|
26
|
+
|
|
14
27
|
export interface OxyContextState {
|
|
15
28
|
user: User | null;
|
|
16
29
|
sessions: ClientSession[];
|
|
@@ -75,12 +88,6 @@ export interface OxyContextState {
|
|
|
75
88
|
deviceFingerprint?: string;
|
|
76
89
|
}) => Promise<void>;
|
|
77
90
|
|
|
78
|
-
/**
|
|
79
|
-
* Create a brand-new account whose first authentication method is a passkey.
|
|
80
|
-
* WEB-ONLY — throws on native or an unsupported browser.
|
|
81
|
-
*/
|
|
82
|
-
registerWithPasskey: (params: { username: string; deviceName?: string }) => Promise<void>;
|
|
83
|
-
|
|
84
91
|
/**
|
|
85
92
|
* Add a passkey to the already-signed-in account (bearer present). Does NOT
|
|
86
93
|
* commit a new session; refreshes the linked auth-methods list on success.
|
|
@@ -125,10 +132,11 @@ export interface OxyContextState {
|
|
|
125
132
|
*/
|
|
126
133
|
requestOAuthConsent: (options: RequestOAuthConsentOptions) => Promise<OAuthConsentResult>;
|
|
127
134
|
|
|
128
|
-
logout: (targetSessionId?: string) => Promise<
|
|
135
|
+
logout: (targetSessionId?: string) => Promise<LogoutResult>;
|
|
129
136
|
logoutAll: () => Promise<void>;
|
|
130
137
|
switchSession: (sessionId: string) => Promise<User>;
|
|
131
|
-
|
|
138
|
+
/** The same operation as `logout(sessionId)`, with the same {@link LogoutResult}. */
|
|
139
|
+
removeSession: (sessionId: string) => Promise<LogoutResult>;
|
|
132
140
|
refreshSessions: () => Promise<void>;
|
|
133
141
|
setLanguage: (languageId: string) => Promise<void>;
|
|
134
142
|
getDeviceSessions: () => Promise<
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* Extracted from `OxyContext` so the fixed `options → ceremony → verify → commit`
|
|
5
5
|
* ordering is unit-testable with injected deps — the exact pattern
|
|
6
6
|
* `commitSessionFlow.ts` (`commitDeviceSetAndResolve`) uses. The context methods
|
|
7
|
-
* (`signInWithPasskey` / `
|
|
7
|
+
* (`signInWithPasskey` / `addPasskey`) are thin wrappers
|
|
8
8
|
* that supply the real deps: the core `webauthn*` methods, the platform ceremony
|
|
9
9
|
* client (`webauthn/passkeyClient`), and the internal `commitSession` funnel.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Both GATE on `isSupported()` first (the platform client returns `false`
|
|
12
12
|
* off the web) so an unsupported surface fails loudly before touching a ceremony.
|
|
13
13
|
*/
|
|
14
14
|
|
|
@@ -84,43 +84,6 @@ export async function runPasskeyLogin(deps: RunPasskeyLoginDeps): Promise<void>
|
|
|
84
84
|
await deps.commit(toCommitInput(result));
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/** Injected dependencies for {@link runPasskeyRegister}. */
|
|
88
|
-
export interface RunPasskeyRegisterDeps {
|
|
89
|
-
isSupported: () => boolean;
|
|
90
|
-
getRegisterOptions: (username: string) => Promise<unknown>;
|
|
91
|
-
runCeremony: (optionsJSON: unknown) => Promise<unknown>;
|
|
92
|
-
registerVerify: (
|
|
93
|
-
response: unknown,
|
|
94
|
-
envelope: { username: string; deviceName?: string },
|
|
95
|
-
) => Promise<PasskeyRegisterVerifyResult>;
|
|
96
|
-
commit: (input: CommitInput) => Promise<void>;
|
|
97
|
-
username: string;
|
|
98
|
-
deviceName?: string;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Passkey SIGNUP: create a brand-new account whose first auth method is a
|
|
103
|
-
* passkey. The verify signup branch mints a session (a {@link LoginSessionResult}
|
|
104
|
-
* carrying `sessionId`), which is committed exactly like a password signup.
|
|
105
|
-
*/
|
|
106
|
-
export async function runPasskeyRegister(deps: RunPasskeyRegisterDeps): Promise<void> {
|
|
107
|
-
if (!deps.isSupported()) {
|
|
108
|
-
throw new Error(PASSKEY_UNSUPPORTED_MESSAGE);
|
|
109
|
-
}
|
|
110
|
-
const options = await deps.getRegisterOptions(deps.username);
|
|
111
|
-
const response = await deps.runCeremony(options);
|
|
112
|
-
const result = await deps.registerVerify(response, {
|
|
113
|
-
username: deps.username,
|
|
114
|
-
deviceName: deps.deviceName,
|
|
115
|
-
});
|
|
116
|
-
// Only the signup session arm carries `sessionId`; the link branch
|
|
117
|
-
// (`{ success, message }`) does not.
|
|
118
|
-
if (!('sessionId' in result)) {
|
|
119
|
-
throw new Error('Passkey registration did not establish a session.');
|
|
120
|
-
}
|
|
121
|
-
await deps.commit(toCommitInput(result));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
87
|
/** Injected dependencies for {@link runPasskeyAdd}. */
|
|
125
88
|
export interface RunPasskeyAddDeps {
|
|
126
89
|
isSupported: () => boolean;
|
|
@@ -27,11 +27,13 @@ export const EMPTY_ACCOUNT_DIALOG_SNAPSHOT: AccountDialogSnapshot = {
|
|
|
27
27
|
qrPayload: null,
|
|
28
28
|
expiresAt: null,
|
|
29
29
|
error: null,
|
|
30
|
+
failure: null,
|
|
30
31
|
route: null,
|
|
31
32
|
routeFailed: false,
|
|
32
33
|
pushSentAt: null,
|
|
33
34
|
openedAt: null,
|
|
34
35
|
progress: 'idle',
|
|
36
|
+
attempt: 0,
|
|
35
37
|
},
|
|
36
38
|
commonsAvailability: 'unknown',
|
|
37
39
|
};
|
|
@@ -31,6 +31,7 @@ export interface ProfileUpdateData {
|
|
|
31
31
|
phone?: string;
|
|
32
32
|
address?: string;
|
|
33
33
|
birthday?: string;
|
|
34
|
+
dateOfBirth?: string;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
type ProfileFieldValue = string | ProfileLocation[] | ProfileLinkMetadata[];
|
|
@@ -87,6 +88,13 @@ export const useProfileEditing = () => {
|
|
|
87
88
|
if (updates.birthday !== undefined) {
|
|
88
89
|
updateData.birthday = updates.birthday;
|
|
89
90
|
}
|
|
91
|
+
if (updates.dateOfBirth !== undefined) {
|
|
92
|
+
// '' clears it — `updateData.dateOfBirth` is `string | null`
|
|
93
|
+
// per the contract, and there is no separate blank sentinel for
|
|
94
|
+
// a structured date the way `blankToNull` gives every free-text
|
|
95
|
+
// field.
|
|
96
|
+
updateData.dateOfBirth = updates.dateOfBirth === '' ? null : updates.dateOfBirth;
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
// Handle name field
|
|
92
100
|
if (updates.firstName !== undefined || updates.lastName !== undefined) {
|
|
@@ -142,6 +150,13 @@ export const useProfileEditing = () => {
|
|
|
142
150
|
if (typeof value !== 'string') return false;
|
|
143
151
|
updates.birthday = value;
|
|
144
152
|
break;
|
|
153
|
+
case 'dateOfBirth':
|
|
154
|
+
// '' clears it (the picker's "no complete date selected"
|
|
155
|
+
// state) — `updateUserProfile`'s `dateOfBirth` block treats
|
|
156
|
+
// an empty string the same as `null`.
|
|
157
|
+
if (typeof value !== 'string') return false;
|
|
158
|
+
updates.dateOfBirth = value;
|
|
159
|
+
break;
|
|
145
160
|
case 'location':
|
|
146
161
|
if (!isProfileLocationArray(value)) return false;
|
|
147
162
|
updates.locations = value;
|