Merge pull request #7820 from overleaf/msm-settings-leavers-survey

[Settings] Institutional Leavers Survey

GitOrigin-RevId: 1774af7fc4bb90cb39c9fc188323c44a1256bb3d
This commit is contained in:
Timothée Alby 2022-05-16 10:03:42 +02:00 committed by Copybot
parent afe9e486ae
commit cb065e7f12
8 changed files with 131 additions and 1 deletions

View file

@ -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": "",

View file

@ -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 && <LeaversSurveyAlert />}
{isInitializingSuccess && <AddEmail />}
{isInitializingError && (
<Alert bsStyle="danger" className="text-center">

View file

@ -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(() => {})
}

View file

@ -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 (
<Alert className="mb-0" bsStyle="info" onDismiss={handleDismiss}>
<p>
<strong>{t('limited_offer')}</strong>
{`: ${t('institutional_leavers_survey_notification')} `}
<a
href="https://docs.google.com/forms/d/e/1FAIpQLSfYdeeoY5p1d31r5iUx1jw0O-Gd66vcsBi_Ntu3lJRMjV2EJA/viewform"
target="_blank"
rel="noopener noreferrer"
>
{t('take_short_survey')}
</a>
</p>
</Alert>
)
}

View file

@ -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]

View file

@ -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 <LeaversSurveyAlert />
}
export default {
title: 'Account Settings / Survey Alerts',
component: EmailsSection,
}

View file

@ -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('<LeaversSurveyAlert/>', function () {
it('should render before the expiration date', function () {
const tomorrow = Date.now() + 1000 * 60 * 60 * 24
localStorage.setItem(
'showInstitutionalLeaversSurveyUntil',
tomorrow.toString()
)
render(<LeaversSurveyAlert />)
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(<LeaversSurveyAlert />)
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(<LeaversSurveyAlert />)
screen.getByRole('alert')
fireEvent.click(screen.getByRole('button'))
expect(screen.queryByRole('alert')).to.be.null
expect(localStorage.getItem('showInstitutionalLeaversSurveyUntil')).to.be
.null
})
})

View file

@ -7,4 +7,5 @@ export type UserEmailData = {
default: boolean
samlProviderId?: string
ssoAvailable?: boolean
emailHasInstitutionLicence?: boolean
}