@oxy.so/services 1.0.3 → 1.2.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/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/LanguageBridge.js +51 -0
- package/lib/commonjs/ui/components/LanguageBridge.js.map +1 -0
- package/lib/commonjs/ui/components/OxyAuthChooser.js +95 -140
- package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/commonjs/ui/components/OxyConsentScreen.js +132 -2
- package/lib/commonjs/ui/components/OxyConsentScreen.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +19 -1
- package/lib/commonjs/ui/components/OxyProvider.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/hooks/useAuthOperations.js +15 -2
- package/lib/commonjs/ui/context/hooks/useAuthOperations.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/EditProfileFieldScreen.js +225 -9
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.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/module/index.js.map +1 -1
- package/lib/module/ui/components/LanguageBridge.js +47 -0
- package/lib/module/ui/components/LanguageBridge.js.map +1 -0
- package/lib/module/ui/components/OxyAuthChooser.js +97 -142
- package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/module/ui/components/OxyConsentScreen.js +132 -2
- package/lib/module/ui/components/OxyConsentScreen.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +19 -1
- package/lib/module/ui/components/OxyProvider.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/hooks/useAuthOperations.js +15 -2
- package/lib/module/ui/context/hooks/useAuthOperations.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/EditProfileFieldScreen.js +225 -9
- package/lib/module/ui/screens/EditProfileFieldScreen.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/typescript/commonjs/index.d.ts +2 -2
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/LanguageBridge.d.ts +19 -0
- package/lib/typescript/commonjs/ui/components/LanguageBridge.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/OxyConsentScreen.d.ts +17 -1
- package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +14 -0
- package/lib/typescript/commonjs/ui/components/OxyProvider.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 -2
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.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/commonjs/ui/types/navigation.d.ts +29 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -2
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/LanguageBridge.d.ts +19 -0
- package/lib/typescript/module/ui/components/LanguageBridge.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/OxyConsentScreen.d.ts +17 -1
- package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyProvider.d.ts +14 -0
- package/lib/typescript/module/ui/components/OxyProvider.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 -2
- package/lib/typescript/module/ui/context/oxyContextTypes.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/lib/typescript/module/ui/types/navigation.d.ts +29 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/index.ts +2 -1
- package/src/ui/components/LanguageBridge.tsx +44 -0
- package/src/ui/components/OxyAuthChooser.tsx +106 -156
- package/src/ui/components/OxyConsentScreen.tsx +127 -0
- package/src/ui/components/OxyProvider.tsx +17 -0
- package/src/ui/components/__tests__/LanguageBridge.test.tsx +81 -0
- package/src/ui/components/__tests__/OxyConsentScreen.test.tsx +32 -0
- 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 +1 -1
- package/src/ui/context/hooks/useAuthOperations.ts +10 -5
- package/src/ui/context/oxyContextTypes.ts +16 -2
- package/src/ui/hooks/accountDialogSnapshot.ts +2 -0
- package/src/ui/hooks/useProfileEditing.ts +15 -0
- package/src/ui/screens/EditProfileFieldScreen.tsx +202 -7
- package/src/ui/screens/ManageAccountScreen.tsx +12 -0
- package/src/ui/types/navigation.ts +30 -0
- 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
|
@@ -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[];
|
|
@@ -125,10 +138,11 @@ export interface OxyContextState {
|
|
|
125
138
|
*/
|
|
126
139
|
requestOAuthConsent: (options: RequestOAuthConsentOptions) => Promise<OAuthConsentResult>;
|
|
127
140
|
|
|
128
|
-
logout: (targetSessionId?: string) => Promise<
|
|
141
|
+
logout: (targetSessionId?: string) => Promise<LogoutResult>;
|
|
129
142
|
logoutAll: () => Promise<void>;
|
|
130
143
|
switchSession: (sessionId: string) => Promise<User>;
|
|
131
|
-
|
|
144
|
+
/** The same operation as `logout(sessionId)`, with the same {@link LogoutResult}. */
|
|
145
|
+
removeSession: (sessionId: string) => Promise<LogoutResult>;
|
|
132
146
|
refreshSessions: () => Promise<void>;
|
|
133
147
|
setLanguage: (languageId: string) => Promise<void>;
|
|
134
148
|
getDeviceSessions: () => Promise<
|
|
@@ -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;
|
|
@@ -12,6 +12,16 @@ import { useTheme } from '@oxy.so/bloom/theme';
|
|
|
12
12
|
import { Text } from '@oxy.so/bloom/typography';
|
|
13
13
|
import { Button } from '@oxy.so/bloom/button';
|
|
14
14
|
import { TextField, TextFieldInput } from '@oxy.so/bloom/text-field';
|
|
15
|
+
import {
|
|
16
|
+
Select,
|
|
17
|
+
SelectContent,
|
|
18
|
+
SelectIcon,
|
|
19
|
+
SelectItem,
|
|
20
|
+
SelectItemIndicator,
|
|
21
|
+
SelectItemText,
|
|
22
|
+
SelectTrigger,
|
|
23
|
+
SelectValue,
|
|
24
|
+
} from '@oxy.so/bloom/select';
|
|
15
25
|
import { normalizeTheme } from '@oxy.so/core';
|
|
16
26
|
import type { User } from '@oxy.so/core';
|
|
17
27
|
import { useI18n } from '../hooks/useI18n';
|
|
@@ -53,6 +63,67 @@ interface FieldConfig {
|
|
|
53
63
|
inputProps?: Partial<TextInputProps>;
|
|
54
64
|
}>;
|
|
55
65
|
isList?: boolean;
|
|
66
|
+
/** Renders `renderBirthdayPicker` instead of the plain-text `fields`. */
|
|
67
|
+
isDatePicker?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The earliest calendar year the birthday picker offers — matches the
|
|
72
|
+
* platform's own lower bound (`dateOfBirthSchema`, `@oxy.so/contracts`) so a
|
|
73
|
+
* value this picker can produce is never one the API would reject.
|
|
74
|
+
*/
|
|
75
|
+
const MIN_BIRTH_YEAR = 1900;
|
|
76
|
+
|
|
77
|
+
/** `MIN_BIRTH_YEAR` through the current year, most recent first. */
|
|
78
|
+
function birthYearOptions(): Array<{ value: string; label: string }> {
|
|
79
|
+
const currentYear = new Date().getUTCFullYear();
|
|
80
|
+
const years: Array<{ value: string; label: string }> = [];
|
|
81
|
+
for (let year = currentYear; year >= MIN_BIRTH_YEAR; year--) {
|
|
82
|
+
years.push({ value: String(year), label: String(year) });
|
|
83
|
+
}
|
|
84
|
+
return years;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Month names are not run through `t()` here: this SDK has no existing
|
|
89
|
+
* calendar-month translation catalogue to hook into, and inventing one is out
|
|
90
|
+
* of scope for wiring up the date picker itself. English month names are a
|
|
91
|
+
* known, deliberate limitation of this pass.
|
|
92
|
+
*/
|
|
93
|
+
const MONTH_OPTIONS: Array<{ value: string; label: string }> = [
|
|
94
|
+
{ value: '01', label: 'January' },
|
|
95
|
+
{ value: '02', label: 'February' },
|
|
96
|
+
{ value: '03', label: 'March' },
|
|
97
|
+
{ value: '04', label: 'April' },
|
|
98
|
+
{ value: '05', label: 'May' },
|
|
99
|
+
{ value: '06', label: 'June' },
|
|
100
|
+
{ value: '07', label: 'July' },
|
|
101
|
+
{ value: '08', label: 'August' },
|
|
102
|
+
{ value: '09', label: 'September' },
|
|
103
|
+
{ value: '10', label: 'October' },
|
|
104
|
+
{ value: '11', label: 'November' },
|
|
105
|
+
{ value: '12', label: 'December' },
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Real Gregorian days in `month` (1-12) of `year`, leap years included — the
|
|
110
|
+
* same rule `dateOfBirthSchema` (`@oxy.so/contracts`) applies server-side,
|
|
111
|
+
* written a second time here because a picker's OWN day list must bound
|
|
112
|
+
* itself before the value ever reaches it.
|
|
113
|
+
*/
|
|
114
|
+
function daysInMonth(year: number, month: number): number {
|
|
115
|
+
const isLeapYear = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
116
|
+
const DAYS_PER_MONTH = [31, isLeapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
117
|
+
return DAYS_PER_MONTH[month - 1];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Day options for the current year/month selection — 31 while either is unset. */
|
|
121
|
+
function dayOptions(year: string, month: string): Array<{ value: string; label: string }> {
|
|
122
|
+
const count = year && month ? daysInMonth(Number(year), Number(month)) : 31;
|
|
123
|
+
return Array.from({ length: count }, (_, index) => {
|
|
124
|
+
const day = String(index + 1).padStart(2, '0');
|
|
125
|
+
return { value: day, label: day };
|
|
126
|
+
});
|
|
56
127
|
}
|
|
57
128
|
|
|
58
129
|
interface EditProfileFieldScreenProps extends BaseScreenProps {
|
|
@@ -131,7 +202,13 @@ function buildInitialProfileState(
|
|
|
131
202
|
fieldValues.lastName = String(userData.lastName || userData.name?.last || '');
|
|
132
203
|
break;
|
|
133
204
|
case 'birthday':
|
|
134
|
-
|
|
205
|
+
// Seeded from `dateOfBirth` ONLY, never the legacy free-text
|
|
206
|
+
// `birthday` — a structured picker needs a real `YYYY-MM-DD`
|
|
207
|
+
// value to select against, and `birthday` has no guaranteed
|
|
208
|
+
// shape (see its own comment in `db/schema/users.ts`). An
|
|
209
|
+
// account whose `dateOfBirth` is not yet set opens the picker
|
|
210
|
+
// empty rather than seeded with a guess.
|
|
211
|
+
fieldValues.dateOfBirth = String(userData.dateOfBirth || '');
|
|
135
212
|
break;
|
|
136
213
|
case 'address':
|
|
137
214
|
fieldValues.address = String(userData.address || '');
|
|
@@ -322,17 +399,18 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
322
399
|
return {
|
|
323
400
|
title: t('editProfile.items.birthday.title') || 'Birthday',
|
|
324
401
|
subtitle: t('editProfile.items.birthday.subtitle') || 'Your date of birth',
|
|
402
|
+
// One entry, naming the field `handleSave`'s generic branch writes
|
|
403
|
+
// (`fieldConfig.fields[0]?.key`) — `renderBirthdayPicker` renders the
|
|
404
|
+
// actual day/month/year controls; this array is never mapped through
|
|
405
|
+
// `renderField` for this field type (see `isDatePicker` below).
|
|
325
406
|
fields: [
|
|
326
407
|
{
|
|
327
|
-
key: '
|
|
408
|
+
key: 'dateOfBirth',
|
|
328
409
|
label: t('editProfile.items.birthday.label') || 'Birthday',
|
|
329
410
|
placeholder: t('editProfile.items.birthday.placeholder') || 'YYYY-MM-DD',
|
|
330
|
-
inputProps: {
|
|
331
|
-
autoCapitalize: 'none',
|
|
332
|
-
autoCorrect: false,
|
|
333
|
-
},
|
|
334
411
|
},
|
|
335
412
|
],
|
|
413
|
+
isDatePicker: true,
|
|
336
414
|
};
|
|
337
415
|
case 'locations':
|
|
338
416
|
return {
|
|
@@ -364,6 +442,54 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
364
442
|
}
|
|
365
443
|
}, [fieldErrors]);
|
|
366
444
|
|
|
445
|
+
// The birthday picker's three pieces, seeded once from the same
|
|
446
|
+
// one-time-mount value `fieldValues.dateOfBirth` already carries (empty
|
|
447
|
+
// string parts when unset). Kept separate from `fieldValues` because a
|
|
448
|
+
// `Select` needs its OWN current value per control; each change handler
|
|
449
|
+
// below composes the three back into the single `dateOfBirth` field
|
|
450
|
+
// `handleSave` actually writes, in the event handler itself rather than
|
|
451
|
+
// an effect.
|
|
452
|
+
const [dobYear, setDobYear] = useState(() => (fieldValues.dateOfBirth || '').split('-')[0] || '');
|
|
453
|
+
const [dobMonth, setDobMonth] = useState(() => (fieldValues.dateOfBirth || '').split('-')[1] || '');
|
|
454
|
+
const [dobDay, setDobDay] = useState(() => (fieldValues.dateOfBirth || '').split('-')[2] || '');
|
|
455
|
+
|
|
456
|
+
// A partial selection (e.g. year and month but no day yet) writes ''`,
|
|
457
|
+
// which `updateField`/the API's `dateOfBirth` write path already treats
|
|
458
|
+
// as "clear" — the same behaviour a fully-erased text field would have
|
|
459
|
+
// had. There is deliberately no separate "incomplete date" error: the
|
|
460
|
+
// three controls are either all filled (a real date) or the field is
|
|
461
|
+
// unset, and both are valid states to save.
|
|
462
|
+
const commitDateOfBirth = useCallback((year: string, month: string, day: string) => {
|
|
463
|
+
handleFieldChange('dateOfBirth', year && month && day ? `${year}-${month}-${day}` : '');
|
|
464
|
+
}, [handleFieldChange]);
|
|
465
|
+
|
|
466
|
+
// Changing the year or month can strand a selected day past the new
|
|
467
|
+
// month's length (31 March -> April has none) — clamped down to the new
|
|
468
|
+
// last day rather than left pointing at a day `dayOptions` no longer
|
|
469
|
+
// lists, the same way a native date picker behaves.
|
|
470
|
+
const handleDobYearChange = useCallback((year: string) => {
|
|
471
|
+
const clampedDay = dobDay && dobMonth && Number(dobDay) > daysInMonth(Number(year), Number(dobMonth))
|
|
472
|
+
? String(daysInMonth(Number(year), Number(dobMonth))).padStart(2, '0')
|
|
473
|
+
: dobDay;
|
|
474
|
+
setDobYear(year);
|
|
475
|
+
setDobDay(clampedDay);
|
|
476
|
+
commitDateOfBirth(year, dobMonth, clampedDay);
|
|
477
|
+
}, [dobMonth, dobDay, commitDateOfBirth]);
|
|
478
|
+
|
|
479
|
+
const handleDobMonthChange = useCallback((month: string) => {
|
|
480
|
+
const clampedDay = dobDay && dobYear && Number(dobDay) > daysInMonth(Number(dobYear), Number(month))
|
|
481
|
+
? String(daysInMonth(Number(dobYear), Number(month))).padStart(2, '0')
|
|
482
|
+
: dobDay;
|
|
483
|
+
setDobMonth(month);
|
|
484
|
+
setDobDay(clampedDay);
|
|
485
|
+
commitDateOfBirth(dobYear, month, clampedDay);
|
|
486
|
+
}, [dobYear, dobDay, commitDateOfBirth]);
|
|
487
|
+
|
|
488
|
+
const handleDobDayChange = useCallback((day: string) => {
|
|
489
|
+
setDobDay(day);
|
|
490
|
+
commitDateOfBirth(dobYear, dobMonth, day);
|
|
491
|
+
}, [dobYear, dobMonth, commitDateOfBirth]);
|
|
492
|
+
|
|
367
493
|
// Validate all fields
|
|
368
494
|
const validateFields = useCallback((): boolean => {
|
|
369
495
|
const errors: Record<string, string | undefined> = {};
|
|
@@ -511,6 +637,71 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
511
637
|
);
|
|
512
638
|
};
|
|
513
639
|
|
|
640
|
+
// Render the birthday picker: three `Select`s (day, month, year) rather
|
|
641
|
+
// than a native date-picker dependency. Neither this app nor
|
|
642
|
+
// `@oxy.so/bloom` has one installed, and `Select` is already this
|
|
643
|
+
// design system's cross-platform choice control (a bottom sheet on
|
|
644
|
+
// native, an anchored dropdown on web — see its own docs) with no new
|
|
645
|
+
// dependency to add and no native linking to carry across platforms.
|
|
646
|
+
const renderBirthdayPicker = () => (
|
|
647
|
+
<View className="flex-row gap-space-8">
|
|
648
|
+
<View className="flex-1">
|
|
649
|
+
<Select value={dobMonth} onValueChange={handleDobMonthChange}>
|
|
650
|
+
<SelectTrigger label={t('editProfile.items.birthday.month') || 'Month'}>
|
|
651
|
+
<SelectValue placeholder={t('editProfile.items.birthday.month') || 'Month'} />
|
|
652
|
+
<SelectIcon />
|
|
653
|
+
</SelectTrigger>
|
|
654
|
+
<SelectContent
|
|
655
|
+
label={t('editProfile.items.birthday.month') || 'Month'}
|
|
656
|
+
items={MONTH_OPTIONS}
|
|
657
|
+
renderItem={(item) => (
|
|
658
|
+
<SelectItem value={item.value} label={item.label}>
|
|
659
|
+
<SelectItemIndicator />
|
|
660
|
+
<SelectItemText>{item.label}</SelectItemText>
|
|
661
|
+
</SelectItem>
|
|
662
|
+
)}
|
|
663
|
+
/>
|
|
664
|
+
</Select>
|
|
665
|
+
</View>
|
|
666
|
+
<View className="flex-1">
|
|
667
|
+
<Select value={dobDay} onValueChange={handleDobDayChange}>
|
|
668
|
+
<SelectTrigger label={t('editProfile.items.birthday.day') || 'Day'}>
|
|
669
|
+
<SelectValue placeholder={t('editProfile.items.birthday.day') || 'Day'} />
|
|
670
|
+
<SelectIcon />
|
|
671
|
+
</SelectTrigger>
|
|
672
|
+
<SelectContent
|
|
673
|
+
label={t('editProfile.items.birthday.day') || 'Day'}
|
|
674
|
+
items={dayOptions(dobYear, dobMonth)}
|
|
675
|
+
renderItem={(item) => (
|
|
676
|
+
<SelectItem value={item.value} label={item.label}>
|
|
677
|
+
<SelectItemIndicator />
|
|
678
|
+
<SelectItemText>{item.label}</SelectItemText>
|
|
679
|
+
</SelectItem>
|
|
680
|
+
)}
|
|
681
|
+
/>
|
|
682
|
+
</Select>
|
|
683
|
+
</View>
|
|
684
|
+
<View className="flex-1">
|
|
685
|
+
<Select value={dobYear} onValueChange={handleDobYearChange}>
|
|
686
|
+
<SelectTrigger label={t('editProfile.items.birthday.year') || 'Year'}>
|
|
687
|
+
<SelectValue placeholder={t('editProfile.items.birthday.year') || 'Year'} />
|
|
688
|
+
<SelectIcon />
|
|
689
|
+
</SelectTrigger>
|
|
690
|
+
<SelectContent
|
|
691
|
+
label={t('editProfile.items.birthday.year') || 'Year'}
|
|
692
|
+
items={birthYearOptions()}
|
|
693
|
+
renderItem={(item) => (
|
|
694
|
+
<SelectItem value={item.value} label={item.label}>
|
|
695
|
+
<SelectItemIndicator />
|
|
696
|
+
<SelectItemText>{item.label}</SelectItemText>
|
|
697
|
+
</SelectItem>
|
|
698
|
+
)}
|
|
699
|
+
/>
|
|
700
|
+
</Select>
|
|
701
|
+
</View>
|
|
702
|
+
</View>
|
|
703
|
+
);
|
|
704
|
+
|
|
514
705
|
// Render list content (locations or links)
|
|
515
706
|
const renderListContent = () => {
|
|
516
707
|
const addLabel = fieldType === 'locations'
|
|
@@ -590,7 +781,11 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
590
781
|
<View className="px-screen-margin pt-space-16 pb-space-32 gap-space-24">
|
|
591
782
|
{/* Form Content */}
|
|
592
783
|
<View className="gap-space-16 p-space-16 rounded-radius-20 bg-fill">
|
|
593
|
-
{fieldConfig.isList
|
|
784
|
+
{fieldConfig.isList
|
|
785
|
+
? renderListContent()
|
|
786
|
+
: fieldConfig.isDatePicker
|
|
787
|
+
? renderBirthdayPicker()
|
|
788
|
+
: fieldConfig.fields.map(renderField)}
|
|
594
789
|
</View>
|
|
595
790
|
</View>
|
|
596
791
|
);
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
View,
|
|
5
5
|
StyleSheet,
|
|
6
6
|
ActivityIndicator,
|
|
7
|
+
Linking,
|
|
7
8
|
Platform,
|
|
8
9
|
} from 'react-native';
|
|
9
10
|
import Ionicons from '../icons/Ionicons';
|
|
@@ -13,6 +14,7 @@ import { useTheme } from '@oxy.so/bloom/theme';
|
|
|
13
14
|
import { Text } from '@oxy.so/bloom/typography';
|
|
14
15
|
import { SettingsListGroup, SettingsListItem } from '@oxy.so/bloom/settings-list';
|
|
15
16
|
import {
|
|
17
|
+
IDENTITY_WEB_ORIGIN,
|
|
16
18
|
getAccountDisplayName,
|
|
17
19
|
getAccountFallbackHandle,
|
|
18
20
|
getNormalizedUserHandle,
|
|
@@ -27,6 +29,7 @@ import { presentActionSheet } from '../components/surfaces/ActionSheetSurface';
|
|
|
27
29
|
import { useOxy } from '../context/OxyContext';
|
|
28
30
|
import { useI18n } from '../hooks/useI18n';
|
|
29
31
|
import { useSurfaceHeader } from '../hooks/useSurfaceHeader';
|
|
32
|
+
import { isWebBrowser } from '../utils/isWebBrowser';
|
|
30
33
|
import { useCurrentUser } from '../hooks/queries/useAccountQueries';
|
|
31
34
|
import { useUserSubscription } from '../hooks/queries/usePaymentQueries';
|
|
32
35
|
import { useDeviceSessions } from '../hooks/queries/useServicesQueries';
|
|
@@ -326,6 +329,15 @@ const ManageAccountScreen: React.FC<BaseScreenProps> = ({
|
|
|
326
329
|
);
|
|
327
330
|
return;
|
|
328
331
|
}
|
|
332
|
+
// Web: deleting an account is signed with its identity key, which only
|
|
333
|
+
// the identity origin can unseal — the deletion happens there, never on
|
|
334
|
+
// this page.
|
|
335
|
+
if (isWebBrowser()) {
|
|
336
|
+
await Linking.openURL(`${IDENTITY_WEB_ORIGIN}/`).catch(() => {
|
|
337
|
+
toast.error(t('accountSwitcher.linkOpenFailed'));
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
329
341
|
const deleted = await presentDeleteAccount({
|
|
330
342
|
username: user.username || '',
|
|
331
343
|
onDelete: handleConfirmDelete,
|
|
@@ -172,4 +172,34 @@ export interface OxyProviderProps {
|
|
|
172
172
|
* @default 'persistent'
|
|
173
173
|
*/
|
|
174
174
|
deviceCredentialStorage?: 'persistent' | 'ephemeral';
|
|
175
|
+
/**
|
|
176
|
+
* Wires the app's OWN i18n library to Oxy's resolved language, so the
|
|
177
|
+
* account (or, signed out, the device/guest locale) drives the app's
|
|
178
|
+
* translated UI directly — no app-local effect or "sync" component to
|
|
179
|
+
* remember to mount. Oxy decides WHICH language; the app keeps owning its
|
|
180
|
+
* own translation catalogs and library (i18next, FormatJS, or anything
|
|
181
|
+
* else) and is told through `onChange` when to switch.
|
|
182
|
+
*
|
|
183
|
+
* Omitted entirely (the default) when an app has no i18n of its own, or
|
|
184
|
+
* manages it independently of the account/device locale.
|
|
185
|
+
*/
|
|
186
|
+
language?: OxyLanguageConfig;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface OxyLanguageConfig {
|
|
190
|
+
/** The exact locales this app ships a translation catalog for. */
|
|
191
|
+
supportedLocales: readonly string[];
|
|
192
|
+
/**
|
|
193
|
+
* Used when Oxy's resolved language matches none of `supportedLocales`,
|
|
194
|
+
* not even by base language (Oxy's account-locale catalog is broader
|
|
195
|
+
* than any one app's translations).
|
|
196
|
+
*/
|
|
197
|
+
fallbackLocale: string;
|
|
198
|
+
/**
|
|
199
|
+
* Called whenever the resolved locale changes, coerced to the closest
|
|
200
|
+
* one in `supportedLocales`. May return a promise; a rejection is
|
|
201
|
+
* reported to `onError` instead of throwing into the app tree.
|
|
202
|
+
*/
|
|
203
|
+
onChange: (locale: string) => void | Promise<void>;
|
|
204
|
+
onError?: (error: unknown, locale: string) => void;
|
|
175
205
|
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.useUsernameAvailability = useUsernameAvailability;
|
|
7
|
-
var _react = require("react");
|
|
8
|
-
var _toast = require("@oxy.so/bloom/toast");
|
|
9
|
-
/**
|
|
10
|
-
* Debounced username-availability check over the existing SDK method.
|
|
11
|
-
*
|
|
12
|
-
* One responsibility: turn keystrokes into an `idle | checking | available |
|
|
13
|
-
* taken` verdict without racing. A sequence guard drops superseded responses,
|
|
14
|
-
* and a NETWORK failure resets the (now stale) verdict and toasts — it never
|
|
15
|
-
* paints an inline error inside the dialog (owner mandate).
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/** Keystrokes settle for this long before a check is issued. */
|
|
19
|
-
const DEBOUNCE_MS = 400;
|
|
20
|
-
/** Shorter than this and there is nothing worth asking the server about. */
|
|
21
|
-
const MIN_USERNAME_LENGTH = 3;
|
|
22
|
-
function useUsernameAvailability(oxyServices, t) {
|
|
23
|
-
const [status, setStatus] = (0, _react.useState)('idle');
|
|
24
|
-
const timerRef = (0, _react.useRef)(null);
|
|
25
|
-
const requestSeqRef = (0, _react.useRef)(0);
|
|
26
|
-
const check = (0, _react.useCallback)(value => {
|
|
27
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
28
|
-
const seq = ++requestSeqRef.current;
|
|
29
|
-
if (!value || value.length < MIN_USERNAME_LENGTH) {
|
|
30
|
-
setStatus('idle');
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
setStatus('checking');
|
|
34
|
-
timerRef.current = setTimeout(() => {
|
|
35
|
-
void oxyServices.checkUsernameAvailability(value).then(result => {
|
|
36
|
-
if (seq !== requestSeqRef.current) return; // superseded by a newer check
|
|
37
|
-
setStatus(result.available ? 'available' : 'taken');
|
|
38
|
-
}).catch(() => {
|
|
39
|
-
if (seq !== requestSeqRef.current) return;
|
|
40
|
-
setStatus('idle');
|
|
41
|
-
_toast.toast.error(t('accounts.create.username.checkFailed'));
|
|
42
|
-
});
|
|
43
|
-
}, DEBOUNCE_MS);
|
|
44
|
-
}, [oxyServices, t]);
|
|
45
|
-
return {
|
|
46
|
-
status,
|
|
47
|
-
check
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=useUsernameAvailability.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_toast","DEBOUNCE_MS","MIN_USERNAME_LENGTH","useUsernameAvailability","oxyServices","t","status","setStatus","useState","timerRef","useRef","requestSeqRef","check","useCallback","value","current","clearTimeout","seq","length","setTimeout","checkUsernameAvailability","then","result","available","catch","toast","error"],"sourceRoot":"../../../../../src","sources":["ui/components/authChooser/useUsernameAvailability.ts"],"mappings":";;;;;;AASA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,MAAA,GAAAD,OAAA;AAVA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AASA;AACA,MAAME,WAAW,GAAG,GAAG;AACvB;AACA,MAAMC,mBAAmB,GAAG,CAAC;AAEtB,SAASC,uBAAuBA,CACrCC,WAAwB,EACxBC,CAAY,EAIZ;EACA,MAAM,CAACC,MAAM,EAAEC,SAAS,CAAC,GAAG,IAAAC,eAAQ,EAAiB,MAAM,CAAC;EAC5D,MAAMC,QAAQ,GAAG,IAAAC,aAAM,EAAuC,IAAI,CAAC;EACnE,MAAMC,aAAa,GAAG,IAAAD,aAAM,EAAC,CAAC,CAAC;EAE/B,MAAME,KAAK,GAAG,IAAAC,kBAAW,EACtBC,KAAa,IAAK;IACjB,IAAIL,QAAQ,CAACM,OAAO,EAAEC,YAAY,CAACP,QAAQ,CAACM,OAAO,CAAC;IACpD,MAAME,GAAG,GAAG,EAAEN,aAAa,CAACI,OAAO;IACnC,IAAI,CAACD,KAAK,IAAIA,KAAK,CAACI,MAAM,GAAGhB,mBAAmB,EAAE;MAChDK,SAAS,CAAC,MAAM,CAAC;MACjB;IACF;IACAA,SAAS,CAAC,UAAU,CAAC;IACrBE,QAAQ,CAACM,OAAO,GAAGI,UAAU,CAAC,MAAM;MAClC,KAAKf,WAAW,CACbgB,yBAAyB,CAACN,KAAK,CAAC,CAChCO,IAAI,CAAEC,MAAM,IAAK;QAChB,IAAIL,GAAG,KAAKN,aAAa,CAACI,OAAO,EAAE,OAAO,CAAC;QAC3CR,SAAS,CAACe,MAAM,CAACC,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;MACrD,CAAC,CAAC,CACDC,KAAK,CAAC,MAAM;QACX,IAAIP,GAAG,KAAKN,aAAa,CAACI,OAAO,EAAE;QACnCR,SAAS,CAAC,MAAM,CAAC;QACjBkB,YAAK,CAACC,KAAK,CAACrB,CAAC,CAAC,sCAAsC,CAAC,CAAC;MACxD,CAAC,CAAC;IACN,CAAC,EAAEJ,WAAW,CAAC;EACjB,CAAC,EACD,CAACG,WAAW,EAAEC,CAAC,CACjB,CAAC;EAED,OAAO;IAAEC,MAAM;IAAEM;EAAM,CAAC;AAC1B","ignoreList":[]}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Debounced username-availability check over the existing SDK method.
|
|
5
|
-
*
|
|
6
|
-
* One responsibility: turn keystrokes into an `idle | checking | available |
|
|
7
|
-
* taken` verdict without racing. A sequence guard drops superseded responses,
|
|
8
|
-
* and a NETWORK failure resets the (now stale) verdict and toasts — it never
|
|
9
|
-
* paints an inline error inside the dialog (owner mandate).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { useCallback, useRef, useState } from 'react';
|
|
13
|
-
import { toast } from '@oxy.so/bloom/toast';
|
|
14
|
-
/** Keystrokes settle for this long before a check is issued. */
|
|
15
|
-
const DEBOUNCE_MS = 400;
|
|
16
|
-
/** Shorter than this and there is nothing worth asking the server about. */
|
|
17
|
-
const MIN_USERNAME_LENGTH = 3;
|
|
18
|
-
export function useUsernameAvailability(oxyServices, t) {
|
|
19
|
-
const [status, setStatus] = useState('idle');
|
|
20
|
-
const timerRef = useRef(null);
|
|
21
|
-
const requestSeqRef = useRef(0);
|
|
22
|
-
const check = useCallback(value => {
|
|
23
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
24
|
-
const seq = ++requestSeqRef.current;
|
|
25
|
-
if (!value || value.length < MIN_USERNAME_LENGTH) {
|
|
26
|
-
setStatus('idle');
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
setStatus('checking');
|
|
30
|
-
timerRef.current = setTimeout(() => {
|
|
31
|
-
void oxyServices.checkUsernameAvailability(value).then(result => {
|
|
32
|
-
if (seq !== requestSeqRef.current) return; // superseded by a newer check
|
|
33
|
-
setStatus(result.available ? 'available' : 'taken');
|
|
34
|
-
}).catch(() => {
|
|
35
|
-
if (seq !== requestSeqRef.current) return;
|
|
36
|
-
setStatus('idle');
|
|
37
|
-
toast.error(t('accounts.create.username.checkFailed'));
|
|
38
|
-
});
|
|
39
|
-
}, DEBOUNCE_MS);
|
|
40
|
-
}, [oxyServices, t]);
|
|
41
|
-
return {
|
|
42
|
-
status,
|
|
43
|
-
check
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
//# sourceMappingURL=useUsernameAvailability.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useRef","useState","toast","DEBOUNCE_MS","MIN_USERNAME_LENGTH","useUsernameAvailability","oxyServices","t","status","setStatus","timerRef","requestSeqRef","check","value","current","clearTimeout","seq","length","setTimeout","checkUsernameAvailability","then","result","available","catch","error"],"sourceRoot":"../../../../../src","sources":["ui/components/authChooser/useUsernameAvailability.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,WAAW,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AACrD,SAASC,KAAK,QAAQ,qBAAqB;AAM3C;AACA,MAAMC,WAAW,GAAG,GAAG;AACvB;AACA,MAAMC,mBAAmB,GAAG,CAAC;AAE7B,OAAO,SAASC,uBAAuBA,CACrCC,WAAwB,EACxBC,CAAY,EAIZ;EACA,MAAM,CAACC,MAAM,EAAEC,SAAS,CAAC,GAAGR,QAAQ,CAAiB,MAAM,CAAC;EAC5D,MAAMS,QAAQ,GAAGV,MAAM,CAAuC,IAAI,CAAC;EACnE,MAAMW,aAAa,GAAGX,MAAM,CAAC,CAAC,CAAC;EAE/B,MAAMY,KAAK,GAAGb,WAAW,CACtBc,KAAa,IAAK;IACjB,IAAIH,QAAQ,CAACI,OAAO,EAAEC,YAAY,CAACL,QAAQ,CAACI,OAAO,CAAC;IACpD,MAAME,GAAG,GAAG,EAAEL,aAAa,CAACG,OAAO;IACnC,IAAI,CAACD,KAAK,IAAIA,KAAK,CAACI,MAAM,GAAGb,mBAAmB,EAAE;MAChDK,SAAS,CAAC,MAAM,CAAC;MACjB;IACF;IACAA,SAAS,CAAC,UAAU,CAAC;IACrBC,QAAQ,CAACI,OAAO,GAAGI,UAAU,CAAC,MAAM;MAClC,KAAKZ,WAAW,CACba,yBAAyB,CAACN,KAAK,CAAC,CAChCO,IAAI,CAAEC,MAAM,IAAK;QAChB,IAAIL,GAAG,KAAKL,aAAa,CAACG,OAAO,EAAE,OAAO,CAAC;QAC3CL,SAAS,CAACY,MAAM,CAACC,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;MACrD,CAAC,CAAC,CACDC,KAAK,CAAC,MAAM;QACX,IAAIP,GAAG,KAAKL,aAAa,CAACG,OAAO,EAAE;QACnCL,SAAS,CAAC,MAAM,CAAC;QACjBP,KAAK,CAACsB,KAAK,CAACjB,CAAC,CAAC,sCAAsC,CAAC,CAAC;MACxD,CAAC,CAAC;IACN,CAAC,EAAEJ,WAAW,CAAC;EACjB,CAAC,EACD,CAACG,WAAW,EAAEC,CAAC,CACjB,CAAC;EAED,OAAO;IAAEC,MAAM;IAAEI;EAAM,CAAC;AAC1B","ignoreList":[]}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debounced username-availability check over the existing SDK method.
|
|
3
|
-
*
|
|
4
|
-
* One responsibility: turn keystrokes into an `idle | checking | available |
|
|
5
|
-
* taken` verdict without racing. A sequence guard drops superseded responses,
|
|
6
|
-
* and a NETWORK failure resets the (now stale) verdict and toasts — it never
|
|
7
|
-
* paints an inline error inside the dialog (owner mandate).
|
|
8
|
-
*/
|
|
9
|
-
import type { OxyServices } from '@oxy.so/core';
|
|
10
|
-
import type { Translate } from './types';
|
|
11
|
-
export type UsernameStatus = 'idle' | 'checking' | 'available' | 'taken';
|
|
12
|
-
export declare function useUsernameAvailability(oxyServices: OxyServices, t: Translate): {
|
|
13
|
-
status: UsernameStatus;
|
|
14
|
-
check: (value: string) => void;
|
|
15
|
-
};
|
|
16
|
-
//# sourceMappingURL=useUsernameAvailability.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useUsernameAvailability.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/authChooser/useUsernameAvailability.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;AAOzE,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,CAAC,EAAE,SAAS,GACX;IACD,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,CAgCA"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debounced username-availability check over the existing SDK method.
|
|
3
|
-
*
|
|
4
|
-
* One responsibility: turn keystrokes into an `idle | checking | available |
|
|
5
|
-
* taken` verdict without racing. A sequence guard drops superseded responses,
|
|
6
|
-
* and a NETWORK failure resets the (now stale) verdict and toasts — it never
|
|
7
|
-
* paints an inline error inside the dialog (owner mandate).
|
|
8
|
-
*/
|
|
9
|
-
import type { OxyServices } from '@oxy.so/core';
|
|
10
|
-
import type { Translate } from './types.js';
|
|
11
|
-
export type UsernameStatus = 'idle' | 'checking' | 'available' | 'taken';
|
|
12
|
-
export declare function useUsernameAvailability(oxyServices: OxyServices, t: Translate): {
|
|
13
|
-
status: UsernameStatus;
|
|
14
|
-
check: (value: string) => void;
|
|
15
|
-
};
|
|
16
|
-
//# sourceMappingURL=useUsernameAvailability.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useUsernameAvailability.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/authChooser/useUsernameAvailability.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAS,CAAC;AAEzC,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;AAOzE,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,CAAC,EAAE,SAAS,GACX;IACD,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,CAgCA"}
|