mirror of
https://github.com/logto-io/logto.git
synced 2025-01-13 21:30:30 -05:00
feat(console): save and update user default tenant id
This commit is contained in:
parent
166ccf93ae
commit
d2ba119cb6
5 changed files with 78 additions and 19 deletions
|
@ -3,15 +3,12 @@ import { useContext, useEffect } from 'react';
|
||||||
import AppLoading from '@/components/AppLoading';
|
import AppLoading from '@/components/AppLoading';
|
||||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||||
|
|
||||||
function Redirect() {
|
function Redirect({ toTenantId }: { toTenantId: string }) {
|
||||||
const { navigateTenant, tenants, currentTenant } = useContext(TenantsContext);
|
const { navigateTenant } = useContext(TenantsContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentTenant) {
|
navigateTenant(toTenantId);
|
||||||
/** Fallback to another available tenant instead of showing `Forbidden`. */
|
}, [navigateTenant, toTenantId]);
|
||||||
navigateTenant(tenants[0]?.id ?? '');
|
|
||||||
}
|
|
||||||
}, [navigateTenant, currentTenant, tenants]);
|
|
||||||
|
|
||||||
return <AppLoading />;
|
return <AppLoading />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
import AppLoading from '@/components/AppLoading';
|
import AppLoading from '@/components/AppLoading';
|
||||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
import useMeCustomData from '@/hooks/use-me-custom-data';
|
||||||
|
import useUserDefaultTenantId from '@/hooks/use-user-default-tenant-id';
|
||||||
import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
|
import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
|
||||||
|
|
||||||
import AutoCreateTenant from './AutoCreateTenant';
|
import AutoCreateTenant from './AutoCreateTenant';
|
||||||
|
@ -9,19 +8,17 @@ import Redirect from './Redirect';
|
||||||
import TenantLandingPage from './TenantLandingPage';
|
import TenantLandingPage from './TenantLandingPage';
|
||||||
|
|
||||||
export default function Main() {
|
export default function Main() {
|
||||||
const { tenants } = useContext(TenantsContext);
|
const { isLoaded } = useMeCustomData();
|
||||||
const { isOnboarding, isLoaded } = useUserOnboardingData();
|
const { isOnboarding } = useUserOnboardingData();
|
||||||
|
const { defaultTenantId } = useUserDefaultTenantId();
|
||||||
|
|
||||||
if (!isLoaded) {
|
if (!isLoaded) {
|
||||||
return <AppLoading />;
|
return <AppLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// If current tenant ID is not set, but the defaultTenantId is available.
|
||||||
* If current tenant ID is not set, but there is at least one tenant available,
|
if (defaultTenantId) {
|
||||||
* trigger a redirect to the first tenant.
|
return <Redirect toTenantId={defaultTenantId} />;
|
||||||
*/
|
|
||||||
if (tenants[0]) {
|
|
||||||
return <Redirect />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A new user has just signed up and has no tenant, needs to create a new tenant.
|
// A new user has just signed up and has no tenant, needs to create a new tenant.
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { getCallbackUrl } from '@/consts';
|
||||||
// eslint-disable-next-line unused-imports/no-unused-imports
|
// eslint-disable-next-line unused-imports/no-unused-imports
|
||||||
import type ProtectedRoutes from '@/containers/ProtectedRoutes';
|
import type ProtectedRoutes from '@/containers/ProtectedRoutes';
|
||||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||||
|
import useUserDefaultTenantId from '@/hooks/use-user-default-tenant-id';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The container that ensures the user has access to the current tenant. When the user is
|
* The container that ensures the user has access to the current tenant. When the user is
|
||||||
|
@ -19,7 +20,8 @@ import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||||
* Before the validation is complete, it renders `<AppLoading />`; after the validation is
|
* Before the validation is complete, it renders `<AppLoading />`; after the validation is
|
||||||
* complete:
|
* complete:
|
||||||
*
|
*
|
||||||
* - If the user has access to the current tenant, it renders `<Outlet />`.
|
* - If the user has access to the current tenant, it renders `<Outlet />` and sets the
|
||||||
|
* user's default tenant ID to the current tenant ID.
|
||||||
* - If the tenant is unavailable to the user, it redirects to the home page.
|
* - If the tenant is unavailable to the user, it redirects to the home page.
|
||||||
* - If the tenant is available to the user but getAccessToken() fails, it redirects to the
|
* - If the tenant is available to the user but getAccessToken() fails, it redirects to the
|
||||||
* sign-in page to fetch a full-scoped token.
|
* sign-in page to fetch a full-scoped token.
|
||||||
|
@ -45,6 +47,7 @@ export default function TenantAccess() {
|
||||||
const { getAccessToken, signIn, isAuthenticated } = useLogto();
|
const { getAccessToken, signIn, isAuthenticated } = useLogto();
|
||||||
const { currentTenant, currentTenantId, currentTenantStatus, setCurrentTenantStatus } =
|
const { currentTenant, currentTenantId, currentTenantStatus, setCurrentTenantStatus } =
|
||||||
useContext(TenantsContext);
|
useContext(TenantsContext);
|
||||||
|
const { updateIfNeeded } = useUserDefaultTenantId();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const validate = async ({ indicator, id: tenantId }: TenantInfo) => {
|
const validate = async ({ indicator, id: tenantId }: TenantInfo) => {
|
||||||
|
@ -81,5 +84,12 @@ export default function TenantAccess() {
|
||||||
signIn,
|
signIn,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Update the user's default tenant ID if the current tenant is validated.
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentTenantStatus === 'validated') {
|
||||||
|
void updateIfNeeded();
|
||||||
|
}
|
||||||
|
}, [currentTenantStatus, updateIfNeeded]);
|
||||||
|
|
||||||
return currentTenantStatus === 'validated' ? <Outlet /> : <AppLoading />;
|
return currentTenantStatus === 'validated' ? <Outlet /> : <AppLoading />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ type Tenants = {
|
||||||
*/
|
*/
|
||||||
currentTenantStatus: CurrentTenantStatus;
|
currentTenantStatus: CurrentTenantStatus;
|
||||||
setCurrentTenantStatus: (status: CurrentTenantStatus) => void;
|
setCurrentTenantStatus: (status: CurrentTenantStatus) => void;
|
||||||
|
/** Navigate to the given tenant ID. */
|
||||||
navigateTenant: (tenantId: string, options?: NavigateOptions) => void;
|
navigateTenant: (tenantId: string, options?: NavigateOptions) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
54
packages/console/src/hooks/use-user-default-tenant-id.ts
Normal file
54
packages/console/src/hooks/use-user-default-tenant-id.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { trySafe } from '@silverhand/essentials';
|
||||||
|
import { useCallback, useContext, useMemo } from 'react';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||||
|
|
||||||
|
import useMeCustomData from './use-me-custom-data';
|
||||||
|
|
||||||
|
const key = 'defaultTenantId';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook that gets the default tenant ID for the current user from user's `customData`.
|
||||||
|
*
|
||||||
|
* - By default, the default tenant ID is empty, which means the first tenant is the default tenant.
|
||||||
|
* - If the default tenant ID is not available to the user anymore, it semantically equals to the first tenant ID.
|
||||||
|
* - If the user manually navigates to a tenant, the default tenant ID will be set to the target tenant ID.
|
||||||
|
*/
|
||||||
|
const useUserDefaultTenantId = () => {
|
||||||
|
const { data, update: updateMeCustomData } = useMeCustomData();
|
||||||
|
const { tenants, currentTenantId } = useContext(TenantsContext);
|
||||||
|
|
||||||
|
const defaultTenantId = useMemo(() => {
|
||||||
|
const storedId = trySafe(() => z.object({ [key]: z.string() }).parse(data)[key]);
|
||||||
|
|
||||||
|
// Ensure the stored ID is still available to the user.
|
||||||
|
if (storedId && tenants.some(({ id }) => id === storedId)) {
|
||||||
|
return storedId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the first tenant ID.
|
||||||
|
return tenants[0]?.id;
|
||||||
|
}, [data, tenants]);
|
||||||
|
|
||||||
|
const updateIfNeeded = useCallback(async () => {
|
||||||
|
if (currentTenantId !== defaultTenantId) {
|
||||||
|
await updateMeCustomData({
|
||||||
|
[key]: currentTenantId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [currentTenantId, defaultTenantId, updateMeCustomData]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultTenantId,
|
||||||
|
/**
|
||||||
|
* Update the default tenant ID to the current tenant ID if:
|
||||||
|
*
|
||||||
|
* 1. The current tenant ID is not empty.
|
||||||
|
* 2. The default tenant ID does not equal to the current tenant ID.
|
||||||
|
*/
|
||||||
|
updateIfNeeded,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useUserDefaultTenantId;
|
Loading…
Add table
Reference in a new issue