0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-23 20:33:16 -05:00

refactor(console): refactor guide pages

This commit is contained in:
Charles Zhao 2022-05-09 23:08:40 +08:00
parent 1f55f779e6
commit 6bdf6cd928
No known key found for this signature in database
GPG key ID: 4858774754C92DF2
7 changed files with 119 additions and 185 deletions

View file

@ -1,12 +1,23 @@
import React from 'react';
import { MDXProvider } from '@mdx-js/react';
import i18next from 'i18next';
import { MDXProps } from 'mdx/types';
import React, { cloneElement, lazy, LazyExoticComponent, Suspense, useState } from 'react';
import { useForm, FormProvider } from 'react-hook-form';
import Modal from 'react-modal';
import Guide from '@/pages/Guide';
import Button from '@/components/Button';
import CardTitle from '@/components/CardTitle';
import CodeEditor from '@/components/CodeEditor';
import DangerousRaw from '@/components/DangerousRaw';
import IconButton from '@/components/IconButton';
import Spacer from '@/components/Spacer';
import Close from '@/icons/Close';
import * as modalStyles from '@/scss/modal.module.scss';
import { SupportedJavascriptLibraries } from '@/types/applications';
import { GuideForm } from '@/types/guide';
import LibrarySelector from '../LibrarySelector';
import * as styles from './index.module.scss';
type Props = {
appName: string;
@ -15,17 +26,101 @@ type Props = {
onComplete: (data: GuideForm) => Promise<void>;
};
const GuideModal = ({ appName, isOpen, onClose, onComplete }: Props) => (
<Modal isOpen={isOpen} className={modalStyles.fullScreen}>
<Guide
bannerComponent={<LibrarySelector />}
title={appName}
subtitle="applications.get_started.header_description"
defaultSubtype={SupportedJavascriptLibraries.React}
onClose={onClose}
onComplete={onComplete}
/>
</Modal>
);
const Guides: Record<string, LazyExoticComponent<(props: MDXProps) => JSX.Element>> = {
react: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/react.mdx')),
'react_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/react_zh-cn.mdx')),
};
const onClickFetchSampleProject = (projectName: string) => {
const sampleUrl = `https://github.com/logto-io/js/tree/master/packages/${projectName}-sample`;
window.open(sampleUrl, '_blank');
};
const GuideModal = ({ appName, isOpen, onClose, onComplete }: Props) => {
const [subtype, setSubtype] = useState<string>(SupportedJavascriptLibraries.React);
const [activeStepIndex, setActiveStepIndex] = useState(-1);
const [invalidStepIndex, setInvalidStepIndex] = useState(-1);
const locale = i18next.language;
const guideKey = `${subtype}_${locale}`.toLowerCase();
const GuideComponent = Guides[guideKey] ?? Guides[subtype];
const methods = useForm<GuideForm>({ mode: 'onSubmit', reValidateMode: 'onChange' });
const {
formState: { isSubmitting },
handleSubmit,
} = methods;
const onSubmit = handleSubmit((data) => {
if (isSubmitting) {
return;
}
void onComplete(data);
});
return (
<Modal isOpen={isOpen} className={modalStyles.fullScreen}>
<div className={styles.container}>
<div className={styles.header}>
<IconButton size="large" onClick={onClose}>
<Close />
</IconButton>
<div className={styles.separator} />
<CardTitle
size="small"
title={<DangerousRaw>{appName}</DangerousRaw>}
subtitle="applications.guide.header_description"
/>
<Spacer />
<Button type="plain" size="small" title="general.skip" onClick={onClose} />
<Button
type="outline"
title="admin_console.applications.guide.get_sample_file"
onClick={() => {
onClickFetchSampleProject(subtype);
}}
/>
</div>
<div className={styles.content}>
<FormProvider {...methods}>
<form onSubmit={onSubmit}>
{cloneElement(<LibrarySelector />, {
className: styles.banner,
onChange: setSubtype,
onToggle: () => {
setActiveStepIndex(0);
},
})}
<MDXProvider
components={{
code: ({ className, children }) => {
const [, language] = /language-(\w+)/.exec(className ?? '') ?? [];
return <CodeEditor isReadonly language={language} value={String(children)} />;
},
}}
>
<Suspense fallback={<div>Loading...</div>}>
{GuideComponent && (
<GuideComponent
activeStepIndex={activeStepIndex}
invalidStepIndex={invalidStepIndex}
onNext={(nextIndex: number) => {
setActiveStepIndex(nextIndex);
}}
onError={(invalidIndex: number) => {
setInvalidStepIndex(invalidIndex);
}}
/>
)}
</Suspense>
</MDXProvider>
</form>
</FormProvider>
</div>
</div>
</Modal>
);
};
export default GuideModal;

