diff --git a/services/web/frontend/js/features/settings/components/emails/email.tsx b/services/web/frontend/js/features/settings/components/emails/email.tsx index e895e484f7..63e2d69cea 100644 --- a/services/web/frontend/js/features/settings/components/emails/email.tsx +++ b/services/web/frontend/js/features/settings/components/emails/email.tsx @@ -29,7 +29,7 @@ function Email({ userEmailData }: EmailProps) { )} {userEmailData.confirmedAt && userEmailData.affiliation?.institution.confirmed && - userEmailData.affiliation?.licence !== 'free' && ( + userEmailData.affiliation.licence !== 'free' && (
{t('professional')}
diff --git a/services/web/frontend/js/features/settings/components/emails/institution-and-role.tsx b/services/web/frontend/js/features/settings/components/emails/institution-and-role.tsx new file mode 100644 index 0000000000..4ff2672a6d --- /dev/null +++ b/services/web/frontend/js/features/settings/components/emails/institution-and-role.tsx @@ -0,0 +1,50 @@ +import { useTranslation } from 'react-i18next' +import { UserEmailData } from '../../../../../../types/user-email' +import { Button } from 'react-bootstrap' + +type InstitutionAndRoleProps = { + userEmailData: UserEmailData +} + +function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) { + const { t } = useTranslation() + const { affiliation } = userEmailData + + const handleAddRoleDepartment = () => { + console.log('TODO: add role department') + } + + const handleChangeAffiliation = () => { + console.log('TODO: change affiliation') + } + + if (!affiliation?.institution) { + return null + } + + return ( + <> +
{affiliation.institution.name}
+ {!affiliation.department && !affiliation.role && ( +
+ +
+ )} + {(affiliation.role || affiliation.department) && ( +
+ {[affiliation.role, affiliation.department] + .filter(Boolean) + .join(', ')} +
+ +
+ )} + + ) +} + +export default InstitutionAndRole diff --git a/services/web/frontend/js/features/settings/components/emails/resend-confirmation-email-button.tsx b/services/web/frontend/js/features/settings/components/emails/resend-confirmation-email-button.tsx index 9cde7bb097..b5a1808f5a 100644 --- a/services/web/frontend/js/features/settings/components/emails/resend-confirmation-email-button.tsx +++ b/services/web/frontend/js/features/settings/components/emails/resend-confirmation-email-button.tsx @@ -1,6 +1,7 @@ import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import Icon from '../../../../shared/components/icon' +import { Button } from 'react-bootstrap' import useAsync from '../../../../shared/hooks/use-async' import { postJSON } from '../../../../infrastructure/fetch-json' import { UserEmailData } from '../../../../../../types/user-email' @@ -42,12 +43,12 @@ function ResendConfirmationEmailButton({ return ( <> - +
{isError && ( diff --git a/services/web/frontend/js/features/settings/components/emails/row.tsx b/services/web/frontend/js/features/settings/components/emails/row.tsx index 791c9bc8f0..e092dbd100 100644 --- a/services/web/frontend/js/features/settings/components/emails/row.tsx +++ b/services/web/frontend/js/features/settings/components/emails/row.tsx @@ -1,6 +1,7 @@ import { UserEmailData } from '../../../../../../types/user-email' import { Row, Col } from 'react-bootstrap' import Email from './email' +import InstitutionAndRole from './institution-and-role' import EmailCell from './cell' type EmailsRowProps = { @@ -16,7 +17,11 @@ function EmailsRow({ userEmailData }: EmailsRowProps) { - todo + {userEmailData.affiliation?.institution && ( + + + + )} todo diff --git a/services/web/frontend/stories/settings.stories.js b/services/web/frontend/stories/settings.stories.js index 23e6117a34..ede3090288 100644 --- a/services/web/frontend/stories/settings.stories.js +++ b/services/web/frontend/stories/settings.stories.js @@ -21,6 +21,7 @@ const fakeUsersData = [ affiliation: { institution: { confirmed: true, + name: 'Overleaf', }, licence: 'pro_plus', }, @@ -34,6 +35,15 @@ const fakeUsersData = [ default: false, }, { + affiliation: { + institution: { + confirmed: true, + name: 'Overleaf', + }, + licence: 'pro_plus', + department: 'Art & Art History', + role: 'Reader', + }, email: 'baz@overleaf.com', default: false, }, diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section-institution-and-role.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section-institution-and-role.test.tsx new file mode 100644 index 0000000000..ecfbe25652 --- /dev/null +++ b/services/web/test/frontend/features/settings/components/emails/emails-section-institution-and-role.test.tsx @@ -0,0 +1,84 @@ +import { render, screen } from '@testing-library/react' +import { expect } from 'chai' +import { UserEmailData } from '../../../../../../types/user-email' +import InstitutionAndRole from '../../../../../../frontend/js/features/settings/components/emails/institution-and-role' + +const userData1: UserEmailData = { + affiliation: { + cachedConfirmedAt: null, + cachedPastReconfirmDate: false, + cachedReconfirmedAt: null, + department: null, + institution: { + commonsAccount: false, + confirmed: true, + id: 1, + isUniversity: false, + name: 'Overleaf', + ssoEnabled: false, + ssoBeta: false, + }, + inReconfirmNotificationPeriod: false, + inferred: false, + licence: 'pro_plus', + pastReconfirmDate: false, + portal: { slug: '', templates_count: 1 }, + role: null, + }, + confirmedAt: '2022-03-09T10:59:44.139Z', + email: 'foo@overleaf.com', + default: true, +} + +const userData2: UserEmailData = { + affiliation: { + cachedConfirmedAt: null, + cachedPastReconfirmDate: false, + cachedReconfirmedAt: null, + department: 'Art History', + institution: { + commonsAccount: false, + confirmed: true, + id: 1, + isUniversity: false, + name: 'Overleaf', + ssoEnabled: false, + ssoBeta: false, + }, + inReconfirmNotificationPeriod: false, + inferred: false, + licence: 'pro_plus', + pastReconfirmDate: false, + portal: { slug: '', templates_count: 1 }, + role: 'Reader', + }, + email: 'baz@overleaf.com', + default: false, +} + +describe('user role and institution', function () { + it('renders affiliation name with add role/department button', function () { + const userEmailData = userData1 + render() + + screen.getByText(userEmailData.affiliation.institution.name, { + exact: false, + }) + screen.getByRole('button', { name: /add role and department/i }) + expect(screen.queryByRole('button', { name: /change/i })).to.not.exist + }) + + it('renders affiliation name, role and department with change button', function () { + const userEmailData = userData2 + render() + + screen.getByText(userEmailData.affiliation.institution.name, { + exact: false, + }) + screen.getByText(userEmailData.affiliation.department, { exact: false }) + screen.getByText(userEmailData.affiliation.role, { exact: false }) + screen.getByRole('button', { name: /change/i }) + expect(screen.queryByRole('button', { name: /add role and department/i })) + .to.not.exist + }) +}) diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx index c62455ddb5..c96828797e 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx @@ -8,24 +8,40 @@ import { import EmailsSection from '../../../../../../frontend/js/features/settings/components/emails-section' import { expect } from 'chai' import fetchMock from 'fetch-mock' +import { UserEmailData } from '../../../../../../types/user-email' -const confirmedUserData = { +const confirmedUserData: UserEmailData = { confirmedAt: '2022-03-10T10:59:44.139Z', email: 'bar@overleaf.com', default: false, } -const unconfirmedUserData = { +const unconfirmedUserData: UserEmailData = { email: 'baz@overleaf.com', default: false, } -const professionalUserData = { +const professionalUserData: UserEmailData = { affiliation: { + cachedConfirmedAt: null, + cachedPastReconfirmDate: false, + cachedReconfirmedAt: null, + department: 'Art History', institution: { + commonsAccount: false, confirmed: true, + id: 1, + isUniversity: false, + name: 'Overleaf', + ssoEnabled: false, + ssoBeta: false, }, + inReconfirmNotificationPeriod: false, + inferred: false, licence: 'pro_plus', + pastReconfirmDate: false, + portal: { slug: '', templates_count: 1 }, + role: 'Reader', }, confirmedAt: '2022-03-09T10:59:44.139Z', email: 'foo@overleaf.com', @@ -69,12 +85,10 @@ describe('', function () { }) it('renders primary status', function () { - window.metaAttributesCache.set('ol-userEmails', fakeUsersData) + window.metaAttributesCache.set('ol-userEmails', [professionalUserData]) render() - const primary = fakeUsersData.find(userData => userData.default) - - screen.getByText(`${primary.email} (primary)`) + screen.getByText(`${professionalUserData.email} (primary)`) }) it('shows confirmation status for unconfirmed users', function () { diff --git a/services/web/types/affiliation.ts b/services/web/types/affiliation.ts index 5b76d77494..d60aa25ba1 100644 --- a/services/web/types/affiliation.ts +++ b/services/web/types/affiliation.ts @@ -1,6 +1,19 @@ import { Institution } from './institution' +import { Portal } from './portal' +import { Nullable } from './utils' export type Affiliation = { + cachedConfirmedAt: Nullable + cachedEntitlement: Nullable + cachedLastDayToReconfirm: Nullable + cachedPastReconfirmDate: boolean + cachedReconfirmedAt: Nullable + department: Nullable + inReconfirmNotificationPeriod: boolean + inferred: boolean institution: Institution - licence?: 'free' | 'pro_plus' + licence: 'free' | 'pro_plus' + pastReconfirmDate: boolean + portal: Portal + role: Nullable } diff --git a/services/web/types/institution.ts b/services/web/types/institution.ts index f857032b17..4edba50eb9 100644 --- a/services/web/types/institution.ts +++ b/services/web/types/institution.ts @@ -1 +1,12 @@ -export type Institution = Record +import { Nullable } from './utils' + +export type Institution = { + commonsAccount: boolean + confirmed: boolean + id: number + isUniversity: boolean + maxConfirmationMonths: Nullable + name: string + ssoBeta: boolean + ssoEnabled: boolean +} diff --git a/services/web/types/portal.ts b/services/web/types/portal.ts new file mode 100644 index 0000000000..ef8d2eca4d --- /dev/null +++ b/services/web/types/portal.ts @@ -0,0 +1,4 @@ +export type Portal = { + slug: string + templates_count: number +} diff --git a/services/web/types/user-email.ts b/services/web/types/user-email.ts index b6ae8b8d42..2e95af337e 100644 --- a/services/web/types/user-email.ts +++ b/services/web/types/user-email.ts @@ -2,7 +2,7 @@ import { Affiliation } from './affiliation' export type UserEmailData = { affiliation?: Affiliation - confirmedAt: string + confirmedAt?: string email: string default: boolean ssoAvailable?: boolean