diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a98bf468..d3a7711d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ - App Overview - Enhance Sub Menu by adding 'Change Documents' for active apps - Add roles for active apps +- My Organization + - Unsubscribe subscription flow - Service Subscription - UI Changes - Add filters diff --git a/src/assets/locales/de/main.json b/src/assets/locales/de/main.json index 64cc49c3d..57a138de4 100644 --- a/src/assets/locales/de/main.json +++ b/src/assets/locales/de/main.json @@ -372,6 +372,19 @@ "subscriptions": { "title": "App-Abonnements", "error": "Fehler - keine Daten gefunden" + }, + "unsubscribe": { + "title": "Geschäftsanwendung Deaktivieren", + "description": "Möchten Sie sich die Geschäftsanwendung wirklich deaktivieren?", + "note": "Bitte beachten Sie: Alle möglicherweise verbundenen Objekte (siehe Details oben) werden ebenfalls deaktiviert. Die Abmeldung ist nicht rückgängig zu machen.", + "table": { + "app": "App", + "status": "Status", + "connector": "Connector", + "techUser": "Technischer User" + }, + "checkBoxLabel": "Ja, ich stimme zu, dass ich die Auswirkungen der Abmeldung verstanden habe und mit dem Vorgang fortfahren möchte.", + "buttonText": "Abbestellen" } }, "datamanagement": { @@ -1739,7 +1752,8 @@ "error": "Error", "retry": "Retry", "exit": "exit", - "loading": "Loading..." + "loading": "Loading...", + "unsubscribe": "Unsubscribe" }, "state": { "enabled": "aktiv", diff --git a/src/assets/locales/en/main.json b/src/assets/locales/en/main.json index f9a9ae512..38b9d32ba 100644 --- a/src/assets/locales/en/main.json +++ b/src/assets/locales/en/main.json @@ -371,6 +371,21 @@ "subscriptions": { "title": "App Subscriptions", "error": "error - no data found" + }, + "unsubscribe": { + "title": "Unsubscribe Business Application", + "description": "Are you sure to unsubscribe from the business application?", + "note": "Please Note: all possibly connected objects (see details above) will get deactivated as well.The unsubscription is not revertible.", + "table": { + "app": "App", + "status": "Status", + "connector": "Connector", + "techUser": "Technical User" + }, + "checkBoxLabel": "Yes I agree that I understood the unsubscribe impact and want to proceed with the process.", + "buttonText": "Unsubscribe", + "unsubscribeSuccess": "Unsubscribe subscription is successful", + "unsubscribeError": "Something went wrong. Try again after sometime or contact administrator" } }, "datamanagement": { @@ -1702,7 +1717,8 @@ "error": "Error", "retry": "Retry", "exit": "exit", - "loading": "Loading..." + "loading": "Loading...", + "unsubscribe": "Unsubscribe" }, "state": { "enabled": "enabled", diff --git a/src/components/pages/Organization/AppSubscriptions.tsx b/src/components/pages/Organization/AppSubscriptions.tsx index 2bf883246..af8370a87 100644 --- a/src/components/pages/Organization/AppSubscriptions.tsx +++ b/src/components/pages/Organization/AppSubscriptions.tsx @@ -19,8 +19,10 @@ ********************************************************************************/ import { SubscriptionStatus, ImageType } from 'features/apps/apiSlice' -import { Image, LogoGrayData } from '@catena-x/portal-shared-components' +import { Button, Image, LogoGrayData } from '@catena-x/portal-shared-components' import { fetchImageWithToken } from 'services/ImageService' +import './Organization.scss' +import { useTranslation } from 'react-i18next' export default function AppSubscriptions({ name, @@ -28,13 +30,16 @@ export default function AppSubscriptions({ status, image, onButtonClick = () => {}, + onUnsubscribe = () => {}, }: { name: string provider: string status: SubscriptionStatus | undefined image: ImageType | undefined onButtonClick?: React.MouseEventHandler + onUnsubscribe?: React.MouseEventHandler }) { + const { t } = useTranslation() const colorCode = [ { name: SubscriptionStatus.PENDING, code: ' #969696' }, { name: SubscriptionStatus.INACTIVE, code: 'red' }, @@ -47,27 +52,37 @@ export default function AppSubscriptions({ return (
- +
+ - - {name} - by {provider} - - - {status ? ( - - {' '} - {'\u00a0' + capitalizeFirstLetter(status.toLocaleLowerCase())} - - ) : null} +
+ {name} - by {provider} - + {status ? ( + + {' '} + {'\u00a0' + capitalizeFirstLetter(status.toLocaleLowerCase())} + + ) : null} +
+
+ +
) } diff --git a/src/components/pages/Organization/Organization.scss b/src/components/pages/Organization/Organization.scss index 6e93d0b46..e0de8586d 100644 --- a/src/components/pages/Organization/Organization.scss +++ b/src/components/pages/Organization/Organization.scss @@ -46,9 +46,57 @@ cursor: pointer; display: flex; align-items: center; + justify-content: space-between; + padding: 10px 15px; + border-bottom: 1px solid rgb(220, 220, 220); } .organization-error { padding: 10px; } } + .iconNameContainer { + display: flex; + } + .title { + font-size: 16px !important; + font-weight: 700 !important; + background-color: rgb(237, 240, 244) !important; + padding: 15px !important; + margin-bottom: 20px !important; + font-family: inherit !important; + } + + .unsubButton { + height: 40px; + border-color: #fb6540 !important; + background-color: #fb6540 !important; + color: #ffffff !important; + text-transform: lowercase !important; + } +} + +@media (max-width: 480px) { + .name { + max-width: 130px; + } + .unsubButton { + width: auto; + height: 30px; + font-size: 8px !important; + margin-left: 10px !important; + } +} + +.noteBox { + margin: 0px 50px; + border: 1px solid #e3e3e3; + background: #e3e3e3; +} + +.noteText { + padding: 20px; +} + +.detailsTable { + margin: 50px 50px; } diff --git a/src/components/pages/Organization/UnSubscribeOverlay.tsx b/src/components/pages/Organization/UnSubscribeOverlay.tsx new file mode 100644 index 000000000..f2b2de26f --- /dev/null +++ b/src/components/pages/Organization/UnSubscribeOverlay.tsx @@ -0,0 +1,146 @@ +/******************************************************************************** + * 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 React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + Dialog, + DialogContent, + Button, + DialogActions, + DialogHeader, + Typography, + Checkbox, + StaticTable, + LoadingButton, +} from '@catena-x/portal-shared-components' +import Box from '@mui/material/Box' +import { useFetchSubscriptionAppQuery } from 'features/apps/apiSlice' +import './Organization.scss' + +interface UnSubscribeOverlayProps { + openDialog?: boolean + handleOverlayClose: React.MouseEventHandler + handleConfirmClick: React.MouseEventHandler + loading?: boolean + subscriptionId: string + appId: string +} + +const UnSubscribeOverlay = ({ + openDialog = false, + handleOverlayClose, + handleConfirmClick, + loading, + subscriptionId, + appId, +}: UnSubscribeOverlayProps) => { + const { t } = useTranslation() + const { data } = useFetchSubscriptionAppQuery({ appId, subscriptionId }) + const [checkBoxSelected, setCheckBoxSelected] = useState(false) + return ( +
+ + + + {t('content.organization.unsubscribe.description')} + + + + {t('content.organization.unsubscribe.note')} + + + + + + + setCheckBoxSelected(!checkBoxSelected)} + /> + + + + + {!loading && ( + + )} + {loading && ( + {}} + sx={{ marginLeft: '10px', textTransform: 'none' }} + /> + )} + + +
+ ) +} + +export default UnSubscribeOverlay diff --git a/src/components/pages/Organization/index.tsx b/src/components/pages/Organization/index.tsx index 301472dc1..43545a8a8 100644 --- a/src/components/pages/Organization/index.tsx +++ b/src/components/pages/Organization/index.tsx @@ -18,28 +18,30 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { - PageHeader, - StaticTable, - TableType, -} from '@catena-x/portal-shared-components' +import { PageHeader, Typography } from '@catena-x/portal-shared-components' import { useTranslation } from 'react-i18next' import AppSubscriptions from './AppSubscriptions' import { useDispatch } from 'react-redux' import { show } from 'features/control/overlay' import './Organization.scss' import { OVERLAYS } from 'types/Constants' -import { useFetchSubscribedActiveAppsQuery } from 'features/apps/apiSlice' +import { + useFetchSubscribedActiveAppsQuery, + useUnsubscribeAppMutation, +} from 'features/apps/apiSlice' import { subscribedApps } from 'features/apps/mapper' import { useFetchOwnCompanyDetailsQuery } from 'features/admin/userApiSlice' import LoadingError from './LoadingError' import { CompanyDetailsToCards } from 'features/admin/mapper' import { UserDetailCard } from 'components/shared/basic/UserDetailInfo/UserDetailCard' +import { useState } from 'react' +import UnSubscribeOverlay from './UnSubscribeOverlay' +import { error, success } from 'services/NotifyService' export default function Organization() { const { t } = useTranslation() const dispatch = useDispatch() - const { data } = useFetchSubscribedActiveAppsQuery() + const { data, refetch } = useFetchSubscribedActiveAppsQuery() const appSubscribedData = data && subscribedApps(data) const { data: companyDetails, @@ -48,31 +50,44 @@ export default function Organization() { } = useFetchOwnCompanyDetailsQuery() const companyDetailsData = companyDetails && CompanyDetailsToCards(companyDetails) + const [subscriptionId, setSubscriptionId] = useState('') + const [appId, setAppId] = useState('') + const [showUnsubscribeOverlay, setShowUnsubscribeOverlay] = + useState(false) + const [loading, setLoading] = useState(false) + const [unsubscribeMutation] = useUnsubscribeAppMutation() const handleClick = (id: string | undefined) => { dispatch(show(OVERLAYS.APP, id, t('content.organization.company.title'))) } - const appSubscriptionsTableBody = - appSubscribedData?.map((app) => [ - () => ( - handleClick(app.offerId)} - name={app.name || ''} - provider={app.provider} - status={app.status} - /> - ), - ]) || [] - - const appSubscriptionsTableData: TableType = { - head: [t('content.organization.subscriptions.title')], - body: appSubscriptionsTableBody, + const onUnsubscribe = async () => { + setLoading(true) + await unsubscribeMutation(subscriptionId) + .unwrap() + .then(() => { + success(t('content.organization.unsubscribe.unsubscribeSuccess')) + }) + .catch(() => { + error(t('content.organization.unsubscribe.unsubscribeError')) + }) + setLoading(false) + setShowUnsubscribeOverlay(false) + refetch() } return (
+ {showUnsubscribeOverlay && ( + setShowUnsubscribeOverlay(false)} + handleConfirmClick={() => onUnsubscribe()} + loading={loading} + appId={appId} + subscriptionId={subscriptionId} + /> + )} + {appSubscribedData?.length !== 0 && ( + + {t('content.organization.subscriptions.title')} + + )}
- + {appSubscribedData?.map((app) => ( + handleClick(app.offerId)} + name={app.name || ''} + provider={app.provider} + status={app.status} + onUnsubscribe={(e) => { + setShowUnsubscribeOverlay(true) + setAppId(app.offerId) + setSubscriptionId(app.subscriptionId) + e.stopPropagation() + }} + /> + ))}
diff --git a/src/components/shared/templates/Subscription/Subscription.scss b/src/components/shared/templates/Subscription/Subscription.scss index 5a50be06e..30a6ad909 100644 --- a/src/components/shared/templates/Subscription/Subscription.scss +++ b/src/components/shared/templates/Subscription/Subscription.scss @@ -177,11 +177,17 @@ .firstSection { width: 30%; font-weight: 600; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .secondSection { width: 35%; font-weight: 600; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .viewDetails { diff --git a/src/features/apps/apiSlice.ts b/src/features/apps/apiSlice.ts index e241b0c99..e8ca45757 100644 --- a/src/features/apps/apiSlice.ts +++ b/src/features/apps/apiSlice.ts @@ -185,6 +185,35 @@ export interface ActiveSubscription { name: string provider: string image: string + subscriptionId: string +} + +export interface SubscribeTechnicalUserData { + id: string + name: string + permissions: Array +} + +export interface SubscribeConnectorData { + id: string + name: string + endpoint: string +} + +export interface ActiveSubscriptionDetails { + offerId: string + name: string + provider: string + image: string + subscriptionId: string + offerSubscriptionStatus: string + technicalUserData: SubscribeTechnicalUserData[] + connectorData: SubscribeConnectorData[] +} + +interface FetchSubscriptionAppQueryType { + subscriptionId: string + appId: string } export const apiSlice = createApi({ @@ -281,6 +310,19 @@ export const apiSlice = createApi({ fetchSubscribedActiveApps: builder.query({ query: () => '/api/apps/subscribed/activesubscriptions', }), + fetchSubscriptionApp: builder.query< + ActiveSubscriptionDetails, + FetchSubscriptionAppQueryType + >({ + query: (obj) => + `/api/apps/${obj.appId}/subscription/${obj.subscriptionId}/subscriber`, + }), + unsubscribeApp: builder.mutation({ + query: (subscriptionId) => ({ + url: `/api/apps/${subscriptionId}/unsubscribe`, + method: 'PUT', + }), + }), }), }) @@ -296,4 +338,6 @@ export const { useFetchAgreementsQuery, useDeactivateAppMutation, useFetchSubscribedActiveAppsQuery, + useFetchSubscriptionAppQuery, + useUnsubscribeAppMutation, } = apiSlice