mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
refactor(console): update all logo uploaders (#6209)
This commit is contained in:
parent
6060919a21
commit
94c4cc0e56
47 changed files with 212 additions and 327 deletions
|
@ -89,7 +89,7 @@ export const defaultMetadata: ConnectorMetadata = {
|
|||
},
|
||||
{
|
||||
key: 'appLogo',
|
||||
label: 'App Logo',
|
||||
label: 'Email logo',
|
||||
type: ConnectorConfigFormItemType.Text,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { useState } from 'react';
|
||||
import { Theme } from '@logto/schemas';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import CaretDown from '@/assets/icons/caret-down.svg';
|
||||
import CaretUp from '@/assets/icons/caret-up.svg';
|
||||
import Error from '@/assets/icons/toast-error.svg';
|
||||
import LogoInputs from '@/components/ImageInputs';
|
||||
import UnnamedTrans from '@/components/UnnamedTrans';
|
||||
import Button from '@/ds-components/Button';
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import Select from '@/ds-components/Select';
|
||||
import TextInput from '@/ds-components/TextInput';
|
||||
|
@ -14,23 +12,21 @@ import TextLink from '@/ds-components/TextLink';
|
|||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||
import type { ConnectorFormType } from '@/types/connector';
|
||||
import { SyncProfileMode } from '@/types/connector';
|
||||
import { uriValidator } from '@/utils/validator';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const themeToField = Object.freeze({
|
||||
[Theme.Light]: 'logo',
|
||||
[Theme.Dark]: 'logoDark',
|
||||
} as const satisfies Record<Theme, string>);
|
||||
|
||||
type Props = {
|
||||
readonly isAllowEditTarget?: boolean;
|
||||
readonly isDarkDefaultVisible?: boolean;
|
||||
readonly isStandard?: boolean;
|
||||
readonly conflictConnectorName?: Record<string, string>;
|
||||
};
|
||||
|
||||
function BasicForm({
|
||||
isAllowEditTarget,
|
||||
isDarkDefaultVisible,
|
||||
isStandard,
|
||||
conflictConnectorName,
|
||||
}: Props) {
|
||||
function BasicForm({ isAllowEditTarget, isStandard, conflictConnectorName }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { getDocumentationUrl } = useDocumentationUrl();
|
||||
const {
|
||||
|
@ -38,17 +34,6 @@ function BasicForm({
|
|||
register,
|
||||
formState: { errors },
|
||||
} = useFormContext<ConnectorFormType>();
|
||||
const [darkVisible, setDarkVisible] = useState(Boolean(isDarkDefaultVisible));
|
||||
|
||||
const toggleDarkVisible = () => {
|
||||
setDarkVisible((previous) => !previous);
|
||||
};
|
||||
|
||||
const toggleVisibleButtonTitle = darkVisible
|
||||
? 'connectors.guide.logo_dark_collapse'
|
||||
: 'connectors.guide.logo_dark_show';
|
||||
|
||||
const ToggleVisibleCaretIcon = darkVisible ? CaretUp : CaretDown;
|
||||
|
||||
const syncProfileOptions = [
|
||||
{
|
||||
|
@ -72,37 +57,18 @@ function BasicForm({
|
|||
{...register('name', { required: true })}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField title="connectors.guide.logo" tip={t('connectors.guide.logo_tip')}>
|
||||
<TextInput
|
||||
placeholder={t('connectors.guide.logo_placeholder')}
|
||||
error={errors.logo?.message}
|
||||
{...register('logo', {
|
||||
validate: (value) =>
|
||||
!value || uriValidator(value) || t('errors.invalid_uri_format'),
|
||||
})}
|
||||
/>
|
||||
</FormField>
|
||||
{darkVisible && (
|
||||
<FormField title="connectors.guide.logo_dark" tip={t('connectors.guide.logo_dark_tip')}>
|
||||
<TextInput
|
||||
placeholder={t('connectors.guide.logo_dark_placeholder')}
|
||||
error={errors.logoDark?.message}
|
||||
{...register('logoDark', {
|
||||
validate: (value) =>
|
||||
!value || uriValidator(value) || t('errors.invalid_uri_format'),
|
||||
})}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
<div className={styles.fieldButton}>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
title={toggleVisibleButtonTitle}
|
||||
trailingIcon={<ToggleVisibleCaretIcon className={styles.trailingIcon} />}
|
||||
onClick={toggleDarkVisible}
|
||||
/>
|
||||
</div>
|
||||
<LogoInputs
|
||||
uploadTitle="connectors.guide.connector_logo"
|
||||
tip={t('connectors.guide.connector_logo_tip')}
|
||||
control={control}
|
||||
register={register}
|
||||
fields={Object.values(Theme).map((theme) => ({
|
||||
name: themeToField[theme],
|
||||
error: errors[themeToField[theme]],
|
||||
type: 'app_logo',
|
||||
theme,
|
||||
}))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<FormField
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { type LocalePhrase } from '@logto/phrases';
|
||||
import { type Theme } from '@logto/schemas';
|
||||
import { Theme } from '@logto/schemas';
|
||||
import { cond, noop } from '@silverhand/essentials';
|
||||
import classNames from 'classnames';
|
||||
import type React from 'react';
|
||||
|
@ -24,6 +24,11 @@ import { uriValidator } from '@/utils/validator';
|
|||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export const themeToLogoName = Object.freeze({
|
||||
[Theme.Light]: 'logoUrl',
|
||||
[Theme.Dark]: 'darkLogoUrl',
|
||||
} as const satisfies Record<Theme, string>);
|
||||
|
||||
export type ImageField<FormContext extends FieldValues> = {
|
||||
/** The name (field path) of the field in the form. */
|
||||
name: FieldPath<FormContext>;
|
||||
|
@ -40,6 +45,8 @@ export type ImageField<FormContext extends FieldValues> = {
|
|||
type Props<FormContext extends FieldValues> = {
|
||||
/** The condensed title when user assets service is available. */
|
||||
readonly uploadTitle: React.ComponentProps<typeof FormField>['title'];
|
||||
/** The tooltip to show for all the fields. */
|
||||
readonly tip?: React.ComponentProps<typeof FormField>['tip'];
|
||||
readonly control: Control<FormContext>;
|
||||
readonly register: UseFormRegister<FormContext>;
|
||||
readonly fields: Array<ImageField<FormContext>>;
|
||||
|
@ -53,6 +60,7 @@ type Props<FormContext extends FieldValues> = {
|
|||
*/
|
||||
function ImageInputs<FormContext extends FieldValues>({
|
||||
uploadTitle,
|
||||
tip,
|
||||
control,
|
||||
register,
|
||||
fields,
|
||||
|
@ -84,6 +92,7 @@ function ImageInputs<FormContext extends FieldValues>({
|
|||
{fields.map((field) => (
|
||||
<FormField
|
||||
key={field.name}
|
||||
tip={tip}
|
||||
title={
|
||||
<>
|
||||
{t(`sign_in_exp.branding.with_${field.theme}`, {
|
||||
|
@ -109,7 +118,7 @@ function ImageInputs<FormContext extends FieldValues>({
|
|||
}
|
||||
|
||||
return (
|
||||
<FormField title={uploadTitle}>
|
||||
<FormField title={uploadTitle} tip={tip}>
|
||||
<div className={styles.container}>
|
||||
{fields.map((field) => (
|
||||
<Controller
|
||||
|
|
|
@ -19,6 +19,10 @@ export const logtoThirdPartyGuideLink = '/docs/recipes/logto-as-idp/';
|
|||
export const logtoThirdPartyAppPermissionsLink =
|
||||
'/docs/recipes/logto-as-idp/permissions-management/';
|
||||
export const logtoThirdPartyAppBrandingLink = '/docs/recipes/logto-as-idp/branding-customization/';
|
||||
export const appSpecificBrandingLink =
|
||||
'/docs/recipes/customize-sie/match-your-brand/#app-specific-branding';
|
||||
export const organizationLogosForExperienceLink =
|
||||
'/docs/recipes/customize-sie/match-your-brand/#organization-specific-logos';
|
||||
export const signingKeysLink = '/docs/references/openid-connect/signing-keys-rotation/';
|
||||
export const organizationTemplateLink =
|
||||
'/docs/recipes/organizations/understand-how-it-works/#organization-template';
|
||||
|
|
|
@ -6,22 +6,25 @@ import { useTranslation } from 'react-i18next';
|
|||
|
||||
import DetailsForm from '@/components/DetailsForm';
|
||||
import FormCard, { FormCardSkeleton } from '@/components/FormCard';
|
||||
import LogoInputs, { themeToLogoName } from '@/components/ImageInputs';
|
||||
import LogoAndFavicon from '@/components/ImageInputs/LogoAndFavicon';
|
||||
import RequestDataError from '@/components/RequestDataError';
|
||||
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
||||
import { logtoThirdPartyAppBrandingLink } from '@/consts';
|
||||
import { appSpecificBrandingLink, logtoThirdPartyAppBrandingLink } from '@/consts';
|
||||
import ColorPicker from '@/ds-components/ColorPicker';
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import Switch from '@/ds-components/Switch';
|
||||
import TextInput from '@/ds-components/TextInput';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||
import { emptyBranding } from '@/types/sign-in-experience';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
import { uriValidator } from '@/utils/validator';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
import useApplicationSignInExperienceSWR from './use-application-sign-in-experience-swr';
|
||||
import useSignInExperienceSWR from './use-sign-in-experience-swr';
|
||||
import { formatFormToSubmitData } from './utils';
|
||||
import { type ApplicationSignInExperienceForm, formatFormToSubmitData } from './utils';
|
||||
|
||||
type Props = {
|
||||
readonly application: Application;
|
||||
|
@ -32,11 +35,12 @@ function Branding({ application, isActive }: Props) {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { getDocumentationUrl } = useDocumentationUrl();
|
||||
|
||||
const formMethods = useForm<ApplicationSignInExperience>({
|
||||
const formMethods = useForm<ApplicationSignInExperienceForm>({
|
||||
defaultValues: {
|
||||
tenantId: application.tenantId,
|
||||
applicationId: application.id,
|
||||
branding: {},
|
||||
isBrandingEnabled: application.isThirdParty,
|
||||
branding: emptyBranding,
|
||||
color: {},
|
||||
},
|
||||
});
|
||||
|
@ -46,6 +50,8 @@ function Branding({ application, isActive }: Props) {
|
|||
register,
|
||||
reset,
|
||||
control,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { isDirty, isSubmitting, errors },
|
||||
} = formMethods;
|
||||
|
||||
|
@ -57,6 +63,7 @@ function Branding({ application, isActive }: Props) {
|
|||
const isApplicationSieLoading = !data && !error;
|
||||
const isSieLoading = !sieData && !sieError;
|
||||
const isLoading = isApplicationSieLoading || isSieLoading;
|
||||
const [isBrandingEnabled, color] = watch(['isBrandingEnabled', 'color']);
|
||||
|
||||
const onSubmit = handleSubmit(
|
||||
trySubmitSafe(async (data) => {
|
||||
|
@ -85,8 +92,72 @@ function Branding({ application, isActive }: Props) {
|
|||
return;
|
||||
}
|
||||
|
||||
reset(data);
|
||||
}, [data, reset]);
|
||||
reset({
|
||||
...data,
|
||||
branding: { ...emptyBranding, ...data.branding },
|
||||
isBrandingEnabled: application.isThirdParty
|
||||
? true
|
||||
: Object.keys(data.branding).length > 0 || Object.keys(data.color).length > 0,
|
||||
});
|
||||
}, [application.isThirdParty, data, reset]);
|
||||
|
||||
// When enabling branding for the first time, fill the default color values to ensure the form
|
||||
// is valid; otherwise, directly save the form will be a no-op.
|
||||
useEffect(() => {
|
||||
if (isBrandingEnabled && Object.values(color).filter(Boolean).length === 0) {
|
||||
setValue('color', { primaryColor: '#000000', darkPrimaryColor: '#000000' });
|
||||
}
|
||||
}, [color, isBrandingEnabled, setValue]);
|
||||
|
||||
const NonThirdPartyBrandingForm = useCallback(
|
||||
() => (
|
||||
<>
|
||||
<LogoAndFavicon
|
||||
control={control}
|
||||
register={register}
|
||||
theme={Theme.Light}
|
||||
type="app_logo"
|
||||
logo={{ name: 'branding.logoUrl', error: errors.branding?.logoUrl }}
|
||||
favicon={{
|
||||
name: 'branding.favicon',
|
||||
error: errors.branding?.favicon,
|
||||
}}
|
||||
/>
|
||||
<LogoAndFavicon
|
||||
control={control}
|
||||
register={register}
|
||||
theme={Theme.Dark}
|
||||
type="app_logo"
|
||||
logo={{ name: 'branding.darkLogoUrl', error: errors.branding?.darkLogoUrl }}
|
||||
favicon={{
|
||||
name: 'branding.darkFavicon',
|
||||
error: errors.branding?.darkFavicon,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.colors}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="color.primaryColor"
|
||||
render={({ field: { name, value, onChange } }) => (
|
||||
<FormField title="application_details.branding.brand_color">
|
||||
<ColorPicker name={name} value={value} onChange={onChange} />
|
||||
</FormField>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="color.darkPrimaryColor"
|
||||
render={({ field: { name, value, onChange } }) => (
|
||||
<FormField title="application_details.branding.brand_color_dark">
|
||||
<ColorPicker name={name} value={value} onChange={onChange} />
|
||||
</FormField>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
[control, errors.branding, register]
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <FormCardSkeleton />;
|
||||
|
@ -110,63 +181,41 @@ function Branding({ application, isActive }: Props) {
|
|||
description={`application_details.branding.${
|
||||
application.isThirdParty ? 'description_third_party' : 'description'
|
||||
}`}
|
||||
learnMoreLink={
|
||||
application.isThirdParty
|
||||
? {
|
||||
href: getDocumentationUrl(logtoThirdPartyAppBrandingLink),
|
||||
targetBlank: 'noopener',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
learnMoreLink={{
|
||||
href: getDocumentationUrl(
|
||||
application.isThirdParty ? logtoThirdPartyAppBrandingLink : appSpecificBrandingLink
|
||||
),
|
||||
targetBlank: 'noopener',
|
||||
}}
|
||||
>
|
||||
{application.isThirdParty && (
|
||||
<FormField title="application_details.branding.display_name">
|
||||
<TextInput {...register('displayName')} placeholder={application.name} />
|
||||
</FormField>
|
||||
<>
|
||||
<FormField title="application_details.branding.display_name">
|
||||
<TextInput {...register('displayName')} placeholder={application.name} />
|
||||
</FormField>
|
||||
<LogoInputs
|
||||
uploadTitle="application_details.branding.app_logo"
|
||||
control={control}
|
||||
register={register}
|
||||
fields={Object.values(Theme).map((theme) => ({
|
||||
name: `branding.${themeToLogoName[theme]}`,
|
||||
error: errors.branding?.[themeToLogoName[theme]],
|
||||
type: 'app_logo',
|
||||
theme,
|
||||
}))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<LogoAndFavicon
|
||||
control={control}
|
||||
register={register}
|
||||
theme={Theme.Light}
|
||||
type="app_logo"
|
||||
logo={{ name: 'branding.logoUrl', error: errors.branding?.logoUrl }}
|
||||
favicon={{
|
||||
name: 'branding.favicon',
|
||||
error: errors.branding?.favicon,
|
||||
}}
|
||||
/>
|
||||
<LogoAndFavicon
|
||||
control={control}
|
||||
register={register}
|
||||
theme={Theme.Dark}
|
||||
type="app_logo"
|
||||
logo={{ name: 'branding.darkLogoUrl', error: errors.branding?.darkLogoUrl }}
|
||||
favicon={{
|
||||
name: 'branding.darkFavicon',
|
||||
error: errors.branding?.darkFavicon,
|
||||
}}
|
||||
/>
|
||||
{!application.isThirdParty && (
|
||||
<div className={styles.colors}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="color.primaryColor"
|
||||
render={({ field: { name, value, onChange } }) => (
|
||||
<FormField title="application_details.branding.brand_color">
|
||||
<ColorPicker name={name} value={value} onChange={onChange} />
|
||||
</FormField>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="color.darkPrimaryColor"
|
||||
render={({ field: { name, value, onChange } }) => (
|
||||
<FormField title="application_details.branding.brand_color_dark">
|
||||
<ColorPicker name={name} value={value} onChange={onChange} />
|
||||
</FormField>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<FormField title="application_details.branding.app_level_sie">
|
||||
<Switch
|
||||
description="application_details.branding.app_level_sie_switch"
|
||||
{...register('isBrandingEnabled')}
|
||||
/>
|
||||
</FormField>
|
||||
{isBrandingEnabled && <NonThirdPartyBrandingForm />}
|
||||
</>
|
||||
)}
|
||||
</FormCard>
|
||||
{application.isThirdParty && (
|
||||
|
|
|
@ -2,18 +2,28 @@ import { type ApplicationSignInExperience } from '@logto/schemas';
|
|||
|
||||
import { removeFalsyValues } from '@/utils/object';
|
||||
|
||||
export type ApplicationSignInExperienceForm = ApplicationSignInExperience & {
|
||||
/**
|
||||
* Used to determine if the application enables branding for the app-level sign-in experience.
|
||||
* Only effective for non-third-party applications.
|
||||
*/
|
||||
isBrandingEnabled: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format the form data to match the API request body
|
||||
* - Omit `applicationId` and `tenantId` from the request body
|
||||
* - Remove the empty `logoUrl` and `darkLogoUrl` fields in the `branding` object
|
||||
**/
|
||||
export const formatFormToSubmitData = (
|
||||
data: ApplicationSignInExperience
|
||||
data: ApplicationSignInExperienceForm
|
||||
): Omit<ApplicationSignInExperience, 'applicationId' | 'tenantId'> => {
|
||||
const { branding, applicationId, tenantId, ...rest } = data;
|
||||
const { branding, color, applicationId, tenantId, isBrandingEnabled, ...rest } = data;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
branding: removeFalsyValues(branding),
|
||||
...(isBrandingEnabled
|
||||
? { color, branding: removeFalsyValues(branding) }
|
||||
: { color: {}, branding: {} }),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -92,8 +92,8 @@ function EmailServiceConnectorForm({ extraInfo }: Props) {
|
|||
</div>
|
||||
</FormField>
|
||||
<FormField
|
||||
title="connector_details.logto_email.app_logo_field"
|
||||
tip={<DynamicT forKey="connector_details.logto_email.app_logo_tip" />}
|
||||
title="connector_details.logto_email.email_logo_field"
|
||||
tip={<DynamicT forKey="connector_details.logto_email.email_logo_tip" />}
|
||||
headlineSpacing={isUserAssetsServiceReady ? 'large' : 'default'}
|
||||
>
|
||||
{isUserAssetsServiceReady ? (
|
||||
|
|
|
@ -21,6 +21,7 @@ import { SyncProfileMode } from '@/types/connector';
|
|||
import type { ConnectorFormType } from '@/types/connector';
|
||||
import { convertResponseToForm } from '@/utils/connector-form';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
import { removeFalsyValues } from '@/utils/object';
|
||||
|
||||
import EmailServiceConnectorForm from './EmailServiceConnectorForm';
|
||||
|
||||
|
@ -54,7 +55,6 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop
|
|||
type: connectorType,
|
||||
formItems,
|
||||
isStandard: isStandardConnector,
|
||||
metadata: { logoDark },
|
||||
} = connectorData;
|
||||
|
||||
const isSocialConnector = connectorType === ConnectorType.Social;
|
||||
|
@ -78,7 +78,7 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop
|
|||
: { config };
|
||||
const standardConnectorPayload = {
|
||||
...payload,
|
||||
metadata: { name: { en: name }, logo, logoDark, target },
|
||||
metadata: { name: { en: name }, target, ...removeFalsyValues({ logo, logoDark }) },
|
||||
};
|
||||
// Should not update `target` for neither passwordless connectors nor non-standard social connectors.
|
||||
const body = isStandard ? standardConnectorPayload : { ...payload, target: undefined };
|
||||
|
@ -124,7 +124,7 @@ function ConnectorContent({ isDeleted, connectorData, onConnectorUpdated }: Prop
|
|||
targetBlank: 'noopener',
|
||||
}}
|
||||
>
|
||||
<BasicForm isStandard={isStandardConnector} isDarkDefaultVisible={Boolean(logoDark)} />
|
||||
<BasicForm isStandard={isStandardConnector} />
|
||||
</FormCard>
|
||||
)}
|
||||
{isEmailServiceConnector ? (
|
||||
|
|
|
@ -7,8 +7,9 @@ import useSWR from 'swr';
|
|||
|
||||
import DetailsForm from '@/components/DetailsForm';
|
||||
import FormCard from '@/components/FormCard';
|
||||
import LogoInputs from '@/components/ImageInputs';
|
||||
import LogoInputs, { themeToLogoName } from '@/components/ImageInputs';
|
||||
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
||||
import { organizationLogosForExperienceLink } from '@/consts';
|
||||
import CodeEditor from '@/ds-components/CodeEditor';
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
|
@ -17,6 +18,7 @@ import TextInput from '@/ds-components/TextInput';
|
|||
import TextLink from '@/ds-components/TextLink';
|
||||
import useApi, { type RequestError } from '@/hooks/use-api';
|
||||
import { mfa } from '@/hooks/use-console-routes/routes/mfa';
|
||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
|
||||
import { type OrganizationDetailsOutletContext } from '../types';
|
||||
|
@ -25,11 +27,6 @@ import JitSettings from './JitSettings';
|
|||
import * as styles from './index.module.scss';
|
||||
import { assembleData, isJsonObject, normalizeData, type FormData } from './utils';
|
||||
|
||||
const themeToLogoName = Object.freeze({
|
||||
[Theme.Light]: 'logoUrl',
|
||||
[Theme.Dark]: 'darkLogoUrl',
|
||||
} as const satisfies Record<Theme, string>);
|
||||
|
||||
function Settings() {
|
||||
const { isDeleting, data, jit, onUpdated } = useOutletContext<OrganizationDetailsOutletContext>();
|
||||
const { data: signInExperience } = useSWR<SignInExperience, RequestError>('api/sign-in-exp');
|
||||
|
@ -51,6 +48,7 @@ function Settings() {
|
|||
} = form;
|
||||
const [isMfaRequired] = watch(['isMfaRequired']);
|
||||
const api = useApi();
|
||||
const { getDocumentationUrl } = useDocumentationUrl();
|
||||
|
||||
const onSubmit = handleSubmit(
|
||||
trySubmitSafe(async (data) => {
|
||||
|
@ -111,6 +109,19 @@ function Settings() {
|
|||
</FormField>
|
||||
<LogoInputs
|
||||
uploadTitle="organization_details.branding.logo"
|
||||
tip={
|
||||
<Trans
|
||||
i18nKey="admin_console.organization_details.branding.logo_tooltip"
|
||||
components={{
|
||||
a: (
|
||||
<TextLink
|
||||
targetBlank="noopener"
|
||||
href={getDocumentationUrl(organizationLogosForExperienceLink)}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
control={control}
|
||||
register={register}
|
||||
fields={Object.values(Theme).map((theme) => ({
|
||||
|
|
|
@ -2,6 +2,7 @@ import { type Organization } from '@logto/schemas';
|
|||
import { trySafe } from '@silverhand/essentials';
|
||||
|
||||
import { type Option } from '@/ds-components/Select/MultiSelect';
|
||||
import { emptyBranding } from '@/types/sign-in-experience';
|
||||
import { removeFalsyValues } from '@/utils/object';
|
||||
|
||||
export type FormData = Partial<Omit<Organization, 'customData'> & { customData: string }> & {
|
||||
|
@ -20,6 +21,10 @@ export const normalizeData = (
|
|||
jit: { emailDomains: string[]; roles: Array<Option<string>>; ssoConnectorIds: string[] }
|
||||
): FormData => ({
|
||||
...data,
|
||||
branding: {
|
||||
...emptyBranding,
|
||||
...data.branding,
|
||||
},
|
||||
jitEmailDomains: jit.emailDomains,
|
||||
jitRoles: jit.roles,
|
||||
jitSsoConnectorIds: jit.ssoConnectorIds,
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from '@logto/schemas';
|
||||
import { isSameArray } from '@silverhand/essentials';
|
||||
|
||||
import { emptyBranding } from '@/types/sign-in-experience';
|
||||
import { removeFalsyValues } from '@/utils/object';
|
||||
|
||||
import {
|
||||
|
@ -56,10 +57,8 @@ export const sieFormDataParser = {
|
|||
createAccountEnabled: signInMode !== SignInMode.SignIn,
|
||||
customCss: customCss ?? undefined,
|
||||
branding: {
|
||||
...emptyBranding,
|
||||
...branding,
|
||||
logoUrl: branding.logoUrl ?? '',
|
||||
darkLogoUrl: branding.darkLogoUrl ?? '',
|
||||
favicon: branding.favicon ?? '',
|
||||
},
|
||||
/** Parse password policy with default values. */
|
||||
passwordPolicy: {
|
||||
|
|
11
packages/console/src/types/sign-in-experience.ts
Normal file
11
packages/console/src/types/sign-in-experience.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
// This is required to ensure the URLs will be reset when removing an image. RHF will ignore the
|
||||
|
||||
import { type SignInExperience } from '@logto/schemas';
|
||||
|
||||
// `undefined` value, so we need to provide an empty string to trigger the reset.
|
||||
export const emptyBranding: Required<SignInExperience['branding']> = Object.freeze({
|
||||
logoUrl: '',
|
||||
darkLogoUrl: '',
|
||||
favicon: '',
|
||||
darkFavicon: '',
|
||||
});
|
|
@ -68,8 +68,8 @@ export const convertResponseToForm = (connector: ConnectorResponse): ConnectorFo
|
|||
|
||||
return {
|
||||
name: name?.en,
|
||||
logo,
|
||||
logoDark,
|
||||
logo: logo ?? '',
|
||||
logoDark: logoDark ?? '',
|
||||
target: conditional(
|
||||
type === ConnectorType.Social && (isStandard ? target : metadata.target ?? target)
|
||||
),
|
||||
|
|
|
@ -5,11 +5,7 @@ import { logtoConsoleUrl as logtoConsoleUrlString, logtoUrl } from '#src/constan
|
|||
import { goToAdminConsole } from '#src/ui-helpers/index.js';
|
||||
import { expectNavigation, appendPathname } from '#src/utils.js';
|
||||
|
||||
import {
|
||||
expectToSaveSignInExperience,
|
||||
expectToSelectPreviewLanguage,
|
||||
waitForFormCard,
|
||||
} from './helpers.js';
|
||||
import { expectToSelectPreviewLanguage, waitForFormCard } from './helpers.js';
|
||||
|
||||
await page.setViewport({ width: 1920, height: 1080 });
|
||||
|
||||
|
@ -86,9 +82,6 @@ describe('sign-in experience: sign-in preview', () => {
|
|||
await expect(page).toClick(
|
||||
'form div[class$=field] label[class$=switch]:has(input[name="color.isDarkModeEnabled"])'
|
||||
);
|
||||
|
||||
// Save since the switch will lead a dirty form
|
||||
await expectToSaveSignInExperience(page);
|
||||
});
|
||||
|
||||
it('switch between preview languages', async () => {
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'Zeigen Sie den Firmennamen, die Adresse oder die Postleitzahl am Ende der E-Mails an, um die Authentizität zu erhöhen.',
|
||||
company_information_placeholder: 'Die grundlegenden Informationen Ihres Unternehmens',
|
||||
app_logo_field: 'App-Logo',
|
||||
app_logo_tip:
|
||||
'Zeigen Sie Ihr Markenlogo oben in E-Mails an. Verwenden Sie dasselbe Bild für den hellen und den dunklen Modus.',
|
||||
urls_not_allowed: 'URLs sind nicht erlaubt',
|
||||
test_notes: 'Logto verwendet die "Generic"-Vorlage zum Testen.',
|
||||
},
|
||||
|
|
|
@ -45,16 +45,6 @@ const connectors = {
|
|||
name_placeholder: 'Geben Sie den Namen für den Social-Sign-In-Button ein',
|
||||
name_tip:
|
||||
'Der Name des Connector-Buttons wird als "Weiter mit {{name}}" angezeigt. Achten Sie darauf, dass der Name nicht zu lang wird.',
|
||||
logo: 'Logo-URL für Social-Sign-In-Button',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip:
|
||||
'Das Logobild wird auf dem Connector angezeigt. Holen Sie sich einen öffentlich zugänglichen Bildlink und fügen Sie den Link hier ein.',
|
||||
logo_dark: 'Logo-URL für Social-Sign-In-Button (Dark mode)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip:
|
||||
'Legen Sie das Logo Ihres Connectors für den Dark-Modus fest, nachdem Sie ihn in der Anmeldeerfahrung der Admin Konsole aktiviert haben.',
|
||||
logo_dark_collapse: 'Zusammenklappen',
|
||||
logo_dark_show: 'Logo-Einstellung für Dark-Modus anzeigen',
|
||||
target: 'Identity Provider Name',
|
||||
target_placeholder: 'Geben Sie den Namen des Connector Identity Providers ein',
|
||||
target_tip:
|
||||
|
|
|
@ -95,10 +95,13 @@ const application_details = {
|
|||
no_organization_placeholder: 'No organization found. <a>Go to organizations</a>',
|
||||
branding: {
|
||||
name: 'Branding',
|
||||
description:
|
||||
'Customize the logos and brand colors of this application. The settings here will override the global sign-in experience settings.',
|
||||
description: 'Customize your app logo and branding color for the app-level experience.',
|
||||
description_third_party:
|
||||
"Customize your application's display name and logo on the consent screen.",
|
||||
app_logo: 'App logo',
|
||||
app_level_sie: 'App-level sign-in experience',
|
||||
app_level_sie_switch:
|
||||
'Enable the app-level sign-in experience and set up app-specific branding. If disabled, the omni sign-in experience will be used.',
|
||||
more_info: 'More info',
|
||||
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||
display_name: 'Display name',
|
||||
|
|
|
@ -49,7 +49,7 @@ const cloud = {
|
|||
'Feeling unsure about sign in experience? Just click the "Inspire Me" and let the magic happen!',
|
||||
inspire_me: 'Inspire me',
|
||||
},
|
||||
logo_field: 'App Logo',
|
||||
logo_field: 'App logo',
|
||||
color_field: 'Brand color',
|
||||
identifier_field: 'Identifier',
|
||||
identifier_options: {
|
||||
|
|
|
@ -48,8 +48,8 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'Display your company name, address, or zip code in the bottom of emails to enhance authenticity.',
|
||||
company_information_placeholder: "Your company's basic information",
|
||||
app_logo_field: 'App Logo',
|
||||
app_logo_tip:
|
||||
email_logo_field: 'Email logo',
|
||||
email_logo_tip:
|
||||
'Display your brand logo in the top of emails. Use the same image for both light mode and dark mode.',
|
||||
urls_not_allowed: 'URLs are not allowed',
|
||||
test_notes: 'Logto uses the “Generic” template for testing.',
|
||||
|
|
|
@ -44,16 +44,8 @@ const connectors = {
|
|||
name_placeholder: 'Enter name for social sign-in button',
|
||||
name_tip:
|
||||
'The name of the connector button will be displayed as "Continue with {{name}}." Be mindful of the length of the naming in case it gets too long.',
|
||||
logo: 'Logo URL for social sign-in button',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip:
|
||||
'Logo image will show on the connector. Get a publicly accessible image link and insert the link here.',
|
||||
logo_dark: 'Logo URL for social sign-in button (Dark mode)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip:
|
||||
'Set your connector’s logo for dark mode after enabling it in the sign-in experience.',
|
||||
logo_dark_collapse: 'Collapse',
|
||||
logo_dark_show: 'Show logo setting for dark mode',
|
||||
connector_logo: 'Connector logo',
|
||||
connector_logo_tip: 'The logo will be displayed on the connector sign-in button.',
|
||||
target: 'Identity provider name',
|
||||
target_placeholder: 'Enter connector identity provider name',
|
||||
target_tip:
|
||||
|
|
|
@ -40,6 +40,8 @@ const organization_details = {
|
|||
invalid_json_object: 'Invalid JSON object.',
|
||||
branding: {
|
||||
logo: 'Organization logos',
|
||||
logo_tooltip:
|
||||
'You can pass the organization ID to display this logo in the sign-in experience; the dark version of the logo is needed if dark mode is enabled in the omni sign-in experience settings. <a>Learn more</a>',
|
||||
},
|
||||
jit: {
|
||||
title: 'Just-in-time provisioning',
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'Muestre el nombre de su empresa, dirección o código postal en la parte inferior de los correos electrónicos para mejorar la autenticidad.',
|
||||
company_information_placeholder: 'Información básica de su empresa',
|
||||
app_logo_field: 'Logotipo de la aplicación',
|
||||
app_logo_tip:
|
||||
'Muestre el logotipo de su marca en la parte superior de los correos electrónicos. Utilice la misma imagen para el modo claro y oscuro.',
|
||||
urls_not_allowed: 'Las URL no están permitidas',
|
||||
test_notes: 'Logto utiliza la plantilla "Generic" para realizar pruebas.',
|
||||
},
|
||||
|
|
|
@ -45,16 +45,6 @@ const connectors = {
|
|||
name_placeholder: 'Ingrese el nombre para el botón de inicio de sesión social',
|
||||
name_tip:
|
||||
'El nombre del botón del conector se mostrará como "Continuar con {{name}}". Siempre tenga en cuenta la longitud del nombre en caso de que sea demasiado largo.',
|
||||
logo: 'URL del logotipo para el botón de inicio de sesión social',
|
||||
logo_placeholder: 'https://tudominio.cdn/logo.png',
|
||||
logo_tip:
|
||||
'La imagen del logotipo se mostrará en el conector. Obtenga un enlace de imagen accesible públicamente e insértelo aquí.',
|
||||
logo_dark: 'URL del logotipo para el botón de inicio de sesión social (modo oscuro)',
|
||||
logo_dark_placeholder: 'https://tudominio.cdn/logo.png',
|
||||
logo_dark_tip:
|
||||
'Configure el logotipo de su conector para el modo oscuro después de activarlo en la Experiencia de inicio de sesión del Panel de administrador.',
|
||||
logo_dark_collapse: 'Colapso',
|
||||
logo_dark_show: 'Mostrar configuración de logotipo para modo oscuro',
|
||||
target: 'Nombre del proveedor de identidad',
|
||||
target_placeholder: 'Ingrese el nombre del proveedor de identidad del conector',
|
||||
target_tip:
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
"Affichez le nom de votre entreprise, votre adresse ou votre code postal en bas des emails pour renforcer l'authenticité.",
|
||||
company_information_placeholder: 'Les informations de base de votre entreprise',
|
||||
app_logo_field: 'Logo de l’application',
|
||||
app_logo_tip:
|
||||
'Affichez le logo de votre marque en haut des emails. Utilisez la même image pour le mode clair et le mode sombre.',
|
||||
urls_not_allowed: 'Les URLs ne sont pas autorisées',
|
||||
test_notes: 'Logto utilise le modèle "Generic" pour les tests.',
|
||||
},
|
||||
|
|
|
@ -46,16 +46,6 @@ const connectors = {
|
|||
name_placeholder: 'Entrez un nom pour le bouton de connexion sociale',
|
||||
name_tip:
|
||||
'Le nom du bouton de connexion s\'affichera comme "Continuer avec {{name}}". Tenez compte de la longueur du nom si celui-ci devient trop long.',
|
||||
logo: 'URL du logo pour le bouton de connexion sociale',
|
||||
logo_placeholder: 'https://votre.domaine.cdn/logo.png',
|
||||
logo_tip:
|
||||
"L'image du logo s'affichera sur le connecteur. Obtenez un lien d'image accessible au public et insérez le lien ici.",
|
||||
logo_dark: 'URL du logo pour le bouton de connexion sociale (mode sombre)',
|
||||
logo_dark_placeholder: 'https://votre.domaine.cdn/logo.png',
|
||||
logo_dark_tip:
|
||||
"Définissez le logo de votre connecteur pour le mode sombre après l'avoir activé dans l'expérience de connexion de la console d'administration.",
|
||||
logo_dark_collapse: 'Réduire',
|
||||
logo_dark_show: "Afficher l'option du logo pour le mode sombre",
|
||||
target: "Nom du fournisseur d'identité",
|
||||
target_placeholder: "Entrez le nom du fournisseur d'identité du connecteur",
|
||||
target_tip:
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
"Mostra il nome dell'azienda, l'indirizzo o il codice postale in fondo alle email per migliorare l'autenticità.",
|
||||
company_information_placeholder: 'Le informazioni di base della tua azienda',
|
||||
app_logo_field: "Logo dell'app",
|
||||
app_logo_tip:
|
||||
'Mostra il logo del tuo marchio in cima alle email. Utilizza la stessa immagine per la modalità chiara e scura.',
|
||||
urls_not_allowed: 'URL non sono ammessi',
|
||||
test_notes: 'Logto utilizza il modello "Generico" per i test.',
|
||||
},
|
||||
|
|
|
@ -45,16 +45,6 @@ const connectors = {
|
|||
name_placeholder: 'Inserisci il nome per il pulsante di accesso tramite social media',
|
||||
name_tip:
|
||||
'Il nome del pulsante del connettore verrà visualizzato come "Continua con {{name}}." Presta attenzione alla lunghezza del nome in caso risulti troppo lungo.',
|
||||
logo: 'URL del logo per il pulsante di accesso tramite social media',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip:
|
||||
"L'immagine del logo verrà mostrata sul connettore. Otteni un link di immagine pubblicamente accessibile e inserisci qui il link.",
|
||||
logo_dark: 'URL del logo per il pulsante di accesso tramite social media (modalità scura)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip:
|
||||
"Imposta il logo del tuo connettore per la modalità scura dopo averla abilitata nell'esperienza di accesso nel Console dell'Amministratore.",
|
||||
logo_dark_collapse: 'Comprimi',
|
||||
logo_dark_show: 'Mostra le impostazioni del logo per la modalità scura',
|
||||
target: 'Nome del provider di identità',
|
||||
target_placeholder: 'Inserisci il nome del provider di identità del connettore',
|
||||
target_tip:
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'メールの下部に会社名、住所、郵便番号などを表示して、真正性を高めます。',
|
||||
company_information_placeholder: '会社の基本情報を入力してください',
|
||||
app_logo_field: 'アプリのロゴ',
|
||||
app_logo_tip:
|
||||
'メールの上部にブランドロゴを表示します。ライトモードとダークモードの両方で同じ画像を使用してください。',
|
||||
urls_not_allowed: 'URLは許可されません',
|
||||
test_notes: 'Logtoはテストのために「共通」テンプレートを使用しています。',
|
||||
},
|
||||
|
|
|
@ -44,15 +44,6 @@ const connectors = {
|
|||
name_placeholder: 'ソーシャルサインインボタンの名前を入力',
|
||||
name_tip:
|
||||
'コネクタボタンの名前は「{{name}}で続ける」で表示されます。名前が長くなりすぎないように注意してください。',
|
||||
logo: 'ソーシャルサインインボタンのロゴURL',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip:
|
||||
'ロゴ画像はコネクタに表示されます。一般的にアバターやトンマークなどを使用します。公開アクセス可能な画像リンクを取得し、リンクをここに挿入してください。',
|
||||
logo_dark: 'ソーシャルサインインボタンのロゴURL(ダークモード)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip: 'ダークモードでコネクタのロゴを設定してください。',
|
||||
logo_dark_collapse: '折りたたむ',
|
||||
logo_dark_show: 'ダークモード用のロゴ設定を表示',
|
||||
target: 'Identity Providerの名前',
|
||||
target_placeholder: 'コネクタIdentity Providerの名前を入力',
|
||||
target_tip: '「IdP名」として、ソーシャルIDを識別するための一意の識別子文字列を指定します。',
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'이메일 하단에 회사 이름, 주소 또는 우편번호를 표시하여 신뢰성을 높입니다.',
|
||||
company_information_placeholder: '회사의 기본 정보',
|
||||
app_logo_field: '앱 로고',
|
||||
app_logo_tip:
|
||||
'이메일 상단에 브랜드 로고를 표시합니다. 라이트 모드와 다크 모드에 모두 동일한 이미지를 사용합니다.',
|
||||
urls_not_allowed: 'URL은 허용되지 않습니다.',
|
||||
test_notes: 'Logto는 "Generic" 템플릿을 사용하여 테스트합니다.',
|
||||
},
|
||||
|
|
|
@ -44,15 +44,6 @@ const connectors = {
|
|||
name: '소셜 로그인 버튼 이름',
|
||||
name_placeholder: '소셜 로그인 버튼 이름을 입력하세요',
|
||||
name_tip: '다음과 같이 연동 이름이 출력돼요. "{{name}}으로 계속하기".',
|
||||
logo: '연동 로고 URL',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip: '이 이미지는 연동 버튼에 보여질 거에요.',
|
||||
logo_dark: '소셜 로그인에 사용될 로고 URL (다크 모드)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip:
|
||||
'관리자 콘솔의 로그인 경험에서 다크 모드를 위한 로고를 활성화한 후 다크 모드용 연동 로고를 설정해 주세요.',
|
||||
logo_dark_collapse: '최소화',
|
||||
logo_dark_show: '다크 모드를 위한 로고 설정 보이기',
|
||||
target: '연동 ID 공급자',
|
||||
target_placeholder: '연동 ID 공급자 이름을 입력하세요',
|
||||
target_tip: '"IdP 이름"의 값은 소셜 식별자를 구분하기 위한 고유 식별자 문자열이 될 수 있어요.',
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'Wyświetlaj nazwę firmy, adres lub kod pocztowy na dole wiadomości e-mail, aby zwiększyć autentyczność.',
|
||||
company_information_placeholder: 'Podstawowe informacje o Twojej firmie',
|
||||
app_logo_field: 'Logo aplikacji',
|
||||
app_logo_tip:
|
||||
'Wyświetlaj logo marki na górze wiadomości e-mail. Użyj tego samego obrazu zarówno dla trybu jasnego, jak i ciemnego.',
|
||||
urls_not_allowed: 'Nie dozwolone adresy URL',
|
||||
test_notes: 'Logto używa szablonu "Ogólny" do testów.',
|
||||
},
|
||||
|
|
|
@ -44,16 +44,6 @@ const connectors = {
|
|||
name_placeholder: 'Wpisz nazwę przycisku logowania społecznościowego',
|
||||
name_tip:
|
||||
'Nazwa przycisku łącznika będzie wyświetlana jako "Kontynuuj z {{name}}." Uwzględnij długość nazwy, gdyż może stać się zbyt długa.',
|
||||
logo: 'Logo URL łącznika społecznościowego',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip:
|
||||
'Obraz logo zostanie wyświetlony na łączniku. Uzyskaj publicznie dostępny link do obrazu i wklej tutaj link.',
|
||||
logo_dark: 'Logo URL łącznika społecznościowego (tryb ciemny)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip:
|
||||
'Ustaw logo łącznika dla trybu ciemnego po jego włączeniu w Doświadczeniu logowania w Konsoli Admina.',
|
||||
logo_dark_collapse: 'Zwiń',
|
||||
logo_dark_show: 'Pokaż ustawienia logo dla trybu ciemnego',
|
||||
target: 'Nazwa dostawcy tożsamości',
|
||||
target_placeholder: 'Wpisz nazwę dostawcy tożsamości łącznika',
|
||||
target_tip:
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'Exiba o nome da sua empresa, endereço ou código postal no final dos e-mails para melhorar a autenticidade.',
|
||||
company_information_placeholder: 'Informações básicas da sua empresa',
|
||||
app_logo_field: 'Logotipo do aplicativo',
|
||||
app_logo_tip:
|
||||
'Exiba o logotipo da sua marca no topo dos e-mails. Use a mesma imagem para o modo claro e escuro.',
|
||||
urls_not_allowed: 'URLs não permitidas',
|
||||
test_notes: 'Logto utiliza o modelo "Genérico" para testes.',
|
||||
},
|
||||
|
|
|
@ -44,15 +44,6 @@ const connectors = {
|
|||
name: 'Nome do botão de login social',
|
||||
name_placeholder: 'Insira o nome do botão de login social',
|
||||
name_tip: 'O nome do botão do conector será exibido como "Continuar com {{Nome do Conector}}".',
|
||||
logo: 'URL do logo para o botão de login social',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip: 'A imagem do logotipo também será exibida no botão do conector.',
|
||||
logo_dark: 'URL do logo para o botão de login social (modo escuro)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip:
|
||||
'Defina o logotipo do seu conector para o modo escuro depois de ativá-lo na Experiência de login do Console de Administração.',
|
||||
logo_dark_collapse: 'Expandir',
|
||||
logo_dark_show: 'Mostrar configuração de logotipo para modo escuro',
|
||||
target: 'Nome do fornecedor de identidade',
|
||||
target_placeholder: 'Insira o nome do fornecedor de identidade do conector',
|
||||
target_tip:
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'Exiba o nome da sua empresa, endereço ou código postal no rodapé dos e-mails para aumentar a autenticidade.',
|
||||
company_information_placeholder: 'Informação básica da empresa',
|
||||
app_logo_field: 'Logotipo do aplicativo',
|
||||
app_logo_tip:
|
||||
'Exiba o logotipo da sua marca no topo dos e-mails. Utilize a mesma imagem para o modo claro e escuro.',
|
||||
urls_not_allowed: 'Os URLs não são permitidos',
|
||||
test_notes: 'O Logto utiliza o modelo "Genérico" para os testes.',
|
||||
},
|
||||
|
|
|
@ -45,16 +45,6 @@ const connectors = {
|
|||
name_placeholder: 'Insira o nome do botão de login social',
|
||||
name_tip:
|
||||
'O nome do botão do conector será exibido como "Continuar com {{name}}." Esteja atento ao comprimento da nomenclatura caso ela fique muito longa.',
|
||||
logo: 'URL do logo para o botão de login social',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip:
|
||||
'A imagem do logo será exibida no conector. Obtenha um link de imagem publicamente acessível e insira o link aqui.',
|
||||
logo_dark: 'URL do logo para o botão de login social (modo escuro)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip:
|
||||
'Defina o logo do seu conector para o modo escuro depois de ativá-lo na Experiência de login no Console do administrador.',
|
||||
logo_dark_collapse: 'Fechar',
|
||||
logo_dark_show: 'Exibir configurações de logo para o modo escuro',
|
||||
target: 'Nome do provedor de identidade',
|
||||
target_placeholder: 'Insira o nome do provedor de identidade do conector',
|
||||
target_tip:
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'Отображайте имя вашей компании, адрес или почтовый код внизу электронных писем, чтобы улучшить подлинность.',
|
||||
company_information_placeholder: 'Основная информация о вашей компании',
|
||||
app_logo_field: 'Логотип приложения',
|
||||
app_logo_tip:
|
||||
'Отобразите логотип вашего бренда в верхней части электронных писем. Используйте одно и то же изображение для светлого и темного режимов.',
|
||||
urls_not_allowed: 'URL-адреса не разрешены',
|
||||
test_notes: 'Logto использует шаблон "Общий" для тестирования.',
|
||||
},
|
||||
|
|
|
@ -44,16 +44,6 @@ const connectors = {
|
|||
name_placeholder: 'Введите имя для кнопки входа через социальные сети',
|
||||
name_tip:
|
||||
'Имя кнопки коннектора будет отображаться как "Продолжить с {{name}}". Будьте внимательны к длине названия, в случае, если оно станет слишком длинным.',
|
||||
logo: 'URL логотипа для кнопки входа через социальные сети',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip:
|
||||
'Логотип будет отображаться на коннекторе. Получите публично доступную ссылку на изображение и вставьте ссылку сюда.',
|
||||
logo_dark: 'URL логотипа для кнопки входа через социальные сети (Темный режим)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip:
|
||||
'Установите логотип коннектора для темного режима после его включения в опыте входа в систему в Административной консоли.',
|
||||
logo_dark_collapse: 'Свернуть',
|
||||
logo_dark_show: 'Показать настройки логотипа для темного режима',
|
||||
target: 'Имя поставщика идентификации',
|
||||
target_placeholder: 'Введите имя поставщика идентификации коннектора',
|
||||
target_tip:
|
||||
|
|
|
@ -48,9 +48,6 @@ const connector_details = {
|
|||
company_information_description:
|
||||
'E-postaların alt kısmında şirket adınızı, adresinizi veya posta kodunuzu görüntüleyin.',
|
||||
company_information_placeholder: 'Şirketinizin temel bilgileri',
|
||||
app_logo_field: 'Uygulama Logosu',
|
||||
app_logo_tip:
|
||||
'E-postaların üstünde marka logosunu görüntüleyin. Aynı görüntüyü açık mod ve karanlık mod için kullanın.',
|
||||
urls_not_allowed: 'URLler izin verilmez',
|
||||
test_notes: 'Logto testler için "Generic" şablonunu kullanır.',
|
||||
},
|
||||
|
|
|
@ -45,15 +45,6 @@ const connectors = {
|
|||
name_placeholder: 'Sosyal oturum açma düğmesi için ad girin',
|
||||
name_tip:
|
||||
'Bağlayıcı düğmesinin adı "{{name}} ile devam et" olarak görüntülenecektir. İsimlendirmenin uzunluğuna dikkat edin, çok uzun olursa bir sorun oluşabilir.',
|
||||
logo: 'Sosyal oturum açma düğmesi için logo URL',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip:
|
||||
'Logo resmi bağlantısı düğmede görüntülenecektir. Herkese açık bir bağlantı alın ve bağlantıyı buraya yapıştırın.',
|
||||
logo_dark: 'Sosyal oturum açma düğmesi için logo URL (Karanlık mod)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip: 'Karanlık modu etkinleştikten sonra bağlayıcı logonuzu ayarlayın.',
|
||||
logo_dark_collapse: 'Daralt',
|
||||
logo_dark_show: 'Karanlık mod logu ayarını göster',
|
||||
target: 'Kimlik sağlayıcısı adı',
|
||||
target_placeholder: 'Bağlayıcı kimlik sağlayıcısı adını girin',
|
||||
target_tip:
|
||||
|
|
|
@ -45,8 +45,6 @@ const connector_details = {
|
|||
company_information_field: '公司信息',
|
||||
company_information_description: '在电子邮件底部显示您公司的名称、地址或邮编,以增强真实性。',
|
||||
company_information_placeholder: '你公司的基本信息',
|
||||
app_logo_field: '应用程序标志',
|
||||
app_logo_tip: '在电子邮件顶部显示您的品牌标志。在浅色模式和深色模式下使用相同的图像。',
|
||||
urls_not_allowed: '不允许使用 URL',
|
||||
test_notes: 'Logto 使用 "通用" 模板进行测试。',
|
||||
},
|
||||
|
|
|
@ -40,14 +40,6 @@ const connectors = {
|
|||
name: '社交登录按钮的名称',
|
||||
name_placeholder: '输入社交登录按钮的名称',
|
||||
name_tip: '按钮上将展示「通过 {{name}} 继续」。名字不宜过长而导致信息无法展示完整。',
|
||||
logo: '社交登录按钮的 Logo 图片链接',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip: '该图片将用于连接器的展示。获取图片链接后粘贴在此处。',
|
||||
logo_dark: '社交登录按钮的 Logo 图片链接(深色模式)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip: '在管理控制台的登录体验中启用“深色模式”后,可设置此连接器 Logo 用于深色模式。',
|
||||
logo_dark_collapse: '收起',
|
||||
logo_dark_show: '展开深色模式 Logo 设置',
|
||||
target: '身份提供商名称',
|
||||
target_placeholder: '输入身份提供商的名称',
|
||||
target_tip: '在“身份供应商名称”字段中输入唯一的标识符字符串,用于区分社交身份来源。',
|
||||
|
|
|
@ -45,8 +45,6 @@ const connector_details = {
|
|||
company_information_field: '公司信息',
|
||||
company_information_description: '在郵件底部顯示您的公司名稱、地址或郵政編碼,以增強真實性。',
|
||||
company_information_placeholder: '您公司的基本信息',
|
||||
app_logo_field: '應用程序標誌',
|
||||
app_logo_tip: '在郵件頂部顯示您的品牌標誌。在淺色模式和深色模式下使用相同的圖像。',
|
||||
urls_not_allowed: '不允許使用 URL',
|
||||
test_notes: 'Logto 使用 "通用" 模板進行測試。',
|
||||
},
|
||||
|
|
|
@ -40,14 +40,6 @@ const connectors = {
|
|||
name: '社交登錄按鈕的名稱',
|
||||
name_placeholder: '輸入社交登錄按鈕的名稱',
|
||||
name_tip: '按鈕上將展示「通過 {{name}} 繼續」。名字不宜過長而導致信息無法展示完整。',
|
||||
logo: '社交登錄按鈕的 Logo 圖片鏈接',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip: '該圖片將用於連接器的展示。獲取圖片鏈接後粘貼在此處。',
|
||||
logo_dark: '社交登錄按鈕的 Logo 圖片鏈接(深色模式)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip: '在管理控制臺的登錄體驗中啟用“深色模式”後,可設置此連接器 Logo 用於深色模式。',
|
||||
logo_dark_collapse: '收起',
|
||||
logo_dark_show: '展開深色模式 Logo 設置',
|
||||
target: '身份提供商名稱',
|
||||
target_placeholder: '輸入身份提供商的名稱',
|
||||
target_tip: '在“身份供應商名稱”字段中輸入唯一的標識符字符串,用於區分社交身份來源。',
|
||||
|
|
|
@ -45,8 +45,6 @@ const connector_details = {
|
|||
company_information_field: '公司信息',
|
||||
company_information_description: '在郵件底部顯示您的公司名稱、地址或郵編,以提高真實性。',
|
||||
company_information_placeholder: '您的公司基本信息',
|
||||
app_logo_field: '應用程式標誌',
|
||||
app_logo_tip: '在郵件頂部顯示您的品牌標誌。在淺色模式和深色模式下使用相同的圖像。',
|
||||
urls_not_allowed: '不允許使用 URL',
|
||||
test_notes: 'Logto 使用「通用」模板進行測試。',
|
||||
},
|
||||
|
|
|
@ -40,14 +40,6 @@ const connectors = {
|
|||
name: '社交登錄按鈕的名稱',
|
||||
name_placeholder: '輸入社交登錄按鈕的名稱',
|
||||
name_tip: '按鈕上將展示「通過 {{name}} 繼續」。名字不宜過長而導致信息無法展示完整。',
|
||||
logo: '社交登錄按鈕的 Logo 圖片鏈接',
|
||||
logo_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_tip: '該圖片將用於連接器的展示。獲取圖片鏈接後粘貼在此處。',
|
||||
logo_dark: '社交登錄按鈕的 Logo 圖片鏈接(深色模式)',
|
||||
logo_dark_placeholder: 'https://your.cdn.domain/logo.png',
|
||||
logo_dark_tip: '在管理控制台的登錄體驗中啟用“深色模式”後,可設置此連接器 Logo 用於深色模式。',
|
||||
logo_dark_collapse: '收起',
|
||||
logo_dark_show: '展開深色模式 Logo 設置',
|
||||
target: '身份提供商名稱',
|
||||
target_placeholder: '輸入身份提供商的名稱',
|
||||
target_tip: '在“身份供應商名稱”字段中輸入唯一的標識符字符串,用於區分社交身份來源。',
|
||||
|
|
Loading…
Add table
Reference in a new issue