mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
feat: custom css (#3155)
This commit is contained in:
parent
c93d744041
commit
1ef5519e75
7 changed files with 74 additions and 43 deletions
|
@ -91,4 +91,5 @@ export const mockSignInExperience: SignInExperience = {
|
||||||
},
|
},
|
||||||
socialSignInConnectorTargets: ['github', 'facebook', 'wechat'],
|
socialSignInConnectorTargets: ['github', 'facebook', 'wechat'],
|
||||||
signInMode: SignInMode.SignInAndRegister,
|
signInMode: SignInMode.SignInAndRegister,
|
||||||
|
customCss: null,
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe('sign-in-experience query', () => {
|
||||||
it('findDefaultSignInExperience', async () => {
|
it('findDefaultSignInExperience', async () => {
|
||||||
/* eslint-disable sql/no-unsafe-query */
|
/* eslint-disable sql/no-unsafe-query */
|
||||||
const expectSql = `
|
const expectSql = `
|
||||||
select "tenant_id", "id", "color", "branding", "language_info", "terms_of_use_url", "sign_in", "sign_up", "social_sign_in_connector_targets", "sign_in_mode"
|
select "tenant_id", "id", "color", "branding", "language_info", "terms_of_use_url", "sign_in", "sign_up", "social_sign_in_connector_targets", "sign_in_mode", "custom_css"
|
||||||
from "sign_in_experiences"
|
from "sign_in_experiences"
|
||||||
where "id"=$1
|
where "id"=$1
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { sql } from 'slonik';
|
||||||
|
|
||||||
|
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||||
|
|
||||||
|
const alteration: AlterationScript = {
|
||||||
|
up: async (pool) => {
|
||||||
|
await pool.query(sql`
|
||||||
|
alter table sign_in_experiences
|
||||||
|
add column if not exists custom_css text;
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
down: async (pool) => {
|
||||||
|
await pool.query(sql`
|
||||||
|
alter table sign_in_experiences
|
||||||
|
drop column custom_css;
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default alteration;
|
|
@ -1,55 +1,55 @@
|
||||||
import { generateDarkColor } from '@logto/core-kit';
|
import { generateDarkColor } from '@logto/core-kit';
|
||||||
|
|
||||||
import type { CreateSignInExperience } from '../db-entries/index.js';
|
import type { SignInExperience } from '../db-entries/index.js';
|
||||||
import { SignInMode } from '../db-entries/index.js';
|
import { SignInMode } from '../db-entries/index.js';
|
||||||
import { BrandingStyle, SignInIdentifier } from '../foundations/index.js';
|
import { BrandingStyle, SignInIdentifier } from '../foundations/index.js';
|
||||||
import { defaultTenantId } from './tenant.js';
|
import { defaultTenantId } from './tenant.js';
|
||||||
|
|
||||||
const defaultPrimaryColor = '#6139F6';
|
const defaultPrimaryColor = '#6139F6';
|
||||||
|
|
||||||
export const createDefaultSignInExperience = (
|
export const createDefaultSignInExperience = (forTenantId: string): Readonly<SignInExperience> =>
|
||||||
forTenantId: string
|
Object.freeze({
|
||||||
): Readonly<CreateSignInExperience> => ({
|
tenantId: forTenantId,
|
||||||
tenantId: forTenantId,
|
id: 'default',
|
||||||
id: 'default',
|
color: {
|
||||||
color: {
|
primaryColor: defaultPrimaryColor,
|
||||||
primaryColor: defaultPrimaryColor,
|
isDarkModeEnabled: false,
|
||||||
isDarkModeEnabled: false,
|
darkPrimaryColor: generateDarkColor(defaultPrimaryColor),
|
||||||
darkPrimaryColor: generateDarkColor(defaultPrimaryColor),
|
},
|
||||||
},
|
branding: {
|
||||||
branding: {
|
style: BrandingStyle.Logo,
|
||||||
style: BrandingStyle.Logo,
|
logoUrl: 'https://logto.io/logo.svg',
|
||||||
logoUrl: 'https://logto.io/logo.svg',
|
darkLogoUrl: 'https://logto.io/logo-dark.svg',
|
||||||
darkLogoUrl: 'https://logto.io/logo-dark.svg',
|
},
|
||||||
},
|
languageInfo: {
|
||||||
languageInfo: {
|
autoDetect: true,
|
||||||
autoDetect: true,
|
fallbackLanguage: 'en' as const,
|
||||||
fallbackLanguage: 'en',
|
},
|
||||||
},
|
termsOfUseUrl: null,
|
||||||
termsOfUseUrl: null,
|
signUp: {
|
||||||
signUp: {
|
identifiers: [SignInIdentifier.Username],
|
||||||
identifiers: [SignInIdentifier.Username],
|
password: true,
|
||||||
password: true,
|
verify: false,
|
||||||
verify: false,
|
},
|
||||||
},
|
signIn: {
|
||||||
signIn: {
|
methods: [
|
||||||
methods: [
|
{
|
||||||
{
|
identifier: SignInIdentifier.Username,
|
||||||
identifier: SignInIdentifier.Username,
|
password: true,
|
||||||
password: true,
|
verificationCode: false,
|
||||||
verificationCode: false,
|
isPasswordPrimary: true,
|
||||||
isPasswordPrimary: true,
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
socialSignInConnectorTargets: [],
|
||||||
socialSignInConnectorTargets: [],
|
signInMode: SignInMode.SignInAndRegister,
|
||||||
signInMode: SignInMode.SignInAndRegister,
|
customCss: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @deprecated Use `createDefaultSignInExperience()` instead. */
|
/** @deprecated Use `createDefaultSignInExperience()` instead. */
|
||||||
export const defaultSignInExperience = createDefaultSignInExperience(defaultTenantId);
|
export const defaultSignInExperience = createDefaultSignInExperience(defaultTenantId);
|
||||||
|
|
||||||
export const adminConsoleSignInExperience: CreateSignInExperience = {
|
export const adminConsoleSignInExperience: Readonly<SignInExperience> = Object.freeze({
|
||||||
...defaultSignInExperience,
|
...defaultSignInExperience,
|
||||||
color: {
|
color: {
|
||||||
...defaultSignInExperience.color,
|
...defaultSignInExperience.color,
|
||||||
|
@ -61,4 +61,4 @@ export const adminConsoleSignInExperience: CreateSignInExperience = {
|
||||||
darkLogoUrl: 'https://logto.io/logo-dark.svg',
|
darkLogoUrl: 'https://logto.io/logo-dark.svg',
|
||||||
slogan: 'admin_console.welcome.title', // TODO: @simeng should we programmatically support an i18n key for slogan?
|
slogan: 'admin_console.welcome.title', // TODO: @simeng should we programmatically support an i18n key for slogan?
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
|
@ -12,5 +12,6 @@ create table sign_in_experiences (
|
||||||
sign_up jsonb /* @use SignUp */ not null,
|
sign_up jsonb /* @use SignUp */ not null,
|
||||||
social_sign_in_connector_targets jsonb /* @use ConnectorTargets */ not null default '[]'::jsonb,
|
social_sign_in_connector_targets jsonb /* @use ConnectorTargets */ not null default '[]'::jsonb,
|
||||||
sign_in_mode sign_in_mode not null default 'SignInAndRegister',
|
sign_in_mode sign_in_mode not null default 'SignInAndRegister',
|
||||||
|
custom_css text,
|
||||||
primary key (tenant_id, id)
|
primary key (tenant_id, id)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { SignInMode } from '@logto/schemas';
|
import { SignInMode } from '@logto/schemas';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { Route, Routes, BrowserRouter, Navigate } from 'react-router-dom';
|
import { Route, Routes, BrowserRouter, Navigate } from 'react-router-dom';
|
||||||
|
|
||||||
import AppBoundary from './containers/AppBoundary';
|
import AppBoundary from './containers/AppBoundary';
|
||||||
|
@ -29,8 +29,13 @@ import './scss/normalized.scss';
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { context, Provider } = usePageContext();
|
const { context, Provider } = usePageContext();
|
||||||
const { experienceSettings, setLoading, setExperienceSettings } = context;
|
const { experienceSettings, setLoading, setExperienceSettings } = context;
|
||||||
|
const customCssRef = useRef(document.createElement('style'));
|
||||||
const [isPreview] = usePreview(context);
|
const [isPreview] = usePreview(context);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.head.append(customCssRef.current);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isPreview) {
|
if (isPreview) {
|
||||||
return;
|
return;
|
||||||
|
@ -38,6 +43,8 @@ const App = () => {
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const settings = await getSignInExperienceSettings();
|
const settings = await getSignInExperienceSettings();
|
||||||
|
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||||
|
customCssRef.current.textContent = settings.customCss;
|
||||||
|
|
||||||
// Note: i18n must be initialized ahead of page render
|
// Note: i18n must be initialized ahead of page render
|
||||||
await initI18n(settings.languageInfo);
|
await initI18n(settings.languageInfo);
|
||||||
|
|
|
@ -209,6 +209,7 @@ export const mockSignInExperience: SignInExperience = {
|
||||||
},
|
},
|
||||||
socialSignInConnectorTargets: ['BE8QXN0VsrOH7xdWFDJZ9', 'lcXT4o2GSjbV9kg2shZC7'],
|
socialSignInConnectorTargets: ['BE8QXN0VsrOH7xdWFDJZ9', 'lcXT4o2GSjbV9kg2shZC7'],
|
||||||
signInMode: SignInMode.SignInAndRegister,
|
signInMode: SignInMode.SignInAndRegister,
|
||||||
|
customCss: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockSignInExperienceSettings: SignInExperienceResponse = {
|
export const mockSignInExperienceSettings: SignInExperienceResponse = {
|
||||||
|
@ -230,6 +231,7 @@ export const mockSignInExperienceSettings: SignInExperienceResponse = {
|
||||||
email: true,
|
email: true,
|
||||||
phone: true,
|
phone: true,
|
||||||
},
|
},
|
||||||
|
customCss: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const usernameSettings = {
|
const usernameSettings = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue