mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
feat(console): application table placeholder (#3261)
This commit is contained in:
parent
cae97fb87a
commit
4606fd6821
14 changed files with 215 additions and 46 deletions
|
@ -0,0 +1,42 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: _.unit(5);
|
||||
|
||||
.title {
|
||||
font: var(--font-title-1);
|
||||
}
|
||||
|
||||
.description {
|
||||
font: var(--font-body-2);
|
||||
color: var(--color-text-secondary);
|
||||
margin-top: _.unit(1);
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.options {
|
||||
margin-top: _.unit(6);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
max-width: 736px;
|
||||
gap: _.unit(4);
|
||||
|
||||
.option {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--color-divider);
|
||||
border-radius: 12px;
|
||||
padding: _.unit(3);
|
||||
|
||||
.createButton {
|
||||
margin-top: _.unit(2.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import type { Application } from '@logto/schemas';
|
||||
import { ApplicationType } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Modal from 'react-modal';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useConfigs from '@/hooks/use-configs';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
import { applicationTypeI18nKey } from '@/types/applications';
|
||||
|
||||
import Guide from '../Guide';
|
||||
import TypeDescription from '../TypeDescription';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const defaultAppName = 'My App';
|
||||
|
||||
const ApplicationsPlaceholder = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const navigate = useNavigate();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [createdApplication, setCreatedApplication] = useState<Application>();
|
||||
const isGetStartedModalOpen = Boolean(createdApplication);
|
||||
const api = useApi();
|
||||
const { updateConfigs } = useConfigs();
|
||||
|
||||
const handleCreate = async (type: ApplicationType) => {
|
||||
if (isCreating) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreating(true);
|
||||
const payload = {
|
||||
type,
|
||||
name: defaultAppName,
|
||||
};
|
||||
|
||||
try {
|
||||
const createdApp = await api.post('api/applications', { json: payload }).json<Application>();
|
||||
|
||||
setCreatedApplication(createdApp);
|
||||
|
||||
void updateConfigs({
|
||||
applicationCreated: true,
|
||||
...conditional(
|
||||
createdApp.type === ApplicationType.MachineToMachine && { m2mApplicationCreated: true }
|
||||
),
|
||||
});
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const closeGuideModal = () => {
|
||||
if (!createdApplication) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(`/applications/${createdApplication.id}`);
|
||||
setCreatedApplication(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.placeholder}>
|
||||
<div className={styles.title}>{t('applications.placeholder_title')}</div>
|
||||
<div className={styles.description}>{t('applications.placeholder_description')}</div>
|
||||
<div className={styles.options}>
|
||||
{Object.values(ApplicationType).map((type) => (
|
||||
<div key={type} className={styles.option}>
|
||||
<TypeDescription
|
||||
size="small"
|
||||
type={type}
|
||||
title={t(`${applicationTypeI18nKey[type]}.title`)}
|
||||
subtitle={t(`${applicationTypeI18nKey[type]}.subtitle`)}
|
||||
description={t(`${applicationTypeI18nKey[type]}.description`)}
|
||||
/>
|
||||
<Button
|
||||
className={styles.createButton}
|
||||
disabled={isCreating}
|
||||
title="general.create"
|
||||
onClick={async () => {
|
||||
await handleCreate(type);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{createdApplication && (
|
||||
<Modal
|
||||
shouldCloseOnEsc
|
||||
isOpen={isGetStartedModalOpen}
|
||||
className={modalStyles.fullScreen}
|
||||
onRequestClose={closeGuideModal}
|
||||
>
|
||||
<Guide app={createdApplication} onClose={closeGuideModal} />
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApplicationsPlaceholder;
|
|
@ -62,7 +62,7 @@ const CreateForm = ({ onClose }: Props) => {
|
|||
void updateConfigs({
|
||||
applicationCreated: true,
|
||||
...conditional(
|
||||
createdApp.type === ApplicationType.MachineToMachine && { applicationM2mCreated: true }
|
||||
createdApp.type === ApplicationType.MachineToMachine && { m2mApplicationCreated: true }
|
||||
),
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,23 +1,37 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.title {
|
||||
font: var(--font-label-2);
|
||||
color: var(--color-text);
|
||||
padding-right: _.unit(6); /* For check mark */
|
||||
}
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.subtitle,
|
||||
.description {
|
||||
margin-top: _.unit(3);
|
||||
flex: 2;
|
||||
}
|
||||
.title {
|
||||
font: var(--font-label-2);
|
||||
color: var(--color-text);
|
||||
margin-top: _.unit(2.5);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font: var(--font-body-2);
|
||||
color: var(--color-text);
|
||||
}
|
||||
.subtitle,
|
||||
.description {
|
||||
margin-top: _.unit(3);
|
||||
flex: 2;
|
||||
font: var(--font-body-2);
|
||||
}
|
||||
|
||||
.description {
|
||||
font: var(--font-body-2);
|
||||
color: var(--color-text-secondary);
|
||||
.subtitle {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
&.small {
|
||||
.subtitle,
|
||||
.description {
|
||||
margin-top: _.unit(1);
|
||||
flex: 2;
|
||||
font: var(--font-body-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { ApplicationType } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ApplicationIcon from '@/components/ApplicationIcon';
|
||||
|
||||
|
@ -9,17 +10,16 @@ type Props = {
|
|||
subtitle: string;
|
||||
description: string;
|
||||
type: ApplicationType;
|
||||
size?: 'large' | 'small';
|
||||
};
|
||||
|
||||
const TypeDescription = ({ title, subtitle, description, type }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<ApplicationIcon type={type} />
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.subtitle}>{subtitle}</div>
|
||||
<div className={styles.description}>{description}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const TypeDescription = ({ title, subtitle, description, type, size = 'large' }: Props) => (
|
||||
<div className={classNames(styles.container, styles[size])}>
|
||||
<ApplicationIcon type={type} />
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.subtitle}>{subtitle}</div>
|
||||
<div className={styles.description}>{description}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default TypeDescription;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type { Application } from '@logto/schemas';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Modal from 'react-modal';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
@ -10,7 +9,6 @@ import ApplicationIcon from '@/components/ApplicationIcon';
|
|||
import Button from '@/components/Button';
|
||||
import CardTitle from '@/components/CardTitle';
|
||||
import CopyToClipboard from '@/components/CopyToClipboard';
|
||||
import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
|
||||
import ItemPreview from '@/components/ItemPreview';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import Table from '@/components/Table';
|
||||
|
@ -22,6 +20,7 @@ import * as resourcesStyles from '@/scss/resources.module.scss';
|
|||
import { applicationTypeI18nKey } from '@/types/applications';
|
||||
import { buildUrl } from '@/utils/url';
|
||||
|
||||
import ApplicationsPlaceholder from './components/ApplicationsPlaceholder';
|
||||
import CreateForm from './components/CreateForm';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
|
@ -81,7 +80,6 @@ const Applications = () => {
|
|||
<CreateForm
|
||||
onClose={(createdApp) => {
|
||||
if (createdApp) {
|
||||
toast.success(t('applications.application_created', { name: createdApp.name }));
|
||||
navigate(buildDetailsPathname(createdApp.id), { replace: true });
|
||||
|
||||
return;
|
||||
|
@ -121,21 +119,7 @@ const Applications = () => {
|
|||
render: ({ id }) => <CopyToClipboard value={id} variant="text" />,
|
||||
},
|
||||
]}
|
||||
placeholder={
|
||||
<>
|
||||
<EmptyDataPlaceholder />
|
||||
<Button
|
||||
title="applications.create"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
navigate({
|
||||
pathname: createApplicationPathname,
|
||||
search,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
placeholder={<ApplicationsPlaceholder />}
|
||||
rowClickHandler={({ id }) => {
|
||||
navigate(buildDetailsPathname(id));
|
||||
}}
|
||||
|
|
|
@ -45,6 +45,9 @@ const applications = {
|
|||
description_by_sdk:
|
||||
'Diese Schnellstart-Anleitung zeigt, wie man Logto in die {{sdk}} App integriert',
|
||||
},
|
||||
placeholder_title: 'Select an application type to continue', // UNTRANSLATED
|
||||
placeholder_description:
|
||||
'Logto uses an application entity for OIDC to help with tasks such as identifying your apps, managing sign-in, and creating audit logs.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default applications;
|
||||
|
|
|
@ -44,6 +44,9 @@ const applications = {
|
|||
description_by_sdk:
|
||||
'This quick start guide demonstrates how to integrate Logto into {{sdk}} app',
|
||||
},
|
||||
placeholder_title: 'Select an application type to continue',
|
||||
placeholder_description:
|
||||
'Logto uses an application entity for OIDC to help with tasks such as identifying your apps, managing sign-in, and creating audit logs.',
|
||||
};
|
||||
|
||||
export default applications;
|
||||
|
|
|
@ -46,6 +46,9 @@ const applications = {
|
|||
description_by_sdk:
|
||||
"Ce guide de démarrage rapide montre comment intégrer Logto dans l'application {{sdk}}.",
|
||||
},
|
||||
placeholder_title: 'Select an application type to continue', // UNTRANSLATED
|
||||
placeholder_description:
|
||||
'Logto uses an application entity for OIDC to help with tasks such as identifying your apps, managing sign-in, and creating audit logs.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default applications;
|
||||
|
|
|
@ -42,6 +42,9 @@ const applications = {
|
|||
subtitle: '앱 설정을 마치기 위해 아래 단계를 따라주세요. SDK 종류를 선택해 주세요.',
|
||||
description_by_sdk: '아래 과정을 따라서 Logto를 {{sdk}} 앱과 빠르게 연동해 보세요.',
|
||||
},
|
||||
placeholder_title: 'Select an application type to continue', // UNTRANSLATED
|
||||
placeholder_description:
|
||||
'Logto uses an application entity for OIDC to help with tasks such as identifying your apps, managing sign-in, and creating audit logs.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default applications;
|
||||
|
|
|
@ -45,6 +45,9 @@ const applications = {
|
|||
description_by_sdk:
|
||||
'Este guia de início rápido demonstra como integrar o Logto ao aplicativo {{sdk}}',
|
||||
},
|
||||
placeholder_title: 'Select an application type to continue', // UNTRANSLATED
|
||||
placeholder_description:
|
||||
'Logto uses an application entity for OIDC to help with tasks such as identifying your apps, managing sign-in, and creating audit logs.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default applications;
|
||||
|
|
|
@ -44,6 +44,9 @@ const applications = {
|
|||
'Agora siga as etapas abaixo para concluir as configurações da aplicação. Selecione o tipo de SDK para continuar.',
|
||||
description_by_sdk: 'Este guia de início rápido demonstra como integrar o Logto em {{sdk}}',
|
||||
},
|
||||
placeholder_title: 'Select an application type to continue', // UNTRANSLATED
|
||||
placeholder_description:
|
||||
'Logto uses an application entity for OIDC to help with tasks such as identifying your apps, managing sign-in, and creating audit logs.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default applications;
|
||||
|
|
|
@ -46,6 +46,9 @@ const applications = {
|
|||
description_by_sdk:
|
||||
'Bu hızlı başlangıç kılavuzu, Logtoyu {{sdk}} uygulamasına nasıl entegre edeceğinizi gösterir',
|
||||
},
|
||||
placeholder_title: 'Select an application type to continue', // UNTRANSLATED
|
||||
placeholder_description:
|
||||
'Logto uses an application entity for OIDC to help with tasks such as identifying your apps, managing sign-in, and creating audit logs.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default applications;
|
||||
|
|
|
@ -41,6 +41,9 @@ const applications = {
|
|||
subtitle: '参考以下步骤完成你的应用设置。首先,选择你要使用的 SDK 类型:',
|
||||
description_by_sdk: '本教程向你演示如何在 {{sdk}} 应用中集成 Logto 登录功能',
|
||||
},
|
||||
placeholder_title: 'Select an application type to continue', // UNTRANSLATED
|
||||
placeholder_description:
|
||||
'Logto uses an application entity for OIDC to help with tasks such as identifying your apps, managing sign-in, and creating audit logs.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default applications;
|
||||
|
|
Loading…
Add table
Reference in a new issue