0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-06 20:40:08 -05:00

refactor(console): hide check demo button if demo app is deleted

This commit is contained in:
Charles Zhao 2022-06-07 11:51:26 +08:00
parent 69e32cb646
commit fbd7ac3a69
No known key found for this signature in database
GPG key ID: 4858774754C92DF2
6 changed files with 116 additions and 31 deletions
packages/console/src

View file

@ -1,17 +1,18 @@
import { useLogto } from '@logto/react';
import { RequestErrorBody } from '@logto/schemas';
import { managementResource } from '@logto/schemas/lib/seeds';
import { conditional } from '@silverhand/essentials';
import { t } from 'i18next';
import ky from 'ky';
import { useMemo } from 'react';
import { toast } from 'react-hot-toast';
export class RequestError extends Error {
status: number;
body?: RequestErrorBody;
constructor(body: RequestErrorBody) {
constructor(status: number, body: RequestErrorBody) {
super('Request error occurred.');
this.status = status;
this.body = body;
}
}
@ -36,17 +37,15 @@ const useApi = ({ hideErrorToast }: Props = {}) => {
() =>
ky.create({
hooks: {
beforeError: conditional(
!hideErrorToast && [
(error) => {
const { response } = error;
beforeError: hideErrorToast
? []
: [
(error) => {
void toastError(error.response);
void toastError(response);
return error;
},
]
),
return error;
},
],
beforeRequest: [
async (request) => {
if (isAuthenticated) {

View file

@ -41,7 +41,7 @@ const useSwrFetcher: useSwrFetcherHook = <T>() => {
if (error instanceof HTTPError) {
const { response } = error;
const metadata = await response.json<RequestErrorBody>();
throw new RequestError(metadata);
throw new RequestError(response.status, metadata);
}
throw error;
}

View file

@ -0,0 +1,44 @@
@use '@/scss/underscore' as _;
.card {
display: flex;
padding: _.unit(6) _.unit(8);
background-color: var(--color-layer-1);
border-radius: 16px;
.icon {
@include _.shimmering-animation;
width: 48px;
height: 48px;
margin-right: _.unit(6);
}
.wrapper {
flex: 1;
display: flex;
flex-direction: column;
.title {
@include _.shimmering-animation;
width: 113px;
height: 20px;
}
.subtitle {
@include _.shimmering-animation;
width: 453px;
height: 20px;
margin-top: _.unit(1);
}
}
.button {
@include _.shimmering-animation;
width: 129px;
height: 44px;
}
}
.card + .card {
margin-top: _.unit(4);
}

View file

@ -0,0 +1,19 @@
import React from 'react';
import * as styles from './index.module.scss';
const Skeleton = () => (
<>
{[...Array.from({ length: 5 }).keys()].map((key) => (
<div key={key} className={styles.card}>
<div className={styles.icon} />
<div className={styles.wrapper}>
<div className={styles.title} />
<div className={styles.subtitle} />
</div>
</div>
))}
</>
);
export default Skeleton;

View file

@ -1,5 +1,7 @@
import { AdminConsoleKey, I18nKey } from '@logto/phrases';
import { Application } from '@logto/schemas';
import { useNavigate } from 'react-router-dom';
import useSWR from 'swr';
import checkDemoIcon from '@/assets/images/check-demo.svg';
import createAppIcon from '@/assets/images/create-app.svg';
@ -7,6 +9,7 @@ import customizeIcon from '@/assets/images/customize.svg';
import furtherReadingsIcon from '@/assets/images/further-readings.svg';
import oneClickIcon from '@/assets/images/one-click.svg';
import passwordlessIcon from '@/assets/images/passwordless.svg';
import { RequestError } from '@/hooks/use-api';
import useSettings from '@/hooks/use-settings';
type GetStartedMetadata = {
@ -16,12 +19,24 @@ type GetStartedMetadata = {
icon: string;
buttonText: I18nKey;
isComplete?: boolean;
isHidden?: boolean;
onClick: () => void;
};
const useGetStartedMetadata = () => {
const { settings, updateSettings } = useSettings();
const { data: demoApp, error } = useSWR<Application, RequestError>('/api/applications/demo_app', {
shouldRetryOnError: (error: unknown) => {
if (error instanceof RequestError) {
return error.status !== 404;
}
return true;
},
});
const navigate = useNavigate();
const isLoadingDemoApp = !demoApp && !error;
const hideDemo = error?.status === 404;
const data: GetStartedMetadata[] = [
{
@ -31,6 +46,7 @@ const useGetStartedMetadata = () => {
icon: checkDemoIcon,
buttonText: 'general.check_out',
isComplete: settings?.checkDemo,
isHidden: hideDemo,
onClick: async () => {
void updateSettings({ checkDemo: true });
window.open('/demo-app', '_blank');
@ -97,6 +113,7 @@ const useGetStartedMetadata = () => {
data,
completedCount: data.filter(({ isComplete }) => isComplete).length,
totalCount: data.length,
isLoading: isLoadingDemoApp,
};
};

View file

@ -9,13 +9,14 @@ import ConfirmModal from '@/components/ConfirmModal';
import Spacer from '@/components/Spacer';
import useUserPreferences from '@/hooks/use-user-preferences';
import Skeleton from './components/Skeleton';
import useGetStartedMetadata from './hook';
import * as styles from './index.module.scss';
const GetStarted = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const navigate = useNavigate();
const { data } = useGetStartedMetadata();
const { data, isLoading } = useGetStartedMetadata();
const { update } = useUserPreferences();
const [showConfirmModal, setShowConfirmModal] = useState(false);
@ -45,23 +46,28 @@ const GetStarted = () => {
</span>
</div>
</div>
{data.map(({ id, title, subtitle, icon, isComplete, buttonText, onClick }) => (
<Card key={id} className={styles.card}>
<img className={styles.icon} src={icon} />
{isComplete && <img className={styles.completeIndicator} src={completeIndicator} />}
<div className={styles.wrapper}>
<div className={styles.title}>{t(title)}</div>
<div className={styles.subtitle}>{t(subtitle)}</div>
</div>
<Button
className={styles.button}
type="outline"
size="large"
title={buttonText}
onClick={onClick}
/>
</Card>
))}
{isLoading && <Skeleton />}
{!isLoading &&
data.map(
({ id, title, subtitle, icon, isComplete, isHidden, buttonText, onClick }) =>
!isHidden && (
<Card key={id} className={styles.card}>
<img className={styles.icon} src={icon} />
{isComplete && <img className={styles.completeIndicator} src={completeIndicator} />}
<div className={styles.wrapper}>
<div className={styles.title}>{t(title)}</div>
<div className={styles.subtitle}>{t(subtitle)}</div>
</div>
<Button
className={styles.button}
type="outline"
size="large"
title={buttonText}
onClick={onClick}
/>
</Card>
)
)}
<ConfirmModal
title="get_started.confirm"
isOpen={showConfirmModal}