mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(console): create user process (#2698)
This commit is contained in:
parent
3daf30835a
commit
5743515804
4 changed files with 78 additions and 80 deletions
|
@ -4,4 +4,3 @@ export * from './logs';
|
|||
|
||||
export const themeStorageKey = 'logto:admin_console:theme';
|
||||
export const requestTimeout = 20_000;
|
||||
export const generatedPasswordStorageKey = 'logto:admin_console:generated_password';
|
||||
|
|
|
@ -19,7 +19,6 @@ import DetailsSkeleton from '@/components/DetailsSkeleton';
|
|||
import TabNav, { TabNavItem } from '@/components/TabNav';
|
||||
import TextLink from '@/components/TextLink';
|
||||
import UserAvatar from '@/components/UserAvatar';
|
||||
import { generatedPasswordStorageKey } from '@/consts';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import * as detailsStyles from '@/scss/details.module.scss';
|
||||
|
@ -42,7 +41,6 @@ const UserDetails = () => {
|
|||
const [isDeleted, setIsDeleted] = useState(false);
|
||||
const [isResetPasswordFormOpen, setIsResetPasswordFormOpen] = useState(false);
|
||||
const [resetResult, setResetResult] = useState<string>();
|
||||
const [password, setPassword] = useState(sessionStorage.getItem(generatedPasswordStorageKey));
|
||||
|
||||
const { data, error, mutate } = useSWR<User, RequestError>(userId && `/api/users/${userId}`);
|
||||
const isLoading = !data && !error;
|
||||
|
@ -176,17 +174,6 @@ const UserDetails = () => {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
{data && password && (
|
||||
<CreateSuccess
|
||||
title="user_details.created_title"
|
||||
username={data.username ?? '-'}
|
||||
password={password}
|
||||
onClose={() => {
|
||||
setPassword(null);
|
||||
sessionStorage.removeItem(generatedPasswordStorageKey);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{data && resetResult && (
|
||||
<CreateSuccess
|
||||
title="user_details.reset_password.congratulations"
|
||||
|
|
|
@ -1,31 +1,44 @@
|
|||
import { usernameRegEx } from '@logto/core-kit';
|
||||
import type { User } from '@logto/schemas';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Modal from 'react-modal';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import FormField from '@/components/FormField';
|
||||
import ModalLayout from '@/components/ModalLayout';
|
||||
import TextInput from '@/components/TextInput';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import CreateSuccess from '@/pages/UserDetails/components/CreateSuccess';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
type FormData = {
|
||||
username: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type CreatedUserInfo = {
|
||||
user: User;
|
||||
password: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
onClose?: (createdUser?: User, password?: string) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const CreateForm = ({ onClose }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const navigate = useNavigate();
|
||||
const [createdUserInfo, setCreatedUserInfo] = useState<CreatedUserInfo>();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm<FormData>();
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const api = useApi();
|
||||
|
||||
|
@ -37,49 +50,70 @@ const CreateForm = ({ onClose }: Props) => {
|
|||
const password = nanoid(8);
|
||||
|
||||
const createdUser = await api.post('/api/users', { json: { ...data, password } }).json<User>();
|
||||
onClose?.(createdUser, password);
|
||||
|
||||
setCreatedUserInfo({
|
||||
user: createdUser,
|
||||
password,
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<ModalLayout
|
||||
title="users.create"
|
||||
footer={
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
htmlType="submit"
|
||||
title="users.create"
|
||||
size="large"
|
||||
type="primary"
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
}
|
||||
onClose={onClose}
|
||||
return createdUserInfo ? (
|
||||
<CreateSuccess
|
||||
title="user_details.created_title"
|
||||
username={createdUserInfo.user.username ?? '-'}
|
||||
password={createdUserInfo.password}
|
||||
onClose={() => {
|
||||
navigate(`/users/${createdUserInfo.user.id}`, { replace: true });
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Modal
|
||||
shouldCloseOnEsc
|
||||
isOpen
|
||||
className={modalStyles.content}
|
||||
overlayClassName={modalStyles.overlay}
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<form>
|
||||
<FormField isRequired title="users.create_form_username">
|
||||
<TextInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
{...register('username', {
|
||||
required: true,
|
||||
pattern: {
|
||||
value: usernameRegEx,
|
||||
message: t('errors.username_pattern_error'),
|
||||
},
|
||||
})}
|
||||
hasError={Boolean(errors.username)}
|
||||
errorMessage={errors.username?.message}
|
||||
<ModalLayout
|
||||
title="users.create"
|
||||
footer={
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
htmlType="submit"
|
||||
title="users.create"
|
||||
size="large"
|
||||
type="primary"
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title="users.create_form_name">
|
||||
<TextInput
|
||||
{...register('name')}
|
||||
hasError={Boolean(errors.name)}
|
||||
errorMessage={errors.name?.message}
|
||||
/>
|
||||
</FormField>
|
||||
</form>
|
||||
</ModalLayout>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<form>
|
||||
<FormField isRequired title="users.create_form_username">
|
||||
<TextInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
{...register('username', {
|
||||
required: true,
|
||||
pattern: {
|
||||
value: usernameRegEx,
|
||||
message: t('errors.username_pattern_error'),
|
||||
},
|
||||
})}
|
||||
hasError={Boolean(errors.username)}
|
||||
errorMessage={errors.username?.message}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title="users.create_form_name">
|
||||
<TextInput
|
||||
{...register('name')}
|
||||
hasError={Boolean(errors.name)}
|
||||
errorMessage={errors.name?.message}
|
||||
/>
|
||||
</FormField>
|
||||
</form>
|
||||
</ModalLayout>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import type { User } from '@logto/schemas';
|
|||
import { conditional, conditionalString } from '@silverhand/essentials';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Modal from 'react-modal';
|
||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
||||
|
@ -18,9 +17,7 @@ import TableEmpty from '@/components/Table/TableEmpty';
|
|||
import TableError from '@/components/Table/TableError';
|
||||
import TableLoading from '@/components/Table/TableLoading';
|
||||
import UserAvatar from '@/components/UserAvatar';
|
||||
import { generatedPasswordStorageKey } from '@/consts';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
import * as resourcesStyles from '@/scss/resources.module.scss';
|
||||
import * as tableStyles from '@/scss/table.module.scss';
|
||||
|
||||
|
@ -68,36 +65,17 @@ const Users = () => {
|
|||
});
|
||||
}}
|
||||
/>
|
||||
<Modal
|
||||
shouldCloseOnEsc
|
||||
isOpen={isCreateNew}
|
||||
className={modalStyles.content}
|
||||
overlayClassName={modalStyles.overlay}
|
||||
onRequestClose={() => {
|
||||
navigate({
|
||||
pathname: usersPathname,
|
||||
search,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isCreateNew && (
|
||||
<CreateForm
|
||||
onClose={(createdUser, password) => {
|
||||
if (createdUser && password) {
|
||||
sessionStorage.setItem(generatedPasswordStorageKey, password);
|
||||
navigate(buildDetailsPathname(createdUser.id), { replace: true });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
onClose={() => {
|
||||
navigate({
|
||||
pathname: usersPathname,
|
||||
search,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={classNames(resourcesStyles.table, styles.tableLayout)}>
|
||||
<div className={styles.filter}>
|
||||
<Search
|
||||
|
|
Loading…
Reference in a new issue