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

feat(console): sign in experience preview (#783)

This commit is contained in:
Wang Sijie 2022-05-12 16:22:38 +08:00 committed by GitHub
parent 2b5254369b
commit 6ab54c968b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 208 additions and 54 deletions

View file

@ -8,6 +8,7 @@
a {
color: var(--color-caption);
text-decoration: none;
cursor: pointer;
}
}

View file

@ -0,0 +1,25 @@
import classNames from 'classnames';
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import * as styles from './TabNavItem.module.scss';
type Props = {
href?: string;
isActive?: boolean;
onClick?: () => void;
children: React.ReactNode;
};
const TabNavItem = ({ children, href, isActive, onClick }: Props) => {
const location = useLocation();
const selected = href ? location.pathname === href : isActive;
return (
<div className={classNames(styles.link, selected && styles.selected)}>
{href ? <Link to={href}>{children}</Link> : <a onClick={onClick}>{children}</a>}
</div>
);
};
export default TabNavItem;

View file

@ -1,23 +0,0 @@
import classNames from 'classnames';
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import * as styles from './TabNavLink.module.scss';
type Props = {
href: string;
children: React.ReactNode;
};
const TabNavLink = ({ children, href }: Props) => {
const location = useLocation();
const selected = location.pathname === href;
return (
<div className={classNames(styles.link, selected && styles.selected)}>
<Link to={href}>{children}</Link>
</div>
);
};
export default TabNavLink;

View file

