diff --git a/packages/console/src/App.tsx b/packages/console/src/App.tsx index d4a003df6..22a404cb5 100644 --- a/packages/console/src/App.tsx +++ b/packages/console/src/App.tsx @@ -7,8 +7,10 @@ import './scss/normalized.scss'; import * as styles from './App.module.scss'; import AppContent from './components/AppContent'; -import { getPath, sections } from './components/AppContent/components/Sidebar'; +import { getPath } from './components/AppContent/components/Sidebar'; +import { useSidebarMenuItems } from './components/AppContent/components/Sidebar/hook'; import ErrorBoundary from './components/ErrorBoundary'; +import LogtoLoading from './components/LogtoLoading'; import Toast from './components/Toast'; import { themeStorageKey, logtoApiResource } from './consts'; import { RequestError } from './hooks/use-api'; @@ -45,6 +47,8 @@ const Main = () => { settingsFetcher ); + const sections = useSidebarMenuItems(); + useEffect(() => { const theme = data?.adminConsole.appearanceMode ?? defaultTheme; const isFollowSystem = theme === AppearanceMode.SyncWithSystem; @@ -69,9 +73,13 @@ const Main = () => { useEffect(() => { if (location.pathname === '/') { - navigate(getPath(sections[0]?.items[0]?.title ?? '')); + navigate(getPath(sections?.[0]?.items[0]?.title ?? '')); } - }, [location.pathname, navigate]); + }, [location.pathname, navigate, sections]); + + if (sections?.length === 0) { + return ; + } return ( diff --git a/packages/console/src/components/AppContent/components/Sidebar/consts.tsx b/packages/console/src/components/AppContent/components/Sidebar/consts.tsx deleted file mode 100644 index ef25227ed..000000000 --- a/packages/console/src/components/AppContent/components/Sidebar/consts.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { FC, ReactNode } from 'react'; -import { TFuncKey } from 'react-i18next'; - -import Contact from './components/Contact'; -import BarGraph from './icons/BarGraph'; -import Bolt from './icons/Bolt'; -import Box from './icons/Box'; -import Cloud from './icons/Cloud'; -import Connection from './icons/Connection'; -import ContactIcon from './icons/Contact'; -import Document from './icons/Document'; -import List from './icons/List'; -import UserProfile from './icons/UserProfile'; -import Web from './icons/Web'; - -type SidebarItem = { - Icon: FC; - title: TFuncKey<'translation', 'admin_console.tabs'>; - modal?: (isOpen: boolean, onCancel: () => void) => ReactNode; -}; - -type SidebarSection = { - title: TFuncKey<'translation', 'admin_console.tab_sections'>; - items: SidebarItem[]; -}; - -export const sections: SidebarSection[] = [ - { - title: 'overview', - items: [ - { - Icon: Bolt, - title: 'get_started', - }, - { - Icon: BarGraph, - title: 'dashboard', - }, - ], - }, - { - title: 'resource_management', - items: [ - { - Icon: Box, - title: 'applications', - }, - { - Icon: Cloud, - title: 'api_resources', - }, - { - Icon: Web, - title: 'sign_in_experience', - }, - { - Icon: Connection, - title: 'connectors', - }, - ], - }, - { - title: 'user_management', - items: [ - { - Icon: UserProfile, - title: 'users', - }, - { - Icon: List, - title: 'audit_logs', - }, - ], - }, - { - title: 'help_and_support', - items: [ - { - Icon: ContactIcon, - title: 'contact_us', - modal: (isOpen, onCancel) => , - }, - { - Icon: Document, - title: 'documentation', - }, - ], - }, -]; diff --git a/packages/console/src/components/AppContent/components/Sidebar/hook.tsx b/packages/console/src/components/AppContent/components/Sidebar/hook.tsx new file mode 100644 index 000000000..032ed38e3 --- /dev/null +++ b/packages/console/src/components/AppContent/components/Sidebar/hook.tsx @@ -0,0 +1,101 @@ +import React, { FC, ReactNode } from 'react'; +import { TFuncKey } from 'react-i18next'; + +import useAdminConsoleConfigs from '@/hooks/use-configs'; + +import Contact from './components/Contact'; +import BarGraph from './icons/BarGraph'; +import Bolt from './icons/Bolt'; +import Box from './icons/Box'; +import Cloud from './icons/Cloud'; +import Connection from './icons/Connection'; +import ContactIcon from './icons/Contact'; +import Document from './icons/Document'; +import List from './icons/List'; +import UserProfile from './icons/UserProfile'; +import Web from './icons/Web'; + +type SidebarItem = { + Icon: FC; + title: TFuncKey<'translation', 'admin_console.tabs'>; + isHidden?: boolean; + modal?: (isOpen: boolean, onCancel: () => void) => ReactNode; +}; + +type SidebarSection = { + title: TFuncKey<'translation', 'admin_console.tab_sections'>; + items: SidebarItem[]; +}; + +export const useSidebarMenuItems = (): SidebarSection[] | undefined => { + const { configs } = useAdminConsoleConfigs(); + + if (!configs) { + return; + } + + return [ + { + title: 'overview', + items: [ + { + Icon: Bolt, + title: 'get_started', + isHidden: configs.hideGetStarted, + }, + { + Icon: BarGraph, + title: 'dashboard', + }, + ], + }, + { + title: 'resource_management', + items: [ + { + Icon: Box, + title: 'applications', + }, + { + Icon: Cloud, + title: 'api_resources', + }, + { + Icon: Web, + title: 'sign_in_experience', + }, + { + Icon: Connection, + title: 'connectors', + }, + ], + }, + { + title: 'user_management', + items: [ + { + Icon: UserProfile, + title: 'users', + }, + { + Icon: List, + title: 'audit_logs', + }, + ], + }, + { + title: 'help_and_support', + items: [ + { + Icon: ContactIcon, + title: 'contact_us', + modal: (isOpen, onCancel) => , + }, + { + Icon: Document, + title: 'documentation', + }, + ], + }, + ]; +}; diff --git a/packages/console/src/components/AppContent/components/Sidebar/index.tsx b/packages/console/src/components/AppContent/components/Sidebar/index.tsx index 85c344940..9e4a40717 100644 --- a/packages/console/src/components/AppContent/components/Sidebar/index.tsx +++ b/packages/console/src/components/AppContent/components/Sidebar/index.tsx @@ -4,7 +4,7 @@ import { useLocation } from 'react-router-dom'; import Item from './components/Item'; import Section from './components/Section'; -import { sections } from './consts'; +import { useSidebarMenuItems } from './hook'; import Gear from './icons/Gear'; import * as styles from './index.module.scss'; import { getPath } from './utils'; @@ -14,20 +14,24 @@ const Sidebar = () => { keyPrefix: 'admin_console.tab_sections', }); const location = useLocation(); + const sections = useSidebarMenuItems(); return (
- {sections.map(({ title, items }) => ( + {sections?.map(({ title, items }) => (
- {items.map(({ title, Icon, modal }) => ( - } - isActive={location.pathname.startsWith(getPath(title))} - modal={modal} - /> - ))} + {items.map( + ({ title, Icon, isHidden, modal }) => + !isHidden && ( + } + isActive={location.pathname.startsWith(getPath(title))} + modal={modal} + /> + ) + )}
))}
@@ -42,5 +46,4 @@ const Sidebar = () => { export default Sidebar; -export * from './consts'; export * from './utils'; diff --git a/packages/console/src/components/ConfirmModal/index.tsx b/packages/console/src/components/ConfirmModal/index.tsx new file mode 100644 index 000000000..3bb36a903 --- /dev/null +++ b/packages/console/src/components/ConfirmModal/index.tsx @@ -0,0 +1,55 @@ +import { AdminConsoleKey, I18nKey } from '@logto/phrases'; +import React from 'react'; +import Modal from 'react-modal'; + +import * as modalStyles from '@/scss/modal.module.scss'; + +import Button from '../Button'; +import ModalLayout from '../ModalLayout'; + +type Props = { + title: AdminConsoleKey; + children: React.ReactNode; + className?: string; + confirmButtonText?: I18nKey; + cancelButtonText?: I18nKey; + isOpen: boolean; + isPending?: boolean; + onConfirm: () => void; + onCancel: () => void; +}; + +const ConfirmModal = ({ + title, + children, + className, + confirmButtonText = 'general.confirm', + cancelButtonText = 'general.cancel', + isOpen, + isPending, + onConfirm, + onCancel, +}: Props) => ( + + +
@@ -43,6 +62,17 @@ const GetStarted = () => { /> ))} + { + setShowConfirmModal(false); + }} + > + {t('get_started.confirm_message')} +
); }; diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index b9ccf913a..2e4027d9c 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -280,6 +280,8 @@ const translation = { subtitle_part1: 'Here are the following things you can do', subtitle_part2: 'I’m done with this set up. ', hide_this: 'Hide this', + confirm: 'Reminder', + confirm_message: 'Are you sure you want to hide this page? This action cannot be undone.', card1_title: 'Logto is a XXX for customer identity. Check out our demo', card1_subtitle: 'Setup a mobile, single page or traditional application to use Logto for Authentication.', diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index 8cf23ca03..5ab1470e4 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -277,6 +277,8 @@ const translation = { subtitle_part1: '下列是一些适合您快速上手的事情', subtitle_part2: '这配置页面我要看吐了,', hide_this: '退下!', + confirm: '确认提醒', + confirm_message: '您确认要隐藏该页面吗? 本操作将无法恢复。', card1_title: 'Logto 是您没有使用过的全新身份管理工具,来看看我们的 Demo 吧', card1_subtitle: '无论是 mobile 应用,SPA web 还是传统 web 应用,您都可以使用 Logto,快速创建和管理身份,实现登录验证服务。', diff --git a/packages/schemas/src/foundations/jsonb-types.ts b/packages/schemas/src/foundations/jsonb-types.ts index 914e24cff..e8f1b3b22 100644 --- a/packages/schemas/src/foundations/jsonb-types.ts +++ b/packages/schemas/src/foundations/jsonb-types.ts @@ -145,6 +145,7 @@ export const adminConsoleConfigGuard = z.object({ appearanceMode: z.nativeEnum(AppearanceMode), experienceNoticeConfirmed: z.boolean().optional(), experienceGuideDone: z.boolean().optional(), + hideGetStarted: z.boolean().optional(), // Get started challenges checkDemo: z.boolean(), createApplication: z.boolean(),