From aee90d0a6f40c25c89f68bab3cb165b91959217e Mon Sep 17 00:00:00 2001 From: Charles Zhao <charleszhao@silverhand.io> Date: Fri, 26 Jan 2024 09:39:44 +0800 Subject: [PATCH] refactor(console): improve app creation page empty state layout (#5310) --- .../EmptyDataPlaceholder/index.module.scss | 8 ++++ .../components/EmptyDataPlaceholder/index.tsx | 2 + .../components/GuideLibrary/index.module.scss | 10 ++-- .../components/GuideLibrary/index.tsx | 46 ++++++++++++------- .../components/GuideLibraryModal/index.tsx | 2 +- .../ProtectedAppCard/index.module.scss | 4 ++ .../components/ProtectedAppCard/index.tsx | 11 ++++- .../src/pages/Applications/index.module.scss | 3 +- .../console/src/pages/Applications/index.tsx | 5 +- .../console/src/scss/page-layout.module.scss | 2 +- packages/console/src/types/applications.ts | 2 +- 11 files changed, 66 insertions(+), 29 deletions(-) diff --git a/packages/console/src/components/EmptyDataPlaceholder/index.module.scss b/packages/console/src/components/EmptyDataPlaceholder/index.module.scss index eecfa7350..c27b7122e 100644 --- a/packages/console/src/components/EmptyDataPlaceholder/index.module.scss +++ b/packages/console/src/components/EmptyDataPlaceholder/index.module.scss @@ -39,4 +39,12 @@ height: 128px; } } + + .topSpace { + flex: 2; + } + + .bottomSpace { + flex: 3; + } } diff --git a/packages/console/src/components/EmptyDataPlaceholder/index.tsx b/packages/console/src/components/EmptyDataPlaceholder/index.tsx index 21a1b932f..8ce743d1a 100644 --- a/packages/console/src/components/EmptyDataPlaceholder/index.tsx +++ b/packages/console/src/components/EmptyDataPlaceholder/index.tsx @@ -22,8 +22,10 @@ function EmptyDataPlaceholder({ title, size = 'medium', className }: Props) { return ( <div className={classNames(styles.empty, styles[size], className)}> + <div className={styles.topSpace} /> <EmptyImage className={styles.image} /> <div className={styles.title}>{title ?? t('errors.empty')}</div> + <div className={styles.bottomSpace} /> </div> ); } diff --git a/packages/console/src/pages/Applications/components/GuideLibrary/index.module.scss b/packages/console/src/pages/Applications/components/GuideLibrary/index.module.scss index bc3bf17a2..8472cd136 100644 --- a/packages/console/src/pages/Applications/components/GuideLibrary/index.module.scss +++ b/packages/console/src/pages/Applications/components/GuideLibrary/index.module.scss @@ -7,6 +7,7 @@ .wrapper { width: 100%; + min-height: 100%; min-width: dim.$guide-content-min-width; max-width: dim.$guide-content-max-width; margin: 0 auto; @@ -87,10 +88,13 @@ } .emptyPlaceholder { - justify-content: center; - position: absolute; width: 100%; - height: 70%; + height: calc(100vh - 188px); + display: flex; +} + +.viewAll { + margin-top: _.unit(8); } @media screen and (max-width: dim.$guide-content-max-width) { diff --git a/packages/console/src/pages/Applications/components/GuideLibrary/index.tsx b/packages/console/src/pages/Applications/components/GuideLibrary/index.tsx index daeb6ded6..c6438bccf 100644 --- a/packages/console/src/pages/Applications/components/GuideLibrary/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideLibrary/index.tsx @@ -1,17 +1,19 @@ -import { type Application } from '@logto/schemas'; +import { ApplicationType, type Application } from '@logto/schemas'; import classNames from 'classnames'; import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useLocation } from 'react-router-dom'; import SearchIcon from '@/assets/icons/search.svg'; import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder'; import { type SelectedGuide } from '@/components/Guide/GuideCard'; import GuideCardGroup from '@/components/Guide/GuideCardGroup'; import { useAppGuideMetadata } from '@/components/Guide/hooks'; -import { isDevFeaturesEnabled } from '@/consts/env'; +import { isCloud, isDevFeaturesEnabled } from '@/consts/env'; import { CheckboxGroup } from '@/ds-components/Checkbox'; import OverlayScrollbar from '@/ds-components/OverlayScrollbar'; import TextInput from '@/ds-components/TextInput'; +import TextLink from '@/ds-components/TextLink'; import useTenantPathname from '@/hooks/use-tenant-pathname'; import { allAppGuideCategories, type AppGuideCategory } from '@/types/applications'; import { thirdPartyAppCategory } from '@/types/applications'; @@ -25,17 +27,18 @@ type Props = { className?: string; hasCardBorder?: boolean; hasCardButton?: boolean; - hasFilters?: boolean; }; -function GuideLibrary({ className, hasCardBorder, hasCardButton, hasFilters }: Props) { - const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.guide' }); +function GuideLibrary({ className, hasCardBorder, hasCardButton }: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { navigate } = useTenantPathname(); + const { pathname } = useLocation(); const [keyword, setKeyword] = useState<string>(''); const [filterCategories, setFilterCategories] = useState<AppGuideCategory[]>([]); const [selectedGuide, setSelectedGuide] = useState<SelectedGuide>(); const { getFilteredAppGuideMetadata, getStructuredAppGuideMetadata } = useAppGuideMetadata(); const [showCreateForm, setShowCreateForm] = useState<boolean>(false); + const isApplicationCreateModal = pathname.includes('/applications/create'); const structuredMetadata = useMemo( () => getStructuredAppGuideMetadata({ categories: filterCategories }), @@ -72,16 +75,16 @@ function GuideLibrary({ className, hasCardBorder, hasCardButton, hasFilters }: P return ( <OverlayScrollbar className={classNames(styles.container, className)}> - <div className={classNames(styles.wrapper, hasFilters && styles.hasFilters)}> + <div className={classNames(styles.wrapper, isApplicationCreateModal && styles.hasFilters)}> <div className={styles.groups}> - {hasFilters && ( + {isApplicationCreateModal && ( <div className={styles.filterAnchor}> <div className={styles.filters}> - <label>{t('filter.title')}</label> + <label>{t('guide.filter.title')}</label> <TextInput className={styles.searchInput} icon={<SearchIcon />} - placeholder={t('filter.placeholder')} + placeholder={t('guide.filter.placeholder')} value={keyword} onChange={(event) => { setKeyword(event.currentTarget.value); @@ -92,9 +95,7 @@ function GuideLibrary({ className, hasCardBorder, hasCardButton, hasFilters }: P className={styles.checkboxGroup} options={allAppGuideCategories .filter( - (category) => - category !== 'Protected' && - (isDevFeaturesEnabled || category !== thirdPartyAppCategory) + (category) => isDevFeaturesEnabled || category !== thirdPartyAppCategory ) .map((category) => ({ title: `guide.categories.${category}`, @@ -126,9 +127,17 @@ function GuideLibrary({ className, hasCardBorder, hasCardButton, hasFilters }: P ))} {!keyword && ( <> - {isDevFeaturesEnabled && ( - <ProtectedAppCard hasLabel hasCreateButton className={styles.protectedAppCard} /> - )} + {isDevFeaturesEnabled && + isCloud && + (filterCategories.length === 0 || + filterCategories.includes(ApplicationType.Protected)) && ( + <ProtectedAppCard + hasLabel + hasCreateButton + hasBorder={hasCardBorder} + className={styles.protectedAppCard} + /> + )} {(filterCategories.length > 0 ? filterCategories : allAppGuideCategories).map( (category) => structuredMetadata[category].length > 0 && ( @@ -137,7 +146,7 @@ function GuideLibrary({ className, hasCardBorder, hasCardButton, hasFilters }: P className={styles.guideGroup} hasCardBorder={hasCardBorder} hasCardButton={hasCardButton} - categoryName={t(`categories.${category}`)} + categoryName={t(`guide.categories.${category}`)} guides={structuredMetadata[category]} onClickGuide={onClickGuide} /> @@ -145,6 +154,11 @@ function GuideLibrary({ className, hasCardBorder, hasCardButton, hasFilters }: P )} </> )} + {!isApplicationCreateModal && ( + <TextLink className={styles.viewAll} to="/applications/create"> + {t('get_started.view_all')} + </TextLink> + )} </div> </div> {selectedGuide?.target !== 'API' && showCreateForm && ( diff --git a/packages/console/src/pages/Applications/components/GuideLibraryModal/index.tsx b/packages/console/src/pages/Applications/components/GuideLibraryModal/index.tsx index db3a336bb..91dd7a338 100644 --- a/packages/console/src/pages/Applications/components/GuideLibraryModal/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideLibraryModal/index.tsx @@ -36,7 +36,7 @@ function GuideLibraryModal({ isOpen, onClose }: Props) { requestSuccessMessage="guide.request_guide_successfully" onClose={onClose} /> - <GuideLibrary hasFilters hasCardButton className={styles.content} /> + <GuideLibrary hasCardButton className={styles.content} /> <ModalFooter wrapperClassName={styles.footerInnerWrapper} content="guide.do_not_need_tutorial" diff --git a/packages/console/src/pages/Applications/components/ProtectedAppCard/index.module.scss b/packages/console/src/pages/Applications/components/ProtectedAppCard/index.module.scss index d58d144e9..eee6e1e79 100644 --- a/packages/console/src/pages/Applications/components/ProtectedAppCard/index.module.scss +++ b/packages/console/src/pages/Applications/components/ProtectedAppCard/index.module.scss @@ -21,6 +21,10 @@ background-color: var(--color-layer-1); border-radius: 12px; + &.hasBorder { + border: 1px solid var(--color-divider); + } + .logo { width: 48px; height: 48px; diff --git a/packages/console/src/pages/Applications/components/ProtectedAppCard/index.tsx b/packages/console/src/pages/Applications/components/ProtectedAppCard/index.tsx index 5ea73c21c..ee42262cb 100644 --- a/packages/console/src/pages/Applications/components/ProtectedAppCard/index.tsx +++ b/packages/console/src/pages/Applications/components/ProtectedAppCard/index.tsx @@ -16,12 +16,19 @@ import * as styles from './index.module.scss'; type Props = { className?: string; + hasBorder?: boolean; hasLabel?: boolean; hasCreateButton?: boolean; onCreateSuccess?: (app: Application) => void; }; -function ProtectedAppCard({ className, hasLabel, hasCreateButton, onCreateSuccess }: Props) { +function ProtectedAppCard({ + className, + hasBorder, + hasLabel, + hasCreateButton, + onCreateSuccess, +}: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.protected_app' }); const { documentationSiteUrl } = useDocumentationUrl(); const [showCreateModal, setShowCreateModal] = useState<boolean>(false); @@ -32,7 +39,7 @@ function ProtectedAppCard({ className, hasLabel, hasCreateButton, onCreateSucces <> <div className={classNames(styles.container, className)}> {hasLabel && <label>{t('name')}</label>} - <div className={styles.card}> + <div className={classNames(styles.card, hasBorder && styles.hasBorder)}> <Icon className={styles.logo} /> <div className={styles.wrapper}> <div className={styles.name}>{t('title')}</div> diff --git a/packages/console/src/pages/Applications/index.module.scss b/packages/console/src/pages/Applications/index.module.scss index c12f30ce1..e34e24b15 100644 --- a/packages/console/src/pages/Applications/index.module.scss +++ b/packages/console/src/pages/Applications/index.module.scss @@ -18,11 +18,10 @@ .guideLibraryContainer { flex: 1; - overflow-y: auto; background: var(--color-layer-1); border-radius: 12px; padding: _.unit(6) 0; - margin: _.unit(4) 0; + margin-top: _.unit(4); .title { align-items: center; diff --git a/packages/console/src/pages/Applications/index.tsx b/packages/console/src/pages/Applications/index.tsx index e5aa53cf8..b8eaab22b 100644 --- a/packages/console/src/pages/Applications/index.tsx +++ b/packages/console/src/pages/Applications/index.tsx @@ -12,7 +12,6 @@ import { isDevFeaturesEnabled, isCloud } from '@/consts/env'; import Button from '@/ds-components/Button'; import CardTitle from '@/ds-components/CardTitle'; import CopyToClipboard from '@/ds-components/CopyToClipboard'; -import OverlayScrollbar from '@/ds-components/OverlayScrollbar'; import TabNav, { TabNavItem } from '@/ds-components/TabNav'; import Table from '@/ds-components/Table'; import useApplicationsUsage from '@/hooks/use-applications-usage'; @@ -124,14 +123,14 @@ function Applications({ tab }: Props) { )} {!isLoading && !applications?.length && ( - <OverlayScrollbar className={styles.guideLibraryContainer}> + <div className={styles.guideLibraryContainer}> <CardTitle className={styles.title} title="guide.app.select_framework_or_tutorial" subtitle="guide.app.modal_subtitle" /> <GuideLibrary hasCardBorder hasCardButton className={styles.library} /> - </OverlayScrollbar> + </div> )} {(isLoading || !!applications?.length) && ( <Table diff --git a/packages/console/src/scss/page-layout.module.scss b/packages/console/src/scss/page-layout.module.scss index 4b519aac9..be77e0c1f 100644 --- a/packages/console/src/scss/page-layout.module.scss +++ b/packages/console/src/scss/page-layout.module.scss @@ -5,7 +5,7 @@ display: flex; flex-direction: column; padding-bottom: _.unit(6); - height: 100%; + min-height: 100%; } .headline { diff --git a/packages/console/src/types/applications.ts b/packages/console/src/types/applications.ts index 7fd745200..27774ad7b 100644 --- a/packages/console/src/types/applications.ts +++ b/packages/console/src/types/applications.ts @@ -18,12 +18,12 @@ export const applicationTypeI18nKey = Object.freeze({ * plus the "featured" category. */ export const allAppGuideCategories = Object.freeze([ + 'Protected', 'featured', 'Traditional', 'SPA', 'Native', 'MachineToMachine', - 'Protected', thirdPartyAppCategory, ] as const);