0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

fix(console): fix details page state (#3569)

This commit is contained in:
Xiao Yijun 2023-03-22 17:07:24 +08:00 committed by GitHub
parent 540c41ff64
commit 9c17de0906
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 161 additions and 229 deletions

View file

@ -0,0 +1,17 @@
@use '@/scss/underscore' as _;
.container {
min-height: 100%;
display: flex;
flex-direction: column;
min-width: min-content;
> *:not(:first-child) {
margin-top: _.unit(4);
}
}
.backLink {
margin: _.unit(1) 0 0 _.unit(1);
user-select: none;
}

View file

@ -0,0 +1,52 @@
import type { AdminConsoleKey } from '@logto/phrases';
import classNames from 'classnames';
import type { ReactElement, ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import Back from '@/assets/images/back.svg';
import type { RequestError } from '@/hooks/use-api';
import type DangerousRaw from '../DangerousRaw';
import DetailsSkeleton from '../DetailsSkeleton';
import RequestDataError from '../RequestDataError';
import TextLink from '../TextLink';
import * as styles from './index.module.scss';
type Props = {
backLink: string;
backLinkTitle?: AdminConsoleKey | ReactElement<typeof DangerousRaw>;
isLoading?: boolean;
error?: RequestError;
onRetry?: () => void;
children: ReactNode;
className?: string;
};
function DetailsPage({
backLink,
backLinkTitle,
isLoading,
error,
onRetry,
children,
className,
}: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
return (
<div className={classNames(styles.container, className)}>
<TextLink to={backLink} icon={<Back />} className={styles.backLink}>
{typeof backLinkTitle === 'string' ? t(backLinkTitle) : backLinkTitle}
</TextLink>
{isLoading ? (
<DetailsSkeleton />
) : error ? (
<RequestDataError error={error} onRetry={onRetry} />
) : (
children
)}
</div>
);
}
export default DetailsPage;

View file

@ -4,11 +4,6 @@
height: 100%;
}
.backLink {
margin: _.unit(1) 0 0 _.unit(1);
user-select: none;
}
.deleteConfirm {
> :not(:first-child) {
margin-top: _.unit(6);

View file

@ -9,22 +9,18 @@ import useSWR from 'swr';
import ApiResourceDark from '@/assets/images/api-resource-dark.svg';
import ApiResource from '@/assets/images/api-resource.svg';
import Back from '@/assets/images/back.svg';
import Delete from '@/assets/images/delete.svg';
import More from '@/assets/images/more.svg';
import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
import Card from '@/components/Card';
import CopyToClipboard from '@/components/CopyToClipboard';
import DeleteConfirmModal from '@/components/DeleteConfirmModal';
import DetailsSkeleton from '@/components/DetailsSkeleton';
import RequestDataError from '@/components/RequestDataError';
import DetailsPage from '@/components/DetailsPage';
import TabNav, { TabNavItem } from '@/components/TabNav';
import TextLink from '@/components/TextLink';
import { ApiResourceDetailsTabs } from '@/consts/page-tabs';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import useTheme from '@/hooks/use-theme';
import * as detailsStyles from '@/scss/details.module.scss';
import { withAppInsights } from '@/utils/app-insights';
import * as styles from './index.module.scss';
@ -70,21 +66,14 @@ function ApiResourceDetails() {
};
return (
<div
className={classNames(detailsStyles.container, isOnPermissionPage && styles.permissionPage)}
<DetailsPage
backLink="/api-resources"
backLinkTitle="api_resource_details.back_to_api_resources"
isLoading={isLoading}
error={error}
className={classNames(isOnPermissionPage && styles.permissionPage)}
onRetry={mutate}
>
<TextLink to="/api-resources" icon={<Back />} className={styles.backLink}>
{t('api_resource_details.back_to_api_resources')}
</TextLink>
{isLoading && <DetailsSkeleton />}
{error && (
<RequestDataError
error={error}
onRetry={() => {
void mutate();
}}
/>
)}
{data && (
<>
<Card className={styles.header}>
@ -153,7 +142,7 @@ function ApiResourceDetails() {
/>
</>
)}
</div>
</DetailsPage>
);
}

View file

@ -1,10 +1,5 @@
@use '@/scss/underscore' as _;
.backLink {
margin: _.unit(1) 0 0 _.unit(1);
user-select: none;
}
.deleteConfirm {
> :not(:first-child) {
margin-top: _.unit(6);

View file

@ -7,7 +7,6 @@ import { Trans, useTranslation } from 'react-i18next';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import useSWR from 'swr';
import Back from '@/assets/images/back.svg';
import Delete from '@/assets/images/delete.svg';
import More from '@/assets/images/more.svg';
import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
@ -17,16 +16,13 @@ import Card from '@/components/Card';
import CopyToClipboard from '@/components/CopyToClipboard';
import DeleteConfirmModal from '@/components/DeleteConfirmModal';
import DetailsForm from '@/components/DetailsForm';
import DetailsSkeleton from '@/components/DetailsSkeleton';
import DetailsPage from '@/components/DetailsPage';
import Drawer from '@/components/Drawer';
import RequestDataError from '@/components/RequestDataError';
import TabNav, { TabNavItem } from '@/components/TabNav';
import TextLink from '@/components/TextLink';
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import useDocumentationUrl from '@/hooks/use-documentation-url';
import * as detailsStyles from '@/scss/details.module.scss';
import { applicationTypeI18nKey } from '@/types/applications';
import { withAppInsights } from '@/utils/app-insights';
@ -131,20 +127,16 @@ function ApplicationDetails() {
};
return (
<div className={detailsStyles.container}>
<TextLink to="/applications" icon={<Back />} className={styles.backLink}>
{t('application_details.back_to_applications')}
</TextLink>
{isLoading && <DetailsSkeleton />}
{requestError && (
<RequestDataError
error={requestError}
onRetry={() => {
void mutate();
void mutateOidcConfig();
}}
/>
)}
<DetailsPage
backLink="/applications"
backLinkTitle="application_details.back_to_applications"
isLoading={isLoading}
error={requestError}
onRetry={() => {
void mutate();
void mutateOidcConfig();
}}
>
{data && oidcConfig && (
<>
<Card className={styles.header}>
@ -236,7 +228,7 @@ function ApplicationDetails() {
}}
/>
)}
</div>
</DetailsPage>
);
}

View file

@ -1,10 +1,5 @@
@use '@/scss/underscore' as _;
.backLink {
margin: _.unit(1) 0 0 _.unit(1);
user-select: none;
}
.header {
padding: _.unit(6);
display: flex;
@ -47,6 +42,10 @@
}
.body {
flex: 1;
display: flex;
flex-direction: column;
padding-bottom: 0;
margin-bottom: _.unit(6);
> :not(:first-child) {

View file

@ -1,23 +1,19 @@
import type { User, Log } from '@logto/schemas';
import { demoAppApplicationId } from '@logto/schemas';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { useLocation, useParams } from 'react-router-dom';
import useSWR from 'swr';
import Back from '@/assets/images/back.svg';
import ApplicationName from '@/components/ApplicationName';
import Card from '@/components/Card';
import CodeEditor from '@/components/CodeEditor';
import DetailsSkeleton from '@/components/DetailsSkeleton';
import DangerousRaw from '@/components/DangerousRaw';
import DetailsPage from '@/components/DetailsPage';
import FormField from '@/components/FormField';
import RequestDataError from '@/components/RequestDataError';
import TabNav, { TabNavItem } from '@/components/TabNav';
import TextLink from '@/components/TextLink';
import UserName from '@/components/UserName';
import { logEventTitle } from '@/consts/logs';
import type { RequestError } from '@/hooks/use-api';
import * as detailsStyles from '@/scss/details.module.scss';
import { withAppInsights } from '@/utils/app-insights';
import EventIcon from './components/EventIcon';
@ -48,19 +44,13 @@ function AuditLogDetails() {
}
return (
<div className={detailsStyles.container}>
<TextLink to={backLink} icon={<Back />} className={styles.backLink}>
{backLinkTitle}
</TextLink>
{isLoading && <DetailsSkeleton />}
{error && (
<RequestDataError
error={error}
onRetry={() => {
void mutate();
}}
/>
)}
<DetailsPage
backLink={backLink}
backLinkTitle={<DangerousRaw>{backLinkTitle}</DangerousRaw>}
isLoading={isLoading}
error={error}
onRetry={mutate}
>
{data && (
<>
<Card className={styles.header}>
@ -117,7 +107,7 @@ function AuditLogDetails() {
{t('log_details.tab_details')}
</TabNavItem>
</TabNav>
<Card className={classNames(styles.body, detailsStyles.body)}>
<Card className={styles.body}>
<div className={styles.main}>
<FormField title="log_details.raw_data">
<CodeEditor language="json" value={JSON.stringify(data.payload, null, 2)} />
@ -126,7 +116,7 @@ function AuditLogDetails() {
</Card>
</>
)}
</div>
</DetailsPage>
);
}

View file

@ -1,10 +1,5 @@
@use '@/scss/underscore' as _;
.backLink {
margin: _.unit(1) 0 0 _.unit(1);
user-select: none;
}
.header {
padding: _.unit(6) _.unit(8);
display: flex;

View file

@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import useSWR, { useSWRConfig } from 'swr';
import Back from '@/assets/images/back.svg';
import Delete from '@/assets/images/delete.svg';
import More from '@/assets/images/more.svg';
import Reset from '@/assets/images/reset.svg';
@ -15,19 +14,16 @@ import Button from '@/components/Button';
import Card from '@/components/Card';
import ConnectorLogo from '@/components/ConnectorLogo';
import CopyToClipboard from '@/components/CopyToClipboard';
import DetailsSkeleton from '@/components/DetailsSkeleton';
import DetailsPage from '@/components/DetailsPage';
import Drawer from '@/components/Drawer';
import Markdown from '@/components/Markdown';
import RequestDataError from '@/components/RequestDataError';
import Status from '@/components/Status';
import TabNav, { TabNavItem } from '@/components/TabNav';
import TextLink from '@/components/TextLink';
import UnnamedTrans from '@/components/UnnamedTrans';
import { ConnectorsTabs } from '@/consts/page-tabs';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import useConnectorInUse from '@/hooks/use-connector-in-use';
import * as detailsStyles from '@/scss/details.module.scss';
import { withAppInsights } from '@/utils/app-insights';
import CreateForm from '../Connectors/components/CreateForm';
@ -108,22 +104,18 @@ function ConnectorDetails() {
}
return (
<div className={detailsStyles.container}>
<TextLink to={getConnectorsPathname(isSocial)} icon={<Back />} className={styles.backLink}>
{t('connector_details.back_to_connectors')}
</TextLink>
{requestError && (
<RequestDataError
error={requestError}
onRetry={() => {
void mutate();
void mutateConnectorFactory();
}}
/>
)}
{isLoading && !requestError && <DetailsSkeleton />}
<DetailsPage
backLink={getConnectorsPathname(isSocial)}
backLinkTitle="connector_details.back_to_connectors"
isLoading={isLoading}
error={requestError}
onRetry={() => {
void mutate();
void mutateConnectorFactory();
}}
>
{isSocial && <ConnectorTabs target={data.target} connectorId={data.id} />}
{!requestError && data && (
{data && (
<>
<Card className={styles.header}>
<ConnectorLogo data={data} size="large" />
@ -232,7 +224,7 @@ function ConnectorDetails() {
/>
</>
)}
</div>
</DetailsPage>
);
}

View file

@ -4,39 +4,31 @@
height: 100%;
}
.container {
.backLink {
margin: _.unit(1) 0 0 _.unit(1);
user-select: none;
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: _.unit(6) _.unit(8);
.info {
.name {
font: var(--font-title-1);
color: var(--color-text);
}
.meta {
display: flex;
align-items: center;
.idText {
font: var(--font-label-2);
color: var(--color-text-secondary);
margin-right: _.unit(1);
}
}
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: _.unit(6) _.unit(8);
.info {
.name {
font: var(--font-title-1);
color: var(--color-text);
}
.meta {
display: flex;
align-items: center;
.idText {
font: var(--font-label-2);
color: var(--color-text-secondary);
margin-right: _.unit(1);
}
}
}
.moreIcon {
color: var(--color-text-secondary);
}
.moreIcon {
color: var(--color-text-secondary);
}
}

View file

@ -6,21 +6,17 @@ import { useTranslation } from 'react-i18next';
import { Outlet, useLocation, useNavigate, useParams } from 'react-router-dom';
import useSWR, { useSWRConfig } from 'swr';
import Back from '@/assets/images/back.svg';
import Delete from '@/assets/images/delete.svg';
import More from '@/assets/images/more.svg';
import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
import Card from '@/components/Card';
import ConfirmModal from '@/components/ConfirmModal';
import CopyToClipboard from '@/components/CopyToClipboard';
import DetailsSkeleton from '@/components/DetailsSkeleton';
import RequestDataError from '@/components/RequestDataError';
import DetailsPage from '@/components/DetailsPage';
import TabNav, { TabNavItem } from '@/components/TabNav';
import TextLink from '@/components/TextLink';
import { RoleDetailsTabs } from '@/consts/page-tabs';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import * as detailsStyles from '@/scss/details.module.scss';
import { withAppInsights } from '@/utils/app-insights';
import * as styles from './index.module.scss';
@ -67,25 +63,14 @@ function RoleDetails() {
};
return (
<div
className={classNames(
detailsStyles.container,
styles.container,
isPageHasTable && styles.withTable
)}
<DetailsPage
backLink="/roles"
backLinkTitle="role_details.back_to_roles"
isLoading={isLoading}
error={error}
className={classNames(isPageHasTable && styles.withTable)}
onRetry={mutate}
>
<TextLink to="/roles" icon={<Back />} className={styles.backLink}>
{t('role_details.back_to_roles')}
</TextLink>
{isLoading && <DetailsSkeleton />}
{error && (
<RequestDataError
error={error}
onRetry={() => {
void mutate();
}}
/>
)}
{data && (
<>
<Card className={styles.header}>
@ -146,7 +131,7 @@ function RoleDetails() {
/>
</>
)}
</div>
</DetailsPage>
);
}

View file

@ -1,10 +1,5 @@
@use '@/scss/underscore' as _;
.backLink {
margin: _.unit(1) 0 0 _.unit(1);
user-select: none;
}
.resourceLayout {
height: 100%;
}

View file

@ -7,7 +7,6 @@ import ReactModal from 'react-modal';
import { Outlet, useLocation, useNavigate, useParams } from 'react-router-dom';
import useSWR from 'swr';
import Back from '@/assets/images/back.svg';
import Delete from '@/assets/images/delete.svg';
import More from '@/assets/images/more.svg';
import Reset from '@/assets/images/reset.svg';
@ -15,15 +14,12 @@ import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
import Card from '@/components/Card';
import CopyToClipboard from '@/components/CopyToClipboard';
import DeleteConfirmModal from '@/components/DeleteConfirmModal';
import DetailsSkeleton from '@/components/DetailsSkeleton';
import RequestDataError from '@/components/RequestDataError';
import DetailsPage from '@/components/DetailsPage';
import TabNav, { TabNavItem } from '@/components/TabNav';
import TextLink from '@/components/TextLink';
import UserAvatar from '@/components/UserAvatar';
import { UserDetailsTabs } from '@/consts/page-tabs';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import * as detailsStyles from '@/scss/details.module.scss';
import * as modalStyles from '@/scss/modal.module.scss';
import { withAppInsights } from '@/utils/app-insights';
@ -70,19 +66,14 @@ function UserDetails() {
};
return (
<div className={classNames(detailsStyles.container, isPageHasTable && styles.resourceLayout)}>
<TextLink to="/users" icon={<Back />} className={styles.backLink}>
{t('user_details.back_to_users')}
</TextLink>
{isLoading && <DetailsSkeleton />}
{error && (
<RequestDataError
error={error}
onRetry={() => {
void mutate();
}}
/>
)}
<DetailsPage
backLink="/users"
backLinkTitle="user_details.back_to_users"
isLoading={isLoading}
error={error}
className={classNames(isPageHasTable && styles.resourceLayout)}
onRetry={mutate}
>
{data && (
<>
<Card className={styles.header}>
@ -194,7 +185,7 @@ function UserDetails() {
)}
</>
)}
</div>
</DetailsPage>
);
}

View file

@ -1,47 +0,0 @@
@use '@/scss/underscore' as _;
.container {
min-height: 100%;
display: flex;
flex-direction: column;
min-width: min-content;
> *:not(:first-child) {
margin-top: _.unit(4);
}
.body {
position: relative;
padding-bottom: 0;
flex: 1;
display: flex;
flex-direction: column;
}
.footer {
position: sticky;
bottom: 0;
margin: 0 _.unit(-6);
// Use the same color with app's background to cover card body
// simulate the always-on border-radius
background: var(--color-base);
.footerMain {
display: flex;
justify-content: flex-end;
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
background: var(--color-layer-1);
padding: _.unit(6);
}
&::before {
content: '';
background: var(--color-border);
opacity: 50%;
height: 1px;
display: block;
margin: 0;
}
}
}