From 6d4f021a9a14adb708fbed9959fc4e1b8be28448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Guimar=C3=A3es=20Ribeiro?= Date: Fri, 29 Jul 2022 13:32:01 -0300 Subject: [PATCH] [NEW] Marketplace apps page new list view layout (#26181) --- apps/meteor/app/apps/client/orchestrator.ts | 3 +- .../meteor/client/components/FilterByText.tsx | 14 +- .../client/components/Page/PageHeader.tsx | 6 +- .../client/components/avatar/AppAvatar.tsx | 2 +- .../views/admin/apps/AppDetailsHeader.tsx | 2 +- .../meteor/client/views/admin/apps/AppMenu.js | 23 +-- .../meteor/client/views/admin/apps/AppRow.tsx | 122 +++++++------ .../client/views/admin/apps/AppStatus.js | 52 ++++-- .../client/views/admin/apps/AppsFilters.tsx | 79 +++++++++ .../apps/{AppsTable.tsx => AppsList.tsx} | 167 ++++++++---------- .../client/views/admin/apps/AppsListMain.tsx | 68 +++++++ .../client/views/admin/apps/AppsPage.tsx | 11 +- .../client/views/admin/apps/BundleChips.tsx | 15 +- .../views/admin/apps/MarketplaceRow.tsx | 115 ------------ .../CategoryFilter/CategoryDropDown.tsx | 2 - .../CategoryFilter/CategoryDropDownAnchor.tsx | 9 +- .../RadioDropDown/RadioDropDown.tsx | 2 +- .../CategoryDropdownDefinitions.ts | 12 +- .../apps/helpers/filterAppsByDisabled.ts | 7 + .../admin/apps/helpers/filterAppsByEnabled.ts | 5 + .../views/admin/apps/hooks/useCategories.ts | 15 +- .../views/admin/apps/hooks/useFilteredApps.ts | 19 +- .../rocketchat-i18n/i18n/en.i18n.json | 9 +- 23 files changed, 430 insertions(+), 329 deletions(-) create mode 100644 apps/meteor/client/views/admin/apps/AppsFilters.tsx rename apps/meteor/client/views/admin/apps/{AppsTable.tsx => AppsList.tsx} (60%) create mode 100644 apps/meteor/client/views/admin/apps/AppsListMain.tsx delete mode 100644 apps/meteor/client/views/admin/apps/MarketplaceRow.tsx create mode 100644 apps/meteor/client/views/admin/apps/helpers/filterAppsByDisabled.ts create mode 100644 apps/meteor/client/views/admin/apps/helpers/filterAppsByEnabled.ts diff --git a/apps/meteor/app/apps/client/orchestrator.ts b/apps/meteor/app/apps/client/orchestrator.ts index d4052fa6730..13d1a0e06b0 100644 --- a/apps/meteor/app/apps/client/orchestrator.ts +++ b/apps/meteor/app/apps/client/orchestrator.ts @@ -107,7 +107,7 @@ class AppClientOrchestrator { } return (result as App[]).map((app: App) => { - const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app; + const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt, bundledIn } = app; return { ...latest, price, @@ -115,6 +115,7 @@ class AppClientOrchestrator { purchaseType, isEnterpriseOnly, modifiedAt, + bundledIn, }; }); } diff --git a/apps/meteor/client/components/FilterByText.tsx b/apps/meteor/client/components/FilterByText.tsx index b551ffd7294..a9f6610e9f9 100644 --- a/apps/meteor/client/components/FilterByText.tsx +++ b/apps/meteor/client/components/FilterByText.tsx @@ -6,6 +6,7 @@ type FilterByTextCommonProps = { children?: ReactNode | undefined; placeholder?: string; inputRef?: () => void; + shouldFiltersStack?: boolean; onChange: (filter: { text: string }) => void; }; @@ -20,7 +21,14 @@ type FilterByTextProps = FilterByTextCommonProps | FilterByTextPropsWithButton; const isFilterByTextPropsWithButton = (props: any): props is FilterByTextPropsWithButton => 'displayButton' in props && props.displayButton === true; -const FilterByText = ({ placeholder, onChange: setFilter, inputRef, children, ...props }: FilterByTextProps): ReactElement => { +const FilterByText = ({ + placeholder, + onChange: setFilter, + inputRef, + children, + shouldFiltersStack, + ...props +}: FilterByTextProps): ReactElement => { const t = useTranslation(); const [text, setText] = useState(''); @@ -38,7 +46,7 @@ const FilterByText = ({ placeholder, onChange: setFilter, inputRef, children, .. }, []); return ( - + ) : ( children && ( - + {children} ) diff --git a/apps/meteor/client/components/Page/PageHeader.tsx b/apps/meteor/client/components/Page/PageHeader.tsx index 6a584c90b2d..f8a60031636 100644 --- a/apps/meteor/client/components/Page/PageHeader.tsx +++ b/apps/meteor/client/components/Page/PageHeader.tsx @@ -1,6 +1,6 @@ import { Box, IconButton } from '@rocket.chat/fuselage'; import { useLayout, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useContext, FC, ReactNode } from 'react'; +import React, { useContext, FC, ComponentProps, ReactNode } from 'react'; import BurgerMenu from '../BurgerMenu'; import TemplateHeader from '../Header'; @@ -10,7 +10,7 @@ type PageHeaderProps = { title: ReactNode; onClickBack?: () => void; borderBlockEndColor?: string; -}; +} & Omit, 'title'>; const PageHeader: FC = ({ children = undefined, title, onClickBack, borderBlockEndColor, ...props }) => { const t = useTranslation(); @@ -18,7 +18,7 @@ const PageHeader: FC = ({ children = undefined, title, onClickB const { isMobile } = useLayout(); return ( - + { {description} - + {(installed || isSubscribed) && } diff --git a/apps/meteor/client/views/admin/apps/AppMenu.js b/apps/meteor/client/views/admin/apps/AppMenu.js index ed4bbf9b0e7..b0d12d2cd88 100644 --- a/apps/meteor/client/views/admin/apps/AppMenu.js +++ b/apps/meteor/client/views/admin/apps/AppMenu.js @@ -130,17 +130,18 @@ function AppMenu({ app, ...props }) { action: handleSubscription, }, }), - ...(context !== 'details' && { - viewLogs: { - label: ( - - - {t('View_Logs')} - - ), - action: handleViewLogs, - }, - }), + ...(context !== 'details' && + app.installed && { + viewLogs: { + label: ( + + + {t('View_Logs')} + + ), + action: handleViewLogs, + }, + }), ...(app.installed && isAppEnabled && { disable: { diff --git a/apps/meteor/client/views/admin/apps/AppRow.tsx b/apps/meteor/client/views/admin/apps/AppRow.tsx index 33fcfd4a33e..a778a193291 100644 --- a/apps/meteor/client/views/admin/apps/AppRow.tsx +++ b/apps/meteor/client/views/admin/apps/AppRow.tsx @@ -1,38 +1,39 @@ -import { Box, Table, Tag } from '@rocket.chat/fuselage'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { FC, useState, memo, KeyboardEvent, MouseEvent } from 'react'; +import { css } from '@rocket.chat/css-in-js'; +import { Box } from '@rocket.chat/fuselage'; +import { useMediaQueries } from '@rocket.chat/fuselage-hooks'; +import colors from '@rocket.chat/fuselage-tokens/colors'; +import { useRoute } from '@rocket.chat/ui-contexts'; +import React, { FC, memo, KeyboardEvent, MouseEvent } from 'react'; import AppAvatar from '../../../components/avatar/AppAvatar'; import AppMenu from './AppMenu'; import AppStatus from './AppStatus'; +import BundleChips from './BundleChips'; import { App } from './types'; -type AppRowProps = App & { - medium: boolean; - large: boolean; -}; - -const AppRow: FC = ({ medium, ...props }) => { - const { - author: { name: authorName }, - name, - id, - description, - categories, - iconFileData, - marketplaceVersion, - iconFileContent, - installed, - } = props; - const t = useTranslation(); +const AppRow: FC = (props) => { + const { name, id, description, iconFileData, marketplaceVersion, iconFileContent, installed, isSubscribed, isMarketplace, bundledIn } = + props; - const [isFocused, setFocused] = useState(false); - const [isHovered, setHovered] = useState(false); - const isStatusVisible = isFocused || isHovered; + const [isAppNameTruncated, isBundleTextVisible, isDescriptionVisible] = useMediaQueries( + '(max-width: 510px)', + '(max-width: 887px)', + '(min-width: 1200px)', + ); const appsRoute = useRoute('admin-apps'); + const marketplaceRoute = useRoute('admin-marketplace'); const handleClick = (): void => { + if (isMarketplace) { + marketplaceRoute.push({ + context: 'details', + version: marketplaceVersion, + id, + }); + return; + } + appsRoute.push({ context: 'details', version: marketplaceVersion, @@ -52,53 +53,58 @@ const AppRow: FC = ({ medium, ...props }) => { e.stopPropagation(); }; + const hover = css` + &:hover, + &:focus { + cursor: pointer; + outline: 0; + background-color: ${colors.n200} !important; + } + `; + return ( - setFocused(true)} - onBlur={(): void => setFocused(false)} - onMouseEnter={(): void => setHovered(true)} - onMouseLeave={(): void => setHovered(false)} + display='flex' + flexDirection='row' + justifyContent='space-between' + alignItems='center' + bg='surface' + mbe='x8' + pb='x8' + pis='x16' + pie='x4' + className={hover} > - - - - + + + + {name} - {`${t('By')} ${authorName}`} - - {medium && ( - - - + + {bundledIn && Boolean(bundledIn.length) && ( + + + + )} + {isDescriptionVisible && ( + {description} - {categories && ( - - {categories.map((current) => ( - - {current} - - ))} - - )} - - - )} - - - - {installed && } + )} - - + + + + {(installed || isSubscribed) && } + + ); }; diff --git a/apps/meteor/client/views/admin/apps/AppStatus.js b/apps/meteor/client/views/admin/apps/AppStatus.js index a18df981fa0..4b01b870cc0 100644 --- a/apps/meteor/client/views/admin/apps/AppStatus.js +++ b/apps/meteor/client/views/admin/apps/AppStatus.js @@ -1,8 +1,8 @@ -import { Box, Button, Icon, Throbber } from '@rocket.chat/fuselage'; +import { Box, Button, Icon, Throbber, Tooltip, PositionAnimated, AnimatedVisibility } from '@rocket.chat/fuselage'; import { useSafely } from '@rocket.chat/fuselage-hooks'; import colors from '@rocket.chat/fuselage-tokens/colors.json'; import { useSetModal, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useCallback, useState, memo } from 'react'; +import React, { useCallback, useState, useRef, memo } from 'react'; import { Apps } from '../../../../app/apps/client/orchestrator'; import AppPermissionsReviewModal from './AppPermissionsReviewModal'; @@ -33,11 +33,13 @@ const actions = { }, }; -const AppStatus = ({ app, showStatus = true, isAppDetailsPage, installed = false, ...props }) => { +const AppStatus = ({ app, showStatus = true, isAppDetailsPage, isSubscribed, installed = false, ...props }) => { const t = useTranslation(); const [loading, setLoading] = useSafely(useState()); const [isAppPurchased, setPurchased] = useSafely(useState(app?.isPurchased)); + const [isHovered, setIsHovered] = useState(false); const setModal = useSetModal(); + const statusRef = useRef(); const { price, purchaseType, pricingPlans } = app; @@ -117,7 +119,7 @@ const AppStatus = ({ app, showStatus = true, isAppDetailsPage, installed = false {button && ( - @@ -145,10 +158,29 @@ const AppStatus = ({ app, showStatus = true, isAppDetailsPage, installed = false )} {status && ( - - - {t(status.label)} - + <> + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + display='flex' + alignItems='center' + pi='x8' + pb='x8' + bg={AppStatusStyle.bg} + color={AppStatusStyle.color} + > + + + + {`App ${status.label}`} + + )} ); diff --git a/apps/meteor/client/views/admin/apps/AppsFilters.tsx b/apps/meteor/client/views/admin/apps/AppsFilters.tsx new file mode 100644 index 00000000000..a60818d1c84 --- /dev/null +++ b/apps/meteor/client/views/admin/apps/AppsFilters.tsx @@ -0,0 +1,79 @@ +import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React, { ReactElement } from 'react'; + +import FilterByText from '../../../components/FilterByText'; +import CategoryDropDown from './components/CategoryFilter/CategoryDropDown'; +import TagList from './components/CategoryFilter/TagList'; +import RadioDropDown from './components/RadioDropDown/RadioDropDown'; +import { CategoryDropdownItem, CategoryOnSelected, selectedCategoriesList } from './definitions/CategoryDropdownDefinitions'; +import { RadioDropDownGroup, RadioDropDownOnSelected } from './definitions/RadioDropDownDefinitions'; + +type AppsFiltersProps = { + setText: React.Dispatch> & { + flush: () => void; + cancel: () => void; + }; + freePaidFilterStructure: RadioDropDownGroup; + freePaidFilterOnSelected: RadioDropDownOnSelected; + categories: { + label?: string | undefined; + items: CategoryDropdownItem[]; + }[]; + selectedCategories: selectedCategoriesList; + onSelected: CategoryOnSelected; + sortFilterStructure: RadioDropDownGroup; + sortFilterOnSelected: RadioDropDownOnSelected; + categoryTagList: selectedCategoriesList; + statusFilterStructure: RadioDropDownGroup; + statusFilterOnSelected: RadioDropDownOnSelected; +}; + +const AppsFilters = ({ + setText, + freePaidFilterStructure, + freePaidFilterOnSelected, + categories, + selectedCategories, + onSelected, + sortFilterStructure, + sortFilterOnSelected, + categoryTagList, + statusFilterStructure, + statusFilterOnSelected, +}: AppsFiltersProps): ReactElement => { + const t = useTranslation(); + + const shouldFiltersStack = useMediaQuery('(max-width: 1060px)'); + const hasFilterStackMargin = shouldFiltersStack ? '' : 'x8'; + const hasNotFilterStackMargin = shouldFiltersStack ? 'x8' : ''; + + return ( + <> + setText(text)} shouldFiltersStack={shouldFiltersStack}> + + + + + + + + ); +}; + +export default AppsFilters; diff --git a/apps/meteor/client/views/admin/apps/AppsTable.tsx b/apps/meteor/client/views/admin/apps/AppsList.tsx similarity index 60% rename from apps/meteor/client/views/admin/apps/AppsTable.tsx rename to apps/meteor/client/views/admin/apps/AppsList.tsx index b4d157856a0..a3c96bbdf38 100644 --- a/apps/meteor/client/views/admin/apps/AppsTable.tsx +++ b/apps/meteor/client/views/admin/apps/AppsList.tsx @@ -10,73 +10,55 @@ import { StatesSuggestionListItem, StatesSuggestionText, StatesTitle, - Pagination, Icon, } from '@rocket.chat/fuselage'; import { useDebouncedState } from '@rocket.chat/fuselage-hooks'; import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, useMemo, useState } from 'react'; -import FilterByText from '../../../components/FilterByText'; -import { - GenericTable, - GenericTableBody, - GenericTableHeader, - GenericTableHeaderCell, - GenericTableLoadingTable, -} from '../../../components/GenericTable'; import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; -import { useResizeInlineBreakpoint } from '../../../hooks/useResizeInlineBreakpoint'; import { AsyncStatePhase } from '../../../lib/asyncState'; -import AppRow from './AppRow'; import { useAppsReload, useAppsResult } from './AppsContext'; -import MarketplaceRow from './MarketplaceRow'; -import CategoryDropDown from './components/CategoryFilter/CategoryDropDown'; -import TagList from './components/CategoryFilter/TagList'; -import RadioDropDown from './components/RadioDropDown/RadioDropDown'; +import AppsFilters from './AppsFilters'; +import AppsListMain from './AppsListMain'; import { RadioDropDownGroup } from './definitions/RadioDropDownDefinitions'; import { useCategories } from './hooks/useCategories'; import { useFilteredApps } from './hooks/useFilteredApps'; import { useRadioToggle } from './hooks/useRadioToggle'; -const AppsTable: FC<{ +const AppsList: FC<{ isMarketplace: boolean; }> = ({ isMarketplace }) => { const t = useTranslation(); - - const [ref, onLargeBreakpoint, onMediumBreakpoint] = useResizeInlineBreakpoint([800, 600], 200) as [ - React.RefObject, - boolean, - boolean, - ]; - const { marketplaceApps, installedApps } = useAppsResult(); - - const marketplaceRoute = useRoute('admin-marketplace'); - - const Row = isMarketplace ? MarketplaceRow : AppRow; - const [text, setText] = useDebouncedState('', 500); - const reload = useAppsReload(); - const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); - const [freePaidFilterStructure, setFreePaidFilterStructure] = useState({ + const marketplaceRoute = useRoute('admin-marketplace'); + + const [freePaidFilterStructure, setFreePaidFilterStructure] = useState({ label: t('Filter_By_Price'), items: [ - { id: 'all', label: t('All_Apps'), checked: true }, + { id: 'all', label: t('All_Prices'), checked: true }, { id: 'free', label: t('Free_Apps'), checked: false }, { id: 'paid', label: t('Paid_Apps'), checked: false }, ], }); - const freePaidFilterOnSelected = useRadioToggle(setFreePaidFilterStructure); - const [categories, selectedCategories, categoryTagList, onSelected] = useCategories(); + const [statusFilterStructure, setStatusFilterStructure] = useState({ + label: t('Filter_By_Status'), + items: [ + { id: 'all', label: t('All_status'), checked: true }, + { id: 'enabled', label: t('Enabled'), checked: false }, + { id: 'disabled', label: t('Disabled'), checked: false }, + ], + }); + const statusFilterOnSelected = useRadioToggle(setStatusFilterStructure); const [sortFilterStructure, setSortFilterStructure] = useState({ - label: 'Sort by', + label: t('Sort_By'), items: [ { id: 'az', label: 'A-Z', checked: true }, { id: 'za', label: 'Z-A', checked: false }, @@ -84,9 +66,9 @@ const AppsTable: FC<{ { id: 'lru', label: t('Least_recent_updated'), checked: false }, ], }); - const sortFilterOnSelected = useRadioToggle(setSortFilterStructure); + const [categories, selectedCategories, categoryTagList, onSelected] = useCategories(); const appsResult = useFilteredApps({ appsData: isMarketplace ? marketplaceApps : installedApps, text, @@ -95,57 +77,53 @@ const AppsTable: FC<{ categories: useMemo(() => selectedCategories.map(({ label }) => label), [selectedCategories]), purchaseType: useMemo(() => freePaidFilterStructure.items.find(({ checked }) => checked)?.id, [freePaidFilterStructure]), sortingMethod: useMemo(() => sortFilterStructure.items.find(({ checked }) => checked)?.id, [sortFilterStructure]), + status: useMemo(() => statusFilterStructure.items.find(({ checked }) => checked)?.id, [statusFilterStructure]), }); + const isAppListReadyOrLoading = + appsResult.phase === AsyncStatePhase.LOADING || (appsResult.phase === AsyncStatePhase.RESOLVED && Boolean(appsResult.value.count)); + + const noInstalledAppsFound = appsResult.phase === AsyncStatePhase.RESOLVED && !isMarketplace && appsResult.value.total === 0; + + const noMarketplaceOrInstalledAppMatches = appsResult.phase === AsyncStatePhase.RESOLVED && isMarketplace && appsResult.value.count === 0; + + const noInstalledAppMatches = + appsResult.phase === AsyncStatePhase.RESOLVED && !isMarketplace && appsResult.value.total !== 0 && appsResult.value.count === 0; + return ( <> - {/* TODO Divide into two components: Filters and AppsTable */} - setText(text)}> - - - - - - {(appsResult.phase === AsyncStatePhase.LOADING || - (appsResult.phase === AsyncStatePhase.RESOLVED && Boolean(appsResult.value.count))) && ( - <> - - - {t('Name')} - {onMediumBreakpoint && {t('Details')}} - {isMarketplace && {t('Price')}} - - {t('Status')} - - - {appsResult.phase === AsyncStatePhase.LOADING && ( - - )} - {appsResult.phase === AsyncStatePhase.RESOLVED && - appsResult.value.items.map((app) => )} - - - {appsResult.phase === AsyncStatePhase.RESOLVED && ( - - )} - + + + {isAppListReadyOrLoading && ( + )} - {appsResult.phase === AsyncStatePhase.RESOLVED && isMarketplace && appsResult.value.count === 0 && ( + + {noMarketplaceOrInstalledAppMatches && ( {t('No_app_matches')} - {appsResult.value.shouldShowSearchText ? ( + {appsResult?.value?.shouldShowSearchText ? ( {t('No_marketplace_matches_for')}: "{text}" @@ -164,24 +142,13 @@ const AppsTable: FC<{ )} - {appsResult.phase === AsyncStatePhase.RESOLVED && !isMarketplace && appsResult.value.total === 0 && ( - - - - {t('No_apps_installed')} - {t('Explore_the_marketplace_to_find_awesome_apps')} - - marketplaceRoute.push({ context: '' })}>{t('Explore_marketplace')} - - - - )} - {appsResult.phase === AsyncStatePhase.RESOLVED && !isMarketplace && appsResult.value.total !== 0 && appsResult.value.count === 0 && ( + + {noInstalledAppMatches && ( {t('No_installed_app_matches')} - {appsResult.value.shouldShowSearchText ? ( + {appsResult?.value?.shouldShowSearchText ? ( {t('No_app_matches_for')} "{text}" @@ -199,6 +166,20 @@ const AppsTable: FC<{ )} + + {noInstalledAppsFound && ( + + + + {t('No_apps_installed')} + {t('Explore_the_marketplace_to_find_awesome_apps')} + + marketplaceRoute.push({ context: '' })}>{t('Explore_marketplace')} + + + + )} + {appsResult.phase === AsyncStatePhase.REJECTED && ( @@ -218,4 +199,4 @@ const AppsTable: FC<{ ); }; -export default AppsTable; +export default AppsList; diff --git a/apps/meteor/client/views/admin/apps/AppsListMain.tsx b/apps/meteor/client/views/admin/apps/AppsListMain.tsx new file mode 100644 index 00000000000..ddb9b67f7a3 --- /dev/null +++ b/apps/meteor/client/views/admin/apps/AppsListMain.tsx @@ -0,0 +1,68 @@ +import { App } from '@rocket.chat/core-typings'; +import { Box, Pagination, Skeleton } from '@rocket.chat/fuselage'; +import colors from '@rocket.chat/fuselage-tokens/colors'; +import React, { ReactElement } from 'react'; + +import { AsyncState, AsyncStatePhase } from '../../../lib/asyncState'; +import AppRow from './AppRow'; + +type itemsPerPage = 25 | 50 | 100; + +type AppsListMainProps = { + appsResult: AsyncState< + { + items: App[]; + } & { + shouldShowSearchText: boolean; + } & { + count: number; + offset: number; + total: number; + } + >; + current: number; + itemsPerPage: itemsPerPage; + onSetItemsPerPage: React.Dispatch>; + onSetCurrent: React.Dispatch>; + paginationProps: { + itemsPerPageLabel: () => string; + showingResultsLabel: (context: { count: number; current: number; itemsPerPage: itemsPerPage }) => string; + }; + isMarketplace: boolean; +}; + +const AppsListMain = ({ + appsResult, + current, + itemsPerPage, + onSetItemsPerPage, + onSetCurrent, + paginationProps, + isMarketplace, +}: AppsListMainProps): ReactElement => { + const loadingRows = Array.from({ length: 8 }, (_, i) => ); + + return ( + <> + + {appsResult.phase === AsyncStatePhase.LOADING + ? loadingRows + : appsResult.phase === AsyncStatePhase.RESOLVED && + appsResult.value.items.map((app) => )} + + {appsResult.phase === AsyncStatePhase.RESOLVED && ( + + )} + + ); +}; + +export default AppsListMain; diff --git a/apps/meteor/client/views/admin/apps/AppsPage.tsx b/apps/meteor/client/views/admin/apps/AppsPage.tsx index 44542bee035..71fb447282e 100644 --- a/apps/meteor/client/views/admin/apps/AppsPage.tsx +++ b/apps/meteor/client/views/admin/apps/AppsPage.tsx @@ -1,9 +1,10 @@ import { Button, ButtonGroup, Icon, Skeleton, Tabs } from '@rocket.chat/fuselage'; +import colors from '@rocket.chat/fuselage-tokens/colors'; import { useRoute, useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useEffect, useState, ReactElement } from 'react'; import Page from '../../../components/Page'; -import AppsTable from './AppsTable'; +import AppsList from './AppsList'; type AppsPageProps = { isMarketplace: boolean; @@ -37,7 +38,7 @@ const AppsPage = ({ isMarketplace }: AppsPageProps): ReactElement => { return ( - + {isMarketplace && !isLoggedInCloud && (