0
Fork 0
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:
Gao Sun 2023-02-21 10:55:44 +08:00 committed by GitHub
parent c93d744041
commit 1ef5519e75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 74 additions and 43 deletions

View file

@ -91,4 +91,5 @@ export const mockSignInExperience: SignInExperience = {
}, },
socialSignInConnectorTargets: ['github', 'facebook', 'wechat'], socialSignInConnectorTargets: ['github', 'facebook', 'wechat'],
signInMode: SignInMode.SignInAndRegister, signInMode: SignInMode.SignInAndRegister,
customCss: null,
}; };

View file

@ -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
`; `;

View file

@ -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;

View file

@ -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?
}, },
}; });

View file

@ -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)
); );

View file

@ -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);

View file

@ -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 = {