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}
+ >
+
{!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?.()}>
-
-
-
-
+
);
};
diff --git a/packages/console/src/scss/_dimensions.scss b/packages/console/src/scss/_dimensions.scss
new file mode 100644
index 000000000..a23649d7f
--- /dev/null
+++ b/packages/console/src/scss/_dimensions.scss
@@ -0,0 +1,3 @@
+$modal-layout-max-width: 800px;
+$modal-layout-min-width: 400px;
+$form-text-field-width: 556px;
diff --git a/packages/console/src/scss/_underscore.scss b/packages/console/src/scss/_underscore.scss
index 82ec3ef5c..d439ddd40 100644
--- a/packages/console/src/scss/_underscore.scss
+++ b/packages/console/src/scss/_underscore.scss
@@ -1,3 +1,5 @@
+@use '@/scss/dimensions' as dim;
+
@function unit($factor: 1, $unit: 'px') {
@return #{$factor * 4}#{$unit};
}
@@ -16,7 +18,7 @@
}
@mixin form-text-field {
- width: 556px;
+ width: dim.$form-text-field-width;
}
@mixin vertical-bar {
diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts
index 6362b8950..fce29c149 100644
--- a/packages/phrases/src/locales/en.ts
+++ b/packages/phrases/src/locales/en.ts
@@ -135,6 +135,7 @@ const translation = {
create: 'Create API Resource',
api_name: 'API Name',
api_identifier: 'API Identifier',
+ api_resource_created: 'The API resource {{name}} has been successfully created!',
},
api_resource_details: {
back_to_api_resources: 'Back to my API resources',
@@ -222,6 +223,7 @@ const translation = {
title: 'Reset Password',
label: 'New password:',
reset_password: 'Reset password',
+ reset_password_success: 'Reset password successfully.',
},
tab_settings: 'Settings',
tab_logs: 'User Logs',
diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts
index 4546cd2f5..059f720a5 100644
--- a/packages/phrases/src/locales/zh-cn.ts
+++ b/packages/phrases/src/locales/zh-cn.ts
@@ -135,6 +135,7 @@ const translation = {
create: 'Create API Resource',
api_name: 'API Name',
api_identifier: 'API Identifier',
+ api_resource_created: 'The API resource {{name}} has been successfully created!',
},
api_resource_details: {
back_to_api_resources: 'Back to my API resources',
@@ -221,6 +222,7 @@ const translation = {
title: '重置密码',
label: '新密码:',
reset_password: '重置密码',
+ reset_password_success: '密码已成功重置。',
},
tab_settings: '设置',
tab_logs: '用户日志',