mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
refactor(console): add routes for create modals (#2690)
This commit is contained in:
parent
1865b74272
commit
b431b0bc5e
5 changed files with 91 additions and 43 deletions
|
@ -53,13 +53,11 @@ const Main = () => {
|
||||||
<Route path="applications">
|
<Route path="applications">
|
||||||
<Route index element={<Applications />} />
|
<Route index element={<Applications />} />
|
||||||
<Route path="create" element={<Applications />} />
|
<Route path="create" element={<Applications />} />
|
||||||
<Route path=":id">
|
<Route path=":id" element={<ApplicationDetails />} />
|
||||||
<Route index element={<Navigate replace to="settings" />} />
|
|
||||||
<Route path="settings" element={<ApplicationDetails />} />
|
|
||||||
</Route>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="api-resources">
|
<Route path="api-resources">
|
||||||
<Route index element={<ApiResources />} />
|
<Route index element={<ApiResources />} />
|
||||||
|
<Route path="create" element={<ApiResources />} />
|
||||||
<Route path=":id" element={<ApiResourceDetails />} />
|
<Route path=":id" element={<ApiResourceDetails />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="connectors">
|
<Route path="connectors">
|
||||||
|
@ -69,6 +67,7 @@ const Main = () => {
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="users">
|
<Route path="users">
|
||||||
<Route index element={<Users />} />
|
<Route index element={<Users />} />
|
||||||
|
<Route path="create" element={<Users />} />
|
||||||
<Route path=":userId" element={<UserDetails />} />
|
<Route path=":userId" element={<UserDetails />} />
|
||||||
<Route path=":userId/logs" element={<UserDetails />} />
|
<Route path=":userId/logs" element={<UserDetails />} />
|
||||||
<Route path=":userId/logs/:logId" element={<AuditLogDetails />} />
|
<Route path=":userId/logs/:logId" element={<AuditLogDetails />} />
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import type { Resource } from '@logto/schemas';
|
import type { Resource } from '@logto/schemas';
|
||||||
import { AppearanceMode } from '@logto/schemas';
|
import { AppearanceMode } from '@logto/schemas';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useState } from 'react';
|
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import ApiResourceDark from '@/assets/images/api-resource-dark.svg';
|
import ApiResourceDark from '@/assets/images/api-resource-dark.svg';
|
||||||
|
@ -28,14 +27,18 @@ import * as tableStyles from '@/scss/table.module.scss';
|
||||||
import CreateForm from './components/CreateForm';
|
import CreateForm from './components/CreateForm';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
const buildDetailsLink = (id: string) => `/api-resources/${id}`;
|
const apiResourcesPathname = '/api-resources';
|
||||||
|
const createApiResourcePathname = `${apiResourcesPathname}/create`;
|
||||||
|
const buildDetailsPathname = (id: string) => `${apiResourcesPathname}/${id}`;
|
||||||
|
|
||||||
const pageSize = 20;
|
const pageSize = 20;
|
||||||
|
|
||||||
const ApiResources = () => {
|
const ApiResources = () => {
|
||||||
const [isCreateFormOpen, setIsCreateFormOpen] = useState(false);
|
const { pathname } = useLocation();
|
||||||
|
const isCreateNew = pathname.endsWith('/create');
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const [query, setQuery] = useSearchParams();
|
const [query, setQuery] = useSearchParams();
|
||||||
|
const search = query.toString();
|
||||||
const pageIndex = Number(query.get('page') ?? '1');
|
const pageIndex = Number(query.get('page') ?? '1');
|
||||||
const { data, error, mutate } = useSWR<[Resource[], number], RequestError>(
|
const { data, error, mutate } = useSWR<[Resource[], number], RequestError>(
|
||||||
`/api/resources?page=${pageIndex}&page_size=${pageSize}`
|
`/api/resources?page=${pageIndex}&page_size=${pageSize}`
|
||||||
|
@ -55,28 +58,38 @@ const ApiResources = () => {
|
||||||
size="large"
|
size="large"
|
||||||
icon={<Plus />}
|
icon={<Plus />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsCreateFormOpen(true);
|
navigate({
|
||||||
|
pathname: createApiResourcePathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
shouldCloseOnEsc
|
shouldCloseOnEsc
|
||||||
isOpen={isCreateFormOpen}
|
isOpen={isCreateNew}
|
||||||
className={modalStyles.content}
|
className={modalStyles.content}
|
||||||
overlayClassName={modalStyles.overlay}
|
overlayClassName={modalStyles.overlay}
|
||||||
onRequestClose={() => {
|
onRequestClose={() => {
|
||||||
setIsCreateFormOpen(false);
|
navigate({
|
||||||
|
pathname: apiResourcesPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CreateForm
|
<CreateForm
|
||||||
onClose={(createdApiResource) => {
|
onClose={(createdApiResource) => {
|
||||||
setIsCreateFormOpen(false);
|
|
||||||
|
|
||||||
if (createdApiResource) {
|
if (createdApiResource) {
|
||||||
toast.success(
|
toast.success(
|
||||||
t('api_resources.api_resource_created', { name: createdApiResource.name })
|
t('api_resources.api_resource_created', { name: createdApiResource.name })
|
||||||
);
|
);
|
||||||
navigate(buildDetailsLink(createdApiResource.id));
|
navigate(buildDetailsPathname(createdApiResource.id), { replace: true });
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
navigate({
|
||||||
|
pathname: apiResourcesPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -109,7 +122,10 @@ const ApiResources = () => {
|
||||||
title="api_resources.create"
|
title="api_resources.create"
|
||||||
type="outline"
|
type="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsCreateFormOpen(true);
|
navigate({
|
||||||
|
pathname: createApiResourcePathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TableEmpty>
|
</TableEmpty>
|
||||||
|
@ -123,14 +139,14 @@ const ApiResources = () => {
|
||||||
key={id}
|
key={id}
|
||||||
className={tableStyles.clickable}
|
className={tableStyles.clickable}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(buildDetailsLink(id));
|
navigate(buildDetailsPathname(id));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
<ItemPreview
|
<ItemPreview
|
||||||
title={name}
|
title={name}
|
||||||
icon={<ResourceIcon className={styles.icon} />}
|
icon={<ResourceIcon className={styles.icon} />}
|
||||||
to={buildDetailsLink(id)}
|
to={buildDetailsPathname(id)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import Back from '@/assets/images/back.svg';
|
import Back from '@/assets/images/back.svg';
|
||||||
|
@ -41,7 +41,6 @@ const mapToUriOriginFormatArrays = (value?: string[]) =>
|
||||||
|
|
||||||
const ApplicationDetails = () => {
|
const ApplicationDetails = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { pathname } = useLocation();
|
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const { data, error, mutate } = useSWR<Application, RequestError>(
|
const { data, error, mutate } = useSWR<Application, RequestError>(
|
||||||
id && `/api/applications/${id}`
|
id && `/api/applications/${id}`
|
||||||
|
@ -197,9 +196,7 @@ const ApplicationDetails = () => {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<TabNav>
|
<TabNav>
|
||||||
<TabNavItem href={`/applications/${data.id}/settings`}>
|
<TabNavItem href={`/applications/${data.id}`}>{t('general.settings_nav')}</TabNavItem>
|
||||||
{t('general.settings_nav')}
|
|
||||||
</TabNavItem>
|
|
||||||
</TabNav>
|
</TabNav>
|
||||||
<FormProvider {...formMethods}>
|
<FormProvider {...formMethods}>
|
||||||
<DetailsForm
|
<DetailsForm
|
||||||
|
|
|
@ -27,12 +27,17 @@ import * as styles from './index.module.scss';
|
||||||
|
|
||||||
const pageSize = 20;
|
const pageSize = 20;
|
||||||
|
|
||||||
|
const applicationsPathname = '/applications';
|
||||||
|
const createApplicationPathname = `${applicationsPathname}/create`;
|
||||||
|
const buildDetailsPathname = (id: string) => `${applicationsPathname}/${id}`;
|
||||||
|
|
||||||
const Applications = () => {
|
const Applications = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const { pathname } = useLocation();
|
||||||
const isCreateNew = location.pathname.endsWith('/create');
|
const isCreateNew = pathname === createApplicationPathname;
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const [query, setQuery] = useSearchParams();
|
const [query, setQuery] = useSearchParams();
|
||||||
|
const search = query.toString();
|
||||||
const pageIndex = Number(query.get('page') ?? '1');
|
const pageIndex = Number(query.get('page') ?? '1');
|
||||||
const { data, error, mutate } = useSWR<[Application[], number], RequestError>(
|
const { data, error, mutate } = useSWR<[Application[], number], RequestError>(
|
||||||
`/api/applications?page=${pageIndex}&page_size=${pageSize}`
|
`/api/applications?page=${pageIndex}&page_size=${pageSize}`
|
||||||
|
@ -50,7 +55,10 @@ const Applications = () => {
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate('/applications/create');
|
navigate({
|
||||||
|
pathname: createApplicationPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -59,18 +67,24 @@ const Applications = () => {
|
||||||
className={modalStyles.content}
|
className={modalStyles.content}
|
||||||
overlayClassName={modalStyles.overlay}
|
overlayClassName={modalStyles.overlay}
|
||||||
onRequestClose={() => {
|
onRequestClose={() => {
|
||||||
navigate('/applications');
|
navigate({
|
||||||
|
pathname: applicationsPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CreateForm
|
<CreateForm
|
||||||
onClose={(createdApp) => {
|
onClose={(createdApp) => {
|
||||||
if (createdApp) {
|
if (createdApp) {
|
||||||
toast.success(t('applications.application_created', { name: createdApp.name }));
|
toast.success(t('applications.application_created', { name: createdApp.name }));
|
||||||
navigate(`/applications/${createdApp.id}`);
|
navigate(buildDetailsPathname(createdApp.id), { replace: true });
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate('/applications');
|
navigate({
|
||||||
|
pathname: applicationsPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -105,7 +119,10 @@ const Applications = () => {
|
||||||
title="applications.create"
|
title="applications.create"
|
||||||
type="outline"
|
type="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate('/applications/create');
|
navigate({
|
||||||
|
pathname: createApplicationPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TableEmpty>
|
</TableEmpty>
|
||||||
|
@ -115,7 +132,7 @@ const Applications = () => {
|
||||||
key={id}
|
key={id}
|
||||||
className={tableStyles.clickable}
|
className={tableStyles.clickable}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/applications/${id}`);
|
navigate(buildDetailsPathname(id));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
|
@ -123,7 +140,7 @@ const Applications = () => {
|
||||||
title={name}
|
title={name}
|
||||||
subtitle={t(`${applicationTypeI18nKey[type]}.title`)}
|
subtitle={t(`${applicationTypeI18nKey[type]}.title`)}
|
||||||
icon={<ApplicationIcon className={styles.icon} type={type} />}
|
icon={<ApplicationIcon className={styles.icon} type={type} />}
|
||||||
to={`/applications/${id}`}
|
to={buildDetailsPathname(id)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import type { User } from '@logto/schemas';
|
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 { useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import Plus from '@/assets/images/plus.svg';
|
import Plus from '@/assets/images/plus.svg';
|
||||||
|
@ -32,10 +31,16 @@ const pageSize = 20;
|
||||||
|
|
||||||
const userTableColumn = 3;
|
const userTableColumn = 3;
|
||||||
|
|
||||||
|
const usersPathname = '/users';
|
||||||
|
const createUserPathname = `${usersPathname}/create`;
|
||||||
|
const buildDetailsPathname = (id: string) => `${usersPathname}/id`;
|
||||||
|
|
||||||
const Users = () => {
|
const Users = () => {
|
||||||
const [isCreateFormOpen, setIsCreateFormOpen] = useState(false);
|
const { pathname } = useLocation();
|
||||||
|
const isCreateNew = pathname === createUserPathname;
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const [query, setQuery] = useSearchParams();
|
const [query, setQuery] = useSearchParams();
|
||||||
|
const search = query.toString();
|
||||||
const pageIndex = Number(query.get('page') ?? '1');
|
const pageIndex = Number(query.get('page') ?? '1');
|
||||||
const keyword = query.get('search') ?? '';
|
const keyword = query.get('search') ?? '';
|
||||||
const { data, error, mutate } = useSWR<[User[], number], RequestError>(
|
const { data, error, mutate } = useSWR<[User[], number], RequestError>(
|
||||||
|
@ -57,26 +62,37 @@ const Users = () => {
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<Plus />}
|
icon={<Plus />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsCreateFormOpen(true);
|
navigate({
|
||||||
|
pathname: createUserPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
shouldCloseOnEsc
|
shouldCloseOnEsc
|
||||||
isOpen={isCreateFormOpen}
|
isOpen={isCreateNew}
|
||||||
className={modalStyles.content}
|
className={modalStyles.content}
|
||||||
overlayClassName={modalStyles.overlay}
|
overlayClassName={modalStyles.overlay}
|
||||||
onRequestClose={() => {
|
onRequestClose={() => {
|
||||||
setIsCreateFormOpen(false);
|
navigate({
|
||||||
|
pathname: usersPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CreateForm
|
<CreateForm
|
||||||
onClose={(createdUser, password) => {
|
onClose={(createdUser, password) => {
|
||||||
setIsCreateFormOpen(false);
|
|
||||||
|
|
||||||
if (createdUser && password) {
|
if (createdUser && password) {
|
||||||
sessionStorage.setItem(generatedPasswordStorageKey, password);
|
sessionStorage.setItem(generatedPasswordStorageKey, password);
|
||||||
navigate(`/users/${createdUser.id}`);
|
navigate(buildDetailsPathname(createdUser.id), { replace: true });
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
pathname: usersPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -124,7 +140,10 @@ const Users = () => {
|
||||||
title="users.create"
|
title="users.create"
|
||||||
type="outline"
|
type="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsCreateFormOpen(true);
|
navigate({
|
||||||
|
pathname: createUserPathname,
|
||||||
|
search,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TableEmpty>
|
</TableEmpty>
|
||||||
|
@ -134,7 +153,7 @@ const Users = () => {
|
||||||
key={id}
|
key={id}
|
||||||
className={tableStyles.clickable}
|
className={tableStyles.clickable}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/users/${id}`);
|
navigate(buildDetailsPathname(id));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
|
@ -142,7 +161,7 @@ const Users = () => {
|
||||||
title={name ?? t('users.unnamed')}
|
title={name ?? t('users.unnamed')}
|
||||||
subtitle={id}
|
subtitle={id}
|
||||||
icon={<UserAvatar className={styles.avatar} url={avatar} />}
|
icon={<UserAvatar className={styles.avatar} url={avatar} />}
|
||||||
to={`/users/${id}`}
|
to={buildDetailsPathname(id)}
|
||||||
size="compact"
|
size="compact"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
Loading…
Add table
Reference in a new issue