mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
Merge pull request #2137 from logto-io/simeng-log-4262
refactor(ui): refactor terms of use modals
This commit is contained in:
commit
b7239af7f5
22 changed files with 215 additions and 457 deletions
|
@ -62,7 +62,6 @@
|
|||
"react-dom": "^18.0.0",
|
||||
"react-i18next": "^11.18.3",
|
||||
"react-modal": "^3.15.1",
|
||||
"react-modal-promise": "^1.0.2",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"react-string-replace": "^1.0.0",
|
||||
"react-timer-hook": "^3.0.5",
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.overlay {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
top: 40px;
|
||||
bottom: 40px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--color-dialogue);
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@include _.flex_column(stretch, center);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: _.unit(5);
|
||||
flex: 1;
|
||||
@include _.flex_column;
|
||||
}
|
||||
|
||||
iframe {
|
||||
display: block;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.footer {
|
||||
border-top: 1px solid var(--color-divider);
|
||||
@include _.flex_row;
|
||||
padding: _.unit(5);
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
> button:first-child {
|
||||
margin-right: _.unit(2);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import { LoadingIcon } from '@/components/LoadingLayer';
|
||||
|
||||
import * as modalStyles from '../../scss/modal.module.scss';
|
||||
import * as styles from './IframeConfirmModal.module.scss';
|
||||
import { ModalProps } from './type';
|
||||
|
||||
type Props = { url: string } & Omit<ModalProps, 'children'>;
|
||||
|
||||
const IframeConfirmModal = ({
|
||||
className,
|
||||
isOpen = false,
|
||||
url,
|
||||
cancelText = 'action.cancel',
|
||||
confirmText = 'action.confirm',
|
||||
onConfirm,
|
||||
onClose,
|
||||
}: Props) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
role="dialog"
|
||||
isOpen={isOpen}
|
||||
className={classNames(styles.modal, className)}
|
||||
overlayClassName={classNames(modalStyles.overlay, styles.overlay)}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
{isLoading && <LoadingIcon />}
|
||||
<iframe
|
||||
sandbox={undefined}
|
||||
className={isLoading ? styles.hidden : undefined}
|
||||
// For styling use
|
||||
// eslint-disable-next-line jsx-a11y/aria-role
|
||||
role="iframe"
|
||||
src={url}
|
||||
title="terms"
|
||||
frameBorder="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
onLoad={() => {
|
||||
setIsLoading(false);
|
||||
}}
|
||||
onError={() => {
|
||||
setIsLoading(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<Button title={cancelText} type="secondary" onClick={onClose} />
|
||||
<Button title={confirmText} onClick={onConfirm ?? onClose} />
|
||||
</div>
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default IframeConfirmModal;
|
|
@ -37,3 +37,29 @@
|
|||
margin-right: _.unit(2);
|
||||
}
|
||||
}
|
||||
|
||||
.iframeModal {
|
||||
top: 40px;
|
||||
bottom: 40px;
|
||||
transform: none;
|
||||
|
||||
.container {
|
||||
padding: 0;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@include _.flex_column(stretch, center);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: _.unit(5);
|
||||
flex: 1;
|
||||
@include _.flex_column;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 0;
|
||||
border-top: 1px solid var(--color-divider);
|
||||
padding: _.unit(5);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
export { default as WebModal } from './AcModal';
|
||||
export { default as MobileModal } from './MobileModal';
|
||||
export { default as IframeModal } from './IframeConfirmModal';
|
||||
export { modalPromisify } from './modalPromisify';
|
||||
|
||||
export type { ModalProps } from './type';
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import { fireEvent } from '@testing-library/react';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import TermsOfUseConfirmModal from '@/containers/TermsOfUse/TermsOfUseConfirmModal';
|
||||
|
||||
import { modalPromisify } from '.';
|
||||
|
||||
describe('modalPromisify', () => {
|
||||
const onResolve = jest.fn();
|
||||
const onReject = jest.fn();
|
||||
|
||||
it('resolve properly', () => {
|
||||
const PromisifyModal = modalPromisify(TermsOfUseConfirmModal);
|
||||
|
||||
const { getByText } = renderWithPageContext(
|
||||
<PromisifyModal isOpen instanceId="foo" onResolve={onResolve} onReject={onReject} />
|
||||
);
|
||||
|
||||
const confirmButton = getByText('action.agree');
|
||||
fireEvent.click(confirmButton);
|
||||
|
||||
expect(onResolve).toBeCalled();
|
||||
});
|
||||
|
||||
it('reject', () => {
|
||||
const PromisifyModal = modalPromisify(TermsOfUseConfirmModal);
|
||||
|
||||
const { getByText } = renderWithPageContext(
|
||||
<PromisifyModal isOpen instanceId="foo" onResolve={onResolve} onReject={onReject} />
|
||||
);
|
||||
|
||||
const cancelButton = getByText('action.cancel');
|
||||
fireEvent.click(cancelButton);
|
||||
|
||||
expect(onReject).toBeCalled();
|
||||
});
|
||||
});
|
|
@ -1,29 +0,0 @@
|
|||
import { InstanceProps } from 'react-modal-promise';
|
||||
|
||||
import { ConfirmModalMessage } from '@/types';
|
||||
|
||||
type Props = {
|
||||
isOpen?: boolean;
|
||||
onConfirm: () => void;
|
||||
onClose: (message?: ConfirmModalMessage) => void;
|
||||
};
|
||||
|
||||
export const modalPromisify =
|
||||
(ConfirmModal: (props: Props) => JSX.Element) =>
|
||||
({
|
||||
isOpen,
|
||||
onResolve,
|
||||
onReject,
|
||||
}: Omit<InstanceProps<boolean | ConfirmModalMessage>, 'open' | 'close'>) => {
|
||||
return (
|
||||
<ConfirmModal
|
||||
isOpen={isOpen}
|
||||
onConfirm={() => {
|
||||
onResolve(true);
|
||||
}}
|
||||
onClose={(message?: ConfirmModalMessage) => {
|
||||
onReject(message ?? false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -4,7 +4,7 @@ import { useState, useRef, useMemo, createContext, useCallback } from 'react';
|
|||
import { WebModal, MobileModal, ModalProps } from '@/components/ConfirmModal';
|
||||
import usePlatform from '@/hooks/use-platform';
|
||||
|
||||
export type ChildRenderProps = {
|
||||
export type ModalContentRenderProps = {
|
||||
confirm: (data?: unknown) => void;
|
||||
cancel: (data?: unknown) => void;
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ export type ChildRenderProps = {
|
|||
type ConfirmModalType = 'alert' | 'confirm';
|
||||
|
||||
type ConfirmModalState = Omit<ModalProps, 'onClose' | 'onConfirm' | 'children'> & {
|
||||
ModalContent: string | ((props: ChildRenderProps) => Nullable<JSX.Element>);
|
||||
ModalContent: string | ((props: ModalContentRenderProps) => Nullable<JSX.Element>);
|
||||
type: ConfirmModalType;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.iframe {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
import { LoadingIcon } from '@/components/LoadingLayer';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = { url?: string; title?: string };
|
||||
|
||||
const IframeConfirmModalContent = ({ url, title }: Props) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading && <LoadingIcon />}
|
||||
<iframe
|
||||
role="article"
|
||||
sandbox={undefined}
|
||||
className={isLoading ? styles.hidden : styles.iframe}
|
||||
src={url}
|
||||
title={title}
|
||||
frameBorder="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
onLoad={() => {
|
||||
setIsLoading(false);
|
||||
}}
|
||||
onError={() => {
|
||||
setIsLoading(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default IframeConfirmModalContent;
|
||||
|
||||
export const createIframeConfirmModalContent = (url?: string, title?: string) => (
|
||||
<IframeConfirmModalContent url={url} title={title} />
|
||||
);
|
|
@ -1,16 +0,0 @@
|
|||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
|
||||
import TermsOfUseModal from '.';
|
||||
|
||||
describe('TermsOfUseModal', () => {
|
||||
const onConfirm = jest.fn();
|
||||
const onCancel = jest.fn();
|
||||
|
||||
it('render properly', () => {
|
||||
const { queryByText } = renderWithPageContext(
|
||||
<TermsOfUseModal isOpen onConfirm={onConfirm} onClose={onCancel} />
|
||||
);
|
||||
|
||||
expect(queryByText('description.agree_with_terms_modal')).not.toBeNull();
|
||||
});
|
||||
});
|
|
@ -1,67 +0,0 @@
|
|||
import { useContext } from 'react';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import { create } from 'react-modal-promise';
|
||||
|
||||
import { WebModal, MobileModal, modalPromisify } from '@/components/ConfirmModal';
|
||||
import TextLink from '@/components/TextLink';
|
||||
import { PageContext } from '@/hooks/use-page-context';
|
||||
import usePlatform from '@/hooks/use-platform';
|
||||
import { ConfirmModalMessage } from '@/types';
|
||||
|
||||
/**
|
||||
* For web use only confirm modal, does not contain Terms iframe
|
||||
*/
|
||||
|
||||
type Props = {
|
||||
isOpen?: boolean;
|
||||
onConfirm: () => void;
|
||||
onClose: (message?: ConfirmModalMessage) => void;
|
||||
};
|
||||
|
||||
const TermsOfUseConfirmModal = ({ isOpen = false, onConfirm, onClose }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { isMobile } = usePlatform();
|
||||
const { setTermsAgreement, experienceSettings } = useContext(PageContext);
|
||||
const { termsOfUse } = experienceSettings ?? {};
|
||||
|
||||
const ConfirmModal = isMobile ? MobileModal : WebModal;
|
||||
|
||||
const terms = t('description.terms_of_use');
|
||||
|
||||
const linkProps = isMobile
|
||||
? {
|
||||
onClick: () => {
|
||||
onClose(ConfirmModalMessage.SHOW_TERMS_DETAIL_MODAL);
|
||||
},
|
||||
}
|
||||
: {
|
||||
href: termsOfUse?.contentUrl,
|
||||
target: '_blank',
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
isOpen={isOpen}
|
||||
confirmText="action.agree"
|
||||
onConfirm={() => {
|
||||
setTermsAgreement(true);
|
||||
onConfirm();
|
||||
}}
|
||||
onClose={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<Trans
|
||||
components={{
|
||||
link: <TextLink key={terms} text="description.terms_of_use" {...linkProps} />,
|
||||
}}
|
||||
>
|
||||
{t('description.agree_with_terms_modal')}
|
||||
</Trans>
|
||||
</ConfirmModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsOfUseConfirmModal;
|
||||
|
||||
export const termsOfUseConfirmModalPromise = create(modalPromisify(TermsOfUseConfirmModal));
|
|
@ -0,0 +1,45 @@
|
|||
import { useContext } from 'react';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
|
||||
import TextLink from '@/components/TextLink';
|
||||
import { ModalContentRenderProps } from '@/hooks/use-confirm-modal';
|
||||
import { PageContext } from '@/hooks/use-page-context';
|
||||
import usePlatform from '@/hooks/use-platform';
|
||||
import { ConfirmModalMessage } from '@/types';
|
||||
|
||||
const TermsOfUseConfirmModalContent = ({ cancel }: ModalContentRenderProps) => {
|
||||
const { experienceSettings } = useContext(PageContext);
|
||||
const { termsOfUse } = experienceSettings ?? {};
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { isMobile } = usePlatform();
|
||||
|
||||
const linkProps = isMobile
|
||||
? {
|
||||
onClick: () => {
|
||||
cancel(ConfirmModalMessage.SHOW_TERMS_DETAIL_MODAL);
|
||||
},
|
||||
}
|
||||
: {
|
||||
href: termsOfUse?.contentUrl,
|
||||
target: '_blank',
|
||||
};
|
||||
|
||||
return (
|
||||
<Trans
|
||||
components={{
|
||||
link: (
|
||||
<TextLink
|
||||
key={t('description.terms_of_use')}
|
||||
text="description.terms_of_use"
|
||||
{...linkProps}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{t('description.agree_with_terms_modal')}
|
||||
</Trans>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsOfUseConfirmModalContent;
|
|
@ -1,36 +0,0 @@
|
|||
import { screen, fireEvent } from '@testing-library/react';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||
import { mockSignInExperienceSettings } from '@/__mocks__/logto';
|
||||
|
||||
import TermsOfUseIframeModal from '.';
|
||||
|
||||
describe('TermsOfUseModal', () => {
|
||||
const onConfirm = jest.fn();
|
||||
const onCancel = jest.fn();
|
||||
|
||||
it('render properly', () => {
|
||||
const { queryByText, getByText } = renderWithPageContext(
|
||||
<SettingsProvider>
|
||||
<TermsOfUseIframeModal isOpen onConfirm={onConfirm} onClose={onCancel} />
|
||||
</SettingsProvider>
|
||||
);
|
||||
|
||||
expect(queryByText('action.agree')).not.toBeNull();
|
||||
|
||||
const iframe = screen.queryByRole('iframe');
|
||||
|
||||
expect(iframe).not.toBeNull();
|
||||
|
||||
if (iframe) {
|
||||
expect(iframe).toHaveProperty('src', mockSignInExperienceSettings.termsOfUse.contentUrl);
|
||||
}
|
||||
|
||||
const confirmButton = getByText('action.agree');
|
||||
|
||||
fireEvent.click(confirmButton);
|
||||
|
||||
expect(onConfirm).toBeCalled();
|
||||
});
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
import { useContext } from 'react';
|
||||
import { create } from 'react-modal-promise';
|
||||
|
||||
import { IframeModal, modalPromisify } from '@/components/ConfirmModal';
|
||||
import { PageContext } from '@/hooks/use-page-context';
|
||||
|
||||
/**
|
||||
* For mobile use only, includes embedded Terms iframe
|
||||
*/
|
||||
type Props = {
|
||||
isOpen?: boolean;
|
||||
onConfirm: () => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const TermsOfUseIframeModal = ({ isOpen = false, onConfirm, onClose }: Props) => {
|
||||
const { setTermsAgreement, experienceSettings } = useContext(PageContext);
|
||||
const { termsOfUse } = experienceSettings ?? {};
|
||||
|
||||
return (
|
||||
<IframeModal
|
||||
isOpen={isOpen}
|
||||
confirmText="action.agree"
|
||||
url={termsOfUse?.contentUrl ?? ''}
|
||||
onConfirm={() => {
|
||||
setTermsAgreement(true);
|
||||
onConfirm();
|
||||
}}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsOfUseIframeModal;
|
||||
|
||||
export const termsOfUseIframeModalPromise = create(modalPromisify(TermsOfUseIframeModal));
|
|
@ -1,5 +1,3 @@
|
|||
import ModalContainer from 'react-modal-promise';
|
||||
|
||||
import TermsOfUseComponent from '@/components/TermsOfUse';
|
||||
import usePlatform from '@/hooks/use-platform';
|
||||
import useTerms from '@/hooks/use-terms';
|
||||
|
@ -18,19 +16,16 @@ const TermsOfUse = ({ className }: Props) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TermsOfUseComponent
|
||||
className={className}
|
||||
name="termsAgreement"
|
||||
termsUrl={termsSettings.contentUrl}
|
||||
isChecked={termsAgreement}
|
||||
onChange={(checked) => {
|
||||
setTermsAgreement(checked);
|
||||
}}
|
||||
onTermsClick={isMobile ? termsOfUseIframeModalHandler : undefined}
|
||||
/>
|
||||
<ModalContainer />
|
||||
</>
|
||||
<TermsOfUseComponent
|
||||
className={className}
|
||||
name="termsAgreement"
|
||||
termsUrl={termsSettings.contentUrl}
|
||||
isChecked={termsAgreement}
|
||||
onChange={(checked) => {
|
||||
setTermsAgreement(checked);
|
||||
}}
|
||||
onTermsClick={isMobile ? termsOfUseIframeModalHandler : undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -4,22 +4,14 @@ import { act } from 'react-dom/test-utils';
|
|||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||
import { signInBasic } from '@/apis/sign-in';
|
||||
import { termsOfUseConfirmModalPromise } from '@/containers/TermsOfUse/TermsOfUseConfirmModal';
|
||||
import { termsOfUseIframeModalPromise } from '@/containers/TermsOfUse/TermsOfUseIframeModal';
|
||||
import { ConfirmModalMessage } from '@/types';
|
||||
import ConfirmModalProvider from '@/containers/ConfirmModalProvider';
|
||||
|
||||
import UsernameSignIn from '.';
|
||||
|
||||
jest.mock('@/apis/sign-in', () => ({ signInBasic: jest.fn(async () => 0) }));
|
||||
jest.mock('@/containers/TermsOfUse/TermsOfUseConfirmModal', () => ({
|
||||
termsOfUseConfirmModalPromise: jest.fn().mockResolvedValue(true),
|
||||
jest.mock('react-device-detect', () => ({
|
||||
isMobile: true,
|
||||
}));
|
||||
jest.mock('@/containers/TermsOfUse/TermsOfUseIframeModal', () => ({
|
||||
termsOfUseIframeModalPromise: jest.fn().mockResolvedValue(true),
|
||||
}));
|
||||
|
||||
const termsOfUseConfirmModalPromiseMock = termsOfUseConfirmModalPromise as jest.Mock;
|
||||
const termsOfUseIframeModalPromiseMock = termsOfUseIframeModalPromise as jest.Mock;
|
||||
|
||||
describe('<UsernameSignIn>', () => {
|
||||
afterEach(() => {
|
||||
|
@ -70,41 +62,11 @@ describe('<UsernameSignIn>', () => {
|
|||
});
|
||||
|
||||
test('should show terms confirm modal', async () => {
|
||||
const { getByText, container } = renderWithPageContext(
|
||||
const { queryByText, getByText, container } = renderWithPageContext(
|
||||
<SettingsProvider>
|
||||
<UsernameSignIn />
|
||||
</SettingsProvider>
|
||||
);
|
||||
const submitButton = getByText('action.sign_in');
|
||||
|
||||
const usernameInput = container.querySelector('input[name="username"]');
|
||||
const passwordInput = container.querySelector('input[name="password"]');
|
||||
|
||||
if (usernameInput) {
|
||||
fireEvent.change(usernameInput, { target: { value: 'username' } });
|
||||
}
|
||||
|
||||
if (passwordInput) {
|
||||
fireEvent.change(passwordInput, { target: { value: 'password' } });
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(submitButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(termsOfUseConfirmModalPromiseMock).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should show terms detail modal', async () => {
|
||||
termsOfUseConfirmModalPromiseMock.mockRejectedValue(
|
||||
ConfirmModalMessage.SHOW_TERMS_DETAIL_MODAL
|
||||
);
|
||||
|
||||
const { getByText, container } = renderWithPageContext(
|
||||
<SettingsProvider>
|
||||
<UsernameSignIn />
|
||||
<ConfirmModalProvider>
|
||||
<UsernameSignIn />
|
||||
</ConfirmModalProvider>
|
||||
</SettingsProvider>
|
||||
);
|
||||
const submitButton = getByText('action.sign_in');
|
||||
|
@ -124,10 +86,49 @@ describe('<UsernameSignIn>', () => {
|
|||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
expect(signInBasic).not.toBeCalled();
|
||||
await waitFor(() => {
|
||||
expect(queryByText('description.agree_with_terms_modal')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
test('should show terms detail modal', async () => {
|
||||
const { getByText, queryByText, container, queryByRole } = renderWithPageContext(
|
||||
<SettingsProvider>
|
||||
<ConfirmModalProvider>
|
||||
<UsernameSignIn />
|
||||
</ConfirmModalProvider>
|
||||
</SettingsProvider>
|
||||
);
|
||||
const submitButton = getByText('action.sign_in');
|
||||
|
||||
const usernameInput = container.querySelector('input[name="username"]');
|
||||
const passwordInput = container.querySelector('input[name="password"]');
|
||||
|
||||
if (usernameInput) {
|
||||
fireEvent.change(usernameInput, { target: { value: 'username' } });
|
||||
}
|
||||
|
||||
if (passwordInput) {
|
||||
fireEvent.change(passwordInput, { target: { value: 'password' } });
|
||||
}
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(termsOfUseIframeModalPromiseMock).toBeCalled();
|
||||
expect(queryByText('description.agree_with_terms_modal')).not.toBeNull();
|
||||
});
|
||||
|
||||
const termsLink = getByText('description.terms_of_use');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(termsLink);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('action.agree')).not.toBeNull();
|
||||
expect(queryByRole('article')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -2,4 +2,6 @@ import { useContext } from 'react';
|
|||
|
||||
import { ConfirmModalContext } from '@/containers/ConfirmModalProvider';
|
||||
|
||||
export type { ModalContentRenderProps } from '@/containers/ConfirmModalProvider';
|
||||
|
||||
export const useConfirmModal = () => useContext(ConfirmModalContext);
|
||||
|
|
|
@ -1,34 +1,54 @@
|
|||
import { useContext, useCallback } from 'react';
|
||||
|
||||
import { termsOfUseConfirmModalPromise } from '@/containers/TermsOfUse/TermsOfUseConfirmModal';
|
||||
import { termsOfUseIframeModalPromise } from '@/containers/TermsOfUse/TermsOfUseIframeModal';
|
||||
import { createIframeConfirmModalContent } from '@/containers/TermsOfUse/IframeConfirmModalContent';
|
||||
import TermsOfUseConfirmModalContent from '@/containers/TermsOfUse/TermsOfUseConfirmModalContent';
|
||||
import { ConfirmModalMessage } from '@/types';
|
||||
import { flattenPromiseResult } from '@/utils/promisify';
|
||||
|
||||
import * as styles from '../components/ConfirmModal/MobileModal.module.scss';
|
||||
import { useConfirmModal } from './use-confirm-modal';
|
||||
import { PageContext } from './use-page-context';
|
||||
|
||||
const useTerms = () => {
|
||||
const { termsAgreement, setTermsAgreement, experienceSettings } = useContext(PageContext);
|
||||
const { show } = useConfirmModal();
|
||||
|
||||
const { termsOfUse } = experienceSettings ?? {};
|
||||
|
||||
const termsOfUseIframeModalHandler = useCallback(async () => {
|
||||
const [result] = await flattenPromiseResult<boolean>(termsOfUseIframeModalPromise());
|
||||
const [result] = await show({
|
||||
className: styles.iframeModal,
|
||||
ModalContent: () => createIframeConfirmModalContent(termsOfUse?.contentUrl),
|
||||
confirmText: 'action.agree',
|
||||
});
|
||||
|
||||
return Boolean(result);
|
||||
}, []);
|
||||
|
||||
const termsOfUseConfirmModalHandler = useCallback(async () => {
|
||||
const [result, error] = await flattenPromiseResult<boolean>(termsOfUseConfirmModalPromise());
|
||||
|
||||
if (error === ConfirmModalMessage.SHOW_TERMS_DETAIL_MODAL) {
|
||||
const result = await termsOfUseIframeModalHandler();
|
||||
|
||||
return result;
|
||||
// Update the local terms status
|
||||
if (result) {
|
||||
setTermsAgreement(true);
|
||||
}
|
||||
|
||||
return Boolean(result);
|
||||
}, [termsOfUseIframeModalHandler]);
|
||||
return result;
|
||||
}, [setTermsAgreement, show, termsOfUse?.contentUrl]);
|
||||
|
||||
const termsOfUseConfirmModalHandler = useCallback(async () => {
|
||||
const [result, data] = await show({
|
||||
ModalContent: TermsOfUseConfirmModalContent,
|
||||
confirmText: 'action.agree',
|
||||
});
|
||||
|
||||
// Show Terms Detail Confirm Modal
|
||||
if (data === ConfirmModalMessage.SHOW_TERMS_DETAIL_MODAL) {
|
||||
const detailResult = await termsOfUseIframeModalHandler();
|
||||
|
||||
return detailResult;
|
||||
}
|
||||
|
||||
// Update the local terms status
|
||||
if (result) {
|
||||
setTermsAgreement(true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [setTermsAgreement, show, termsOfUseIframeModalHandler]);
|
||||
|
||||
const termsValidation = useCallback(async () => {
|
||||
if (termsAgreement || !termsOfUse?.enabled || !termsOfUse.contentUrl) {
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import { flattenPromiseResult } from './promisify';
|
||||
|
||||
describe('promisify', () => {
|
||||
test('flattenPromiseResult', async () => {
|
||||
const promiseResolve = Promise.resolve('resolved');
|
||||
const [result, rejection] = await flattenPromiseResult(promiseResolve);
|
||||
|
||||
expect(result).toBe('resolved');
|
||||
expect(rejection).toBeUndefined();
|
||||
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
const promiseRejection = Promise.reject('rejected');
|
||||
const [_result, _rejection] = await flattenPromiseResult(promiseRejection);
|
||||
|
||||
expect(_result).toBeUndefined();
|
||||
expect(_rejection).toBe('rejected');
|
||||
});
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
export const flattenPromiseResult = async <T>(promise: Promise<T>): Promise<[T?, unknown?]> => {
|
||||
try {
|
||||
const result = await promise;
|
||||
|
||||
return [result];
|
||||
} catch (error: unknown) {
|
||||
return [undefined, error];
|
||||
}
|
||||
};
|
|
@ -692,7 +692,6 @@ importers:
|
|||
react-dom: ^18.0.0
|
||||
react-i18next: ^11.18.3
|
||||
react-modal: ^3.15.1
|
||||
react-modal-promise: ^1.0.2
|
||||
react-router-dom: ^6.2.2
|
||||
react-string-replace: ^1.0.0
|
||||
react-timer-hook: ^3.0.5
|
||||
|
@ -746,7 +745,6 @@ importers:
|
|||
react-dom: 18.2.0_react@18.2.0
|
||||
react-i18next: 11.18.3_shxxmfhtk2bc4pbx5cyq3uoph4
|
||||
react-modal: 3.15.1_biqbaboplfbrettd7655fr4n2y
|
||||
react-modal-promise: 1.0.2_biqbaboplfbrettd7655fr4n2y
|
||||
react-router-dom: 6.2.2_biqbaboplfbrettd7655fr4n2y
|
||||
react-string-replace: 1.0.0
|
||||
react-timer-hook: 3.0.5_biqbaboplfbrettd7655fr4n2y
|
||||
|
@ -13344,16 +13342,6 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/react-modal-promise/1.0.2_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-dqT618ROhG8qh1+O6EZkia5ELw3zaZWGpMX2YfEH4bgwYENPuFonqKw1W70LFx3K/SCZvVBcD6UYEI12yzYXzg==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0 || ^18.0.0'
|
||||
react-dom: '>=16.8.0'
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: true
|
||||
|
||||
/react-modal/3.15.1_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-duB9bxOaYg7Zt6TMFldIFxQRtSP+Dg3F1ZX3FXxSUn+3tZZ/9JCgeAQKDg7rhZSAqopq8TFRw3yIbnx77gyFTw==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
Loading…
Reference in a new issue