mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(console): use button loading in experience flow if possible (#6234)
This commit is contained in:
parent
a84389da13
commit
bc2ccf671e
25 changed files with 194 additions and 77 deletions
|
@ -1,18 +1,16 @@
|
|||
import { useContext } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { useDebouncedLoader } from 'use-debounced-loader';
|
||||
|
||||
import PageContext from '@/Providers/PageContextProvider/PageContext';
|
||||
import LoadingLayer from '@/components/LoadingLayer';
|
||||
import LoadingMask from '@/components/LoadingMask';
|
||||
|
||||
const LoadingLayerProvider = () => {
|
||||
const { loading } = useContext(PageContext);
|
||||
const debouncedLoading = useDebouncedLoader(loading, 500);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Outlet />
|
||||
{debouncedLoading && <LoadingLayer />}
|
||||
{loading && <LoadingMask />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@include _.flex-column;
|
||||
z-index: 300;
|
||||
}
|
||||
|
||||
.loadingIcon {
|
||||
color: var(--color-type-primary);
|
||||
animation: rotating 1s steps(12, end) infinite;
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import LoadingMask from '../LoadingMask';
|
||||
|
||||
import LoadingIcon from './LoadingIcon';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export { default as LoadingIcon } from './LoadingIcon';
|
||||
|
||||
const LoadingLayer = () => (
|
||||
<div className={styles.overlay}>
|
||||
<LoadingMask>
|
||||
<div className={styles.container}>
|
||||
<LoadingIcon />
|
||||
</div>
|
||||
</div>
|
||||
</LoadingMask>
|
||||
);
|
||||
|
||||
export default LoadingLayer;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@include _.flex-column;
|
||||
z-index: 300;
|
||||
}
|
13
packages/experience/src/components/LoadingMask/index.tsx
Normal file
13
packages/experience/src/components/LoadingMask/index.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { type ReactNode } from 'react';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
readonly children?: ReactNode;
|
||||
};
|
||||
|
||||
const LoadingMask = ({ children }: Props) => {
|
||||
return <div className={styles.overlay}>{children}</div>;
|
||||
};
|
||||
|
||||
export default LoadingMask;
|
|
@ -14,7 +14,7 @@ type Props = {
|
|||
readonly className?: string;
|
||||
// eslint-disable-next-line react/boolean-prop-naming
|
||||
readonly autoFocus?: boolean;
|
||||
readonly onSubmit: (password: string) => void;
|
||||
readonly onSubmit: (password: string) => Promise<void>;
|
||||
readonly errorMessage?: string;
|
||||
readonly clearErrorMessage?: () => void;
|
||||
};
|
||||
|
@ -29,7 +29,7 @@ const Lite = ({ className, autoFocus, onSubmit, errorMessage, clearErrorMessage
|
|||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isValid },
|
||||
formState: { errors, isValid, isSubmitting },
|
||||
} = useForm<FieldState>({
|
||||
reValidateMode: 'onBlur',
|
||||
defaultValues: { newPassword: '' },
|
||||
|
@ -45,8 +45,8 @@ const Lite = ({ className, autoFocus, onSubmit, errorMessage, clearErrorMessage
|
|||
(event?: React.FormEvent<HTMLFormElement>) => {
|
||||
clearErrorMessage?.();
|
||||
|
||||
void handleSubmit((data, event) => {
|
||||
onSubmit(data.newPassword);
|
||||
void handleSubmit(async (data) => {
|
||||
await onSubmit(data.newPassword);
|
||||
})(event);
|
||||
},
|
||||
[clearErrorMessage, handleSubmit, onSubmit]
|
||||
|
@ -70,7 +70,12 @@ const Lite = ({ className, autoFocus, onSubmit, errorMessage, clearErrorMessage
|
|||
|
||||
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
|
||||
|
||||
<Button name="submit" title="action.save_password" htmlType="submit" />
|
||||
<Button
|
||||
name="submit"
|
||||
title="action.save_password"
|
||||
htmlType="submit"
|
||||
isLoading={isSubmitting}
|
||||
/>
|
||||
|
||||
<input hidden type="submit" />
|
||||
</form>
|
||||
|
|
|
@ -17,7 +17,7 @@ type Props = {
|
|||
readonly className?: string;
|
||||
// eslint-disable-next-line react/boolean-prop-naming
|
||||
readonly autoFocus?: boolean;
|
||||
readonly onSubmit: (password: string) => void;
|
||||
readonly onSubmit: (password: string) => Promise<void>;
|
||||
readonly errorMessage?: string;
|
||||
readonly clearErrorMessage?: () => void;
|
||||
};
|
||||
|
@ -43,7 +43,7 @@ const SetPassword = ({
|
|||
watch,
|
||||
resetField,
|
||||
handleSubmit,
|
||||
formState: { errors, isValid },
|
||||
formState: { errors, isValid, isSubmitting },
|
||||
} = useForm<FieldState>({
|
||||
reValidateMode: 'onBlur',
|
||||
defaultValues: { newPassword: '', confirmPassword: '' },
|
||||
|
@ -59,8 +59,8 @@ const SetPassword = ({
|
|||
(event?: React.FormEvent<HTMLFormElement>) => {
|
||||
clearErrorMessage?.();
|
||||
|
||||
void handleSubmit((data, event) => {
|
||||
onSubmit(data.newPassword);
|
||||
void handleSubmit(async (data) => {
|
||||
await onSubmit(data.newPassword);
|
||||
})(event);
|
||||
},
|
||||
[clearErrorMessage, handleSubmit, onSubmit]
|
||||
|
@ -119,7 +119,12 @@ const SetPassword = ({
|
|||
|
||||
<TogglePassword isChecked={showPassword} onChange={setShowPassword} />
|
||||
|
||||
<Button name="submit" title="action.save_password" htmlType="submit" />
|
||||
<Button
|
||||
name="submit"
|
||||
title="action.save_password"
|
||||
htmlType="submit"
|
||||
isLoading={isSubmitting}
|
||||
/>
|
||||
|
||||
<input hidden type="submit" />
|
||||
</form>
|
||||
|
|
|
@ -7,7 +7,7 @@ type Props = {
|
|||
readonly className?: string;
|
||||
// eslint-disable-next-line react/boolean-prop-naming
|
||||
readonly autoFocus?: boolean;
|
||||
readonly onSubmit: (password: string) => void;
|
||||
readonly onSubmit: (password: string) => Promise<void>;
|
||||
readonly errorMessage?: string;
|
||||
readonly clearErrorMessage?: () => void;
|
||||
readonly maxLength?: number;
|
||||
|
|
|
@ -3,3 +3,7 @@
|
|||
.totpCodeInput {
|
||||
margin-top: _.unit(4);
|
||||
}
|
||||
|
||||
.continueButton {
|
||||
margin-top: _.unit(6);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import VerificationCodeInput from '@/components/VerificationCode';
|
||||
import { type UserMfaFlow } from '@/types';
|
||||
|
||||
|
@ -8,32 +9,58 @@ import useTotpCodeVerification from './use-totp-code-verification';
|
|||
|
||||
const totpCodeLength = 6;
|
||||
|
||||
const isCodeReady = (code: string[]) => {
|
||||
return code.length === totpCodeLength && code.every(Boolean);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
readonly flow: UserMfaFlow;
|
||||
};
|
||||
|
||||
const TotpCodeVerification = ({ flow }: Props) => {
|
||||
const [code, setCode] = useState<string[]>([]);
|
||||
const [codeInput, setCodeInput] = useState<string[]>([]);
|
||||
const errorCallback = useCallback(() => {
|
||||
setCode([]);
|
||||
setCodeInput([]);
|
||||
}, []);
|
||||
|
||||
const { errorMessage, onSubmit } = useTotpCodeVerification(flow, errorCallback);
|
||||
|
||||
return (
|
||||
<VerificationCodeInput
|
||||
name="totpCode"
|
||||
value={code}
|
||||
className={styles.totpCodeInput}
|
||||
error={errorMessage}
|
||||
onChange={(code) => {
|
||||
setCode(code);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
if (code.length === totpCodeLength && code.every(Boolean)) {
|
||||
onSubmit(code.join(''));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
const handleSubmit = useCallback(
|
||||
async (code: string[]) => {
|
||||
setIsSubmitting(true);
|
||||
await onSubmit(code.join(''));
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
[onSubmit]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<VerificationCodeInput
|
||||
name="totpCode"
|
||||
value={codeInput}
|
||||
className={styles.totpCodeInput}
|
||||
error={errorMessage}
|
||||
onChange={(code) => {
|
||||
setCodeInput(code);
|
||||
if (isCodeReady(code)) {
|
||||
void handleSubmit(code);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="action.continue"
|
||||
type="primary"
|
||||
className={styles.continueButton}
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={!isCodeReady(codeInput)}
|
||||
onClick={() => {
|
||||
void handleSubmit(codeInput);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ const useTotpCodeVerification = (flow: UserMfaFlow, errorCallback?: () => void)
|
|||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(code: string) => {
|
||||
void sendMfaPayload(
|
||||
async (code: string) => {
|
||||
await sendMfaPayload(
|
||||
{ flow, payload: { type: MfaFactor.TOTP, code } },
|
||||
invalidCodeErrorHandlers,
|
||||
errorCallback
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
align-self: start;
|
||||
}
|
||||
|
||||
.switch {
|
||||
.switch,
|
||||
.continueButton {
|
||||
margin-top: _.unit(6);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import TextLink from '@/components/TextLink';
|
||||
import VerificationCodeInput, { defaultLength } from '@/components/VerificationCode';
|
||||
import { UserFlow } from '@/types';
|
||||
|
@ -21,13 +22,18 @@ type Props = {
|
|||
};
|
||||
|
||||
const VerificationCode = ({ flow, identifier, className, hasPasswordButton, target }: Props) => {
|
||||
const [code, setCode] = useState<string[]>([]);
|
||||
const [codeInput, setCodeInput] = useState<string[]>([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isCodeInputReady = useMemo(
|
||||
() => codeInput.length === defaultLength && codeInput.every(Boolean),
|
||||
[codeInput]
|
||||
);
|
||||
|
||||
const useVerificationCode = getCodeVerificationHookByFlow(flow);
|
||||
|
||||
const errorCallback = useCallback(() => {
|
||||
setCode([]);
|
||||
setCodeInput([]);
|
||||
}, []);
|
||||
|
||||
const { errorMessage, clearErrorMessage, onSubmit } = useVerificationCode(
|
||||
|
@ -42,24 +48,37 @@ const VerificationCode = ({ flow, identifier, className, hasPasswordButton, targ
|
|||
target
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (code.length === defaultLength && code.every(Boolean)) {
|
||||
const payload =
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (code: string[]) => {
|
||||
setIsSubmitting(true);
|
||||
|
||||
await onSubmit(
|
||||
identifier === SignInIdentifier.Email
|
||||
? { email: target, verificationCode: code.join('') }
|
||||
: { phone: target, verificationCode: code.join('') };
|
||||
void onSubmit(payload);
|
||||
: { phone: target, verificationCode: code.join('') }
|
||||
);
|
||||
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
[identifier, onSubmit, target]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isCodeInputReady) {
|
||||
void handleSubmit(codeInput);
|
||||
}
|
||||
}, [code, identifier, onSubmit, target]);
|
||||
}, [codeInput, handleSubmit, isCodeInputReady]);
|
||||
|
||||
return (
|
||||
<form className={classNames(styles.form, className)}>
|
||||
<VerificationCodeInput
|
||||
name="passcode"
|
||||
className={classNames(styles.inputField, errorMessage && styles.withError)}
|
||||
value={code}
|
||||
value={codeInput}
|
||||
error={errorMessage}
|
||||
onChange={setCode}
|
||||
onChange={setCodeInput}
|
||||
/>
|
||||
<div className={styles.message}>
|
||||
{isRunning ? (
|
||||
|
@ -75,7 +94,7 @@ const VerificationCode = ({ flow, identifier, className, hasPasswordButton, targ
|
|||
onClick={async () => {
|
||||
clearErrorMessage();
|
||||
await onResendVerificationCode();
|
||||
setCode([]);
|
||||
setCodeInput([]);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
|
@ -88,6 +107,16 @@ const VerificationCode = ({ flow, identifier, className, hasPasswordButton, targ
|
|||
{flow === UserFlow.SignIn && hasPasswordButton && (
|
||||
<PasswordSignInLink className={styles.switch} />
|
||||
)}
|
||||
<Button
|
||||
title="action.continue"
|
||||
type="primary"
|
||||
isDisabled={!isCodeInputReady}
|
||||
isLoading={isSubmitting}
|
||||
className={styles.continueButton}
|
||||
onClick={() => {
|
||||
void handleSubmit(codeInput);
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -27,10 +27,14 @@ const Consent = () => {
|
|||
const [consentData, setConsentData] = useState<ConsentInfoResponse>();
|
||||
const [selectedOrganization, setSelectedOrganization] = useState<Organization>();
|
||||
|
||||
const [isConsentLoading, setIsConsentLoading] = useState(false);
|
||||
|
||||
const asyncGetConsentInfo = useApi(getConsentInfo);
|
||||
|
||||
const consentHandler = useCallback(async () => {
|
||||
setIsConsentLoading(true);
|
||||
const [error, result] = await asyncConsent(selectedOrganization?.id);
|
||||
setIsConsentLoading(false);
|
||||
|
||||
if (error) {
|
||||
await handleError(error);
|
||||
|
@ -113,7 +117,7 @@ const Consent = () => {
|
|||
window.location.replace(consentData.redirectUri);
|
||||
}}
|
||||
/>
|
||||
<Button title="action.authorize" onClick={consentHandler} />
|
||||
<Button title="action.authorize" isLoading={isConsentLoading} onClick={consentHandler} />
|
||||
</div>
|
||||
{!showTerms && (
|
||||
<div className={styles.redirectUri}>
|
||||
|
|
|
@ -43,7 +43,7 @@ const IdentifierProfileForm = ({
|
|||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isValid },
|
||||
formState: { errors, isValid, isSubmitting },
|
||||
} = useForm<FormState>({
|
||||
reValidateMode: 'onBlur',
|
||||
defaultValues: {
|
||||
|
@ -61,7 +61,7 @@ const IdentifierProfileForm = ({
|
|||
}, [clearErrorMessage, isValid]);
|
||||
|
||||
const onSubmitHandler = useCallback(
|
||||
async (event?: React.FormEvent<HTMLFormElement>) => {
|
||||
(event?: React.FormEvent<HTMLFormElement>) => {
|
||||
clearErrorMessage?.();
|
||||
|
||||
void handleSubmit(async ({ identifier: { type, value } }) => {
|
||||
|
@ -115,7 +115,7 @@ const IdentifierProfileForm = ({
|
|||
|
||||
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
|
||||
|
||||
<Button title="action.continue" htmlType="submit" />
|
||||
<Button title="action.continue" htmlType="submit" isLoading={isSubmitting} />
|
||||
|
||||
<input hidden type="submit" />
|
||||
</form>
|
||||
|
|
|
@ -42,12 +42,13 @@ const ForgotPasswordForm = ({
|
|||
UserFlow.ForgotPassword
|
||||
);
|
||||
|
||||
const { setForgotPasswordIdentifierInputValue } = useContext(UserInteractionContext);
|
||||
const { setForgotPasswordIdentifierInputValue, setIdentifierInputValue } =
|
||||
useContext(UserInteractionContext);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isValid },
|
||||
formState: { errors, isValid, isSubmitting },
|
||||
} = useForm<FormState>({
|
||||
reValidateMode: 'onBlur',
|
||||
defaultValues: {
|
||||
|
@ -122,7 +123,7 @@ const ForgotPasswordForm = ({
|
|||
|
||||
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
|
||||
|
||||
<Button title="action.continue" htmlType="submit" />
|
||||
<Button title="action.continue" htmlType="submit" isLoading={isSubmitting} />
|
||||
|
||||
<input hidden type="submit" />
|
||||
</form>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { MfaFactor } from '@logto/schemas';
|
||||
import { t } from 'i18next';
|
||||
import { useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { validate } from 'superstruct';
|
||||
|
||||
|
@ -18,6 +19,7 @@ import * as styles from './index.module.scss';
|
|||
const BackupCodeBinding = () => {
|
||||
const { copyText, downloadText } = useTextHandler();
|
||||
const sendMfaPayload = useSendMfaPayload();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const { state } = useLocation();
|
||||
const [, backupCodeBindingState] = validate(state, backupCodeBindingStateGuard);
|
||||
|
@ -64,11 +66,14 @@ const BackupCodeBinding = () => {
|
|||
</div>
|
||||
<Button
|
||||
title="action.continue"
|
||||
onClick={() => {
|
||||
void sendMfaPayload({
|
||||
isLoading={isSubmitting}
|
||||
onClick={async () => {
|
||||
setIsSubmitting(true);
|
||||
await sendMfaPayload({
|
||||
flow: UserMfaFlow.MfaBinding,
|
||||
payload: { type: MfaFactor.BackupCode },
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { conditional } from '@silverhand/essentials';
|
||||
import { useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { validate } from 'superstruct';
|
||||
|
||||
|
@ -19,6 +20,7 @@ const WebAuthnBinding = () => {
|
|||
const [, webAuthnState] = validate(state, webAuthnStateGuard);
|
||||
const handleWebAuthn = useWebAuthnOperation();
|
||||
const skipMfa = useSkipMfa();
|
||||
const [isCreatingPasskey, setIsCreatingPasskey] = useState(false);
|
||||
|
||||
if (!webAuthnState) {
|
||||
return <ErrorPage title="error.invalid_session" />;
|
||||
|
@ -38,8 +40,11 @@ const WebAuthnBinding = () => {
|
|||
>
|
||||
<Button
|
||||
title="mfa.create_a_passkey"
|
||||
onClick={() => {
|
||||
void handleWebAuthn(options);
|
||||
isLoading={isCreatingPasskey}
|
||||
onClick={async () => {
|
||||
setIsCreatingPasskey(true);
|
||||
await handleWebAuthn(options);
|
||||
setIsCreatingPasskey(false);
|
||||
}}
|
||||
/>
|
||||
<SwitchMfaFactorsLink
|
||||
|
|
|
@ -22,12 +22,16 @@ type FormState = {
|
|||
const BackupCodeVerification = () => {
|
||||
const flowState = useMfaFlowState();
|
||||
const sendMfaPayload = useSendMfaPayload();
|
||||
const { register, handleSubmit } = useForm<FormState>({ defaultValues: { code: '' } });
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<FormState>({ defaultValues: { code: '' } });
|
||||
|
||||
const onSubmitHandler = useCallback(
|
||||
(event?: FormEvent<HTMLFormElement>) => {
|
||||
void handleSubmit(async ({ code }) => {
|
||||
void sendMfaPayload({
|
||||
await sendMfaPayload({
|
||||
flow: UserMfaFlow.MfaVerification,
|
||||
payload: { type: MfaFactor.BackupCode, code },
|
||||
});
|
||||
|
@ -53,7 +57,7 @@ const BackupCodeVerification = () => {
|
|||
className={styles.backupCodeInput}
|
||||
{...register('code')}
|
||||
/>
|
||||
<Button title="action.continue" htmlType="submit" />
|
||||
<Button title="action.continue" htmlType="submit" isLoading={isSubmitting} />
|
||||
</form>
|
||||
</SectionLayout>
|
||||
<SwitchMfaFactorsLink
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { validate } from 'superstruct';
|
||||
|
||||
|
@ -17,6 +18,7 @@ const WebAuthnVerification = () => {
|
|||
const { state } = useLocation();
|
||||
const [, webAuthnState] = validate(state, webAuthnStateGuard);
|
||||
const handleWebAuthn = useWebAuthnOperation();
|
||||
const [isVerifying, setIsVerifying] = useState(false);
|
||||
|
||||
if (!webAuthnState) {
|
||||
return <ErrorPage title="error.invalid_session" />;
|
||||
|
@ -37,8 +39,11 @@ const WebAuthnVerification = () => {
|
|||
<Button
|
||||
title="action.verify_via_passkey"
|
||||
className={styles.verifyButton}
|
||||
onClick={() => {
|
||||
void handleWebAuthn(options);
|
||||
isLoading={isVerifying}
|
||||
onClick={async () => {
|
||||
setIsVerifying(true);
|
||||
await handleWebAuthn(options);
|
||||
setIsVerifying(false);
|
||||
}}
|
||||
/>
|
||||
</SectionLayout>
|
||||
|
|
|
@ -40,7 +40,7 @@ const IdentifierRegisterForm = ({ className, autoFocus, signUpMethods }: Props)
|
|||
const {
|
||||
watch,
|
||||
handleSubmit,
|
||||
formState: { errors, isValid },
|
||||
formState: { errors, isValid, isSubmitting },
|
||||
control,
|
||||
} = useForm<FormState>({
|
||||
reValidateMode: 'onBlur',
|
||||
|
@ -154,6 +154,7 @@ const IdentifierRegisterForm = ({ className, autoFocus, signUpMethods }: Props)
|
|||
title={showSingleSignOnForm ? 'action.single_sign_on' : 'action.create_account'}
|
||||
icon={showSingleSignOnForm ? <LockIcon /> : undefined}
|
||||
htmlType="submit"
|
||||
isLoading={isSubmitting}
|
||||
/>
|
||||
|
||||
<input hidden type="submit" />
|
||||
|
|
|
@ -44,7 +44,7 @@ const IdentifierSignInForm = ({ className, autoFocus, signInMethods }: Props) =>
|
|||
watch,
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isValid },
|
||||
formState: { errors, isValid, isSubmitting },
|
||||
} = useForm<FormState>({
|
||||
reValidateMode: 'onBlur',
|
||||
});
|
||||
|
@ -153,6 +153,7 @@ const IdentifierSignInForm = ({ className, autoFocus, signInMethods }: Props) =>
|
|||
title={showSingleSignOnForm ? 'action.single_sign_on' : 'action.sign_in'}
|
||||
icon={showSingleSignOnForm ? <LockIcon /> : undefined}
|
||||
htmlType="submit"
|
||||
isLoading={isSubmitting}
|
||||
/>
|
||||
|
||||
<input hidden type="submit" />
|
||||
|
|
|
@ -45,7 +45,7 @@ const PasswordSignInForm = ({ className, autoFocus, signInMethods }: Props) => {
|
|||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isValid },
|
||||
formState: { errors, isValid, isSubmitting },
|
||||
} = useForm<FormState>({
|
||||
reValidateMode: 'onBlur',
|
||||
defaultValues: {
|
||||
|
@ -174,6 +174,7 @@ const PasswordSignInForm = ({ className, autoFocus, signInMethods }: Props) => {
|
|||
title={showSingleSignOnForm ? 'action.single_sign_on' : 'action.sign_in'}
|
||||
icon={showSingleSignOnForm ? <LockIcon /> : undefined}
|
||||
htmlType="submit"
|
||||
isLoading={isSubmitting}
|
||||
/>
|
||||
|
||||
<input hidden type="submit" />
|
||||
|
|
|
@ -47,7 +47,7 @@ const PasswordForm = ({
|
|||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isValid },
|
||||
formState: { errors, isValid, isSubmitting },
|
||||
} = useForm<FormState>({
|
||||
reValidateMode: 'onBlur',
|
||||
defaultValues: {
|
||||
|
@ -114,7 +114,7 @@ const PasswordForm = ({
|
|||
<ForgotPasswordLink className={styles.link} identifier={identifier} value={value} />
|
||||
)}
|
||||
|
||||
<Button title="action.continue" name="submit" htmlType="submit" />
|
||||
<Button title="action.continue" name="submit" htmlType="submit" isLoading={isSubmitting} />
|
||||
|
||||
{identifier !== SignInIdentifier.Username && isVerificationCodeEnabled && (
|
||||
<VerificationCodeLink className={styles.switch} identifier={identifier} value={value} />
|
||||
|
|
|
@ -26,7 +26,7 @@ const SingleSignOnEmail = () => {
|
|||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isValid },
|
||||
formState: { errors, isValid, isSubmitting },
|
||||
} = useForm<FormState>({
|
||||
reValidateMode: 'onBlur',
|
||||
});
|
||||
|
@ -82,7 +82,12 @@ const SingleSignOnEmail = () => {
|
|||
|
||||
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
|
||||
|
||||
<Button title="action.single_sign_on" htmlType="submit" icon={<LockIcon />} />
|
||||
<Button
|
||||
title="action.single_sign_on"
|
||||
htmlType="submit"
|
||||
icon={<LockIcon />}
|
||||
isLoading={isSubmitting}
|
||||
/>
|
||||
|
||||
<input hidden type="submit" />
|
||||
</form>
|
||||
|
|
Loading…
Reference in a new issue