From 4f41162ac373c4e3210e7aeb5c0b69a7c72bc3d9 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Tue, 22 Mar 2022 17:40:06 +0800 Subject: [PATCH] refactor(console): refactor modals with modallayout (#435) --- .vscode/scss.code-snippets | 9 +++ .../components/ItemPreview/index.module.scss | 4 ++ .../src/components/ItemPreview/index.tsx | 2 +- .../components/ModalLayout/index.module.scss | 3 +- .../components/CreateForm/index.module.scss | 26 ++------ .../components/CreateForm/index.tsx | 54 +++++++++-------- .../console/src/pages/ApiResources/index.tsx | 4 ++ .../components/CreateForm/index.module.scss | 22 +------ .../components/CreateForm/index.tsx | 52 ++++++++-------- .../components/CreateSuccess.module.scss | 16 ++--- .../UserDetails/components/CreateSuccess.tsx | 31 +++++----- .../components/DeleteForm.module.scss | 36 ++++------- .../UserDetails/components/DeleteForm.tsx | 44 +++++++------- .../components/ResetPasswordForm.tsx | 36 ++++++----- .../components/CreateForm/index.module.scss | 28 +-------- .../Users/components/CreateForm/index.tsx | 59 +++++++++++-------- packages/console/src/scss/_dimensions.scss | 3 + packages/console/src/scss/_underscore.scss | 4 +- packages/phrases/src/locales/en.ts | 2 + packages/phrases/src/locales/zh-cn.ts | 2 + 20 files changed, 211 insertions(+), 226 deletions(-) create mode 100644 packages/console/src/scss/_dimensions.scss diff --git a/.vscode/scss.code-snippets b/.vscode/scss.code-snippets index 408134bbc..7e8d64895 100644 --- a/.vscode/scss.code-snippets +++ b/.vscode/scss.code-snippets @@ -30,5 +30,14 @@ "_.unit(${1:2})" ], "description": "Use underscore unit function." + }, + "Use dimensions": { + "scope": "scss", + "prefix": "used", + "body": [ + "@use '@/scss/dimensions' as dim;", + "$0" + ], + "description": "Add @use dimensions in header." } } diff --git a/packages/console/src/components/ItemPreview/index.module.scss b/packages/console/src/components/ItemPreview/index.module.scss index 5fda44c9c..2d7c57add 100644 --- a/packages/console/src/components/ItemPreview/index.module.scss +++ b/packages/console/src/components/ItemPreview/index.module.scss @@ -9,6 +9,10 @@ margin-left: _.unit(4); } + .icon { + flex-shrink: 0; + } + .title { font: var(--font-body); color: var(--color-primary); diff --git a/packages/console/src/components/ItemPreview/index.tsx b/packages/console/src/components/ItemPreview/index.tsx index b8210cced..28a53ad70 100644 --- a/packages/console/src/components/ItemPreview/index.tsx +++ b/packages/console/src/components/ItemPreview/index.tsx @@ -13,7 +13,7 @@ type Props = { const ItemPreview = ({ title, subtitle, icon, to }: Props) => { return (
- {icon} + {icon &&
{icon}
}
{to && ( *:not(:first-child) { - margin-left: _.unit(3); - } - - > svg { - cursor: pointer; - } -} +@use '@/scss/dimensions' as dim; .form { - margin-top: _.unit(8); + padding: _.unit(8) 0; + display: flex; + align-items: flex-end; } .textField { @@ -27,6 +12,5 @@ } .submit { - margin-top: _.unit(8); - text-align: right; + padding-left: dim.$form-text-field-width; } diff --git a/packages/console/src/pages/ApiResources/components/CreateForm/index.tsx b/packages/console/src/pages/ApiResources/components/CreateForm/index.tsx index 738d60698..5e6b75031 100644 --- a/packages/console/src/pages/ApiResources/components/CreateForm/index.tsx +++ b/packages/console/src/pages/ApiResources/components/CreateForm/index.tsx @@ -1,15 +1,12 @@ import { Resource } from '@logto/schemas'; -import React from 'react'; +import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; import Button from '@/components/Button'; -import Card from '@/components/Card'; -import CardTitle from '@/components/CardTitle'; import FormField from '@/components/FormField'; -import IconButton from '@/components/IconButton'; +import ModalLayout from '@/components/ModalLayout'; import TextInput from '@/components/TextInput'; import useApi from '@/hooks/use-api'; -import Close from '@/icons/Close'; import * as styles from './index.module.scss'; @@ -24,26 +21,43 @@ type Props = { const CreateForm = ({ onClose }: Props) => { const { handleSubmit, register } = useForm(); + const [loading, setLoading] = useState(false); const api = useApi(); const onSubmit = handleSubmit(async (data) => { + if (loading) { + return; + } + + setLoading(true); + try { const createdApiResource = await api.post('/api/resources', { json: data }).json(); onClose?.(createdApiResource); - } catch (error: unknown) { - console.error(error); + } finally { + setLoading(false); } }); return ( - -
- - onClose?.()}> - - -
-
+ +
+ } + onClose={onClose} + > + { > -
-
- + ); }; diff --git a/packages/console/src/pages/ApiResources/index.tsx b/packages/console/src/pages/ApiResources/index.tsx index d3bd212f7..5fdbbb513 100644 --- a/packages/console/src/pages/ApiResources/index.tsx +++ b/packages/console/src/pages/ApiResources/index.tsx @@ -1,6 +1,7 @@ import { Resource } from '@logto/schemas'; import { conditional } from '@silverhand/essentials/lib/utilities/conditional.js'; import React, { useState } from 'react'; +import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import Modal from 'react-modal'; import { useNavigate } from 'react-router-dom'; @@ -49,6 +50,9 @@ const ApiResources = () => { if (createdApiResource) { void mutate(conditional(data && [...data, createdApiResource])); + toast.success( + t('api_resources.api_resource_created', { name: createdApiResource.name }) + ); navigate(buildDetailsLink(createdApiResource.id)); } }} diff --git a/packages/console/src/pages/Applications/components/CreateForm/index.module.scss b/packages/console/src/pages/Applications/components/CreateForm/index.module.scss index fb6257bb3..b9ff68230 100644 --- a/packages/console/src/pages/Applications/components/CreateForm/index.module.scss +++ b/packages/console/src/pages/Applications/components/CreateForm/index.module.scss @@ -1,24 +1,9 @@ @use '@/scss/underscore' as _; +@use '@/scss/dimensions' as dim; -.card { - padding: _.unit(8); -} - -.headline { - display: flex; - justify-content: space-between; - - > *:not(:first-child) { - margin-left: _.unit(3); - } - - > svg { - cursor: pointer; - } -} .form { - margin-top: _.unit(8); + padding: _.unit(8) 0; } .textField { @@ -26,8 +11,7 @@ } .submit { - margin-top: _.unit(8); - text-align: right; + padding-left: dim.$form-text-field-width; } .error { diff --git a/packages/console/src/pages/Applications/components/CreateForm/index.tsx b/packages/console/src/pages/Applications/components/CreateForm/index.tsx index 5a4e239e0..c0ea587ad 100644 --- a/packages/console/src/pages/Applications/components/CreateForm/index.tsx +++ b/packages/console/src/pages/Applications/components/CreateForm/index.tsx @@ -6,14 +6,11 @@ import Modal from 'react-modal'; import useSWR from 'swr'; import Button from '@/components/Button'; -import Card from '@/components/Card'; -import CardTitle from '@/components/CardTitle'; import FormField from '@/components/FormField'; -import IconButton from '@/components/IconButton'; +import ModalLayout from '@/components/ModalLayout'; import RadioGroup, { Radio } from '@/components/RadioGroup'; import TextInput from '@/components/TextInput'; import useApi, { RequestError } from '@/hooks/use-api'; -import Close from '@/icons/Close'; import * as modalStyles from '@/scss/modal.module.scss'; import { applicationTypeI18nKey } from '@/types/applications'; @@ -46,6 +43,7 @@ const CreateForm = ({ onClose }: Props) => { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { data: setting } = useSWR('/api/settings'); const api = useApi(); + const [loading, setLoading] = useState(false); const isGetStartedSkipped = setting?.adminConsole.applicationSkipGetStarted; @@ -55,6 +53,12 @@ const CreateForm = ({ onClose }: Props) => { }; const onSubmit = handleSubmit(async (data) => { + if (loading) { + return; + } + + setLoading(true); + try { const createdApp = await api.post('/api/applications', { json: data }).json(); setCreatedApp(createdApp); @@ -64,20 +68,30 @@ const CreateForm = ({ onClose }: Props) => { } else { setIsQuickStartGuideOpen(true); } - } catch (error: unknown) { - console.error(error); + } finally { + setLoading(false); } }); return ( - -
- - onClose?.()}> - - -
-
+ +
+ } + onClose={onClose} + > + {Object.values(ApplicationType).map((value) => ( @@ -106,21 +120,13 @@ const CreateForm = ({ onClose }: Props) => { > -
-
{!isGetStartedSkipped && createdApp && ( )} - + ); }; diff --git a/packages/console/src/pages/UserDetails/components/CreateSuccess.module.scss b/packages/console/src/pages/UserDetails/components/CreateSuccess.module.scss index 3462f6f8c..60812fd03 100644 --- a/packages/console/src/pages/UserDetails/components/CreateSuccess.module.scss +++ b/packages/console/src/pages/UserDetails/components/CreateSuccess.module.scss @@ -1,15 +1,10 @@ @use '@/scss/underscore' as _; +@use '@/scss/dimensions' as dim; -.card { - min-width: _.unit(100); -} -.header h1 { - font: var(--font-title-large); - margin-top: 0; -} - -.body { +.content { + padding: _.unit(8) 0; + min-width: dim.$modal-layout-min-width; font: var(--font-body-2); .info { @@ -39,9 +34,6 @@ } .footer { - border-top: 1px solid var(--color-neutral-80); - margin-top: _.unit(6); - padding-top: _.unit(6); display: flex; justify-content: right; diff --git a/packages/console/src/pages/UserDetails/components/CreateSuccess.tsx b/packages/console/src/pages/UserDetails/components/CreateSuccess.tsx index 29980b1db..65a6cd48f 100644 --- a/packages/console/src/pages/UserDetails/components/CreateSuccess.tsx +++ b/packages/console/src/pages/UserDetails/components/CreateSuccess.tsx @@ -5,8 +5,8 @@ import ReactModal from 'react-modal'; import { useSearchParams } from 'react-router-dom'; import Button from '@/components/Button'; -import Card from '@/components/Card'; import IconButton from '@/components/IconButton'; +import ModalLayout from '@/components/ModalLayout'; import Eye from '@/icons/Eye'; import * as modalStyles from '@/scss/modal.module.scss'; @@ -44,11 +44,20 @@ const CreateSuccess = ({ username }: Props) => { return ( - -
-

{t('user_details.created_title')}

-
-
+ +
+ } + > +
{t('user_details.created_guide')}
@@ -72,15 +81,7 @@ const CreateSuccess = ({ username }: Props) => {
-
-
-
+
); }; diff --git a/packages/console/src/pages/UserDetails/components/DeleteForm.module.scss b/packages/console/src/pages/UserDetails/components/DeleteForm.module.scss index 3e66c3469..7e934c700 100644 --- a/packages/console/src/pages/UserDetails/components/DeleteForm.module.scss +++ b/packages/console/src/pages/UserDetails/components/DeleteForm.module.scss @@ -1,29 +1,17 @@ @use '@/scss/underscore' as _; +@use '@/scss/dimensions' as dim; -.card { - padding: _.unit(8); - min-width: 400px; - > :not(:first-child) { - margin-top: _.unit(6); - } - - .header { - display: flex; - align-items: center; - justify-content: space-between; - } - - .footer { - border-top: 1px solid var(--color-neutral-80); - margin-top: _.unit(6); - padding-top: _.unit(6); - display: flex; - justify-content: right; - - button:not(:last-child) { - margin-right: _.unit(2); - } - } +.content { + min-width: dim.$modal-layout-min-width; + padding: _.unit(8) 0; } +.footer { + display: flex; + justify-content: right; + + button:not(:last-child) { + margin-right: _.unit(2); + } +} diff --git a/packages/console/src/pages/UserDetails/components/DeleteForm.tsx b/packages/console/src/pages/UserDetails/components/DeleteForm.tsx index 5d130d415..4b57d14a8 100644 --- a/packages/console/src/pages/UserDetails/components/DeleteForm.tsx +++ b/packages/console/src/pages/UserDetails/components/DeleteForm.tsx @@ -4,11 +4,8 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import Button from '@/components/Button'; -import Card from '@/components/Card'; -import CardTitle from '@/components/CardTitle'; -import IconButton from '@/components/IconButton'; +import ModalLayout from '@/components/ModalLayout'; import useApi from '@/hooks/use-api'; -import Close from '@/icons/Close'; import * as styles from './DeleteForm.module.scss'; @@ -39,24 +36,29 @@ const DeleteForm = ({ id, onClose }: Props) => { }; return ( - -
- - - - + +
+ } + onClose={onClose} + > +
+
{t('user_details.delete_description')}
-
{t('user_details.delete_description')}
-
-
-
+ ); }; diff --git a/packages/console/src/pages/UserDetails/components/ResetPasswordForm.tsx b/packages/console/src/pages/UserDetails/components/ResetPasswordForm.tsx index 72f51a306..68f421b76 100644 --- a/packages/console/src/pages/UserDetails/components/ResetPasswordForm.tsx +++ b/packages/console/src/pages/UserDetails/components/ResetPasswordForm.tsx @@ -1,15 +1,14 @@ import { User } from '@logto/schemas'; -import React from 'react'; +import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; +import { toast } from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; import Button from '@/components/Button'; -import Card from '@/components/Card'; -import CardTitle from '@/components/CardTitle'; import FormField from '@/components/FormField'; -import IconButton from '@/components/IconButton'; +import ModalLayout from '@/components/ModalLayout'; import TextInput from '@/components/TextInput'; import useApi from '@/hooks/use-api'; -import Close from '@/icons/Close'; import * as styles from './ResetPasswordForm.module.scss'; @@ -23,22 +22,28 @@ type Props = { }; const ResetPasswordForm = ({ onClose, userId }: Props) => { + const { t } = useTranslation(undefined, { + keyPrefix: 'admin_console', + }); const { handleSubmit, register } = useForm(); const api = useApi(); + const [loading, setLoading] = useState(false); + const onSubmit = handleSubmit(async (data) => { - await api.patch(`/api/users/${userId}/password`, { json: data }).json(); - onClose?.(); + setLoading(true); + + try { + await api.patch(`/api/users/${userId}/password`, { json: data }).json(); + onClose?.(); + toast.success(t('user_details.reset_password.reset_password_success')); + } finally { + setLoading(false); + } }); return ( - -
- - onClose?.()}> - - -
+
{
-
+ ); }; diff --git a/packages/console/src/pages/Users/components/CreateForm/index.module.scss b/packages/console/src/pages/Users/components/CreateForm/index.module.scss index fb6257bb3..f67abb39c 100644 --- a/packages/console/src/pages/Users/components/CreateForm/index.module.scss +++ b/packages/console/src/pages/Users/components/CreateForm/index.module.scss @@ -1,24 +1,9 @@ @use '@/scss/underscore' as _; +@use '@/scss/dimensions' as dim; -.card { - padding: _.unit(8); -} - -.headline { - display: flex; - justify-content: space-between; - - > *:not(:first-child) { - margin-left: _.unit(3); - } - - > svg { - cursor: pointer; - } -} .form { - margin-top: _.unit(8); + padding: _.unit(8) 0; } .textField { @@ -26,12 +11,5 @@ } .submit { - margin-top: _.unit(8); - text-align: right; -} - -.error { - font: var(--font-body-2); - color: var(--color-error); - margin-top: _.unit(2); + padding-left: dim.$form-text-field-width; } diff --git a/packages/console/src/pages/Users/components/CreateForm/index.tsx b/packages/console/src/pages/Users/components/CreateForm/index.tsx index 9894e8f65..4c765ab01 100644 --- a/packages/console/src/pages/Users/components/CreateForm/index.tsx +++ b/packages/console/src/pages/Users/components/CreateForm/index.tsx @@ -1,15 +1,12 @@ import { User } from '@logto/schemas'; -import React from 'react'; +import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; import Button from '@/components/Button'; -import Card from '@/components/Card'; -import CardTitle from '@/components/CardTitle'; import FormField from '@/components/FormField'; -import IconButton from '@/components/IconButton'; +import ModalLayout from '@/components/ModalLayout'; import TextInput from '@/components/TextInput'; import useApi from '@/hooks/use-api'; -import Close from '@/icons/Close'; import * as styles from './index.module.scss'; @@ -27,20 +24,42 @@ const CreateForm = ({ onClose }: Props) => { const { handleSubmit, register } = useForm(); const api = useApi(); + const [loading, setLoading] = useState(false); + const onSubmit = handleSubmit(async (data) => { - const createdUser = await api.post('/api/users', { json: data }).json(); - onClose?.(createdUser, btoa(data.password)); + if (loading) { + return; + } + + setLoading(true); + + try { + const createdUser = await api.post('/api/users', { json: data }).json(); + onClose?.(createdUser, btoa(data.password)); + } finally { + setLoading(false); + } }); return ( - -
- - onClose?.()}> - - -
-
+ +