diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 6f3cf9c574..59c7ff4558 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -516,6 +516,7 @@ "institution_account": "", "institution_acct_successfully_linked_2": "", "institution_and_role": "", + "institution_has_overleaf_subscription": "", "institution_templates": "", "institutional_leavers_survey_notification": "", "integrations": "", @@ -704,6 +705,7 @@ "ok": "", "on": "", "on_free_plan_upgrade_to_access_features": "", + "one_step_away_from_professional_features": "", "only_group_admin_or_managers_can_delete_your_account": "", "open_file": "", "open_link": "", @@ -866,6 +868,7 @@ "republish": "", "resend": "", "resend_confirmation_email": "", + "resend_email": "", "resending_confirmation_email": "", "resolve": "", "resolved_comments": "", diff --git a/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirmation-info.tsx b/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirmation-info.tsx index c34434cca1..41f59693da 100644 --- a/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirmation-info.tsx +++ b/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirmation-info.tsx @@ -11,7 +11,6 @@ function ReconfirmationInfo() { ) as UserEmailData[] const userEmails = getMeta('ol-userEmails', []) as UserEmailData[] const reconfirmedViaSAML = getMeta('ol-reconfirmedViaSAML') as string - return ( <> {allInReconfirmNotificationPeriods.map(userEmail => diff --git a/services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx b/services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx index dd34048d8c..7e9708c453 100644 --- a/services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx +++ b/services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx @@ -1,4 +1,4 @@ -import { useTranslation } from 'react-i18next' +import { Trans, useTranslation } from 'react-i18next' import { Button } from 'react-bootstrap' import Notification from '../notification' import Icon from '../../../../../shared/components/icon' @@ -35,6 +35,28 @@ const ssoAvailable = ({ samlProviderId, affiliation }: UserEmailData) => { return false } +function emailHasLicenceAfterConfirming(emailData: UserEmailData) { + if (emailData.confirmedAt) { + return false + } + if (!emailData.affiliation) { + return false + } + const affiliation = emailData.affiliation + const institution = affiliation.institution + if (!institution) { + return false + } + if (!institution.confirmed) { + return false + } + if (affiliation.pastReconfirmDate) { + return false + } + + return affiliation.institution.commonsAccount +} + const showConfirmEmail = (userEmail: UserEmailData) => { const { emailConfirmationDisabled } = getMeta( 'ol-ExposedSettings' @@ -63,9 +85,48 @@ function ConfirmEmailNotification({ userEmail }: { userEmail: UserEmailData }) { return null } + if (emailHasLicenceAfterConfirming(userEmail)) { + return ( + + + {isLoading ? ( + <> + {t('resending_confirmation_email')} + … + + ) : isError ? ( +
{getUserFacingMessage(error)}
+ ) : ( + <> + ]} // eslint-disable-line react/jsx-key + /> + +
+ ]} // eslint-disable-line react/jsx-key + /> + + )} +
+
+ ) + } + return ( - + {isLoading ? ( <> {t('resending_confirmation_email')} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 518e009c15..b07b6dc97f 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -813,6 +813,7 @@ "institution_acct_successfully_linked_2": "Your <0>__appName__ account was successfully linked to your <0>__institutionName__ institutional account.", "institution_and_role": "Institution and role", "institution_email_new_to_app": "Your __institutionName__ email (__email__) is new to __appName__.", + "institution_has_overleaf_subscription": "<0>__institutionName__ has an Overleaf subscription. Click the confirmation link sent to __emailAddress__ to upgrade to <0>Overleaf Professional.", "institution_templates": "Institution Templates", "institutional": "Institutional", "institutional_leavers_survey_notification": "Provide some quick feedback to receive a 25% discount on an annual subscription!", @@ -1140,6 +1141,7 @@ "one_collaborator": "Only one collaborator", "one_free_collab": "One free collaborator", "one_minute": "1 minute", + "one_step_away_from_professional_features": "You are one step away from accessing <0>Overleaf Professional features!", "one_user": "1 user", "online_latex_editor": "Online LaTeX Editor", "only_group_admin_or_managers_can_delete_your_account": "Only your group admin or group managers will be able to delete your account.", @@ -1402,6 +1404,7 @@ "required": "Required", "resend": "Resend", "resend_confirmation_email": "Resend confirmation email", + "resend_email": "Resend email", "resending_confirmation_email": "Resending confirmation email", "reset_password": "Reset Password", "reset_your_password": "Reset your password", diff --git a/services/web/test/frontend/features/project-list/components/notifications.test.tsx b/services/web/test/frontend/features/project-list/components/notifications.test.tsx index a13843c0a7..e2f0d87170 100644 --- a/services/web/test/frontend/features/project-list/components/notifications.test.tsx +++ b/services/web/test/frontend/features/project-list/components/notifications.test.tsx @@ -5,12 +5,14 @@ import { render, screen, waitForElementToBeRemoved, + within, } from '@testing-library/react' import fetchMock from 'fetch-mock' import { merge, cloneDeep } from 'lodash' import { professionalUserData, unconfirmedUserData, + unconfirmedCommonsUserData, } from '../../settings/fixtures/test-user-email-data' import { notification, @@ -508,9 +510,6 @@ describe('', function () { emailConfirmationDisabled: false, }) window.metaAttributesCache.set('ol-userEmails', [unconfirmedUserData]) - - renderWithinProjectListProvider(ConfirmEmail) - await fetchMock.flush(true) }) afterEach(function () { @@ -518,8 +517,16 @@ describe('', function () { }) it('sends successfully', async function () { + renderWithinProjectListProvider(ConfirmEmail) + await fetchMock.flush(true) fetchMock.post('/user/emails/resend_confirmation', 200) + const email = unconfirmedUserData.email + const notificationBody = screen.getByTestId('pro-notification-body') + expect(notificationBody.textContent).to.contain( + `Please confirm your email ${email} by clicking on the link in the confirmation email` + ) + const resendButton = screen.getByRole('button', { name: /resend/i }) fireEvent.click(resendButton) @@ -534,6 +541,8 @@ describe('', function () { }) it('fails to send', async function () { + renderWithinProjectListProvider(ConfirmEmail) + await fetchMock.flush(true) fetchMock.post('/user/emails/resend_confirmation', 500) const resendButton = screen.getByRole('button', { name: /resend/i }) @@ -546,6 +555,25 @@ describe('', function () { expect(fetchMock.called()).to.be.true screen.getByText(/something went wrong/i) }) + + it('shows notification for commons account', async function () { + window.metaAttributesCache.set('ol-userEmails', [ + unconfirmedCommonsUserData, + ]) + + renderWithinProjectListProvider(ConfirmEmail) + await fetchMock.flush(true) + + const alert = screen.getByRole('alert') + const email = unconfirmedCommonsUserData.email + const notificationBody = within(alert).getByTestId('notification-body') + expect(notificationBody.textContent).to.contain( + 'You are one step away from accessing Overleaf Professional features' + ) + expect(notificationBody.textContent).to.contain( + `Overleaf has an Overleaf subscription. Click the confirmation link sent to ${email} to upgrade to Overleaf Professional` + ) + }) }) describe('', function () { diff --git a/services/web/test/frontend/features/settings/context/user-email-context.test.tsx b/services/web/test/frontend/features/settings/context/user-email-context.test.tsx index b01663c19f..bf0b69c208 100644 --- a/services/web/test/frontend/features/settings/context/user-email-context.test.tsx +++ b/services/web/test/frontend/features/settings/context/user-email-context.test.tsx @@ -12,6 +12,7 @@ import { professionalUserData, unconfirmedUserData, fakeUsersData, + unconfirmedCommonsUserData, } from '../fixtures/test-user-email-data' import localStorage from '../../../../../frontend/js/infrastructure/local-storage' @@ -53,6 +54,7 @@ describe('UserEmailContext', function () { 'bar@overleaf.com': confirmedUserData, 'baz@overleaf.com': unconfirmedUserData, 'foo@overleaf.com': professionalUserData, + 'qux@overleaf.com': unconfirmedCommonsUserData, }) expect(result.current.state.data.linkedInstitutionIds).to.have.lengthOf(0) diff --git a/services/web/test/frontend/features/settings/fixtures/test-user-email-data.ts b/services/web/test/frontend/features/settings/fixtures/test-user-email-data.ts index c7a806163a..66786ec1e0 100644 --- a/services/web/test/frontend/features/settings/fixtures/test-user-email-data.ts +++ b/services/web/test/frontend/features/settings/fixtures/test-user-email-data.ts @@ -44,6 +44,37 @@ export const professionalUserData: UserEmailData & { default: true, } +export const unconfirmedCommonsUserData: UserEmailData & { + affiliation: Affiliation +} = { + affiliation: { + cachedConfirmedAt: null, + cachedEntitlement: null, + cachedLastDayToReconfirm: null, + cachedPastReconfirmDate: false, + cachedReconfirmedAt: null, + department: 'Art History', + institution: { + commonsAccount: true, + confirmed: true, + id: 1, + isUniversity: false, + maxConfirmationMonths: null, + name: 'Overleaf', + ssoEnabled: false, + ssoBeta: false, + }, + inReconfirmNotificationPeriod: false, + inferred: false, + licence: 'free', + pastReconfirmDate: false, + portal: { slug: '', templates_count: 1 }, + role: 'Reader', + }, + email: 'qux@overleaf.com', + default: true, +} + export const ssoUserData: UserEmailData = { affiliation: { cachedConfirmedAt: '2022-02-03T11:46:28.249Z', @@ -79,4 +110,5 @@ export const fakeUsersData = [ { ...confirmedUserData }, { ...unconfirmedUserData }, { ...professionalUserData }, + { ...unconfirmedCommonsUserData }, ]