@ankhorage/zora 0.5.3 → 0.6.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/CHANGELOG.md +12 -0
- package/dist/components/form/Form.d.ts +4 -0
- package/dist/components/form/Form.d.ts.map +1 -0
- package/dist/components/form/Form.js +27 -0
- package/dist/components/form/Form.js.map +1 -0
- package/dist/components/form/FormActions.d.ts +4 -0
- package/dist/components/form/FormActions.d.ts.map +1 -0
- package/dist/components/form/FormActions.js +12 -0
- package/dist/components/form/FormActions.js.map +1 -0
- package/dist/components/form/FormError.d.ts +4 -0
- package/dist/components/form/FormError.d.ts.map +1 -0
- package/dist/components/form/FormError.js +14 -0
- package/dist/components/form/FormError.js.map +1 -0
- package/dist/components/form/FormField.d.ts +4 -0
- package/dist/components/form/FormField.d.ts.map +1 -0
- package/dist/components/form/FormField.js +74 -0
- package/dist/components/form/FormField.js.map +1 -0
- package/dist/components/form/index.d.ts +8 -0
- package/dist/components/form/index.d.ts.map +1 -0
- package/dist/components/form/index.js +7 -0
- package/dist/components/form/index.js.map +1 -0
- package/dist/components/form/types.d.ts +107 -0
- package/dist/components/form/types.d.ts.map +1 -0
- package/dist/components/form/types.js +2 -0
- package/dist/components/form/types.js.map +1 -0
- package/dist/components/form/useFormController.d.ts +3 -0
- package/dist/components/form/useFormController.d.ts.map +1 -0
- package/dist/components/form/useFormController.js +62 -0
- package/dist/components/form/useFormController.js.map +1 -0
- package/dist/components/form/validation.d.ts +6 -0
- package/dist/components/form/validation.d.ts.map +1 -0
- package/dist/components/form/validation.js +52 -0
- package/dist/components/form/validation.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/patterns/auth/ForgotPasswordForm.d.ts +4 -0
- package/dist/patterns/auth/ForgotPasswordForm.d.ts.map +1 -0
- package/dist/patterns/auth/ForgotPasswordForm.js +31 -0
- package/dist/patterns/auth/ForgotPasswordForm.js.map +1 -0
- package/dist/patterns/auth/OtpForm.d.ts +4 -0
- package/dist/patterns/auth/OtpForm.d.ts.map +1 -0
- package/dist/patterns/auth/OtpForm.js +30 -0
- package/dist/patterns/auth/OtpForm.js.map +1 -0
- package/dist/patterns/auth/SignInForm.d.ts +4 -0
- package/dist/patterns/auth/SignInForm.d.ts.map +1 -0
- package/dist/patterns/auth/SignInForm.js +45 -0
- package/dist/patterns/auth/SignInForm.js.map +1 -0
- package/dist/patterns/auth/SignUpForm.d.ts +4 -0
- package/dist/patterns/auth/SignUpForm.d.ts.map +1 -0
- package/dist/patterns/auth/SignUpForm.js +37 -0
- package/dist/patterns/auth/SignUpForm.js.map +1 -0
- package/dist/patterns/auth/index.d.ts +6 -0
- package/dist/patterns/auth/index.d.ts.map +1 -0
- package/dist/patterns/auth/index.js +5 -0
- package/dist/patterns/auth/index.js.map +1 -0
- package/dist/patterns/auth/types.d.ts +57 -0
- package/dist/patterns/auth/types.d.ts.map +1 -0
- package/dist/patterns/auth/types.js +2 -0
- package/dist/patterns/auth/types.js.map +1 -0
- package/dist/patterns/auth/utils.d.ts +8 -0
- package/dist/patterns/auth/utils.d.ts.map +1 -0
- package/dist/patterns/auth/utils.js +51 -0
- package/dist/patterns/auth/utils.js.map +1 -0
- package/package.json +2 -2
- package/src/components/form/Form.tsx +61 -0
- package/src/components/form/FormActions.tsx +23 -0
- package/src/components/form/FormError.tsx +20 -0
- package/src/components/form/FormField.tsx +128 -0
- package/src/components/form/index.ts +24 -0
- package/src/components/form/types.ts +115 -0
- package/src/components/form/useFormController.ts +105 -0
- package/src/components/form/validation.test.ts +79 -0
- package/src/components/form/validation.ts +83 -0
- package/src/index.ts +43 -2
- package/src/patterns/auth/ForgotPasswordForm.tsx +84 -0
- package/src/patterns/auth/OtpForm.tsx +80 -0
- package/src/patterns/auth/SignInForm.tsx +111 -0
- package/src/patterns/auth/SignUpForm.tsx +76 -0
- package/src/patterns/auth/index.ts +17 -0
- package/src/patterns/auth/types.ts +67 -0
- package/src/patterns/auth/utils.ts +80 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../../components/button';
|
|
5
|
+
import { Form, type FormFieldConfig, type FormValues } from '../../components/form';
|
|
6
|
+
import type { OtpFormProps } from './types';
|
|
7
|
+
|
|
8
|
+
type OtpFieldName = 'otp';
|
|
9
|
+
|
|
10
|
+
export function OtpForm({
|
|
11
|
+
length = 6,
|
|
12
|
+
otpLabel = 'Code',
|
|
13
|
+
resendLabel = 'Resend code',
|
|
14
|
+
resendDisabled = false,
|
|
15
|
+
resendLoading = false,
|
|
16
|
+
loading = false,
|
|
17
|
+
disabled = false,
|
|
18
|
+
error,
|
|
19
|
+
submitLabel = 'Verify code',
|
|
20
|
+
onSubmit,
|
|
21
|
+
onResend,
|
|
22
|
+
testID,
|
|
23
|
+
}: OtpFormProps) {
|
|
24
|
+
const resolvedLength = Math.max(1, length);
|
|
25
|
+
const [values, setValues] = React.useState<FormValues<OtpFieldName>>({
|
|
26
|
+
otp: '',
|
|
27
|
+
});
|
|
28
|
+
const fields = React.useMemo<readonly FormFieldConfig<OtpFieldName>[]>(
|
|
29
|
+
() => [
|
|
30
|
+
{
|
|
31
|
+
name: 'otp',
|
|
32
|
+
label: otpLabel,
|
|
33
|
+
type: 'otp',
|
|
34
|
+
maxLength: resolvedLength,
|
|
35
|
+
rules: [{ kind: 'required' }, { kind: 'minLength', value: resolvedLength }],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
[otpLabel, resolvedLength],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const handleSubmit = React.useCallback(
|
|
42
|
+
(formValues: FormValues<OtpFieldName>) =>
|
|
43
|
+
onSubmit({
|
|
44
|
+
otp: formValues.otp.trim(),
|
|
45
|
+
}),
|
|
46
|
+
[onSubmit],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Form
|
|
51
|
+
actions={
|
|
52
|
+
onResend ? (
|
|
53
|
+
<Stack direction="row" gap="s" wrap="wrap">
|
|
54
|
+
<Button
|
|
55
|
+
disabled={disabled || loading || resendDisabled}
|
|
56
|
+
emphasis="ghost"
|
|
57
|
+
loading={resendLoading}
|
|
58
|
+
onPress={() => {
|
|
59
|
+
void onResend();
|
|
60
|
+
}}
|
|
61
|
+
size="s"
|
|
62
|
+
tone="neutral"
|
|
63
|
+
>
|
|
64
|
+
{resendLabel}
|
|
65
|
+
</Button>
|
|
66
|
+
</Stack>
|
|
67
|
+
) : undefined
|
|
68
|
+
}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
error={error}
|
|
71
|
+
fields={fields}
|
|
72
|
+
loading={loading}
|
|
73
|
+
onChange={setValues}
|
|
74
|
+
onSubmit={handleSubmit}
|
|
75
|
+
submitLabel={submitLabel}
|
|
76
|
+
testID={testID}
|
|
77
|
+
values={values}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../../components/button';
|
|
5
|
+
import { Form, type FormFieldConfig, type FormValues } from '../../components/form';
|
|
6
|
+
import type { SignInFormProps } from './types';
|
|
7
|
+
import {
|
|
8
|
+
defaultIdentifiers,
|
|
9
|
+
normalizeIdentifierKind,
|
|
10
|
+
resolveIdentifierLabel,
|
|
11
|
+
resolveIdentifierRules,
|
|
12
|
+
resolveIdentifierType,
|
|
13
|
+
} from './utils';
|
|
14
|
+
|
|
15
|
+
type SignInFieldName = 'identifier' | 'secret';
|
|
16
|
+
|
|
17
|
+
export function SignInForm({
|
|
18
|
+
identifiers = defaultIdentifiers,
|
|
19
|
+
identifierLabel,
|
|
20
|
+
secretLabel = 'Password',
|
|
21
|
+
forgotPasswordLabel = 'Forgot password',
|
|
22
|
+
signUpLabel = 'Sign up',
|
|
23
|
+
loading = false,
|
|
24
|
+
disabled = false,
|
|
25
|
+
error,
|
|
26
|
+
submitLabel = 'Sign in',
|
|
27
|
+
onSubmit,
|
|
28
|
+
onForgotPassword,
|
|
29
|
+
onSignUp,
|
|
30
|
+
testID,
|
|
31
|
+
}: SignInFormProps) {
|
|
32
|
+
const [values, setValues] = React.useState<FormValues<SignInFieldName>>({
|
|
33
|
+
identifier: '',
|
|
34
|
+
secret: '',
|
|
35
|
+
});
|
|
36
|
+
const hasActions = Boolean(onForgotPassword ?? onSignUp);
|
|
37
|
+
const fields = React.useMemo<readonly FormFieldConfig<SignInFieldName>[]>(
|
|
38
|
+
() => [
|
|
39
|
+
{
|
|
40
|
+
name: 'identifier',
|
|
41
|
+
label: identifierLabel ?? resolveIdentifierLabel(identifiers),
|
|
42
|
+
type: resolveIdentifierType(identifiers),
|
|
43
|
+
autoCapitalize: 'none',
|
|
44
|
+
rules: resolveIdentifierRules(identifiers),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'secret',
|
|
48
|
+
label: secretLabel,
|
|
49
|
+
type: 'password',
|
|
50
|
+
rules: [{ kind: 'required' }],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
[identifierLabel, identifiers, secretLabel],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const handleSubmit = React.useCallback(
|
|
57
|
+
(formValues: FormValues<SignInFieldName>) =>
|
|
58
|
+
onSubmit({
|
|
59
|
+
identifier: formValues.identifier.trim(),
|
|
60
|
+
identifierKind: normalizeIdentifierKind(formValues.identifier, identifiers),
|
|
61
|
+
secret: formValues.secret,
|
|
62
|
+
}),
|
|
63
|
+
[identifiers, onSubmit],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Form
|
|
68
|
+
actions={
|
|
69
|
+
hasActions ? (
|
|
70
|
+
<Stack direction="row" gap="s" wrap="wrap">
|
|
71
|
+
{onForgotPassword ? (
|
|
72
|
+
<Button
|
|
73
|
+
disabled={disabled || loading}
|
|
74
|
+
emphasis="ghost"
|
|
75
|
+
onPress={() => {
|
|
76
|
+
void onForgotPassword();
|
|
77
|
+
}}
|
|
78
|
+
size="s"
|
|
79
|
+
tone="neutral"
|
|
80
|
+
>
|
|
81
|
+
{forgotPasswordLabel}
|
|
82
|
+
</Button>
|
|
83
|
+
) : null}
|
|
84
|
+
{onSignUp ? (
|
|
85
|
+
<Button
|
|
86
|
+
disabled={disabled || loading}
|
|
87
|
+
emphasis="ghost"
|
|
88
|
+
onPress={() => {
|
|
89
|
+
void onSignUp();
|
|
90
|
+
}}
|
|
91
|
+
size="s"
|
|
92
|
+
tone="neutral"
|
|
93
|
+
>
|
|
94
|
+
{signUpLabel}
|
|
95
|
+
</Button>
|
|
96
|
+
) : null}
|
|
97
|
+
</Stack>
|
|
98
|
+
) : undefined
|
|
99
|
+
}
|
|
100
|
+
disabled={disabled}
|
|
101
|
+
error={error}
|
|
102
|
+
fields={fields}
|
|
103
|
+
loading={loading}
|
|
104
|
+
onChange={setValues}
|
|
105
|
+
onSubmit={handleSubmit}
|
|
106
|
+
submitLabel={submitLabel}
|
|
107
|
+
testID={testID}
|
|
108
|
+
values={values}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Stack } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../../components/button';
|
|
5
|
+
import { Form, type FormFieldConfig, type FormValues } from '../../components/form';
|
|
6
|
+
import type { SignUpFormProps } from './types';
|
|
7
|
+
|
|
8
|
+
const defaultSignUpFields = [
|
|
9
|
+
{
|
|
10
|
+
name: 'email',
|
|
11
|
+
label: 'Email',
|
|
12
|
+
type: 'email',
|
|
13
|
+
autoCapitalize: 'none',
|
|
14
|
+
rules: [{ kind: 'required' }, { kind: 'email' }],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'password',
|
|
18
|
+
label: 'Password',
|
|
19
|
+
type: 'password',
|
|
20
|
+
rules: [{ kind: 'required' }, { kind: 'minLength', value: 8 }],
|
|
21
|
+
},
|
|
22
|
+
] as const satisfies readonly FormFieldConfig[];
|
|
23
|
+
|
|
24
|
+
function createValues(fields: readonly FormFieldConfig[]): FormValues {
|
|
25
|
+
const values = fields.reduce<Record<string, string>>((nextValues, field) => {
|
|
26
|
+
nextValues[field.name] = '';
|
|
27
|
+
return nextValues;
|
|
28
|
+
}, {});
|
|
29
|
+
|
|
30
|
+
return values;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function SignUpForm({
|
|
34
|
+
fields = defaultSignUpFields,
|
|
35
|
+
signInLabel = 'Sign in',
|
|
36
|
+
loading = false,
|
|
37
|
+
disabled = false,
|
|
38
|
+
error,
|
|
39
|
+
submitLabel = 'Sign up',
|
|
40
|
+
onSubmit,
|
|
41
|
+
onSignIn,
|
|
42
|
+
testID,
|
|
43
|
+
}: SignUpFormProps) {
|
|
44
|
+
const [values, setValues] = React.useState<FormValues>(() => createValues(fields));
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Form
|
|
48
|
+
actions={
|
|
49
|
+
onSignIn ? (
|
|
50
|
+
<Stack direction="row" gap="s" wrap="wrap">
|
|
51
|
+
<Button
|
|
52
|
+
disabled={disabled || loading}
|
|
53
|
+
emphasis="ghost"
|
|
54
|
+
onPress={() => {
|
|
55
|
+
void onSignIn();
|
|
56
|
+
}}
|
|
57
|
+
size="s"
|
|
58
|
+
tone="neutral"
|
|
59
|
+
>
|
|
60
|
+
{signInLabel}
|
|
61
|
+
</Button>
|
|
62
|
+
</Stack>
|
|
63
|
+
) : undefined
|
|
64
|
+
}
|
|
65
|
+
disabled={disabled}
|
|
66
|
+
error={error}
|
|
67
|
+
fields={fields}
|
|
68
|
+
loading={loading}
|
|
69
|
+
onChange={setValues}
|
|
70
|
+
onSubmit={onSubmit}
|
|
71
|
+
submitLabel={submitLabel}
|
|
72
|
+
testID={testID}
|
|
73
|
+
values={values}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { ForgotPasswordForm } from './ForgotPasswordForm';
|
|
2
|
+
export { OtpForm } from './OtpForm';
|
|
3
|
+
export { SignInForm } from './SignInForm';
|
|
4
|
+
export { SignUpForm } from './SignUpForm';
|
|
5
|
+
export type {
|
|
6
|
+
AuthFormBaseProps,
|
|
7
|
+
AuthIdentifierKind,
|
|
8
|
+
ForgotPasswordFormProps,
|
|
9
|
+
ForgotPasswordFormValues,
|
|
10
|
+
OtpFormProps,
|
|
11
|
+
OtpFormValues,
|
|
12
|
+
SignInFormProps,
|
|
13
|
+
SignInFormValues,
|
|
14
|
+
SignUpFormField,
|
|
15
|
+
SignUpFormProps,
|
|
16
|
+
SignUpFormValues,
|
|
17
|
+
} from './types';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { FormFieldConfig, FormValues } from '../../components/form';
|
|
4
|
+
|
|
5
|
+
export type AuthIdentifierKind = 'email' | 'phone' | 'username';
|
|
6
|
+
|
|
7
|
+
export interface AuthFormBaseProps {
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
error?: React.ReactNode;
|
|
11
|
+
submitLabel?: React.ReactNode;
|
|
12
|
+
testID?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SignInFormValues {
|
|
16
|
+
identifier: string;
|
|
17
|
+
identifierKind: AuthIdentifierKind;
|
|
18
|
+
secret: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SignInFormProps extends AuthFormBaseProps {
|
|
22
|
+
identifiers?: readonly AuthIdentifierKind[];
|
|
23
|
+
identifierLabel?: React.ReactNode;
|
|
24
|
+
secretLabel?: React.ReactNode;
|
|
25
|
+
forgotPasswordLabel?: React.ReactNode;
|
|
26
|
+
signUpLabel?: React.ReactNode;
|
|
27
|
+
onSubmit: (values: SignInFormValues) => void | Promise<void>;
|
|
28
|
+
onForgotPassword?: () => void | Promise<void>;
|
|
29
|
+
onSignUp?: () => void | Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type SignUpFormValues = FormValues;
|
|
33
|
+
export type SignUpFormField = FormFieldConfig;
|
|
34
|
+
|
|
35
|
+
export interface SignUpFormProps extends AuthFormBaseProps {
|
|
36
|
+
fields?: readonly SignUpFormField[];
|
|
37
|
+
signInLabel?: React.ReactNode;
|
|
38
|
+
onSubmit: (values: SignUpFormValues) => void | Promise<void>;
|
|
39
|
+
onSignIn?: () => void | Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ForgotPasswordFormValues {
|
|
43
|
+
identifier: string;
|
|
44
|
+
identifierKind: AuthIdentifierKind;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ForgotPasswordFormProps extends AuthFormBaseProps {
|
|
48
|
+
identifiers?: readonly AuthIdentifierKind[];
|
|
49
|
+
identifierLabel?: React.ReactNode;
|
|
50
|
+
signInLabel?: React.ReactNode;
|
|
51
|
+
onSubmit: (values: ForgotPasswordFormValues) => void | Promise<void>;
|
|
52
|
+
onSignIn?: () => void | Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface OtpFormValues {
|
|
56
|
+
otp: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface OtpFormProps extends AuthFormBaseProps {
|
|
60
|
+
length?: number;
|
|
61
|
+
otpLabel?: React.ReactNode;
|
|
62
|
+
resendLabel?: React.ReactNode;
|
|
63
|
+
resendDisabled?: boolean;
|
|
64
|
+
resendLoading?: boolean;
|
|
65
|
+
onSubmit: (values: OtpFormValues) => void | Promise<void>;
|
|
66
|
+
onResend?: () => void | Promise<void>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { FormFieldConfig, ValidationRule } from '../../components/form';
|
|
2
|
+
import type { AuthIdentifierKind } from './types';
|
|
3
|
+
|
|
4
|
+
export const defaultIdentifiers = ['email'] as const satisfies readonly AuthIdentifierKind[];
|
|
5
|
+
|
|
6
|
+
const identifierLabels: Record<AuthIdentifierKind, string> = {
|
|
7
|
+
email: 'Email',
|
|
8
|
+
phone: 'Phone',
|
|
9
|
+
username: 'Username',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function includesIdentifier(
|
|
13
|
+
identifiers: readonly AuthIdentifierKind[],
|
|
14
|
+
identifier: AuthIdentifierKind,
|
|
15
|
+
): boolean {
|
|
16
|
+
return identifiers.includes(identifier);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function resolveIdentifierLabel(identifiers: readonly AuthIdentifierKind[]): string {
|
|
20
|
+
if (identifiers.length === 1) {
|
|
21
|
+
return identifierLabels[identifiers[0] ?? 'email'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return identifiers.map((identifier) => identifierLabels[identifier]).join(', or ');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveIdentifierType(
|
|
28
|
+
identifiers: readonly AuthIdentifierKind[],
|
|
29
|
+
): FormFieldConfig['type'] {
|
|
30
|
+
if (identifiers.length !== 1) {
|
|
31
|
+
return 'text';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const [identifier] = identifiers;
|
|
35
|
+
|
|
36
|
+
if (identifier === 'email') {
|
|
37
|
+
return 'email';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (identifier === 'phone') {
|
|
41
|
+
return 'tel';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return 'text';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveIdentifierRules(
|
|
48
|
+
identifiers: readonly AuthIdentifierKind[],
|
|
49
|
+
): readonly ValidationRule[] {
|
|
50
|
+
if (identifiers.length === 1 && identifiers[0] === 'email') {
|
|
51
|
+
return [{ kind: 'required' }, { kind: 'email' }];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return [{ kind: 'required' }];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function normalizeIdentifierKind(
|
|
58
|
+
identifier: string,
|
|
59
|
+
identifiers: readonly AuthIdentifierKind[],
|
|
60
|
+
): AuthIdentifierKind {
|
|
61
|
+
const normalizedIdentifier = identifier.trim();
|
|
62
|
+
|
|
63
|
+
if (identifiers.length === 1) {
|
|
64
|
+
return identifiers[0] ?? 'email';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (includesIdentifier(identifiers, 'email') && normalizedIdentifier.includes('@')) {
|
|
68
|
+
return 'email';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (includesIdentifier(identifiers, 'phone') && /^[+()\d\s.-]+$/.test(normalizedIdentifier)) {
|
|
72
|
+
return 'phone';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (includesIdentifier(identifiers, 'username')) {
|
|
76
|
+
return 'username';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return identifiers[0] ?? 'email';
|
|
80
|
+
}
|