mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
test
This commit is contained in:
parent
1fa9f85e14
commit
275312698f
11 changed files with 165 additions and 4 deletions
|
@ -4,6 +4,9 @@
|
|||
border: 1px dashed var(--color-border);
|
||||
border-radius: 8px;
|
||||
padding: _.unit(3.25);
|
||||
transition-property: outline, border;
|
||||
transition-timing-function: ease-in-out;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
> input {
|
||||
display: none;
|
||||
|
@ -18,6 +21,9 @@
|
|||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--color-text-secondary);
|
||||
transition-property: color;
|
||||
transition-timing-function: ease-in-out;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.uploadingIcon {
|
||||
|
@ -60,4 +66,3 @@
|
|||
border-color: var(--color-error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import { Theme } from '@logto/schemas';
|
||||
import { noop } from '@silverhand/essentials';
|
||||
import { Controller, type UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import FormCard from '@/components/FormCard';
|
||||
import ColorPicker from '@/ds-components/ColorPicker';
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import ImageUploader from '@/ds-components/Uploader/ImageUploader';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
import { type FormData } from './utils';
|
||||
|
||||
type Props = {
|
||||
readonly form: UseFormReturn<FormData>;
|
||||
};
|
||||
|
||||
function Branding({ form }: Props) {
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
setError,
|
||||
clearErrors,
|
||||
watch,
|
||||
} = form;
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
return (
|
||||
<FormCard
|
||||
title="organization_details.branding.title"
|
||||
description="organization_details.branding.description"
|
||||
>
|
||||
<div className={styles.branding}>
|
||||
{Object.values(Theme).map((theme) => (
|
||||
<section key={theme}>
|
||||
<FormField title={`organization_details.branding.${theme}.primary_color`}>
|
||||
<Controller
|
||||
name="branding.light.primaryColor"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ColorPicker value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title={`organization_details.branding.${theme}.logo`}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="branding.light.logoUrl"
|
||||
render={({ field: { onChange, value, name } }) => (
|
||||
<ImageUploader
|
||||
name={name}
|
||||
value={value ?? ''}
|
||||
actionDescription={t('organization_details.branding.logo_upload_description')}
|
||||
onCompleted={onChange}
|
||||
// OnUploadErrorChange={setUploadLogoError}
|
||||
onUploadErrorChange={noop}
|
||||
onDelete={() => {
|
||||
onChange('');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</FormCard>
|
||||
);
|
||||
}
|
||||
|
||||
export default Branding;
|
|
@ -22,6 +22,12 @@
|
|||
gap: _.unit(3);
|
||||
}
|
||||
|
||||
.branding {
|
||||
section + section {
|
||||
margin-top: _.unit(6);
|
||||
}
|
||||
}
|
||||
|
||||
.mfaWarning {
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import { trySubmitSafe } from '@/utils/form';
|
|||
|
||||
import { type OrganizationDetailsOutletContext } from '../types';
|
||||
|
||||
import Branding from './Branding';
|
||||
import JitSettings from './JitSettings';
|
||||
import * as styles from './index.module.scss';
|
||||
import { assembleData, isJsonObject, normalizeData, type FormData } from './utils';
|
||||
|
@ -136,6 +137,7 @@ function Settings() {
|
|||
)}
|
||||
</FormField>
|
||||
</FormCard>
|
||||
<Branding form={form} />
|
||||
<JitSettings form={form} />
|
||||
<UnsavedChangesAlertModal hasUnsavedChanges={!isDeleting && isDirty} />
|
||||
</DetailsForm>
|
||||
|
|
|
@ -38,6 +38,20 @@ const organization_details = {
|
|||
custom_data_tip:
|
||||
'Custom data is a JSON object that can be used to store additional data associated with the organization.',
|
||||
invalid_json_object: 'Invalid JSON object.',
|
||||
branding: {
|
||||
title: 'Branding',
|
||||
description:
|
||||
'Customize the branding of the organization. The branding can be used in the sign-in experience or for your own reference.',
|
||||
light: {
|
||||
primary_color: 'Primary color',
|
||||
logo: 'Organization logo',
|
||||
},
|
||||
dark: {
|
||||
primary_color: 'Primary color (dark)',
|
||||
logo: 'Organization logo (dark)',
|
||||
},
|
||||
logo_upload_description: 'Click or drop an image to upload',
|
||||
},
|
||||
jit: {
|
||||
title: 'Just-in-time provisioning',
|
||||
description:
|
||||
|
|
|
@ -23,7 +23,7 @@ const sign_in_exp = {
|
|||
color: {
|
||||
title: 'COLOR',
|
||||
primary_color: 'Brand color',
|
||||
dark_primary_color: 'Brand color (Dark)',
|
||||
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.',
|
||||
|
@ -36,7 +36,7 @@ const sign_in_exp = {
|
|||
favicon: 'Favicon',
|
||||
logo_image_url: 'App logo image URL',
|
||||
logo_image_url_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
dark_logo_image_url: 'App logo image URL (Dark)',
|
||||
dark_logo_image_url: 'App logo image URL (dark)',
|
||||
dark_logo_image_url_placeholder: 'https://your.cdn.domain/logo-dark.png',
|
||||
logo_image: 'App logo',
|
||||
dark_logo_image: 'App logo (Dark)',
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { sql } from '@silverhand/slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter table organizations add column branding jsonb not null default '{}'::jsonb;
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter table organizations drop column branding;
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -8,6 +8,7 @@ export * from './sentinel.js';
|
|||
export * from './users.js';
|
||||
export * from './sso-connector.js';
|
||||
export * from './applications.js';
|
||||
export * from './organization.js';
|
||||
|
||||
export {
|
||||
configurableConnectorMetadataGuard,
|
||||
|
|
43
packages/schemas/src/foundations/jsonb-types/organization.ts
Normal file
43
packages/schemas/src/foundations/jsonb-types/organization.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { hexColorRegEx } from '@logto/core-kit';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type Theme } from '../../types/theme.js';
|
||||
import { type ToZodObject } from '../../utils/zod.js';
|
||||
|
||||
/** Organization branding data for a specific theme. */
|
||||
export type OrganizationBrandingTheme = Partial<{
|
||||
logoUrl: string;
|
||||
/**
|
||||
* The primary color of the organization. Should be a hex color.
|
||||
*
|
||||
* @example
|
||||
* '#ff0000'
|
||||
* '#f00'
|
||||
*/
|
||||
primaryColor: string;
|
||||
}>;
|
||||
|
||||
/** Zod representation of {@link OrganizationBrandingTheme}. */
|
||||
export const organizationBrandingThemeGuard = z
|
||||
.object({
|
||||
logoUrl: z.string().url(),
|
||||
primaryColor: z.string().regex(hexColorRegEx),
|
||||
})
|
||||
.partial() satisfies ToZodObject<OrganizationBrandingTheme>;
|
||||
|
||||
/** Organization branding data for all themes. */
|
||||
export type OrganizationBranding = Partial<
|
||||
Record<Theme, OrganizationBrandingTheme> & {
|
||||
/** URL to the favicon of the organization. */
|
||||
faviconUrl: string;
|
||||
}
|
||||
>;
|
||||
|
||||
/** Zod representation of {@link OrganizationBranding}. */
|
||||
export const organizationBrandingGuard = z
|
||||
.object({
|
||||
light: organizationBrandingThemeGuard,
|
||||
dark: organizationBrandingThemeGuard,
|
||||
faviconUrl: z.string().url(),
|
||||
})
|
||||
.partial() satisfies ToZodObject<OrganizationBranding>;
|
|
@ -10,6 +10,5 @@ create table application_sign_in_experiences (
|
|||
terms_of_use_url varchar(2048),
|
||||
privacy_policy_url varchar(2048),
|
||||
display_name varchar(256),
|
||||
|
||||
primary key (tenant_id, application_id)
|
||||
);
|
||||
|
|
|
@ -14,6 +14,8 @@ create table organizations (
|
|||
custom_data jsonb /* @use JsonObject */ not null default '{}'::jsonb,
|
||||
/** Whether multi-factor authentication configuration is required for the members of the organization. */
|
||||
is_mfa_required boolean not null default false,
|
||||
/** The organization's branding configuration. */
|
||||
branding jsonb /* @use OrganizationBranding */ not null default '{}'::jsonb,
|
||||
/** When the organization was created. */
|
||||
created_at timestamptz not null default(now()),
|
||||
primary key (id)
|
||||
|
|
Loading…
Reference in a new issue