0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

refactor(console): optimize onboarding (#6837)

* refactor(console): optimize onboarding

* refactor: fix lint issues

* chore: remove unused phrases

* chore: fix code style
This commit is contained in:
Gao Sun 2024-11-27 16:35:40 +08:00 committed by GitHub
parent 4b5db6ed1c
commit e8c71fa913
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 108 additions and 2759 deletions

View file

@ -26,8 +26,8 @@ function SocialDemoCallback() {
<div className={styles.container}> <div className={styles.container}>
<Card className={styles.card}> <Card className={styles.card}>
{theme === Theme.Light ? <Congrats /> : <CongratsDark />} {theme === Theme.Light ? <Congrats /> : <CongratsDark />}
<div className={styles.title}>{t('cloud.socialCallback.title')}</div> <div className={styles.title}>{t('cloud.social_callback.title')}</div>
<div className={styles.message}>{t('cloud.socialCallback.description')}</div> <div className={styles.message}>{t('cloud.social_callback.description')}</div>
</Card> </Card>
</div> </div>
); );

View file

@ -4,17 +4,23 @@ import ProgressBar from '../ProgressBar';
import styles from './index.module.scss'; import styles from './index.module.scss';
type Props = { type Props =
readonly step: number; | {
readonly totalSteps: number; readonly step: number;
readonly children: ReactNode; readonly totalSteps: number;
}; readonly children: ReactNode;
}
| {
readonly children: ReactNode;
};
function ActionBar({ step, totalSteps, children }: Props) { function ActionBar(props: Props) {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<ProgressBar currentStep={step} totalSteps={totalSteps} /> {'step' in props && 'totalSteps' in props && (
<div className={styles.actions}>{children}</div> <ProgressBar currentStep={props.step} totalSteps={props.totalSteps} />
)}
<div className={styles.actions}>{props.children}</div>
</div> </div>
); );
} }

View file

