0
Fork 0
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:
Xiao Yijun 2022-12-22 11:15:39 +08:00 committed by GitHub
parent 3daf30835a
commit 5743515804
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 78 additions and 80 deletions

View file

@ -4,4 +4,3 @@ export * from './logs';
export const themeStorageKey = 'logto:admin_console:theme'; export const themeStorageKey = 'logto:admin_console:theme';
export const requestTimeout = 20_000; export const requestTimeout = 20_000;
export const generatedPasswordStorageKey = 'logto:admin_console:generated_password';

View file

@ -19,7 +19,6 @@ import DetailsSkeleton from '@/components/DetailsSkeleton';
import TabNav, { TabNavItem } from '@/components/TabNav'; import TabNav, { TabNavItem } from '@/components/TabNav';
import TextLink from '@/components/TextLink'; import TextLink from '@/components/TextLink';
import UserAvatar from '@/components/UserAvatar'; import UserAvatar from '@/components/UserAvatar';
import { generatedPasswordStorageKey } from '@/consts';
import type { RequestError } from '@/hooks/use-api'; import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api'; import useApi from '@/hooks/use-api';
import * as detailsStyles from '@/scss/details.module.scss'; import * as detailsStyles from '@/scss/details.module.scss';
@ -42,7 +41,6 @@ const UserDetails = () => {
const [isDeleted, setIsDeleted] = useState(false); const [isDeleted, setIsDeleted] = useState(false);
const [isResetPasswordFormOpen, setIsResetPasswordFormOpen] = useState(false); const [isResetPasswordFormOpen, setIsResetPasswordFormOpen] = useState(false);
const [resetResult, setResetResult] = useState<string>(); const [resetResult, setResetResult] = useState<string>();
const [password, setPassword] = useState(sessionStorage.getItem(generatedPasswordStorageKey));
const { data, error, mutate } = useSWR<User, RequestError>(userId && `/api/users/${userId}`); const { data, error, mutate } = useSWR<User, RequestError>(userId && `/api/users/${userId}`);
const isLoading = !data && !error; 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 && ( {data && resetResult && (
<CreateSuccess <CreateSuccess
title="user_details.reset_password.congratulations" title="user_details.reset_password.congratulations"

View file

@ -1,31 +1,44 @@
import { usernameRegEx } from '@logto/core-kit'; import { usernameRegEx } from '@logto/core-kit';
import type { User } from '@logto/schemas'; import type { User } from '@logto/schemas';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Modal from 'react-modal';
import { useNavigate } from 'react-router-dom';
import Button from '@/components/Button'; import Button from '@/components/Button';
import FormField from '@/components/FormField'; import FormField from '@/components/FormField';
import ModalLayout from '@/components/ModalLayout'; import ModalLayout from '@/components/ModalLayout';
import TextInput from '@/components/TextInput'; import TextInput from '@/components/TextInput';
import useApi from '@/hooks/use-api'; import useApi from '@/hooks/use-api';
import CreateSuccess from '@/pages/UserDetails/components/CreateSuccess';
import * as modalStyles from '@/scss/modal.module.scss';
type FormData = { type FormData = {
username: string; username: string;
name: string; name: string;
}; };
type CreatedUserInfo = {
user: User;
password: string;
};
type Props = { type Props = {
onClose?: (createdUser?: User, password?: string) => void; onClose: () => void;
}; };
const CreateForm = ({ onClose }: Props) => { const CreateForm = ({ onClose }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const navigate = useNavigate();
const [createdUserInfo, setCreatedUserInfo] = useState<CreatedUserInfo>();
const { const {
handleSubmit, handleSubmit,
register, register,
formState: { isSubmitting, errors }, formState: { isSubmitting, errors },
} = useForm<FormData>(); } = useForm<FormData>();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const api = useApi(); const api = useApi();
@ -37,49 +50,70 @@ const CreateForm = ({ onClose }: Props) => {
const password = nanoid(8); const password = nanoid(8);
const createdUser = await api.post('/api/users', { json: { ...data, password } }).json<User>(); const createdUser = await api.post('/api/users', { json: { ...data, password } }).json<User>();
onClose?.(createdUser, password);
setCreatedUserInfo({
user: createdUser,
password,
});
}); });
return ( return createdUserInfo ? (
<ModalLayout <CreateSuccess
title="users.create" title="user_details.created_title"
footer={ username={createdUserInfo.user.username ?? '-'}
<Button password={createdUserInfo.password}
disabled={isSubmitting} onClose={() => {
htmlType="submit" navigate(`/users/${createdUserInfo.user.id}`, { replace: true });
title="users.create" }}
size="large" />
type="primary" ) : (
onClick={onSubmit} <Modal
/> shouldCloseOnEsc
} isOpen
onClose={onClose} className={modalStyles.content}
overlayClassName={modalStyles.overlay}
onRequestClose={onClose}
> >
<form> <ModalLayout
<FormField isRequired title="users.create_form_username"> title="users.create"
<TextInput footer={
// eslint-disable-next-line jsx-a11y/no-autofocus <Button
autoFocus disabled={isSubmitting}
{...register('username', { htmlType="submit"
required: true, title="users.create"
pattern: { size="large"
value: usernameRegEx, type="primary"
message: t('errors.username_pattern_error'), onClick={onSubmit}
},
})}
hasError={Boolean(errors.username)}
errorMessage={errors.username?.message}
/> />
</FormField> }
<FormField title="users.create_form_name"> onClose={onClose}
<TextInput >
{...register('name')} <form>
hasError={Boolean(errors.name)} <FormField isRequired title="users.create_form_username">
errorMessage={errors.name?.message} <TextInput
/> // eslint-disable-next-line jsx-a11y/no-autofocus
</FormField> autoFocus
</form> {...register('username', {
</ModalLayout> 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>
); );
}; };

View file

@ -2,7 +2,6 @@ import type { User } from '@logto/schemas';
import { conditional, conditionalString } from '@silverhand/essentials'; import { conditional, conditionalString } from '@silverhand/essentials';
import classNames from 'classnames'; import classNames from 'classnames';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Modal from 'react-modal';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import useSWR from 'swr'; import useSWR from 'swr';
@ -18,9 +17,7 @@ import TableEmpty from '@/components/Table/TableEmpty';
import TableError from '@/components/Table/TableError'; import TableError from '@/components/Table/TableError';
import TableLoading from '@/components/Table/TableLoading'; import TableLoading from '@/components/Table/TableLoading';
import UserAvatar from '@/components/UserAvatar'; import UserAvatar from '@/components/UserAvatar';
import { generatedPasswordStorageKey } from '@/consts';
import type { RequestError } from '@/hooks/use-api'; 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 resourcesStyles from '@/scss/resources.module.scss';
import * as tableStyles from '@/scss/table.module.scss'; import * as tableStyles from '@/scss/table.module.scss';
@ -68,36 +65,17 @@ const Users = () => {
}); });
}} }}
/> />
<Modal {isCreateNew && (
shouldCloseOnEsc
isOpen={isCreateNew}
className={modalStyles.content}
overlayClassName={modalStyles.overlay}
onRequestClose={() => {
navigate({
pathname: usersPathname,
search,
});
}}
>
<CreateForm <CreateForm
onClose={(createdUser, password) => { onClose={() => {
if (createdUser && password) {
sessionStorage.setItem(generatedPasswordStorageKey, password);
navigate(buildDetailsPathname(createdUser.id), { replace: true });
return;
}
navigate({ navigate({
pathname: usersPathname, pathname: usersPathname,
search, search,
}); });
}} }}
/> />
</Modal> )}
</div> </div>
<div className={classNames(resourcesStyles.table, styles.tableLayout)}> <div className={classNames(resourcesStyles.table, styles.tableLayout)}>
<div className={styles.filter}> <div className={styles.filter}>
<Search <Search