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

feat(console): add loading masks for protected app (#5357)

This commit is contained in:
Charles Zhao 2024-01-31 16:05:51 +08:00 committed by GitHub
parent 6f6d56ed7c
commit a671a4d9bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 95 additions and 5 deletions

View file

@ -115,3 +115,28 @@
padding-inline-start: _.unit(5);
}
}
.loadingSkeleton {
width: 100%;
display: flex;
flex-direction: column;
gap: _.unit(2);
padding: _.unit(6);
border-radius: 12px;
border: 1px solid var(--color-divider);
.bone {
@include _.shimmering-animation;
border-radius: 8px;
}
.title {
width: 100%;
height: 20px;
}
.description {
width: 100%;
height: 16px;
}
}

View file

@ -5,7 +5,8 @@ import {
type CustomDomain as CustomDomainType,
} from '@logto/schemas';
import { cond } from '@silverhand/essentials';
import { type ChangeEvent } from 'react';
import classNames from 'classnames';
import { type ChangeEvent, useState } from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
import useSWR from 'swr';
@ -41,10 +42,15 @@ const maxSessionDuration = 365; // 1 year
function ProtectedAppSettings({ data }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getDocumentationUrl } = useDocumentationUrl();
const { data: customDomains = [], mutate } = useSWR<CustomDomainType[]>(
const {
data: customDomains = [],
isLoading: isLoadingCustomDomain,
mutate,
} = useSWR<CustomDomainType[]>(
`api/applications/${data.id}/protected-app-metadata/custom-domains`
);
const api = useApi();
const [isDeletingCustomDomain, setIsDeletingCustomDomain] = useState(false);
const {
control,
@ -69,6 +75,8 @@ function ProtectedAppSettings({ data }: Props) {
customDomain?.status === DomainStatus.Active ? customDomain.domain : host
}`;
const showCustomDomainLoadingMask = isLoadingCustomDomain || isDeletingCustomDomain;
return (
<>
<FormCard
@ -124,7 +132,13 @@ function ProtectedAppSettings({ data }: Props) {
</FormField>
{!!host && (
<FormField title="domain.custom.custom_domain_field">
{!customDomain && (
{showCustomDomainLoadingMask && (
<div className={styles.loadingSkeleton}>
<div className={classNames(styles.bone, styles.title)} />
<div className={classNames(styles.bone, styles.description)} />
</div>
)}
{!customDomain && !showCustomDomainLoadingMask && (
<AddDomainForm
className={styles.customDomain}
onSubmitCustomDomain={async (json) => {
@ -136,15 +150,17 @@ function ProtectedAppSettings({ data }: Props) {
}}
/>
)}
{customDomain && (
{customDomain && !showCustomDomainLoadingMask && (
<CustomDomain
hasOpenExternalLink
className={styles.customDomain}
customDomain={customDomain}
onDeleteCustomDomain={async () => {
setIsDeletingCustomDomain(true);
await api.delete(
`api/applications/${data.id}/protected-app-metadata/custom-domains/${customDomain.domain}`
);
setIsDeletingCustomDomain(false);
void mutate();
}}
/>

View file

@ -61,3 +61,39 @@
}
}
}
.loadingSkeleton {
display: flex;
align-items: center;
gap: _.unit(6);
padding: _.unit(6) _.unit(8);
border-radius: 12px;
border: 1px solid var(--color-divider);
.bone {
@include _.shimmering-animation;
border-radius: 8px;
}
.columnWrapper {
flex: 1;
display: flex;
flex-direction: column;
gap: _.unit(2);
}
.icon {
width: 48px;
height: 48px;
}
.title {
width: 100%;
height: 20px;
}
.description {
width: 100%;
height: 16px;
}
}

View file

@ -1,4 +1,5 @@
import { type Application } from '@logto/schemas';
import classNames from 'classnames';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
@ -16,7 +17,7 @@ import * as styles from './index.module.scss';
function ProtectedAppCreationForm() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getTo } = useTenantPathname();
const { data, mutate } = useSWR<Application[]>('api/applications?types=Protected');
const { data, isLoading, mutate } = useSWR<Application[]>('api/applications?types=Protected');
const { hasAppsReachedLimit } = useApplicationsUsage();
const hasProtectedApps = !!data?.length;
const showCreationForm = !hasProtectedApps && !hasAppsReachedLimit;
@ -28,6 +29,18 @@ function ProtectedAppCreationForm() {
[data, mutate]
);
if (isLoading) {
return (
<div className={styles.loadingSkeleton}>
<div className={classNames(styles.bone, styles.icon)} />
<div className={styles.columnWrapper}>
<div className={classNames(styles.bone, styles.title)} />
<div className={classNames(styles.bone, styles.description)} />
</div>
</div>
);
}
return (
<div className={styles.container}>
<ProtectedAppCard hasCreateButton={!showCreationForm} onCreateSuccess={mutateApps} />