diff --git a/client/src/components/RoleCard.jsx b/client/src/components/RoleCard.jsx index a309a670..106191eb 100644 --- a/client/src/components/RoleCard.jsx +++ b/client/src/components/RoleCard.jsx @@ -5,12 +5,19 @@ import I18n from "../locale/I18n"; import {MoreLessText} from "./MoreLessText"; import {Button, Card, CardType, Checkbox, Chip, ChipType} from "@surfnet/sds"; import {useNavigate} from "react-router-dom"; -import {isEmpty} from "../utils/Utils"; +import {isEmpty, splitListSemantically} from "../utils/Utils"; +import {roleName} from "../utils/Manage"; +import {ReactComponent as MultipleIcon} from "../icons/multi-role.svg"; export const RoleCard = ({role, index, invitationSelected, invitationSelectCallback, isNew = false}) => { const navigate = useNavigate(); - const application = role.isUserRole ? role.role.application : role.application; - const logo = application.logo; + + const applications = role.isUserRole ? role.role.applicationMaps : role.applicationMaps; + const multiApp = applications.length === 1; + const application = applications[0]; + const logo = multiApp ? application.logo : + const name = multiApp ? splitListSemantically(applications.map(app => roleName(app)), I18n.t("forms.and")) : + roleName(application); const children =
@@ -21,7 +28,7 @@ export const RoleCard = ({role, index, invitationSelected, invitationSelectCallb }
-

{application[`name:${I18n.locale}`]} ({application[`OrganizationName:${I18n.locale}`]})

+

{name}

{role.name}

diff --git a/client/src/components/RoleCard.scss b/client/src/components/RoleCard.scss index a90d737f..58034235 100644 --- a/client/src/components/RoleCard.scss +++ b/client/src/components/RoleCard.scss @@ -66,7 +66,7 @@ } } - img.provider, svg.provider { + img.provider, svg.provider, svg.multi-role { height: 90px; width: auto; margin: 0 35px auto 0; diff --git a/client/src/pages/Invitation.js b/client/src/pages/Invitation.js index fccd560b..5634bbdb 100644 --- a/client/src/pages/Invitation.js +++ b/client/src/pages/Invitation.js @@ -115,7 +115,12 @@ export const Invitation = ({authenticated}) => { } const organisationName = role => { - return ` (${role.application["OrganizationName:en"]})`; + if (role.applicationMaps.length === 1) { + const name = role.applicationMaps[0][`OrganizationName:${I18n.locale}`] || role.applicationMaps[0]["OrganizationName:en"]; + return ` (${name})`; + } + const set = new Set(role.applicationMaps.map(app => app[`OrganizationName:${I18n.locale}`] || app["OrganizationName:en"])); + return ` (${splitListSemantically([...set], I18n.t("forms.and"))})` } const renderLoginStep = () => { diff --git a/client/src/pages/Role.js b/client/src/pages/Role.js index 7d13a3b4..7481f171 100644 --- a/client/src/pages/Role.js +++ b/client/src/pages/Role.js @@ -58,7 +58,7 @@ export const Role = () => { } Promise.all([roleByID(id, false), userRolesByRoleId(id), invitationsByRoleId(id)]) .then(res => { - deriveApplicationAttributes(res[0], I18n.locale, I18n.t("roles.multiple")) + deriveApplicationAttributes(res[0], I18n.locale, I18n.t("roles.multiple"), I18n.t("forms.and")) setRole(res[0]); setUserRole(res[1].find(userRole => userRole.role.id === res[0].id && userRole.userInfo.id === user.id)); const newTabs = [ diff --git a/client/src/tabs/Applications.js b/client/src/tabs/Applications.js index 9c3f12bc..3d60bc9a 100644 --- a/client/src/tabs/Applications.js +++ b/client/src/tabs/Applications.js @@ -118,7 +118,7 @@ const Applications = () => { searchAttributes={["name", "provider", "provisioning"]} rowLinkMapper={isUserAllowed(AUTHORITIES.INVITER, user) ? openRole : null} inputFocus={true} - rowClassNameResolver={entity => entity.applications.length > 1 ? "multi-role" : ""}> + rowClassNameResolver={entity => (entity.applications || []).length > 1 ? "multi-role" : ""}>
diff --git a/client/src/tabs/Roles.js b/client/src/tabs/Roles.js index 833d2b85..167f2c7e 100644 --- a/client/src/tabs/Roles.js +++ b/client/src/tabs/Roles.js @@ -220,7 +220,7 @@ export const Roles = () => { filters={filter(filterOptions, filterValue)} customSearch={roleSearchRequired && isSuperUser ? search : null} rowLinkMapper={isUserAllowed(AUTHORITIES.INVITER, user) ? openRole : null} - rowClassNameResolver={entity => entity.applications.length > 1 ? "multi-role" : ""} + rowClassNameResolver={entity => (entity.applications || []) .length > 1 ? "multi-role" : ""} busy={searching}/> ); diff --git a/client/src/utils/Manage.js b/client/src/utils/Manage.js index 509e8728..2b27cd28 100644 --- a/client/src/utils/Manage.js +++ b/client/src/utils/Manage.js @@ -1,5 +1,6 @@ -import {isEmpty} from "./Utils"; +import {isEmpty, splitListSemantically} from "./Utils"; import {ReactComponent as MultipleIcon} from "../icons/multi-role.svg"; +import I18n from "../locale/I18n"; export const singleProviderToOption = provider => { const organisation = provider["OrganizationName:en"]; @@ -15,11 +16,17 @@ export const singleProviderToOption = provider => { }; } +export const roleName = app => { + const name = app[`name:${I18n.locale}`] || app["name:en"] + const orgName = app[`OrganizationName:${I18n.locale}`] || app["OrganizationName:en"] + return `${name} (${orgName})`; +} + export const providersToOptions = providers => { return providers.map(provider => singleProviderToOption(provider)); } -export const deriveApplicationAttributes = (role, locale, multiple) => { +export const deriveApplicationAttributes = (role, locale, multiple, separator) => { const applications = role.applicationMaps; if (!isEmpty(applications)) { if (applications.length === 1) { @@ -28,9 +35,9 @@ export const deriveApplicationAttributes = (role, locale, multiple) => { role.logo = applications[0].logo; } else { role.applicationName = multiple; - role.applicationOrganizationName = applications - .map(app => app[`OrganizationName:${locale}`] || app["OrganizationName:en"]) - .join(", ") + const orgNames = new Set(applications + .map(app => app[`OrganizationName:${locale}`] || app["OrganizationName:en"])); + role.applicationOrganizationName = splitListSemantically([...orgNames], separator); role.logo = ; } } diff --git a/client/src/utils/UserRole.js b/client/src/utils/UserRole.js index ced617c5..ebc2ab6c 100644 --- a/client/src/utils/UserRole.js +++ b/client/src/utils/UserRole.js @@ -1,5 +1,6 @@ import {isEmpty} from "./Utils"; import {deriveApplicationAttributes} from "./Manage"; +import I18n from "../locale/I18n"; export const INVITATION_STATUS = { OPEN: "OPEN", @@ -104,13 +105,13 @@ export const markAndFilterRoles = (user, allRoles, locale, multiple) => { role.isUserRole = false; role.label = role.name; role.value = role.id; - deriveApplicationAttributes(role, locale, multiple); + deriveApplicationAttributes(role, locale, multiple, I18n.t("forms.and")); }); const userRoles = user.userRoles; userRoles.forEach(userRole => { userRole.isUserRole = true; const role = userRole.role; - deriveApplicationAttributes(role, locale, multiple); + deriveApplicationAttributes(role, locale, multiple, I18n.t("forms.and")); userRole.name = role.name; userRole.label = role.name; userRole.value = role.id; diff --git a/welcome/src/components/RoleCard.jsx b/welcome/src/components/RoleCard.jsx index 66543a87..e4be39e5 100644 --- a/welcome/src/components/RoleCard.jsx +++ b/welcome/src/components/RoleCard.jsx @@ -4,17 +4,24 @@ import Logo from "./Logo"; import I18n from "../locale/I18n"; import {MoreLessText} from "./MoreLessText"; import {Button, Card, CardType, Chip, ChipType} from "@surfnet/sds"; -import {isEmpty, sanitizeURL} from "../utils/Utils"; - +import {isEmpty, sanitizeURL, splitListSemantically} from "../utils/Utils"; +import {ReactComponent as MultipleIcon} from "../icons/multi-role.svg"; +import {roleName} from "../utils/Manage"; export const RoleCard = ({role, index, isNew = false, skipLaunch= false}) => { - const application = role.application; + const applications = role.applicationMaps; + const multiApp = applications.length === 1; + const application = applications[0]; + const logo = multiApp ? application.logo : + const name = multiApp ? splitListSemantically(applications.map(app => roleName(app)), I18n.t("forms.and")) : + roleName(application); + const children =
- +
-

{application[`name:${I18n.locale}`]} ({application[`OrganizationName:${I18n.locale}`]})

+

{name}

{role.name}

diff --git a/welcome/src/icons/multi-role.svg b/welcome/src/icons/multi-role.svg new file mode 100644 index 00000000..feafbf55 --- /dev/null +++ b/welcome/src/icons/multi-role.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/welcome/src/locale/en.js b/welcome/src/locale/en.js index 93fd2346..24ac7bd5 100644 --- a/welcome/src/locale/en.js +++ b/welcome/src/locale/en.js @@ -44,6 +44,9 @@ const en = { rolesInfo: "You have access to the following applications.", expiryDays: "Expiry days" }, + roles: { + multiple: "Multiple applications", + }, forms: { ok: "Ok", and: "and", diff --git a/welcome/src/locale/nl.js b/welcome/src/locale/nl.js index 65cc0a95..71f34e4a 100644 --- a/welcome/src/locale/nl.js +++ b/welcome/src/locale/nl.js @@ -44,6 +44,9 @@ const nl = { rolesInfo: "Je bezit de volgende rollen.", expiryDays: "Verloopdagen" }, + roles: { + multiple: "Meedere applicaties", + }, forms: { ok: "Ok", and: "en", diff --git a/welcome/src/pages/Invitation.js b/welcome/src/pages/Invitation.js index 4d753880..bc878939 100644 --- a/welcome/src/pages/Invitation.js +++ b/welcome/src/pages/Invitation.js @@ -136,7 +136,8 @@ export const Invitation = ({authenticated}) => { const renderLoginStep = () => { let html = DOMPurify.sanitize(I18n.t("invitationAccept.invited", { type: I18n.t("invitationAccept.role"), - roles: splitListSemantically(invitation.roles.map(invitationRole => `${invitationRole.role.name}${organisationName(invitationRole)}`), I18n.t("forms.and")), + roles: splitListSemantically(invitation.roles + .map(invitationRole => `${invitationRole.role.name}${organisationName(invitationRole.role.applicationMaps)}`), I18n.t("forms.and")), inviter: invitation.inviter.name, plural: invitation.roles.length === 1 ? I18n.t("invitationAccept.role") : I18n.t("invitationAccept.roles"), email: invitation.inviter.email diff --git a/welcome/src/utils/Manage.js b/welcome/src/utils/Manage.js index ba8cf287..ca3b78f4 100644 --- a/welcome/src/utils/Manage.js +++ b/welcome/src/utils/Manage.js @@ -1,3 +1,17 @@ -export const organisationName = invitationRole => { - return ` (${invitationRole.role.application["OrganizationName:en"]})`; +import I18n from "../locale/I18n"; +import {splitListSemantically} from "./Utils"; + +export const organisationName = apps => { + if (apps.length === 1) { + return ` (${apps[0]["OrganizationName:en"]})`; + } else { + const set = new Set(apps.map(app => app["OrganizationName:en"]).sort()); + return splitListSemantically([...set], I18n.t("forms.and")); + } +} + +export const roleName = app => { + const name = app[`name:${I18n.locale}`] || app["name:en"] + const orgName = app[`OrganizationName:${I18n.locale}`] || app["OrganizationName:en"] + return `${name} (${orgName})`; }