View file

@ -32,8 +32,8 @@ const LibrarySelector = ({
<Card className={classNames(styles.card, className)}>
<img src={highFive} alt="success" />
<div>
<div className={styles.title}>{t('applications.get_started.title')}</div>
<div className={styles.subtitle}>{t('applications.get_started.subtitle')}</div>
<div className={styles.title}>{t('applications.guide.title')}</div>
<div className={styles.subtitle}>{t('applications.guide.subtitle')}</div>
</div>
<RadioGroup
className={styles.radioGroup}
@ -67,9 +67,7 @@ const LibrarySelector = ({
() => (
<div className={classNames(styles.card, styles.folded, className)}>
<img src={tada} alt="Tada!" />
<span>
{t('applications.get_started.description_by_library', { library: libraryName })}
</span>
<span>{t('applications.guide.description_by_library', { library: libraryName })}</span>
</div>
),
[className, libraryName, t]

View file

@ -29,11 +29,6 @@ type Props = {
onComplete?: (data: GuideForm) => Promise<void>;
};
const onClickFetchSampleProject = (name: string) => {
const sampleUrl = `https://github.com/logto-io/js/tree/master/packages/connectors/${name}-sample`;
window.open(sampleUrl, '_blank');
};
const GuideModal = ({ connector, isOpen, onClose }: Props) => {
const api = useApi();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
@ -88,19 +83,10 @@ const GuideModal = ({ connector, isOpen, onClose }: Props) => {
<CardTitle
size="small"
title={<DangerousRaw>{connectorName}</DangerousRaw>}
subtitle="connectors.get_started.subtitle"
subtitle="connectors.guide.subtitle"
/>
<Spacer />
<Button type="plain" size="small" title="general.skip" onClick={onClose} />
{connectorName && (
<Button
type="outline"
title="admin_console.get_started.get_sample_file"
onClick={() => {
onClickFetchSampleProject(connectorName);
}}
/>
)}
</div>
<div className={styles.content}>
<ReactMarkdown

View file

@ -1,141 +0,0 @@
import { AdminConsoleKey } from '@logto/phrases';
import { MDXProvider } from '@mdx-js/react';
import i18next from 'i18next';
import { MDXProps } from 'mdx/types';
import React, {
cloneElement,
isValidElement,
lazy,
LazyExoticComponent,
PropsWithChildren,
ReactNode,
Suspense,
useState,
} from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import Button from '@/components/Button';
import CardTitle from '@/components/CardTitle';
import CodeEditor from '@/components/CodeEditor';
import DangerousRaw from '@/components/DangerousRaw';
import IconButton from '@/components/IconButton';
import Spacer from '@/components/Spacer';
import Close from '@/icons/Close';
import { GuideForm } from '@/types/guide';
import * as styles from './index.module.scss';
const Guides: Record<string, LazyExoticComponent<(props: MDXProps) => JSX.Element>> = {
react: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/react.mdx')),
'react_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/react_zh-cn.mdx')),
};
type Props = PropsWithChildren<{
title: string;
subtitle?: AdminConsoleKey;
/** `subtype` can be an actual type of an application or connector.
* e.g. React, Angular, Vue, etc. for application. Or Github, WeChat, etc. for connector.
*/
defaultSubtype?: string;
bannerComponent?: ReactNode;
onClose?: () => void;
onComplete?: (data: GuideForm) => Promise<void>;
}>;
const onClickFetchSampleProject = (projectName: string) => {
const sampleUrl = `https://github.com/logto-io/js/tree/master/packages/${projectName}-sample`;
window.open(sampleUrl, '_blank');
};
const Guide = ({
title,
subtitle,
defaultSubtype = '',
bannerComponent,
onClose,
onComplete,
}: Props) => {
const [subtype, setSubtype] = useState(defaultSubtype);
const [activeStepIndex, setActiveStepIndex] = useState(-1);
const [invalidStepIndex, setInvalidStepIndex] = useState(-1);
const locale = i18next.language;
const guideKey = `${subtype}_${locale}`.toLowerCase();
const GuideComponent = Guides[guideKey] ?? Guides[subtype];
const methods = useForm<GuideForm>({ mode: 'onSubmit', reValidateMode: 'onChange' });
const {
formState: { isSubmitting },
handleSubmit,
} = methods;
const onSubmit = handleSubmit((data) => {
if (isSubmitting) {
return;
}
void onComplete?.(data);
});
return (
<div className={styles.container}>
<div className={styles.header}>
<IconButton size="large" onClick={onClose}>
<Close />
</IconButton>
<div className={styles.separator} />
<CardTitle size="small" title={<DangerousRaw>{title}</DangerousRaw>} subtitle={subtitle} />
<Spacer />
<Button type="plain" size="small" title="general.skip" onClick={onClose} />
{subtype && (
<Button
type="outline"
title="admin_console.get_started.get_sample_file"
onClick={() => {
onClickFetchSampleProject(subtype);
}}
/>
)}
</div>
<div className={styles.content}>
<FormProvider {...methods}>
<form onSubmit={onSubmit}>
{isValidElement(bannerComponent) &&
cloneElement(bannerComponent, {
className: styles.banner,
onChange: setSubtype,
onToggle: () => {
setActiveStepIndex(0);
},
})}
<MDXProvider
components={{
code: ({ className, children }) => {
const [, language] = /language-(\w+)/.exec(className ?? '') ?? [];
return <CodeEditor isReadonly language={language} value={String(children)} />;
},
}}
>
<Suspense fallback={<div>Loading...</div>}>
{GuideComponent && (
<GuideComponent
activeStepIndex={activeStepIndex}
invalidStepIndex={invalidStepIndex}
onNext={(nextIndex: number) => {
setActiveStepIndex(nextIndex);
}}
onError={(invalidIndex: number) => {
setInvalidStepIndex(invalidIndex);
}}
/>
)}
</Suspense>
</MDXProvider>
</form>
</FormProvider>
</div>
</div>
);
};
export default Guide;

View file

@ -153,7 +153,8 @@ const translation = {
description: 'E.g.: Node.js, Express, ASP.NET, Java, PHP',
},
},
get_started: {
guide: {
get_sample_file: 'Get a sample file',
header_description:
'Follow a step by step guide to integrate your application or get a sample configured with your account settings',
title: 'Congratulations! The application has been created successfully.',
@ -241,7 +242,7 @@ const translation = {
sms: 'Setup SMS sender',
social: 'Add social connector',
},
get_started: {
guide: {
subtitle:
'A step by step guide to integrate your connector or get a sample configured with your account settings',
},
@ -267,9 +268,6 @@ const translation = {
more_options: 'MORE OPTIONS',
connector_deleted: 'The connector has been deleted.',
},
get_started: {
get_sample_file: 'Get a sample file',
},
users: {
title: 'User Management',
subtitle:

View file

@ -153,7 +153,8 @@ const translation = {
description: 'E.g.: Node.js, Express, ASP.NET, Java, PHP',
},
},
get_started: {
guide: {
get_sample_file: '获取示例工程',
header_description:
'参考如下教程,将 Logto 集成到您的应用中。您也可以点击右侧链接,获取我们为您准备好的示范工程。',
title: '恭喜!您的应用已成功创建。',
@ -239,7 +240,7 @@ const translation = {
sms: '设置短信服务商',
social: '添加社会化登录',
},
get_started: {
guide: {
subtitle: '请参考下列分步指南,配置您的 connector或点击按钮获取示例配置文件',
},
},
@ -264,9 +265,6 @@ const translation = {
more_options: '更多选项',
connector_deleted: '成功删除连接器。',
},
get_started: {
get_sample_file: '获取示例工程',
},
users: {
title: '用户管理',
subtitle: '管理已注册用户, 创建新用户,编辑用户资料。',