mirror of
https://github.com/logto-io/logto.git
synced 2025-01-27 21:39:16 -05:00
refactor(console): sie preview (#3277)
This commit is contained in:
parent
1477751e30
commit
c93b819b7e
5 changed files with 243 additions and 198 deletions
|
@ -0,0 +1,92 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.preview {
|
||||
background: var(--color-surface-variant);
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.web {
|
||||
.device {
|
||||
width: 480px;
|
||||
height: 380px;
|
||||
position: relative;
|
||||
background: var(--color-surface-1);
|
||||
margin: 0 auto;
|
||||
|
||||
iframe {
|
||||
width: 960px;
|
||||
height: 760px;
|
||||
transform: scale(0.5);
|
||||
position: absolute;
|
||||
top: -190px;
|
||||
left: -240px;
|
||||
background: var(--color-surface-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
padding: _.unit(10) 0;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
|
||||
.deviceWrapper {
|
||||
width: 390px;
|
||||
height: 450px;
|
||||
margin: 0 auto;
|
||||
transform: scale(0.5);
|
||||
transform-origin: top center;
|
||||
|
||||
.device {
|
||||
border-radius: 26px;
|
||||
overflow: hidden;
|
||||
|
||||
.topBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: _.unit(3) _.unit(6);
|
||||
|
||||
.time {
|
||||
flex: 1;
|
||||
font: var(--font-label-2);
|
||||
}
|
||||
}
|
||||
|
||||
&.dark {
|
||||
// Sync with iframe's UI color
|
||||
background: #1a1c1d;
|
||||
|
||||
.topBar {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&.light {
|
||||
// Sync with iframe's UI color
|
||||
background: #fff;
|
||||
|
||||
.topBar {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 390px;
|
||||
height: 808px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-height: 1100px) {
|
||||
transform: unset;
|
||||
height: 900px;
|
||||
}
|
||||
|
||||
@media screen and (min-height: 900px) and (max-height: 1100px) {
|
||||
transform: scale(0.75);
|
||||
height: 675px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
import type { LanguageTag } from '@logto/language-kit';
|
||||
import type { ConnectorMetadata, ConnectorResponse, SignInExperience } from '@logto/schemas';
|
||||
import { ConnectorType, AppearanceMode } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import classNames from 'classnames';
|
||||
import { format } from 'date-fns';
|
||||
import { useContext, useRef, useMemo, useCallback, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import PhoneInfo from '@/assets/images/phone-info.svg';
|
||||
import { AppEndpointsContext } from '@/contexts/AppEndpointsProvider';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useUiLanguages from '@/hooks/use-ui-languages';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
import { PreviewPlatform } from './types';
|
||||
|
||||
type Props = {
|
||||
platform: PreviewPlatform;
|
||||
mode: Omit<AppearanceMode, AppearanceMode.SyncWithSystem>;
|
||||
language: LanguageTag;
|
||||
signInExperience?: SignInExperience;
|
||||
};
|
||||
|
||||
const SignInExperiencePreview = ({ platform, mode, language, signInExperience }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { customPhrases } = useUiLanguages();
|
||||
const { userEndpoint } = useContext(AppEndpointsContext);
|
||||
const previewRef = useRef<HTMLIFrameElement>(null);
|
||||
const { data: allConnectors } = useSWR<ConnectorResponse[], RequestError>('api/connectors');
|
||||
|
||||
const configForUiPage = useMemo(() => {
|
||||
if (!allConnectors || !signInExperience) {
|
||||
return;
|
||||
}
|
||||
|
||||
const socialConnectors = signInExperience.socialSignInConnectorTargets.reduce<
|
||||
Array<ConnectorMetadata & { id: string }>
|
||||
>(
|
||||
(previous, connectorTarget) => [
|
||||
...previous,
|
||||
...allConnectors.filter(({ target }) => target === connectorTarget),
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const hasEmailConnector = allConnectors.some(({ type }) => type === ConnectorType.Email);
|
||||
|
||||
const hasSmsConnector = allConnectors.some(({ type }) => type === ConnectorType.Sms);
|
||||
|
||||
return {
|
||||
signInExperience: {
|
||||
...signInExperience,
|
||||
socialConnectors,
|
||||
forgotPassword: {
|
||||
email: hasEmailConnector,
|
||||
sms: hasSmsConnector,
|
||||
},
|
||||
},
|
||||
language,
|
||||
mode,
|
||||
platform: platform === PreviewPlatform.DesktopWeb ? 'web' : 'mobile',
|
||||
isNative: platform === PreviewPlatform.Mobile,
|
||||
};
|
||||
}, [allConnectors, language, mode, platform, signInExperience]);
|
||||
|
||||
const postPreviewMessage = useCallback(() => {
|
||||
if (!configForUiPage || !customPhrases) {
|
||||
return;
|
||||
}
|
||||
|
||||
previewRef.current?.contentWindow?.postMessage(
|
||||
{ sender: 'ac_preview', config: configForUiPage },
|
||||
userEndpoint?.origin ?? ''
|
||||
);
|
||||
}, [userEndpoint?.origin, configForUiPage, customPhrases]);
|
||||
|
||||
useEffect(() => {
|
||||
postPreviewMessage();
|
||||
|
||||
const iframe = previewRef.current;
|
||||
|
||||
iframe?.addEventListener('load', postPreviewMessage);
|
||||
|
||||
return () => {
|
||||
iframe?.removeEventListener('load', postPreviewMessage);
|
||||
};
|
||||
}, [postPreviewMessage]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
styles.preview,
|
||||
platform === PreviewPlatform.DesktopWeb ? styles.web : styles.mobile
|
||||
)}
|
||||
style={conditional(
|
||||
platform === PreviewPlatform.DesktopWeb && {
|
||||
// Set background color to match iframe's background color on both dark and light mode.
|
||||
backgroundColor: mode === AppearanceMode.DarkMode ? '#000' : '#e5e1ec',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className={styles.deviceWrapper}>
|
||||
<div className={classNames(styles.device, styles[String(mode)])}>
|
||||
{platform !== PreviewPlatform.DesktopWeb && (
|
||||
<div className={styles.topBar}>
|
||||
<div className={styles.time}>{format(Date.now(), 'HH:mm')}</div>
|
||||
<PhoneInfo />
|
||||
</div>
|
||||
)}
|
||||
<iframe
|
||||
ref={previewRef}
|
||||
// Allow all sandbox rules
|
||||
sandbox={undefined}
|
||||
src={new URL('/sign-in?preview=true', userEndpoint).toString()}
|
||||
tabIndex={-1}
|
||||
title={t('sign_in_exp.preview.title')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignInExperiencePreview;
|
|
@ -0,0 +1,5 @@
|
|||
export enum PreviewPlatform {
|
||||
DesktopWeb = 'desktopWeb',
|
||||
Mobile = 'mobile',
|
||||
MobileWeb = 'mobileWeb',
|
||||
}
|
|
@ -38,96 +38,4 @@
|
|||
margin-top: _.unit(6);
|
||||
border-bottom: unset;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
background: var(--color-surface-variant);
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.web {
|
||||
.device {
|
||||
width: 480px;
|
||||
height: 380px;
|
||||
position: relative;
|
||||
background: var(--color-surface-1);
|
||||
margin: 0 auto;
|
||||
|
||||
iframe {
|
||||
width: 960px;
|
||||
height: 760px;
|
||||
transform: scale(0.5);
|
||||
position: absolute;
|
||||
top: -190px;
|
||||
left: -240px;
|
||||
background: var(--color-surface-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
padding: _.unit(10) 0;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
|
||||
.deviceWrapper {
|
||||
width: 390px;
|
||||
height: 450px;
|
||||
margin: 0 auto;
|
||||
transform: scale(0.5);
|
||||
transform-origin: top center;
|
||||
|
||||
.device {
|
||||
border-radius: 26px;
|
||||
overflow: hidden;
|
||||
|
||||
.topBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: _.unit(3) _.unit(6);
|
||||
|
||||
.time {
|
||||
flex: 1;
|
||||
font: var(--font-label-2);
|
||||
}
|
||||
}
|
||||
|
||||
&.dark {
|
||||
// Sync with iframe's UI color
|
||||
background: #1a1c1d;
|
||||
|
||||
.topBar {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&.light {
|
||||
// Sync with iframe's UI color
|
||||
background: #fff;
|
||||
|
||||
.topBar {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 390px;
|
||||
height: 808px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-height: 1100px) {
|
||||
transform: unset;
|
||||
height: 900px;
|
||||
}
|
||||
|
||||
@media screen and (min-height: 900px) and (max-height: 1100px) {
|
||||
transform: scale(0.75);
|
||||
height: 675px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
import type { LanguageTag } from '@logto/language-kit';
|
||||
import { languages as uiLanguageNameMapping } from '@logto/language-kit';
|
||||
import type { ConnectorResponse, ConnectorMetadata, SignInExperience } from '@logto/schemas';
|
||||
import { ConnectorType, AppearanceMode } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import type { SignInExperience } from '@logto/schemas';
|
||||
import { AppearanceMode } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { format } from 'date-fns';
|
||||
import { useEffect, useMemo, useState, useRef, useCallback, useContext } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import PhoneInfo from '@/assets/images/phone-info.svg';
|
||||
import LivePreviewButton from '@/components/LivePreviewButton';
|
||||
import Select from '@/components/Select';
|
||||
import SignInExperiencePreview from '@/components/SignInExperiencePreview';
|
||||
import { PreviewPlatform } from '@/components/SignInExperiencePreview/types';
|
||||
import TabNav, { TabNavItem } from '@/components/TabNav';
|
||||
import ToggleThemeButton from '@/components/ToggleThemeButton';
|
||||
import { AppEndpointsContext } from '@/contexts/AppEndpointsProvider';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useUiLanguages from '@/hooks/use-ui-languages';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
@ -38,11 +34,8 @@ const Preview = ({
|
|||
const [mode, setMode] = useState<Omit<AppearanceMode, AppearanceMode.SyncWithSystem>>(
|
||||
AppearanceMode.LightMode
|
||||
);
|
||||
const [platform, setPlatform] = useState<'desktopWeb' | 'mobile' | 'mobileWeb'>('desktopWeb');
|
||||
const { data: allConnectors } = useSWR<ConnectorResponse[], RequestError>('api/connectors');
|
||||
const previewRef = useRef<HTMLIFrameElement>(null);
|
||||
const { customPhrases, languages } = useUiLanguages();
|
||||
const { userEndpoint } = useContext(AppEndpointsContext);
|
||||
const [platform, setPlatform] = useState<PreviewPlatform>(PreviewPlatform.DesktopWeb);
|
||||
const { languages } = useUiLanguages();
|
||||
|
||||
useEffect(() => {
|
||||
if (!signInExperience?.color.isDarkModeEnabled) {
|
||||
|
@ -74,64 +67,6 @@ const Preview = ({
|
|||
}
|
||||
}, [language, availableLanguageOptions]);
|
||||
|
||||
const config = useMemo(() => {
|
||||
if (!allConnectors || !signInExperience) {
|
||||
return;
|
||||
}
|
||||
|
||||
const socialConnectors = signInExperience.socialSignInConnectorTargets.reduce<
|
||||
Array<ConnectorMetadata & { id: string }>
|
||||
>(
|
||||
(previous, connectorTarget) => [
|
||||
...previous,
|
||||
...allConnectors.filter(({ target }) => target === connectorTarget),
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const hasEmailConnector = allConnectors.some(({ type }) => type === ConnectorType.Email);
|
||||
|
||||
const hasSmsConnector = allConnectors.some(({ type }) => type === ConnectorType.Sms);
|
||||
|
||||
return {
|
||||
signInExperience: {
|
||||
...signInExperience,
|
||||
socialConnectors,
|
||||
forgotPassword: {
|
||||
email: hasEmailConnector,
|
||||
sms: hasSmsConnector,
|
||||
},
|
||||
},
|
||||
language,
|
||||
mode,
|
||||
platform: platform === 'desktopWeb' ? 'web' : 'mobile',
|
||||
isNative: platform === 'mobile',
|
||||
};
|
||||
}, [allConnectors, language, mode, platform, signInExperience]);
|
||||
|
||||
const postPreviewMessage = useCallback(() => {
|
||||
if (!config || !customPhrases) {
|
||||
return;
|
||||
}
|
||||
|
||||
previewRef.current?.contentWindow?.postMessage(
|
||||
{ sender: 'ac_preview', config },
|
||||
userEndpoint?.origin ?? ''
|
||||
);
|
||||
}, [userEndpoint?.origin, config, customPhrases]);
|
||||
|
||||
useEffect(() => {
|
||||
postPreviewMessage();
|
||||
|
||||
const iframe = previewRef.current;
|
||||
|
||||
iframe?.addEventListener('load', postPreviewMessage);
|
||||
|
||||
return () => {
|
||||
iframe?.removeEventListener('load', postPreviewMessage);
|
||||
};
|
||||
}, [postPreviewMessage]);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.preview, className)}>
|
||||
<div className={styles.header}>
|
||||
|
@ -158,58 +93,36 @@ const Preview = ({
|
|||
</div>
|
||||
<TabNav className={styles.nav}>
|
||||
<TabNavItem
|
||||
isActive={platform === 'desktopWeb'}
|
||||
isActive={platform === PreviewPlatform.DesktopWeb}
|
||||
onClick={() => {
|
||||
setPlatform('desktopWeb');
|
||||
setPlatform(PreviewPlatform.DesktopWeb);
|
||||
}}
|
||||
>
|
||||
{t('sign_in_exp.preview.desktop_web')}
|
||||
</TabNavItem>
|
||||
<TabNavItem
|
||||
isActive={platform === 'mobileWeb'}
|
||||
isActive={platform === PreviewPlatform.MobileWeb}
|
||||
onClick={() => {
|
||||
setPlatform('mobileWeb');
|
||||
setPlatform(PreviewPlatform.MobileWeb);
|
||||
}}
|
||||
>
|
||||
{t('sign_in_exp.preview.mobile_web')}
|
||||
</TabNavItem>
|
||||
<TabNavItem
|
||||
isActive={platform === 'mobile'}
|
||||
isActive={platform === PreviewPlatform.Mobile}
|
||||
onClick={() => {
|
||||
setPlatform('mobile');
|
||||
setPlatform(PreviewPlatform.Mobile);
|
||||
}}
|
||||
>
|
||||
{t('sign_in_exp.preview.native')}
|
||||
</TabNavItem>
|
||||
</TabNav>
|
||||
<div
|
||||
className={classNames(styles.body, platform === 'desktopWeb' ? styles.web : styles.mobile)}
|
||||
style={conditional(
|
||||
platform === 'desktopWeb' && {
|
||||
// Set background color to match iframe's background color on both dark and light mode.
|
||||
backgroundColor: mode === AppearanceMode.DarkMode ? '#000' : '#e5e1ec',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className={styles.deviceWrapper}>
|
||||
<div className={classNames(styles.device, styles[String(mode)])}>
|
||||
{platform !== 'desktopWeb' && (
|
||||
<div className={styles.topBar}>
|
||||
<div className={styles.time}>{format(Date.now(), 'HH:mm')}</div>
|
||||
<PhoneInfo />
|
||||
</div>
|
||||
)}
|
||||
<iframe
|
||||
ref={previewRef}
|
||||
// Allow all sandbox rules
|
||||
sandbox={undefined}
|
||||
src={new URL('/sign-in?preview=true', userEndpoint).toString()}
|
||||
tabIndex={-1}
|
||||
title={t('sign_in_exp.preview.title')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SignInExperiencePreview
|
||||
platform={platform}
|
||||
mode={mode}
|
||||
language={language}
|
||||
signInExperience={signInExperience}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue