diff --git a/web/modals/modal.css b/web/modals/modal.css --- a/web/modals/modal.css +++ b/web/modals/modal.css @@ -30,6 +30,10 @@ padding: 32px 32px 0 32px; } +div.modalHeaderCentered { + justify-content: center; +} + h2.title { font-size: 20px; font-weight: 500; diff --git a/web/modals/modal.react.js b/web/modals/modal.react.js --- a/web/modals/modal.react.js +++ b/web/modals/modal.react.js @@ -17,6 +17,7 @@ +onClose: () => void, +withCloseButton?: boolean, +size?: ModalSize, + +modalHeaderCentered?: boolean, }; type ModalProps = { @@ -32,6 +33,7 @@ name, icon, withCloseButton = true, + modalHeaderCentered = false, } = props; const modalContainerClasses = classNames(css.modalContainer, { @@ -39,6 +41,11 @@ [css.modalContainerSmall]: size === 'small', }); + const modalHeader = classNames({ + [css.modalHeader]: true, + [css.modalHeaderCentered]: modalHeaderCentered, + }); + const cornerCloseButton = React.useMemo(() => { if (!withCloseButton) { return null; @@ -60,7 +67,7 @@ return (
-
+

{headerIcon} {name} diff --git a/web/modals/terms-and-privacy-modal.css b/web/modals/terms-and-privacy-modal.css new file mode 100644 --- /dev/null +++ b/web/modals/terms-and-privacy-modal.css @@ -0,0 +1,26 @@ +.container { + color: var(--modal-fg); + padding: 20px 32px; + text-align: center; +} + +.button { + display: flex; + flex-direction: column; + width: 110px; + padding: 5px 0 16px 0; + margin: auto; + justify-content: space-around; +} + +.link { + color: var(--purple-link); +} + +.error { + margin-top: 16px; + font-size: 14px; + color: var(--error); + font-style: italic; + text-align: center; +} diff --git a/web/modals/terms-and-privacy-modal.react.js b/web/modals/terms-and-privacy-modal.react.js new file mode 100644 --- /dev/null +++ b/web/modals/terms-and-privacy-modal.react.js @@ -0,0 +1,94 @@ +// @flow + +import * as React from 'react'; + +import { + policyAcknowledgment, + policyAcknowledgmentActionTypes, +} from 'lib/actions/user-actions.js'; +import { policyTypes } from 'lib/facts/policies.js'; +import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; +import { + useDispatchActionPromise, + useServerCall, +} from 'lib/utils/action-utils.js'; +import { acknowledgePolicy } from 'lib/utils/policy-acknowledge-utlis.js'; + +import Button, { buttonThemes } from '../components/button.react.js'; +import LoadingIndicator from '../loading-indicator.react.js'; +import { useSelector } from '../redux/redux-utils.js'; +import Modal from './modal.react'; +import css from './terms-and-privacy-modal.css'; + +const loadingStatusSelector = createLoadingStatusSelector( + policyAcknowledgmentActionTypes, +); + +function TermsAndPrivacyModal(): React.Node { + const loading = useSelector(loadingStatusSelector); + const [acknowledgmentError, setAcknowledgmentError] = React.useState(''); + const sendAcknowledgmentRequest = useServerCall(policyAcknowledgment); + const dispatchActionPromise = useDispatchActionPromise(); + + const onAccept = React.useCallback(() => { + acknowledgePolicy( + policyTypes.tosAndPrivacyPolicy, + dispatchActionPromise, + sendAcknowledgmentRequest, + e => setAcknowledgmentError(e), + ); + }, [dispatchActionPromise, sendAcknowledgmentRequest]); + + const buttonContent = React.useMemo(() => { + if (loading === 'loading') { + return ; + } + return 'I accept'; + }, [loading]); + + return ( + undefined} + name="Terms of Service and Privacy Policy" + size="large" + > +
+ We recently updated our{' '} + + Terms of Service + + {' & '} + + Privacy Policy + + . We're asking you accept those to make sure you have a chance to + acknowledge the updated policies. +
+ +
+ +
{acknowledgmentError}
+
+
+ ); +} + +export default TermsAndPrivacyModal; diff --git a/web/theme.css b/web/theme.css --- a/web/theme.css +++ b/web/theme.css @@ -190,4 +190,5 @@ --typeahead-overlay-shadow-secondary: rgba(0, 0, 0, 0.4); --spoiler-text-color: var(--shades-black-80); --spoiler-background-color: var(--shades-black-80); + --purple-link: var(--violet-light-100); }