mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(console): refactor modals with modallayout (#435)
This commit is contained in:
parent
2e08ec9db0
commit
4f41162ac3
20 changed files with 211 additions and 226 deletions
9
.vscode/scss.code-snippets
vendored
9
.vscode/scss.code-snippets
vendored
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
margin-left: _.unit(4);
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font: var(--font-body);
|
||||
color: var(--color-primary);
|
||||
|
|
|
@ -13,7 +13,7 @@ type Props = {
|
|||
const ItemPreview = ({ title, subtitle, icon, to }: Props) => {
|
||||
return (
|
||||
<div className={styles.item}>
|
||||
{icon}
|
||||
{icon && <div className={styles.icon}>{icon}</div>}
|
||||
<div>
|
||||
{to && (
|
||||
<Link
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
@use '@/scss/dimensions' as dim;
|
||||
@use '@/scss/underscore' as _;
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 800px;
|
||||
max-width: dim.$modal-layout-max-width;
|
||||
padding: _.unit(8);
|
||||
|
||||
.header {
|
||||
|
|
|
@ -1,25 +1,10 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.card {
|
||||
padding: _.unit(8);
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.headline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
> *: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;
|
||||
}
|
||||
|
|
|
@ -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<FormData>();
|
||||
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<Resource>();
|
||||
onClose?.(createdApiResource);
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className={styles.card}>
|
||||
<div className={styles.headline}>
|
||||
<CardTitle title="api_resources.create" subtitle="api_resources.subtitle" />
|
||||
<IconButton size="large" onClick={() => onClose?.()}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</div>
|
||||
<form className={styles.form} onSubmit={onSubmit}>
|
||||
<ModalLayout
|
||||
title="api_resources.create"
|
||||
subtitle="api_resources.subtitle"
|
||||
footer={
|
||||
<div className={styles.submit}>
|
||||
<Button
|
||||
disabled={loading}
|
||||
htmlType="submit"
|
||||
title="admin_console.api_resources.create"
|
||||
size="large"
|
||||
type="primary"
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<form className={styles.form}>
|
||||
<FormField
|
||||
isRequired
|
||||
title="admin_console.api_resources.api_name"
|
||||
|
@ -58,16 +72,8 @@ const CreateForm = ({ onClose }: Props) => {
|
|||
>
|
||||
<TextInput {...register('indicator', { required: true })} />
|
||||
</FormField>
|
||||
<div className={styles.submit}>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
title="admin_console.api_resources.create"
|
||||
size="large"
|
||||
type="primary"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<Setting, RequestError>('/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<Application>();
|
||||
setCreatedApp(createdApp);
|
||||
|
@ -64,20 +68,30 @@ const CreateForm = ({ onClose }: Props) => {
|
|||
} else {
|
||||
setIsQuickStartGuideOpen(true);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className={styles.card}>
|
||||
<div className={styles.headline}>
|
||||
<CardTitle title="applications.create" subtitle="applications.subtitle" />
|
||||
<IconButton size="large" onClick={() => onClose?.()}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</div>
|
||||
<form className={styles.form} onSubmit={onSubmit}>
|
||||
<ModalLayout
|
||||
title="applications.create"
|
||||
subtitle="applications.subtitle"
|
||||
footer={
|
||||
<div className={styles.submit}>
|
||||
<Button
|
||||
disabled={loading}
|
||||
htmlType="submit"
|
||||
title="admin_console.applications.create"
|
||||
size="large"
|
||||
type="primary"
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<form className={styles.form}>
|
||||
<FormField title="admin_console.applications.select_application_type">
|
||||
<RadioGroup ref={ref} name={name} value={value} onChange={onChange}>
|
||||
{Object.values(ApplicationType).map((value) => (
|
||||
|
@ -106,21 +120,13 @@ const CreateForm = ({ onClose }: Props) => {
|
|||
>
|
||||
<TextInput {...register('description')} />
|
||||
</FormField>
|
||||
<div className={styles.submit}>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
title="admin_console.applications.create"
|
||||
size="large"
|
||||
type="primary"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
{!isGetStartedSkipped && createdApp && (
|
||||
<Modal isOpen={isQuickStartGuideOpen} className={modalStyles.fullScreen}>
|
||||
<GetStarted appName={createdApp.name} onClose={closeModal} />
|
||||
</Modal>
|
||||
)}
|
||||
</Card>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<ReactModal isOpen className={modalStyles.content} overlayClassName={modalStyles.overlay}>
|
||||
<Card className={styles.card}>
|
||||
<div className={styles.header}>
|
||||
<h1>{t('user_details.created_title')}</h1>
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
<ModalLayout
|
||||
title="user_details.created_title"
|
||||
footer={
|
||||
<div className={styles.footer}>
|
||||
<Button title="admin_console.user_details.created_button_close" onClick={handleClose} />
|
||||
<Button
|
||||
type="primary"
|
||||
title="admin_console.user_details.created_button_copy"
|
||||
onClick={handleCopy}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={styles.content}>
|
||||
<div>{t('user_details.created_guide')}</div>
|
||||
<div className={styles.info}>
|
||||
<div className={styles.infoLine}>
|
||||
|
@ -72,15 +81,7 @@ const CreateSuccess = ({ username }: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<Button title="admin_console.user_details.created_button_close" onClick={handleClose} />
|
||||
<Button
|
||||
type="primary"
|
||||
title="admin_console.user_details.created_button_copy"
|
||||
onClick={handleCopy}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</ModalLayout>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<Card className={styles.card}>
|
||||
<div className={styles.header}>
|
||||
<CardTitle title="user_details.delete_title" />
|
||||
<IconButton size="large" onClick={onClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
<ModalLayout
|
||||
title="user_details.delete_title"
|
||||
footer={
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
type="outline"
|
||||
title="admin_console.user_details.delete_cancel"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<Button
|
||||
disabled={loading}
|
||||
type="danger"
|
||||
title="admin_console.user_details.delete_confirm"
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={styles.content}>
|
||||
<div>{t('user_details.delete_description')}</div>
|
||||
</div>
|
||||
<div>{t('user_details.delete_description')}</div>
|
||||
<div className={styles.footer}>
|
||||
<Button type="outline" title="admin_console.user_details.delete_cancel" onClick={onClose} />
|
||||
<Button
|
||||
disabled={loading}
|
||||
type="danger"
|
||||
title="admin_console.user_details.delete_confirm"
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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<FormData>();
|
||||
const api = useApi();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
await api.patch(`/api/users/${userId}/password`, { json: data }).json<User>();
|
||||
onClose?.();
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await api.patch(`/api/users/${userId}/password`, { json: data }).json<User>();
|
||||
onClose?.();
|
||||
toast.success(t('user_details.reset_password.reset_password_success'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className={styles.card}>
|
||||
<div className={styles.headline}>
|
||||
<CardTitle title="user_details.reset_password.title" />
|
||||
<IconButton size="large" onClick={() => onClose?.()}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</div>
|
||||
<ModalLayout title="user_details.reset_password.title" onClose={onClose}>
|
||||
<form className={styles.form} onSubmit={onSubmit}>
|
||||
<FormField
|
||||
isRequired
|
||||
|
@ -49,6 +54,7 @@ const ResetPasswordForm = ({ onClose, userId }: Props) => {
|
|||
</FormField>
|
||||
<div className={styles.submit}>
|
||||
<Button
|
||||
disabled={loading}
|
||||
htmlType="submit"
|
||||
title="admin_console.user_details.reset_password.reset_password"
|
||||
size="large"
|
||||
|
@ -56,7 +62,7 @@ const ResetPasswordForm = ({ onClose, userId }: Props) => {
|
|||
/>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<FormData>();
|
||||
const api = useApi();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
const createdUser = await api.post('/api/users', { json: data }).json<User>();
|
||||
onClose?.(createdUser, btoa(data.password));
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const createdUser = await api.post('/api/users', { json: data }).json<User>();
|
||||
onClose?.(createdUser, btoa(data.password));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className={styles.card}>
|
||||
<div className={styles.headline}>
|
||||
<CardTitle title="users.create" subtitle="users.subtitle" />
|
||||
<IconButton size="large" onClick={() => onClose?.()}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</div>
|
||||
<form className={styles.form} onSubmit={onSubmit}>
|
||||
<ModalLayout
|
||||
title="users.create"
|
||||
subtitle="users.subtitle"
|
||||
footer={
|
||||
<div className={styles.submit}>
|
||||
<Button
|
||||
disabled={loading}
|
||||
htmlType="submit"
|
||||
title="admin_console.users.create"
|
||||
size="large"
|
||||
type="primary"
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<form className={styles.form}>
|
||||
<FormField
|
||||
isRequired
|
||||
title="admin_console.users.create_form_username"
|
||||
|
@ -62,16 +81,8 @@ const CreateForm = ({ onClose }: Props) => {
|
|||
>
|
||||
<TextInput {...register('password', { required: true })} />
|
||||
</FormField>
|
||||
<div className={styles.submit}>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
title="admin_console.users.create"
|
||||
size="large"
|
||||
type="primary"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
3
packages/console/src/scss/_dimensions.scss
Normal file
3
packages/console/src/scss/_dimensions.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
$modal-layout-max-width: 800px;
|
||||
$modal-layout-min-width: 400px;
|
||||
$form-text-field-width: 556px;
|
|
@ -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 {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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: '用户日志',
|
||||
|
|
Loading…
Reference in a new issue