From cb065e7f121eb4a2710e15477b7acd1e66acedff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Alby?= Date: Mon, 16 May 2022 10:03:42 +0200 Subject: [PATCH] Merge pull request #7820 from overleaf/msm-settings-leavers-survey [Settings] Institutional Leavers Survey GitOrigin-RevId: 1774af7fc4bb90cb39c9fc188323c44a1256bb3d --- .../web/frontend/extracted-translations.json | 3 ++ .../settings/components/emails-section.tsx | 2 + .../components/emails/actions/remove.tsx | 4 +- .../components/leavers-survey-alert.tsx | 35 +++++++++++++++ .../settings/context/user-email-context.tsx | 29 +++++++++++++ .../settings/leavers-survey-alert.stories.tsx | 15 +++++++ .../components/leavers-survey-alert.test.tsx | 43 +++++++++++++++++++ services/web/types/user-email.ts | 1 + 8 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 services/web/frontend/js/features/settings/components/leavers-survey-alert.tsx create mode 100644 services/web/frontend/stories/settings/leavers-survey-alert.stories.tsx create mode 100644 services/web/test/frontend/features/settings/components/leavers-survey-alert.test.tsx diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index c8cd9b40c4..eacb2875ec 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -201,6 +201,7 @@ "in_order_to_match_institutional_metadata_2": "", "institution_acct_successfully_linked_2": "", "institution_and_role": "", + "institutional_leavers_survey_notification": "", "integrations": "", "invalid_email": "", "invalid_file_name": "", @@ -216,6 +217,7 @@ "learn_more_about_link_sharing": "", "learn_more_about_the_symbol_palette": "", "let_us_know": "", + "limited_offer": "", "link": "", "link_accounts_and_add_email": "", "link_sharing_is_off": "", @@ -427,6 +429,7 @@ "tab_connecting": "", "tab_no_longer_connected": "", "tags": "", + "take_short_survey": "", "template_approved_by_publisher": "", "terminated": "", "thanks_settings_updated": "", diff --git a/services/web/frontend/js/features/settings/components/emails-section.tsx b/services/web/frontend/js/features/settings/components/emails-section.tsx index 9cfdc72d38..8439a2966c 100644 --- a/services/web/frontend/js/features/settings/components/emails-section.tsx +++ b/services/web/frontend/js/features/settings/components/emails-section.tsx @@ -11,6 +11,7 @@ import AddEmail from './emails/add-email' import Icon from '../../../shared/components/icon' import { Alert } from 'react-bootstrap' import { ExposedSettings } from '../../../../../types/exposed-settings' +import { LeaversSurveyAlert } from './leavers-survey-alert' function EmailsSectionContent() { const { t } = useTranslation() @@ -51,6 +52,7 @@ function EmailsSectionContent() { ))} )} + {isInitializingSuccess && } {isInitializingSuccess && } {isInitializingError && ( diff --git a/services/web/frontend/js/features/settings/components/emails/actions/remove.tsx b/services/web/frontend/js/features/settings/components/emails/actions/remove.tsx index b9782c8e9f..976e5b28db 100644 --- a/services/web/frontend/js/features/settings/components/emails/actions/remove.tsx +++ b/services/web/frontend/js/features/settings/components/emails/actions/remove.tsx @@ -27,7 +27,8 @@ type RemoveProps = { function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) { const { t } = useTranslation() - const { state, deleteEmail } = useUserEmailsContext() + const { state, deleteEmail, resetLeaversSurveyExpiration } = + useUserEmailsContext() const handleRemoveUserEmail = () => { deleteEmailAsync @@ -40,6 +41,7 @@ function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) { ) .then(() => { deleteEmail(userEmailData.email) + resetLeaversSurveyExpiration(userEmailData) }) .catch(() => {}) } diff --git a/services/web/frontend/js/features/settings/components/leavers-survey-alert.tsx b/services/web/frontend/js/features/settings/components/leavers-survey-alert.tsx new file mode 100644 index 0000000000..dfa2e407b7 --- /dev/null +++ b/services/web/frontend/js/features/settings/components/leavers-survey-alert.tsx @@ -0,0 +1,35 @@ +import { Alert } from 'react-bootstrap' +import { useTranslation } from 'react-i18next' +import usePersistedState from '../../../shared/hooks/use-persisted-state' + +export function LeaversSurveyAlert() { + const { t } = useTranslation() + + const [expirationDate, setExpirationDate] = usePersistedState( + 'showInstitutionalLeaversSurveyUntil', + 0 + ) + function handleDismiss() { + setExpirationDate(0) + } + + if (Date.now() > expirationDate) { + return null + } + + return ( + +

+ {t('limited_offer')} + {`: ${t('institutional_leavers_survey_notification')} `} + + {t('take_short_survey')} + +

