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 { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
|
||||
function Redirect() {
|
||||
const { navigateTenant, tenants, currentTenant } = useContext(TenantsContext);
|
||||
function Redirect({ toTenantId }: { toTenantId: string }) {
|
||||
const { navigateTenant } = useContext(TenantsContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentTenant) {
|
||||
/** Fallback to another available tenant instead of showing `Forbidden`. */
|
||||
navigateTenant(tenants[0]?.id ?? '');
|
||||
}
|
||||
}, [navigateTenant, currentTenant, tenants]);
|
||||
navigateTenant(toTenantId);
|
||||
}, [navigateTenant, toTenantId]);
|
||||
|
||||
return <AppLoading />;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { useContext } from 'react';
|
||||
|
||||
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 AutoCreateTenant from './AutoCreateTenant';
|
||||
|
@ -9,19 +8,17 @@ import Redirect from './Redirect';
|
|||
import TenantLandingPage from './TenantLandingPage';
|
||||
|
||||
export default function Main() {
|
||||
const { tenants } = useContext(TenantsContext);
|
||||
const { isOnboarding, isLoaded } = useUserOnboardingData();
|
||||
const { isLoaded } = useMeCustomData();
|
||||
const { isOnboarding } = useUserOnboardingData();
|
||||
const { defaultTenantId } = useUserDefaultTenantId();
|
||||
|
||||
if (!isLoaded) {
|
||||
return <AppLoading />;
|
||||
}
|
||||
|
||||
/**
|
||||
* If current tenant ID is not set, but there is at least one tenant available,
|
||||
* trigger a redirect to the first tenant.
|
||||
*/
|
||||
if (tenants[0]) {
|
||||
return <Redirect />;
|
||||
// If current tenant ID is not set, but the defaultTenantId is available.
|
||||
if (defaultTenantId) {
|
||||
return <Redirect toTenantId={defaultTenantId} />;
|
||||
}
|
||||
|
||||
// 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
|
||||
import type ProtectedRoutes from '@/containers/ProtectedRoutes';
|
||||
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
|
||||
|
@ -19,7 +20,8 @@ import { TenantsContext } from '@/contexts/TenantsProvider';
|
|||
* Before the validation is complete, it renders `<AppLoading />`; after the validation is
|
||||
* 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 available to the user but getAccessToken() fails, it redirects to the
|
||||
* sign-in page to fetch a full-scoped token.
|
||||
|
@ -45,6 +47,7 @@ export default function TenantAccess() {
|
|||
const { getAccessToken, signIn, isAuthenticated } = useLogto();
|
||||
const { currentTenant, currentTenantId, currentTenantStatus, setCurrentTenantStatus } =
|
||||
useContext(TenantsContext);
|
||||
const { updateIfNeeded } = useUserDefaultTenantId();
|
||||
|
||||
useEffect(() => {
|
||||
const validate = async ({ indicator, id: tenantId }: TenantInfo) => {
|
||||
|
@ -81,5 +84,12 @@ export default function TenantAccess() {
|
|||
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 />;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ type Tenants = {
|
|||
*/
|
||||
currentTenantStatus: CurrentTenantStatus;
|
||||
setCurrentTenantStatus: (status: CurrentTenantStatus) => void;
|
||||
/** Navigate to the given tenant ID. */
|
||||
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