diff --git a/client/src/assets/docs/whats-new.md b/client/src/assets/docs/whats-new.md index 97412d2a..a05384f8 100644 --- a/client/src/assets/docs/whats-new.md +++ b/client/src/assets/docs/whats-new.md @@ -1,4 +1,4 @@ -## [2.0.0] - 2026-02-10 +## [2.0.0] - 2026-02-11 ### Added diff --git a/client/src/components/common/Login/TermsModal.jsx b/client/src/components/common/Login/TermsModal.jsx index 3fe09b36..fe2385d1 100644 --- a/client/src/components/common/Login/TermsModal.jsx +++ b/client/src/components/common/Login/TermsModal.jsx @@ -3,10 +3,10 @@ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md */ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; -import { Button, Checkbox, Dropdown, Modal } from 'semantic-ui-react'; +import { Button, Checkbox, Dropdown, Modal, Segment } from 'semantic-ui-react'; import selectors from '../../../selectors'; import entryActions from '../../../entry-actions'; @@ -18,6 +18,25 @@ import styles from './TermsModal.module.scss'; const LOCALES = TERMS_LANGUAGES.map((language) => localeByLanguage[language]); +const splitTermsAndConfirmations = (content) => { + const separator = '\n---\n'; + const index = content.lastIndexOf(separator); + + if (index === -1) { + return [content.trim(), []]; + } + + const terms = content.slice(0, index).trim(); + + const confirmations = content + .slice(index + separator.length) + .split('\n') + .map((confirmation) => confirmation.replace(/^✔️\s*/, '').replace(/\*\*(.*?)\*\*/, '$1')) + .filter(Boolean); + + return [terms, confirmations]; +}; + const TermsModal = React.memo(() => { const { termsForm: { payload: terms, isSubmitting, isCancelling, isLanguageUpdating }, @@ -25,7 +44,12 @@ const TermsModal = React.memo(() => { const dispatch = useDispatch(); const [t] = useTranslation(); - const [isTermsAccepted, setIsTermsAccepted] = useState(false); + const [acceptedConfirmationsSet, setAcceptedConfirmationsSet] = useState(new Set()); + + const [content, confirmations] = useMemo( + () => splitTermsAndConfirmations(terms.content), + [terms.content], + ); const handleContinueClick = useCallback(() => { dispatch(entryActions.acceptTerms(terms.signature)); @@ -42,10 +66,22 @@ const TermsModal = React.memo(() => { [dispatch], ); - const handleToggleAcceptClick = useCallback((_, { checked }) => { - setIsTermsAccepted(checked); + const handleToggleConfirmationAccept = useCallback((index) => { + setAcceptedConfirmationsSet((prevAcceptedConfirmationsSet) => { + const nextAcceptedConfirmationsSet = new Set(prevAcceptedConfirmationsSet); + + if (nextAcceptedConfirmationsSet.has(index)) { + nextAcceptedConfirmationsSet.delete(index); + } else { + nextAcceptedConfirmationsSet.add(index); + } + + return nextAcceptedConfirmationsSet; + }); }, []); + const isAllConfirmationsAccepted = acceptedConfirmationsSet.size === confirmations.length; + return ( @@ -63,7 +99,20 @@ const TermsModal = React.memo(() => { className={styles.language} onChange={handleLanguageChange} /> - {terms.content} + {content} + {confirmations.length > 0 && ( + + {confirmations.map((confirmation, index) => ( + handleToggleConfirmationAccept(index)} + /> + ))} + + )}