diff --git a/packages/console/src/App.tsx b/packages/console/src/App.tsx
index e211dd52a..c2d8fdd49 100644
--- a/packages/console/src/App.tsx
+++ b/packages/console/src/App.tsx
@@ -22,8 +22,6 @@ import AppLoading from '@/components/AppLoading';
import { isCloud } from '@/consts/env';
import { cloudApi, getManagementApi, meApi } from '@/consts/resources';
import { ConsoleRoutes } from '@/containers/ConsoleRoutes';
-import { OnboardingRoutes } from '@/onboarding';
-import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
import { GlobalScripts } from './components/Conversion';
import { adminTenantEndpoint, mainTitle } from './consts';
@@ -139,7 +137,6 @@ function Providers() {
function AppRoutes() {
const { tenantEndpoint } = useContext(AppDataContext);
const { isLoaded } = useCurrentUser();
- const { isOnboarding } = useUserOnboardingData();
const { isAuthenticated } = useLogto();
// Authenticated user should load onboarding data before rendering the app.
@@ -152,7 +149,7 @@ function AppRoutes() {
return (
<>
- {isAuthenticated && isOnboarding ? : }
+
>
);
}
diff --git a/packages/console/src/cloud/AppRoutes.tsx b/packages/console/src/cloud/AppRoutes.tsx
index 097b4008c..9870c709a 100644
--- a/packages/console/src/cloud/AppRoutes.tsx
+++ b/packages/console/src/cloud/AppRoutes.tsx
@@ -2,6 +2,7 @@ import { Route, Routes } from 'react-router-dom';
import ProtectedRoutes from '@/containers/ProtectedRoutes';
import { GlobalAnonymousRoute, GlobalRoute } from '@/contexts/TenantsProvider';
+import { OnboardingApp } from '@/onboarding';
import AcceptInvitation from '@/pages/AcceptInvitation';
import Callback from '@/pages/Callback';
import CheckoutSuccessCallback from '@/pages/CheckoutSuccessCallback';
@@ -21,12 +22,13 @@ function AppRoutes() {
} />
}>
}
/>
- } />
- } />
+ } />
+ } />
} />
+ } />
} />
diff --git a/packages/console/src/cloud/pages/Main/AutoCreateTenant/index.tsx b/packages/console/src/cloud/pages/Main/AutoCreateTenant/index.tsx
deleted file mode 100644
index b467441d2..000000000
--- a/packages/console/src/cloud/pages/Main/AutoCreateTenant/index.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { useContext, useEffect } from 'react';
-
-import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
-import AppLoading from '@/components/AppLoading';
-import { TenantsContext } from '@/contexts/TenantsProvider';
-
-export default function AutoCreateTenant() {
- const api = useCloudApi();
- const { prependTenant, tenants } = useContext(TenantsContext);
-
- useEffect(() => {
- const createTenant = async () => {
- const newTenant = await api.post('/api/tenants', { body: {} });
- prependTenant(newTenant);
- };
-
- if (tenants.length === 0) {
- void createTenant();
- }
- }, [api, prependTenant, tenants.length]);
-
- return ;
-}
diff --git a/packages/console/src/cloud/pages/Main/index.tsx b/packages/console/src/cloud/pages/Main/index.tsx
index 42947a556..53351449a 100644
--- a/packages/console/src/cloud/pages/Main/index.tsx
+++ b/packages/console/src/cloud/pages/Main/index.tsx
@@ -1,13 +1,14 @@
import { OrganizationInvitationStatus } from '@logto/schemas';
+import { Navigate } from 'react-router-dom';
import AppLoading from '@/components/AppLoading';
import { isCloud } from '@/consts/env';
+import { GlobalRoute } from '@/contexts/TenantsProvider';
import useCurrentUser from '@/hooks/use-current-user';
import useUserDefaultTenantId from '@/hooks/use-user-default-tenant-id';
import useUserInvitations from '@/hooks/use-user-invitations';
import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
-import AutoCreateTenant from './AutoCreateTenant';
import InvitationList from './InvitationList';
import Redirect from './Redirect';
import TenantLandingPage from './TenantLandingPage';
@@ -27,9 +28,9 @@ export default function Main() {
return ;
}
- // A new user has just signed up and has no tenant, needs to create a new tenant.
+ // A new user has just signed up, redirect them to the onboarding flow.
if (isOnboarding) {
- return ;
+ return ;
}
// If user has pending invitations (onboarding will be skipped), show the invitation list and allow them to quick join.
diff --git a/packages/console/src/components/CreateTenantModal/index.module.scss b/packages/console/src/components/CreateTenantModal/index.module.scss
index 8843bd4be..505449e7c 100644
--- a/packages/console/src/components/CreateTenantModal/index.module.scss
+++ b/packages/console/src/components/CreateTenantModal/index.module.scss
@@ -6,16 +6,6 @@
margin-top: _.unit(0.5);
}
-.regionOptions {
- font: var(--font-label-2);
-
- .comingSoon {
- margin-left: _.unit(1);
- font: var(--font-body-2);
- color: var(--color-text-secondary);
- }
-}
-
.envTagRadioGroup {
display: grid;
grid-template-columns: repeat(2, 1fr);
diff --git a/packages/console/src/components/CreateTenantModal/index.tsx b/packages/console/src/components/CreateTenantModal/index.tsx
index 366373fb2..c89003054 100644
--- a/packages/console/src/components/CreateTenantModal/index.tsx
+++ b/packages/console/src/components/CreateTenantModal/index.tsx
@@ -1,13 +1,14 @@
import { Theme, TenantTag } from '@logto/schemas';
import { useState } from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
-import { useTranslation } from 'react-i18next';
import Modal from 'react-modal';
import CreateTenantHeaderIconDark from '@/assets/icons/create-tenant-header-dark.svg';
import CreateTenantHeaderIcon from '@/assets/icons/create-tenant-header.svg';
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
import { type TenantResponse } from '@/cloud/types/router';
+import Region, { RegionName } from '@/components/Region';
+import { isDevFeaturesEnabled } from '@/consts/env';
import Button from '@/ds-components/Button';
import DangerousRaw from '@/ds-components/DangerousRaw';
import FormField from '@/ds-components/FormField';
@@ -30,11 +31,10 @@ type Props = {
const availableTags = [TenantTag.Development, TenantTag.Production];
function CreateTenantModal({ isOpen, onClose }: Props) {
- const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [tenantData, setTenantData] = useState();
const theme = useTheme();
- const defaultValues = { tag: TenantTag.Development };
+ const defaultValues = { tag: TenantTag.Development, regionName: RegionName.EU };
const methods = useForm({
defaultValues,
});
@@ -49,9 +49,9 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
const cloudApi = useCloudApi();
- const createTenant = async (data: CreateTenantData) => {
- const { name, tag } = data;
- const newTenant = await cloudApi.post('/api/tenants', { body: { name, tag } });
+ const createTenant = async ({ name, tag, regionName }: CreateTenantData) => {
+ // @ts-expect-error will be removed once we bump the `@logto/cloud` version
+ const newTenant = await cloudApi.post('/api/tenants', { body: { name, tag, regionName } });
onClose(newTenant);
};
@@ -108,28 +108,31 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
/>
-
-
- πͺπΊ EU
-
- }
- value="eu"
- />
-
-
- πΊπΈ US
- {`(${t('general.coming_soon')})`}
-
-
- }
- value="us"
- />
-
+ (
+
+ {/* Manually maintaining the list of regions to avoid unexpected changes. We may consider using an API in the future. */}
+ {[RegionName.EU, RegionName.US].map((region) => (
+
+
+
+ }
+ value={region}
+ isDisabled={!isDevFeaturesEnabled && region !== RegionName.EU}
+ />
+ ))}
+
+ )}
+ />
;
+import { type RegionName } from '@/components/Region';
+
+export type CreateTenantData = Pick & {
+ regionName: RegionName;
+};
diff --git a/packages/console/src/components/Region/index.module.scss b/packages/console/src/components/Region/index.module.scss
new file mode 100644
index 000000000..569beaf3f
--- /dev/null
+++ b/packages/console/src/components/Region/index.module.scss
@@ -0,0 +1,11 @@
+@use '@/scss/underscore' as _;
+
+.regionText {
+ font: var(--font-label-2);
+
+ .comingSoon {
+ margin-left: _.unit(1);
+ font: var(--font-body-2);
+ color: var(--color-text-secondary);
+ }
+}
diff --git a/packages/console/src/components/Region/index.tsx b/packages/console/src/components/Region/index.tsx
new file mode 100644
index 000000000..9a50cb97e
--- /dev/null
+++ b/packages/console/src/components/Region/index.tsx
@@ -0,0 +1,34 @@
+import classNames from 'classnames';
+import { useTranslation } from 'react-i18next';
+
+import * as styles from './index.module.scss';
+
+// TODO: This is a copy from `@logto/cloud-models`, make a SSoT for this later
+export enum RegionName {
+ EU = 'EU',
+ US = 'US',
+}
+
+const regionFlagMap = Object.freeze({
+ [RegionName.EU]: 'πͺπΊ',
+ [RegionName.US]: 'πΊπΈ',
+} satisfies Record);
+
+type Props = {
+ readonly regionName: RegionName;
+ readonly isComingSoon?: boolean;
+ readonly className?: string;
+};
+
+function Region({ isComingSoon = false, regionName, className }: Props) {
+ const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
+
+ return (
+
+ {regionFlagMap[regionName]} {regionName}
+ {isComingSoon && {`(${t('general.coming_soon')})`}}
+
+ );
+}
+
+export default Region;
diff --git a/packages/console/src/containers/ConsoleRoutes/index.tsx b/packages/console/src/containers/ConsoleRoutes/index.tsx
index e12441646..07ed84c5c 100644
--- a/packages/console/src/containers/ConsoleRoutes/index.tsx
+++ b/packages/console/src/containers/ConsoleRoutes/index.tsx
@@ -8,7 +8,7 @@ import AppContent, { RedirectToFirstItem } from '@/containers/AppContent';
import ConsoleContent from '@/containers/ConsoleContent';
import ProtectedRoutes from '@/containers/ProtectedRoutes';
import TenantAccess from '@/containers/TenantAccess';
-import { GlobalAnonymousRoute, GlobalRoute } from '@/contexts/TenantsProvider';
+import { GlobalRoute } from '@/contexts/TenantsProvider';
import Toast from '@/ds-components/Toast';
import useSwrOptions from '@/hooks/use-swr-options';
import Callback from '@/pages/Callback';
@@ -43,10 +43,7 @@ export function ConsoleRoutes() {
} />
} />
}>
- }
- />
+ } />
}>
{isCloud && (
= Object.freeze([
diff --git a/packages/console/src/hooks/use-user-assets-service.ts b/packages/console/src/hooks/use-user-assets-service.ts
index ff4a39bad..4ad92bc47 100644
--- a/packages/console/src/hooks/use-user-assets-service.ts
+++ b/packages/console/src/hooks/use-user-assets-service.ts
@@ -4,7 +4,7 @@ import useSWRImmutable from 'swr/immutable';
import { adminTenantEndpoint, meApi } from '@/consts';
import { isCloud } from '@/consts/env';
-import { GlobalAnonymousRoute } from '@/contexts/TenantsProvider';
+import { GlobalRoute } from '@/contexts/TenantsProvider';
import useApi, { useStaticApi, type RequestError } from './use-api';
import useSwrFetcher from './use-swr-fetcher';
@@ -17,8 +17,7 @@ const useUserAssetsService = () => {
const api = useApi();
const { pathname } = useLocation();
const isProfilePage =
- pathname === GlobalAnonymousRoute.Profile ||
- pathname.startsWith(GlobalAnonymousRoute.Profile + '/');
+ pathname === GlobalRoute.Profile || pathname.startsWith(GlobalRoute.Profile + '/');
const shouldUseAdminApi = isCloud && isProfilePage;
const fetcher = useSwrFetcher(shouldUseAdminApi ? adminApi : api);
diff --git a/packages/console/src/onboarding/containers/AppContent/index.module.scss b/packages/console/src/onboarding/containers/AppContent/index.module.scss
deleted file mode 100644
index 1333ce6b0..000000000
--- a/packages/console/src/onboarding/containers/AppContent/index.module.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.app {
- position: absolute;
- inset: 0;
- display: flex;
- flex-direction: column;
-}
-
-.content {
- height: 100%;
- overflow: hidden;
- display: flex;
- flex-direction: column;
-}
diff --git a/packages/console/src/onboarding/containers/AppContent/index.tsx b/packages/console/src/onboarding/containers/AppContent/index.tsx
deleted file mode 100644
index 69f35bb7a..000000000
--- a/packages/console/src/onboarding/containers/AppContent/index.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { useRoutes, type RouteObject, Navigate } from 'react-router-dom';
-
-import { usePlausiblePageview } from '@/hooks/use-plausible-pageview';
-import Topbar from '@/onboarding/components/Topbar';
-import SignInExperience from '@/onboarding/pages/SignInExperience';
-import Welcome from '@/onboarding/pages/Welcome';
-import { OnboardingPage, OnboardingRoute } from '@/onboarding/types';
-import NotFound from '@/pages/NotFound';
-
-import * as styles from './index.module.scss';
-
-const routeObjects: RouteObject[] = [
- {
- path: OnboardingRoute.Onboarding,
- children: [
- {
- index: true,
- element: ,
- },
- {
- path: OnboardingPage.Welcome,
- element: ,
- },
- {
- path: OnboardingPage.SignInExperience,
- element: ,
- },
- ],
- },
- {
- path: '*',
- element: ,
- },
-];
-
-function AppContent() {
- const routes = useRoutes(routeObjects);
-
- usePlausiblePageview(routeObjects);
-
- return (
-
-
-
{routes}
-
- );
-}
-
-export default AppContent;
diff --git a/packages/console/src/onboarding/hooks/use-tenant-api.ts b/packages/console/src/onboarding/hooks/use-tenant-api.ts
new file mode 100644
index 000000000..33a377464
--- /dev/null
+++ b/packages/console/src/onboarding/hooks/use-tenant-api.ts
@@ -0,0 +1,36 @@
+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 = {}) => {
+ 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;
diff --git a/packages/console/src/onboarding/hooks/use-tenant-swr-options.ts b/packages/console/src/onboarding/hooks/use-tenant-swr-options.ts
new file mode 100644
index 000000000..c689d718b
--- /dev/null
+++ b/packages/console/src/onboarding/hooks/use-tenant-swr-options.ts
@@ -0,0 +1,28 @@
+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['value']> => {
+ const api = useTenantApi();
+ const fetcher = useSwrFetcher(api);
+
+ const config = useMemo(
+ () => ({
+ fetcher,
+ shouldRetryOnError: shouldRetryOnError({ ignore: [401, 403] }),
+ }),
+ [fetcher]
+ );
+ return config;
+};
+
+export default useTenantSwrOptions;
diff --git a/packages/console/src/onboarding/hooks/use-tenant-user-asset-service.ts b/packages/console/src/onboarding/hooks/use-tenant-user-asset-service.ts
new file mode 100644
index 000000000..a088d6439
--- /dev/null
+++ b/packages/console/src/onboarding/hooks/use-tenant-user-asset-service.ts
@@ -0,0 +1,27 @@
+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(api);
+ const { data, error } = useSWRImmutable(
+ 'api/user-assets/service-status',
+ fetcher
+ );
+
+ return {
+ isReady: data?.status === 'ready',
+ isLoading: !error && !data,
+ };
+};
+
+export default useTenantUserAssetsService;
diff --git a/packages/console/src/onboarding/index.module.scss b/packages/console/src/onboarding/index.module.scss
index 6c542f6d4..1a3d890c7 100644
--- a/packages/console/src/onboarding/index.module.scss
+++ b/packages/console/src/onboarding/index.module.scss
@@ -3,4 +3,13 @@
.app {
position: absolute;
inset: 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.content {
+ height: 100%;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
}
diff --git a/packages/console/src/onboarding/index.tsx b/packages/console/src/onboarding/index.tsx
index 04c984a24..2d6a5fd15 100644
--- a/packages/console/src/onboarding/index.tsx
+++ b/packages/console/src/onboarding/index.tsx
@@ -1,28 +1,49 @@
import { Theme } from '@logto/schemas';
import { useContext, useEffect } from 'react';
-import { Route, Navigate, Outlet, Routes } from 'react-router-dom';
-import { SWRConfig } from 'swr';
+import { Navigate, type RouteObject, useMatch, useRoutes } from 'react-router-dom';
+import AppLoading from '@/components/AppLoading';
import AppBoundary from '@/containers/AppBoundary';
-import ProtectedRoutes from '@/containers/ProtectedRoutes';
-import TenantAccess from '@/containers/TenantAccess';
import { AppThemeContext } from '@/contexts/AppThemeProvider';
import Toast from '@/ds-components/Toast';
-import useSwrOptions from '@/hooks/use-swr-options';
-import useTenantPathname from '@/hooks/use-tenant-pathname';
+import { usePlausiblePageview } from '@/hooks/use-plausible-pageview';
-import AppContent from './containers/AppContent';
+import Topbar from './components/Topbar';
import useUserOnboardingData from './hooks/use-user-onboarding-data';
import * as styles from './index.module.scss';
-import { OnboardingPage, OnboardingRoute } from './types';
+import CreateTenant from './pages/CreateTenant';
+import SignInExperience from './pages/SignInExperience';
+import Welcome from './pages/Welcome';
+import { OnboardingPage } from './types';
import { getOnboardingPage } from './utils';
const welcomePathname = getOnboardingPage(OnboardingPage.Welcome);
-function Layout() {
- const swrOptions = useSwrOptions();
+const routeObjects: RouteObject[] = [
+ {
+ index: true,
+ element: ,
+ },
+ {
+ path: OnboardingPage.Welcome,
+ element: ,
+ },
+ {
+ path: OnboardingPage.CreateTenant,
+ element: ,
+ },
+ {
+ path: `:tenantId/${OnboardingPage.SignInExperience}`,
+ element: ,
+ },
+];
+
+export function OnboardingApp() {
const { setThemeOverride } = useContext(AppThemeContext);
- const { match, getTo } = useTenantPathname();
+ const matched = useMatch(welcomePathname);
+ const routes = useRoutes(routeObjects);
+
+ usePlausiblePageview(routeObjects);
useEffect(() => {
setThemeOverride(Theme.Light);
@@ -33,37 +54,30 @@ function Layout() {
}, [setThemeOverride]);
const {
- data: { questionnaire },
+ isLoading,
+ data: { questionnaire, isOnboardingDone },
} = useUserOnboardingData();
+ if (isLoading) {
+ return ;
+ }
+
+ if (isOnboardingDone) {
+ return ;
+ }
+
// Redirect to the welcome page if the user has not started the onboarding process.
- if (!questionnaire && !match(welcomePathname)) {
- return ;
+ if (!questionnaire && !matched) {
+ return ;
}
return (
-
-
-
-
-
-
+
+
+
+
{routes}
+
);
}
-
-export function OnboardingRoutes() {
- return (
-
- }>
- }>
- }>
- } />
- } />
-
-
-
-
- );
-}
diff --git a/packages/console/src/onboarding/pages/CreateTenant/index.tsx b/packages/console/src/onboarding/pages/CreateTenant/index.tsx
new file mode 100644
index 000000000..677c6c280
--- /dev/null
+++ b/packages/console/src/onboarding/pages/CreateTenant/index.tsx
@@ -0,0 +1,107 @@
+import { joinPath } from '@silverhand/essentials';
+import { useContext } from 'react';
+import { Controller, FormProvider, useForm } from 'react-hook-form';
+
+import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
+import ActionBar from '@/components/ActionBar';
+import { type CreateTenantData } from '@/components/CreateTenantModal/types';
+import Region, { RegionName } from '@/components/Region';
+import { isDevFeaturesEnabled } from '@/consts/env';
+import { TenantsContext } from '@/contexts/TenantsProvider';
+import Button from '@/ds-components/Button';
+import DangerousRaw from '@/ds-components/DangerousRaw';
+import FormField from '@/ds-components/FormField';
+import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
+import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
+import TextInput from '@/ds-components/TextInput';
+import useTenantPathname from '@/hooks/use-tenant-pathname';
+import * as pageLayout from '@/onboarding/scss/layout.module.scss';
+import { OnboardingPage, OnboardingRoute } from '@/onboarding/types';
+import { trySubmitSafe } from '@/utils/form';
+
+type CreateTenantForm = Omit;
+
+function CreateTenant() {
+ const methods = useForm({ defaultValues: { regionName: RegionName.EU } });
+ const {
+ control,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ register,
+ } = methods;
+ const { navigate } = useTenantPathname();
+ const { prependTenant } = useContext(TenantsContext);
+
+ const cloudApi = useCloudApi();
+
+ const onCreateClick = handleSubmit(
+ trySubmitSafe(async ({ name, regionName }: CreateTenantForm) => {
+ // @ts-expect-error will be removed once we bump the `@logto/cloud` version
+ const newTenant = await cloudApi.post('/api/tenants', { body: { name, regionName } });
+ prependTenant(newTenant);
+ navigate(joinPath(OnboardingRoute.Onboarding, newTenant.id, OnboardingPage.SignInExperience));
+ })
+ );
+
+ return (
+
+
+
+
Create your first tenant
+
+ A tenant is an isolated environment where you can manage user identities, applications,
+ and all other Logto resources.
+
+
+
+
+
+
+ (
+
+ {/* Manually maintaining the list of regions to avoid unexpected changes. We may consider using an API in the future. */}
+ {[RegionName.EU, RegionName.US].map((region) => (
+
+
+
+ }
+ value={region}
+ isDisabled={!isDevFeaturesEnabled && region !== RegionName.EU}
+ />
+ ))}
+
+ )}
+ />
+
+
+
+
+
+
+
+
+ );
+}
+
+export default CreateTenant;
diff --git a/packages/console/src/onboarding/pages/SignInExperience/index.module.scss b/packages/console/src/onboarding/pages/SignInExperience/index.module.scss
index 49ab6c146..f1bcc6fbc 100644
--- a/packages/console/src/onboarding/pages/SignInExperience/index.module.scss
+++ b/packages/console/src/onboarding/pages/SignInExperience/index.module.scss
@@ -18,11 +18,6 @@
padding: _.unit(12);
margin-right: _.unit(6);
- .title {
- margin-top: _.unit(6);
- font: var(--font-title-1);
- }
-
.authnSelector {
grid-template-columns: repeat(2, 1fr);
}
diff --git a/packages/console/src/onboarding/pages/SignInExperience/index.tsx b/packages/console/src/onboarding/pages/SignInExperience/index.tsx
index fb195df72..64bb27a47 100644
--- a/packages/console/src/onboarding/pages/SignInExperience/index.tsx
+++ b/packages/console/src/onboarding/pages/SignInExperience/index.tsx
@@ -1,7 +1,7 @@
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, useContext } from 'react';
+import { useCallback, useEffect, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
@@ -10,23 +10,21 @@ import Tools from '@/assets/icons/tools.svg';
import ActionBar from '@/components/ActionBar';
import { GtagConversionId, reportConversion } from '@/components/Conversion/utils';
import PageMeta from '@/components/PageMeta';
-import { TenantsContext } from '@/contexts/TenantsProvider';
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 useApi from '@/hooks/use-api';
import type { RequestError } from '@/hooks/use-api';
import useCurrentUser from '@/hooks/use-current-user';
-import useTenantPathname from '@/hooks/use-tenant-pathname';
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 * as pageLayout from '@/onboarding/scss/layout.module.scss';
-import { OnboardingPage } from '@/onboarding/types';
-import { getOnboardingPage } from '@/onboarding/utils';
import { trySubmitSafe } from '@/utils/form';
import { buildUrl } from '@/utils/url';
import { uriValidator } from '@/utils/validator';
@@ -42,25 +40,23 @@ import { defaultOnboardingSieFormData } from './sie-config-templates';
import { Authentication, type OnboardingSieFormData } from './types';
function SignInExperience() {
+ const swrOptions = useTenantSwrOptions();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
- const { navigate } = useTenantPathname();
const {
data: signInExperience,
error,
mutate,
- } = useSWR('api/sign-in-exp');
+ } = useSWR('api/sign-in-exp', swrOptions);
const isSignInExperienceDataLoading = !error && !signInExperience;
- const { isLoading: isUserAssetsServiceLoading } = useUserAssetsService();
+ const { isLoading: isUserAssetsServiceLoading } = useTenantUserAssetsService();
const isLoading = isSignInExperienceDataLoading || isUserAssetsServiceLoading;
- const api = useApi();
+ const api = useTenantApi();
const { isReady: isUserAssetsServiceReady } = useUserAssetsService();
const { update } = useUserOnboardingData();
const { user } = useCurrentUser();
- const { navigateTenant, currentTenantId } = useContext(TenantsContext);
const enterAdminConsole = async () => {
await update({ isOnboardingDone: true });
- navigateTenant(currentTenantId);
};
const {
@@ -146,7 +142,7 @@ function SignInExperience() {
-
{t('cloud.sie.title')}
+
{t('cloud.sie.title')}
{
for (const [key, value] of Object.entries(template)) {
@@ -235,7 +231,7 @@ function SignInExperience() {