diff --git a/packages/cli/src/commands/install/utils.ts b/packages/cli/src/commands/install/utils.ts index 83b873fa0..7324e0a0f 100644 --- a/packages/cli/src/commands/install/utils.ts +++ b/packages/cli/src/commands/install/utils.ts @@ -159,7 +159,7 @@ export const decompress = async (toPath: string, tarPath: string) => { export const seedDatabase = async (instancePath: string, cloud: boolean) => { try { const pool = await createPoolAndDatabaseIfNeeded(); - await seedByPool(pool); + await seedByPool(pool, cloud); await pool.end(); } catch (error: unknown) { consoleLog.error(error); diff --git a/packages/console/src/assets/images/tenant-landing-page-dark.svg b/packages/console/src/assets/images/tenant-landing-page-dark.svg new file mode 100644 index 000000000..d3060f0cd --- /dev/null +++ b/packages/console/src/assets/images/tenant-landing-page-dark.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/console/src/assets/images/tenant-landing-page.svg b/packages/console/src/assets/images/tenant-landing-page.svg new file mode 100644 index 000000000..7e7a14bd6 --- /dev/null +++ b/packages/console/src/assets/images/tenant-landing-page.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/console/src/cloud/pages/Main/Redirect.tsx b/packages/console/src/cloud/pages/Main/Redirect.tsx index eac7303ed..7b222ec90 100644 --- a/packages/console/src/cloud/pages/Main/Redirect.tsx +++ b/packages/console/src/cloud/pages/Main/Redirect.tsx @@ -15,7 +15,7 @@ type Props = { function Redirect({ tenants, toTenantId }: Props) { const { getAccessToken, signIn } = useLogto(); const tenant = tenants.find(({ id }) => id === toTenantId); - const { setIsSettle } = useContext(TenantsContext); + const { setIsSettle, navigate } = useContext(TenantsContext); const href = useHref(toTenantId + '/callback'); useEffect(() => { @@ -36,7 +36,8 @@ function Redirect({ tenants, toTenantId }: Props) { }, [getAccessToken, href, setIsSettle, signIn, tenant]); if (!tenant) { - return
Forbidden
; + /** Fallback to another available tenant instead of showing `Forbidden`. */ + navigate(tenants[0]?.id ?? ''); } return ; diff --git a/packages/console/src/cloud/pages/Main/CreateTenantModal/index.module.scss b/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal/index.module.scss similarity index 100% rename from packages/console/src/cloud/pages/Main/CreateTenantModal/index.module.scss rename to packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal/index.module.scss diff --git a/packages/console/src/cloud/pages/Main/CreateTenantModal/index.tsx b/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal/index.tsx similarity index 100% rename from packages/console/src/cloud/pages/Main/CreateTenantModal/index.tsx rename to packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal/index.tsx diff --git a/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.module.scss b/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.module.scss new file mode 100644 index 000000000..c1de05e70 --- /dev/null +++ b/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.module.scss @@ -0,0 +1,31 @@ +@use '@/scss/underscore' as _; + +.placeholder { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + + .image { + > svg { + width: 256px; + height: 256px; + } + } + + .title { + font: var(--font-title-1); + } + + .description { + max-width: 470px; + font: var(--font-body-2); + color: var(--color-text-secondary); + margin-top: _.unit(2); + } + + .button { + margin-top: _.unit(6); + } +} + diff --git a/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.tsx b/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.tsx new file mode 100644 index 000000000..648566c69 --- /dev/null +++ b/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.tsx @@ -0,0 +1,72 @@ +import { Theme } from '@logto/schemas'; +import type { TenantInfo } from '@logto/schemas/models'; +import classNames from 'classnames'; +import { useState } from 'react'; +import { toast } from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; + +import Plus from '@/assets/icons/plus.svg'; +import TenantLandingPageImageDark from '@/assets/images/tenant-landing-page-dark.svg'; +import TenantLandingPageImage from '@/assets/images/tenant-landing-page.svg'; +import Button from '@/ds-components/Button'; +import DynamicT from '@/ds-components/DynamicT'; +import useTenants from '@/hooks/use-tenants'; +import useTheme from '@/hooks/use-theme'; + +import CreateTenantModal from './CreateTenantModal'; +import * as styles from './index.module.scss'; + +type Props = { + className?: string; +}; + +function TenantLandingPageContent({ className }: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const { tenants, mutate } = useTenants(); + const theme = useTheme(); + + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + + if (tenants?.length) { + return null; + } + + return ( + <> +
+
+ {theme === Theme.Light ? : } +
+
+ +
+
+ +
+
+ { + if (tenant) { + void mutate(); + toast.success(t('tenants.tenant_created', { name: tenant.name })); + window.location.assign(new URL(`/${tenant.id}`, window.location.origin).toString()); + } + setIsCreateModalOpen(false); + }} + /> + + ); +} + +export default TenantLandingPageContent; diff --git a/packages/console/src/cloud/pages/Main/TenantLandingPage/index.module.scss b/packages/console/src/cloud/pages/Main/TenantLandingPage/index.module.scss new file mode 100644 index 000000000..772f9905b --- /dev/null +++ b/packages/console/src/cloud/pages/Main/TenantLandingPage/index.module.scss @@ -0,0 +1,45 @@ +@use '@/scss/underscore' as _; + +.pageContainer { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + height: 100%; + + .placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + flex-grow: 1; + transform: translateY(-32px); // Half of the topbar height, to make the placeholder vertically centered. + + .image { + > svg { + width: 256px; + height: 256px; + } + } + + .title { + font: var(--font-label-2); + } + + .description { + max-width: 470px; + font: var(--font-body-2); + color: var(--color-text-secondary); + margin-top: _.unit(2); + } + + .button { + margin-top: _.unit(6); + } + } +} + +.topbar { + z-index: 1; +} diff --git a/packages/console/src/cloud/pages/Main/TenantLandingPage/index.tsx b/packages/console/src/cloud/pages/Main/TenantLandingPage/index.tsx new file mode 100644 index 000000000..2d7abf6d1 --- /dev/null +++ b/packages/console/src/cloud/pages/Main/TenantLandingPage/index.tsx @@ -0,0 +1,15 @@ +import Topbar from '@/containers/AppContent/components/Topbar'; + +import TenantLandingPageContent from './TenantLandingPageContent'; +import * as styles from './index.module.scss'; + +function TenantLandingPage() { + return ( +
+ + +
+ ); +} + +export default TenantLandingPage; diff --git a/packages/console/src/cloud/pages/Main/Tenants.tsx b/packages/console/src/cloud/pages/Main/Tenants.tsx deleted file mode 100644 index a95eb359d..000000000 --- a/packages/console/src/cloud/pages/Main/Tenants.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { type TenantInfo, TenantTag } from '@logto/schemas/models'; -import { useCallback, useContext, useEffect } from 'react'; - -import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; -import AppLoading from '@/components/AppLoading'; -import { TenantsContext } from '@/contexts/TenantsProvider'; -import Button from '@/ds-components/Button'; -import DangerousRaw from '@/ds-components/DangerousRaw'; - -import * as styles from './index.module.scss'; - -type Props = { - data: TenantInfo[]; - onAdd: (tenant: TenantInfo) => void; -}; - -function Tenants({ data, onAdd }: Props) { - const api = useCloudApi(); - const { navigate } = useContext(TenantsContext); - - const createTenant = useCallback(async () => { - onAdd( - /** - * `name` and `tag` are required for POST /tenants API, add fixed value to avoid throwing error. - * This page page will be removed in upcoming changes on multi-tenancy cloud console. - */ - await api.post('/api/tenants', { body: { name: 'My Project', tag: TenantTag.Development } }) - ); - }, [api, onAdd]); - - useEffect(() => { - if (data.length > 1) { - return; - } - - if (data[0]) { - navigate(data[0].id); - } else { - void createTenant(); - } - }, [createTenant, data, navigate]); - - if (data.length > 1) { - return ( -
-

Choose a tenant

- {data.map(({ id }) => ( - { - event.preventDefault(); - navigate(id); - }} - > -
- ); - } - - return ; -} - -export default Tenants; diff --git a/packages/console/src/cloud/pages/Main/index.tsx b/packages/console/src/cloud/pages/Main/index.tsx index 3909d3ed3..89fd97053 100644 --- a/packages/console/src/cloud/pages/Main/index.tsx +++ b/packages/console/src/cloud/pages/Main/index.tsx @@ -1,8 +1,7 @@ import { useLogto } from '@logto/react'; -import { type TenantInfo } from '@logto/schemas/models'; import { conditional, yes } from '@silverhand/essentials'; import { HTTPError } from 'ky'; -import { useCallback, useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useHref, useSearchParams } from 'react-router-dom'; import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; @@ -11,13 +10,15 @@ import AppLoading from '@/components/AppLoading'; import SessionExpired from '@/components/SessionExpired'; import { searchKeys } from '@/consts'; import { TenantsContext } from '@/contexts/TenantsProvider'; +import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data'; import Redirect from './Redirect'; -import Tenants from './Tenants'; +import TenantLandingPage from './TenantLandingPage'; function Protected() { const api = useCloudApi(); - const { tenants, setTenants, currentTenantId } = useContext(TenantsContext); + const { tenants, setTenants, currentTenantId, navigate } = useContext(TenantsContext); + const { isOnboarding, isLoaded } = useUserOnboardingData(); const [error, setError] = useState(); useEffect(() => { @@ -37,12 +38,23 @@ function Protected() { } }, [api, setTenants, tenants]); - const onAdd = useCallback( - (tenant: TenantInfo) => { - setTenants([...(tenants ?? []), tenant]); - }, - [setTenants, tenants] - ); + useEffect(() => { + const createFirstTenant = async () => { + setError(undefined); + + try { + const newTenant = await api.post('/api/tenants', { body: {} }); // Use DB default value. + setTenants([newTenant]); + navigate(newTenant.id); + } catch (error: unknown) { + setError(error instanceof Error ? error : new Error(String(error))); + } + }; + + if (isLoaded && isOnboarding && tenants?.length === 0) { + void createFirstTenant(); + } + }, [api, isOnboarding, isLoaded, setTenants, tenants, navigate]); if (error) { if (error instanceof HTTPError && error.response.status === 401) { @@ -53,11 +65,26 @@ function Protected() { } if (tenants) { - if (currentTenantId) { - return ; + /** + * Redirect to the first tenant if the current tenant ID is not set or can not be found. + * + * `currentTenantId` can be empty string, so that Boolean is required and `??` is + * not applicable for current case. + */ + + // eslint-disable-next-line no-extra-boolean-cast + const toTenantId = Boolean(currentTenantId) ? currentTenantId : tenants[0]?.id; + if (toTenantId) { + return ; } - return ; + /** + * Will create a new tenant for new users that need go through onboarding process, + * but create tenant takes time, the screen will have a glance of landing page of empty tenant. + */ + if (isLoaded && !isOnboarding) { + return ; + } } return ; diff --git a/packages/console/src/consts/env.ts b/packages/console/src/consts/env.ts index 147fa6d25..39c14a317 100644 --- a/packages/console/src/consts/env.ts +++ b/packages/console/src/consts/env.ts @@ -1,5 +1,4 @@ import { yes } from '@silverhand/essentials'; -export const isProduction = process.env.NODE_ENV === 'production'; export const isCloud = yes(process.env.IS_CLOUD); export const adminEndpoint = process.env.ADMIN_ENDPOINT; diff --git a/packages/console/src/containers/AppContent/components/Topbar/Contact/index.module.scss b/packages/console/src/containers/AppContent/components/Topbar/Contact/index.module.scss new file mode 100644 index 000000000..0437db49d --- /dev/null +++ b/packages/console/src/containers/AppContent/components/Topbar/Contact/index.module.scss @@ -0,0 +1,5 @@ +.icon { + width: 20px; + height: 20px; + color: var(--color-neutral-variant-30); +} diff --git a/packages/console/src/containers/AppContent/components/Topbar/Contact/index.tsx b/packages/console/src/containers/AppContent/components/Topbar/Contact/index.tsx index 9ff9ff958..1b868a135 100644 --- a/packages/console/src/containers/AppContent/components/Topbar/Contact/index.tsx +++ b/packages/console/src/containers/AppContent/components/Topbar/Contact/index.tsx @@ -4,6 +4,7 @@ import ContactIcon from '@/assets/icons/contact-us.svg'; import IconButton from '@/ds-components/IconButton'; import ContactModal from './ContactModal'; +import * as styles from './index.module.scss'; function Contact() { const [isContactOpen, setIsContactOpen] = useState(false); @@ -12,6 +13,7 @@ function Contact() { <> { setIsContactOpen(true); }} diff --git a/packages/console/src/containers/AppContent/components/Topbar/DocumentNavButton/index.module.scss b/packages/console/src/containers/AppContent/components/Topbar/DocumentNavButton/index.module.scss index 56681fc5d..bb7aaaf53 100644 --- a/packages/console/src/containers/AppContent/components/Topbar/DocumentNavButton/index.module.scss +++ b/packages/console/src/containers/AppContent/components/Topbar/DocumentNavButton/index.module.scss @@ -27,6 +27,7 @@ .icon { width: 20px; height: 20px; + color: var(--color-neutral-variant-30); } span { diff --git a/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.module.scss b/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.module.scss index 9019fe596..5f67b00fe 100644 --- a/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.module.scss +++ b/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.module.scss @@ -4,7 +4,8 @@ display: flex; align-items: center; padding: _.unit(1); - margin-left: _.unit(5); + padding-left: _.unit(2); + margin-left: _.unit(4); max-width: 500px; border-radius: _.unit(2); transition: background-color 0.2s ease-in-out; @@ -45,7 +46,12 @@ background-color: var(--color-neutral-80); flex-shrink: 0; position: absolute; - left: _.unit(-4); + left: _.unit(-3); + } + + &:hover::before { + pointer-events: none; + cursor: default; } } @@ -89,7 +95,7 @@ margin-left: auto; &.visible { - color: var(--color-brand-40); + color: var(--color-primary-40); } } } @@ -116,6 +122,6 @@ .icon { width: 20px; height: 20px; - color: var(--color-placeholder); + color: var(--color-neutral-50); } } diff --git a/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.tsx b/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.tsx index 71c0a3cc9..c0d263fc3 100644 --- a/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.tsx +++ b/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'; import KeyboardArrowDown from '@/assets/icons/keyboard-arrow-down.svg'; import PlusSign from '@/assets/icons/plus.svg'; import Tick from '@/assets/icons/tick.svg'; -import CreateTenantModal from '@/cloud/pages/Main/CreateTenantModal'; +import CreateTenantModal from '@/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal'; import AppError from '@/components/AppError'; import Divider from '@/ds-components/Divider'; import Dropdown, { DropdownItem } from '@/ds-components/Dropdown'; diff --git a/packages/console/src/containers/AppContent/components/Topbar/index.tsx b/packages/console/src/containers/AppContent/components/Topbar/index.tsx index 9c0617a7d..a0b71b432 100644 --- a/packages/console/src/containers/AppContent/components/Topbar/index.tsx +++ b/packages/console/src/containers/AppContent/components/Topbar/index.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import CloudLogo from '@/assets/images/cloud-logo.svg'; import Logo from '@/assets/images/logo.svg'; -import { isProduction, isCloud } from '@/consts/env'; +import { isCloud } from '@/consts/env'; import Spacer from '@/ds-components/Spacer'; import EarlyBirdGift from '@/onboarding/components/EarlyBirdGift'; @@ -24,7 +24,7 @@ function Topbar({ className }: Props) { return (
- {isCloud && !isProduction && } + {isCloud && } {!isCloud && ( <>
diff --git a/packages/console/src/containers/ConsoleContent/index.tsx b/packages/console/src/containers/ConsoleContent/index.tsx index 1c7863895..6d1e0d70e 100644 --- a/packages/console/src/containers/ConsoleContent/index.tsx +++ b/packages/console/src/containers/ConsoleContent/index.tsx @@ -9,7 +9,7 @@ import { WebhookDetailsTabs, TenantSettingsTabs, } from '@/consts'; -import { isCloud, isProduction } from '@/consts/env'; +import { isCloud } from '@/consts/env'; import OverlayScrollbar from '@/ds-components/OverlayScrollbar'; import ApiResourceDetails from '@/pages/ApiResourceDetails'; import ApiResourcePermissions from '@/pages/ApiResourceDetails/ApiResourcePermissions'; @@ -145,9 +145,7 @@ function ConsoleContent() { {isCloud && ( }> } /> - {!isProduction && ( - } /> - )} + } /> } /> )} diff --git a/packages/console/src/contexts/TenantsProvider.tsx b/packages/console/src/contexts/TenantsProvider.tsx index 7f8a2294b..b1b507e02 100644 --- a/packages/console/src/contexts/TenantsProvider.tsx +++ b/packages/console/src/contexts/TenantsProvider.tsx @@ -46,11 +46,20 @@ function TenantsProvider({ children }: Props) { const navigate = useCallback((tenantId: string, options?: NavigateOptions) => { if (options?.replace) { - window.history.replaceState(options.state ?? {}, '', '/' + tenantId); + window.history.replaceState( + options.state ?? {}, + '', + new URL(`/${tenantId}`, window.location.origin).toString() + ); return; } - window.history.pushState(options?.state ?? {}, '', '/' + tenantId); + + window.history.pushState( + options?.state ?? {}, + '', + new URL(`/${tenantId}`, window.location.origin).toString() + ); setCurrentTenantId(tenantId); }, []); diff --git a/packages/console/src/ds-components/RadioGroup/Radio.module.scss b/packages/console/src/ds-components/RadioGroup/Radio.module.scss index ccade727a..706819139 100644 --- a/packages/console/src/ds-components/RadioGroup/Radio.module.scss +++ b/packages/console/src/ds-components/RadioGroup/Radio.module.scss @@ -205,8 +205,8 @@ } .small.checked { - color: var(--color-primary); - border-color: var(--color-primary); + color: var(--color-text-link); + border-color: var(--color-text-link); background-color: var(--color-hover-variant); &:not(:first-child)::before { @@ -216,7 +216,7 @@ top: -1px; left: -1px; bottom: -1px; - background-color: var(--color-primary); + background-color: var(--color-text-link); } } diff --git a/packages/console/src/pages/TenantSettings/TenantBasicSettings/index.tsx b/packages/console/src/pages/TenantSettings/TenantBasicSettings/index.tsx index 42457c762..b37aca90f 100644 --- a/packages/console/src/pages/TenantSettings/TenantBasicSettings/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantBasicSettings/index.tsx @@ -2,6 +2,8 @@ import { type TenantInfo, TenantTag } from '@logto/schemas/models'; import classNames from 'classnames'; import { useEffect, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; +import { toast } from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; import AppError from '@/components/AppError'; @@ -24,6 +26,7 @@ const tenantProfileToForm = (tenant?: TenantInfo): TenantSettingsForm => { }; function TenantBasicSettings() { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const api = useCloudApi(); const { currentTenant, @@ -65,6 +68,7 @@ function TenantBasicSettings() { }); reset({ profile: { name, tag } }); void mutate(); + toast.success(t('tenant_settings.profile.tenant_info_saved')); } catch (error: unknown) { setError( error instanceof Error @@ -98,10 +102,7 @@ function TenantBasicSettings() { try { await api.delete(`/api/tenants/:tenantId`, { params: { tenantId: currentTenantId } }); setIsDeletionModalOpen(false); - await mutate(); - if (tenants?.[0]?.id) { - window.open(new URL(`/${tenants[0].id}`, window.location.origin).toString(), '_self'); - } + void mutate(); } catch (error: unknown) { setError( error instanceof Error @@ -113,6 +114,20 @@ function TenantBasicSettings() { } }; + useEffect(() => { + /** + * Redirect to the first tenant if the current tenant is deleted; + * Redirect to Cloud console landing page if there is no tenant. + */ + if (tenants && !tenants.some(({ id }) => id === currentTenantId)) { + window.location.assign( + tenants[0]?.id + ? new URL(`/${tenants[0]?.id}`, window.location.origin).toString() + : new URL(window.location.origin).toString() + ); + } + }, [currentTenantId, tenants]); + if (isLoading) { return ; } diff --git a/packages/console/src/pages/TenantSettings/index.tsx b/packages/console/src/pages/TenantSettings/index.tsx index d6790bfe8..87e8759c6 100644 --- a/packages/console/src/pages/TenantSettings/index.tsx +++ b/packages/console/src/pages/TenantSettings/index.tsx @@ -1,7 +1,6 @@ import { Outlet } from 'react-router-dom'; import { TenantSettingsTabs } from '@/consts'; -import { isProduction } from '@/consts/env'; import CardTitle from '@/ds-components/CardTitle'; import DynamicT from '@/ds-components/DynamicT'; import TabNav, { TabNavItem } from '@/ds-components/TabNav'; @@ -17,11 +16,9 @@ function TenantSettings() { className={styles.cardTitle} /> - {!isProduction && ( - - - - )} + + + diff --git a/packages/integration-tests/src/tests/ui-cloud/smoke.test.ts b/packages/integration-tests/src/tests/ui-cloud/smoke.test.ts index 872d634e4..c6bdaf278 100644 --- a/packages/integration-tests/src/tests/ui-cloud/smoke.test.ts +++ b/packages/integration-tests/src/tests/ui-cloud/smoke.test.ts @@ -1,10 +1,12 @@ +import { defaultTenantId } from '@logto/schemas'; import { appendPath } from '@silverhand/essentials'; import { setDefaultOptions } from 'expect-puppeteer'; import { logtoCloudUrl as logtoCloudUrlString, logtoConsoleUrl } from '#src/constants.js'; import { generatePassword } from '#src/utils.js'; -setDefaultOptions({ timeout: 2000 }); +await page.setViewport({ width: 1280, height: 720 }); +setDefaultOptions({ timeout: 5000 }); /** * NOTE: This test suite assumes test cases will run sequentially (which is Jest default). @@ -18,6 +20,8 @@ describe('smoke testing for cloud', () => { const logtoCloudUrl = new URL(logtoCloudUrlString); const adminTenantUrl = new URL(logtoConsoleUrl); // In dev mode, the console URL is actually for admin tenant + const createTenantName = 'new-tenant'; + it('can open with app element and navigate to register page', async () => { await page.goto(logtoCloudUrl.href); await page.waitForNavigation({ waitUntil: 'networkidle0' }); @@ -27,8 +31,6 @@ describe('smoke testing for cloud', () => { }); it('can register the first admin account', async () => { - await expect(page).toClick('button', { text: 'Create account' }); - await expect(page).toFill('input[name=identifier]', consoleUsername); await expect(page).toClick('button[name=submit]'); @@ -40,35 +42,12 @@ describe('smoke testing for cloud', () => { confirmPassword: consolePassword, }); await expect(page).toClick('button[name=submit]'); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); - expect(page.url()).toBe(logtoCloudUrl.href); - }); - - it('shows a tenant-select page with two tenants', async () => { - const tenantsWrapper = await page.waitForSelector('div[class$=wrapper]'); - - await expect(tenantsWrapper).toMatchElement('a:nth-of-type(1)', { text: 'default' }); - await expect(tenantsWrapper).toMatchElement('a:nth-of-type(2)', { text: 'admin' }); - }); - - it('can create another tenant', async () => { - await expect(page).toClick('button', { text: 'Create' }); - - await page.waitForTimeout(1000); - const tenants = await page.$$('div[class$=wrapper] > a'); - expect(tenants.length).toBe(3); - }); - - it('can enter the tenant just created', async () => { - const button = await page.waitForSelector('div[class$=wrapper] > a:last-of-type'); - const tenantId = await button.evaluate((element) => element.textContent); - - await button.click(); - - // Wait for our beautiful logo to show up - await page.waitForSelector('div[class$=topbar] > svg[viewbox][class$=logo]'); - expect(page.url()).toBe(new URL(`/${tenantId ?? ''}/onboarding/welcome`, logtoCloudUrl).href); + expect(page.url()).toBe( + appendPath(logtoCloudUrl, `/${defaultTenantId}/onboarding/welcome`).href + ); }); it('can complete the onboarding welcome process and enter the user survey page', async () => { @@ -129,6 +108,7 @@ describe('smoke testing for cloud', () => { await expect(page).toClick('div[class$=content] >button'); // Wait for the admin console to load + await page.waitForNavigation({ waitUntil: 'networkidle0' }); const mainContent = await page.waitForSelector('div[class$=main]:has(div[class$=title])'); await expect(mainContent).toMatchElement('div[class$=title]', { text: 'Something to explore to help you succeed', @@ -137,15 +117,84 @@ describe('smoke testing for cloud', () => { expect(new URL(page.url()).pathname.endsWith('/get-started')).toBeTruthy(); }); + it('can create a new tenant using tenant dropdown', async () => { + // Click 'current tenant card' locates in topbar + const currentTenantCard = await page.waitForSelector( + 'div[class$=topbar] > div[class$=currentTenantCard][role=button]:has(div[class$=name])' + ); + await expect(currentTenantCard).toMatchElement('div[class$=name]', { text: 'My Project' }); + await currentTenantCard.click(); + + await page.waitForTimeout(500); + const createTenantButton = await page.waitForSelector( + 'div[class$=ReactModalPortal] div[class$=dropdownContainer] > div[class$=dropdown] > div[class$=createTenantButton][role=button]:has(div)' + ); + await expect(createTenantButton).toMatchElement('div', { text: 'Create tenant' }); + await createTenantButton.click(); + + // Create tenant with name 'new-tenant' and tag 'production' + await page.waitForTimeout(500); + await page.waitForSelector( + 'div[class$=ReactModalPortal] div[class*=card][class$=medium] input[type=text][name=name]' + ); + await page.waitForSelector( + 'div[class$=ReactModalPortal] div[class*=radioGroup][class$=small] div[class*=radio][class$=small][role=radio] > div[class$=content]:has(input[value=production])' + ); + await expect(page).toFill( + 'div[class$=ReactModalPortal] div[class*=card][class$=medium] input[type=text][name=name]', + createTenantName + ); + await expect(page).toClick( + 'div[class$=ReactModalPortal] div[class*=radioGroup][class$=small] div[class*=radio][class$=small][role=radio] > div[class$=content]:has(input[value=production])' + ); + + // Click create button + await page.waitForTimeout(500); + await expect(page).toClick( + 'div[class$=ReactModalPortal] div[class*=card][class$=medium] div[class$=footer] button[type=submit]' + ); + + expect(new URL(page.url()).pathname.endsWith(`${defaultTenantId}/get-started`)).toBeTruthy(); + }); + + it('check current tenant list and switch to new tenant', async () => { + // Wait for toast to disappear. + await page.waitForTimeout(5000); + + // Click 'current tenant card' locates in topbar + const currentTenantCard = await page.waitForSelector( + 'div[class$=topbar] > div[class$=currentTenantCard][role=button]:has(div[class$=name])' + ); + await expect(currentTenantCard).toMatchElement('div[class$=name]', { text: 'My Project' }); + await currentTenantCard.click(); + + const newTenant = await page.waitForSelector( + 'div[class$=ReactModalPortal] div[class$=dropdownContainer] div[class$=dropdownItem]:first-child' + ); + await expect(newTenant).toMatchElement('div[class$=dropdownName]', { text: createTenantName }); + await newTenant.click(); + + await page.waitForNavigation({ waitUntil: 'networkidle0' }); + }); + it('can sign out of admin console', async () => { - await expect(page).toClick('div[class$=topbar] > div[class$=container]'); + // Check if the current tenant is switched to new tenant. + const currentTenantCard = await page.waitForSelector( + 'div[class$=topbar] > div[class$=currentTenantCard][role=button]:has(div[class$=name])' + ); + await expect(currentTenantCard).toMatchElement('div[class$=name]', { text: createTenantName }); + + const userInfoButton = await page.waitForSelector('div[class$=topbar] > div[class$=container]'); + await userInfoButton.click(); // Try awaiting for 500ms before clicking sign-out button await page.waitForTimeout(500); - await expect(page).toClick( - '.ReactModalPortal div[class$=dropdownContainer] div[class$=dropdownItem]:last-child' + const signOutButton = await page.waitForSelector( + 'div[class$=ReactModalPortal] div[class$=dropdownContainer] div[class$=dropdownItem]:last-child' ); + await signOutButton.click(); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); expect(page.url()).toBe(new URL('sign-in', logtoConsoleUrl).href); @@ -169,9 +218,9 @@ describe('smoke testing for cloud', () => { }); await expect(page).toClick('button[name=submit]'); - await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 5000 }); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); - expect(page.url().startsWith(logtoCloudUrl.href)).toBeTruthy(); - expect(new URL(page.url()).pathname.endsWith('/onboarding/welcome')).toBeTruthy(); + expect(page.url().startsWith(logtoCloudUrl.origin)).toBeTruthy(); + expect(page.url().endsWith('/onboarding/welcome')).toBeTruthy(); }); }); diff --git a/packages/phrases/src/locales/de/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/de/translation/admin-console/tenant-settings.ts index 63836aa2e..1538a1f8b 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Mietername', environment_tag: 'Umgebungsmarke', environment_tag_description: - 'Verwenden Sie Tags, um Mieter-Nutzungsumgebungen zu unterscheiden. Services innerhalb jedes Tags sind identisch und gewährleisten Konsistenz.', - environment_tag_development: 'Entwicklung', - environment_tag_staging: 'Inszenierung', - environment_tag_production: 'Produktion', + 'Die Dienste mit unterschiedlichen Tags sind identisch. Es funktioniert als Suffix, um Ihrem Team Umgebungen zu unterscheiden.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: 'Mieterinformationen erfolgreich gespeichert.', }, deletion_card: { title: 'LÖSCHEN', diff --git a/packages/phrases/src/locales/de/translation/admin-console/tenants.ts b/packages/phrases/src/locales/de/translation/admin-console/tenants.ts index 6eebbe4d8..d2d59fbaf 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'Mein Mieter', environment_tag: 'Umwelt Tag', environment_tag_description: - 'Verwenden Sie Tags, um die Verwendungsumgebungen von Mieter zu unterscheiden. Dienste innerhalb jeder Marke sind identisch und sorgen für Konsistenz.', - environment_tag_development: 'Entwicklung', + 'Die Dienste mit unterschiedlichen Tags sind identisch. Es funktioniert als Suffix, um Ihrem Team Umgebungen zu unterscheiden.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Produktion', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Mieter löschen', @@ -22,6 +22,12 @@ const tenants = { 'Wenn Sie fortfahren möchten, geben Sie bitte den Mieter-Namen "{{name}}" zur Bestätigung ein.', delete_button: 'Dauerhaft löschen', }, + tenant_landing_page: { + title: 'Du hast noch keinen Mandanten erstellt', + description: + 'Um Ihr Projekt mit Logto zu konfigurieren, erstellen Sie bitte einen neuen Mandanten. Wenn Sie sich abmelden oder Ihr Konto löschen möchten, klicken Sie einfach auf die Avatar-Taste in der oberen rechten Ecke.', + create_tenant_button: 'Mandanten erstellen', + }, tenant_created: "Mieter '{{name}}' erfolgreich erstellt.", }; diff --git a/packages/phrases/src/locales/en/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/en/translation/admin-console/tenant-settings.ts index 077e8b363..a29f7e009 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Tenant Name', environment_tag: 'Environment Tag', environment_tag_description: - 'Use tags to differentiate tenant usage environments. Services within each tag are identical, ensuring consistency.', - environment_tag_development: 'Development', + 'The services with different tags are identical. It functions as a suffix to help your team differentiate environments.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Production', + environment_tag_production: 'Prod', + tenant_info_saved: 'Tenant information saved successfully.', }, deletion_card: { title: 'DELETE', diff --git a/packages/phrases/src/locales/en/translation/admin-console/tenants.ts b/packages/phrases/src/locales/en/translation/admin-console/tenants.ts index 91ce82c3f..de46c4f0a 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'My tenant', environment_tag: 'Environment Tag', environment_tag_description: - 'Use tags to differentiate tenant usage environments. Services within each tag are identical, ensuring consistency.', - environment_tag_development: 'Development', + 'The services with different tags are identical. It functions as a suffix to help your team differentiate environments.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Production', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Delete tenant', @@ -22,6 +22,12 @@ const tenants = { 'If you would like to proceed, please enter the tenant name "{{name}}" to confirm.', delete_button: 'Permanently delete', }, + tenant_landing_page: { + title: 'You haven’t created a tenant yet', + description: + 'To start configuring your project with Logto, please create a new tenant. If you need to log out or delete your account, just click on the avatar button in the top right corner.', + create_tenant_button: 'Create tenant', + }, tenant_created: "Tenant '{{name}}' created successfully.", }; diff --git a/packages/phrases/src/locales/es/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/es/translation/admin-console/tenant-settings.ts index e37ed895a..23eb3c2da 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Nombre del inquilino', environment_tag: 'Etiqueta del entorno', environment_tag_description: - 'Use etiquetas para diferenciar entre los entornos de uso del inquilino. Los servicios dentro de cada etiqueta son idénticos, lo que garantiza la consistencia.', - environment_tag_development: 'Desarrollo', - environment_tag_staging: 'Puesta en escena', - environment_tag_production: 'Producción', + 'Los servicios con diferentes etiquetas son idénticos. Funciona como sufijo para ayudar a su equipo a diferenciar entornos.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: 'Información del inquilino guardada correctamente.', }, deletion_card: { title: 'ELIMINAR', diff --git a/packages/phrases/src/locales/es/translation/admin-console/tenants.ts b/packages/phrases/src/locales/es/translation/admin-console/tenants.ts index 31cd70abf..5c8d14a67 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'Mi inquilino', environment_tag: 'Etiqueta de ambiente', environment_tag_description: - 'Use etiquetas para diferenciar los ambientes de uso del inquilino. Los servicios dentro de cada etiqueta son idénticos, lo que garantiza la coherencia.', - environment_tag_development: 'Desarrollo', - environment_tag_staging: 'Puesta en escena', - environment_tag_production: 'Producción', + 'Los servicios con diferentes etiquetas son idénticos. Funciona como sufijo para ayudar a su equipo a diferenciar entornos.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Eliminar inquilino', @@ -22,6 +22,12 @@ const tenants = { 'Si desea continuar, ingrese el nombre del inquilino "{{name}}" para confirmar.', delete_button: 'Eliminar permanentemente', }, + tenant_landing_page: { + title: 'Todavía no has creado un tenant', + description: + 'Para empezar a configurar tu proyecto con Logto, por favor crea un nuevo tenant. Si necesitas cerrar la sesión o eliminar tu cuenta, simplemente haz clic en el botón de avatar en la esquina superior derecha.', + create_tenant_button: 'Crear tenant', + }, tenant_created: "El inquilino '{{name}}' se ha creado correctamente.", }; diff --git a/packages/phrases/src/locales/fr/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/fr/translation/admin-console/tenant-settings.ts index aa6080b81..0504a8342 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Nom du locataire', environment_tag: "Tag de l'environnement", environment_tag_description: - "Utilisez des tags pour différencier les environnements d'utilisation du locataire. Les services au sein de chaque tag sont identiques, assurant ainsi la cohérence.", - environment_tag_development: 'Développement', - environment_tag_staging: 'Mise en scène', - environment_tag_production: 'Production', + 'Les services avec différentes balises sont identiques. Il fonctionne comme un suffixe pour aider votre équipe à différencier les environnements.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: 'Les informations du locataire ont été enregistrées avec succès.', }, deletion_card: { title: 'SUPPRIMER', diff --git a/packages/phrases/src/locales/fr/translation/admin-console/tenants.ts b/packages/phrases/src/locales/fr/translation/admin-console/tenants.ts index e3479235b..4343176f0 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'Mon locataire', environment_tag: 'Balise environnement', environment_tag_description: - "Utilisez des balises pour différencier les environnements d'utilisation des locataires. Les services dans chaque balise sont identiques, assurant ainsi la cohérence.", - environment_tag_development: 'Développement', - environment_tag_staging: 'Mise en scène', - environment_tag_production: 'Production', + 'Les services avec différentes balises sont identiques. Il fonctionne comme un suffixe pour aider votre équipe à différencier les environnements.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Supprimer le locataire', @@ -22,6 +22,12 @@ const tenants = { 'Si vous souhaitez continuer, veuillez entrer le nom du locataire "{{name}}" pour confirmer.', delete_button: 'Supprimer définitivement', }, + tenant_landing_page: { + title: "Vous n'avez pas encore créé de locataire", + description: + "Pour commencer à configurer votre projet avec Logto, veuillez créer un nouveau locataire. Si vous devez vous déconnecter ou supprimer votre compte, cliquez simplement sur le bouton d'avatar dans le coin supérieur droit.", + create_tenant_button: 'Créer un locataire', + }, tenant_created: "Locataire '{{name}}' créé avec succès.", }; diff --git a/packages/phrases/src/locales/it/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/it/translation/admin-console/tenant-settings.ts index 6a059c406..da8cc015e 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Nome Tenant', environment_tag: 'Tag Ambiente', environment_tag_description: - "Utilizza i tag per differenziare gli ambienti di utilizzo del tenant. I servizi all'interno di ogni tag sono identici, garantendo la coerenza.", - environment_tag_development: 'Sviluppo', + 'I servizi con tag diversi sono identici. Funziona come suffisso per aiutare il tuo team a differenziare gli ambienti.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Produzione', + environment_tag_production: 'Prod', + tenant_info_saved: "Le informazioni dell'inquilino sono state salvate correttamente.", }, deletion_card: { title: 'ELIMINA', diff --git a/packages/phrases/src/locales/it/translation/admin-console/tenants.ts b/packages/phrases/src/locales/it/translation/admin-console/tenants.ts index 7aeb04c66..5bc7d16c2 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'Il mio tenant', environment_tag: 'Tag ambiente', environment_tag_description: - "Usa i tag per differenziare gli ambienti di utilizzo del tenant. I servizi all'interno di ogni tag sono identici, garantendo la coerenza.", - environment_tag_development: 'Sviluppo', - environment_tag_staging: 'Sperimentale', - environment_tag_production: 'Produzione', + 'I servizi con tag diversi sono identici. Funziona come suffisso per aiutare il tuo team a differenziare gli ambienti.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Elimina tenant', @@ -22,6 +22,12 @@ const tenants = { 'Se vuoi procedere, inserisci il nome del tenant "{{name}}" per confermare.', delete_button: 'Elimina definitivamente', }, + tenant_landing_page: { + title: 'Non hai ancora creato un tenant', + description: + 'Per iniziare a configurare il tuo progetto con Logto, crea un nuovo tenant. Se hai bisogno di uscire o eliminare il tuo account, clicca sul pulsante avatar in alto a destra.', + create_tenant_button: 'Crea tenant', + }, tenant_created: "Tenant '{{name}}' creato con successo.", }; diff --git a/packages/phrases/src/locales/ja/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/ja/translation/admin-console/tenant-settings.ts index 660bc7e32..a43b81fc7 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'テナント名', environment_tag: '環境タグ', environment_tag_description: - 'タグを使用してテナント使用環境を区別します。 各タグ内のサービスは同一で、一貫性が保たれます。', - environment_tag_development: '開発', - environment_tag_staging: 'ステージング', - environment_tag_production: 'プロダクション', + 'タグの異なるサービスは同一です。環境を区別するためにチームを支援する接尾辞として機能します。', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: 'テナント情報は正常に保存されました。', }, deletion_card: { title: '削除', diff --git a/packages/phrases/src/locales/ja/translation/admin-console/tenants.ts b/packages/phrases/src/locales/ja/translation/admin-console/tenants.ts index 6cde12ce9..0aa6d0c8d 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: '私のテナント', environment_tag: '環境タグ', environment_tag_description: - 'タグを使用して、テナント使用環境を区別します。各タグ内のサービスは同一であり、一貫性が保たれます。', - environment_tag_development: '開発', - environment_tag_staging: 'ステージング', - environment_tag_production: 'プロダクション', + 'タグの異なるサービスは同一です。環境を区別するためにチームを支援する接尾辞として機能します。', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: 'テナントを削除します', @@ -22,6 +22,12 @@ const tenants = { '続行する場合は、テナント名 "{{name}}" を入力して確認してください。', delete_button: '完全に削除する', }, + tenant_landing_page: { + title: 'まだテナントを作成していません', + description: + 'Logto でプロジェクトを設定するには、新しいテナントを作成してください。ログアウトまたはアカウントを削除する必要がある場合は、右上隅のアバターボタンをクリックしてください。', + create_tenant_button: 'テナントを作成', + }, tenant_created: "{{name}}'のテナントが正常に作成されました。", }; diff --git a/packages/phrases/src/locales/ko/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/ko/translation/admin-console/tenant-settings.ts index bcd2c4dee..dc70c004d 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/tenant-settings.ts @@ -11,10 +11,11 @@ const tenant_settings = { tenant_name: '테넌트 이름', environment_tag: '환경 태그', environment_tag_description: - '태그를 사용하여 테넌트 사용 환경을 구분합니다. 각 태그에 대한 서비스는 동일하므로 일관성이 유지됩니다.', - environment_tag_development: '개발', - environment_tag_staging: '스테이징', - environment_tag_production: '프로덕션', + '태그가 다른 서비스는 동일합니다. 환경을 구분하는 데 팀을 돕는 접미사로 기능합니다.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: '세입자 정보가 성공적으로 저장되었습니다.', }, deletion_card: { title: '삭제', diff --git a/packages/phrases/src/locales/ko/translation/admin-console/tenants.ts b/packages/phrases/src/locales/ko/translation/admin-console/tenants.ts index 2f1bd8de4..5ef7d4ab0 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: '내 테넌트', environment_tag: '환경 태그', environment_tag_description: - '태그를 사용하여 테넌트 사용 환경을 구분하세요. 각 태그 내의 서비스는 동일하여 일관성을 보장합니다.', - environment_tag_development: '개발', - environment_tag_staging: '스테이징', - environment_tag_production: '프로덕션', + '태그가 다른 서비스는 동일합니다. 환경을 구분하는 데 팀을 돕는 접미사로 기능합니다.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: '테넌트 삭제', @@ -22,6 +22,12 @@ const tenants = { '삭제하려는 테넌트 이름 "{{name}}"을(를) 입력하여 확인하십시오.', delete_button: '영구 삭제', }, + tenant_landing_page: { + title: '아직 테넌트를 만들지 않았습니다.', + description: + 'Logto 를 사용하여 프로젝트를 구성하려면 새 테넌트를 만드세요. 로그아웃하거나 계정을 삭제하려면 오른쪽 상단 모서리에있는 아바타 버튼을 클릭하세요.', + create_tenant_button: '테넌트 만들기', + }, tenant_created: "테넌트 '{{name}}'가(이) 성공적으로 만들어졌습니다.", }; diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/tenant-settings.ts index f43fb47b4..c8db9aa5a 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Nazwa Najemcy', environment_tag: 'Tag Środowiska', environment_tag_description: - 'Użyj tagów, aby rozróżnić środowiska użytkowe najemcy. Usługi w każdym tagu są identyczne, co zapewnia spójność.', - environment_tag_development: 'Rozwój', + 'Usługi z różnymi tagami są identyczne. Działa jako przyrostek, aby pomóc Twojemu zespołowi w różnicowaniu środowisk.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Produkcja', + environment_tag_production: 'Prod', + tenant_info_saved: 'Informacje o najemcy zostały pomyślnie zapisane.', }, deletion_card: { title: 'USUWANIE', diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/tenants.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/tenants.ts index 256dec034..272b5dbcc 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'Mój najemca', environment_tag: 'Tag środowiska', environment_tag_description: - 'Użyj tagów do odróżniania środowisk wykorzystania najemcy. Usługi w każdym tagu są identyczne, zapewniając spójność.', - environment_tag_development: 'Rozwój', + 'Usługi z różnymi tagami są identyczne. Działa jako przyrostek, aby pomóc Twojemu zespołowi w różnicowaniu środowisk.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Produkcja', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Usuń najemcę', @@ -22,6 +22,12 @@ const tenants = { 'Jeśli chcesz kontynuować, wprowadź nazwę najemcy "{{name}}" w celu potwierdzenia.', delete_button: 'Usuń na stałe', }, + tenant_landing_page: { + title: 'Nie utworzyłeś jeszcze najemcy', + description: + 'Aby rozpocząć konfigurowanie projektu z Logto, utwórz nowego najemcę. Jeśli musisz się wylogować lub usunąć swoje konto, wystarczy kliknąć przycisk awatara w prawym górnym rogu.', + create_tenant_button: 'Utwórz najemcę', + }, tenant_created: "Najemca '{{name}}' utworzony pomyślnie.", }; diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/tenant-settings.ts index 42703153b..e78e3be75 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Nome do Locatário', environment_tag: 'Tag do Ambiente', environment_tag_description: - 'Use tags para diferenciar os ambientes de uso do locatário. Os serviços em cada tag são os mesmos, garantindo consistência.', - environment_tag_development: 'Desenvolvimento', - environment_tag_staging: 'Teste', - environment_tag_production: 'Produção', + 'Os serviços com diferentes tags são idênticos. Funciona como um sufixo para ajudar sua equipe a diferenciar ambientes.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: 'As informações do locatário foram salvas com sucesso.', }, deletion_card: { title: 'EXCLUIR', diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/tenants.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/tenants.ts index 1ff0a91a1..39dfba356 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'Meu inquilino', environment_tag: 'Tag de ambiente', environment_tag_description: - 'Use tags para diferenciar ambientes de uso de inquilino. Serviços dentro de cada tag são idênticos, garantindo consistência.', - environment_tag_development: 'Desenvolvimento', + 'Os serviços com diferentes tags são idênticos. Funciona como um sufixo para ajudar sua equipe a diferenciar ambientes.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Produção', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Excluir locatário', @@ -22,6 +22,12 @@ const tenants = { 'Se você deseja continuar, digite o nome do locatário "{{name}}" para confirmar.', delete_button: 'Excluir permanentemente', }, + tenant_landing_page: { + title: 'Você ainda não criou um inquilino', + description: + 'Para começar a configurar seu projeto com o Logto, crie um novo inquilino. Se você precisar fazer logout ou excluir sua conta, basta clicar no botão de avatar no canto superior direito.', + create_tenant_button: 'Criar inquilino', + }, tenant_created: "Inquilino '{{name}}' criado com sucesso.", }; diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/tenant-settings.ts index 7caf5dfaf..a34556035 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Nome do Inquilino', environment_tag: 'Tag de Ambiente', environment_tag_description: - 'Use tags para diferenciar os ambientes de uso do inquilino. Os serviços dentro de cada tag são idênticos, garantindo consistência.', - environment_tag_development: 'Desenvolvimento', + 'Os serviços com etiquetas diferentes são idênticos. Funciona como um sufixo para ajudar a sua equipa a diferenciar ambientes.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Produção', + environment_tag_production: 'Prod', + tenant_info_saved: 'A informação do arrendatário foi guardada com sucesso.', }, deletion_card: { title: 'ELIMINAR', diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/tenants.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/tenants.ts index 70efe18f8..dcef4376a 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'Meu inquilino', environment_tag: 'Etiqueta de ambiente', environment_tag_description: - 'Use etiquetas para diferenciar os ambientes de utilização do inquilino. Os serviços em cada etiqueta são idênticos, garantindo consistência.', - environment_tag_development: 'Desenvolvimento', + 'Os serviços com etiquetas diferentes são idênticos. Funciona como um sufixo para ajudar a sua equipa a diferenciar ambientes.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Produção', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Eliminar inquilino', @@ -22,6 +22,12 @@ const tenants = { 'Se desejar continuar, introduza o nome do inquilino "{{name}}" para confirmar.', delete_button: 'Eliminar permanentemente', }, + tenant_landing_page: { + title: 'Ainda não criou um inquilino', + description: + 'Para começar a configurar o seu projeto com o Logto, crie um novo inquilino. Se precisar de fazer logout ou excluir a sua conta, basta clicar no botão avatar no canto superior direito.', + create_tenant_button: 'Criar inquilino', + }, tenant_created: "Inquilino '{{name}}' criado com sucesso.", }; diff --git a/packages/phrases/src/locales/ru/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/ru/translation/admin-console/tenant-settings.ts index 164f010bc..813283120 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Имя арендатора', environment_tag: 'Тег окружения', environment_tag_description: - 'Используйте теги для различения окружений использования арендаторов. Сервисы в каждом теге идентичны, обеспечивая согласованность.', - environment_tag_development: 'Разработка', - environment_tag_staging: 'Стадия', - environment_tag_production: 'Производство', + 'Сервисы с разными тегами идентичны. Он работает как суффикс, чтобы помочь вашей команде различать среды.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: 'Информация о квартиросъемщике успешно сохранена.', }, deletion_card: { title: 'УДАЛИТЬ', diff --git a/packages/phrases/src/locales/ru/translation/admin-console/tenants.ts b/packages/phrases/src/locales/ru/translation/admin-console/tenants.ts index ee4c63a9b..d6897f6db 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'Мой арендатор', environment_tag: 'Тег окружения', environment_tag_description: - 'Используйте теги для отделения сред сред использования арендаторов. Сервисы в каждом теге идентичны, обеспечивая согласованность.', - environment_tag_development: 'Разработка', - environment_tag_staging: 'Стадия', - environment_tag_production: 'Производство', + 'Сервисы с разными тегами идентичны. Он работает как суффикс, чтобы помочь вашей команде различать среды.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Удалить арендатора', @@ -22,6 +22,12 @@ const tenants = { 'Если вы хотите продолжить, введите название арендатора "{{name}}" для подтверждения.', delete_button: 'Навсегда удалить', }, + tenant_landing_page: { + title: 'Вы еще не создали арендатора', + description: + 'Чтобы начать настройку вашего проекта с помощью Logto, создайте нового арендатора. Если вам нужно выйти из системы или удалить свою учетную запись, просто нажмите на кнопку аватара в правом верхнем углу.', + create_tenant_button: 'Создать арендатора', + }, tenant_created: "Арендатор '{{name}}' успешно создан.", }; diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/tenant-settings.ts index 25ff8f501..ea1489541 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/tenant-settings.ts @@ -12,10 +12,11 @@ const tenant_settings = { tenant_name: 'Kiracı Adı', environment_tag: 'Çevre Etiketi', environment_tag_description: - 'Etiketleri kullanarak kiracı kullanım ortamlarını farklılaştırın. Her etiketin içindeki hizmetler aynıdır, tutarlılığı sağlar.', - environment_tag_development: 'Geliştirme', + 'Farklı etiketlere sahip hizmetler aynıdır. Ortamları ayırt etmek için ekibinize yardımcı olmak için bir sonek görevi görür.', + environment_tag_development: 'Dev', environment_tag_staging: 'Staging', - environment_tag_production: 'Üretim', + environment_tag_production: 'Prod', + tenant_info_saved: 'Kiracı bilgileri başarıyla kaydedildi.', }, deletion_card: { title: 'SİL', diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/tenants.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/tenants.ts index f1ba15629..2da429eb7 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/tenants.ts @@ -7,10 +7,10 @@ const tenants = { tenant_name_placeholder: 'Benim kiracım', environment_tag: 'Çevre Etiketi', environment_tag_description: - 'Kiracı kullanım ortamlarını ayırt etmek için etiketleri kullanın. Her etiket içindeki hizmetler aynıdır, tutarlılığı sağlar.', - environment_tag_development: 'Geliştirme', - environment_tag_staging: 'Daha Yüksek Birlik', - environment_tag_production: 'Üretim', + 'Farklı etiketlere sahip hizmetler aynıdır. Ortamları ayırt etmek için ekibinize yardımcı olmak için bir sonek görevi görür.', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: 'Kiracıyı Sil', @@ -22,6 +22,12 @@ const tenants = { 'Devam etmek isterseniz, "{{name}}" kiracı adını onaylamak için yazın.', delete_button: 'Kalıcı olarak sil', }, + tenant_landing_page: { + title: 'Henüz bir kiracı oluşturmadınız', + description: + 'Logto ile projenizi yapılandırmaya başlamak için lütfen yeni bir kiracı oluşturun. Hesabınızdan çıkış yapmanız veya hesabınızı silmeniz gerekiyorsa, sağ üst köşedeki avatar düğmesine tıklayın.', + create_tenant_button: 'Kiracı oluştur', + }, tenant_created: "Kiracı '{{name}}' başarıyla oluşturuldu.", }; diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/tenant-settings.ts index 84d51321e..97e6fda85 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/tenant-settings.ts @@ -10,10 +10,12 @@ const tenant_settings = { tenant_id: '租户 ID', tenant_name: '租户名称', environment_tag: '环境标签', - environment_tag_description: '使用标签区分租户使用环境。每个标签中的服务是相同的,确保一致性。', - environment_tag_development: '开发', - environment_tag_staging: '暂存', - environment_tag_production: '生产', + environment_tag_description: + '携带不同标签的服务完全相同。它充当后缀的作用,以帮助您的团队区分不同的环境。', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: '租户信息成功保存。', }, deletion_card: { title: '删除', diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/tenants.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/tenants.ts index 1562c3826..d014141a3 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/tenants.ts @@ -6,10 +6,11 @@ const tenants = { tenant_name: '租户名称', tenant_name_placeholder: '我的租户', environment_tag: '环境标签', - environment_tag_description: '使用标签区分租户使用环境。每个标签内的服务相同,确保一致性。', - environment_tag_development: '开发环境', - environment_tag_staging: '暂存环境', - environment_tag_production: '生产环境', + environment_tag_description: + '携带不同标签的服务完全相同。它充当后缀的作用,以帮助您的团队区分不同的环境。', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: '删除租户', @@ -20,6 +21,12 @@ const tenants = { description_line3: '如果你想继续,请输入租户名 "{{name}}" 确认。', delete_button: '永久删除', }, + tenant_landing_page: { + title: '你还没有创建租户', + description: + '要开始使用 Logto 配置项目,请创建一个新租户。如果您需要注销或删除您的帐户,只需单击右上角的头像按钮。', + create_tenant_button: '创建租户', + }, tenant_created: "租户'{{name}}'创建成功。", }; diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/tenant-settings.ts index ec38422bb..91a864abc 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/tenant-settings.ts @@ -11,10 +11,11 @@ const tenant_settings = { tenant_name: '租户名称', environment_tag: '环境标识', environment_tag_description: - '使用标签区分租户使用环境。在每个标签中的服务是相同的,以确保一致性。', - environment_tag_development: '开发', - environment_tag_staging: '暂存', - environment_tag_production: '生产', + '攜帶不同標籤的服務完全相同。它充當後綴的作用,以幫助您的團隊區分不同的環境。', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: '租戶信息成功保存。', }, deletion_card: { title: '刪除', diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/tenants.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/tenants.ts index 8bb8cb7a5..8b23f57b0 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/tenants.ts @@ -6,10 +6,11 @@ const tenants = { tenant_name: '租戶名稱', tenant_name_placeholder: '我的租戶', environment_tag: '環境標籤', - environment_tag_description: '使用標籤區分租戶環境。每個標籤中的服務均相同,確保一致性。', - environment_tag_development: '開發', - environment_tag_staging: '測試', - environment_tag_production: '生產', + environment_tag_description: + '攜帶不同標籤的服務完全相同。它充當後綴的作用,以幫助您的團隊區分不同的環境。', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: '刪除租戶', @@ -20,6 +21,12 @@ const tenants = { description_line3: '如果您確定要繼續,請輸入租戶名稱 "{{name}}" 以進行確認。', delete_button: '永久刪除', }, + tenant_landing_page: { + title: '您尚未建立租戶', + description: + '要開始使用 Logto 配置您的項目,請創建一個新的租戶。如果您需要退出或刪除您的帳戶,只需單擊右上角的頭像按鈕。', + create_tenant_button: '創建租戶', + }, tenant_created: '成功創建租戶「{{name}}」。', }; diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/tenant-settings.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/tenant-settings.ts index 019a27b26..6f7647018 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/tenant-settings.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/tenant-settings.ts @@ -10,10 +10,12 @@ const tenant_settings = { tenant_id: '租戶 ID', tenant_name: '租戶名稱', environment_tag: '環境標籤', - environment_tag_description: '使用標籤區分租戶使用環境。每個標籤中的服務均相同,確保一致性。', - environment_tag_development: '開發', - environment_tag_staging: '暫存', - environment_tag_production: '生產', + environment_tag_description: + '帶有不同標籤的服務完全相同。它充當後綴的作用,以幫助您的團隊區分不同的環境。', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', + tenant_info_saved: '租戶資訊成功儲存。', }, deletion_card: { title: '刪除', diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/tenants.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/tenants.ts index e7f7aa742..37b67509f 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/tenants.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/tenants.ts @@ -6,10 +6,11 @@ const tenants = { tenant_name: '租戶名稱', tenant_name_placeholder: '我的租戶', environment_tag: '環境標籤', - environment_tag_description: '使用標籤區分租戶使用環境,每個標籤的服務相同,確保一致性。', - environment_tag_development: '開發', - environment_tag_staging: '測試', - environment_tag_production: '生產', + environment_tag_description: + '帶有不同標籤的服務完全相同。它充當後綴的作用,以幫助您的團隊區分不同的環境。', + environment_tag_development: 'Dev', + environment_tag_staging: 'Staging', + environment_tag_production: 'Prod', }, delete_modal: { title: '刪除租戶', @@ -20,6 +21,12 @@ const tenants = { description_line3: '如果您確定要繼續,請輸入租戶名稱 "{{name}}" 以確認。', delete_button: '永久刪除', }, + tenant_landing_page: { + title: '您尚未建立租戶', + description: + '要開始使用Logto配置您的項目,請創建一個新租戶。如果您需要登出或刪除您的帳戶,只需點擊右上角的頭像按鈕。', + create_tenant_button: '創建租戶', + }, tenant_created: "租戶 '{{name}}' 成功建立。", };