diff --git a/CHANGELOG.md b/CHANGELOG.md index 818ab2c0d..26f17a786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## unreleased 1.8.0-RC4 +- App Marketplace + - reduce image flickering on user interaction - Service overview - Add missing translations, fix duplicate error - Service Release Process diff --git a/src/assets/locales/de/main.json b/src/assets/locales/de/main.json index 19cf5fe9e..685ff3ea2 100644 --- a/src/assets/locales/de/main.json +++ b/src/assets/locales/de/main.json @@ -300,7 +300,7 @@ "modularproduction": "Modular Production" }, "noMatch": "Keine Treffer", - "for": "für" + "for": "zu Ihrem Suchausdruck" } }, "semantichub": { diff --git a/src/assets/locales/en/main.json b/src/assets/locales/en/main.json index 795a00022..64b40df77 100644 --- a/src/assets/locales/en/main.json +++ b/src/assets/locales/en/main.json @@ -298,8 +298,8 @@ "real-timecontrol": "Real Time Control", "modularproduction": "Modular Production" }, - "noMatch": "No Matches", - "for": "for" + "noMatch": "No Match", + "for": "for your search term" } }, "semantichub": { diff --git a/src/components/overlays/AppMarketplaceRequest/index.tsx b/src/components/overlays/AppMarketplaceRequest/index.tsx index 622ba76d7..e24e56eaa 100644 --- a/src/components/overlays/AppMarketplaceRequest/index.tsx +++ b/src/components/overlays/AppMarketplaceRequest/index.tsx @@ -31,7 +31,6 @@ import { useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' import { useState } from 'react' import { - type AgreementRequest, useAddSubscribeAppMutation, useFetchAgreementsQuery, useFetchAppDetailsQuery, @@ -41,6 +40,7 @@ import { closeOverlay } from 'features/control/overlay' import './AppMarketplaceRequest.scss' import { error } from 'services/NotifyService' import { AgreementStatus } from '../UpdateCompanyRole' +import { type AgreementRequest } from 'features/apps/types' export default function AppMarketplaceRequest({ id }: { id: string }) { const { t } = useTranslation() diff --git a/src/components/pages/AdminBoardDetail/BoardContentDetails.tsx b/src/components/pages/AdminBoardDetail/BoardContentDetails.tsx index 23bee3189..2b2ff37f0 100644 --- a/src/components/pages/AdminBoardDetail/BoardContentDetails.tsx +++ b/src/components/pages/AdminBoardDetail/BoardContentDetails.tsx @@ -28,7 +28,7 @@ import { import BoardHeader from './components/BoardHeader' import BoardDocuments from './components/BoardDocuments' import BoardProvider from './components/BoardProvider' -import { type AppDetails, DocumentTypeText } from 'features/apps/apiSlice' +import { type AppDetails, DocumentTypeText } from 'features/apps/types' import CommonService from 'services/CommonService' import BoardPrivacy from './components/BoardPrivacy' import BoardRoles from './components/BoardRoles' diff --git a/src/components/pages/AdminBoardDetail/components/BoardConnectedData/index.tsx b/src/components/pages/AdminBoardDetail/components/BoardConnectedData/index.tsx index f397ed871..3bc7666ab 100644 --- a/src/components/pages/AdminBoardDetail/components/BoardConnectedData/index.tsx +++ b/src/components/pages/AdminBoardDetail/components/BoardConnectedData/index.tsx @@ -24,7 +24,7 @@ import { StaticTable, type TableType, } from '@catena-x/portal-shared-components' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' import './BoardConnectedData.scss' export default function BoardConnectedData({ item }: { item: AppDetails }) { diff --git a/src/components/pages/AdminBoardDetail/components/BoardDocuments/index.tsx b/src/components/pages/AdminBoardDetail/components/BoardDocuments/index.tsx index dec1c7881..edc4bbbdf 100644 --- a/src/components/pages/AdminBoardDetail/components/BoardDocuments/index.tsx +++ b/src/components/pages/AdminBoardDetail/components/BoardDocuments/index.tsx @@ -26,8 +26,8 @@ import { type Documents, type DocumentData, DocumentTypeText, - useFetchDocumentByIdMutation, -} from 'features/apps/apiSlice' +} from 'features/apps/types' +import { useFetchDocumentByIdMutation } from 'features/apps/apiSlice' import { download } from 'utils/downloadUtils' import { DocumentTypeId } from 'features/appManagement/apiSlice' diff --git a/src/components/pages/AdminBoardDetail/components/BoardPrivacy/index.tsx b/src/components/pages/AdminBoardDetail/components/BoardPrivacy/index.tsx index b2c5810b9..3755af93b 100644 --- a/src/components/pages/AdminBoardDetail/components/BoardPrivacy/index.tsx +++ b/src/components/pages/AdminBoardDetail/components/BoardPrivacy/index.tsx @@ -22,7 +22,7 @@ import { useTranslation } from 'react-i18next' import { Typography } from '@catena-x/portal-shared-components' import { uniqueId } from 'lodash' import { Apartment, Person, LocationOn, Web, Info } from '@mui/icons-material' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' import './BoardPrivacy.scss' import { PrivacyPolicyType } from 'features/adminBoard/adminBoardApiSlice' diff --git a/src/components/pages/AdminBoardDetail/components/BoardProvider/index.tsx b/src/components/pages/AdminBoardDetail/components/BoardProvider/index.tsx index bdfefc0fc..667769f38 100644 --- a/src/components/pages/AdminBoardDetail/components/BoardProvider/index.tsx +++ b/src/components/pages/AdminBoardDetail/components/BoardProvider/index.tsx @@ -24,7 +24,7 @@ import { StaticTable, type TableType, } from '@catena-x/portal-shared-components' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' import './BoardProvider.scss' import { Box } from '@mui/material' diff --git a/src/components/pages/AdminBoardDetail/components/BoardRoles/index.tsx b/src/components/pages/AdminBoardDetail/components/BoardRoles/index.tsx index 186b849ff..070b47ab3 100644 --- a/src/components/pages/AdminBoardDetail/components/BoardRoles/index.tsx +++ b/src/components/pages/AdminBoardDetail/components/BoardRoles/index.tsx @@ -22,7 +22,7 @@ import { useTranslation } from 'react-i18next' import { Typography } from '@catena-x/portal-shared-components' import { Grid } from '@mui/material' import { uniqueId } from 'lodash' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' export default function BoardRoles({ item }: { item: AppDetails }) { const { t } = useTranslation() diff --git a/src/components/pages/AdminBoardDetail/components/BoardTechnicalUserSetup/index.tsx b/src/components/pages/AdminBoardDetail/components/BoardTechnicalUserSetup/index.tsx index f16fff613..3baddf1db 100644 --- a/src/components/pages/AdminBoardDetail/components/BoardTechnicalUserSetup/index.tsx +++ b/src/components/pages/AdminBoardDetail/components/BoardTechnicalUserSetup/index.tsx @@ -21,7 +21,7 @@ import { useTranslation } from 'react-i18next' import { Typography } from '@catena-x/portal-shared-components' import { Grid } from '@mui/material' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' export default function BoardTechnicalUserSetup({ item, diff --git a/src/components/pages/AdminCredential/AdminCredentialElements.tsx b/src/components/pages/AdminCredential/AdminCredentialElements.tsx index 2e9b0a0ab..6e39c0c21 100644 --- a/src/components/pages/AdminCredential/AdminCredentialElements.tsx +++ b/src/components/pages/AdminCredential/AdminCredentialElements.tsx @@ -33,7 +33,7 @@ import { download } from 'utils/downloadUtils' import { useFetchNewDocumentByIdMutation } from 'features/appManagement/apiSlice' import { error, success } from 'services/NotifyService' import { uniqueId } from 'lodash' -import { SubscriptionStatus } from 'features/apps/apiSlice' +import { SubscriptionStatus } from 'features/apps/types' import { setSearchInput } from 'features/appManagement/actions' import { useDispatch } from 'react-redux' import { diff --git a/src/components/pages/AppDetail/AppDetailContentDetails.tsx b/src/components/pages/AppDetail/AppDetailContentDetails.tsx index 6a22bc391..6c228b629 100644 --- a/src/components/pages/AppDetail/AppDetailContentDetails.tsx +++ b/src/components/pages/AppDetail/AppDetailContentDetails.tsx @@ -31,7 +31,7 @@ import AppDetailPrivacy from './components/AppDetailPrivacy' import AppDetailDocuments from './components/AppDetailDocuments' import AppDetailProvider from './components/AppDetailProvider' import AppDetailTags from './components/AppDetailTags' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' import './AppDetail.scss' import CommonService from 'services/CommonService' import AppDetailTechUserSetup from './components/AppDetailTechUserSetup' diff --git a/src/components/pages/AppDetail/components/AppDetailDocuments/index.tsx b/src/components/pages/AppDetail/components/AppDetailDocuments/index.tsx index b8de91f58..d25bf7fd7 100644 --- a/src/components/pages/AppDetail/components/AppDetailDocuments/index.tsx +++ b/src/components/pages/AppDetail/components/AppDetailDocuments/index.tsx @@ -22,11 +22,8 @@ import { useTranslation } from 'react-i18next' import { Typography } from '@catena-x/portal-shared-components' import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined' import 'components/styles/document.scss' -import { - type AppDetails, - type Documents, - useFetchDocumentByIdMutation, -} from 'features/apps/apiSlice' +import { type AppDetails, type Documents } from 'features/apps/types' +import { useFetchDocumentByIdMutation } from 'features/apps/apiSlice' import { download } from 'utils/downloadUtils' import '../../AppDetail.scss' import { DocumentTypeId } from 'features/appManagement/apiSlice' diff --git a/src/components/pages/AppDetail/components/AppDetailHeader/index.tsx b/src/components/pages/AppDetail/components/AppDetailHeader/index.tsx index 368ecc789..0db4db8a9 100644 --- a/src/components/pages/AppDetail/components/AppDetailHeader/index.tsx +++ b/src/components/pages/AppDetail/components/AppDetailHeader/index.tsx @@ -32,10 +32,8 @@ import { OVERLAYS } from 'types/Constants' import { show } from 'features/control/overlay' import { useParams } from 'react-router-dom' import { useEffect, useState } from 'react' -import { - SubscriptionStatus, - useFetchDocumentByIdMutation, -} from 'features/apps/apiSlice' +import { SubscriptionStatus } from 'features/apps/types' +import { useFetchDocumentByIdMutation } from 'features/apps/apiSlice' import CommonService from 'services/CommonService' import type { UseCaseType } from 'features/appManagement/types' diff --git a/src/components/pages/AppDetail/components/AppDetailPrivacy/index.tsx b/src/components/pages/AppDetail/components/AppDetailPrivacy/index.tsx index 993d56ff1..c42d9b1f5 100644 --- a/src/components/pages/AppDetail/components/AppDetailPrivacy/index.tsx +++ b/src/components/pages/AppDetail/components/AppDetailPrivacy/index.tsx @@ -23,7 +23,7 @@ import { Typography } from '@catena-x/portal-shared-components' import { uniqueId } from 'lodash' import { Apartment, Person, LocationOn, Web, Info } from '@mui/icons-material' import './AppDetailPrivacy.scss' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' import { PrivacyPolicyType } from 'features/adminBoard/adminBoardApiSlice' import '../../AppDetail.scss' diff --git a/src/components/pages/AppDetail/components/AppDetailProvider/index.tsx b/src/components/pages/AppDetail/components/AppDetailProvider/index.tsx index 717937ce0..7aac201f5 100644 --- a/src/components/pages/AppDetail/components/AppDetailProvider/index.tsx +++ b/src/components/pages/AppDetail/components/AppDetailProvider/index.tsx @@ -24,7 +24,7 @@ import { StaticTable, type TableType, } from '@catena-x/portal-shared-components' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' import '../../AppDetail.scss' export default function AppDetailProvider({ item }: { item: AppDetails }) { diff --git a/src/components/pages/AppDetail/components/AppDetailTags/index.tsx b/src/components/pages/AppDetail/components/AppDetailTags/index.tsx index 5d0db52a9..0511a7556 100644 --- a/src/components/pages/AppDetail/components/AppDetailTags/index.tsx +++ b/src/components/pages/AppDetail/components/AppDetailTags/index.tsx @@ -19,7 +19,7 @@ ********************************************************************************/ import { Chip, Typography } from '@catena-x/portal-shared-components' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' import './AppDetailTags.scss' import '../../AppDetail.scss' import { useTranslation } from 'react-i18next' diff --git a/src/components/pages/AppDetail/components/AppDetailTechUserSetup/index.tsx b/src/components/pages/AppDetail/components/AppDetailTechUserSetup/index.tsx index e56ad1dfe..1ffc42c98 100644 --- a/src/components/pages/AppDetail/components/AppDetailTechUserSetup/index.tsx +++ b/src/components/pages/AppDetail/components/AppDetailTechUserSetup/index.tsx @@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next' import { Typography } from '@catena-x/portal-shared-components' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' import { Grid } from '@mui/material' import '../../AppDetail.scss' diff --git a/src/components/pages/AppMarketplace/components/AppListGroup/index.tsx b/src/components/pages/AppMarketplace/components/AppListGroup/index.tsx index bd09af9e3..d35296f19 100644 --- a/src/components/pages/AppMarketplace/components/AppListGroup/index.tsx +++ b/src/components/pages/AppMarketplace/components/AppListGroup/index.tsx @@ -1,6 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -22,14 +21,14 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Cards, CategoryDivider } from '@catena-x/portal-shared-components' import { fetchImageWithToken } from 'services/ImageService' -import type { AppItem } from 'features/apps/apiSlice' +import type { AppMarketplaceCard } from 'features/apps/types' export const AppListGroup = ({ category, items, }: { category: string - items: AppItem[] + items: AppMarketplaceCard[] }) => { const { t } = useTranslation() const [itemsShown, setItemsShown] = useState('4') diff --git a/src/components/pages/AppMarketplace/components/AppListGroupView/index.tsx b/src/components/pages/AppMarketplace/components/AppListGroupView/index.tsx index bfdca5d52..a4e99c4f9 100644 --- a/src/components/pages/AppMarketplace/components/AppListGroupView/index.tsx +++ b/src/components/pages/AppMarketplace/components/AppListGroupView/index.tsx @@ -1,6 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -26,14 +25,13 @@ import { useTranslation } from 'react-i18next' import { AppListGroup } from '../AppListGroup' import NoItems from 'components/pages/NoItems' import { fetchImageWithToken } from 'services/ImageService' +import { AppGroup, type AppMarketplaceCard } from 'features/apps/types' export const AppListGroupView = ({ items, groupKey, }: { - // Add an ESLint exception until there is a solution - // eslint-disable-next-line - items: any[] + items: Array groupKey: string }) => { const { t } = useTranslation() @@ -42,7 +40,7 @@ export const AppListGroupView = ({ return } - if (!groupKey || groupKey === '') { + if (!groupKey || groupKey === AppGroup.ALL) { return ( item[groupKey]) + const group = multiMapBy( + items, + // Note: any here is necessary because the UseCaseType seems to be defined + // incorrectly as Object with id and label while the api returns an array of strings. + // Fix separately. + // eslint-disable-next-line + (item) => (item as Record)[groupKey] + ) return ( <> diff --git a/src/components/pages/AppMarketplace/components/AppListSection/index.tsx b/src/components/pages/AppMarketplace/components/AppListSection/index.tsx index 991e0d172..6402b375f 100644 --- a/src/components/pages/AppMarketplace/components/AppListSection/index.tsx +++ b/src/components/pages/AppMarketplace/components/AppListSection/index.tsx @@ -1,6 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -18,126 +17,74 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' -import { - type CardItems, - SearchInput, - Typography, - ViewSelector, -} from '@catena-x/portal-shared-components' -import { useTheme, CircularProgress, Box } from '@mui/material' +import { Typography } from '@catena-x/portal-shared-components' +import { useTheme, CircularProgress } from '@mui/material' import { AppListGroupView } from '../AppListGroupView' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' -import PageService from 'services/PageService' -import { itemsSelector } from 'features/apps/favorites/slice' +import { addItem, removeItem } from 'features/apps/favorites/actions' import { - addItem, - fetchItems, - removeItem, -} from 'features/apps/favorites/actions' -import { useFetchActiveAppsQuery } from 'features/apps/apiSlice' -import debounce from 'lodash.debounce' + useFetchActiveAppsQuery, + useFetchFavoriteAppsQuery, +} from 'features/apps/apiSlice' import CommonService from 'services/CommonService' import type { AppDispatch } from 'features/store' +import { appsControlSelector } from 'features/apps/control' export const label = 'AppList' export default function AppListSection() { const { t } = useTranslation() const theme = useTheme() - const [group, setGroup] = useState('') - const [filterExpr, setFilterExpr] = useState('') - const [cardsData, setCardsData] = useState([]) const dispatch = useDispatch() const navigate = useNavigate() const { data } = useFetchActiveAppsQuery() - // Add an ESLint exception until there is a solution - // eslint-disable-next-line - const [cards, setCards] = useState([]) - const favoriteItems = useSelector(itemsSelector) - const reference = PageService.registerReference(label, useRef(null)) - - useEffect(() => { - dispatch(fetchItems()) - }, [dispatch]) + const favoriteItems = useFetchFavoriteAppsQuery().data + const control = useSelector(appsControlSelector) - useEffect(() => { - setCardsData(cards) - }, [cards]) - - useEffect(() => { - setCards(data?.map(CommonService.appToCard)) - }, [data, setCards, CommonService.appToCard]) - - const setView = (e: React.MouseEvent) => { - setGroup(e.currentTarget.value) - } - - const checkIsFavorite = (appId: string) => favoriteItems.includes(appId) + const checkIsFavorite = (appId: string) => favoriteItems?.includes(appId) const addOrRemoveFavorite = (event: React.MouseEvent, appId: string) => { event?.stopPropagation() dispatch(checkIsFavorite(appId) ? removeItem(appId) : addItem(appId)) } - const debouncedFilter = useMemo( - () => - debounce((expr: string) => { - setCardsData( - expr && cards?.length > 0 - ? cards.filter( - (card: { title: string; subtitle: string }) => - card.title.toLowerCase().includes(expr.toLowerCase()) || - card?.subtitle?.toLowerCase().includes(expr.toLowerCase()) - ) - : cards - ) - }, 300), - [cards] + const renderProgress = () => ( +
+ +
) - const doFilter = useCallback( - (expr: string) => { - setFilterExpr(expr) - debouncedFilter(expr) - }, - [debouncedFilter] + const renderNoMatch = () => ( +
+ + {t('content.appstore.appOverviewSection.noMatch')} + + + {t('content.appstore.appOverviewSection.for')} + +
) - const categoryViews = [ - { - buttonText: t('content.appstore.appOverviewSection.categoryViews.all'), - buttonValue: '', - onButtonClick: setView, - }, - { - buttonText: t( - 'content.appstore.appOverviewSection.categoryViews.useCases' - ), - buttonValue: 'useCases', - onButtonClick: setView, - }, - ] - - const renderList = () => { - if (filterExpr && cardsData && !cardsData.length) { - return ( -
- - {t('content.appstore.appOverviewSection.noMatch')} - - - {t('content.appstore.appOverviewSection.for') + ' ' + filterExpr} - -
- ) - } else if (cardsData?.length) { - return ( - ({ + const renderGroups = () => + data ? ( + + app?.name?.toLowerCase().includes(control.search.toLowerCase()) ?? + app?.title?.toLowerCase().includes(control.search.toLowerCase()) + ) + .map(CommonService.appToCard) + .map((card) => ({ ...card, onButtonClick: () => { navigate(`/appdetail/${card.id}`) @@ -147,49 +94,17 @@ export default function AppListSection() { }, addButtonClicked: checkIsFavorite(card.id!), }))} - groupKey={group} - /> - ) - } else { - return ( -
- -
- ) - } - } - - return ( - -
- - {t('content.appstore.appOverviewSection.title')} - + groupKey={control.group} + /> + ) : ( + <> + ) - + const renderList = () => { + if (!data) return renderProgress() + if (!data.length) return renderNoMatch() + return renderGroups() + } - - { - doFilter(e.target.value) - }} - /> - - {renderList()} -
-
- ) + return
{renderList()}
} diff --git a/src/features/apps/apiSliceTest.ts b/src/components/pages/AppMarketplace/components/HeaderSection/index.tsx similarity index 56% rename from src/features/apps/apiSliceTest.ts rename to src/components/pages/AppMarketplace/components/HeaderSection/index.tsx index 56d07b071..9b66ef7aa 100644 --- a/src/features/apps/apiSliceTest.ts +++ b/src/components/pages/AppMarketplace/components/HeaderSection/index.tsx @@ -1,6 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -18,20 +17,21 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' -import { getAssetBase } from 'services/EnvironmentService' -import type { AppMarketplaceApp } from './apiSlice' +import { Typography } from '@mui/material' +import { useTranslation } from 'react-i18next' -export const apiSlice = createApi({ - reducerPath: 'rtk/apps/marketplace/test', - baseQuery: fetchBaseQuery({ - baseUrl: getAssetBase(), - }), - endpoints: (builder) => ({ - fetchLatestApps: builder.query({ - query: () => '/api/apps/latest.json', - }), - }), -}) +export default function HeaderSection() { + const { t } = useTranslation() -export const { useFetchLatestAppsQuery } = apiSlice + return ( +
+ + {t('content.appstore.appOverviewSection.title')} + +
+ ) +} diff --git a/src/components/pages/AppMarketplace/components/SearchSection/index.tsx b/src/components/pages/AppMarketplace/components/SearchSection/index.tsx new file mode 100644 index 000000000..dfd3a9ee8 --- /dev/null +++ b/src/components/pages/AppMarketplace/components/SearchSection/index.tsx @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2021, 2024 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 { ViewSelector } from '@catena-x/portal-shared-components' +import DebouncedSearchInput from 'components/shared/basic/Input/DebouncedSearchInput' +import { + appsControlSelector, + setAppGroup, + setAppSearch, +} from 'features/apps/control' +import { AppGroup } from 'features/apps/types' +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' + +export default function SearchSection() { + const { t } = useTranslation() + const dispatch = useDispatch() + const control = useSelector(appsControlSelector) + + const setGroup = (e: React.MouseEvent) => { + dispatch(setAppGroup(e.currentTarget.value as AppGroup)) + } + + const triggerSearch = (expr: string) => { + dispatch(setAppSearch(expr)) + } + + const categoryViews = [ + { + buttonText: t('content.appstore.appOverviewSection.categoryViews.all'), + buttonValue: AppGroup.ALL, + onButtonClick: setGroup, + }, + { + buttonText: t( + 'content.appstore.appOverviewSection.categoryViews.useCases' + ), + buttonValue: AppGroup.USE_CASES, + onButtonClick: setGroup, + }, + ] + + return ( +
+ + +
+ +
+
+ ) +} diff --git a/src/components/pages/AppMarketplace/components/StageSection/index.tsx b/src/components/pages/AppMarketplace/components/StageSection/index.tsx index 06dbb176c..a22dff2b0 100644 --- a/src/components/pages/AppMarketplace/components/StageSection/index.tsx +++ b/src/components/pages/AppMarketplace/components/StageSection/index.tsx @@ -1,6 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. diff --git a/src/components/pages/AppMarketplace/index.tsx b/src/components/pages/AppMarketplace/index.tsx index 35278fe33..1df65175e 100644 --- a/src/components/pages/AppMarketplace/index.tsx +++ b/src/components/pages/AppMarketplace/index.tsx @@ -1,6 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -20,13 +19,24 @@ import StageSection from './components/StageSection' import AppListSection from './components/AppListSection' +import SearchSection from './components/SearchSection' +import HeaderSection from './components/HeaderSection' +import { Box } from '@mui/material' +import PageService from 'services/PageService' +import { useRef } from 'react' import './AppMarketplace.scss' export default function AppMarketplace() { + const reference = PageService.registerReference('AppList', useRef(null)) + return (
- + + + + +
) } diff --git a/src/components/pages/AppOverview/AppOverviewList.tsx b/src/components/pages/AppOverview/AppOverviewList.tsx index da9c85b72..67999dd99 100644 --- a/src/components/pages/AppOverview/AppOverviewList.tsx +++ b/src/components/pages/AppOverview/AppOverviewList.tsx @@ -22,7 +22,7 @@ import { type CardItems, Cards } from '@catena-x/portal-shared-components' import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { PAGES } from 'types/Constants' -import type { AppInfo } from 'features/apps/apiSlice' +import type { AppInfo } from 'features/apps/types' import { fetchImageWithToken } from 'services/ImageService' enum AppSubMenuItems { diff --git a/src/components/pages/AppOverview/index.tsx b/src/components/pages/AppOverview/index.tsx index de4f994e3..036803a4c 100644 --- a/src/components/pages/AppOverview/index.tsx +++ b/src/components/pages/AppOverview/index.tsx @@ -37,11 +37,8 @@ import { appCardRecentlyApps, appToCard, } from 'features/apps/mapper' -import { - useFetchProvidedAppsQuery, - type AppInfo, - type AppMarketplaceApp, -} from 'features/apps/apiSlice' +import { useFetchProvidedAppsQuery } from 'features/apps/apiSlice' +import { type AppInfo, type AppMarketplaceApp } from 'features/apps/types' import { useDispatch } from 'react-redux' import debounce from 'lodash.debounce' import { OVERLAYS } from 'types/Constants' diff --git a/src/components/pages/AppOverviewNew/index.tsx b/src/components/pages/AppOverviewNew/index.tsx index ce32eb0f9..07ffe0f6c 100644 --- a/src/components/pages/AppOverviewNew/index.tsx +++ b/src/components/pages/AppOverviewNew/index.tsx @@ -21,10 +21,8 @@ import { useTranslation } from 'react-i18next' import { PageBreadcrumb } from 'components/shared/frame/PageBreadcrumb/PageBreadcrumb' import { ErrorBar, PageHeader } from '@catena-x/portal-shared-components' -import { - type AppMarketplaceApp, - useFetchProvidedAppsQuery, -} from 'features/apps/apiSlice' +import { type AppMarketplaceApp } from 'features/apps/types' +import { useFetchProvidedAppsQuery } from 'features/apps/apiSlice' import NoItems from '../NoItems' import { AppOverviewList } from '../AppOverview/AppOverviewList' import { appToCard } from 'features/apps/mapper' diff --git a/src/components/pages/AppSubscription/AppSubscriptionDetailOverlay/index.tsx b/src/components/pages/AppSubscription/AppSubscriptionDetailOverlay/index.tsx index 9fbc292de..888aa93a9 100644 --- a/src/components/pages/AppSubscription/AppSubscriptionDetailOverlay/index.tsx +++ b/src/components/pages/AppSubscription/AppSubscriptionDetailOverlay/index.tsx @@ -34,7 +34,7 @@ import { useUpdateTenantUrlMutation, } from 'features/appSubscription/appSubscriptionApiSlice' import ReleaseStepper from 'components/shared/basic/ReleaseProcess/stepper' -import { SubscriptionStatus } from 'features/apps/apiSlice' +import { SubscriptionStatus } from 'features/apps/types' import UserService from 'services/UserService' import { ROLES } from 'types/Constants' import { useState } from 'react' diff --git a/src/components/pages/Home/components/BusinessApplicationsSection/index.tsx b/src/components/pages/Home/components/BusinessApplicationsSection/index.tsx index e1b4adb92..9631d0cbd 100644 --- a/src/components/pages/Home/components/BusinessApplicationsSection/index.tsx +++ b/src/components/pages/Home/components/BusinessApplicationsSection/index.tsx @@ -24,10 +24,8 @@ import { Typography, Carousel, Card } from '@catena-x/portal-shared-components' import Box from '@mui/material/Box' import uniqueId from 'lodash/uniqueId' import PageService from 'services/PageService' -import { - type AppMarketplaceApp, - useFetchBusinessAppsQuery, -} from 'features/apps/apiSlice' +import { type AppMarketplaceApp } from 'features/apps/types' +import { useFetchBusinessAppsQuery } from 'features/apps/apiSlice' import { appToCard } from 'features/apps/mapper' import { fetchImageWithToken } from 'services/ImageService' diff --git a/src/components/pages/Organization/AppSubscriptions.tsx b/src/components/pages/Organization/AppSubscriptions.tsx index a51fcefb7..b0a85d45b 100644 --- a/src/components/pages/Organization/AppSubscriptions.tsx +++ b/src/components/pages/Organization/AppSubscriptions.tsx @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { SubscriptionStatus, type ImageType } from 'features/apps/apiSlice' +import { SubscriptionStatus, type ImageType } from 'features/apps/types' import { Button, Image, LogoGrayData } from '@catena-x/portal-shared-components' import { fetchImageWithToken } from 'services/ImageService' import './Organization.scss' diff --git a/src/components/pages/ServiceAdminBoardDetail/index.tsx b/src/components/pages/ServiceAdminBoardDetail/index.tsx index 14f4b9819..936c21a16 100644 --- a/src/components/pages/ServiceAdminBoardDetail/index.tsx +++ b/src/components/pages/ServiceAdminBoardDetail/index.tsx @@ -40,7 +40,7 @@ import { PAGES } from 'types/Constants' import ArrowForwardIcon from '@mui/icons-material/ArrowForward' import { Grid, Box, Divider } from '@mui/material' import { download } from 'utils/downloadUtils' -import { DocumentTypeText } from 'features/apps/apiSlice' +import { DocumentTypeText } from 'features/apps/types' import { DocumentTypeId } from 'features/appManagement/apiSlice' enum TableData { diff --git a/src/components/pages/ServiceReleaseProcess/components/ServiceDetails.tsx b/src/components/pages/ServiceReleaseProcess/components/ServiceDetails.tsx index 733fdde39..625b458c7 100644 --- a/src/components/pages/ServiceReleaseProcess/components/ServiceDetails.tsx +++ b/src/components/pages/ServiceReleaseProcess/components/ServiceDetails.tsx @@ -39,7 +39,7 @@ import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined' import { useParams } from 'react-router-dom' import { download } from 'utils/downloadUtils' import { getAssetBase } from 'services/EnvironmentService' -import { type DocumentData } from 'features/apps/apiSlice' +import { type DocumentData } from 'features/apps/types' import { DocumentTypeId } from 'features/appManagement/apiSlice' export default function ServiceDetails() { diff --git a/src/components/pages/Test/index.notify.tsx b/src/components/pages/Test/index.notify.tsx new file mode 100644 index 000000000..383a9e7a1 --- /dev/null +++ b/src/components/pages/Test/index.notify.tsx @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2021, 2024 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 { Button } from '@catena-x/portal-shared-components' +import { error, success } from 'services/NotifyService' + +const checkDate = (date: number) => { + date % 2 + ? success('current time', 'even', date) + : error('current time', 'odd ', date) +} + +export default function TestNotify() { + return ( +
+ +
+ ) +} diff --git a/src/components/pages/Test/index.tsx b/src/components/pages/Test/index.tsx index 8c8a921e8..fe8f79f6e 100644 --- a/src/components/pages/Test/index.tsx +++ b/src/components/pages/Test/index.tsx @@ -1,6 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -18,27 +17,12 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { Button } from '@catena-x/portal-shared-components' -import { error, success } from 'services/NotifyService' - -const checkDate = (date: number) => { - date % 2 - ? success('current time', 'even', date) - : error('current time', 'odd ', date) -} +import TestNotify from './index.notify' export default function Test() { return ( -
-
- -
+
+
) } diff --git a/src/components/pages/UsecaseParticipation/index.tsx b/src/components/pages/UsecaseParticipation/index.tsx index 547af922b..67157a622 100644 --- a/src/components/pages/UsecaseParticipation/index.tsx +++ b/src/components/pages/UsecaseParticipation/index.tsx @@ -37,7 +37,7 @@ import { useFetchUsecaseQuery, } from 'features/usecase/usecaseApiSlice' import './UsecaseParticipation.scss' -import { SubscriptionStatus } from 'features/apps/apiSlice' +import { SubscriptionStatus } from 'features/apps/types' import { useEffect } from 'react' import { Link } from 'react-router-dom' diff --git a/src/components/pages/UserManagement/AppArea/index.tsx b/src/components/pages/UserManagement/AppArea/index.tsx index e397e40aa..0cd3e3b4b 100644 --- a/src/components/pages/UserManagement/AppArea/index.tsx +++ b/src/components/pages/UserManagement/AppArea/index.tsx @@ -23,10 +23,8 @@ import { useNavigate } from 'react-router-dom' import Box from '@mui/material/Box' import { useTranslation } from 'react-i18next' import SubHeaderTitle from 'components/shared/frame/SubHeaderTitle' -import { - type ActiveSubscriptionItem, - useFetchSubscriptionStatusQuery, -} from 'features/apps/apiSlice' +import { type ActiveSubscriptionItem } from 'features/apps/types' +import { useFetchSubscriptionStatusQuery } from 'features/apps/apiSlice' import { fetchImageWithToken } from 'services/ImageService' export const AppArea = () => { diff --git a/src/components/shared/basic/Input/DebouncedSearchInput.tsx b/src/components/shared/basic/Input/DebouncedSearchInput.tsx index 370793f25..3b6863e80 100644 --- a/src/components/shared/basic/Input/DebouncedSearchInput.tsx +++ b/src/components/shared/basic/Input/DebouncedSearchInput.tsx @@ -21,15 +21,22 @@ import debounce from 'lodash.debounce' import { useCallback, useMemo, useState } from 'react' import { SearchInput } from '@catena-x/portal-shared-components' +import type { SxProps, Theme } from '@mui/material' const DebouncedSearchInput = ({ + sx = {}, + placeholder = '', + value = '', onSearch, debounceTime = 300, }: { + sx?: SxProps + placeholder?: string + value?: string onSearch: (expr: string) => void debounceTime?: number }) => { - const [searchExpr, setSearchExpr] = useState('') + const [searchExpr, setSearchExpr] = useState(value ?? '') const debouncedSearch = useMemo(() => debounce(onSearch, debounceTime), []) @@ -43,6 +50,8 @@ const DebouncedSearchInput = ({ return ( { doSearch(e.target.value) diff --git a/src/components/shared/basic/LoadingProgress/index.tsx b/src/components/shared/basic/LoadingProgress/index.tsx new file mode 100644 index 000000000..4f4ba6c2d --- /dev/null +++ b/src/components/shared/basic/LoadingProgress/index.tsx @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2021, 2024 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 { CircleProgress } from '@catena-x/portal-shared-components' + +export default function LoadingProgress() { + return ( + + ) +} diff --git a/src/components/shared/basic/ReleaseProcess/components/CommonValidateAndPublish.tsx b/src/components/shared/basic/ReleaseProcess/components/CommonValidateAndPublish.tsx index 8f1e1b7fa..932160fd6 100644 --- a/src/components/shared/basic/ReleaseProcess/components/CommonValidateAndPublish.tsx +++ b/src/components/shared/basic/ReleaseProcess/components/CommonValidateAndPublish.tsx @@ -50,7 +50,7 @@ import { import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined' import CommonService from 'services/CommonService' import ReleaseStepHeader from '../components/ReleaseStepHeader' -import { DocumentTypeText } from 'features/apps/apiSlice' +import { DocumentTypeText } from 'features/apps/types' import { download } from 'utils/downloadUtils' import { AppOverviewTypes, diff --git a/src/components/shared/templates/Subscription/SubscriptionElements.tsx b/src/components/shared/templates/Subscription/SubscriptionElements.tsx index 879eff428..ca97f13e0 100644 --- a/src/components/shared/templates/Subscription/SubscriptionElements.tsx +++ b/src/components/shared/templates/Subscription/SubscriptionElements.tsx @@ -39,7 +39,7 @@ import './Subscription.scss' import AppSubscriptionDetailOverlay from 'components/pages/AppSubscription/AppSubscriptionDetailOverlay' import ActivateSubscriptionOverlay from 'components/pages/AppSubscription/ActivateSubscriptionOverlay' import { useState, useReducer } from 'react' -import { SubscriptionStatus } from 'features/apps/apiSlice' +import { SubscriptionStatus } from 'features/apps/types' import ActivateServiceSubscription from 'components/overlays/ActivateServiceSubscription' import { SubscriptionTypes } from '.' import { getAssetBase } from 'services/EnvironmentService' diff --git a/src/features/adminBoard/adminBoardApiSlice.ts b/src/features/adminBoard/adminBoardApiSlice.ts index 98588ed76..6c14688c4 100644 --- a/src/features/adminBoard/adminBoardApiSlice.ts +++ b/src/features/adminBoard/adminBoardApiSlice.ts @@ -22,7 +22,7 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import type { StatusVariants } from '@catena-x/portal-shared-components' import { apiBaseQuery } from 'utils/rtkUtil' import i18next from 'i18next' -import type { AppDetails } from 'features/apps/apiSlice' +import type { AppDetails } from 'features/apps/types' const PAGE_SIZE = 15 diff --git a/src/features/apps/apiSlice.ts b/src/features/apps/apiSlice.ts index a155937c4..6b2ef70c5 100644 --- a/src/features/apps/apiSlice.ts +++ b/src/features/apps/apiSlice.ts @@ -1,6 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -20,221 +19,23 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { LogoGrayData } from '@catena-x/portal-shared-components' -import type { PrivacyPolicyType } from 'features/adminBoard/adminBoardApiSlice' -import type { UseCaseType } from 'features/appManagement/types' import i18next from 'i18next' import { getApiBase } from 'services/EnvironmentService' import { apiBaseQuery } from 'utils/rtkUtil' - -export type ImageType = { - src: string - alt?: string -} - -export interface AppInfo { - status: string - id: string | undefined - name: string | undefined -} - -export type AppItem = { - addButtonClicked: boolean - description: string - id: string - image: ImageType - leadPictureId: string - licenseType: string - name: string - onButtonClick: () => void - onClick: () => void - onSecondaryButtonClick: () => void - price: string - provider: string - shortDescription: string - subtitle: string - title: string - useCases: string[] -} - -export type AppMarketplaceApp = { - id: string - title: string - provider: string - leadPictureUri: string - shortDescription: string - useCases: UseCaseType[] - price: string - rating?: number - uri?: string - status?: SubscriptionStatus - image?: ImageType - name?: string - lastChanged?: string - timestamp?: number - leadPictureId?: string - subscriptionStatus?: SubscriptionStatus -} - -export interface ProvidedApps { - meta: { - contentSize: number - page: number - totalElements: number - totalPages: number - } - content: AppMarketplaceApp[] -} - -export enum SubscriptionStatus { - ACTIVE = 'ACTIVE', - PENDING = 'PENDING', - INACTIVE = 'INACTIVE', -} - -export enum SubscriptionStatusText { - ACTIVE = 'Active', - PENDING = 'Pending', - INACTIVE = 'Inactive', - IN_REVIEW = 'In Review', - CREATED = 'In Progress', -} - -export type ActiveSubscriptionItem = { - offerId: string - name: string - provider: string - image?: ImageType -} - -export type SubscriptionStatusItem = { - offerId: string - status: SubscriptionStatus -} - -export type SubscriptionStatusDuplicateItem = { - appId?: string - offerSubscriptionStatus?: SubscriptionStatus - name?: string - provider?: string - image?: ImageType - status?: SubscriptionStatus - offerId?: string -} - -export enum DocumentTypeText { - CONFORMITY_DOCUMENT = 'ConformityDocument', - DOCUMENTS = 'Documents', - CONFORMITY_APPROVAL_BUSINESS_APPS = 'CONFORMITY_APPROVAL_BUSINESS_APPS', - CONFORMITY_APPROVAL_SERVICES = 'CONFORMITY_APPROVAL_SERVICES', -} - -export type DocumentData = { - documentId: string - documentName: string -} - -export type AppDetails = AppMarketplaceApp & { - providerUri: string - contactEmail: string - contactNumber: string - images: string[] - documents: Documents - longDescription: string - isSubscribed: string - tags: string[] - languages: string[] - leadPictureUri?: ImageType - privacyPolicies: PrivacyPolicyType[] - roles?: string[] - description?: { - languageCode: string - longDescription: string - shortDescription: string - }[] - technicalUserProfile?: { - [key: string]: string[] | null - } -} - -export type Documents = { - ADDITIONAL_DETAILS: Array - APP_CONTRACT: Array - APP_TECHNICAL_INFORMATION: Array - CONFORMITY_APPROVAL_BUSINESS_APPS: Array -} - -export type AppDetailsState = { - item: AppDetails - loading: boolean - error: string -} - -export type AgreementRequest = { - agreementId: string - name: string -} - -export interface SubscriptionRequestBody { - agreementId: string - consentStatusId: string -} - -export type SubscriptionAppRequest = { - appId: string - body: SubscriptionRequestBody[] -} - -export type DocumentRequestData = { - appId: string - documentId: string -} - -export type ActiveAppsData = { - id: string - name: string - shortDescription: string - provider: string - price: string - leadPictureId: string - useCases: string[] - status?: string -} - -export interface ActiveSubscription { - offerId: string - 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 -} +import type { + AppDetails, + AppMarketplaceApp, + SubscriptionStatusItem, + SubscriptionStatusDuplicateItem, + ActiveSubscriptionItem, + ProvidedApps, + DocumentRequestData, + SubscriptionAppRequest, + AgreementRequest, + ActiveSubscription, + ActiveSubscriptionDetails, + FetchSubscriptionAppQueryType, +} from './types' export const apiSlice = createApi({ reducerPath: 'rtk/apps/marketplace', diff --git a/src/features/apps/control.ts b/src/features/apps/control.ts new file mode 100644 index 000000000..2c93065a8 --- /dev/null +++ b/src/features/apps/control.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2021, 2024 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 PayloadAction, createSlice } from '@reduxjs/toolkit' +import type { RootState } from 'features/store' +import { type AppsControlState, type AppGroup, initialState } from './types' + +export const slice = createSlice({ + name: 'apps/control', + initialState, + reducers: { + setAppSearch: (state: AppsControlState, action: PayloadAction) => ({ + ...state, + search: action.payload, + }), + setAppGroup: ( + state: AppsControlState, + action: PayloadAction + ) => ({ + ...state, + group: action.payload, + }), + }, +}) + +export const appsControlSelector = (state: RootState): AppsControlState => + state.apps.control + +export const { setAppSearch, setAppGroup } = slice.actions + +export default slice diff --git a/src/features/apps/index.ts b/src/features/apps/index.ts index 7bf141a7a..3e9b74149 100644 --- a/src/features/apps/index.ts +++ b/src/features/apps/index.ts @@ -22,8 +22,10 @@ import { combineReducers } from 'redux' import { slice as details } from './details/slice' import { slice as favorites } from './favorites/slice' import { slice as marketplace } from './marketplaceDeprecated/slice' +import { slice as control } from './control' export default combineReducers({ + control: control.reducer, details: details.reducer, favorites: favorites.reducer, marketplace: marketplace.reducer, diff --git a/src/features/apps/mapper.ts b/src/features/apps/mapper.ts index 3cf89c906..20ec685dc 100644 --- a/src/features/apps/mapper.ts +++ b/src/features/apps/mapper.ts @@ -28,10 +28,10 @@ import { getApiBase, getAssetBase } from 'services/EnvironmentService' import { type ActiveSubscription, type AppMarketplaceApp, - SubscriptionStatus, type SubscriptionStatusItem, + SubscriptionStatus, SubscriptionStatusText, -} from './apiSlice' +} from './types' const baseAssets = getAssetBase() diff --git a/src/features/apps/marketplaceDeprecated/api.ts b/src/features/apps/marketplaceDeprecated/api.ts index 93eeac728..588bfcdaa 100644 --- a/src/features/apps/marketplaceDeprecated/api.ts +++ b/src/features/apps/marketplaceDeprecated/api.ts @@ -22,7 +22,7 @@ import { getApiBase } from 'services/EnvironmentService' import { HttpClient } from 'utils/HttpClient' import type { SubscribedApps } from './types' import { getHeaders } from 'services/RequestService' -import type { AppMarketplaceApp } from '../apiSlice' +import type { AppMarketplaceApp } from '../types' export class Api extends HttpClient { private static classInstance?: Api diff --git a/src/features/apps/types.ts b/src/features/apps/types.ts new file mode 100644 index 000000000..15a017c4a --- /dev/null +++ b/src/features/apps/types.ts @@ -0,0 +1,249 @@ +/******************************************************************************** + * Copyright (c) 2021, 2024 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 { CardItems } from '@catena-x/portal-shared-components' +import type { PrivacyPolicyType } from 'features/adminBoard/adminBoardApiSlice' +import type { UseCaseType } from 'features/appManagement/types' + +export type ImageType = { + src: string + alt?: string +} + +export interface AppInfo { + status: string + id: string | undefined + name: string | undefined +} + +export type AppItem = { + addButtonClicked: boolean + description: string + id: string + image: ImageType + leadPictureId: string + licenseType: string + name: string + onButtonClick: () => void + onClick: () => void + onSecondaryButtonClick: () => void + price: string + provider: string + shortDescription: string + subtitle: string + title: string + useCases: string[] +} + +export type AppMarketplaceApp = { + id: string + title: string + provider: string + leadPictureUri: string + shortDescription: string + useCases: UseCaseType[] + price: string + rating?: number + uri?: string + status?: SubscriptionStatus + image?: ImageType + name?: string + lastChanged?: string + timestamp?: number + leadPictureId?: string + subscriptionStatus?: SubscriptionStatus +} + +export interface ProvidedApps { + meta: { + contentSize: number + page: number + totalElements: number + totalPages: number + } + content: AppMarketplaceApp[] +} + +export enum SubscriptionStatus { + ACTIVE = 'ACTIVE', + PENDING = 'PENDING', + INACTIVE = 'INACTIVE', +} + +export enum SubscriptionStatusText { + ACTIVE = 'Active', + PENDING = 'Pending', + INACTIVE = 'Inactive', + IN_REVIEW = 'In Review', + CREATED = 'In Progress', +} + +export type ActiveSubscriptionItem = { + offerId: string + name: string + provider: string + image?: ImageType +} + +export type SubscriptionStatusItem = { + offerId: string + status: SubscriptionStatus +} + +export type SubscriptionStatusDuplicateItem = { + appId?: string + offerSubscriptionStatus?: SubscriptionStatus + name?: string + provider?: string + image?: ImageType + status?: SubscriptionStatus + offerId?: string +} + +export enum DocumentTypeText { + CONFORMITY_DOCUMENT = 'ConformityDocument', + DOCUMENTS = 'Documents', + CONFORMITY_APPROVAL_BUSINESS_APPS = 'CONFORMITY_APPROVAL_BUSINESS_APPS', + CONFORMITY_APPROVAL_SERVICES = 'CONFORMITY_APPROVAL_SERVICES', +} + +export type DocumentData = { + documentId: string + documentName: string +} + +export type AppDetails = AppMarketplaceApp & { + providerUri: string + contactEmail: string + contactNumber: string + images: string[] + documents: Documents + longDescription: string + isSubscribed: string + tags: string[] + languages: string[] + leadPictureUri?: ImageType + privacyPolicies: PrivacyPolicyType[] + roles?: string[] + description?: { + languageCode: string + longDescription: string + shortDescription: string + }[] + technicalUserProfile?: { + [key: string]: string[] | null + } +} + +export type Documents = { + ADDITIONAL_DETAILS: Array + APP_CONTRACT: Array + APP_TECHNICAL_INFORMATION: Array + CONFORMITY_APPROVAL_BUSINESS_APPS: Array +} + +export type AppDetailsState = { + item: AppDetails + loading: boolean + error: string +} + +export type AgreementRequest = { + agreementId: string + name: string +} + +export interface SubscriptionRequestBody { + agreementId: string + consentStatusId: string +} + +export type SubscriptionAppRequest = { + appId: string + body: SubscriptionRequestBody[] +} + +export type DocumentRequestData = { + appId: string + documentId: string +} + +export type ActiveAppsData = { + id: string + name: string + shortDescription: string + provider: string + price: string + leadPictureId: string + useCases: string[] + status?: string +} + +export interface ActiveSubscription { + offerId: string + 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[] +} + +export interface FetchSubscriptionAppQueryType { + subscriptionId: string + appId: string +} + +export type AppMarketplaceCard = AppMarketplaceApp & CardItems + +export enum AppGroup { + ALL = 'all', + USE_CASES = 'useCases', +} + +export type AppsControlState = { + search: string + group: AppGroup +} + +export const initialState: AppsControlState = { + search: '', + group: AppGroup.ALL, +} diff --git a/src/features/images/slice.ts b/src/features/images/slice.ts new file mode 100644 index 000000000..a2598d01c --- /dev/null +++ b/src/features/images/slice.ts @@ -0,0 +1,53 @@ +/******************************************************************************** + * 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 { PayloadAction } from '@reduxjs/toolkit' +import { createSlice } from '@reduxjs/toolkit' +import type { RootState } from 'features/store' + +const name = 'images' + +export type ImagesState = Record + +const initialState: ImagesState = {} + +export const slice = createSlice({ + name, + initialState, + reducers: { + put: (state, action: PayloadAction) => ({ + ...state, + ...action.payload, + }), + delete: (state, action: PayloadAction) => { + const copy = { ...state } + // Redux doesn't support Map type so we have to go with a generic Record + // eslint-disable-next-line + delete copy[action.payload] + return copy + }, + }, +}) + +export const { put } = slice.actions + +export const imagesSelector = (state: RootState): ImagesState => state.images + +export default slice.reducer diff --git a/src/features/info/search/actions.ts b/src/features/info/search/actions.ts index 0e31cfbc0..095a3cafa 100644 --- a/src/features/info/search/actions.ts +++ b/src/features/info/search/actions.ts @@ -47,7 +47,7 @@ import { hasAccessOverlay, } from 'services/AccessService' import { initialPaginResult } from 'types/MainTypes' -import type { AppMarketplaceApp } from 'features/apps/apiSlice' +import type { AppMarketplaceApp } from 'features/apps/types' const emptyAppResult: AppMarketplaceApp[] = [] const emptyNewsResult: CardItems[] = [] diff --git a/src/features/info/search/mapper.ts b/src/features/info/search/mapper.ts index 7ffea8bff..281d8147a 100644 --- a/src/features/info/search/mapper.ts +++ b/src/features/info/search/mapper.ts @@ -20,7 +20,7 @@ import type { CardItems } from '@catena-x/portal-shared-components' import type { TenantUser } from 'features/admin/userApiSlice' -import type { AppMarketplaceApp } from 'features/apps/apiSlice' +import type { AppMarketplaceApp } from 'features/apps/types' import type { BusinessPartner } from 'features/partnerNetwork/types' import { SearchCategory, type SearchItem } from './types' diff --git a/src/features/store.ts b/src/features/store.ts index 3cbceea2c..5c33cc65e 100644 --- a/src/features/store.ts +++ b/src/features/store.ts @@ -29,6 +29,7 @@ import partnerNetworkSlice from './partnerNetwork/slice' import connectorSlice from './connector/slice' import notificationSliceDep from './notification/slice' import ErrorSlice from './error/slice' +import images from './images/slice' import managementSlice from './appManagement/slice' import serviceManagementSlice from './serviceManagement/slice' import serviceMarketplaceSlice from './serviceMarketplace/slice' @@ -45,7 +46,6 @@ import userRoleSlice, { apiSlice as appRolesSlice, } from './admin/appuserApiSlice' import { apiSlice as appMarketplaceSlice } from './apps/apiSlice' -import { apiSlice as appMarketplaceSliceTest } from './apps/apiSliceTest' import { apiSlice as appManagementSlice } from './appManagement/apiSlice' import { apiSlice as serviceMarketplaceApiSlice } from './serviceMarketplace/serviceApiSlice' import { apiSlice as serviceProviderApiSlice } from './serviceProvider/serviceProviderApiSlice' @@ -78,6 +78,7 @@ export const reducers = { apps, control, info, + images, management: managementSlice.reducer, serviceManagement: serviceManagementSlice.reducer, serviceMarketplace: serviceMarketplaceSlice.reducer, @@ -101,7 +102,6 @@ export const reducers = { [notificationSlice.reducerPath]: notificationSlice.reducer, [appRolesSlice.reducerPath]: appRolesSlice.reducer, [appMarketplaceSlice.reducerPath]: appMarketplaceSlice.reducer, - [appMarketplaceSliceTest.reducerPath]: appMarketplaceSliceTest.reducer, [appManagementSlice.reducerPath]: appManagementSlice.reducer, [serviceMarketplaceApiSlice.reducerPath]: serviceMarketplaceApiSlice.reducer, [serviceProviderApiSlice.reducerPath]: serviceProviderApiSlice.reducer, @@ -139,7 +139,6 @@ export const store = configureStore({ .concat(notificationSlice.middleware) .concat(appRolesSlice.middleware) .concat(appMarketplaceSlice.middleware) - .concat(appMarketplaceSliceTest.middleware) .concat(appManagementSlice.middleware) .concat(serviceMarketplaceApiSlice.middleware) .concat(serviceProviderApiSlice.middleware) diff --git a/src/services/CommonService.ts b/src/services/CommonService.ts index 4519e1f4b..c33bb5da1 100644 --- a/src/services/CommonService.ts +++ b/src/services/CommonService.ts @@ -20,7 +20,7 @@ import { getApiBase, getAssetBase } from './EnvironmentService' import i18next from 'i18next' -import type { AppMarketplaceApp } from 'features/apps/apiSlice' +import type { AppMarketplaceApp, AppMarketplaceCard } from 'features/apps/types' import type { ImageType } from '@catena-x/portal-shared-components' import { fetchImageWithToken } from './ImageService' import type { RoleDescData } from 'components/pages/RoleDetails' @@ -36,7 +36,7 @@ const onClick = (app: AppMarketplaceApp) => const getPrice = (app: AppMarketplaceApp) => app.price === 'ERROR' ? '' : app.price -const appToCard = (app: AppMarketplaceApp) => ({ +const appToCard = (app: AppMarketplaceApp): AppMarketplaceCard => ({ ...app, subtitle: app.provider, title: getName(app), diff --git a/src/services/ImageService.tsx b/src/services/ImageService.tsx index 14bbbad64..812534e39 100644 --- a/src/services/ImageService.tsx +++ b/src/services/ImageService.tsx @@ -17,17 +17,26 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +import { store } from 'features/store' import UserService from './UserService' +import { type ImagesState, put } from 'features/images/slice' export const fetchImageWithToken = async ( url: string ): Promise => { - const response = await fetch(url, { - headers: { - authorization: `Bearer ${UserService.getToken()}`, - }, - }) - return await response.arrayBuffer() + let buffer = store.getState().images?.[url] + if (!buffer) { + const response = await fetch(url, { + headers: { + authorization: `Bearer ${UserService.getToken()}`, + }, + }) + buffer = await response.arrayBuffer() + const newItem: ImagesState = {} + newItem[url] = buffer + store.dispatch(put(newItem)) + } + return buffer } const ImageService = {