Skip to content

Commit

Permalink
refactor(console): use button loading in experience flow if possible
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoyijun committed Jul 12, 2024
1 parent 6bf3beb commit 76e6d77
Show file tree
Hide file tree
Showing 25 changed files with 201 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -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 />}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
6 changes: 4 additions & 2 deletions packages/experience/src/components/LoadingLayer/index.tsx
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@use '@/scss/underscore' as _;

.overlay {
position: fixed;
inset: 0;
@include _.flex-column;
z-index: 300;
}
13 changes: 13 additions & 0 deletions packages/experience/src/components/LoadingMask/index.tsx
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 11 additions & 6 deletions packages/experience/src/containers/SetPassword/Lite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -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: '' },
Expand All @@ -42,11 +42,11 @@ const Lite = ({ className, autoFocus, onSubmit, errorMessage, clearErrorMessage
}, [clearErrorMessage, isValid]);

const onSubmitHandler = useCallback(
(event?: React.FormEvent<HTMLFormElement>) => {
async (event?: React.FormEvent<HTMLFormElement>) => {
clearErrorMessage?.();

void handleSubmit((data, event) => {
onSubmit(data.newPassword);
await handleSubmit(async (data) => {
await onSubmit(data.newPassword);
})(event);
},
[clearErrorMessage, handleSubmit, onSubmit]
Expand All @@ -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>
Expand Down
17 changes: 11 additions & 6 deletions packages/experience/src/containers/SetPassword/SetPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -43,7 +43,7 @@ const SetPassword = ({
watch,
resetField,
handleSubmit,
formState: { errors, isValid },
formState: { errors, isValid, isSubmitting },
} = useForm<FieldState>({
reValidateMode: 'onBlur',
defaultValues: { newPassword: '', confirmPassword: '' },
Expand All @@ -56,11 +56,11 @@ const SetPassword = ({
}, [clearErrorMessage, isValid]);

const onSubmitHandler = useCallback(
(event?: React.FormEvent<HTMLFormElement>) => {
async (event?: React.FormEvent<HTMLFormElement>) => {
clearErrorMessage?.();

void handleSubmit((data, event) => {
onSubmit(data.newPassword);
await handleSubmit(async (data) => {
await onSubmit(data.newPassword);
})(event);
},
[clearErrorMessage, handleSubmit, onSubmit]
Expand Down Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion packages/experience/src/containers/SetPassword/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
.totpCodeInput {
margin-top: _.unit(4);
}

.continueButton {
margin-top: _.unit(6);
}
57 changes: 42 additions & 15 deletions packages/experience/src/containers/TotpCodeVerification/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useState } from 'react';

import Button from '@/components/Button';
import VerificationCodeInput from '@/components/VerificationCode';
import { type UserMfaFlow } from '@/types';

Expand All @@ -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);

const [isSubmitting, setIsSubmitting] = useState(false);

const handleSubmit = useCallback(
async (code: string[]) => {
setIsSubmitting(true);
await onSubmit(code.join(''));
setIsSubmitting(false);
},
[onSubmit]
);

return (
<VerificationCodeInput
name="totpCode"
value={code}
className={styles.totpCodeInput}
error={errorMessage}
onChange={(code) => {
setCode(code);

if (code.length === totpCodeLength && code.every(Boolean)) {
onSubmit(code.join(''));
}
}}
/>
<>
<VerificationCodeInput
name="totpCode"
value={codeInput}
className={styles.totpCodeInput}
error={errorMessage}
onChange={async (code) => {
setCodeInput(code);
if (isCodeReady(code)) {
await handleSubmit(code);
}
}}
/>
<Button
title="action.continue"
type="primary"
className={styles.continueButton}
isLoading={isSubmitting}
isDisabled={!isCodeReady(codeInput)}
onClick={async () => {
await handleSubmit(codeInput);
}}
/>
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
align-self: start;
}

.switch {
.switch,
.continueButton {
margin-top: _.unit(6);
}
}
39 changes: 31 additions & 8 deletions packages/experience/src/containers/VerificationCode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classNames from 'classnames';
import { useState, useEffect, useCallback } 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';
Expand All @@ -24,6 +25,8 @@ const VerificationCode = ({ flow, identifier, className, hasPasswordButton, targ
const [code, setCode] = useState<string[]>([]);
const { t } = useTranslation();

const isCodeReady = code.length === defaultLength && code.every(Boolean);

const useVerificationCode = getCodeVerificationHookByFlow(flow);

const errorCallback = useCallback(() => {
Expand All @@ -42,15 +45,27 @@ const VerificationCode = ({ flow, identifier, className, hasPasswordButton, targ
target
);

useEffect(() => {
if (code.length === defaultLength && code.every(Boolean)) {
const payload =
identifier === SignInIdentifier.Email
? { email: target, verificationCode: code.join('') }
: { phone: target, verificationCode: code.join('') };
void onSubmit(payload);
const [isSubmitting, setIsSubmitting] = useState(false);

const handleSubmit = useCallback(async () => {
if (!isCodeReady) {
return;
}
}, [code, identifier, onSubmit, target]);

setIsSubmitting(true);

await onSubmit(
identifier === SignInIdentifier.Email
? { email: target, verificationCode: code.join('') }
: { phone: target, verificationCode: code.join('') }
);

setIsSubmitting(false);
}, [code, identifier, isCodeReady, onSubmit, target]);

useEffect(() => {
void handleSubmit();
}, [handleSubmit]);

return (
<form className={classNames(styles.form, className)}>
Expand Down Expand Up @@ -88,6 +103,14 @@ const VerificationCode = ({ flow, identifier, className, hasPasswordButton, targ
{flow === UserFlow.SignIn && hasPasswordButton && (
<PasswordSignInLink className={styles.switch} />
)}
<Button
title="action.continue"
type="primary"
isDisabled={!isCodeReady}
isLoading={isSubmitting}
className={styles.continueButton}
onClick={handleSubmit}
/>
</form>
);
};
Expand Down
6 changes: 5 additions & 1 deletion packages/experience/src/pages/Consent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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}>
Expand Down
Loading

0 comments on commit 76e6d77

Please sign in to comment.