0
Fork 0
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:
Gao Sun 2023-07-04 18:23:43 +08:00
parent 166ccf93ae
commit d2ba119cb6
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
5 changed files with 78 additions and 19 deletions

View file

@ -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 />;
}

View file

@ -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.

View file

@ -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 />;
}

View file

@ -41,6 +41,7 @@ type Tenants = {
*/
currentTenantStatus: CurrentTenantStatus;
setCurrentTenantStatus: (status: CurrentTenantStatus) => void;
/** Navigate to the given tenant ID. */
navigateTenant: (tenantId: string, options?: NavigateOptions) => void;
};

View 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;