mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
feat(console): sie form reorg (#1218)
This commit is contained in:
parent
9aca422578
commit
2c413341d1
21 changed files with 174 additions and 89 deletions
|
@ -73,7 +73,7 @@ const Main = () => {
|
|||
<Route path=":userId/logs/:logId" element={<AuditLogDetails />} />
|
||||
</Route>
|
||||
<Route path="sign-in-experience">
|
||||
<Route index element={<Navigate replace to="experience" />} />
|
||||
<Route index element={<Navigate replace to="branding" />} />
|
||||
<Route path=":tab" element={<SignInExperience />} />
|
||||
</Route>
|
||||
<Route path="settings" element={<Settings />} />
|
||||
|
|
|
@ -3,10 +3,8 @@ import React from 'react';
|
|||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ColorPicker from '@/components/ColorPicker';
|
||||
import FormField from '@/components/FormField';
|
||||
import RadioGroup, { Radio } from '@/components/RadioGroup';
|
||||
import Switch from '@/components/Switch';
|
||||
import TextInput from '@/components/TextInput';
|
||||
import { uriValidator } from '@/utilities/validator';
|
||||
|
||||
|
@ -22,39 +20,13 @@ const BrandingForm = () => {
|
|||
formState: { errors },
|
||||
} = useFormContext<SignInExperienceForm>();
|
||||
|
||||
const isDarkModeEnabled = watch('branding.isDarkModeEnabled');
|
||||
const isDarkModeEnabled = watch('color.isDarkModeEnabled');
|
||||
const style = watch('branding.style');
|
||||
const isSloganRequired = style === BrandingStyle.Logo_Slogan;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.title}>{t('sign_in_exp.branding.title')}</div>
|
||||
<FormField title="admin_console.sign_in_exp.branding.primary_color">
|
||||
<Controller
|
||||
name="branding.primaryColor"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ColorPicker value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title="admin_console.sign_in_exp.branding.dark_mode">
|
||||
<Switch
|
||||
label={t('sign_in_exp.branding.dark_mode_description')}
|
||||
{...register('branding.isDarkModeEnabled')}
|
||||
/>
|
||||
</FormField>
|
||||
{isDarkModeEnabled && (
|
||||
<FormField title="admin_console.sign_in_exp.branding.dark_primary_color">
|
||||
<Controller
|
||||
name="branding.darkPrimaryColor"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ColorPicker value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
<FormField title="admin_console.sign_in_exp.branding.ui_style">
|
||||
<Controller
|
||||
name="branding.style"
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ColorPicker from '@/components/ColorPicker';
|
||||
import FormField from '@/components/FormField';
|
||||
import Switch from '@/components/Switch';
|
||||
|
||||
import { SignInExperienceForm } from '../types';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const ColorForm = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { watch, register, control } = useFormContext<SignInExperienceForm>();
|
||||
|
||||
const isDarkModeEnabled = watch('color.isDarkModeEnabled');
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.title}>{t('sign_in_exp.color.title')}</div>
|
||||
<FormField title="admin_console.sign_in_exp.color.primary_color">
|
||||
<Controller
|
||||
name="color.primaryColor"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ColorPicker value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title="admin_console.sign_in_exp.color.dark_mode">
|
||||
<Switch
|
||||
label={t('sign_in_exp.color.dark_mode_description')}
|
||||
{...register('color.isDarkModeEnabled')}
|
||||
/>
|
||||
</FormField>
|
||||
{isDarkModeEnabled && (
|
||||
<FormField title="admin_console.sign_in_exp.color.dark_primary_color">
|
||||
<Controller
|
||||
name="color.darkPrimaryColor"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ColorPicker value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorForm;
|
|
@ -20,6 +20,7 @@ import usePreviewConfigs from '../hooks';
|
|||
import { SignInExperienceForm } from '../types';
|
||||
import { signInExperienceParser } from '../utilities';
|
||||
import BrandingForm from './BrandingForm';
|
||||
import ColorForm from './ColorForm';
|
||||
import * as styles from './GuideModal.module.scss';
|
||||
import LanguagesForm from './LanguagesForm';
|
||||
import Preview from './Preview';
|
||||
|
@ -100,6 +101,9 @@ const GuideModal = ({ isOpen, onClose }: Props) => {
|
|||
<form onSubmit={onSubmit}>
|
||||
<div className={styles.main}>
|
||||
<div className={styles.form}>
|
||||
<div className={styles.card}>
|
||||
<ColorForm />
|
||||
</div>
|
||||
<div className={styles.card}>
|
||||
<BrandingForm />
|
||||
</div>
|
||||
|
|
|
@ -17,6 +17,7 @@ import useSettings from '@/hooks/use-settings';
|
|||
import * as detailsStyles from '@/scss/details.module.scss';
|
||||
|
||||
import BrandingForm from './components/BrandingForm';
|
||||
import ColorForm from './components/ColorForm';
|
||||
import LanguagesForm from './components/LanguagesForm';
|
||||
import Preview from './components/Preview';
|
||||
import SignInMethodsChangePreview from './components/SignInMethodsChangePreview';
|
||||
|
@ -100,8 +101,8 @@ const SignInExperience = () => {
|
|||
<Card className={styles.card}>
|
||||
<CardTitle title="sign_in_exp.title" subtitle="sign_in_exp.description" />
|
||||
<TabNav className={styles.tabs}>
|
||||
<TabNavItem href="/sign-in-experience/experience">
|
||||
{t('sign_in_exp.tabs.experience')}
|
||||
<TabNavItem href="/sign-in-experience/branding">
|
||||
{t('sign_in_exp.tabs.branding')}
|
||||
</TabNavItem>
|
||||
<TabNavItem href="/sign-in-experience/methods">
|
||||
{t('sign_in_exp.tabs.methods')}
|
||||
|
@ -116,14 +117,19 @@ const SignInExperience = () => {
|
|||
<FormProvider {...methods}>
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className={classNames(detailsStyles.body, styles.form)}>
|
||||
{tab === 'experience' && (
|
||||
{tab === 'branding' && (
|
||||
<>
|
||||
<ColorForm />
|
||||
<BrandingForm />
|
||||
<TermsForm />
|
||||
</>
|
||||
)}
|
||||
{tab === 'methods' && <SignInMethodsForm />}
|
||||
{tab === 'others' && <LanguagesForm />}
|
||||
{tab === 'others' && (
|
||||
<>
|
||||
<TermsForm />
|
||||
<LanguagesForm />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className={detailsStyles.footer}>
|
||||
<div className={detailsStyles.footerMain}>
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
SignInMethods,
|
||||
SignInMethodState,
|
||||
} from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
|
||||
import { LanguageMode, SignInExperienceForm } from './types';
|
||||
|
||||
|
@ -57,18 +58,23 @@ export const signInExperienceParser = {
|
|||
},
|
||||
toRemoteModel: (setup: SignInExperienceForm): SignInExperience => {
|
||||
const {
|
||||
color,
|
||||
branding,
|
||||
languageInfo: { mode, fallbackLanguage, fixedLanguage },
|
||||
} = setup;
|
||||
|
||||
return {
|
||||
...setup,
|
||||
color: {
|
||||
...color,
|
||||
// Transform empty string to undefined
|
||||
darkPrimaryColor: conditional(color.darkPrimaryColor?.length && color.darkPrimaryColor),
|
||||
},
|
||||
branding: {
|
||||
...branding,
|
||||
// Transform empty string to undefined
|
||||
darkPrimaryColor: branding.darkPrimaryColor?.length ? branding.darkPrimaryColor : undefined,
|
||||
darkLogoUrl: branding.darkLogoUrl?.length ? branding.darkLogoUrl : undefined,
|
||||
slogan: branding.slogan?.length ? branding.slogan : undefined,
|
||||
darkLogoUrl: conditional(branding.darkLogoUrl?.length && branding.darkLogoUrl),
|
||||
slogan: conditional(branding.slogan?.length && branding.slogan),
|
||||
},
|
||||
signInMethods: {
|
||||
username: findMethodState(setup, 'username'),
|
||||
|
|
|
@ -8,14 +8,17 @@ import {
|
|||
SignInMethodState,
|
||||
TermsOfUse,
|
||||
SignInMode,
|
||||
Color,
|
||||
} from '@logto/schemas';
|
||||
|
||||
export const mockSignInExperience: SignInExperience = {
|
||||
id: 'foo',
|
||||
branding: {
|
||||
color: {
|
||||
primaryColor: '#000',
|
||||
isDarkModeEnabled: true,
|
||||
darkPrimaryColor: '#fff',
|
||||
},
|
||||
branding: {
|
||||
style: BrandingStyle.Logo,
|
||||
logoUrl: 'http://logto.png',
|
||||
slogan: 'logto',
|
||||
|
@ -38,10 +41,13 @@ export const mockSignInExperience: SignInExperience = {
|
|||
signInMode: SignInMode.SignInAndRegister,
|
||||
};
|
||||
|
||||
export const mockBranding: Branding = {
|
||||
export const mockColor: Color = {
|
||||
primaryColor: '#000',
|
||||
isDarkModeEnabled: true,
|
||||
darkPrimaryColor: '#fff',
|
||||
};
|
||||
|
||||
export const mockBranding: Branding = {
|
||||
style: BrandingStyle.Logo_Slogan,
|
||||
logoUrl: 'http://silverhand.png',
|
||||
slogan: 'Silverhand.',
|
||||
|
|
|
@ -21,6 +21,7 @@ describe('sign-in-experience query', () => {
|
|||
|
||||
const dbvalue = {
|
||||
...mockSignInExperience,
|
||||
color: JSON.stringify(mockSignInExperience.color),
|
||||
branding: JSON.stringify(mockSignInExperience.branding),
|
||||
termsOfUse: JSON.stringify(mockSignInExperience.termsOfUse),
|
||||
languageInfo: JSON.stringify(mockSignInExperience.languageInfo),
|
||||
|
@ -31,7 +32,7 @@ describe('sign-in-experience query', () => {
|
|||
it('findDefaultSignInExperience', async () => {
|
||||
/* eslint-disable sql/no-unsafe-query */
|
||||
const expectSql = `
|
||||
select "id", "branding", "language_info", "terms_of_use", "sign_in_methods", "social_sign_in_connector_targets", "sign_in_mode"
|
||||
select "id", "color", "branding", "language_info", "terms_of_use", "sign_in_methods", "social_sign_in_connector_targets", "sign_in_mode"
|
||||
from "sign_in_experiences"
|
||||
where "id" = $1
|
||||
`;
|
||||
|
|
|
@ -26,30 +26,6 @@ const expectPatchResponseStatus = async (signInExperience: any, status: number)
|
|||
};
|
||||
|
||||
describe('branding', () => {
|
||||
const colorKeys = ['primaryColor', 'darkPrimaryColor'];
|
||||
const invalidColors = [null, '#0'];
|
||||
|
||||
describe('colors', () => {
|
||||
test.each(invalidColors)('should fail when color is %p', async (invalidColor) => {
|
||||
for (const colorKey of colorKeys) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await expectPatchResponseStatus(
|
||||
{ branding: { ...mockBranding, [colorKey]: invalidColor } },
|
||||
400
|
||||
);
|
||||
}
|
||||
});
|
||||
it('should succeed when color is valid', async () => {
|
||||
for (const colorKey of colorKeys) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await expectPatchResponseStatus(
|
||||
{ branding: { ...mockBranding, [colorKey]: '#169deF' } },
|
||||
200
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('style', () => {
|
||||
test.each(Object.values(BrandingStyle))('%p should succeed', async (style) => {
|
||||
const signInExperience = { branding: { ...mockBranding, style } };
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { CreateSignInExperience, SignInExperience } from '@logto/schemas';
|
||||
|
||||
import { mockColor, mockSignInExperience } from '@/__mocks__';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import signInExperiencesRoutes from './sign-in-experience';
|
||||
|
||||
jest.mock('@/queries/sign-in-experience', () => ({
|
||||
updateDefaultSignInExperience: jest.fn(
|
||||
async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
})
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/connectors', () => ({
|
||||
getConnectorInstances: jest.fn(async () => []),
|
||||
}));
|
||||
|
||||
const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
|
||||
|
||||
const expectPatchResponseStatus = async (signInExperience: any, status: number) => {
|
||||
const response = await signInExperienceRequester.patch('/sign-in-exp').send(signInExperience);
|
||||
expect(response.status).toEqual(status);
|
||||
};
|
||||
|
||||
const colorKeys = ['primaryColor', 'darkPrimaryColor'];
|
||||
const invalidColors = [null, '#0'];
|
||||
|
||||
describe('colors', () => {
|
||||
test.each(invalidColors)('should fail when color is %p', async (invalidColor) => {
|
||||
for (const colorKey of colorKeys) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await expectPatchResponseStatus({ color: { ...mockColor, [colorKey]: invalidColor } }, 400);
|
||||
}
|
||||
});
|
||||
it('should succeed when color is valid', async () => {
|
||||
for (const colorKey of colorKeys) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await expectPatchResponseStatus({ color: { ...mockColor, [colorKey]: '#169deF' } }, 200);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -13,6 +13,7 @@ import {
|
|||
mockSignInExperience,
|
||||
mockSignInMethods,
|
||||
mockWechatConnectorInstance,
|
||||
mockColor,
|
||||
} from '@/__mocks__';
|
||||
import * as signInExpLib from '@/lib/sign-in-experience';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
@ -142,6 +143,7 @@ describe('PATCH /sign-in-exp', () => {
|
|||
const validateSignInMethods = jest.spyOn(signInExpLib, 'validateSignInMethods');
|
||||
|
||||
const response = await signInExperienceRequester.patch('/sign-in-exp').send({
|
||||
color: mockColor,
|
||||
branding: mockBranding,
|
||||
termsOfUse,
|
||||
signInMethods: mockSignInMethods,
|
||||
|
@ -160,6 +162,7 @@ describe('PATCH /sign-in-exp', () => {
|
|||
status: 200,
|
||||
body: {
|
||||
...mockSignInExperience,
|
||||
color: mockColor,
|
||||
branding: mockBranding,
|
||||
termsOfUse,
|
||||
signInMethods: mockSignInMethods,
|
||||
|
|
|
@ -406,7 +406,7 @@ const translation = {
|
|||
title: 'Sign-in Experience',
|
||||
description: 'Customize the sign in UI to match your brand and preview in real time.',
|
||||
tabs: {
|
||||
experience: 'Experience',
|
||||
branding: 'Branding',
|
||||
methods: 'Sign in methods',
|
||||
others: 'Others',
|
||||
},
|
||||
|
@ -418,14 +418,17 @@ const translation = {
|
|||
'Please note that sign-in experience will apply to all applications under this account.',
|
||||
got_it: 'Got it',
|
||||
},
|
||||
branding: {
|
||||
title: 'BRANDING',
|
||||
color: {
|
||||
title: 'COLOR',
|
||||
primary_color: 'Brand color',
|
||||
dark_primary_color: 'Brand color (Dark)',
|
||||
dark_mode: 'Enable dark mode',
|
||||
dark_mode_description:
|
||||
'Your app will have an auto-generated dark mode theme based on your brand color and Logto algorithm. You are free to customize.',
|
||||
ui_style: 'Branding area',
|
||||
},
|
||||
branding: {
|
||||
title: 'BRANDING AREA',
|
||||
ui_style: 'Style',
|
||||
styles: {
|
||||
logo_slogan: 'App logo with slogan',
|
||||
logo: 'App logo',
|
||||
|
|
|
@ -396,7 +396,7 @@ const translation = {
|
|||
title: '登录体验',
|
||||
description: '自定义登录界面,并实时预览真实效果。',
|
||||
tabs: {
|
||||
experience: '体验',
|
||||
branding: '品牌',
|
||||
methods: '登录方式',
|
||||
others: '其它',
|
||||
},
|
||||
|
@ -406,14 +406,17 @@ const translation = {
|
|||
apply_remind: '请注意,登录体验将会应用到当前账户下的所有应用。',
|
||||
got_it: '知道了',
|
||||
},
|
||||
branding: {
|
||||
title: '品牌',
|
||||
color: {
|
||||
title: '颜色',
|
||||
primary_color: '品牌颜色',
|
||||
dark_primary_color: '品牌颜色 (暗黑)',
|
||||
dark_mode: '开启暗黑模式',
|
||||
dark_primary_color: '品牌颜色 (深色)',
|
||||
dark_mode: '开启深色模式',
|
||||
dark_mode_description:
|
||||
'基于你的品牌颜色和 Logto 的算法,你的应用将会有一个自动生成的暗黑模式。当然,你可以自定义和修改。',
|
||||
ui_style: '品牌定制区',
|
||||
'基于你的品牌颜色和 Logto 的算法,你的应用将会有一个自动生成的深色模式。当然,你可以自定义和修改。',
|
||||
},
|
||||
branding: {
|
||||
title: '品牌定制区',
|
||||
ui_style: '样式',
|
||||
styles: {
|
||||
logo_slogan: 'App logo 和标语',
|
||||
logo: '仅有Logo',
|
||||
|
|
|
@ -72,15 +72,20 @@ export type Identities = z.infer<typeof identitiesGuard>;
|
|||
* SignIn Experiences
|
||||
*/
|
||||
|
||||
export const colorGuard = z.object({
|
||||
primaryColor: z.string().regex(hexColorRegEx),
|
||||
isDarkModeEnabled: z.boolean(),
|
||||
darkPrimaryColor: z.string().regex(hexColorRegEx).optional(),
|
||||
});
|
||||
|
||||
export type Color = z.infer<typeof colorGuard>;
|
||||
|
||||
export enum BrandingStyle {
|
||||
Logo = 'Logo',
|
||||
Logo_Slogan = 'Logo_Slogan',
|
||||
}
|
||||
|
||||
export const brandingGuard = z.object({
|
||||
primaryColor: z.string().regex(hexColorRegEx),
|
||||
isDarkModeEnabled: z.boolean(),
|
||||
darkPrimaryColor: z.string().regex(hexColorRegEx).optional(),
|
||||
style: z.nativeEnum(BrandingStyle),
|
||||
logoUrl: z.string().url(),
|
||||
darkLogoUrl: z.string().url().optional(),
|
||||
|
|
|
@ -5,10 +5,12 @@ import { BrandingStyle, SignInMethodState } from '../foundations';
|
|||
|
||||
export const defaultSignInExperience: Readonly<CreateSignInExperience> = {
|
||||
id: 'default',
|
||||
branding: {
|
||||
color: {
|
||||
primaryColor: '#6139F6',
|
||||
isDarkModeEnabled: false,
|
||||
darkPrimaryColor: '#6139F6',
|
||||
},
|
||||
branding: {
|
||||
style: BrandingStyle.Logo,
|
||||
logoUrl: 'https://logto.io/logo.svg',
|
||||
darkLogoUrl: 'https://logto.io/logo.svg',
|
||||
|
|
|
@ -2,6 +2,7 @@ create type sign_in_mode as enum ('SignIn', 'Register', 'SignInAndRegister');
|
|||
|
||||
create table sign_in_experiences (
|
||||
id varchar(21) not null,
|
||||
color jsonb /* @use Color */ not null,
|
||||
branding jsonb /* @use Branding */ not null,
|
||||
language_info jsonb /* @use LanguageInfo */ not null,
|
||||
terms_of_use jsonb /* @use TermsOfUse */ not null,
|
||||
|
|
|
@ -123,10 +123,12 @@ export const mockSocialConnectorData = {
|
|||
|
||||
export const mockSignInExperience: SignInExperience = {
|
||||
id: 'foo',
|
||||
branding: {
|
||||
color: {
|
||||
primaryColor: '#000',
|
||||
isDarkModeEnabled: true,
|
||||
darkPrimaryColor: '#fff',
|
||||
},
|
||||
branding: {
|
||||
style: BrandingStyle.Logo_Slogan,
|
||||
logoUrl: 'http://logto.png',
|
||||
slogan: 'logto',
|
||||
|
@ -151,6 +153,7 @@ export const mockSignInExperience: SignInExperience = {
|
|||
};
|
||||
|
||||
export const mockSignInExperienceSettings: SignInExperienceSettings = {
|
||||
color: mockSignInExperience.color,
|
||||
branding: mockSignInExperience.branding,
|
||||
termsOfUse: mockSignInExperience.termsOfUse,
|
||||
languageInfo: mockSignInExperience.languageInfo,
|
||||
|
|
|
@ -22,10 +22,7 @@ const AppContent = ({ children }: Props) => {
|
|||
}, [setToast]);
|
||||
|
||||
// Set Primary Color
|
||||
useColorTheme(
|
||||
experienceSettings?.branding.primaryColor,
|
||||
experienceSettings?.branding.darkPrimaryColor
|
||||
);
|
||||
useColorTheme(experienceSettings?.color.primaryColor, experienceSettings?.color.darkPrimaryColor);
|
||||
|
||||
// Set Theme Mode
|
||||
useEffect(() => {
|
||||
|
|
|
@ -60,7 +60,7 @@ const usePreview = (context: Context): [boolean, PreviewConfig?] => {
|
|||
}
|
||||
|
||||
const {
|
||||
signInExperience: { signInMethods, socialConnectors, branding, ...rest },
|
||||
signInExperience: { signInMethods, socialConnectors, color, ...rest },
|
||||
language,
|
||||
mode,
|
||||
platform,
|
||||
|
@ -68,8 +68,8 @@ const usePreview = (context: Context): [boolean, PreviewConfig?] => {
|
|||
|
||||
const experienceSettings: SignInExperienceSettings = {
|
||||
...rest,
|
||||
branding: {
|
||||
...branding,
|
||||
color: {
|
||||
...color,
|
||||
isDarkModeEnabled: false, // Disable theme mode auto detection on preview
|
||||
},
|
||||
primarySignInMethod: getPrimarySignInMethod(signInMethods),
|
||||
|
|
|
@ -11,7 +11,7 @@ export default function useTheme(): Theme {
|
|||
const { experienceSettings, theme, setTheme } = useContext(PageContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (!experienceSettings?.branding.isDarkModeEnabled) {
|
||||
if (!experienceSettings?.color.isDarkModeEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
SignInExperience,
|
||||
ConnectorMetadata,
|
||||
SignInMode,
|
||||
Color,
|
||||
} from '@logto/schemas';
|
||||
|
||||
export type UserFlow = 'sign-in' | 'register';
|
||||
|
@ -30,6 +31,7 @@ export type SignInExperienceSettingsResponse = SignInExperience & {
|
|||
};
|
||||
|
||||
export type SignInExperienceSettings = {
|
||||
color: Color;
|
||||
branding: Branding;
|
||||
languageInfo: LanguageInfo;
|
||||
termsOfUse: TermsOfUse;
|
||||
|
|
Loading…
Add table
Reference in a new issue