diff --git a/.eslintrc.json b/.eslintrc.json index f5c01e22e..ce64da96c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -32,14 +32,11 @@ "@typescript-eslint/comma-dangle": 0, "@typescript-eslint/explicit-function-return-type": 0, "@typescript-eslint/ban-types": 0, - "@typescript-eslint/no-empty-function": 0, - "@typescript-eslint/no-extra-semi": 0, "@typescript-eslint/no-unsafe-argument": 0, "@typescript-eslint/no-unsafe-assignment": 0, "@typescript-eslint/no-unsafe-call": 0, "@typescript-eslint/no-unsafe-member-access": 0, "@typescript-eslint/no-unsafe-return": 0, - "@typescript-eslint/prefer-optional-chain": 0, "@typescript-eslint/restrict-template-expressions": 0, "@typescript-eslint/strict-boolean-expressions": 0, "@typescript-eslint/array-type": 0, diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c08161a3..ca7fd6261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,27 @@ # Changelog -## unreleased +## 1.7.0-RC2 -- ... +### Change + +- User Management screen for user invite block due to multiple active IdPs got enhanced inside the frontend-business-logic to exclude "MANAGED" idps +- Enhanced /StaticTemplateResponsive/Cards/TextImageCenterAligned.tsx to support multiple images + +### Feature + +- released prototype (identity provider creation "MANAGED"; register 3rd party company; approve consent osp registration company) onboarding service provider registration flow + +### Technical Support + +- Code quality & style improvements implemented based on ESLINT rules + - removed extra semicolons to avoid warnings + - no-empty-function + - prefer-optional-chain + +### Bugfix + +- Partner Network - update search query parameter name to legalName +- Fixed broken KeyValueView/index.tsx for undefined object attributes ## 1.7.0-RC1 @@ -35,8 +54,14 @@ - Fixed image display in app overview cards - IDP - Add load element for IDP list +- Onboarding Serviceprovider + - Add the OSP prototype - Linter Findings - Fix ban-types + - No empty function + - No Extra Semi +- Partner Network + - Search for Company name issue fix - App Subscription and Servcie Subscripiton - Status UI Changes - Technical User Detail @@ -44,6 +69,8 @@ - UI Changes - Home - Fixed image display for 'my business applications' +- App Subscription and Servcie Subscripiton + - Status UI Changes - Vulnerability from dependency - Set resolution for @babel/traverse (CVE-2023-45133) diff --git a/package.json b/package.json index b5d55413b..9b5d0453f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@catena-x/portal-frontend", - "version": "v1.7.0-RC1", + "version": "v1.7.0-RC2", "description": "Catena-X Portal Frontend", "author": "Catena-X Contributors", "license": "Apache-2.0", diff --git a/src/assets/locales/de/main.json b/src/assets/locales/de/main.json index df7a6ba02..0f89cf767 100644 --- a/src/assets/locales/de/main.json +++ b/src/assets/locales/de/main.json @@ -972,6 +972,7 @@ }, "description": "Description", "imageGallery": "Images", + "tags": "Tags", "privacy": { "heading": "Datenschutz", "message": "Die Datenschutzerklärung gibt an, welche Daten die Geschäftsanwendungen erheben, handhaben und verarbeiten. Die nachstehenden Details zeigen explizit, welche Unternehmens-/Benutzerinformationen gesammelt/gespeichert werden.", @@ -1185,9 +1186,9 @@ "recommendations": "Recommendations", "allServices": "All Services", "tabs": { - "all": "All Services", - "dataspaceService": "Dataspace Services", - "consultancyService": "Consultancy Services" + "all": "Alle Dienstleistungen", + "dataspaceService": "Datenraumdienste", + "consultancyService": "Beratungsservice" }, "sortOptions": { "new": "Newest First", @@ -1233,6 +1234,8 @@ "activateBtn": "Activate", "configureBtn": "Configure", "pleaseEnterValidURL": "Please enter a valid URL", + "error": "Error", + "success": "Success", "tabs": { "request": "Request", "active": "Active", diff --git a/src/assets/locales/en/idp.json b/src/assets/locales/en/idp.json index 9b814a456..c89e2a55d 100644 --- a/src/assets/locales/en/idp.json +++ b/src/assets/locales/en/idp.json @@ -188,7 +188,12 @@ }, "providerType": { "name": "Identity Provider Type", - "placeholder": "Please select a type" + "placeholder": "Please select a type", + "hint": "Select provider type for your own company or managed for a third party company", + "option": { + "OWN": "create an IdP connection to your company IdP for yourself", + "MANAGED": "create a managed IdP connection for a third party" + } }, "redirectUri": { "name": "Redirect URL", @@ -213,6 +218,26 @@ "copy": { "hint": "click to copy" }, + "name": { + "name": "Client ID", + "hint": "Enter the client ID provided by your IdP" + }, + "company": { "name": "company", "hint": "company" }, + "extid": { "name": "extid", "hint": "extid" }, + "bpn": { "name": "bpn", "hint": "bpn" }, + "country": { "name": "country", "hint": "country" }, + "region": { "name": "region", "hint": "region" }, + "zipcode": { "name": "zipcode", "hint": "zipcode" }, + "city": { "name": "city", "hint": "city" }, + "streetName": { "name": "street", "hint": "street" }, + "streetNumber": { "name": "street no", "hint": "street no" }, + "uniqueId": { "name": "unique id", "hint": "unique id" }, + "uniqeIdValue": { "name": "uid value", "hint": "uid value" }, + "providerId": { "name": "provider id", "hint": "provider id" }, + "email": { "name": "email", "hint": "email" }, + "firstName": { "name": "first name", "hint": "first name" }, + "lastName": { "name": "last name", "hint": "last name" }, + "companyRoles": { "name": "company roles", "hint": "company roles" }, "not": "not", "configured": "configured", "enabled": "enabled", @@ -227,6 +252,45 @@ "status3": "disabled", "status4": "active" }, + "osp": { + "title": "Onboarding Serviceprovider", + "desc": "{Description of the Onboarding Serviceprovider process}", + "steps": { + "one": { + "text": "done", + "title": "Create" + }, + "two": { + "text": "done", + "title": "Configure" + }, + "three": { + "text": "done", + "title": "Register" + }, + "four": { + "text": "done", + "title": "Next Steps" + } + }, + "register": { + "title": "Register Partner", + "desc": "{Description of the Register Partner process}", + "error": "Error submitting registration", + "success": "Registration successful" + }, + "register_next": { + "title": "Next Steps", + "desc": "{Description of next steps}", + "body1": "Registration of IDP {{id}} successful. Now do ..." + }, + "consent": { + "title": "Consent", + "desc": "{Description of the Consent process}", + "error": "Error submitting consent", + "success": "Consent successful" + } + }, "action": { "actions": "Actions", "createIdp": "Create IdP entry", @@ -237,6 +301,8 @@ "delete": "Delete", "edit": "Edit", "configure": "Configure", + "register": "Register Partner", + "consent": "Consent", "users": "Users", "signout": "Sign out", "confirm": "Confirm", diff --git a/src/assets/locales/en/main.json b/src/assets/locales/en/main.json index fa1e5c52b..87cd60f70 100644 --- a/src/assets/locales/en/main.json +++ b/src/assets/locales/en/main.json @@ -935,6 +935,7 @@ }, "description": "Description", "imageGallery": "Images", + "tags": "Tags", "privacy": { "heading": "Privacy Policy", "message": "The Privacy Policy states which data the business applications collects, handles and processes. The details below explicitly show which company / user information are getting collected/stored.", @@ -1197,6 +1198,8 @@ "activateBtn": "Activate", "configureBtn": "Configure", "pleaseEnterValidURL": "Please enter a valid URL", + "error": "Error", + "success": "Success", "tabs": { "request": "Request", "active": "Active", diff --git a/src/components/overlays/ActivateServiceSubscription/index.tsx b/src/components/overlays/ActivateServiceSubscription/index.tsx index b3052ebf2..0f13c5590 100644 --- a/src/components/overlays/ActivateServiceSubscription/index.tsx +++ b/src/components/overlays/ActivateServiceSubscription/index.tsx @@ -90,245 +90,241 @@ export default function ActivateserviceSubscription({ } return ( - <> - - {activationResponse && ( -
- - {' '} - {t('serviceSubscription.activation.title')} - - } - intro={t('serviceSubscription.activation.subtitle') + companyName} - closeWithIcon={true} - /> - - + {activationResponse && ( +
+ + {' '} + {t('serviceSubscription.activation.title')} + + } + intro={t('serviceSubscription.activation.subtitle') + companyName} + closeWithIcon={true} + /> + + + - + {isTechUser + ? t( + 'serviceSubscription.activation.successDescriptionWithTechUser' + ) + : t('serviceSubscription.activation.successDescription')} + + + + {isTechUser && techUserInfo && ( + <> + - - {isTechUser - ? t( - 'serviceSubscription.activation.successDescriptionWithTechUser' - ) - : t('serviceSubscription.activation.successDescription')} - - - - {isTechUser && techUserInfo && ( - <> - - {t('serviceSubscription.activation.tableheader')} - - - - - - - - - - - - - - {techUserInfo.clientInfo ? ( - - ) : ( - - )} - - + {t('serviceSubscription.activation.tableheader')} + +
- - {t('serviceSubscription.activation.userId')} - - - - {techUserInfo.technicalUserInfo.technicalUserId} - -
- - {t('serviceSubscription.activation.sercret')} - - - - {techUserInfo.technicalUserInfo.technicalUserSecret} - -
- - {t('serviceSubscription.activation.url')} - - - - {techUserInfo.clientInfo.clientUrl} - - - -
+ + + + + + + + + + + + {techUserInfo.clientInfo ? ( + ) : ( - - -
+ + {t('serviceSubscription.activation.userId')} + + + + {techUserInfo.technicalUserInfo.technicalUserId} + +
+ + {t('serviceSubscription.activation.sercret')} + + + + {techUserInfo.technicalUserInfo.technicalUserSecret} + +
+ + {t('serviceSubscription.activation.url')} + + - {t( - 'serviceSubscription.activation.technicaluserType' - )} + {techUserInfo.clientInfo.clientUrl} - - {techUserInfo.technicalUserInfo.technicalUserPermissions.join( - ', ' - )} - +
- - )} -
- - - -
- )} - {!activationResponse && ( - <> - - - + + + + {t( + 'serviceSubscription.activation.technicaluserType' + )} + + + + + {techUserInfo.technicalUserInfo.technicalUserPermissions.join( + ', ' + )} + + + + + + + )} + + + + +
+ )} + {!activationResponse && ( + <> + + + + - - - {isTechUser - ? t( - 'serviceSubscription.register.descriptionWithTechUser' - ) - : t('serviceSubscription.register.description')} - - - {isTechUser && ( - <> - - - {t('serviceSubscription.register.sectionHeader')} - + + {isTechUser + ? t('serviceSubscription.register.descriptionWithTechUser') + : t('serviceSubscription.register.description')} + + + {isTechUser && ( + <> + + + {t('serviceSubscription.register.sectionHeader')} + + + {t('serviceSubscription.register.sectionDescription')} + + + + + + {t('serviceSubscription.register.help')} + + + + + {t('serviceSubscription.register.sectionHeader')} + + {techUserProfiles && techUserProfiles?.length > 0 ? ( - {t('serviceSubscription.register.sectionDescription')} + {techUserProfiles.join(', ')} - - - - - {t('serviceSubscription.register.help')} - - - - - {t('serviceSubscription.register.sectionHeader')} + ) : ( + + {t('serviceSubscription.register.loading')} - {techUserProfiles && techUserProfiles?.length > 0 ? ( - - {techUserProfiles.join(', ')} - - ) : ( - - {t('serviceSubscription.register.loading')} - - )} - - - )} - - - - + {loading ? ( + { + // do nothing }} - > - {t('serviceSubscription.register.close')} + sx={{ marginLeft: '10px' }} + /> + ) : ( + - {loading ? ( - {}} - sx={{ marginLeft: '10px' }} - /> - ) : ( - - )} - - - )} -
- + )} + + + )} + ) } diff --git a/src/components/overlays/AddBPN/index.tsx b/src/components/overlays/AddBPN/index.tsx index b2510a8a2..a2dc8bdb5 100644 --- a/src/components/overlays/AddBPN/index.tsx +++ b/src/components/overlays/AddBPN/index.tsx @@ -104,28 +104,27 @@ export default function AddBPN({ id }: { id: string }) {
void }) => { const { t } = useTranslation('idp') + const [type, setType] = useState(providerType) + return ( -
+
{t('field.providerType.name')} + + +
+ } + />
- { - onChange(e?.value) + { + setType(IDPProviderType.OWN) + onChange(IDPProviderType.OWN) + }} + value={IDPProviderType.OWN} + inputProps={{ 'aria-label': IDPProviderType.OWN }} + /> + { + setType(IDPProviderType.MANAGED) + onChange(IDPProviderType.MANAGED) }} - keyTitle={'title'} + value={IDPProviderType.MANAGED} + inputProps={{ 'aria-label': IDPProviderType.MANAGED }} />
) @@ -107,7 +123,9 @@ const SelectIdpAuthType = ({ const [type, setType] = useState(IDPAuthType.OIDC) return ( -
+
{t('field.type.name')} @@ -126,21 +144,19 @@ const SelectIdpAuthType = ({ } />
-
- { - setType(IDPAuthType.OIDC) - onChange(IDPAuthType.OIDC) - }} - value={IDPAuthType.OIDC} - inputProps={{ 'aria-label': IDPAuthType.OIDC }} - /> -
{ + setType(IDPAuthType.OIDC) + onChange(IDPAuthType.OIDC) + }} + value={IDPAuthType.OIDC} + inputProps={{ 'aria-label': IDPAuthType.OIDC }} + /> + void }) => { const { t } = useTranslation('idp') - const [formData, setFormData] = useState( - initialAddIDPPrepareForm - ) + const [formData, setFormData] = useState({ + type: IDPType.COMPANY, + providerType, + authType: IDPAuthType.OIDC, + name: '', + }) return ( <> @@ -194,32 +208,51 @@ const AddIDPPrepareForm = ({ onChange(currentData) }} /> - { - const currentData = { ...formData } - currentData.providerType = value - setFormData(currentData) - onChange(currentData) +
- { - const currentData = { ...formData } - currentData.authType = value - setFormData(currentData) - onChange(currentData) - }} - /> + > + { + const currentData = { + ...formData, + authType: value, + } + setFormData(currentData) + onChange(currentData) + }} + /> + { + const currentData = { + ...formData, + providerType: value, + } + setFormData(currentData) + onChange(currentData) + }} + /> +
) } -export const AddIdp = () => { +export const AddIdp = ({ + providerType = IDPProviderType.OWN, +}: { + providerType?: IDPProviderType +}) => { const { t } = useTranslation('idp') const dispatch = useDispatch() - const [formData, setFormData] = useState( - initialAddIDPPrepareForm - ) + const [formData, setFormData] = useState({ + type: IDPType.COMPANY, + providerType, + authType: IDPAuthType.OIDC, + name: '', + }) const [loading, setLoading] = useState(false) const [addIdp] = useAddIDPMutation() const [updateIdp] = useUpdateIDPMutation() @@ -279,16 +312,22 @@ export const AddIdp = () => { />
- +
{t('add.desc')} - { - setFormData(data) - }} - /> + { loadIndicator={t('action.loading')} loading size="medium" - onButtonClick={() => {}} + onButtonClick={() => { + // do nothing + }} sx={{ marginLeft: '10px' }} /> ) : ( + {state === ProcessingType.BUSY ? ( + { + // do nothing }} - > - {`${t('global.actions.cancel')}`} + sx={{ marginLeft: '10px' }} + /> + ) : ( + - {state === ProcessingType.BUSY ? ( - {}} - sx={{ marginLeft: '10px' }} - /> - ) : ( - - )} - - - + )} + + ) } diff --git a/src/components/overlays/OSPConsent/OSPConsentContent.tsx b/src/components/overlays/OSPConsent/OSPConsentContent.tsx new file mode 100644 index 000000000..8c56d6d82 --- /dev/null +++ b/src/components/overlays/OSPConsent/OSPConsentContent.tsx @@ -0,0 +1,176 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { Checkbox } from '@catena-x/portal-shared-components' +import { Typography } from '@mui/material' +import { + CONSENT_STATUS, + type CompanyRole, + type CompanyRoleAgreementData, + type PartnerRegistrationConsent, + type PartnerRegistration, +} from 'features/admin/networkApiSlice' +import i18next from 'i18next' +import { useState } from 'react' +import { getHeaders } from 'services/RequestService' +import { getApiBase } from 'services/EnvironmentService' +import './style.scss' + +const DocumentDownloadLink = ({ id, name }: { id: string; name: string }) => { + const onClick = (e: React.MouseEvent) => { + e.preventDefault() + fetch( + `${getApiBase()}/api/administration/registration/documents/${id}`, + getHeaders() + ) + .then((response) => response.blob()) + .then((blob) => { + const url = window.URL.createObjectURL(new Blob([blob])) + const link = document.createElement('a') + link.href = url + link.setAttribute('download', `${name}.pdf`) + document.body.appendChild(link) + link.click() + link.parentNode!.removeChild(link) + }) + } + + return ( + + [doc] + + ) +} + +const OSPConsentContentForm = ({ + application, + companyRoleAgreementData, + onChange, +}: { + application: PartnerRegistration + companyRoleAgreementData: CompanyRoleAgreementData + onChange: (consent: Set) => void +}) => { + const [consent, setConsent] = useState>( + new Set( + application.agreements?.map((agreement) => agreement.agreementId) + ) + ) + + const toggleConsent = (id: string) => { + const newConsent = new Set(consent) + if (newConsent.has(id)) { + newConsent.delete(id) + } else { + newConsent.add(id) + } + setConsent(newConsent) + onChange(newConsent) + } + + return ( +
    + {companyRoleAgreementData.companyRoles + .filter((role) => application.companyRoles.includes(role.companyRole)) + .map((role: CompanyRole) => ( +
  • + {role.companyRole} +
    {role.descriptions[i18next.language]}
    +
      + {role.agreementIds.map((id) => { + const agreement = companyRoleAgreementData.agreements.filter( + (a) => id === a.agreementId + )?.[0] + return agreement ? ( +
    • + { + toggleConsent(agreement.agreementId) + }} + checked={consent.has(agreement.agreementId)} + /> + +
    • + ) : ( + <> + ) + })} +
    +
  • + ))} +
+ ) +} + +export const OSPConsentContent = ({ + application, + companyRoleAgreementData, + onValid, +}: { + application: PartnerRegistration + companyRoleAgreementData: CompanyRoleAgreementData + onValid: (consent: PartnerRegistrationConsent) => void +}) => { + const [debug, setDebug] = useState(false) + const toggleDebug = () => { + setDebug(!debug) + } + + const doCheckData = (consent: Set) => { + onValid({ + companyRoles: application.companyRoles, + agreements: [...consent].map((agreementId) => ({ + agreementId, + consentStatus: CONSENT_STATUS.ACTIVE, + })), + }) + } + + return ( +
+
+        {JSON.stringify(application, null, 2)}
+      
+ +
+        {debug ? JSON.stringify(companyRoleAgreementData, null, 2) : '{...}'}
+      
+
+ ) +} diff --git a/src/components/overlays/OSPConsent/index.tsx b/src/components/overlays/OSPConsent/index.tsx new file mode 100644 index 000000000..cd8cf6a2b --- /dev/null +++ b/src/components/overlays/OSPConsent/index.tsx @@ -0,0 +1,139 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { Trans, useTranslation } from 'react-i18next' +import { + Button, + DialogActions, + DialogContent, + DialogHeader, + LoadingButton, + Typography, +} from '@catena-x/portal-shared-components' +import { useDispatch } from 'react-redux' +import { closeOverlay } from 'features/control/overlay' +import { useState } from 'react' +import { OSPConsentContent } from './OSPConsentContent' +import { error, success } from 'services/NotifyService' +import { + type PartnerRegistrationConsent, + useFetchCompanyRoleAgreementDataQuery, + useRegisterPartnerConsentMutation, + emptyPartnerRegistrationConsent, + useFetchRegistrationApplicationsQuery, + useFetchRegistrationApplicationDataQuery, +} from 'features/admin/networkApiSlice' + +export const OSPApplicationConsent = ({ id }: { id: string }) => { + const { t } = useTranslation('idp') + const dispatch = useDispatch() + const application = useFetchRegistrationApplicationDataQuery(id).data + const companyRoleAgreementData = useFetchCompanyRoleAgreementDataQuery().data + const [registerPartnerConsent] = useRegisterPartnerConsentMutation() + const [consent, setConsent] = useState( + emptyPartnerRegistrationConsent + ) + const [loading, setLoading] = useState(false) + + console.log(application, companyRoleAgreementData) + + const doConsent = async () => { + if (!(application && consent)) return + setLoading(true) + try { + console.log(consent) + await registerPartnerConsent(consent).unwrap() + success(t('osp.consent.success')) + dispatch(closeOverlay()) + } catch (err) { + error(t('osp.consent.error'), '', err as object) + } + setLoading(false) + } + + return ( + <> + dispatch(closeOverlay())} + /> + +
+ + {t('osp.consent.desc')} + +
+ {application && companyRoleAgreementData && ( + + )} +
+ + + {loading ? ( + { + // do nothing + }} + sx={{ marginLeft: '10px' }} + /> + ) : ( + + )} + + + ) +} + +export const OSPConsent = () => { + const applications = useFetchRegistrationApplicationsQuery().data + return applications && applications.length > 0 ? ( + + ) : ( + <> + ) +} diff --git a/src/components/overlays/OSPConsent/style.scss b/src/components/overlays/OSPConsent/style.scss new file mode 100644 index 000000000..a3c981854 --- /dev/null +++ b/src/components/overlays/OSPConsent/style.scss @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +ul.roles { + list-style-type: none; + padding-inline-start: 0; +} + +ul.roles > li { + margin: 20px 0; +} + +ul.agreements { + list-style-type: none; +} diff --git a/src/components/overlays/OSPRegister/OSPRegisterContent.tsx b/src/components/overlays/OSPRegister/OSPRegisterContent.tsx new file mode 100644 index 000000000..ff94e6e5e --- /dev/null +++ b/src/components/overlays/OSPRegister/OSPRegisterContent.tsx @@ -0,0 +1,515 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { type CSSProperties, useState } from 'react' +import { type IdentityProvider } from 'features/admin/idpApiSlice' +import { + isBPNOrEmpty, + isCityName, + isCompanyName, + isCountryCode, + isFirstName, + isID, + isLastName, + isMail, + isPartnerUniqueID, + isRegionNameOrEmpty, + isStreetName, + isStreetNumberOrEmpty, + isUUID, + isZipCodeOrEmpty, +} from 'types/Patterns' +import { useTranslation } from 'react-i18next' +import ValidatingInput from 'components/shared/basic/Input/ValidatingInput' +import { + Checkbox, + SelectList, + Typography, +} from '@catena-x/portal-shared-components' +import { + type PartnerRegistration, + type CompanyRoleAgreementData, + type CompanyRole, + UNIQUE_ID_TYPE, + emptyPartnerRegistration, +} from 'features/admin/networkApiSlice' + +const getEmptyPartnerRegistration = (identityProviderId: string) => ({ + ...emptyPartnerRegistration, + userDetails: [ + { + ...emptyPartnerRegistration.userDetails[0], + identityProviderId, + }, + ], +}) + +type ItemType = { id: string } + +const uniqeIdItems: Array = Object.keys(UNIQUE_ID_TYPE).map( + (item) => ({ + id: item, + }) +) + +const OSPRegisterForm = ({ + idp, + companyRoleAgreementData, + onChange, +}: { + idp: IdentityProvider + companyRoleAgreementData: CompanyRoleAgreementData + onChange: (partnerRegistration: PartnerRegistration | undefined) => boolean +}) => { + const { t } = useTranslation('idp') + + const [data, setData] = useState( + getEmptyPartnerRegistration(idp.identityProviderId) + ) + + const updateData = (newData: PartnerRegistration | undefined) => { + if (newData) setData(newData) + onChange(newData) + } + + const invalidate = () => { + updateData(undefined) + } + + const inputStyle2: CSSProperties = { width: 406 } + const inputStyle3: CSSProperties = { width: 260 } + + const inputs: Record = { + extid: ( + { + updateData({ + ...data, + externalId: value, + }) + }} + /> + ), + company: ( + { + updateData({ + ...data, + name: value, + }) + }} + /> + ), + bpn: ( + { + updateData({ + ...data, + bpn: value ? value.toLocaleUpperCase() : null, + }) + }} + /> + ), + country: ( + { + updateData({ + ...data, + countryAlpha2Code: value, + }) + }} + /> + ), + region: ( + { + updateData({ + ...data, + region: value, + }) + }} + /> + ), + city: ( + { + updateData({ + ...data, + city: value, + }) + }} + /> + ), + zipcode: ( + { + updateData({ + ...data, + zipCode: value, + }) + }} + /> + ), + streetName: ( + { + updateData({ + ...data, + streetName: value, + }) + }} + /> + ), + streetNumber: ( + { + updateData({ + ...data, + streetNumber: value, + }) + }} + /> + ), + providerId: ( + { + updateData({ + ...data, + userDetails: [ + { + ...data.userDetails[0], + providerId: value, + }, + ], + }) + }} + /> + ), + firstName: ( + { + updateData({ + ...data, + userDetails: [ + { + ...data.userDetails[0], + firstName: value, + }, + ], + }) + }} + /> + ), + lastName: ( + { + updateData({ + ...data, + userDetails: [ + { + ...data.userDetails[0], + lastName: value, + }, + ], + }) + }} + /> + ), + email: ( + { + updateData({ + ...data, + userDetails: [ + { + ...data.userDetails[0], + email: value, + }, + ], + }) + }} + /> + ), + uniqueId: ( +
+ { + updateData({ + ...data, + uniqueIds: value + ? [ + { + ...data.uniqueIds[0], + type: value.id as UNIQUE_ID_TYPE, + }, + ] + : [], + }) + }, + }} + /> +
+ ), + uniqeIdValue: ( + { + updateData({ + ...data, + uniqueIds: [ + { + ...data.uniqueIds[0], + value, + }, + ], + }) + }} + /> + ), + companyRoles: ( +
+
+ + {t('field.companyRoles.name')} + +
+ {companyRoleAgreementData.companyRoles.map((role: CompanyRole) => ( + { + const useRole = e.target.checked + const companyRoles = [...data.companyRoles] + if (useRole) { + companyRoles.push(role.companyRole) + companyRoles.sort((a, b) => a.localeCompare(b)) + } else { + const index = companyRoles.indexOf(role.companyRole) + if (index > -1) { + companyRoles.splice(index, 1) + } + } + updateData({ + ...data, + companyRoles, + }) + }} + /> + ))} +
+ ), + } + + const rowStyle: CSSProperties = { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + width: 864, + } + + return ( + <> +
+ {inputs.company} + {inputs.extid} + {inputs.bpn} +
+
+ {inputs.country} + {inputs.region} + {inputs.zipcode} +
+
+ {inputs.city} + {inputs.streetName} + {inputs.streetNumber} +
+
+ {inputs.uniqueId} + {inputs.uniqeIdValue} +
+
+ {inputs.providerId} + {inputs.email} +
+
+ {inputs.firstName} + {inputs.lastName} +
+
{inputs.companyRoles}
+ + ) +} + +export const OSPRegisterContent = ({ + idp, + companyRoleAgreementData, + onValid, +}: { + idp: IdentityProvider + companyRoleAgreementData: CompanyRoleAgreementData + onValid: (form: PartnerRegistration | undefined) => void +}) => { + const initialData: PartnerRegistration = { + ...emptyPartnerRegistration, + userDetails: [ + { + ...emptyPartnerRegistration.userDetails[0], + identityProviderId: idp.identityProviderId, + }, + ], + } + const [formData, setFormData] = useState( + JSON.stringify(initialData, null, 2) + ) + const [debug, setDebug] = useState(false) + const toggleDebug = () => { + setDebug(!debug) + } + + const doCheckForm = ( + partnerRegistration: PartnerRegistration | undefined + ) => { + const valid = + partnerRegistration && + partnerRegistration.companyRoles.length > 0 && + partnerRegistration?.uniqueIds.length > 0 + onValid(valid ? partnerRegistration : undefined) + setFormData( + valid ? JSON.stringify(partnerRegistration, null, 2) : '# data invalid' + ) + return !!valid + } + + return ( + <> + +
+        {debug ? formData : '{...}'}
+      
+ , + + ) +} diff --git a/src/components/overlays/OSPRegister/OSPRegisterNext.tsx b/src/components/overlays/OSPRegister/OSPRegisterNext.tsx new file mode 100644 index 000000000..3dd0bc644 --- /dev/null +++ b/src/components/overlays/OSPRegister/OSPRegisterNext.tsx @@ -0,0 +1,89 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { Trans, useTranslation } from 'react-i18next' +import { + Button, + DialogActions, + DialogContent, + DialogHeader, + Stepper, + Typography, +} from '@catena-x/portal-shared-components' +import { closeOverlay } from 'features/control/overlay' +import { useDispatch } from 'react-redux' + +export const OSPRegisterNext = ({ id }: { id: string }) => { + const { t } = useTranslation('idp') + const dispatch = useDispatch() + + const steps = [ + { + step: 1, + headline: t('osp.steps.one.title'), + text: t('osp.steps.one.text'), + }, + { + step: 2, + headline: t('osp.steps.two.title'), + text: t('osp.steps.two.text'), + }, + { + step: 3, + headline: t('osp.steps.three.title'), + text: t('osp.steps.three.text'), + }, + { + step: 4, + headline: t('osp.steps.four.title'), + }, + ] + + return ( + <> + dispatch(closeOverlay())} + /> + +
+ +
+
+ + + {t('osp.register_next.desc')} + + +
+ + {t('osp.register_next.body1', { id })} + +
+ + + + + ) +} diff --git a/src/components/overlays/OSPRegister/index.tsx b/src/components/overlays/OSPRegister/index.tsx new file mode 100644 index 000000000..ea110e6ae --- /dev/null +++ b/src/components/overlays/OSPRegister/index.tsx @@ -0,0 +1,120 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { Trans, useTranslation } from 'react-i18next' +import { + Button, + DialogActions, + DialogContent, + DialogHeader, + LoadingButton, + Typography, +} from '@catena-x/portal-shared-components' +import { useDispatch } from 'react-redux' +import { closeOverlay } from 'features/control/overlay' +import { useState } from 'react' +import { useFetchIDPDetailQuery } from 'features/admin/idpApiSlice' +import { OSPRegisterContent } from './OSPRegisterContent' +import { error, success } from 'services/NotifyService' +import { + useRegisterPartnerMutation, + type PartnerRegistration, + useFetchCompanyRoleAgreementDataQuery, +} from 'features/admin/networkApiSlice' + +export const OSPRegister = ({ id }: { id: string }) => { + const { t } = useTranslation('idp') + const dispatch = useDispatch() + const { data } = useFetchIDPDetailQuery(id) + const companyRoleAgreementData = useFetchCompanyRoleAgreementDataQuery().data + + const [registerPartner] = useRegisterPartnerMutation() + + const [registerData, setRegisterData] = useState< + PartnerRegistration | undefined + >(undefined) + const [loading, setLoading] = useState(false) + + const doRegister = async () => { + if (!(data && registerData)) return + setLoading(true) + try { + await registerPartner(registerData).unwrap() + dispatch(closeOverlay()) + success(t('osp.register.success')) + } catch (err) { + error(t('osp.register.error'), '', err as object) + } + setLoading(false) + } + + return ( + <> + dispatch(closeOverlay())} + /> + +
+ + {t('osp.register.desc')} + +
+ {data && companyRoleAgreementData && ( + + )} +
+ + + {loading ? ( + { + // do nothing + }} + sx={{ marginLeft: '10px' }} + /> + ) : ( + + )} + + + ) +} diff --git a/src/components/overlays/ServiceRequest/index.tsx b/src/components/overlays/ServiceRequest/index.tsx index e0bae512b..ad19e2012 100644 --- a/src/components/overlays/ServiceRequest/index.tsx +++ b/src/components/overlays/ServiceRequest/index.tsx @@ -85,9 +85,7 @@ export default function ServiceRequest({ id }: { id: string }) { agreement.agreementId, ]) } else { - const index = - selectedAgreementsIds && - selectedAgreementsIds.indexOf(agreement.agreementId) + const index = selectedAgreementsIds?.indexOf(agreement.agreementId) if (index > -1) { selectedAgreementsIds.splice(index, 1) setSelectedAgreementsIds([...selectedAgreementsIds]) @@ -113,18 +111,19 @@ export default function ServiceRequest({ id }: { id: string }) { )}
    - {serviceAgreements && - serviceAgreements.map((agreement, index) => ( -
  • - { - handleSelectedAgreement(e.target.checked, agreement) - }} - onFocusVisible={function noRefCheck() {}} - /> -
  • - ))} + {serviceAgreements?.map((agreement, index) => ( +
  • + { + handleSelectedAgreement(e.target.checked, agreement) + }} + onFocusVisible={function noRefCheck() { + // do nothing + }} + /> +
  • + ))}
diff --git a/src/components/overlays/UpdateCertificate/index.tsx b/src/components/overlays/UpdateCertificate/index.tsx index f0b8502ab..6b57891f5 100644 --- a/src/components/overlays/UpdateCertificate/index.tsx +++ b/src/components/overlays/UpdateCertificate/index.tsx @@ -306,7 +306,9 @@ export default function UpdateCertificate({ id }: { id: string }) { loadIndicator="Loading ..." loading size="medium" - onButtonClick={() => {}} + onButtonClick={() => { + // do nothing + }} sx={{ marginLeft: '10px' }} /> ) : ( diff --git a/src/components/overlays/UpdateCompanyRole/index.tsx b/src/components/overlays/UpdateCompanyRole/index.tsx index b319a6b9f..c7fa733ec 100644 --- a/src/components/overlays/UpdateCompanyRole/index.tsx +++ b/src/components/overlays/UpdateCompanyRole/index.tsx @@ -420,7 +420,9 @@ export default function UpdateCompanyRole({ roles }: { roles: string[] }) { loadIndicator="Loading ..." loading size="medium" - onButtonClick={() => {}} + onButtonClick={() => { + // do nothing + }} sx={{ marginLeft: '10px' }} /> ) : ( diff --git a/src/components/overlays/UpdateIDP/index.tsx b/src/components/overlays/UpdateIDP/index.tsx index 73b43a1f4..131c39438 100644 --- a/src/components/overlays/UpdateIDP/index.tsx +++ b/src/components/overlays/UpdateIDP/index.tsx @@ -36,6 +36,8 @@ import { type IdentityProviderUpdate, useFetchIDPDetailQuery, useUpdateIDPMutation, + useEnableIDPMutation, + IDPCategory, } from 'features/admin/idpApiSlice' import { UpdateIDPContent } from './UpdateIDPContent' import { OVERLAYS } from 'types/Constants' @@ -46,18 +48,30 @@ export const UpdateIDP = ({ id }: { id: string }) => { const dispatch = useDispatch() const { data } = useFetchIDPDetailQuery(id) const [updateIdp] = useUpdateIDPMutation() + const [enableIdp] = useEnableIDPMutation() const [idpUpdateData, setIdpUpdateData] = useState< IdentityProviderUpdate | undefined >(undefined) const [loading, setLoading] = useState(false) + console.log(data) + const doUpdateIDP = async () => { if (!(data && idpUpdateData)) return setLoading(true) try { await updateIdp(idpUpdateData).unwrap() - dispatch(show(OVERLAYS.ENABLE_IDP, id)) success(t('edit.success')) + if (data.identityProviderTypeId === IDPCategory.MANAGED) { + await enableIdp({ + id, + enabled: true, + }).unwrap() + success(t('enable.success')) + dispatch(closeOverlay()) + } else { + dispatch(show(OVERLAYS.ENABLE_IDP, id)) + } } catch (err) { error(t('edit.error'), '', err as object) } @@ -82,6 +96,14 @@ export const UpdateIDP = ({ id }: { id: string }) => { }, ] + const steps = + data?.identityProviderTypeId === IDPCategory.MANAGED + ? UpdateStepsList.slice(0, 2) + : UpdateStepsList + + const numberSteps = + data?.identityProviderTypeId === IDPCategory.MANAGED ? 2 : 3 + return ( <> { />
- +
@@ -134,7 +156,9 @@ export const UpdateIDP = ({ id }: { id: string }) => { label="" loading loadIndicator={t('action.loading')} - onButtonClick={() => {}} + onButtonClick={() => { + // do nothing + }} sx={{ marginLeft: '10px' }} /> ) : ( diff --git a/src/components/pages/Admin/components/RegistrationRequests/CompanyDetailOverlay/index.tsx b/src/components/pages/Admin/components/RegistrationRequests/CompanyDetailOverlay/index.tsx index 72e2247f0..2a51e96ec 100644 --- a/src/components/pages/Admin/components/RegistrationRequests/CompanyDetailOverlay/index.tsx +++ b/src/components/pages/Admin/components/RegistrationRequests/CompanyDetailOverlay/index.tsx @@ -72,18 +72,15 @@ const CompanyDetailOverlay = ({ const { data } = useFetchCompanySearchQuery({ page: 0, args: { - expr: selectedCompany && selectedCompany.name, + expr: selectedCompany?.name, }, }) useEffect(() => { if (data) { - const selected = - data && - data.content && - data.content.filter( - (company: { bpn: string }) => selectedCompany.bpn === company.bpn - ) + const selected = data?.content?.filter( + (company: { bpn: string }) => selectedCompany.bpn === company.bpn + ) setCompany(selected[0]) } }, [data, selectedCompany]) @@ -135,7 +132,7 @@ const CompanyDetailOverlay = ({ newValue: number ) => { setHeight( - modalElement && modalElement.current + modalElement?.current ? `${modalElement?.current?.clientHeight}px` : '400px' ) @@ -250,18 +247,17 @@ const CompanyDetailOverlay = ({ value: selectedCompany?.bpn, }} /> - {selectedCompany.uniqueIds && - selectedCompany.uniqueIds.map( - (id: { type: string; value: string }) => ( - - ) - )} + {selectedCompany?.uniqueIds?.map( + (id: { type: string; value: string }) => ( + + ) + )} diff --git a/src/components/pages/Admin/components/RegistrationRequests/components/CheckList/index.tsx b/src/components/pages/Admin/components/RegistrationRequests/components/CheckList/index.tsx index b82522d64..5118b038b 100644 --- a/src/components/pages/Admin/components/RegistrationRequests/components/CheckList/index.tsx +++ b/src/components/pages/Admin/components/RegistrationRequests/components/CheckList/index.tsx @@ -188,7 +188,9 @@ export default function CheckList({ { - onButtonClick && onButtonClick(button) + if (onButtonClick) { + onButtonClick(button) + } }} variant="filled" icon={button?.icon} diff --git a/src/components/pages/Admin/components/RegistrationRequests/registrationTableColumns.tsx b/src/components/pages/Admin/components/RegistrationRequests/registrationTableColumns.tsx index 5909de809..87090991a 100644 --- a/src/components/pages/Admin/components/RegistrationRequests/registrationTableColumns.tsx +++ b/src/components/pages/Admin/components/RegistrationRequests/registrationTableColumns.tsx @@ -79,7 +79,12 @@ export const RegistrationRequestsTableColumns = ( paddingTop: '2px', }} onClick={() => { - showConfirmOverlay && showConfirmOverlay(row.applicationId) + if (showConfirmOverlay) { + showConfirmOverlay(row.applicationId) + } + }} + onKeyDown={() => { + // do nothing }} > @@ -156,7 +161,9 @@ export const RegistrationRequestsTableColumns = ( 'content.admin.registration-requests.buttonprogress' ), type: 'progress', - onClick: () => {}, + onClick: () => { + // do nothing + }, withIcon: true, }} /> @@ -204,12 +211,14 @@ export const RegistrationRequestsTableColumns = ( cancelText={t('content.admin.registration-requests.cancel')} alignRow="center" onButtonClick={(button) => { - onChipButtonSelect && + if (onChipButtonSelect) { onChipButtonSelect(button, row.applicationId) + } }} onCancel={() => { - onConfirmationCancel && + if (onConfirmationCancel) { onConfirmationCancel(row.applicationId, row.companyName) + } }} /> ) : null} diff --git a/src/components/pages/AdminBoardDetail/components/BoardPrivacy/index.tsx b/src/components/pages/AdminBoardDetail/components/BoardPrivacy/index.tsx index 6d02879d9..b2c5810b9 100644 --- a/src/components/pages/AdminBoardDetail/components/BoardPrivacy/index.tsx +++ b/src/components/pages/AdminBoardDetail/components/BoardPrivacy/index.tsx @@ -54,7 +54,7 @@ export default function BoardPrivacy({ item }: { item: AppDetails }) { {t('heading')} {t('message')}
- {item.privacyPolicies && item.privacyPolicies.length ? ( + {item?.privacyPolicies?.length ? (
{item.privacyPolicies.map((policy: PrivacyPolicyType) => (
-
+
{item.longDescription}
-