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() {
- +
-
); diff --git a/packages/console/src/onboarding/pages/Welcome/index.module.scss b/packages/console/src/onboarding/pages/Welcome/index.module.scss index f6fb2b644..4a3fc0cc1 100644 --- a/packages/console/src/onboarding/pages/Welcome/index.module.scss +++ b/packages/console/src/onboarding/pages/Welcome/index.module.scss @@ -1,15 +1,5 @@ @use '@/scss/underscore' as _; -.title { - font: var(--font-title-1); - margin-top: _.unit(6); -} - -.description { - font: var(--font-body-2); - margin-top: _.unit(3); -} - .form { width: 100%; margin-top: _.unit(6); diff --git a/packages/console/src/onboarding/pages/Welcome/index.tsx b/packages/console/src/onboarding/pages/Welcome/index.tsx index 6c1ab69ac..e045fa9ca 100644 --- a/packages/console/src/onboarding/pages/Welcome/index.tsx +++ b/packages/console/src/onboarding/pages/Welcome/index.tsx @@ -48,7 +48,7 @@ function Welcome() { const onNext = async () => { await onSubmit(); - navigate(getOnboardingPage(OnboardingPage.SignInExperience)); + navigate(getOnboardingPage(OnboardingPage.CreateTenant)); }; return ( @@ -57,8 +57,8 @@ function Welcome() {
-
{t('cloud.welcome.title')}
-
{t('cloud.welcome.description')}
+
{t('cloud.welcome.title')}
+
{t('cloud.welcome.description')}
- +