Explorar o código

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

[Settings] Institutional Leavers Survey

GitOrigin-RevId: 1774af7fc4bb90cb39c9fc188323c44a1256bb3d
Timothée Alby %!s(int64=4) %!d(string=hai) anos
pai
achega
cb065e7f12

+ 3 - 0
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": "",

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

+ 3 - 1
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(() => {})
   }

+ 35 - 0
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 (
+    <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>
+  )
+}

+ 29 - 0
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]

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

+ 43 - 0
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('<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
+  })
+})

+ 1 - 0
services/web/types/user-email.ts

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