0
Fork 0
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:
Xiao Yijun 2023-03-02 17:06:46 +08:00 committed by GitHub
parent cae97fb87a
commit 4606fd6821
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 215 additions and 46 deletions

View file

@ -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);
}
}
}
}

View file

@ -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;

View file

@ -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 }
),
});
});

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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));
}}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;