+
+ ) +} diff --git a/services/web/frontend/js/features/settings/context/user-email-context.tsx b/services/web/frontend/js/features/settings/context/user-email-context.tsx index e42fe91f2a..c8d017a6b6 100644 --- a/services/web/frontend/js/features/settings/context/user-email-context.tsx +++ b/services/web/frontend/js/features/settings/context/user-email-context.tsx @@ -13,6 +13,9 @@ import { Affiliation } from '../../../../../types/affiliation' import { normalize, NormalizedObject } from '../../../utils/normalize' import { getJSON } from '../../../infrastructure/fetch-json' import useAsync from '../../../shared/hooks/use-async' +import usePersistedState from '../../../shared/hooks/use-persisted-state' + +const ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000 // eslint-disable-next-line no-unused-vars export enum Actions { @@ -194,6 +197,10 @@ const reducer = (state: State, action: Action) => { } function useUserEmails() { + const [, setExpirationDate] = usePersistedState( + 'showInstitutionalLeaversSurveyUntil', + 0 + ) const [state, unsafeDispatch] = useReducer(reducer, initialState) const dispatch = useSafeDispatch(unsafeDispatch) const { data, isLoading, isError, isSuccess, runAsync } = useAsync() @@ -211,12 +218,34 @@ function useUserEmails() { getEmails() }, [getEmails]) + const resetLeaversSurveyExpiration = useCallback( + (deletedEmail: UserEmailData) => { + if (!data) { + return + } + const emailData = data as UserEmailData[] + if ( + deletedEmail.emailHasInstitutionLicence || + deletedEmail.affiliation?.pastReconfirmDate + ) { + const stillHasLicenseAccess = emailData.some( + userEmail => userEmail.emailHasInstitutionLicence + ) + if (stillHasLicenseAccess) { + setExpirationDate(Date.now() + ONE_WEEK_IN_MS) + } + } + }, + [data, setExpirationDate] + ) + return { state, isInitializing: isLoading && !data, isInitializingSuccess: isSuccess, isInitializingError: isError, getEmails, + resetLeaversSurveyExpiration, setLoading: useCallback( (flag: boolean) => dispatch(ActionCreators.setLoading(flag)), [dispatch] diff --git a/services/web/frontend/stories/settings/leavers-survey-alert.stories.tsx b/services/web/frontend/stories/settings/leavers-survey-alert.stories.tsx new file mode 100644 index 0000000000..b793913f9d --- /dev/null +++ b/services/web/frontend/stories/settings/leavers-survey-alert.stories.tsx @@ -0,0 +1,15 @@ +import EmailsSection from '../../js/features/settings/components/emails-section' +import { LeaversSurveyAlert } from '../../js/features/settings/components/leavers-survey-alert' + +export const SurveyAlert = () => { + localStorage.setItem( + 'showInstitutionalLeaversSurveyUntil', + (Date.now() + 1000 * 60 * 60).toString() + ) + return +} + +export default { + title: 'Account Settings / Survey Alerts', + component: EmailsSection, +} diff --git a/services/web/test/frontend/features/settings/components/leavers-survey-alert.test.tsx b/services/web/test/frontend/features/settings/components/leavers-survey-alert.test.tsx new file mode 100644 index 0000000000..6af4bf7ba4 --- /dev/null +++ b/services/web/test/frontend/features/settings/components/leavers-survey-alert.test.tsx @@ -0,0 +1,43 @@ +import { expect } from 'chai' +import { fireEvent, screen, render } from '@testing-library/react' +import { LeaversSurveyAlert } from '../../../../../frontend/js/features/settings/components/leavers-survey-alert' + +describe('', function () { + it('should render before the expiration date', function () { + const tomorrow = Date.now() + 1000 * 60 * 60 * 24 + localStorage.setItem( + 'showInstitutionalLeaversSurveyUntil', + tomorrow.toString() + ) + render() + screen.getByRole('alert') + screen.getByText(/Provide some quick feedback/) + screen.getByRole('link', { name: 'Take a short survey' }) + }) + + it('should not render after the expiration date', function () { + const yesterday = Date.now() - 1000 * 60 * 60 * 24 + localStorage.setItem( + 'showInstitutionalLeaversSurveyUntil', + yesterday.toString() + ) + render() + expect(screen.queryByRole('alert')).to.be.null + }) + + it('should reset the expiration date when it is closed', function () { + const tomorrow = Date.now() + 1000 * 60 * 60 * 24 + localStorage.setItem( + 'showInstitutionalLeaversSurveyUntil', + tomorrow.toString() + ) + render() + screen.getByRole('alert') + + fireEvent.click(screen.getByRole('button')) + expect(screen.queryByRole('alert')).to.be.null + + expect(localStorage.getItem('showInstitutionalLeaversSurveyUntil')).to.be + .null + }) +}) diff --git a/services/web/types/user-email.ts b/services/web/types/user-email.ts index f1978fda17..fc5328e0e5 100644 --- a/services/web/types/user-email.ts +++ b/services/web/types/user-email.ts @@ -7,4 +7,5 @@ export type UserEmailData = { default: boolean samlProviderId?: string ssoAvailable?: boolean + emailHasInstitutionLicence?: boolean }