@ -10,6 +10,7 @@ import CreateTenantHeaderIcon from '@/assets/icons/create-tenant-header.svg?reac
import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
import { type TenantResponse } from '@/cloud/types/router'; import { type TenantResponse } from '@/cloud/types/router';
import Region, { RegionName } from '@/components/Region'; import Region, { RegionName } from '@/components/Region';
import { availableRegions } from '@/consts';
import Button from '@/ds-components/Button'; import Button from '@/ds-components/Button';
import DangerousRaw from '@/ds-components/DangerousRaw'; import DangerousRaw from '@/ds-components/DangerousRaw';
import FormField from '@/ds-components/FormField'; import FormField from '@/ds-components/FormField';
@ -124,8 +125,7 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
rules={{ required: true }} rules={{ required: true }}
render={({ field: { onChange, value, name } }) => ( render={({ field: { onChange, value, name } }) => (
<RadioGroup type="small" name={name} value={value} onChange={onChange}> <RadioGroup type="small" name={name} value={value} onChange={onChange}>
{/* Manually maintaining the list of regions to avoid unexpected changes. We may consider using an API in the future. */} {availableRegions.map((region) => (
{[RegionName.EU, RegionName.US, RegionName.AU].map((region) => (
<Radio <Radio
key={region} key={region}
title={ title={

View file

@ -146,3 +146,10 @@ const getAdminTenantEndpoint = () => {
export const adminTenantEndpoint = getAdminTenantEndpoint(); export const adminTenantEndpoint = getAdminTenantEndpoint();
export const mainTitle = isCloud ? 'Logto Cloud' : 'Logto Console'; export const mainTitle = isCloud ? 'Logto Cloud' : 'Logto Console';
// Manually maintaining the list of regions to avoid unexpected changes. We may consider using an API in the future.
export const availableRegions = Object.freeze([
RegionName.EU,
RegionName.US,
RegionName.AU,
] as const);

View file

@ -22,7 +22,7 @@ type AppData = {
export const AppDataContext = createContext<AppData>({}); export const AppDataContext = createContext<AppData>({});
export const useTenantEndpoint = (tenantId: string) => { const useTenantEndpoint = (tenantId: string) => {
return useSWRImmutable(`api/.well-known/endpoints/${tenantId}`, async (pathname) => { return useSWRImmutable(`api/.well-known/endpoints/${tenantId}`, async (pathname) => {
const { user } = await ky.get(new URL(pathname, adminTenantEndpoint)).json<{ user: string }>(); const { user } = await ky.get(new URL(pathname, adminTenantEndpoint)).json<{ user: string }>();
return new URL(user); return new URL(user);

View file

@ -1,29 +0,0 @@
import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
import type { CardSelectorOption } from './types';
type Props = {
readonly name: string;
readonly value: string;
readonly options: CardSelectorOption[];
readonly onChange: (value: string) => void;
readonly optionClassName?: string;
};
function CardSelector({ name, value, options, onChange, optionClassName }: Props) {
return (
<RadioGroup type="compact" value={value} name={name} onChange={onChange}>
{options.map(({ value: optionValue, title, icon }) => (
<Radio
key={optionValue}
icon={icon}
title={title}
value={optionValue}
className={optionClassName}
/>
))}
</RadioGroup>
);
}
export default CardSelector;

View file

@ -1,65 +0,0 @@
@use '@/scss/underscore' as _;
.item {
border: 1px solid var(--color-border);
border-radius: 12px;
min-height: 80px;
padding: _.unit(5);
font: var(--font-label-2);
user-select: none;
background-color: var(--color-layer-1);
color: var(--color-text);
display: flex;
align-items: center;
.icon {
color: var(--color-text-secondary);
margin-inline-end: _.unit(4);
vertical-align: middle;
> svg {
display: block;
}
}
.content {
.tag {
font: var(--font-body-3);
color: var(--color-text-secondary);
}
.trailingTag {
margin-inline-start: _.unit(1);
}
}
&.disabled {
border-color: var(--color-layer-2);
background-color: var(--color-layer-2);
&:hover {
cursor: not-allowed;
}
}
&:not(.disabled).selected {
border-color: var(--color-primary);
background-color: var(--color-hover-variant);
color: var(--color-primary);
.icon {
color: var(--color-primary);
}
}
&:not(.disabled):hover {
cursor: pointer;
border-color: var(--color-primary);
color: var(--color-primary);
.icon {
color: var(--color-primary);
}
}
}

View file

@ -1,71 +0,0 @@
import { conditional } from '@silverhand/essentials';
import classNames from 'classnames';
import DynamicT from '@/ds-components/DynamicT';
import { Tooltip } from '@/ds-components/Tip';
import { onKeyDownHandler } from '@/utils/a11y';
import type { MultiCardSelectorOption } from '../types';
import styles from './CardItem.module.scss';
type Props = {
readonly option: MultiCardSelectorOption;
readonly isSelected: boolean;
readonly onClick: (value: string) => void;
readonly className?: string;
};
function CardItem({
option: { icon, title, value, tag, trailingTag, isDisabled, disabledTip },
isSelected,
onClick,
className,
}: Props) {
return (
<Tooltip content={conditional(isDisabled && disabledTip && <DynamicT forKey={disabledTip} />)}>
<div
key={value}
role="button"
tabIndex={0}
className={classNames(
styles.item,
isDisabled && styles.disabled,
isSelected && styles.selected,
className
)}
onClick={() => {
if (isDisabled) {
return;
}
onClick(value);
}}
onKeyDown={onKeyDownHandler(() => {
if (isDisabled) {
return;
}
onClick(value);
})}
>
{icon && <span className={styles.icon}>{icon}</span>}
<div className={styles.content}>
<div>
{typeof title === 'string' ? <DynamicT forKey={title} /> : title}
{trailingTag && (
<span className={classNames(styles.tag, styles.trailingTag)}>
<DynamicT forKey={trailingTag} />
</span>
)}
</div>
{tag && (
<span className={styles.tag}>
<DynamicT forKey={tag} />
</span>
)}
</div>
</div>
</Tooltip>
);
}
export default CardItem;

View file

@ -1,7 +0,0 @@
@use '@/scss/underscore' as _;
.selector {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: _.unit(4);
}

View file

@ -1,52 +0,0 @@
import classNames from 'classnames';
import type { MultiCardSelectorOption } from '../types';
import CardItem from './CardItem';
import styles from './index.module.scss';
type Props = {
readonly options: MultiCardSelectorOption[];
readonly value: string[];
readonly onChange: (value: string[]) => void;
readonly isNotAllowEmpty?: boolean;
readonly className?: string;
readonly optionClassName?: string;
};
function MultiCardSelector({
options,
value: selectedValues,
onChange,
isNotAllowEmpty = false,
className,
optionClassName,
}: Props) {
const onToggle = (value: string) => {
if (selectedValues.includes(value) && selectedValues.length === 1 && isNotAllowEmpty) {
return;
}
onChange(
selectedValues.includes(value)
? selectedValues.filter((selected) => selected !== value)
: [...selectedValues, value]
);
};
return (
<div className={classNames(styles.selector, className)}>
{options.map((option) => (
<CardItem
key={option.value}
option={option}
isSelected={selectedValues.includes(option.value)}
className={optionClassName}
onClick={onToggle}
/>
))}
</div>
);
}
export default MultiCardSelector;

View file

@ -1,3 +0,0 @@
export type { CardSelectorOption, MultiCardSelectorOption } from './types';
export { default as CardSelector } from './CardSelector';
export { default as MultiCardSelector } from './MultiCardSelector';

View file

@ -1,17 +0,0 @@
import type { AdminConsoleKey } from '@logto/phrases';
import type { ReactElement, ReactNode } from 'react';
import type DangerousRaw from '@/ds-components/DangerousRaw';
export type CardSelectorOption = {
icon?: ReactNode;
title: AdminConsoleKey | ReactElement<typeof DangerousRaw>;
value: string;
};
export type MultiCardSelectorOption = CardSelectorOption & {
tag?: AdminConsoleKey;
trailingTag?: AdminConsoleKey;
isDisabled?: boolean;
disabledTip?: AdminConsoleKey;
};

View file

@ -9,7 +9,7 @@ function DemoConnectorNotice() {
return ( return (
<InlineNotification className={styles.notice}> <InlineNotification className={styles.notice}>
{t('cloud.sie.connectors.notice')} {t('cloud.social_callback.notice')}
</InlineNotification> </InlineNotification>
); );
} }

View file

@ -1,36 +0,0 @@
import { buildOrganizationUrn } from '@logto/core-kit';
import { getTenantOrganizationId } from '@logto/schemas';
import { appendPath } from '@silverhand/essentials';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { type StaticApiProps, useStaticApi } from '@/hooks/use-api';
/**
* A hook to get a Ky instance with the current tenant's Management API prefix URL. Note this hook
* can only be used in a route with a `:tenantId` param.
*/
const useTenantApi = (props: Omit<StaticApiProps, 'prefixUrl' | 'resourceIndicator'> = {}) => {
const { tenantId: currentTenantId } = useParams();
if (!currentTenantId) {
throw new Error(
'No tenant ID param found in the current route. This hook should be used in a route with a tenant ID param.'
);
}
const config = useMemo(
() => ({
prefixUrl: appendPath(new URL(window.location.origin), 'm', currentTenantId),
resourceIndicator: buildOrganizationUrn(getTenantOrganizationId(currentTenantId)),
}),
[currentTenantId]
);
return useStaticApi({
...props,
...config,
});
};
export default useTenantApi;

View file

@ -1,28 +0,0 @@
import type React from 'react';
import { useMemo } from 'react';
import type { SWRConfig } from 'swr';
import useSwrFetcher from '@/hooks/use-swr-fetcher';
import { shouldRetryOnError } from '@/utils/request';
import useTenantApi from './use-tenant-api';
/**
* A hook to get the SWR options for the current tenant by reading the `:tenantId` param from the
* route.
*/
const useTenantSwrOptions = (): Partial<React.ComponentProps<typeof SWRConfig>['value']> => {
const api = useTenantApi();
const fetcher = useSwrFetcher(api);
const config = useMemo(
() => ({
fetcher,
shouldRetryOnError: shouldRetryOnError({ ignore: [401, 403] }),
}),
[fetcher]
);
return config;
};
export default useTenantSwrOptions;

View file

@ -1,27 +0,0 @@
import { type UserAssetsServiceStatus } from '@logto/schemas';
import useSWRImmutable from 'swr/immutable';
import { type RequestError } from '@/hooks/use-api';
import useSwrFetcher from '@/hooks/use-swr-fetcher';
import useTenantApi from './use-tenant-api';
/**
* A hook to check if the current tenant's user assets service is ready. The tenant ID is read from
* `:tenantId` param in the route.
*/
const useTenantUserAssetsService = () => {
const api = useTenantApi();
const fetcher = useSwrFetcher<UserAssetsServiceStatus>(api);
const { data, error } = useSWRImmutable<UserAssetsServiceStatus, RequestError>(
'api/user-assets/service-status',
fetcher
);
return {
isReady: data?.status === 'ready',
isLoading: !error && !data,
};
};
export default useTenantUserAssetsService;

View file

@ -1,6 +1,6 @@
import { Theme } from '@logto/schemas'; import { Theme } from '@logto/schemas';
import { useContext, useEffect } from 'react'; import { useContext, useEffect } from 'react';
import { Navigate, type RouteObject, useMatch, useRoutes } from 'react-router-dom'; import { Navigate, type RouteObject, useRoutes } from 'react-router-dom';
import AppLoading from '@/components/AppLoading'; import AppLoading from '@/components/AppLoading';
import AppBoundary from '@/containers/AppBoundary'; import AppBoundary from '@/containers/AppBoundary';
@ -11,35 +11,21 @@ import Topbar from './components/Topbar';
import useUserOnboardingData from './hooks/use-user-onboarding-data'; import useUserOnboardingData from './hooks/use-user-onboarding-data';
import styles from './index.module.scss'; import styles from './index.module.scss';
import CreateTenant from './pages/CreateTenant'; import CreateTenant from './pages/CreateTenant';
import SignInExperience from './pages/SignInExperience';
import Welcome from './pages/Welcome';
import { OnboardingPage } from './types'; import { OnboardingPage } from './types';
import { getOnboardingPage } from './utils';
const welcomePathname = getOnboardingPage(OnboardingPage.Welcome);
const routeObjects: RouteObject[] = [ const routeObjects: RouteObject[] = [
{ {
index: true, index: true,
element: <Navigate replace to={OnboardingPage.Welcome} />, element: <Navigate replace to={OnboardingPage.CreateTenant} />,
},
{
path: OnboardingPage.Welcome,
element: <Welcome />,
}, },
{ {
path: OnboardingPage.CreateTenant, path: OnboardingPage.CreateTenant,
element: <CreateTenant />, element: <CreateTenant />,
}, },
{
path: `:tenantId/${OnboardingPage.SignInExperience}`,
element: <SignInExperience />,
},
]; ];
export function OnboardingApp() { export function OnboardingApp() {
const { setThemeOverride } = useContext(AppThemeContext); const { setThemeOverride } = useContext(AppThemeContext);
const matched = useMatch(welcomePathname);
const routes = useRoutes(routeObjects); const routes = useRoutes(routeObjects);
usePlausiblePageview(routeObjects, 'onboarding'); usePlausiblePageview(routeObjects, 'onboarding');
@ -54,7 +40,7 @@ export function OnboardingApp() {
const { const {
isLoading, isLoading,
data: { questionnaire, isOnboardingDone }, data: { isOnboardingDone },
} = useUserOnboardingData(); } = useUserOnboardingData();
if (isLoading) { if (isLoading) {
@ -65,11 +51,6 @@ export function OnboardingApp() {
return <Navigate replace to="/" />; return <Navigate replace to="/" />;
} }
// Redirect to the welcome page if the user has not started the onboarding process.
if (!questionnaire && !matched) {
return <Navigate replace to={welcomePathname} />;
}
return ( return (
<div className={styles.app}> <div className={styles.app}>
<AppBoundary> <AppBoundary>

View file

@ -1,7 +1,6 @@
import { emailRegEx } from '@logto/core-kit'; import { emailRegEx } from '@logto/core-kit';
import { useLogto } from '@logto/react'; import { useLogto } from '@logto/react';
import { TenantRole, Theme } from '@logto/schemas'; import { TenantRole, Theme } from '@logto/schemas';
import { joinPath } from '@silverhand/essentials';
import { useCallback, useContext } from 'react'; import { useCallback, useContext } from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form'; import { Controller, FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
@ -11,9 +10,11 @@ import CreateTenantHeaderIconDark from '@/assets/icons/create-tenant-header-dark
import CreateTenantHeaderIcon from '@/assets/icons/create-tenant-header.svg?react'; import CreateTenantHeaderIcon from '@/assets/icons/create-tenant-header.svg?react';
import { createTenantApi, useCloudApi } from '@/cloud/hooks/use-cloud-api'; import { createTenantApi, useCloudApi } from '@/cloud/hooks/use-cloud-api';
import ActionBar from '@/components/ActionBar'; import ActionBar from '@/components/ActionBar';
import { GtagConversionId, reportConversion } from '@/components/Conversion/utils';
import { type CreateTenantData } from '@/components/CreateTenantModal/types'; import { type CreateTenantData } from '@/components/CreateTenantModal/types';
import PageMeta from '@/components/PageMeta'; import PageMeta from '@/components/PageMeta';
import Region, { RegionName } from '@/components/Region'; import Region, { RegionName } from '@/components/Region';
import { availableRegions } from '@/consts';
import { TenantsContext } from '@/contexts/TenantsProvider'; import { TenantsContext } from '@/contexts/TenantsProvider';
import Button from '@/ds-components/Button'; import Button from '@/ds-components/Button';
import DangerousRaw from '@/ds-components/DangerousRaw'; import DangerousRaw from '@/ds-components/DangerousRaw';
@ -21,10 +22,10 @@ import FormField from '@/ds-components/FormField';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar'; import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import RadioGroup, { Radio } from '@/ds-components/RadioGroup'; import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
import TextInput from '@/ds-components/TextInput'; import TextInput from '@/ds-components/TextInput';
import useTenantPathname from '@/hooks/use-tenant-pathname'; import useCurrentUser from '@/hooks/use-current-user';
import useTheme from '@/hooks/use-theme'; import useTheme from '@/hooks/use-theme';
import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
import pageLayout from '@/onboarding/scss/layout.module.scss'; import pageLayout from '@/onboarding/scss/layout.module.scss';
import { OnboardingPage, OnboardingRoute } from '@/onboarding/types';
import InviteEmailsInput from '@/pages/TenantSettings/TenantMembers/InviteEmailsInput'; import InviteEmailsInput from '@/pages/TenantSettings/TenantMembers/InviteEmailsInput';
import { type InviteeEmailItem } from '@/pages/TenantSettings/TenantMembers/types'; import { type InviteeEmailItem } from '@/pages/TenantSettings/TenantMembers/types';
import { trySubmitSafe } from '@/utils/form'; import { trySubmitSafe } from '@/utils/form';
@ -33,7 +34,7 @@ type CreateTenantForm = Omit<CreateTenantData, 'tag'> & { collaboratorEmails: In
function CreateTenant() { function CreateTenant() {
const methods = useForm<CreateTenantForm>({ const methods = useForm<CreateTenantForm>({
defaultValues: { regionName: RegionName.EU, collaboratorEmails: [] }, defaultValues: { name: 'My project', regionName: RegionName.EU, collaboratorEmails: [] },
}); });
const { const {
control, control,
@ -41,10 +42,10 @@ function CreateTenant() {
formState: { errors, isSubmitting }, formState: { errors, isSubmitting },
register, register,
} = methods; } = methods;
const { navigate } = useTenantPathname();
const { prependTenant } = useContext(TenantsContext); const { prependTenant } = useContext(TenantsContext);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { update } = useUserOnboardingData();
const parseEmailOptions = useCallback( const parseEmailOptions = useCallback(
(values: InviteeEmailItem[]) => { (values: InviteeEmailItem[]) => {
const validEmails = values.filter(({ value }) => emailRegEx.test(value)); const validEmails = values.filter(({ value }) => emailRegEx.test(value));
@ -62,9 +63,15 @@ function CreateTenant() {
const { isAuthenticated, getOrganizationToken } = useLogto(); const { isAuthenticated, getOrganizationToken } = useLogto();
const cloudApi = useCloudApi(); const cloudApi = useCloudApi();
const { user } = useCurrentUser();
const onCreateClick = handleSubmit( const onCreateClick = handleSubmit(
trySubmitSafe(async ({ name, regionName, collaboratorEmails }: CreateTenantForm) => { trySubmitSafe(async ({ name, regionName, collaboratorEmails }: CreateTenantForm) => {
reportConversion({
gtagId: GtagConversionId.SignUp,
redditType: 'SignUp',
transactionId: user?.id,
});
const newTenant = await cloudApi.post('/api/tenants', { const newTenant = await cloudApi.post('/api/tenants', {
body: { name: name || 'My project', regionName }, body: { name: name || 'My project', regionName },
}); });
@ -93,7 +100,7 @@ function CreateTenant() {
toast.error(t('tenants.create_modal.invitation_failed', { duration: 5 })); toast.error(t('tenants.create_modal.invitation_failed', { duration: 5 }));
} }
} }
navigate(joinPath(OnboardingRoute.Onboarding, newTenant.id, OnboardingPage.SignInExperience)); await update({ isOnboardingDone: true });
}) })
); );
@ -126,8 +133,7 @@ function CreateTenant() {
rules={{ required: true }} rules={{ required: true }}
render={({ field: { onChange, value, name } }) => ( render={({ field: { onChange, value, name } }) => (
<RadioGroup type="small" name={name} value={value} onChange={onChange}> <RadioGroup type="small" name={name} value={value} onChange={onChange}>
{/* Manually maintaining the list of regions to avoid unexpected changes. We may consider using an API in the future. */} {availableRegions.map((region) => (
{[RegionName.EU, RegionName.US].map((region) => (
<Radio <Radio
key={region} key={region}
title={ title={
@ -167,7 +173,7 @@ function CreateTenant() {
</FormProvider> </FormProvider>
</div> </div>
</OverlayScrollbar> </OverlayScrollbar>
<ActionBar step={2} totalSteps={3}> <ActionBar>
<Button <Button
title="general.create" title="general.create"
type="primary" type="primary"

View file

@ -1,45 +0,0 @@
@use '@/scss/underscore' as _;
.inspire {
margin-top: _.unit(3);
display: flex;
padding: _.unit(4) _.unit(5);
border-radius: 12px;
background-color: var(--color-base);
align-items: center;
justify-content: space-between;
.inspireContent {
margin-inline-end: _.unit(6);
display: flex;
flex-direction: column;
.inspireTitle {
font: var(--font-title-2);
margin-bottom: _.unit(1);
}
.inspireDescription {
font: var(--font-body-2);
}
}
.button {
border-color: var(--color-neutral-variant-80);
// Note: this is a special case for the inspire me button since the bulb icon does not follow the standard icon size
padding-inline-end: _.unit(7);
&:not(:disabled) {
&:not(:active),
&:active {
&:hover {
background: var(--color-layer-1) center / 90% no-repeat url('../../../assets/images/fireworks.svg');
}
}
&:active:hover {
background-color: transparent;
}
}
}
}

View file

@ -1,63 +0,0 @@
import { ConnectorType } from '@logto/connector-kit';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import Bulb from '@/assets/icons/bulb.svg?react';
import LightBulb from '@/assets/icons/light-bulb.svg?react';
import Button from '@/ds-components/Button';
import useConnectorGroups from '@/hooks/use-connector-groups';
import { randomSieFormDataTemplate } from '../sie-config-templates';
import { type OnboardingSieFormData } from '../types';
import styles from './index.module.scss';
type Props = {
readonly onInspired: (template: OnboardingSieFormData) => void;
};
function InspireMe({ onInspired }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [isButtonHover, setIsButtonHover] = useState(false);
const BulbIcon = isButtonHover ? LightBulb : Bulb;
const [lastTemplateIndex, setLastTemplateIndex] = useState<number>();
const { data: connectorData, error } = useConnectorGroups();
const availableSocialTargets = error
? []
: connectorData
?.filter(({ type }) => type === ConnectorType.Social)
.map(({ target }) => target) ?? [];
const handleInspire = () => {
const { template, templateIndex } = randomSieFormDataTemplate(
lastTemplateIndex,
availableSocialTargets
);
setLastTemplateIndex(templateIndex);
onInspired(template);
};
return (
<div className={styles.inspire}>
<div className={styles.inspireContent}>
<div className={styles.inspireTitle}>{t('cloud.sie.inspire.title')}</div>
<div className={styles.inspireDescription}>{t('cloud.sie.inspire.description')}</div>
</div>
<Button
icon={<BulbIcon />}
size="large"
className={styles.button}
title="cloud.sie.inspire.inspire_me"
onMouseEnter={() => {
setIsButtonHover(true);
}}
onMouseLeave={() => {
setIsButtonHover(false);
}}
onClick={handleInspire}
/>
</div>
);
}
export default InspireMe;

View file

@ -1,34 +0,0 @@
@use '@/scss/underscore' as _;
.tab {
display: flex;
align-items: center;
border-radius: 6px;
font: var(--font-label-2);
padding: _.unit(1) _.unit(2);
user-select: none;
cursor: pointer;
.icon {
color: var(--color-primary);
margin-inline-end: _.unit(2);
> svg {
display: block;
}
}
&.selected {
color: var(--color-layer-1);
background-color: var(--color-inverse-primary);
.icon {
color: var(--color-static-white);
opacity: 70%;
}
}
&:not(.selected):hover {
background-color: var(--color-hover-variant);
}
}

View file

@ -1,38 +0,0 @@
import type { AdminConsoleKey } from '@logto/phrases';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import type { PreviewPlatform } from '@/components/SignInExperiencePreview/types';
import DynamicT from '@/ds-components/DynamicT';
import { onKeyDownHandler } from '@/utils/a11y';
import styles from './PlatformTab.module.scss';
type Props = {
readonly isSelected: boolean;
readonly icon: ReactNode;
readonly title: AdminConsoleKey;
readonly tab: PreviewPlatform;
readonly onClick: (tab: PreviewPlatform) => void;
};
function PlatformTab({ isSelected, icon, title, tab, onClick }: Props) {
return (
<div
role="tab"
tabIndex={0}
className={classNames(styles.tab, isSelected && styles.selected)}
onClick={() => {
onClick(tab);
}}
onKeyDown={onKeyDownHandler(() => {
onClick(tab);
})}
>
<span className={styles.icon}>{icon}</span>
<DynamicT forKey={title} />
</div>
);
}
export default PlatformTab;

View file

@ -1,12 +0,0 @@
@use '@/scss/underscore' as _;
.container {
border-radius: 8px;
background-color: var(--color-layer-1);
display: flex;
align-items: center;
padding: _.unit(1);
gap: 12px;
border: 1px solid var(--color-surface-5);
}

View file

@ -1,34 +0,0 @@
import Native from '@/assets/icons/connector-platform-icon-native.svg?react';
import Web from '@/assets/icons/connector-platform-icon-web.svg?react';
import { PreviewPlatform } from '@/components/SignInExperiencePreview/types';
import PlatformTab from './PlatformTab';
import styles from './index.module.scss';
type Props = {
readonly currentTab: PreviewPlatform;
readonly onSelect: (tab: PreviewPlatform) => void;
};
function PlatformTabs({ currentTab, onSelect }: Props) {
return (
<div className={styles.container}>
<PlatformTab
icon={<Web />}
title="cloud.sie.preview.web_tab"
tab={PreviewPlatform.DesktopWeb}
isSelected={currentTab === PreviewPlatform.DesktopWeb}
onClick={onSelect}
/>
<PlatformTab
icon={<Native />}
title="cloud.sie.preview.mobile_tab"
tab={PreviewPlatform.MobileWeb}
isSelected={currentTab === PreviewPlatform.MobileWeb}
onClick={onSelect}
/>
</div>
);
}
export default PlatformTabs;

View file

@ -1,10 +0,0 @@
@use '@/scss/underscore' as _;
.container {
border-radius: 16px;
background-color: var(--color-neutral-variant-90);
padding: _.unit(6);
display: flex;
flex-direction: column;
align-items: center;
}

View file

@ -1,38 +0,0 @@
import type { SignInExperience } from '@logto/schemas';
import { Theme } from '@logto/schemas';
import classNames from 'classnames';
import { useState } from 'react';
import SignInExperiencePreview from '@/components/SignInExperiencePreview';
import { PreviewPlatform } from '@/components/SignInExperiencePreview/types';
import PlatformTabs from './PlatformTabs';
import styles from './index.module.scss';
type Props = {
readonly signInExperience?: SignInExperience;
readonly className?: string;
/**
* The Logto endpoint to use for the preview. If not provided, the current tenant endpoint from
* the `AppDataContext` will be used.
*/
readonly endpoint?: URL;
};
function Preview({ signInExperience, className, endpoint }: Props) {
const [currentTab, setCurrentTab] = useState(PreviewPlatform.DesktopWeb);
return (
<div className={classNames(styles.container, className)}>
<PlatformTabs currentTab={currentTab} onSelect={setCurrentTab} />
<SignInExperiencePreview
platform={currentTab}
mode={Theme.Light}
signInExperience={signInExperience}
endpoint={endpoint}
/>
</div>
);
}
export default Preview;

View file

@ -1,107 +0,0 @@
@use '@/scss/underscore' as _;
.fieldWrapper {
padding: _.unit(2);
>:not(:first-child) {
margin-top: _.unit(6);
}
.title {
width: 80px;
height: 16px;
@include _.shimmering-animation;
}
.field {
width: 100%;
height: 44px;
@include _.shimmering-animation;
}
&:not(:first-child) {
margin-top: _.unit(3);
}
}
.preview {
background: var(--color-surface-variant);
padding: _.unit(6);
border-radius: 12px;
overflow: hidden;
.header {
padding: _.unit(2);
margin-bottom: _.unit(6);
display: flex;
justify-content: space-between;
.actions {
display: flex;
gap: _.unit(3);
}
.smallButton {
width: 30px;
height: 30px;
margin-inline-start: _.unit(2.5);
@include _.shimmering-animation;
}
.button {
width: 104px;
height: 30px;
margin-inline-start: _.unit(2.5);
@include _.shimmering-animation;
}
}
.mobile {
display: flex;
flex-direction: column;
align-items: center;
width: 375px;
height: 667px;
background: var(--color-surface);
margin: 0 auto;
padding: _.unit(6);
border-radius: 16px;
transform: scale(0.6);
transform-origin: top;
.logo {
width: 64px;
height: 64px;
margin-top: _.unit(16);
@include _.shimmering-animation;
}
.slogan {
width: 177px;
height: 16px;
margin: _.unit(3) 0 _.unit(10);
@include _.shimmering-animation;
}
.field {
width: 100%;
height: 36px;
margin-top: _.unit(3);
@include _.shimmering-animation;
}
.button {
width: 100%;
height: 36px;
margin-top: _.unit(10);
@include _.shimmering-animation;
}
.social {
width: 180px;
height: 24px;
margin-top: _.unit(3);
@include _.shimmering-animation;
}
}
}

View file

@ -1,45 +0,0 @@
import pageLayout from '@/onboarding/scss/layout.module.scss';
import sieLayout from '../index.module.scss';
import styles from './index.module.scss';
function Skeleton() {
return (
<div className={pageLayout.page}>
<div className={pageLayout.contentContainer}>
<div className={sieLayout.content}>
<div className={sieLayout.config}>
{Array.from({ length: 3 }).map((_, index) => (
// eslint-disable-next-line react/no-array-index-key
<div key={index} className={styles.fieldWrapper}>
<div className={styles.title} />
<div className={styles.field} />
<div className={styles.field} />
</div>
))}
</div>
<div className={styles.preview}>
<div className={styles.header}>
<div className={styles.button} />
<div className={styles.actions}>
<div className={styles.smallButton} />
<div className={styles.button} />
</div>
</div>
<div className={styles.mobile}>
<div className={styles.logo} />
<div className={styles.slogan} />
<div className={styles.field} />
<div className={styles.field} />
<div className={styles.button} />
<div className={styles.social} />
</div>
</div>
</div>
</div>
</div>
);
}
export default Skeleton;

View file

@ -1,48 +0,0 @@
import { ConnectorType } from '@logto/schemas';
import ConnectorLogo from '@/components/ConnectorLogo';
import UnnamedTrans from '@/components/UnnamedTrans';
import DangerousRaw from '@/ds-components/DangerousRaw';
import useConnectorGroups from '@/hooks/use-connector-groups';
import { MultiCardSelector } from '@/onboarding/components/CardSelector';
import type { MultiCardSelectorOption } from '@/onboarding/components/CardSelector';
import { fakeSocialTargetOptions } from '../options';
type Props = {
readonly value: string[];
readonly onChange: (value: string[]) => void;
};
function SocialSelector({ value, onChange }: Props) {
const { data: connectorData, error } = useConnectorGroups();
if (!connectorData || error) {
return null;
}
const connectorOptions: MultiCardSelectorOption[] = connectorData
.filter(({ type }) => type === ConnectorType.Social)
.map((item) => {
return {
icon: <ConnectorLogo size="small" data={item} />,
title: (
<DangerousRaw>
<UnnamedTrans resource={item.name} />
</DangerousRaw>
),
value: item.target,
tag: 'general.demo',
};
});
return (
<MultiCardSelector
options={[...connectorOptions, ...fakeSocialTargetOptions]}
value={value}
onChange={onChange}
/>
);
}
export default SocialSelector;

View file

@ -1,112 +0,0 @@
import type { SignInExperience } from '@logto/schemas';
import { SignInIdentifier } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import { Authentication, type UpdateOnboardingSieData, type OnboardingSieFormData } from './types';
const fromSignInExperience = (signInExperience: SignInExperience): OnboardingSieFormData => {
const {
color: { primaryColor },
branding: { logoUrl: logo },
signIn: { methods: signInMethods },
signUp: { identifiers: signUpIdentifiers },
socialSignInConnectorTargets,
} = signInExperience;
const identifier =
signInMethods.find(({ identifier }) => signUpIdentifiers.includes(identifier))?.identifier ??
SignInIdentifier.Username;
const authentications = signInMethods.reduce<Authentication[]>((result, method) => {
const { password, verificationCode } = method;
return [
...result,
...(password ? [Authentication.Password] : []),
...(verificationCode ? [Authentication.VerificationCode] : []),
];
}, []);
return {
logo,
color: primaryColor,
identifier,
authentications,
socialTargets: socialSignInConnectorTargets,
};
};
const toSignInExperience = (
formData: OnboardingSieFormData,
initialSignInExperience: SignInExperience
): SignInExperience => {
const { logo, color: onboardConfigColor, identifier, authentications, socialTargets } = formData;
const { color: initialColor, branding: initialBranding } = initialSignInExperience;
// Map to sign-up config
const shouldSetPasswordAtSignUp =
identifier === SignInIdentifier.Username || authentications.includes(Authentication.Password);
const shouldVerifyAtSignUp = identifier !== SignInIdentifier.Username;
// Map to sign-in methods
const isSignInByPasswordEnabled =
identifier === SignInIdentifier.Username || authentications.includes(Authentication.Password);
const isSignInByVerificationCodeEnabled =
identifier !== SignInIdentifier.Username &&
authentications.includes(Authentication.VerificationCode);
const signInExperience: SignInExperience = {
...initialSignInExperience,
branding: {
...initialBranding,
logoUrl: conditional(logo?.length && logo),
darkLogoUrl: conditional(logo?.length && logo),
},
color: {
...initialColor,
primaryColor: onboardConfigColor,
},
signUp: {
identifiers: [identifier],
verify: shouldVerifyAtSignUp,
password: shouldSetPasswordAtSignUp,
},
signIn: {
methods: [
{
identifier,
password: isSignInByPasswordEnabled,
verificationCode: isSignInByVerificationCodeEnabled,
isPasswordPrimary: isSignInByPasswordEnabled,
},
],
},
socialSignInConnectorTargets: socialTargets ?? [],
};
return signInExperience;
};
const toUpdateOnboardingSieData = (
formData: OnboardingSieFormData,
initialSignInExperience: SignInExperience
): UpdateOnboardingSieData => {
const { color, branding, signUp, signIn, socialSignInConnectorTargets } = toSignInExperience(
formData,
initialSignInExperience
);
return {
color,
branding,
signUp,
signIn,
socialSignInConnectorTargets,
};
};
export const formDataParser = {
fromSignInExperience,
toSignInExperience,
toUpdateOnboardingSieData,
};

View file

@ -1,38 +0,0 @@
@use '@/scss/underscore' as _;
.content {
display: flex;
justify-content: center;
min-width: min-content;
padding: 0 _.unit(17);
> div {
max-width: 800px;
min-width: 540px;
flex: 1;
}
.config {
background-color: var(--color-layer-1);
border-radius: 8px;
padding: _.unit(12);
margin-inline-end: _.unit(6);
.authnSelector {
grid-template-columns: repeat(2, 1fr);
}
}
.preview {
position: sticky;
top: 0;
align-self: flex-start;
}
}
.continueActions {
display: flex;
align-items: center;
justify-content: space-between;
gap: _.unit(4);
}

View file

@ -1,279 +0,0 @@
import { ConnectorType, ServiceConnector } from '@logto/connector-kit';
import { SignInIdentifier } from '@logto/schemas';
import type { SignInExperience as SignInExperienceType, ConnectorResponse } from '@logto/schemas';
import { useCallback, useEffect, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import useSWR, { SWRConfig } from 'swr';
import Tools from '@/assets/icons/tools.svg?react';
import ActionBar from '@/components/ActionBar';
import { GtagConversionId, reportConversion } from '@/components/Conversion/utils';
import PageMeta from '@/components/PageMeta';
import { useTenantEndpoint } from '@/contexts/AppDataProvider';
import Button from '@/ds-components/Button';
import ColorPicker from '@/ds-components/ColorPicker';
import FormField from '@/ds-components/FormField';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import TextInput from '@/ds-components/TextInput';
import ImageUploaderField from '@/ds-components/Uploader/ImageUploaderField';
import type { RequestError } from '@/hooks/use-api';
import useCurrentUser from '@/hooks/use-current-user';
import useUserAssetsService from '@/hooks/use-user-assets-service';
import { CardSelector, MultiCardSelector } from '@/onboarding/components/CardSelector';
import useTenantApi from '@/onboarding/hooks/use-tenant-api';
import useTenantSwrOptions from '@/onboarding/hooks/use-tenant-swr-options';
import useTenantUserAssetsService from '@/onboarding/hooks/use-tenant-user-asset-service';
import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
import pageLayout from '@/onboarding/scss/layout.module.scss';
import { trySubmitSafe } from '@/utils/form';
import { buildUrl } from '@/utils/url';
import { uriValidator } from '@/utils/validator';
import InspireMe from './InspireMe';
import Preview from './Preview';
import Skeleton from './Skeleton';
import SocialSelector from './SocialSelector';
import { formDataParser } from './form-data-parser';
import styles from './index.module.scss';
import { authenticationOptions, identifierOptions } from './options';
import { defaultOnboardingSieFormData } from './sie-config-templates';
import { Authentication, type OnboardingSieFormData } from './types';
const useCurrentTenantEndpoint = () => {
const { tenantId: currentTenantId } = useParams();
if (!currentTenantId) {
throw new Error(
'No tenant ID param found in the current route. This hook should be used in a route with a tenant ID param.'
);
}
const { data } = useTenantEndpoint(currentTenantId);
return data;
};
function SignInExperience() {
const swrOptions = useTenantSwrOptions();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
data: signInExperience,
error,
mutate,
} = useSWR<SignInExperienceType, RequestError>('api/sign-in-exp', swrOptions);
const isSignInExperienceDataLoading = !error && !signInExperience;
const { isLoading: isUserAssetsServiceLoading } = useTenantUserAssetsService();
const isLoading = isSignInExperienceDataLoading || isUserAssetsServiceLoading;
const api = useTenantApi();
const { isReady: isUserAssetsServiceReady } = useUserAssetsService();
const { update } = useUserOnboardingData();
const { user } = useCurrentUser();
const endpoint = useCurrentTenantEndpoint();
const enterAdminConsole = async () => {
await update({ isOnboardingDone: true });
};
const {
reset,
control,
watch,
register,
handleSubmit,
getValues,
setValue,
formState: { isSubmitting, errors },
} = useForm<OnboardingSieFormData>({ defaultValues: defaultOnboardingSieFormData });
const updateAuthenticationConfig = useCallback(() => {
const identifier = getValues('identifier');
if (identifier === SignInIdentifier.Username) {
setValue('authentications', [Authentication.Password]);
}
}, [getValues, setValue]);
useEffect(() => {
if (signInExperience) {
reset(formDataParser.fromSignInExperience(signInExperience));
updateAuthenticationConfig();
}
}, [reset, signInExperience, updateAuthenticationConfig]);
const onboardingSieFormData = watch();
const previewSieConfig = useMemo(() => {
if (signInExperience) {
return formDataParser.toSignInExperience(onboardingSieFormData, signInExperience);
}
}, [onboardingSieFormData, signInExperience]);
const submit = (onSuccess: () => void) =>
trySubmitSafe(async (formData: OnboardingSieFormData) => {
if (!signInExperience) {
return;
}
/**
* If choose `Email` as `identifier`, we will create email service connector for the tenant (when there is no existing email connector).
* Should create this connector before updating the sign-in experience, otherwise the sign-in experience can not be updated.
*/
if (formData.identifier === SignInIdentifier.Email) {
const connectors = await api.get('api/connectors').json<ConnectorResponse[]>();
if (!connectors.some(({ type }) => type === ConnectorType.Email)) {
await api.post('api/connectors', {
json: {
connectorId: ServiceConnector.Email,
},
});
}
}
const updatedData = await api
.patch(buildUrl('api/sign-in-exp', { removeUnusedDemoSocialConnector: '1' }), {
json: formDataParser.toUpdateOnboardingSieData(formData, signInExperience),
})
.json<SignInExperienceType>();
reportConversion({
gtagId: GtagConversionId.SignUp,
redditType: 'SignUp',
transactionId: user?.id,
});
void mutate(updatedData);
onSuccess();
});
if (isLoading) {
return <Skeleton />;
}
return (
<SWRConfig value={swrOptions}>
<div className={pageLayout.page}>
<PageMeta titleKey={['cloud.sie.page_title', 'cloud.general.onboarding']} />
<OverlayScrollbar className={pageLayout.contentContainer}>
<div className={styles.content}>
<div className={styles.config}>
<Tools />
<div className={pageLayout.title}>{t('cloud.sie.title')}</div>
<InspireMe
onInspired={(template) => {
for (const [key, value] of Object.entries(template)) {
// eslint-disable-next-line no-restricted-syntax
setValue(key as keyof OnboardingSieFormData, value, { shouldDirty: true });
}
updateAuthenticationConfig();
}}
/>
<FormField title="cloud.sie.logo_field">
{isUserAssetsServiceReady ? (
<Controller
name="logo"
control={control}
render={({ field: { onChange, value, name } }) => (
<ImageUploaderField
apiInstance={api}
name={name}
value={value ?? ''}
onChange={onChange}
/>
)}
/>
) : (
<TextInput
{...register('logo', {
validate: (value) =>
!value || uriValidator(value) || t('errors.invalid_uri_format'),
})}
error={errors.logo?.message}
/>
)}
</FormField>
<FormField title="cloud.sie.color_field">
<Controller
name="color"
control={control}
render={({ field: { onChange, value } }) => (
<ColorPicker value={value} onChange={onChange} />
)}
/>
</FormField>
<FormField title="cloud.sie.identifier_field" headlineSpacing="large">
<Controller
name="identifier"
control={control}
render={({ field: { name, value, onChange } }) => (
<CardSelector
name={name}
value={value}
options={identifierOptions}
onChange={(value) => {
onChange(value);
updateAuthenticationConfig();
}}
/>
)}
/>
</FormField>
<FormField isMultiple title="cloud.sie.authn_field" headlineSpacing="large">
<Controller
name="authentications"
control={control}
defaultValue={defaultOnboardingSieFormData.authentications}
render={({ field: { value, onChange } }) => (
<MultiCardSelector
isNotAllowEmpty
className={styles.authnSelector}
value={value}
options={authenticationOptions.filter(
({ value }) =>
onboardingSieFormData.identifier !== SignInIdentifier.Username ||
value === Authentication.Password
)}
onChange={onChange}
/>
)}
/>
</FormField>
<FormField isMultiple title="cloud.sie.social_field" headlineSpacing="large">
<Controller
name="socialTargets"
control={control}
defaultValue={defaultOnboardingSieFormData.socialTargets}
render={({ field: { value, onChange } }) => (
<SocialSelector value={value ?? []} onChange={onChange} />
)}
/>
</FormField>
</div>
{endpoint && (
<Preview
className={styles.preview}
signInExperience={previewSieConfig}
endpoint={endpoint}
/>
)}
</div>
</OverlayScrollbar>
<ActionBar step={3} totalSteps={3}>
<div className={styles.continueActions}>
<Button
type="primary"
title="cloud.sie.finish_and_done"
disabled={isSubmitting}
onClick={async () => {
await handleSubmit(submit(enterAdminConsole))();
}}
/>
</div>
</ActionBar>
</div>
</SWRConfig>
);
}
export default SignInExperience;

View file

@ -1,88 +0,0 @@
import { SignInIdentifier } from '@logto/schemas';
import Envelop from '@/assets/icons/envelop.svg?react';
import Keyboard from '@/assets/icons/keyboard.svg?react';
import Label from '@/assets/icons/label.svg?react';
import Lock from '@/assets/icons/lock.svg?react';
import DangerousRaw from '@/ds-components/DangerousRaw';
import type {
MultiCardSelectorOption,
CardSelectorOption,
} from '@/onboarding/components/CardSelector';
import Apple from '../../assets/icons/social-apple.svg?react';
import Facebook from '../../assets/icons/social-facebook.svg?react';
import Kakao from '../../assets/icons/social-kakao.svg?react';
import Microsoft from '../../assets/icons/social-microsoft.svg?react';
import Oidc from '../../assets/icons/social-oidc.svg?react';
import { Authentication } from './types';
export const identifierOptions: CardSelectorOption[] = [
{
icon: <Envelop />,
title: 'sign_in_exp.sign_up_and_sign_in.identifiers_email',
value: SignInIdentifier.Email,
},
{
icon: <Label />,
title: 'sign_in_exp.sign_up_and_sign_in.identifiers_username',
value: SignInIdentifier.Username,
},
];
export const authenticationOptions: MultiCardSelectorOption[] = [
{
icon: <Lock />,
title: 'sign_in_exp.sign_up_and_sign_in.sign_in.password_auth',
value: Authentication.Password,
},
{
icon: <Keyboard />,
title: 'sign_in_exp.sign_up_and_sign_in.sign_in.verification_code_auth',
value: Authentication.VerificationCode,
},
];
export const fakeSocialTargetOptions: MultiCardSelectorOption[] = [
{
icon: <Apple />,
title: <DangerousRaw>Apple</DangerousRaw>,
value: 'fake-apple',
tag: 'cloud.sie.connectors.unlocked_later',
isDisabled: true,
disabledTip: 'cloud.sie.connectors.unlocked_later_tip',
},
{
icon: <Facebook />,
title: <DangerousRaw>Facebook</DangerousRaw>,
value: 'fake-facebook',
tag: 'cloud.sie.connectors.unlocked_later',
isDisabled: true,
disabledTip: 'cloud.sie.connectors.unlocked_later_tip',
},
{
icon: <Microsoft />,
title: <DangerousRaw>Microsoft</DangerousRaw>,
value: 'fake-microsoft',
tag: 'cloud.sie.connectors.unlocked_later',
isDisabled: true,
disabledTip: 'cloud.sie.connectors.unlocked_later_tip',
},
{
icon: <Kakao />,
title: <DangerousRaw>Kakao</DangerousRaw>,
value: 'fake-kakao',
tag: 'cloud.sie.connectors.unlocked_later',
isDisabled: true,
disabledTip: 'cloud.sie.connectors.unlocked_later_tip',
},
{
icon: <Oidc />,
title: <DangerousRaw>OIDC</DangerousRaw>,
value: 'fake-oidc',
tag: 'cloud.sie.connectors.unlocked_later',
isDisabled: true,
disabledTip: 'cloud.sie.connectors.unlocked_later_tip',
},
];

View file

@ -1,80 +0,0 @@
import { SignInIdentifier } from '@logto/schemas';
import type { OnboardingSieFormData } from './types';
import { Authentication } from './types';
const assetsUrl =
'https://logtodev.blob.core.windows.net/public-blobs/admin/BY4BCq8GvfBF/2023/03/10';
export const defaultOnboardingSieFormData: OnboardingSieFormData = {
color: '#5D34F2',
identifier: SignInIdentifier.Email,
authentications: [Authentication.Password],
};
// Email + password sign-up; password sign-in
const formDataTemplate1: OnboardingSieFormData = {
logo: `${assetsUrl}/tVCAHjAB/logo1.png`,
color: '#19BEFD',
identifier: SignInIdentifier.Email,
authentications: [Authentication.Password],
};
// Email + password sign-up; password + code sign-in
const formDataTemplate2: OnboardingSieFormData = {
logo: `${assetsUrl}/IcI0snBP/logo3.png`,
color: '#FF5449',
identifier: SignInIdentifier.Email,
authentications: [Authentication.Password, Authentication.VerificationCode],
};
// Email + code sign-up; code sign-in
const formDataTemplate3: OnboardingSieFormData = {
logo: `${assetsUrl}/7UQyvuFc/logo4.png`,
color: '#CA4E96',
identifier: SignInIdentifier.Email,
authentications: [Authentication.VerificationCode],
};
// Username sign-up; password sign-in
const formDataTemplate4: OnboardingSieFormData = {
logo: `${assetsUrl}/uLoMzrlz/logo7.png`,
color: '#FF5449',
identifier: SignInIdentifier.Username,
authentications: [Authentication.Password],
};
const formDataTemplates: OnboardingSieFormData[] = [
formDataTemplate1,
formDataTemplate2,
formDataTemplate3,
formDataTemplate4,
];
export const randomSieFormDataTemplate = (
lastTemplateIndex: number | undefined,
availableSocialTargets: string[]
) => {
// Get random template
const randomIndex = Math.floor(Math.random() * formDataTemplates.length);
const index =
randomIndex === lastTemplateIndex ? (randomIndex + 1) % formDataTemplates.length : randomIndex;
const template = formDataTemplates[index] ?? formDataTemplate1;
// Get 2 or 3 random social targets
const randomCount = Math.floor(Math.random() * 2) + 2;
// Take the first randomCount after shuffling.
const socialTargets = availableSocialTargets
.slice()
.sort(() => 0.5 - Math.random())
.slice(0, randomCount);
return {
template: {
...template,
socialTargets,
},
templateIndex: index,
};
};

View file

@ -1,19 +0,0 @@
import { type SignInExperience, type SignInIdentifier } from '@logto/schemas';
export enum Authentication {
Password = 'password',
VerificationCode = 'verificationCode',
}
export type OnboardingSieFormData = {
logo?: string;
color: string;
identifier: SignInIdentifier;
authentications: Authentication[];
socialTargets?: string[];
};
export type UpdateOnboardingSieData = Pick<
SignInExperience,
'branding' | 'color' | 'signUp' | 'signIn' | 'socialSignInConnectorTargets'
>;

View file

@ -1,15 +0,0 @@
@use '@/scss/underscore' as _;
.form {
width: 100%;
margin-top: _.unit(6);
.titleSelector {
grid-template-columns: repeat(6, 1fr);
align-items: center;
}
.option {
min-height: 100px;
}
}

View file

@ -1,132 +0,0 @@
import { type Questionnaire, Project } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import { useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import Case from '@/assets/icons/case.svg?react';
import ActionBar from '@/components/ActionBar';
import PageMeta from '@/components/PageMeta';
import Button from '@/ds-components/Button';
import FormField from '@/ds-components/FormField';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import TextInput from '@/ds-components/TextInput';
import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
import pageLayout from '@/onboarding/scss/layout.module.scss';
import { trySubmitSafe } from '@/utils/form';
import { CardSelector, MultiCardSelector } from '../../components/CardSelector';
import { OnboardingPage } from '../../types';
import { getOnboardingPage } from '../../utils';
import styles from './index.module.scss';
import { stageOptions, additionalFeaturesOptions, projectOptions } from './options';
function Welcome() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const navigate = useNavigate();
const {
data: { questionnaire },
update,
} = useUserOnboardingData();
const { control, register, handleSubmit, reset, watch } = useForm<Questionnaire>({
mode: 'onChange',
});
useEffect(() => {
reset(questionnaire);
}, [questionnaire, reset]);
const onSubmit = handleSubmit(
trySubmitSafe(async (formData) => {
await update({ questionnaire: formData });
})
);
const onNext = async () => {
await onSubmit();
navigate(getOnboardingPage(OnboardingPage.CreateTenant));
};
return (
<div className={pageLayout.page}>
<PageMeta titleKey={['cloud.welcome.page_title', 'cloud.general.onboarding']} />
<OverlayScrollbar className={pageLayout.contentContainer}>
<div className={pageLayout.content}>
<Case />
<div className={pageLayout.title}>{t('cloud.welcome.title')}</div>
<div className={pageLayout.description}>{t('cloud.welcome.description')}</div>
<form className={styles.form}>
<FormField title="cloud.welcome.project_field" headlineSpacing="large">
<Controller
control={control}
name="project"
render={({ field: { onChange, value, name } }) => (
<CardSelector
name={name}
value={value ?? ''}
options={projectOptions}
onChange={onChange}
/>
)}
/>
</FormField>
{/* Check whether it is a business use case */}
{watch('project') === Project.Company && (
<FormField title="cloud.welcome.company_name_field">
<TextInput
placeholder={t('cloud.welcome.company_name_placeholder')}
{...register('companyName')}
/>
</FormField>
)}
<FormField title="cloud.welcome.stage_field" headlineSpacing="large">
<Controller
control={control}
name="stage"
render={({ field: { name, onChange, value } }) => (
<CardSelector
name={name}
value={value ?? ''}
options={stageOptions}
onChange={(value) => {
onChange(conditional(value && value));
}}
/>
)}
/>
</FormField>
<FormField
isMultiple
title="cloud.welcome.additional_features_field"
headlineSpacing="large"
>
<Controller
control={control}
name="additionalFeatures"
render={({ field: { onChange, value } }) => (
<MultiCardSelector
optionClassName={styles.option}
value={value ?? []}
options={additionalFeaturesOptions}
onChange={(value) => {
onChange(value.length === 0 ? undefined : value);
}}
/>
)}
/>
</FormField>
</form>
</div>
</OverlayScrollbar>
<ActionBar step={1} totalSteps={3}>
<Button title="general.next" type="primary" onClick={onNext} />
</ActionBar>
</div>
);
}
export default Welcome;

View file

@ -1,54 +0,0 @@
import { AdditionalFeatures, Project, Stage } from '@logto/schemas';
import Building from '@/assets/icons/building.svg?react';
import Pizza from '@/assets/icons/pizza.svg?react';
import type {
CardSelectorOption,
MultiCardSelectorOption,
} from '@/onboarding/components/CardSelector';
export const projectOptions: CardSelectorOption[] = [
{
icon: <Pizza />,
title: 'cloud.welcome.project_options.personal',
value: Project.Personal,
},
{
icon: <Building />,
title: 'cloud.welcome.project_options.company',
value: Project.Company,
},
];
export const stageOptions: CardSelectorOption[] = [
{ title: 'cloud.welcome.stage_options.new_product', value: Stage.NewProduct },
{ title: 'cloud.welcome.stage_options.existing_product', value: Stage.ExistingProduct },
{
title: 'cloud.welcome.stage_options.target_enterprise_ready',
value: Stage.TargetEnterpriseReady,
},
];
export const additionalFeaturesOptions: MultiCardSelectorOption[] = [
{
title: 'cloud.welcome.additional_features_options.customize_ui_and_flow',
value: AdditionalFeatures.CustomizeUiAndFlow,
},
{
title: 'cloud.welcome.additional_features_options.compliance',
value: AdditionalFeatures.Compliance,
},
{
title: 'cloud.welcome.additional_features_options.export_user_data',
value: AdditionalFeatures.ExportUserDataFromLogto,
},
{
title: 'cloud.welcome.additional_features_options.budget_control',
value: AdditionalFeatures.BudgetControl,
},
{
title: 'cloud.welcome.additional_features_options.bring_own_auth',
value: AdditionalFeatures.BringOwnAuth,
},
{ title: 'cloud.welcome.additional_features_options.others', value: AdditionalFeatures.Others },
];

View file

@ -1,9 +1,3 @@
export enum OnboardingRoute {
Onboarding = 'onboarding',
}
export enum OnboardingPage { export enum OnboardingPage {
Welcome = 'welcome',
CreateTenant = 'create-tenant', CreateTenant = 'create-tenant',
SignInExperience = 'sign-in-experience',
} }

View file

@ -1,7 +0,0 @@
import { joinPath } from '@silverhand/essentials';
import type { OnboardingPage } from './types';
import { OnboardingRoute } from './types';
export const getOnboardingPage = (page: OnboardingPage) =>
joinPath(OnboardingRoute.Onboarding, page);

View file

@ -2,36 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'عملية التسجيل', onboarding: 'عملية التسجيل',
}, },
welcome: {
page_title: 'مرحبًا',
title: 'مرحبًا بك في سحابة Logto! نحب أن نتعرف عليك قليلاً.',
description: 'لنجعل تجربة Logto فريدة لك من خلال معرفة المزيد عنك. معلوماتك آمنة معنا.',
project_field: 'أستخدم Logto لـ',
project_options: {
personal: 'مشروع شخصي',
company: 'مشروع الشركة',
},
company_name_field: 'اسم الشركة',
company_name_placeholder: 'Acme.co',
stage_field: 'ما هو مرحلة منتجك حاليًا؟',
stage_options: {
new_product: 'بدء مشروع جديد والبحث عن حل سريع وجاهز',
existing_product:
'الانتقال من المصادقة الحالية (مثل البناء الذاتي، Auth0، Cognito، Microsoft)',
target_enterprise_ready:
'لقد حصلت للتو على عملاء أكبر والآن أرغب في جعل منتجي جاهزًا للبيع للشركات',
},
additional_features_field: 'هل لديك أي شيء آخر تود أن نعرفه؟',
additional_features_options: {
customize_ui_and_flow:
'بناء وإدارة واجهة المستخدم الخاصة بي، وليس فقط استخدام حل Logto الجاهز وقابل للتخصيص',
compliance: 'SOC2 و GDPR ضروريان',
export_user_data: 'تحتاج إلى القدرة على تصدير بيانات المستخدم من Logto',
budget_control: 'لدي مراقبة ميزانية صارمة جدًا',
bring_own_auth: 'لدي خدمات المصادقة الخاصة بي وأحتاج فقط إلى بعض ميزات Logto',
others: 'لا شيء من هذه الخيارات',
},
},
create_tenant: { create_tenant: {
page_title: 'إنشاء مستأجر', page_title: 'إنشاء مستأجر',
title: 'أنشئ أول مستأجر لك', title: 'أنشئ أول مستأجر لك',
@ -39,45 +9,13 @@ const cloud = {
'المستأجر هو بيئة معزولة حيث يمكنك إدارة هويات المستخدمين والتطبيقات وجميع الموارد الأخرى في Logto.', 'المستأجر هو بيئة معزولة حيث يمكنك إدارة هويات المستخدمين والتطبيقات وجميع الموارد الأخرى في Logto.',
invite_collaborators: 'ادعو مشاركيك عبر البريد الإلكتروني', invite_collaborators: 'ادعو مشاركيك عبر البريد الإلكتروني',
}, },
sie: { social_callback: {
page_title: 'تخصيص تجربة تسجيل الدخول',
title: 'لنقم بتخصيص تجربة تسجيل الدخول الخاصة بك بسهولة أولاً',
inspire: {
title: 'إنشاء أمثلة جذابة',
description: 'هل تشعر بالتردد حول تجربة تسجيل الدخول؟ فقط انقر على "ألهمني" ودع السحر يحدث!',
inspire_me: 'ألهمني',
},
logo_field: 'شعار التطبيق',
color_field: 'لون العلامة التجارية',
identifier_field: 'المعرف',
identifier_options: {
email: 'البريد الإلكتروني',
phone: 'الهاتف',
user_name: 'اسم المستخدم',
},
authn_field: 'المصادقة',
authn_options: {
password: 'كلمة المرور',
verification_code: 'رمز التحقق',
},
social_field: 'تسجيل الدخول الاجتماعي',
finish_and_done: 'انتهاء وتم',
preview: {
mobile_tab: 'الجوال',
web_tab: 'الويب',
},
connectors: {
unlocked_later: 'سيتم فتحه لاحقًا',
unlocked_later_tip:
'بمجرد الانتهاء من عملية التسجيل والدخول إلى المنتج، ستتمكن من الوصول إلى المزيد من طرق تسجيل الدخول الاجتماعي.',
notice:
'يرجى تجنب استخدام موصل العرض التوضيحي لأغراض الإنتاج. بمجرد الانتهاء من الاختبار، يرجى حذف موصل العرض التوضيحي وإعداد موصلك الخاص بك ببيانات الاعتماد الخاصة بك.',
},
},
socialCallback: {
title: 'لقد قمت بتسجيل الدخول بنجاح', title: 'لقد قمت بتسجيل الدخول بنجاح',
description: description:
'لقد قمت بتسجيل الدخول بنجاح باستخدام حسابك الاجتماعي. لضمان التكامل السلس والوصول إلى جميع ميزات Logto، نوصي بمتابعة تكوين موصلك الاجتماعي الخاص بك.', 'لقد قمت بتسجيل الدخول بنجاح باستخدام حسابك الاجتماعي. لضمان التكامل السلس والوصول إلى جميع ميزات Logto، نوصي بمتابعة تكوين موصلك الاجتماعي الخاص بك.',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'إنشاء مستأجر', create_tenant: 'إنشاء مستأجر',

View file

@ -2,38 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Einführung', onboarding: 'Einführung',
}, },
welcome: {
page_title: 'Willkommen',
title: 'Willkommen bei Logto Cloud! Wir möchten gerne ein bisschen mehr über Sie erfahren.',
description:
'Machen Sie Ihre Logto-Erfahrung einzigartig, indem Sie uns besser kennenlernen. Ihre Informationen sind bei uns sicher.',
project_field: 'Ich verwende Logto für',
project_options: {
personal: 'Persönliches Projekt',
company: 'Unternehmensprojekt',
},
company_name_field: 'Firmenname',
company_name_placeholder: 'Acme.co',
stage_field: 'In welchem Stadium befindet sich Ihr Produkt derzeit?',
stage_options: {
new_product: 'Starte ein neues Projekt und suche nach einer schnellen, Out-of-the-Box-Lösung',
existing_product:
'Migration von der derzeitigen Authentifizierung (z. B. selbst erstellt, Auth0, Cognito, Microsoft)',
target_enterprise_ready:
'Ich habe gerade größere Kunden gewonnen und möchte mein Produkt jetzt bereit machen, um es an Unternehmen zu verkaufen',
},
additional_features_field: 'Haben Sie noch etwas, das Sie uns wissen lassen möchten?',
additional_features_options: {
customize_ui_and_flow:
'Erstellen und verwalten Sie Ihre eigene Benutzeroberfläche und Ihren eigenen Ablauf, anstatt nur die vorgefertigte und anpassbare Lösung von Logto zu verwenden',
compliance: 'SOC2 und GDPR sind Pflicht',
export_user_data: 'Benötigen Sie die Möglichkeit, Benutzerdaten von Logto zu exportieren',
budget_control: 'Ich habe sehr strenge Budgetkontrolle',
bring_own_auth:
'Ich habe eigene Authentifizierungsdienste und benötige nur einige Logto-Funktionen',
others: 'Keines der oben genannten',
},
},
create_tenant: { create_tenant: {
page_title: 'Mandant erstellen', page_title: 'Mandant erstellen',
title: 'Erstellen Sie Ihren ersten Mandanten', title: 'Erstellen Sie Ihren ersten Mandanten',
@ -41,46 +9,13 @@ const cloud = {
'Ein Mandant ist eine isolierte Umgebung, in der Sie Benutzeridentitäten, Anwendungen und alle anderen Logto-Ressourcen verwalten können.', 'Ein Mandant ist eine isolierte Umgebung, in der Sie Benutzeridentitäten, Anwendungen und alle anderen Logto-Ressourcen verwalten können.',
invite_collaborators: 'Laden Sie Ihre Mitarbeiter per E-Mail ein', invite_collaborators: 'Laden Sie Ihre Mitarbeiter per E-Mail ein',
}, },
sie: { social_callback: {
page_title: 'Meldeeinrichtung anpassen',
title: 'Passen Sie zuerst Ihre Anmeldungserfahrung mit Leichtigkeit an',
inspire: {
title: 'Erstellen Sie überzeugende Beispiele',
description:
'Fühlen Sie sich unsicher bei der Anmeldeerfahrung? Klicken Sie einfach auf "Inspire me" und lassen Sie die Magie geschehen!',
inspire_me: 'Inspirieren Sie mich',
},
logo_field: 'App-Logo',
color_field: 'Markenfarbe',
identifier_field: 'Identifikator',
identifier_options: {
email: 'E-Mail',
phone: 'Telefon',
user_name: 'Benutzername',
},
authn_field: 'Authentifizierung',
authn_options: {
password: 'Passwort',
verification_code: 'Verifizierungscode',
},
social_field: 'Soziale Anmeldung',
finish_and_done: 'Fertig und erledigt',
preview: {
mobile_tab: 'Mobil',
web_tab: 'Web',
},
connectors: {
unlocked_later: 'Später entsperrt',
unlocked_later_tip:
'Sobald Sie den Onboarding-Prozess abgeschlossen und das Produkt betreten haben, haben Sie Zugriff auf noch mehr soziale Anmeldeverfahren.',
notice:
'Bitte verwenden Sie den Demo-Connector nicht für Produktionszwecke. Sobald Sie mit dem Testen fertig sind, löschen Sie bitte den Demo-Connector und richten Sie Ihren eigenen Connector mit Ihren Anmeldeinformationen ein.',
},
},
socialCallback: {
title: 'Sie haben sich erfolgreich angemeldet', title: 'Sie haben sich erfolgreich angemeldet',
description: description:
'Sie haben sich erfolgreich mit Ihrem Social-Account angemeldet. Um eine nahtlose Integration und den Zugriff auf alle Funktionen von Logto zu gewährleisten, empfehlen wir Ihnen, Ihren eigenen Social-Connector zu konfigurieren.', 'Sie haben sich erfolgreich mit Ihrem Social-Account angemeldet. Um eine nahtlose Integration und den Zugriff auf alle Funktionen von Logto zu gewährleisten, empfehlen wir Ihnen, Ihren eigenen Social-Connector zu konfigurieren.',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Tenant erstellen', create_tenant: 'Tenant erstellen',

View file

@ -2,37 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Onboarding', onboarding: 'Onboarding',
}, },
welcome: {
page_title: 'Welcome',
title: "Welcome to Logto Cloud! We'd love to learn a bit about you.",
description:
"Let's make your Logto experience unique to you by getting to know you better. Your information is safe with us.",
project_field: "I'm using Logto for",
project_options: {
personal: 'Personal project',
company: 'Company project',
},
company_name_field: 'Company name',
company_name_placeholder: 'Acme.co',
stage_field: 'What stage is your product currently in?',
stage_options: {
new_product: 'Start a new project and looking for a quick, out-of-the-box solution',
existing_product:
'Migrate from current authentication (e.g., self-built, Auth0, Cognito, Microsoft)',
target_enterprise_ready:
'I just landed bigger clients and now make my product ready to sell to enterprises',
},
additional_features_field: 'Do you have anything else you want us to know?',
additional_features_options: {
customize_ui_and_flow:
'Build and manage my own UI, not just use Logto pre-built and customizable solution',
compliance: 'SOC2 and GDPR are must-haves',
export_user_data: 'Need the ability to export user data from Logto',
budget_control: 'I have very tight budget control',
bring_own_auth: 'Have my own auth services and just need some Logto features',
others: 'None of these above',
},
},
create_tenant: { create_tenant: {
page_title: 'Create tenant', page_title: 'Create tenant',
title: 'Create your first tenant', title: 'Create your first tenant',
@ -40,46 +9,12 @@ const cloud = {
'A tenant is an isolated environment where you can manage user identities, applications, and all other Logto resources.', 'A tenant is an isolated environment where you can manage user identities, applications, and all other Logto resources.',
invite_collaborators: 'Invite your collaborators by email', invite_collaborators: 'Invite your collaborators by email',
}, },
sie: { social_callback: {
page_title: 'Customize sign-in experience',
title: "Let's first customize your sign-in experience with ease",
inspire: {
title: 'Create compelling examples',
description:
'Feeling unsure about sign in experience? Just click the "Inspire Me" and let the magic happen!',
inspire_me: 'Inspire me',
},
logo_field: 'App logo',
color_field: 'Brand color',
identifier_field: 'Identifier',
identifier_options: {
email: 'Email',
phone: 'Phone',
user_name: 'Username',
},
authn_field: 'Authentication',
authn_options: {
password: 'Password',
verification_code: 'Verification code',
},
social_field: 'Social sign-in',
finish_and_done: 'Finish and done',
preview: {
mobile_tab: 'Mobile',
web_tab: 'Web',
},
connectors: {
unlocked_later: 'Unlocked later',
unlocked_later_tip:
'Once you have completed the onboarding process and entered the product, you will have access to even more social sign-in methods.',
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
},
},
socialCallback: {
title: "You've successfully signed in", title: "You've successfully signed in",
description: description:
'You have successfully signed in using your social account. To ensure seamless integration and access to all the features of Logto, we recommend that you proceed to configure your own social connector.', 'You have successfully signed in using your social account. To ensure seamless integration and access to all the features of Logto, we recommend that you proceed to configure your own social connector.',
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Create tenant', create_tenant: 'Create tenant',

View file

@ -2,38 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Integración', onboarding: 'Integración',
}, },
welcome: {
page_title: 'Bienvenida',
title: '¡Bienvenido a Logto Cloud! Nos encantaría saber un poco más sobre ti.',
description:
'Hagamos que su experiencia de Logto sea única para usted al conocerlo mejor. Su información está segura con nosotros.',
project_field: 'Estoy usando Logto para:',
project_options: {
personal: 'Proyecto personal',
company: 'Proyecto empresarial',
},
company_name_field: 'Nombre de la empresa',
company_name_placeholder: 'Acme.co',
stage_field: '¿En qué etapa se encuentra su producto actualmente?',
stage_options: {
new_product: 'Comenzar un nuevo proyecto y buscar una solución rápida y lista para usar',
existing_product:
'Migrar desde una autenticación actual (por ejemplo, creada por uno mismo, Auth0, Cognito, Microsoft)',
target_enterprise_ready:
'Acabo de obtener clientes más grandes y ahora debo hacer que mi producto esté listo para vender a empresas',
},
additional_features_field: '¿Hay algo más que desee que sepamos?',
additional_features_options: {
customize_ui_and_flow:
'Construir y gestionar mi propia interfaz de usuario, no solo usar la solución preconstruida y personalizable de Logto',
compliance: 'SOC2 y GDPR son imprescindibles',
export_user_data: 'Necesito la capacidad de exportar datos de usuario de Logto',
budget_control: 'Tengo un control presupuestario muy ajustado',
bring_own_auth:
'Tengo mis propios servicios de autenticación y solo necesito algunas características de Logto',
others: 'Ninguna de las anteriores',
},
},
create_tenant: { create_tenant: {
page_title: 'Crear inquilino', page_title: 'Crear inquilino',
title: 'Crea tu primer inquilino', title: 'Crea tu primer inquilino',
@ -41,46 +9,13 @@ const cloud = {
'Un inquilino es un entorno aislado donde puedes gestionar identidades de usuarios, aplicaciones y todos los demás recursos de Logto.', 'Un inquilino es un entorno aislado donde puedes gestionar identidades de usuarios, aplicaciones y todos los demás recursos de Logto.',
invite_collaborators: 'Invita a tus colaboradores por correo electrónico', invite_collaborators: 'Invita a tus colaboradores por correo electrónico',
}, },
sie: { social_callback: {
page_title: 'Personalización de la experiencia de inicio de sesión',
title: 'Primero personalicemos su experiencia de inicio de sesión con facilidad',
inspire: {
title: 'Crear ejemplos convincentes',
description:
'¿Se siente inseguro acerca de la experiencia de inicio de sesión? ¡Simplemente haga clic en "Inspíreme" y deje que suceda la magia!',
inspire_me: 'Inspírame',
},
logo_field: 'Logotipo de la aplicación',
color_field: 'Color de la marca',
identifier_field: 'Identificador',
identifier_options: {
email: 'Correo electrónico',
phone: 'Teléfono',
user_name: 'Nombre de usuario',
},
authn_field: 'Autenticación',
authn_options: {
password: 'Contraseña',
verification_code: 'Código de verificación',
},
social_field: 'Inicio de sesión social',
finish_and_done: 'Terminar y listo',
preview: {
mobile_tab: 'Móvil',
web_tab: 'Web',
},
connectors: {
unlocked_later: 'Desbloqueado más adelante',
unlocked_later_tip:
'Una vez que haya completado el proceso de incorporación y haya ingresado al producto, tendrá acceso a una mayor variedad de métodos de inicio de sesión social.',
notice:
'Evite usar el conector de demostración para fines de producción. Una vez completadas las pruebas, elimine amablemente el conector de demostración y configure su propio conector con sus credenciales.',
},
},
socialCallback: {
title: 'Ha iniciado sesión correctamente', title: 'Ha iniciado sesión correctamente',
description: description:
'Ha iniciado sesión correctamente utilizando su cuenta social. Para garantizar una integración perfecta y el acceso a todas las funciones de Logto, recomendamos que proceda a configurar su propio conector social.', 'Ha iniciado sesión correctamente utilizando su cuenta social. Para garantizar una integración perfecta y el acceso a todas las funciones de Logto, recomendamos que proceda a configurar su propio conector social.',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Crear inquilino', create_tenant: 'Crear inquilino',

View file

@ -2,39 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Intégration', onboarding: 'Intégration',
}, },
welcome: {
page_title: 'Bienvenue',
title: 'Bienvenue dans Logto Cloud ! Nous aimerions en savoir un peu plus sur vous.',
description:
'Personnalisons votre expérience Logto en vous connaissant mieux. Vos informations sont en sécurité avec nous.',
project_field: "J'utilise Logto pour",
project_options: {
personal: 'Projet personnel',
company: "Projet d'entreprise",
},
company_name_field: "Nom de l'entreprise",
company_name_placeholder: 'Acme.co',
stage_field: 'Dans quelle étape se trouve actuellement votre produit?',
stage_options: {
new_product: "Démarrer un nouveau projet et chercher une solution rapide et prête à l'emploi",
existing_product:
"Migrer à partir d'une authentification actuelle (par exemple, auto-construite, Auth0, Cognito, Microsoft)",
target_enterprise_ready:
'Je viens de conclure des contrats avec des clients plus importants et je veux maintenant rendre mon produit prêt à être vendu aux entreprises',
},
additional_features_field: "Avez-vous d'autres informations à nous communiquer?",
additional_features_options: {
customize_ui_and_flow:
'Construire et gérer ma propre interface utilisateur, pas seulement utiliser la solution pré-construite et personnalisable de Logto',
compliance: 'SOC2 et le respect du RGPD sont indispensables',
export_user_data:
"Besoin de la possibilité d'exporter des données utilisateur à partir de Logto",
budget_control: 'Je dois avoir un contrôle de budget très strict',
bring_own_auth:
"J'ai mes propres services d'authentification et j'ai juste besoin de certaines fonctionnalités de Logto",
others: 'Aucun de ceux mentionnés ci-dessus',
},
},
create_tenant: { create_tenant: {
page_title: 'Créer un locataire', page_title: 'Créer un locataire',
title: 'Créez votre premier locataire', title: 'Créez votre premier locataire',
@ -42,46 +9,13 @@ const cloud = {
'Un locataire est un environnement isolé où vous pouvez gérer les identités des utilisateurs, les applications et toutes les autres ressources Logto.', 'Un locataire est un environnement isolé où vous pouvez gérer les identités des utilisateurs, les applications et toutes les autres ressources Logto.',
invite_collaborators: 'Invitez vos collaborateurs par e-mail', invite_collaborators: 'Invitez vos collaborateurs par e-mail',
}, },
sie: { social_callback: {
page_title: "Personnalisez l'expérience de connexion",
title: "Personnalisons d'abord votre expérience de connexion en toute simplicité",
inspire: {
title: 'Créez des exemples convaincants',
description:
'Vous vous sentez incertain de l\'expérience de connexion ? Cliquez simplement sur "Inspirez-moi" et laissez la magie opérer!',
inspire_me: 'Inspirez-moi',
},
logo_field: "Logo de l'application",
color_field: 'Couleur de la marque',
identifier_field: 'Identifiant',
identifier_options: {
email: 'E-mail',
phone: 'Téléphone',
user_name: "Nom d'utilisateur",
},
authn_field: 'Authentification',
authn_options: {
password: 'Mot de passe',
verification_code: 'Code de vérification',
},
social_field: 'Connexion sociale',
finish_and_done: 'Terminer et terminé',
preview: {
mobile_tab: 'Mobile',
web_tab: 'Web',
},
connectors: {
unlocked_later: 'Débloqué ultérieurement',
unlocked_later_tip:
"Une fois que vous avez terminé le processus d'inscription et que vous êtes entré dans le produit, vous aurez accès à encore plus de méthodes de connexion sociale.",
notice:
"Veuillez éviter d'utiliser le connecteur de démonstration à des fins de production. Lorsque vous avez terminé les tests, veuillez supprimer le connecteur de démonstration et mettre en place votre propre connecteur avec vos informations d'identification.",
},
},
socialCallback: {
title: 'Connexion réussie', title: 'Connexion réussie',
description: description:
'Vous vous êtes connecté avec succès en utilisant votre compte social. Pour assurer une intégration fluide et accéder à toutes les fonctionnalités de Logto, nous vous recommandons de configurer votre propre connecteur social.', 'Vous vous êtes connecté avec succès en utilisant votre compte social. Pour assurer une intégration fluide et accéder à toutes les fonctionnalités de Logto, nous vous recommandons de configurer votre propre connecteur social.',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Créer un locataire', create_tenant: 'Créer un locataire',

View file

@ -2,38 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Inizio', onboarding: 'Inizio',
}, },
welcome: {
page_title: 'Benvenuto',
title: "Benvenuto in Logto Cloud! Ci piacerebbe conoscere un po' di te.",
description:
'Facciamo diventare la tua esperienza Logto unica conoscendoti meglio. Le tue informazioni sono al sicuro con noi.',
project_field: 'Sto usando Logto per',
project_options: {
personal: 'Progetto personale',
company: 'Progetto aziendale',
},
company_name_field: "Nome dell'azienda",
company_name_placeholder: 'Acme.co',
stage_field: 'In quale fase si trova attualmente il tuo prodotto?',
stage_options: {
new_product: "Inizia un nuovo progetto e cerca una soluzione rapida e pronta all'uso",
existing_product:
"Migra dall'autenticazione attuale (ad esempio, autenticazione autocostruita, Auth0, Cognito, Microsoft)",
target_enterprise_ready:
'Ho appena acquisito clienti più importanti e ora rendo il mio prodotto pronto per essere venduto alle imprese',
},
additional_features_field: "Qualcos'altro che desideri farci sapere?",
additional_features_options: {
customize_ui_and_flow:
'Costruisci e gestisci la mia stessa interfaccia utente, non solo utilizzo la soluzione predefinita e personalizzabile di Logto',
compliance: 'SOC2 e GDPR sono imprescindibili',
export_user_data: 'Necessità di esportare i dati utente da Logto',
budget_control: 'Ho un controllo molto stretto sul budget',
bring_own_auth:
'Ho i miei servizi di autenticazione e ho solo bisogno di alcune funzionalità di Logto',
others: 'Nessuna delle opzioni sopra',
},
},
create_tenant: { create_tenant: {
page_title: 'Crea tenant', page_title: 'Crea tenant',
title: 'Crea il tuo primo tenant', title: 'Crea il tuo primo tenant',
@ -41,46 +9,13 @@ const cloud = {
'Un tenant è un ambiente isolato in cui puoi gestire identità degli utenti, applicazioni e tutte le altre risorse di Logto.', 'Un tenant è un ambiente isolato in cui puoi gestire identità degli utenti, applicazioni e tutte le altre risorse di Logto.',
invite_collaborators: 'Invita i tuoi collaboratori via email', invite_collaborators: 'Invita i tuoi collaboratori via email',
}, },
sie: { social_callback: {
page_title: "Personalizza l'esperienza di accesso",
title: 'Personalizziamo insieme la tua esperienza di accesso',
inspire: {
title: 'Crea esempi coinvolgenti',
description:
'Ti senti incerto riguardo l\'esperienza di accesso? Fai clic su "Ispirami" e lascia che la magia accada!',
inspire_me: 'Ispirami',
},
logo_field: "Logo dell'app",
color_field: 'Colore del brand',
identifier_field: 'Identificativo',
identifier_options: {
email: 'Email',
phone: 'Telefono',
user_name: 'Nome utente',
},
authn_field: 'Autenticazione',
authn_options: {
password: 'Password',
verification_code: 'Codice di verifica',
},
social_field: 'Accesso tramite social',
finish_and_done: 'Termina e completato',
preview: {
mobile_tab: 'Mobile',
web_tab: 'Web',
},
connectors: {
unlocked_later: 'Sbloccato in seguito',
unlocked_later_tip:
'Una volta completato il processo di onboarding e inserito il prodotto, avrai accesso a ancora più metodi di accesso tramite social.',
notice:
'Si prega di evitare di utilizzare il connettore demo per scopi di produzione. Una volta completati i test, cancellare gentilmente il connettore demo e configurare il proprio connettore con le proprie credenziali.',
},
},
socialCallback: {
title: 'Accesso effettuato con successo', title: 'Accesso effettuato con successo',
description: description:
"Hai effettuato l'accesso con successo utilizzando il tuo account social. Per garantire integrazione senza problemi e accesso a tutte le funzionalità di Logto, ti consigliamo di procedere alla configurazione del tuo connettore social.", "Hai effettuato l'accesso con successo utilizzando il tuo account social. Per garantire integrazione senza problemi e accesso a tutte le funzionalità di Logto, ti consigliamo di procedere alla configurazione del tuo connettore social.",
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Crea tenant', create_tenant: 'Crea tenant',

View file

@ -2,36 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'オンボーディング', onboarding: 'オンボーディング',
}, },
welcome: {
page_title: 'ようこそ',
title: 'Logto Cloud へようこそ!あなたについて少し学びたいです。',
description:
'あなたの情報を知ることで、あなたにユニークな Logto エクスペリエンスを提供します。あなたの情報は安全に保管されます。',
project_field: 'ログトを使用しています',
project_options: {
personal: '個人プロジェクト',
company: '会社プロジェクト',
},
company_name_field: '会社名',
company_name_placeholder: 'Acme.co',
stage_field: '製品は現在どの段階にありますか?',
stage_options: {
new_product: '新しいプロジェクトを開始し、素早く立ち上げたい場合',
existing_product: '現在の認証自社構築、Auth0、Cognito、Microsoftから移行する',
target_enterprise_ready:
'大きなクライアントを獲得したため、製品を企業向けに販売できるようにしたい',
},
additional_features_field: '私たちに伝えたいことはありますか?',
additional_features_options: {
customize_ui_and_flow:
'自分の UI を構築および管理し、Logto の事前に構築されたカスタマイズ可能なソリューションだけではなく使用する',
compliance: 'SOC2 と GDPR は必須です',
export_user_data: 'Logto からユーザーデータをエクスポートする機能が必要です',
budget_control: '予算管理が非常に厳しいです',
bring_own_auth: '独自の認証サービスを持っており、Logto の機能が必要な場合',
others: '上記のどれにも該当しません',
},
},
create_tenant: { create_tenant: {
page_title: 'テナントを作成', page_title: 'テナントを作成',
title: '最初のテナントを作成', title: '最初のテナントを作成',
@ -39,46 +9,13 @@ const cloud = {
'テナントはユーザーアイデンティティ、アプリケーション、およびその他すべての Logto リソースを管理するための独立した環境です。', 'テナントはユーザーアイデンティティ、アプリケーション、およびその他すべての Logto リソースを管理するための独立した環境です。',
invite_collaborators: 'メールでコラボレーターを招待', invite_collaborators: 'メールでコラボレーターを招待',
}, },
sie: { social_callback: {
page_title: 'サインインエクスペリエンスのカスタマイズ',
title: 'まずは簡単にサインインエクスペリエンスをカスタマイズしましょう',
inspire: {
title: '魅力的な例を作成',
description:
'サインインエクスペリエンスに自信がない場合は、「Inspire Me」をクリックして、マジックが行われるのを待ちましょう',
inspire_me: 'インスパイア',
},
logo_field: 'アプリのロゴ',
color_field: 'ブランドカラー',
identifier_field: '識別子',
identifier_options: {
email: 'メール',
phone: '電話',
user_name: 'ユーザー名',
},
authn_field: '認証',
authn_options: {
password: 'パスワード',
verification_code: '検証コード',
},
social_field: 'ソーシャルサインイン',
finish_and_done: '完了',
preview: {
mobile_tab: 'モバイル',
web_tab: 'Web',
},
connectors: {
unlocked_later: '後でロックを解除',
unlocked_later_tip:
'オンボーディングプロセスを完了してプロダクトに入った後、より多くのソーシャルサインイン方法にアクセスできるようになります。',
notice:
'本番目的でのデモコネクタの使用は避けてください。テストを完了したら、デモコネクタを削除し、自分のクレデンシャルを使用して独自のコネクタを設定してください。',
},
},
socialCallback: {
title: 'ログインが成功しました', title: 'ログインが成功しました',
description: description:
'ソーシャルアカウントを使用して正常にサインインしました。Logto のすべての機能にシームレスにアクセスできるようにするために、独自のソーシャルコネクタを設定することをお勧めします。', 'ソーシャルアカウントを使用して正常にサインインしました。Logto のすべての機能にシームレスにアクセスできるようにするために、独自のソーシャルコネクタを設定することをお勧めします。',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'テナントを作成する', create_tenant: 'テナントを作成する',

View file

@ -2,35 +2,6 @@ const cloud = {
general: { general: {
onboarding: '온보딩', onboarding: '온보딩',
}, },
welcome: {
page_title: '환영합니다',
title: 'Logto Cloud 에 오신 것을 환영합니다! 조금 더 알고 싶어요.',
description:
'당신에 대해 더 잘 파악하여 Logto 경험을 특별하게 만들어 드릴게요. 정보는 저희가 안전하게 관리해요.',
project_field: 'Logto 를 아래의 목적으로 사용해요',
project_options: {
personal: '개인 프로젝트',
company: '기업 프로젝트',
},
company_name_field: '회사 이름',
company_name_placeholder: 'Acme.co',
stage_field: '지금까지 당신의 제품은 어떤 단계에 있나요?',
stage_options: {
new_product: '새로운 프로젝트를 시작하고 빠르고 편리한 솔루션을 찾고 있어요',
existing_product: '현재 인증을 이전하려고 합니다 (예: 자체 구축, Auth0, Cognito, Microsoft)',
target_enterprise_ready: '큰 고객들을 얻었으니 제품을 기업에 판매할 준비를 하고 있어요',
},
additional_features_field: '알려주고 싶은 다른 사항이 있으세요?',
additional_features_options: {
customize_ui_and_flow:
'내 UI를 직접 구축하고 관리하려고 하지만 Logto의 미리 구축되고 사용자 정의 가능한 솔루션을 사용하지 않는다',
compliance: 'SOC2와 GDPR가 필수 사항이에요',
export_user_data: 'Logto 에서 사용자 데이터를 내보낼 수 있는 기능이 필요해요',
budget_control: '매우 엄격한 예산 통제가 있어요',
bring_own_auth: '내장 인증 서비스가 있고 Logto 기능만 필요해요',
others: '위에 나열된 것 중에 없어요',
},
},
create_tenant: { create_tenant: {
page_title: '테넌트 만들기', page_title: '테넌트 만들기',
title: '첫 번째 테넌트 만들기', title: '첫 번째 테넌트 만들기',
@ -38,46 +9,13 @@ const cloud = {
'테넌트는 사용자 신원, 애플리케이션 및 기타 모든 Logto 리소스를 관리할 수 있는 독립된 환경입니다.', '테넌트는 사용자 신원, 애플리케이션 및 기타 모든 Logto 리소스를 관리할 수 있는 독립된 환경입니다.',
invite_collaborators: '이메일로 협력자를 초대하세요', invite_collaborators: '이메일로 협력자를 초대하세요',
}, },
sie: { social_callback: {
page_title: '로그인 환경 변경하기',
title: '먼저 로그인 환경을 간편하게 사용자화해 보세요.',
inspire: {
title: '흥미로운 예시 만들기',
description:
'로그인 환경에 대해 확신이 서지 않으시나요? "영감을 주세요"를 클릭하고 마법을 일으켜 보세요!',
inspire_me: '영감을 주세요',
},
logo_field: '앱 로고',
color_field: '브랜드 색상',
identifier_field: '식별자',
identifier_options: {
email: '이메일',
phone: '휴대전화',
user_name: '사용자 이름',
},
authn_field: '인증',
authn_options: {
password: '비밀번호',
verification_code: '인증 코드',
},
social_field: '소셜 로그인',
finish_and_done: '완료하고 넘어가기',
preview: {
mobile_tab: '모바일',
web_tab: '웹',
},
connectors: {
unlocked_later: '나중에 잠금 해제',
unlocked_later_tip:
'등록 절차를 완료하고 제품에 가입하면 더 많은 소셜 로그인 방법에 액세스할 수 있습니다.',
notice:
'데모 연동을 실제 운영 목적으로 사용하지 마세요. 테스트를 완료한 후에는 데모 연동을 삭제하고 자격 증명을 사용하여 고유한 연동을 설정하세요.',
},
},
socialCallback: {
title: '성공적으로 로그인했어요', title: '성공적으로 로그인했어요',
description: description:
'소셜 계정을 사용하여 로그인에 성공했어요. Logto의 모든 기능을 원활하게 통합하고 접근하려면 당신의 소셜 연동을 구성하는 것이 좋습니다.', '소셜 계정을 사용하여 로그인에 성공했어요. Logto의 모든 기능을 원활하게 통합하고 접근하려면 당신의 소셜 연동을 구성하는 것이 좋습니다.',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: '테넌트 생성하기', create_tenant: '테넌트 생성하기',

View file

@ -2,38 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Wdrażanie', onboarding: 'Wdrażanie',
}, },
welcome: {
page_title: 'Witamy',
title: 'Witaj w chmurze Logto! Chcielibyśmy dowiedzieć się trochę o Tobie.',
description:
'Stwórz indywidualne wrażenia z Logto dzięki naszej wiedzy na temat Ciebie. Twoje informacje są bezpieczne u nas.',
project_field: 'Używam Logto do',
project_options: {
personal: 'Projektu osobistego',
company: 'Projektu firmowego',
},
company_name_field: 'Nazwa firmy',
company_name_placeholder: 'Acme.co',
stage_field: 'W jakim etapie jest Twój produkt aktualnie?',
stage_options: {
new_product: 'Rozpocznij nowy projekt i szukasz szybkiego, gotowego rozwiązania',
existing_product:
'Migracja z bieżącej autoryzacji (np. własna autoryzacja, Auth0, Cognito, Microsoft)',
target_enterprise_ready:
'Właśnie pozyskałem większych klientów i teraz przygotowuję mój produkt do sprzedaży dla przedsiębiorstw',
},
additional_features_field: 'Czy masz coś jeszcze, o czym chcesz, żebyśmy wiedzieli?',
additional_features_options: {
customize_ui_and_flow:
'Zbuduj i zarządzaj własnym interfejsem użytkownika, nie tylko korzystaj z gotowego i dostosowywalnego rozwiązania Logto',
compliance: 'SOC2 i GDPR są konieczne',
export_user_data: 'Potrzebuję możliwości eksportu danych użytkownika z Logto',
budget_control: 'Mam bardzo ściśłą kontrolę budżetu',
bring_own_auth:
'Mam swoje własne usługi autoryzacji i potrzebuję tylko niektórych funkcji Logto',
others: 'Nic z powyższych',
},
},
create_tenant: { create_tenant: {
page_title: 'Utwórz najemcę', page_title: 'Utwórz najemcę',
title: 'Utwórz swojego pierwszego najemcę', title: 'Utwórz swojego pierwszego najemcę',
@ -41,46 +9,13 @@ const cloud = {
'Najemca to odizolowane środowisko, w którym możesz zarządzać tożsamościami użytkowników, aplikacjami i wszystkimi innymi zasobami Logto.', 'Najemca to odizolowane środowisko, w którym możesz zarządzać tożsamościami użytkowników, aplikacjami i wszystkimi innymi zasobami Logto.',
invite_collaborators: 'Zaproś swoich współpracowników za pomocą e-maila', invite_collaborators: 'Zaproś swoich współpracowników za pomocą e-maila',
}, },
sie: { social_callback: {
page_title: 'Dostosuj doświadczenie logowania',
title: 'Najpierw dostosuj swoje doświadczenie logowania',
inspire: {
title: 'Stwórz przykłady',
description:
'Nie jesteś pewien swojego doświadczenia logowania? Kliknij "Zainspiruj mnie" i pozwól, żeby magia się stała!',
inspire_me: 'Zainspiruj mnie',
},
logo_field: 'Logo aplikacji',
color_field: 'Kolor marki',
identifier_field: 'Identyfikator',
identifier_options: {
email: 'E-mail',
phone: 'Telefon',
user_name: 'Nazwa użytkownika',
},
authn_field: 'Uwierzytelnianie',
authn_options: {
password: 'Hasło',
verification_code: 'Kod weryfikacyjny',
},
social_field: 'Logowanie społecznościowe',
finish_and_done: 'Skończone i gotowe',
preview: {
mobile_tab: 'Mobilny',
web_tab: 'Sieć',
},
connectors: {
unlocked_later: 'Zostanie odblokowane później',
unlocked_later_tip:
'Po ukończeniu procesu wprowadzenia do użytku i wejściu do produktu będziesz mieć dostęp do jeszcze większej liczby metod logowania społecznościowego',
notice:
'Prosimy, unikaj korzystania z demo konektora do celów produkcyjnych. Po zakończeniu testów, uprzejmie usuń demokonwerter i skonfiguruj swój własny konektor z własnymi poświadczeniami.',
},
},
socialCallback: {
title: 'Zalogowałeś się pomyślnie', title: 'Zalogowałeś się pomyślnie',
description: description:
'Zalogowałeś się pomyślnie używając swojego konta społecznościowego. Aby zapewnić bezproblemową integrację i dostęp do wszystkich funkcji Logto, zalecamy przejście do konfiguracji własnego konektora społecznościowego.', 'Zalogowałeś się pomyślnie używając swojego konta społecznościowego. Aby zapewnić bezproblemową integrację i dostęp do wszystkich funkcji Logto, zalecamy przejście do konfiguracji własnego konektora społecznościowego.',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Stwórz najemcę', create_tenant: 'Stwórz najemcę',

View file

@ -2,38 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Integração', onboarding: 'Integração',
}, },
welcome: {
page_title: 'Bem-vindo',
title: 'Bem-vindo ao Logto Cloud! Gostaríamos de conhecer um pouco sobre você.',
description:
'Vamos tornar sua experiência Logto única, conhecendo você melhor. Suas informações estão seguras conosco.',
project_field: 'Estou usando o Logto para',
project_options: {
personal: 'Projeto pessoal',
company: 'Projeto da empresa',
},
company_name_field: 'Nome da empresa',
company_name_placeholder: 'Acme.co',
stage_field: 'Em qual estágio seu produto está atualmente?',
stage_options: {
new_product: 'Iniciar um novo projeto e procurar por uma solução rápida e pronta para uso',
existing_product:
'Migrar da autenticação atual (por exemplo, construída internamente, Auth0, Cognito, Microsoft)',
target_enterprise_ready:
'Acabei de conseguir clientes maiores e agora estou preparando meu produto para vender para empresas',
},
additional_features_field: 'Você tem mais alguma coisa que deseja que saibamos?',
additional_features_options: {
customize_ui_and_flow:
'Construir e gerenciar minha própria interface de usuário, e não apenas usar a solução pré-construída e personalizável do Logto',
compliance: 'SOC2 e GDPR são imprescindíveis',
export_user_data: 'Preciso da capacidade de exportar dados do usuário do Logto',
budget_control: 'Tenho controle de orçamento muito rígido',
bring_own_auth:
'Tenho meus próprios serviços de autenticação e apenas preciso de alguns recursos do Logto',
others: 'Nenhuma das opções acima',
},
},
create_tenant: { create_tenant: {
page_title: 'Criar inquilino', page_title: 'Criar inquilino',
title: 'Crie seu primeiro inquilino', title: 'Crie seu primeiro inquilino',
@ -41,46 +9,13 @@ const cloud = {
'Um inquilino é um ambiente isolado onde você pode gerenciar identidades de usuário, aplicações e todos os outros recursos do Logto.', 'Um inquilino é um ambiente isolado onde você pode gerenciar identidades de usuário, aplicações e todos os outros recursos do Logto.',
invite_collaborators: 'Convide seus colaboradores por e-mail', invite_collaborators: 'Convide seus colaboradores por e-mail',
}, },
sie: { social_callback: {
page_title: 'Personalize a experiência de logon',
title: 'Vamos personalizar sua experiência de logon facilmente',
inspire: {
title: 'Crie exemplos convincentes',
description:
'Sentindo-se inseguro sobre a experiência de login? Basta clicar em "Inspirar-me" e deixar a mágica acontecer!',
inspire_me: 'Inspirar-me',
},
logo_field: 'Logotipo do aplicativo',
color_field: 'Cor da marca',
identifier_field: 'Identificador',
identifier_options: {
email: 'E-mail',
phone: 'Telefone',
user_name: 'Nome de usuário',
},
authn_field: 'Autenticação',
authn_options: {
password: 'Senha',
verification_code: 'Código de verificação',
},
social_field: 'Login social',
finish_and_done: 'Finalizar e feito',
preview: {
mobile_tab: 'Celular',
web_tab: 'Internet',
},
connectors: {
unlocked_later: 'Desbloqueado mais tarde',
unlocked_later_tip:
'Assim que você concluir o processo de integração e entrar no produto, terá acesso a ainda mais métodos de login social.',
notice:
'Evite usar o conector de demonstração para fins de produção. Depois de concluídos os testes, exclua gentilmente o conector de demonstração e configure seu próprio conector com suas credenciais.',
},
},
socialCallback: {
title: 'Você entrou com sucesso', title: 'Você entrou com sucesso',
description: description:
'Você entrou com sucesso usando sua conta social. Para garantir a integração perfeita e o acesso a todos os recursos do Logto, recomendamos que você prossiga para configurar seu próprio conector social.', 'Você entrou com sucesso usando sua conta social. Para garantir a integração perfeita e o acesso a todos os recursos do Logto, recomendamos que você prossiga para configurar seu próprio conector social.',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Criar inquilino', create_tenant: 'Criar inquilino',

View file

@ -2,38 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Introdução', onboarding: 'Introdução',
}, },
welcome: {
page_title: 'Bem-vindo',
title: 'Bem-vindo ao Logto Cloud! Gostaríamos de aprender um pouco sobre si.',
description:
'Vamos tornar a experiência da Logto única para si conhecendo-o melhor. As suas informações estão seguras connosco.',
project_field: 'Estou a usar a Logto para',
project_options: {
personal: 'Projeto pessoal',
company: 'Projeto da empresa',
},
company_name_field: 'Nome da empresa',
company_name_placeholder: 'Acme.co',
stage_field: 'Em que fase está atualmente o seu produto?',
stage_options: {
new_product: 'Iniciar um novo projeto e procurar uma solução rápida e pronta a usar',
existing_product:
'Migrar da autenticação atual (p. ex., autenticação feita por si, Auth0, Cognito, Microsoft)',
target_enterprise_ready:
'Acabei de conquistar clientes mais importantes e agora pretendo preparar o meu produto para vender a empresas',
},
additional_features_field: 'Tem algo mais que queira que saibamos?',
additional_features_options: {
customize_ui_and_flow:
'Construir e gerir a minha própria IU, não apenas usar a solução pré-construída e personalizável da Logto',
compliance: 'A conformidade SOC2 e GDPR são imprescindíveis',
export_user_data: 'Necessidade de exportar dados de utilizadores da Logto',
budget_control: 'Tenho um controlo orçamental muito apertado',
bring_own_auth:
'Tenho os meus próprios serviços de autenticação e só preciso de algumas funcionalidades da Logto',
others: 'Nenhuma das acima mencionadas',
},
},
create_tenant: { create_tenant: {
page_title: 'Criar inquilino', page_title: 'Criar inquilino',
title: 'Crie o seu primeiro inquilino', title: 'Crie o seu primeiro inquilino',
@ -41,46 +9,13 @@ const cloud = {
'Um inquilino é um ambiente isolado onde pode gerir identidades de utilizadores, aplicações e todos os demais recursos da Logto.', 'Um inquilino é um ambiente isolado onde pode gerir identidades de utilizadores, aplicações e todos os demais recursos da Logto.',
invite_collaborators: 'Convide os seus colaboradores por email', invite_collaborators: 'Convide os seus colaboradores por email',
}, },
sie: { social_callback: {
page_title: 'Personalize a Experiência de Login',
title: 'Vamos personalizar a sua experiência de login com facilidade',
inspire: {
title: 'Crie exemplos convincentes',
description:
'Sentindo-se inseguro/a sobre a experiência de login? Basta clicar em "Me inspire" e deixar a mágica acontecer!',
inspire_me: 'Me inspire',
},
logo_field: 'Logotipo do aplicativo',
color_field: 'Cor da marca',
identifier_field: 'Identificador',
identifier_options: {
email: 'E-mail',
phone: 'Telefone',
user_name: 'Nome de utilizador',
},
authn_field: 'Autenticação',
authn_options: {
password: 'Senha',
verification_code: 'Código de verificação',
},
social_field: 'Login social',
finish_and_done: 'Terminar e pronto',
preview: {
mobile_tab: 'Telemóvel',
web_tab: 'Web',
},
connectors: {
unlocked_later: 'Desbloqueado mais tarde',
unlocked_later_tip:
'Depois de concluir o processo de integração e entrar no produto, você terá acesso a ainda mais métodos de login social.',
notice:
'Evite usar o conector de demonstração para fins de produção. Depois de concluído o teste, exclua gentilmente o conector de demonstração e configure o seu próprio conector com suas credenciais.',
},
},
socialCallback: {
title: 'Entrou com Sucesso', title: 'Entrou com Sucesso',
description: description:
'Entrou com sucesso usando a sua conta social. Para garantir uma integração perfeita e acesso a todos os recursos da Logto, recomendamos que prossiga para configurar o seu próprio conector social.', 'Entrou com sucesso usando a sua conta social. Para garantir uma integração perfeita e acesso a todos os recursos da Logto, recomendamos que prossiga para configurar o seu próprio conector social.',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Criar novo inquilino', create_tenant: 'Criar novo inquilino',

View file

@ -2,38 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Вводный курс', onboarding: 'Вводный курс',
}, },
welcome: {
page_title: 'Добро пожаловать',
title: 'Добро пожаловать в облако Logto! Мы хотели бы узнать вас получше.',
description:
'Сделайте свой опыт работы с Logto уникальным для вас, узнав вас получше. Ваша информация надежно защищена.',
project_field: 'Я использую Logto для',
project_options: {
personal: 'Личного проекта',
company: 'Компании',
},
company_name_field: 'Название компании',
company_name_placeholder: 'Acme.co',
stage_field: 'На каком этапе находится ваш продукт сейчас?',
stage_options: {
new_product: 'Начать новый проект и ищете быстрое, заранее созданное решение',
existing_product:
'Миграция с текущей аутентификации (например, собственная разработка, Auth0, Cognito, Microsoft)',
target_enterprise_ready:
'Я только что получил больших клиентов и теперь готовлю свой продукт к продаже предприятиям',
},
additional_features_field: 'Хотели бы вы сообщить нам что-то еще?',
additional_features_options: {
customize_ui_and_flow:
'Построить и управлять своим собственным пользовательским интерфейсом, а не просто использовать заранее созданное и настраиваемое решение Logto',
compliance: 'SOC2 и GDPR - обязательные требования',
export_user_data: 'Необходима возможность экспортировать данные пользователей из Logto',
budget_control: 'У меня очень тщательный контроль над бюджетом',
bring_own_auth:
'Имею собственные службы аутентификации и просто нужны некоторые возможности Logto',
others: 'Ничего из вышеперечисленного',
},
},
create_tenant: { create_tenant: {
page_title: 'Создать арендатора', page_title: 'Создать арендатора',
title: 'Создайте вашего первого арендатора', title: 'Создайте вашего первого арендатора',
@ -41,46 +9,13 @@ const cloud = {
'Арендатор это изолированная среда, где вы можете управлять пользовательскими идентификаторами, приложениями и всеми другими ресурсами Logto.', 'Арендатор это изолированная среда, где вы можете управлять пользовательскими идентификаторами, приложениями и всеми другими ресурсами Logto.',
invite_collaborators: 'Пригласите ваших сотрудников по электронной почте', invite_collaborators: 'Пригласите ваших сотрудников по электронной почте',
}, },
sie: { social_callback: {
page_title: 'Настройка опыта входа',
title: 'Давайте сначала легко настроим ваш опыт входа',
inspire: {
title: 'Создайте убедительные примеры',
description:
'Чувствуете неуверенность в опыте входа? Просто нажмите «Вдохновить меня» и позвольте волшебству совершиться!',
inspire_me: 'Вдохнови меня',
},
logo_field: 'Логотип приложения',
color_field: 'Цвет бренда',
identifier_field: 'Идентификатор',
identifier_options: {
email: 'Электронная почта',
phone: 'Номер телефона',
user_name: 'Имя пользователя',
},
authn_field: 'Аутентификация',
authn_options: {
password: 'Пароль',
verification_code: 'Код подтверждения',
},
social_field: 'Вход через социальную сеть',
finish_and_done: 'Готово',
preview: {
mobile_tab: 'Мобильный',
web_tab: 'Веб',
},
connectors: {
unlocked_later: 'Разблокируется позже',
unlocked_later_tip:
'После того, как завершите процесс настройки и войдете в продукт, у вас появится доступ к большему количеству методов входа через социальные сети.',
notice:
'Пожалуйста, не используйте демонстрационный коннектор для производственных целей. После тестирования удалите демонстрационный коннектор и настройте собственный коннектор со своими учетными данными.',
},
},
socialCallback: {
title: 'Вход выполнен успешно', title: 'Вход выполнен успешно',
description: description:
'Вы успешно вошли, используя свою учетную запись в социальной сети. Чтобы обеспечить безпроблемную интеграцию и доступ ко всем функциям Logto, рекомендуем настроить свой собственный социальный коннектор.', 'Вы успешно вошли, используя свою учетную запись в социальной сети. Чтобы обеспечить безпроблемную интеграцию и доступ ко всем функциям Logto, рекомендуем настроить свой собственный социальный коннектор.',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Создать арендатора', create_tenant: 'Создать арендатора',

View file

@ -2,38 +2,6 @@ const cloud = {
general: { general: {
onboarding: 'Başlatma', onboarding: 'Başlatma',
}, },
welcome: {
page_title: 'Hoş geldiniz',
title: "Logto Cloud'a hoş geldiniz! Sizi biraz tanımak istiyoruz.",
description:
'Sizi daha iyi tanıyarak Logto deneyiminizi size özel hale getirelim. Bilgileriniz bizimle güvende.',
project_field: "Logto'yu kullanıyorum çünkü",
project_options: {
personal: 'Kişisel proje',
company: 'Şirket projesi',
},
company_name_field: 'Şirket adı',
company_name_placeholder: 'Acme.co',
stage_field: 'Ürününüz şu anda hangi aşamada?',
stage_options: {
new_product: 'Yeni bir proje başlatmak ve hızlı, hazır bir çözüm arıyorum',
existing_product:
'Mevcut kimlik doğrulamadan göç etmek (ör. özelleştirilmiş, Auth0, Cognito, Microsoft)',
target_enterprise_ready:
'Yeni büyük müşteriler kazandım ve ürünümü şimdi kurumsal müşterilere satılabilir hale getiriyorum',
},
additional_features_field: 'Bilmemizi istediğiniz başka bir şey var mı?',
additional_features_options: {
customize_ui_and_flow:
"Kendi UI'ımı inşa etmek ve yönetmek, sadece Logto'nun önceden yapılandırılmış ve özelleştirilebilir çözümünü kullanmamak",
compliance: 'SOC2 ve GDPR olmazsa olmaz',
export_user_data: "Kullanıcı verilerini Logto'dan dışa aktarma yeteneğine ihtiyacım var",
budget_control: 'Çok sıkı bir bütçe kontrolüm var',
bring_own_auth:
'Kendi kimlik doğrulama hizmetlerim var ve sadece bazı Logto özelliklerine ihtiyacım var',
others: 'Yukarıdakilerden hiçbiri',
},
},
create_tenant: { create_tenant: {
page_title: 'Kiracı oluştur', page_title: 'Kiracı oluştur',
title: 'İlk kiracınızı oluşturun', title: 'İlk kiracınızı oluşturun',
@ -41,46 +9,13 @@ const cloud = {
'Bir kiracı, kullanıcı kimliklerini, uygulamaları ve diğer tüm Logto kaynaklarını yönetebileceğiniz izole bir ortamdır.', 'Bir kiracı, kullanıcı kimliklerini, uygulamaları ve diğer tüm Logto kaynaklarını yönetebileceğiniz izole bir ortamdır.',
invite_collaborators: 'E-posta ile işbirlikçilerinizi davet edin', invite_collaborators: 'E-posta ile işbirlikçilerinizi davet edin',
}, },
sie: { social_callback: {
page_title: 'Oturum açma deneyimini özelleştirin',
title: 'Öncelikle giriş deneyiminizi kolaylıkla özelleştirin',
inspire: {
title: 'Etkileyici örnekler oluşturun',
description:
'Giriş deneyiminizden emin değilseniz, sadece "Beni İlhamla"ya tıklayın ve sihrin gerçekleşmesine izin verin!',
inspire_me: 'Beni ilhamla',
},
logo_field: 'Uygulama Logosu',
color_field: 'Marka rengi',
identifier_field: 'Tanımlayıcı',
identifier_options: {
email: 'E-posta',
phone: 'Telefon',
user_name: 'Kullanıcı adı',
},
authn_field: 'Kimlik doğrulama',
authn_options: {
password: 'Şifre',
verification_code: 'Doğrulama kodu',
},
social_field: 'Sosyal oturum açma',
finish_and_done: 'Bitir ve tamamla',
preview: {
mobile_tab: 'Mobil',
web_tab: 'Web',
},
connectors: {
unlocked_later: 'Daha sonra kilidi açılacak',
unlocked_later_tip:
'Onboarding sürecini tamamladıktan ve ürüne girdikten sonra, daha fazla sosyal oturum açma yöntemine erişiminiz olacaktır.',
notice:
'Lütfen üretim amaçlı olarak demo konektörünü kullanmaktan kaçının. Testi tamamladıktan sonra lütfen demo konektörünü silin ve kendi kimlik bilgilerinizle kendi konektörünüzü ayarlayın.',
},
},
socialCallback: {
title: 'Başarıyla giriş yaptınız', title: 'Başarıyla giriş yaptınız',
description: description:
"Sosyal hesabınızı kullanarak başarılı bir şekilde giriş yaptınız. Logto'nun tüm özelliklerine sorunsuz entegrasyon ve erişim sağlamak için kendi sosyal konektörünüzü yapılandırmaya devam etmenizi öneririz.", "Sosyal hesabınızı kullanarak başarılı bir şekilde giriş yaptınız. Logto'nun tüm özelliklerine sorunsuz entegrasyon ve erişim sağlamak için kendi sosyal konektörünüzü yapılandırmaya devam etmenizi öneririz.",
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: 'Kiracı Oluştur', create_tenant: 'Kiracı Oluştur',

View file

@ -2,77 +2,19 @@ const cloud = {
general: { general: {
onboarding: '入门', onboarding: '入门',
}, },
welcome: {
page_title: '欢迎',
title: '欢迎来到 Logto Cloud我们很想了解你。',
description: '通过更好地了解你,我们可以使你的 Logto 体验更加个性化。你的信息是安全的。',
project_field: '我使用 Logto 是为了',
project_options: {
personal: '个人项目',
company: '公司项目',
},
company_name_field: '公司名称',
company_name_placeholder: 'Acme.co',
stage_field: '你的产品目前处于哪个阶段?',
stage_options: {
new_product: '开始一个新项目,寻找一个快速的即插即用解决方案',
existing_product: '从当前的身份验证系统迁移例如自建、Auth0、Cognito、Microsoft',
target_enterprise_ready: '我刚刚赢得了更大的客户,现在希望让我的产品适应企业销售',
},
additional_features_field: '你还有其他想告诉我们的信息么?',
additional_features_options: {
customize_ui_and_flow: '构建并管理我的 UI而不仅仅使用 Logto 预构建和可定制的解决方案',
compliance: 'SOC2 和 GDPR 是必须的',
export_user_data: '需要能够从 Logto 导出用户数据',
budget_control: '我有非常严格的预算控制',
bring_own_auth: '有自己的身份验证服务,只需要一些 Logto 功能',
others: '以上都不是',
},
},
create_tenant: { create_tenant: {
page_title: '创建租户', page_title: '创建租户',
title: '创建你的第一个租户', title: '创建你的第一个租户',
description: '租户是一个隔离的环境,你可以在其中管理用户身份、应用程序和所有其他 Logto 资源。', description: '租户是一个隔离的环境,你可以在其中管理用户身份、应用程序和所有其他 Logto 资源。',
invite_collaborators: '通过电子邮件邀请你的合作者', invite_collaborators: '通过电子邮件邀请你的合作者',
}, },
sie: { social_callback: {
page_title: '定制登录体验',
title: '让我们轻松定制你的登录体验',
inspire: {
title: '创建引人入胜的示例',
description: '对登录体验不确定吗?只需点击“启发我”,让魔法发生!',
inspire_me: '来点灵感',
},
logo_field: '应用商标',
color_field: '品牌颜色',
identifier_field: '标识符',
identifier_options: {
email: '电子邮件',
phone: '电话',
user_name: '用户名',
},
authn_field: '身份验证',
authn_options: {
password: '密码',
verification_code: '验证码',
},
social_field: '社交登录',
finish_and_done: '完成并完成',
preview: {
mobile_tab: '移动端',
web_tab: '网页端',
},
connectors: {
unlocked_later: '稍后解锁',
unlocked_later_tip: '完成入门流程并进入产品后,你将获得访问更多社交登录方式的权限。',
notice:
'请勿将演示连接器用于生产目的。 完成测试后,请删除演示连接器并使用你的凭证设置自己的连接器。',
},
},
socialCallback: {
title: '你已成功登录', title: '你已成功登录',
description: description:
'你已成功使用社交账户登录。为确保与 Logto 的无缝集成并获得所有功能的访问权限,我们建议你继续配置自己的社交连接器。', '你已成功使用社交账户登录。为确保与 Logto 的无缝集成并获得所有功能的访问权限,我们建议你继续配置自己的社交连接器。',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: '创建租户', create_tenant: '创建租户',

View file

@ -2,77 +2,19 @@ const cloud = {
general: { general: {
onboarding: '入門', onboarding: '入門',
}, },
welcome: {
page_title: '歡迎',
title: '歡迎來到 Logto Cloud我們很想了解你的一些信息。',
description: '通過更好地了解你,我們可以使你的 Logto 體驗更加個性化。你的信息是安全的。',
project_field: '我使用 Logto 是為了',
project_options: {
personal: '個人專案',
company: '公司專案',
},
company_name_field: '公司名稱',
company_name_placeholder: 'Acme.co',
stage_field: '你的產品目前處於哪個階段?',
stage_options: {
new_product: '啟動新項目並尋找快速、開箱即用的解決方案',
existing_product: '從當前身份驗證 (例如自建、Auth0、Cognito、Microsoft) 遷移',
target_enterprise_ready: '我剛剛贏得了更大的客戶,現在要讓我的產品準備面向企業銷售',
},
additional_features_field: '你還有其他事情要告訴我們嗎?',
additional_features_options: {
customize_ui_and_flow: '構建並管理自己的 UI而不僅僅使用 Logto 預先構建和可定製的解決方案',
compliance: 'SOC2 和 GDPR 是必不可少的',
export_user_data: '需要從 Logto 導出用戶數據的能力',
budget_control: '我有非常嚴格的預算控制',
bring_own_auth: '有自己的身份驗證服務,只需要一些 Logto 功能',
others: '以上都不是',
},
},
create_tenant: { create_tenant: {
page_title: '創建租戶', page_title: '創建租戶',
title: '創建你的第一個租戶', title: '創建你的第一個租戶',
description: '租戶是一個獨立的環境,在這裡你可以管理用戶身份、應用程式和所有其他 Logto 資源。', description: '租戶是一個獨立的環境,在這裡你可以管理用戶身份、應用程式和所有其他 Logto 資源。',
invite_collaborators: '通過電子郵件邀請你的合作者', invite_collaborators: '通過電子郵件邀請你的合作者',
}, },
sie: { social_callback: {
page_title: '定制登錄體驗',
title: '讓我們輕鬆定制你的登錄體驗',
inspire: {
title: '創建引人入勝的示例',
description: '對登錄體驗不確定嗎?只需點擊“啟發我”,讓魔法發生!',
inspire_me: '來點靈感',
},
logo_field: '應用商標',
color_field: '品牌顏色',
identifier_field: '標識符',
identifier_options: {
email: '電子郵件',
phone: '電話',
user_name: '用戶名',
},
authn_field: '身份驗證',
authn_options: {
password: '密碼',
verification_code: '驗證碼',
},
social_field: '社交登錄',
finish_and_done: '完成並完成',
preview: {
mobile_tab: '移動端',
web_tab: '網頁端',
},
connectors: {
unlocked_later: '稍後解鎖',
unlocked_later_tip: '完成入門流程並進入產品後,你將獲得訪問更多社交登錄方式的權限。',
notice:
'請勿將演示連接器用於生產目的。完成測試後,請刪除演示連接器並使用你的憑證設置自己的連接器。',
},
},
socialCallback: {
title: '你已成功登錄', title: '你已成功登錄',
description: description:
'你已成功使用社交帳戶登錄。為確保與 Logto 的無縫集成並獲得所有功能的訪問權限,我們建議你繼續配置自己的社交連接器。', '你已成功使用社交帳戶登錄。為確保與 Logto 的無縫集成並獲得所有功能的訪問權限,我們建議你繼續配置自己的社交連接器。',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: '創建租戶', create_tenant: '創建租戶',

View file

@ -2,77 +2,19 @@ const cloud = {
general: { general: {
onboarding: '入門', onboarding: '入門',
}, },
welcome: {
page_title: '歡迎',
title: '歡迎來到 Logto Cloud我們很想了解一些關於你的資訊。',
description: '通過更好地了解你,我們可以使你的 Logto 體驗更加個性化。你的信息是安全的。',
project_field: '我使用 Logto 是為了',
project_options: {
personal: '個人專案',
company: '公司專案',
},
company_name_field: '公司名稱',
company_name_placeholder: 'Acme.co',
stage_field: '你的產品目前處於什麼階段?',
stage_options: {
new_product: '啟動新項目並尋找快速、即插即用的解決方案',
existing_product: '從當前身份驗證進行遷移例如自行建立、Auth0、Cognito、Microsoft',
target_enterprise_ready: '我剛剛簽下了更大的客戶,現在要使我的產品能夠銷售給企業',
},
additional_features_field: '你還有其他想讓我們知道的事情嗎?',
additional_features_options: {
customize_ui_and_flow: '構建和管理我的自己的 UI而不僅僅是使用 Logto 預製和可定制的解決方案',
compliance: 'SOC2 和 GDPR 是必不可少的',
export_user_data: '需要從 Logto 導出用戶數據的能力',
budget_control: '我有非常嚴格的預算控制',
bring_own_auth: '擁有自己的身份驗證服務,只需要一些 Logto 功能',
others: '以上都不是',
},
},
create_tenant: { create_tenant: {
page_title: '新增租戶', page_title: '新增租戶',
title: '創建你的第一個租戶', title: '創建你的第一個租戶',
description: '租戶是一個隔離的環境,你可以在其中管理用戶身份、應用程式和所有其他 Logto 資源。', description: '租戶是一個隔離的環境,你可以在其中管理用戶身份、應用程式和所有其他 Logto 資源。',
invite_collaborators: '通過電子郵件邀請你的合作者', invite_collaborators: '通過電子郵件邀請你的合作者',
}, },
sie: { social_callback: {
page_title: '定制登錄體驗',
title: '讓我們輕鬆定制你的登錄體驗',
inspire: {
title: '創建引人入勝的示例',
description: '對登錄體驗不確定嗎?只需點擊“啓發我”,讓魔法發生!',
inspire_me: '來點靈感',
},
logo_field: '應用商標',
color_field: '品牌顏色',
identifier_field: '標識符',
identifier_options: {
email: '電子郵件',
phone: '電話',
user_name: '用戶名',
},
authn_field: '身份驗證',
authn_options: {
password: '密碼',
verification_code: '驗證碼',
},
social_field: '社交登錄',
finish_and_done: '完成並完成',
preview: {
mobile_tab: '移動端',
web_tab: '網頁端',
},
connectors: {
unlocked_later: '稍後解鎖',
unlocked_later_tip: '完成入門流程並進入產品後,你將獲得訪問更多社交登錄方式的權限。',
notice:
'請勿將演示連接器用於生產目的。完成測試後,請刪除演示連接器並使用你的憑證設置自己的連接器。',
},
},
socialCallback: {
title: '你已成功登錄', title: '你已成功登錄',
description: description:
'你已成功使用社交帳戶登錄。為確保與 Logto 的無縫集成並獲得所有功能的訪問權限,我們建議你繼續配置自己的社交連接器。', '你已成功使用社交帳戶登錄。為確保與 Logto 的無縫集成並獲得所有功能的訪問權限,我們建議你繼續配置自己的社交連接器。',
/** UNTRANSLATED */
notice:
"Please avoid using the demo connector for production purposes. Once you've completed testing, kindly delete the demo connector and set up your own connector with your credentials.",
}, },
tenant: { tenant: {
create_tenant: '新增租戶', create_tenant: '新增租戶',