@ -3,7 +3,7 @@ import React from 'react';
import * as styles from './index.module.scss';
export { default as TabNavLink } from './TabNavLink';
export { default as TabNavItem } from './TabNavItem';
type Props = {
className?: string;

View file

@ -16,7 +16,7 @@ import Drawer from '@/components/Drawer';
import FormField from '@/components/FormField';
import ImagePlaceholder from '@/components/ImagePlaceholder';
import LinkButton from '@/components/LinkButton';
import TabNav, { TabNavLink } from '@/components/TabNav';
import TabNav, { TabNavItem } from '@/components/TabNav';
import TextInput from '@/components/TextInput';
import useApi, { RequestError } from '@/hooks/use-api';
import Back from '@/icons/Back';
@ -144,7 +144,7 @@ const ApiResourceDetails = () => {
</Card>
<Card className={classNames(styles.body, detailsStyles.body)}>
<TabNav>
<TabNavLink href={location.pathname}>{t('api_resource_details.settings')}</TabNavLink>
<TabNavItem href={location.pathname}>{t('api_resource_details.settings')}</TabNavItem>
</TabNav>
<form className={classNames(styles.form, detailsStyles.body)} onSubmit={onSubmit}>
<div className={styles.fields}>

View file

@ -15,7 +15,7 @@ import CopyToClipboard from '@/components/CopyToClipboard';
import Drawer from '@/components/Drawer';
import ImagePlaceholder from '@/components/ImagePlaceholder';
import LinkButton from '@/components/LinkButton';
import TabNav, { TabNavLink } from '@/components/TabNav';
import TabNav, { TabNavItem } from '@/components/TabNav';
import useApi, { RequestError } from '@/hooks/use-api';
import Back from '@/icons/Back';
import Delete from '@/icons/Delete';
@ -166,12 +166,12 @@ const ApplicationDetails = () => {
</Card>
<Card className={classNames(styles.body, detailsStyles.body)}>
<TabNav>
<TabNavLink href={`/applications/${data.id}/settings`}>
<TabNavItem href={`/applications/${data.id}/settings`}>
{t('application_details.settings')}
</TabNavLink>
<TabNavLink href={`/applications/${data.id}/advanced-settings`}>
</TabNavItem>
<TabNavItem href={`/applications/${data.id}/advanced-settings`}>
{t('application_details.advanced_settings')}
</TabNavLink>
</TabNavItem>
</TabNav>
<FormProvider {...formMethods}>
<form className={classNames(styles.form, detailsStyles.body)} onSubmit={onSubmit}>

View file

@ -15,7 +15,7 @@ import ImagePlaceholder from '@/components/ImagePlaceholder';
import LinkButton from '@/components/LinkButton';
import Markdown from '@/components/Markdown';
import Status from '@/components/Status';
import TabNav, { TabNavLink } from '@/components/TabNav';
import TabNav, { TabNavItem } from '@/components/TabNav';
import UnnamedTrans from '@/components/UnnamedTrans';
import useApi, { RequestError } from '@/hooks/use-api';
import Back from '@/icons/Back';
@ -182,9 +182,9 @@ const ConnectorDetails = () => {
{data && (
<Card className={classNames(styles.body, detailsStyles.body)}>
<TabNav>
<TabNavLink href={`/connectors/${connectorId ?? ''}`}>
<TabNavItem href={`/connectors/${connectorId ?? ''}`}>
{t('connector_details.tab_settings')}
</TabNavLink>
</TabNavItem>
</TabNav>
<div className={styles.main}>
<CodeEditor

View file

@ -8,7 +8,7 @@ import useSWR from 'swr';
import Button from '@/components/Button';
import Card from '@/components/Card';
import CardTitle from '@/components/CardTitle';
import TabNav, { TabNavLink } from '@/components/TabNav';
import TabNav, { TabNavItem } from '@/components/TabNav';
import TableEmpty from '@/components/Table/TableEmpty';
import TableError from '@/components/Table/TableError';
import TableLoading from '@/components/Table/TableLoading';
@ -67,8 +67,8 @@ const Connectors = () => {
)}
</div>
<TabNav className={styles.tabs}>
<TabNavLink href="/connectors">{t('connectors.tab_email_sms')}</TabNavLink>
<TabNavLink href="/connectors/social">{t('connectors.tab_social')}</TabNavLink>
<TabNavItem href="/connectors">{t('connectors.tab_email_sms')}</TabNavItem>
<TabNavItem href="/connectors/social">{t('connectors.tab_social')}</TabNavItem>
</TabNav>
<div className={classNames(styles.table, tableStyles.scrollable)}>
<table>

View file

@ -12,7 +12,7 @@ import Card from '@/components/Card';
import CardTitle from '@/components/CardTitle';
import FormField from '@/components/FormField';
import Select from '@/components/Select';
import TabNav, { TabNavLink } from '@/components/TabNav';
import TabNav, { TabNavItem } from '@/components/TabNav';
import { themeStorageKey } from '@/consts';
import useApi, { RequestError } from '@/hooks/use-api';
import * as detailsStyles from '@/scss/details.module.scss';
@ -57,7 +57,7 @@ const Settings = () => {
<Card className={classNames(detailsStyles.container, styles.container)}>
<CardTitle title="settings.title" subtitle="settings.description" />
<TabNav>
<TabNavLink href="/settings">{t('settings.tabs.general')}</TabNavLink>
<TabNavItem href="/settings">{t('settings.tabs.general')}</TabNavItem>
</TabNav>
{!data && !error && <div>loading</div>}
{error && <div>{`error occurred: ${error.body.message}`}</div>}

View file

@ -0,0 +1,46 @@
@use '@/scss/underscore' as _;
.preview {
width: 578px;
display: flex;
flex-direction: column;
padding: 0;
overflow: hidden;
.header {
display: flex;
margin: _.unit(6) _.unit(6) 0;
.title {
font: var(--font-subhead-1);
flex: 1;
}
.selects {
display: flex;
align-items: center;
> * {
width: 104px;
&:first-child {
margin-right: _.unit(2);
}
}
}
}
.nav {
padding: 0 _.unit(6);
}
.body {
flex: 1;
iframe {
border: none;
width: 100%;
height: 100%;
}
}
}

View file

@ -0,0 +1,84 @@
import { Language } from '@logto/phrases';
import { AppearanceMode, SignInExperience } from '@logto/schemas';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import Card from '@/components/Card';
import Select from '@/components/Select';
import TabNav, { TabNavItem } from '@/components/TabNav';
import * as styles from './Preview.module.scss';
type Props = {
signInExperience: SignInExperience;
};
const Preview = ({ signInExperience }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [language, setLanguage] = useState<Language>(Language.English);
const [mode, setMode] = useState<AppearanceMode>(AppearanceMode.LightMode);
const [platform, setPlatform] = useState<'web' | 'mobile'>('web');
// TODO: is a placeholder
const config = encodeURIComponent(
JSON.stringify({
...signInExperience,
language,
mode,
platform,
})
);
return (
<Card className={styles.preview}>
<div className={styles.header}>
<div className={styles.title}>{t('sign_in_exp.preview.title')}</div>
<div className={styles.selects}>
<Select
value={language}
options={[
{ value: Language.English, title: t('sign_in_exp.preview.languages.english') },
{ value: Language.Chinese, title: t('sign_in_exp.preview.languages.chinese') },
]}
onChange={(value) => {
setLanguage(value as Language);
}}
/>
<Select
value={mode}
options={[
{ value: AppearanceMode.LightMode, title: t('sign_in_exp.preview.light') },
{ value: AppearanceMode.DarkMode, title: t('sign_in_exp.preview.dark') },
]}
onChange={(value) => {
setMode(value as AppearanceMode);
}}
/>
</div>
</div>
<TabNav className={styles.nav}>
<TabNavItem
isActive={platform === 'web'}
onClick={() => {
setPlatform('web');
}}
>
{t('sign_in_exp.preview.web')}
</TabNavItem>
<TabNavItem
isActive={platform === 'mobile'}
onClick={() => {
setPlatform('mobile');
}}
>
{t('sign_in_exp.preview.mobile')}
</TabNavItem>
</TabNav>
<div className={styles.body}>
<iframe src={`/sign-in?config=${config}`} />
</div>
</Card>
);
};
export default Preview;

View file

@ -22,8 +22,4 @@
padding-bottom: _.unit(8);
}
}
.preview {
width: 500px;
}
}

View file

@ -11,7 +11,7 @@ import useSWR from 'swr';
import Button from '@/components/Button';
import Card from '@/components/Card';
import CardTitle from '@/components/CardTitle';
import TabNav, { TabNavLink } from '@/components/TabNav';
import TabNav, { TabNavItem } from '@/components/TabNav';
import useApi, { RequestError } from '@/hooks/use-api';
import useAdminConsoleConfigs from '@/hooks/use-configs';
import * as detailsStyles from '@/scss/details.module.scss';
@ -19,6 +19,7 @@ import * as modalStyles from '@/scss/modal.module.scss';
import BrandingForm from './components/BrandingForm';
import LanguagesForm from './components/LanguagesForm';
import Preview from './components/Preview';
import SaveAlert from './components/SaveAlert';
import SignInMethodsForm from './components/SignInMethodsForm';
import TermsForm from './components/TermsForm';
@ -39,9 +40,11 @@ const SignInExperience = () => {
reset,
handleSubmit,
getValues,
watch,
formState: { isSubmitting },
} = methods;
const api = useApi();
const formData = watch();
useEffect(() => {
if (data) {
@ -95,15 +98,15 @@ const SignInExperience = () => {
<Card className={styles.card}>
<CardTitle title="sign_in_exp.title" subtitle="sign_in_exp.description" />
<TabNav className={styles.tabs}>
<TabNavLink href="/sign-in-experience/experience">
<TabNavItem href="/sign-in-experience/experience">
{t('sign_in_exp.tabs.experience')}
</TabNavLink>
<TabNavLink href="/sign-in-experience/methods">
</TabNavItem>
<TabNavItem href="/sign-in-experience/methods">
{t('sign_in_exp.tabs.methods')}
</TabNavLink>
<TabNavLink href="/sign-in-experience/others">
</TabNavItem>
<TabNavItem href="/sign-in-experience/others">
{t('sign_in_exp.tabs.others')}
</TabNavLink>
</TabNavItem>
</TabNav>
{!data && !error && <div>loading</div>}
{error && <div>{`error occurred: ${error.body.message}`}</div>}
@ -135,7 +138,7 @@ const SignInExperience = () => {
)}
</Card>
</div>
<Card className={styles.preview}>TODO</Card>
{formData.id && <Preview signInExperience={signInExperienceParser.toRemoteModel(formData)} />}
{data && (
<ReactModal
isOpen={Boolean(dataToCompare)}

View file

@ -17,7 +17,7 @@ import CopyToClipboard from '@/components/CopyToClipboard';
import FormField from '@/components/FormField';
import ImagePlaceholder from '@/components/ImagePlaceholder';
import LinkButton from '@/components/LinkButton';
import TabNav, { TabNavLink } from '@/components/TabNav';
import TabNav, { TabNavItem } from '@/components/TabNav';
import TextInput from '@/components/TextInput';
import useApi, { RequestError } from '@/hooks/use-api';
import Back from '@/icons/Back';
@ -179,8 +179,8 @@ const UserDetails = () => {
</Card>
<Card className={classNames(styles.body, detailsStyles.body)}>
<TabNav>
<TabNavLink href={`/users/${id}`}>{t('user_details.tab_settings')}</TabNavLink>
<TabNavLink href={`/users/${id}/logs`}>{t('user_details.tab_logs')}</TabNavLink>
<TabNavItem href={`/users/${id}`}>{t('user_details.tab_settings')}</TabNavItem>
<TabNavItem href={`/users/${id}/logs`}>{t('user_details.tab_logs')}</TabNavItem>
</TabNav>
<form className={styles.form} onSubmit={onSubmit}>
<div className={styles.fields}>

View file

@ -470,6 +470,17 @@ const translation = {
before: 'Before',
after: 'After',
},
preview: {
title: 'Sign in preview',
languages: {
english: 'English',
chinese: 'Chinese',
},
dark: 'Dark',
light: 'Light',
mobile: 'Mobile',
web: 'Web',
},
},
settings: {
title: 'Settings',

View file

@ -465,6 +465,17 @@ const translation = {
before: '修改前',
after: '修改后',
},
preview: {
title: 'Sign in preview',
languages: {
english: 'English',
chinese: 'Chinese',
},
dark: 'Dark',
light: 'Light',
mobile: 'Mobile',
web: 'Web',
},
},
settings: {
title: '设置',