From c807bedb659c9c7ba52aed2d1d691f08ecaa9dff Mon Sep 17 00:00:00 2001 From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Date: Wed, 11 May 2022 12:19:10 +0300 Subject: [PATCH] Merge pull request #7841 from overleaf/ii-refactor-add-email Refactor add email section GitOrigin-RevId: 41de440caaf3baf43673c4a5f07a18b990f28c7b --- .../settings/components/emails/add-email.tsx | 293 ++++-------------- .../add-email/add-another-email-btn.tsx | 14 + .../emails/add-email/add-new-email-btn.tsx | 27 ++ .../emails/{ => add-email}/country-input.tsx | 5 +- .../email-affiliated-with-institution.tsx | 18 ++ .../input.tsx} | 27 +- .../emails/add-email/institution-fields.tsx | 154 +++++++++ .../components/emails/add-email/layout.tsx | 24 ++ .../sso-linking-info.tsx} | 15 +- .../emails/institution-and-role.tsx | 12 +- .../settings/{ => data}/countries-list.ts | 7 +- .../settings/{ => data}/departments.ts | 4 +- .../js/features/settings/{ => data}/roles.ts | 4 +- .../js/features/settings/utils/sso.ts | 23 ++ .../settings/add-email-input.stories.tsx | 6 +- .../emails/add-email-input.test.tsx | 15 +- services/web/types/country.ts | 252 --------------- services/web/types/university.ts | 2 +- 18 files changed, 377 insertions(+), 525 deletions(-) create mode 100644 services/web/frontend/js/features/settings/components/emails/add-email/add-another-email-btn.tsx create mode 100644 services/web/frontend/js/features/settings/components/emails/add-email/add-new-email-btn.tsx rename services/web/frontend/js/features/settings/components/emails/{ => add-email}/country-input.tsx (94%) create mode 100644 services/web/frontend/js/features/settings/components/emails/add-email/email-affiliated-with-institution.tsx rename services/web/frontend/js/features/settings/components/emails/{add-email-input.tsx => add-email/input.tsx} (86%) create mode 100644 services/web/frontend/js/features/settings/components/emails/add-email/institution-fields.tsx create mode 100644 services/web/frontend/js/features/settings/components/emails/add-email/layout.tsx rename services/web/frontend/js/features/settings/components/emails/{add-email-sso-linking-info.tsx => add-email/sso-linking-info.tsx} (80%) rename services/web/frontend/js/features/settings/{ => data}/countries-list.ts (98%) rename services/web/frontend/js/features/settings/{ => data}/departments.ts (96%) rename services/web/frontend/js/features/settings/{ => data}/roles.ts (85%) create mode 100644 services/web/frontend/js/features/settings/utils/sso.ts delete mode 100644 services/web/types/country.ts diff --git a/services/web/frontend/js/features/settings/components/emails/add-email.tsx b/services/web/frontend/js/features/settings/components/emails/add-email.tsx index 41d138e7cd..694571d8df 100644 --- a/services/web/frontend/js/features/settings/components/emails/add-email.tsx +++ b/services/web/frontend/js/features/settings/components/emails/add-email.tsx @@ -1,46 +1,25 @@ -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Button, Row, Col, Alert } from 'react-bootstrap' +import { Col } from 'react-bootstrap' import Cell from './cell' -import Icon from '../../../../shared/components/icon' -import DownshiftInput from './downshift-input' -import CountryInput from './country-input' -import { AddEmailInput, InstitutionInfo } from './add-email-input' +import Layout from './add-email/layout' +import Input, { InstitutionInfo } from './add-email/input' +import AddAnotherEmailBtn from './add-email/add-another-email-btn' +import InstitutionFields from './add-email/institution-fields' +import SsoLinkingInfo from './add-email/sso-linking-info' +import AddNewEmailBtn from './add-email/add-new-email-btn' import useAsync from '../../../../shared/hooks/use-async' import { useUserEmailsContext } from '../../context/user-email-context' -import { getJSON, postJSON } from '../../../../infrastructure/fetch-json' -import { defaults as defaultRoles } from '../../roles' -import { defaults as defaultDepartments } from '../../departments' +import { isSsoAvailable } from '../../utils/sso' +import { postJSON } from '../../../../infrastructure/fetch-json' import { University } from '../../../../../../types/university' -import { CountryCode } from '../../../../../../types/country' -import { ExposedSettings } from '../../../../../../types/exposed-settings' -import getMeta from '../../../../utils/meta' -import { AddEmailSSOLinkingInfo } from './add-email-sso-linking-info' - -const isValidEmail = (email: string) => { - return Boolean(email) -} - -const ssoAvailableForDomain = (domain: InstitutionInfo | null) => { - const { hasSamlBeta, hasSamlFeature } = getMeta( - 'ol-ExposedSettings' - ) as ExposedSettings - if (!hasSamlFeature || !domain || !domain.confirmed || !domain.university) { - return false - } - if (domain.university.ssoEnabled) { - return true - } - return hasSamlBeta && domain.university.ssoBeta -} +import { CountryCode } from '../../data/countries-list' function AddEmail() { const { t } = useTranslation() const [isFormVisible, setIsFormVisible] = useState( () => window.location.hash === '#add-email' ) - const emailRef = useRef(null) - const countryRef = useRef(null) const [newEmail, setNewEmail] = useState('') const [newEmailMatchedInstitution, setNewEmailMatchedInstitution] = useState(null) @@ -48,15 +27,10 @@ function AddEmail() { const [universities, setUniversities] = useState< Partial> >({}) - const [university, setUniversity] = useState('') + const [universityName, setUniversityName] = useState('') const [role, setRole] = useState('') const [department, setDepartment] = useState('') - const [departments, setDepartments] = useState(defaultDepartments) - const [isInstitutionFieldsVisible, setIsInstitutionFieldsVisible] = - useState(false) - const [isUniversityDirty, setIsUniversityDirty] = useState(false) const { isLoading, isError, error, runAsync } = useAsync() - const { runAsync: institutionRunAsync } = useAsync() const { state, setLoading: setUserEmailsContextLoading, @@ -67,61 +41,10 @@ function AddEmail() { setUserEmailsContextLoading(isLoading) }, [setUserEmailsContextLoading, isLoading]) - useEffect(() => { - if (isFormVisible && emailRef.current) { - emailRef.current?.focus() - } - }, [emailRef, isFormVisible]) - - useEffect(() => { - if (isInstitutionFieldsVisible && countryRef.current) { - countryRef.current?.focus() - } - }, [countryRef, isInstitutionFieldsVisible]) - - useEffect(() => { - if (university) { - setIsUniversityDirty(true) - } - }, [setIsUniversityDirty, university]) - - useEffect(() => { - const selectedKnownUniversity = countryCode - ? universities[countryCode]?.find(({ name }) => name === university) - : undefined - - if (selectedKnownUniversity && selectedKnownUniversity.departments.length) { - setDepartments(selectedKnownUniversity.departments) - } else { - setDepartments(defaultDepartments) - } - }, [countryCode, universities, university]) - - // Fetch country institution - useEffect(() => { - // Skip if country not selected or universities for - // that country are already fetched - if (!countryCode || universities[countryCode]) { - return - } - - institutionRunAsync( - getJSON(`/institutions/list?country_code=${countryCode}`) - ) - .then(data => { - setUniversities(state => ({ ...state, [countryCode]: data })) - }) - .catch(() => {}) - }, [countryCode, universities, setUniversities, institutionRunAsync]) - const handleShowAddEmailForm = () => { setIsFormVisible(true) } - const handleShowInstitutionFields = () => { - setIsInstitutionFieldsVisible(true) - } - const handleEmailChange = (value: string, institution?: InstitutionInfo) => { setNewEmail(value) setNewEmailMatchedInstitution(institution || null) @@ -129,10 +52,10 @@ function AddEmail() { const handleAddNewEmail = () => { const selectedKnownUniversity = countryCode - ? universities[countryCode]?.find(({ name }) => name === university) + ? universities[countryCode]?.find(({ name }) => name === universityName) : undefined - const knownUniversityData = university && + const knownUniversityData = universityName && selectedKnownUniversity && { university: { id: selectedKnownUniversity.id, @@ -141,10 +64,10 @@ function AddEmail() { department, } - const unknownUniversityData = university && + const unknownUniversityData = universityName && !selectedKnownUniversity && { university: { - name: university, + name: universityName, country_code: countryCode, }, role, @@ -162,153 +85,73 @@ function AddEmail() { ) .then(() => { getEmails() - setIsFormVisible(false) - setNewEmail('') - setNewEmailMatchedInstitution(null) - setCountryCode(null) - setIsUniversityDirty(false) - setUniversity('') - setRole('') - setDepartment('') - setIsInstitutionFieldsVisible(false) }) .catch(() => {}) } - const getUniversityItems = () => { - if (!countryCode) { - return [] - } - - return universities[countryCode]?.map(({ name }) => name) ?? [] + if (!isFormVisible) { + return ( + + + + + + + + ) } - const ssoAvailable = - newEmailMatchedInstitution && - ssoAvailableForDomain(newEmailMatchedInstitution) - return ( -
- - {!isFormVisible ? ( - + +
+ + + + + + + {isSsoAvailable(newEmailMatchedInstitution) ? ( + - + ) : ( - - + <> + - - + - - {ssoAvailable && ( - - - - - - )} - - {!ssoAvailable && ( - <> - - - {isInstitutionFieldsVisible ? ( - <> -
- -
-
- -
- {isUniversityDirty && ( - <> -
- -
-
- -
- - )} - - ) : ( -
- {t('is_email_affiliated')} -
- -
- )} -
- - - - - - - - - )} -
+ + + + + + )} -
- {isError && ( - - {error.getUserFacingMessage()} - - )} -
+ + ) } diff --git a/services/web/frontend/js/features/settings/components/emails/add-email/add-another-email-btn.tsx b/services/web/frontend/js/features/settings/components/emails/add-email/add-another-email-btn.tsx new file mode 100644 index 0000000000..502bb34b14 --- /dev/null +++ b/services/web/frontend/js/features/settings/components/emails/add-email/add-another-email-btn.tsx @@ -0,0 +1,14 @@ +import { useTranslation } from 'react-i18next' +import { Button, ButtonProps } from 'react-bootstrap' + +function AddAnotherEmailBtn({ onClick, ...props }: ButtonProps) { + const { t } = useTranslation() + + return ( + + ) +} + +export default AddAnotherEmailBtn diff --git a/services/web/frontend/js/features/settings/components/emails/add-email/add-new-email-btn.tsx b/services/web/frontend/js/features/settings/components/emails/add-email/add-new-email-btn.tsx new file mode 100644 index 0000000000..92f09fa438 --- /dev/null +++ b/services/web/frontend/js/features/settings/components/emails/add-email/add-new-email-btn.tsx @@ -0,0 +1,27 @@ +import { useTranslation } from 'react-i18next' +import { Button, ButtonProps } from 'react-bootstrap' + +const isValidEmail = (email: string) => { + return Boolean(email) +} + +type AddNewEmailColProps = { + email: string +} & ButtonProps + +function AddNewEmailBtn({ email, disabled, ...props }: AddNewEmailColProps) { + const { t } = useTranslation() + + return ( + + ) +} + +export default AddNewEmailBtn diff --git a/services/web/frontend/js/features/settings/components/emails/country-input.tsx b/services/web/frontend/js/features/settings/components/emails/add-email/country-input.tsx similarity index 94% rename from services/web/frontend/js/features/settings/components/emails/country-input.tsx rename to services/web/frontend/js/features/settings/components/emails/add-email/country-input.tsx index 1c1dd05a3e..6cb9f165d9 100644 --- a/services/web/frontend/js/features/settings/components/emails/country-input.tsx +++ b/services/web/frontend/js/features/settings/components/emails/add-email/country-input.tsx @@ -2,8 +2,7 @@ import { useState, forwardRef } from 'react' import { useTranslation } from 'react-i18next' import { useCombobox } from 'downshift' import classnames from 'classnames' -import { defaults as countries } from '../../countries-list' -import { CountryCode } from '../../../../../../types/country' +import countries, { CountryCode } from '../../../data/countries-list' type CountryInputProps = { setValue: React.Dispatch> @@ -14,7 +13,7 @@ const itemToString = (item: typeof countries[number] | null) => item?.name ?? '' function Downshift({ setValue, inputRef }: CountryInputProps) { const { t } = useTranslation() - const [inputItems, setInputItems] = useState(() => countries) + const [inputItems, setInputItems] = useState(() => [...countries]) const [inputValue, setInputValue] = useState('') const { diff --git a/services/web/frontend/js/features/settings/components/emails/add-email/email-affiliated-with-institution.tsx b/services/web/frontend/js/features/settings/components/emails/add-email/email-affiliated-with-institution.tsx new file mode 100644 index 0000000000..37ab6f1ae2 --- /dev/null +++ b/services/web/frontend/js/features/settings/components/emails/add-email/email-affiliated-with-institution.tsx @@ -0,0 +1,18 @@ +import { useTranslation } from 'react-i18next' +import { Button, ButtonProps } from 'react-bootstrap' + +function EmailAffiliatedWithInstitution({ onClick, ...props }: ButtonProps) { + const { t } = useTranslation() + + return ( +
+ {t('is_email_affiliated')} +
+ +
+ ) +} + +export default EmailAffiliatedWithInstitution diff --git a/services/web/frontend/js/features/settings/components/emails/add-email-input.tsx b/services/web/frontend/js/features/settings/components/emails/add-email/input.tsx similarity index 86% rename from services/web/frontend/js/features/settings/components/emails/add-email-input.tsx rename to services/web/frontend/js/features/settings/components/emails/add-email/input.tsx index ddb5f0a32b..07a6ced61e 100644 --- a/services/web/frontend/js/features/settings/components/emails/add-email-input.tsx +++ b/services/web/frontend/js/features/settings/components/emails/add-email/input.tsx @@ -4,10 +4,10 @@ import { useCallback, useEffect, useState, - forwardRef, + useRef, } from 'react' -import { getJSON } from '../../../../infrastructure/fetch-json' -import useAbortController from '../../../../shared/hooks/use-abort-controller' +import { getJSON } from '../../../../../infrastructure/fetch-json' +import useAbortController from '../../../../../shared/hooks/use-abort-controller' const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/ @@ -37,18 +37,22 @@ export function clearDomainCache() { domainCache = new Map() } -type AddEmailInputProps = { +type InputProps = { onChange: (value: string, institution?: InstitutionInfo) => void - inputRef?: React.ForwardedRef } -function AddEmailInputBase({ onChange, inputRef }: AddEmailInputProps) { +function Input({ onChange }: InputProps) { const { signal } = useAbortController() + const inputRef = useRef(null) const [suggestion, setSuggestion] = useState(null) const [inputValue, setInputValue] = useState(null) const [matchedInstitution, setMatchedInstitution] = - useState(null) + useState(null) + + useEffect(() => { + inputRef.current?.focus() + }, [inputRef]) useEffect(() => { if (inputValue == null) { @@ -151,11 +155,4 @@ function AddEmailInputBase({ onChange, inputRef }: AddEmailInputProps) { ) } -const AddEmailInput = forwardRef< - HTMLInputElement, - Omit ->((props, ref) => ) - -AddEmailInput.displayName = 'AddEmailInput' - -export { AddEmailInput } +export default Input diff --git a/services/web/frontend/js/features/settings/components/emails/add-email/institution-fields.tsx b/services/web/frontend/js/features/settings/components/emails/add-email/institution-fields.tsx new file mode 100644 index 0000000000..cfa1432ef8 --- /dev/null +++ b/services/web/frontend/js/features/settings/components/emails/add-email/institution-fields.tsx @@ -0,0 +1,154 @@ +import { useEffect, useState, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import CountryInput from './country-input' +import DownshiftInput from '../downshift-input' +import EmailAffiliatedWithInstitution from './email-affiliated-with-institution' +import defaultRoles from '../../../data/roles' +import defaultDepartments from '../../../data/departments' +import { CountryCode } from '../../../data/countries-list' +import { University } from '../../../../../../../types/university' +import { getJSON } from '../../../../../infrastructure/fetch-json' +import useAsync from '../../../../../shared/hooks/use-async' + +type InstitutionFieldsProps = { + countryCode: CountryCode | null + setCountryCode: React.Dispatch> + universities: Partial> + setUniversities: React.Dispatch< + React.SetStateAction>> + > + universityName: string + setUniversityName: React.Dispatch> + role: string + setRole: React.Dispatch> + department: string + setDepartment: React.Dispatch> +} + +function InstitutionFields({ + countryCode, + setCountryCode, + universities, + setUniversities, + universityName, + setUniversityName, + role, + setRole, + department, + setDepartment, +}: InstitutionFieldsProps) { + const { t } = useTranslation() + const countryRef = useRef(null) + const [departments, setDepartments] = useState([ + ...defaultDepartments, + ]) + const [isInstitutionFieldsVisible, setIsInstitutionFieldsVisible] = + useState(false) + const [isUniversityDirty, setIsUniversityDirty] = useState(false) + const { runAsync: institutionRunAsync } = useAsync() + + useEffect(() => { + if (isInstitutionFieldsVisible && countryRef.current) { + countryRef.current?.focus() + } + }, [countryRef, isInstitutionFieldsVisible]) + + useEffect(() => { + if (universityName) { + setIsUniversityDirty(true) + } + }, [setIsUniversityDirty, universityName]) + + useEffect(() => { + const selectedKnownUniversity = countryCode + ? universities[countryCode]?.find(({ name }) => name === universityName) + : undefined + + if (selectedKnownUniversity && selectedKnownUniversity.departments.length) { + setDepartments(selectedKnownUniversity.departments) + } else { + setDepartments([...defaultDepartments]) + } + }, [countryCode, universities, universityName]) + + // Fetch country institution + useEffect(() => { + // Skip if country not selected or universities for + // that country are already fetched + if (!countryCode || universities[countryCode]) { + return + } + + institutionRunAsync( + getJSON(`/institutions/list?country_code=${countryCode}`) + ) + .then(data => { + setUniversities(state => ({ ...state, [countryCode]: data })) + }) + .catch(() => {}) + }, [countryCode, universities, setUniversities, institutionRunAsync]) + + const getUniversityItems = () => { + if (!countryCode) { + return [] + } + + return universities[countryCode]?.map(({ name }) => name) ?? [] + } + + const handleShowInstitutionFields = () => { + setIsInstitutionFieldsVisible(true) + } + + if (!isInstitutionFieldsVisible) { + return ( + + ) + } + + return ( + <> +
+ +
+
+ +
+ {isUniversityDirty && ( + <> +
+ +
+
+ +
+ + )} + + ) +} + +export default InstitutionFields diff --git a/services/web/frontend/js/features/settings/components/emails/add-email/layout.tsx b/services/web/frontend/js/features/settings/components/emails/add-email/layout.tsx new file mode 100644 index 0000000000..dfde8e7cb9 --- /dev/null +++ b/services/web/frontend/js/features/settings/components/emails/add-email/layout.tsx @@ -0,0 +1,24 @@ +import { Row, Alert } from 'react-bootstrap' +import Icon from '../../../../../shared/components/icon' +import { UseAsyncReturnType } from '../../../../../shared/hooks/use-async' + +type LayoutProps = { + children: React.ReactNode + isError: UseAsyncReturnType['isError'] + error: UseAsyncReturnType['error'] +} + +function Layout({ isError, error, children }: LayoutProps) { + return ( +
+ {children} + {isError && ( + + {error.getUserFacingMessage()} + + )} +
+ ) +} + +export default Layout diff --git a/services/web/frontend/js/features/settings/components/emails/add-email-sso-linking-info.tsx b/services/web/frontend/js/features/settings/components/emails/add-email/sso-linking-info.tsx similarity index 80% rename from services/web/frontend/js/features/settings/components/emails/add-email-sso-linking-info.tsx rename to services/web/frontend/js/features/settings/components/emails/add-email/sso-linking-info.tsx index de76e29a72..be6e633695 100644 --- a/services/web/frontend/js/features/settings/components/emails/add-email-sso-linking-info.tsx +++ b/services/web/frontend/js/features/settings/components/emails/add-email/sso-linking-info.tsx @@ -1,17 +1,14 @@ import { Trans, useTranslation } from 'react-i18next' -import { InstitutionInfo } from './add-email-input' -import { ExposedSettings } from '../../../../../../types/exposed-settings' -import getMeta from '../../../../utils/meta' +import { InstitutionInfo } from './input' +import { ExposedSettings } from '../../../../../../../types/exposed-settings' +import getMeta from '../../../../../utils/meta' -type AddEmailSSOLinkingInfoProps = { +type SSOLinkingInfoProps = { institutionInfo: InstitutionInfo email: string } -export function AddEmailSSOLinkingInfo({ - institutionInfo, - email, -}: AddEmailSSOLinkingInfoProps) { +function SsoLinkingInfo({ institutionInfo, email }: SSOLinkingInfoProps) { const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings const { t } = useTranslation() @@ -50,3 +47,5 @@ export function AddEmailSSOLinkingInfo({ ) } + +export default SsoLinkingInfo 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 index b6b920e952..efd2e012d4 100644 --- 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 @@ -7,8 +7,8 @@ import { useUserEmailsContext } from '../../context/user-email-context' import DownshiftInput from './downshift-input' import useAsync from '../../../../shared/hooks/use-async' import { getJSON, postJSON } from '../../../../infrastructure/fetch-json' -import { defaults as defaultRoles } from '../../roles' -import { defaults as defaultDepartments } from '../../departments' +import defaultRoles from '../../data/roles' +import defaultDepartments from '../../data/departments' import { University } from '../../../../../../types/university' type InstitutionAndRoleProps = { @@ -28,7 +28,9 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) { } = useUserEmailsContext() const [role, setRole] = useState(affiliation?.role || '') const [department, setDepartment] = useState(affiliation?.department || '') - const [departments, setDepartments] = useState(defaultDepartments) + const [departments, setDepartments] = useState(() => [ + ...defaultDepartments, + ]) const roleRef = useRef(null) const isChangingAffiliationInProgress = isChangingAffiliation( state, @@ -62,7 +64,7 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) { } }) .catch(() => { - setDepartments(defaultDepartments) + setDepartments([...defaultDepartments]) }) } @@ -117,7 +119,7 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
[ { code: 'af', name: 'Afghanistan' }, { code: 'ax', name: 'Ă…land Islands' }, { code: 'al', name: 'Albania' }, @@ -253,3 +251,6 @@ export const defaults: { code: CountryCode; name: string }[] = [ { code: 'zm', name: 'Zambia' }, { code: 'zw', name: 'Zimbabwe' }, ] + +export default countries +export type CountryCode = typeof countries[number]['code'] diff --git a/services/web/frontend/js/features/settings/departments.ts b/services/web/frontend/js/features/settings/data/departments.ts similarity index 96% rename from services/web/frontend/js/features/settings/departments.ts rename to services/web/frontend/js/features/settings/data/departments.ts index 0b514b3243..2c66e1101d 100644 --- a/services/web/frontend/js/features/settings/departments.ts +++ b/services/web/frontend/js/features/settings/data/departments.ts @@ -1,4 +1,4 @@ -export const defaults = [ +const departments = [ 'Aeronautics & Astronautics', 'Anesthesia', 'Anthropology', @@ -74,3 +74,5 @@ export const defaults = [ 'Theater and Performance Studies', 'Urology', ] + +export default departments diff --git a/services/web/frontend/js/features/settings/roles.ts b/services/web/frontend/js/features/settings/data/roles.ts similarity index 85% rename from services/web/frontend/js/features/settings/roles.ts rename to services/web/frontend/js/features/settings/data/roles.ts index 7d8d81ee01..dfeeb13d9c 100644 --- a/services/web/frontend/js/features/settings/roles.ts +++ b/services/web/frontend/js/features/settings/data/roles.ts @@ -1,4 +1,4 @@ -export const defaults = [ +const roles = [ 'Undergraduate Student', 'Masters Student (MSc, MA, ...)', 'Doctoral Student (PhD, EngD, ...)', @@ -11,3 +11,5 @@ export const defaults = [ 'Professor', 'Emeritus Professor', ] + +export default roles diff --git a/services/web/frontend/js/features/settings/utils/sso.ts b/services/web/frontend/js/features/settings/utils/sso.ts new file mode 100644 index 0000000000..72cfdc19ef --- /dev/null +++ b/services/web/frontend/js/features/settings/utils/sso.ts @@ -0,0 +1,23 @@ +import getMeta from '../../../utils/meta' +import { InstitutionInfo } from '../components/emails/add-email/input' +import { ExposedSettings } from '../../../../../types/exposed-settings' +import { Nullable } from '../../../../../types/utils' + +const ssoAvailableForDomain = (domain: InstitutionInfo | null) => { + const { hasSamlBeta, hasSamlFeature } = getMeta( + 'ol-ExposedSettings' + ) as ExposedSettings + if (!hasSamlFeature || !domain || !domain.confirmed || !domain.university) { + return false + } + if (domain.university.ssoEnabled) { + return true + } + return hasSamlBeta && domain.university.ssoBeta +} + +export const isSsoAvailable = ( + institutionInfo: Nullable +): institutionInfo is InstitutionInfo => { + return Boolean(institutionInfo && ssoAvailableForDomain(institutionInfo)) +} diff --git a/services/web/frontend/stories/settings/add-email-input.stories.tsx b/services/web/frontend/stories/settings/add-email-input.stories.tsx index d6e6e639ef..03c89fb549 100644 --- a/services/web/frontend/stories/settings/add-email-input.stories.tsx +++ b/services/web/frontend/stories/settings/add-email-input.stories.tsx @@ -1,5 +1,5 @@ import useFetchMock from './../hooks/use-fetch-mock' -import { AddEmailInput } from '../../js/features/settings/components/emails/add-email-input' +import Input from '../../js/features/settings/components/emails/add-email/input' export const EmailInput = args => { useFetchMock(fetchMock => @@ -12,7 +12,7 @@ export const EmailInput = args => { ) return ( <> - +
Use autocomplete.edu as domain to trigger an autocomplete @@ -23,7 +23,7 @@ export const EmailInput = args => { export default { title: 'Account Settings / Emails and Affiliations', - component: AddEmailInput, + component: Input, argTypes: { onChange: { action: 'change' }, }, diff --git a/services/web/test/frontend/features/settings/components/emails/add-email-input.test.tsx b/services/web/test/frontend/features/settings/components/emails/add-email-input.test.tsx index c848a4d2ea..2dc8a202fe 100644 --- a/services/web/test/frontend/features/settings/components/emails/add-email-input.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/add-email-input.test.tsx @@ -7,10 +7,9 @@ import { import { expect } from 'chai' import sinon from 'sinon' import fetchMock from 'fetch-mock' -import { - AddEmailInput, +import Input, { clearDomainCache, -} from '../../../../../../frontend/js/features/settings/components/emails/add-email-input' +} from '../../../../../../frontend/js/features/settings/components/emails/add-email/input' const testInstitutionData = [ { university: { id: 124 }, hostname: 'domain.edu' }, @@ -28,13 +27,13 @@ describe('', function () { describe('on initial render', function () { it('should render an input with a placeholder', function () { - render() + render() screen.getByPlaceholderText('e.g. johndoe@mit.edu') }) it('should not dispatch any `change` event', function () { const onChangeStub = sinon.stub() - render() + render() expect(onChangeStub.called).to.equal(false) }) }) @@ -45,7 +44,7 @@ describe('', function () { beforeEach(function () { fetchMock.get('express:/institutions/domains', 200) onChangeStub = sinon.stub() - render() + render() fireEvent.change(screen.getByRole('textbox'), { target: { value: 'user' }, }) @@ -74,7 +73,7 @@ describe('', function () { beforeEach(function () { onChangeStub = sinon.stub() - render() + render() }) describe('when there are no matches', function () { @@ -228,7 +227,7 @@ describe('', function () { // initial request populates the suggestion fetchMock.get('express:/institutions/domains', testInstitutionData) onChangeStub = sinon.stub() - render() + render() fireEvent.change(screen.getByRole('textbox'), { target: { value: 'user@d' }, }) diff --git a/services/web/types/country.ts b/services/web/types/country.ts deleted file mode 100644 index 75af793a46..0000000000 --- a/services/web/types/country.ts +++ /dev/null @@ -1,252 +0,0 @@ -export type CountryCode = - | 'af' - | 'ax' - | 'al' - | 'dz' - | 'as' - | 'ad' - | 'ao' - | 'ai' - | 'aq' - | 'ag' - | 'ar' - | 'am' - | 'aw' - | 'au' - | 'at' - | 'az' - | 'bs' - | 'bh' - | 'bd' - | 'bb' - | 'by' - | 'be' - | 'bz' - | 'bj' - | 'bm' - | 'bt' - | 'bo' - | 'bq' - | 'ba' - | 'bw' - | 'bv' - | 'br' - | 'io' - | 'vg' - | 'bn' - | 'bg' - | 'bf' - | 'bi' - | 'kh' - | 'cm' - | 'ca' - | 'cv' - | 'ky' - | 'cf' - | 'td' - | 'cl' - | 'cn' - | 'cx' - | 'cc' - | 'co' - | 'km' - | 'cg' - | 'ck' - | 'cr' - | 'ci' - | 'hr' - | 'cu' - | 'cw' - | 'cy' - | 'cz' - | 'kp' - | 'cd' - | 'dk' - | 'dj' - | 'dm' - | 'do' - | 'ec' - | 'eg' - | 'sv' - | 'gq' - | 'er' - | 'ee' - | 'et' - | 'fk' - | 'fo' - | 'fj' - | 'fi' - | 'fr' - | 'gf' - | 'pf' - | 'tf' - | 'ga' - | 'gm' - | 'ge' - | 'de' - | 'gh' - | 'gi' - | 'gr' - | 'gl' - | 'gd' - | 'gp' - | 'gu' - | 'gt' - | 'gg' - | 'gn' - | 'gw' - | 'gy' - | 'ht' - | 'hm' - | 'va' - | 'hn' - | 'hk' - | 'hu' - | 'is' - | 'in' - | 'id' - | 'ir' - | 'iq' - | 'ie' - | 'im' - | 'il' - | 'it' - | 'jm' - | 'jp' - | 'je' - | 'jo' - | 'kz' - | 'ke' - | 'ki' - | 'xk' - | 'kw' - | 'kg' - | 'la' - | 'lv' - | 'lb' - | 'ls' - | 'lr' - | 'ly' - | 'li' - | 'lt' - | 'lu' - | 'mo' - | 'mk' - | 'mg' - | 'mw' - | 'my' - | 'mv' - | 'ml' - | 'mt' - | 'mh' - | 'mq' - | 'mr' - | 'mu' - | 'yt' - | 'mx' - | 'fm' - | 'md' - | 'mc' - | 'mn' - | 'me' - | 'ms' - | 'ma' - | 'mz' - | 'mm' - | 'na' - | 'nr' - | 'np' - | 'nl' - | 'an' - | 'nc' - | 'nz' - | 'ni' - | 'ne' - | 'ng' - | 'nu' - | 'nf' - | 'mp' - | 'no' - | 'om' - | 'pk' - | 'pw' - | 'ps' - | 'pa' - | 'pg' - | 'py' - | 'pe' - | 'ph' - | 'pn' - | 'pl' - | 'pt' - | 'pr' - | 'qa' - | 'kr' - | 're' - | 'ro' - | 'ru' - | 'rw' - | 'bl' - | 'sh' - | 'kn' - | 'lc' - | 'mf' - | 'pm' - | 'vc' - | 'ws' - | 'sm' - | 'st' - | 'sa' - | 'sn' - | 'rs' - | 'sc' - | 'sl' - | 'sg' - | 'sx' - | 'sk' - | 'si' - | 'sb' - | 'so' - | 'za' - | 'gs' - | 'ss' - | 'es' - | 'lk' - | 'sd' - | 'sr' - | 'sj' - | 'sz' - | 'se' - | 'ch' - | 'sy' - | 'tw' - | 'tj' - | 'tz' - | 'th' - | 'tl' - | 'tg' - | 'tk' - | 'to' - | 'tt' - | 'tn' - | 'tr' - | 'tm' - | 'tc' - | 'tv' - | 'vi' - | 'ug' - | 'ua' - | 'ae' - | 'gb' - | 'us' - | 'um' - | 'uy' - | 'uz' - | 'vu' - | 've' - | 'vn' - | 'wf' - | 'eh' - | 'ye' - | 'zm' - | 'zw' diff --git a/services/web/types/university.ts b/services/web/types/university.ts index a6377d6dbf..76325a2caa 100644 --- a/services/web/types/university.ts +++ b/services/web/types/university.ts @@ -1,4 +1,4 @@ -import { CountryCode } from './country' +import { CountryCode } from '../frontend/js/features/settings/data/countries-list' export type University = { id: number