mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor: fix test and components
This commit is contained in:
parent
c4e13ff525
commit
a3e3363b10
8 changed files with 52 additions and 62 deletions
|
@ -1,41 +1,17 @@
|
|||
import { useLogto } from '@logto/react';
|
||||
import { trySafe } from '@silverhand/essentials';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useHref } from 'react-router-dom';
|
||||
|
||||
import AppLoading from '@/components/AppLoading';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
|
||||
function Redirect() {
|
||||
const { getAccessToken, signIn } = useLogto();
|
||||
const { navigateTenant, tenants, currentTenantId } = useContext(TenantsContext);
|
||||
|
||||
const tenant = tenants.find(({ id }) => id === currentTenantId);
|
||||
const href = useHref(currentTenantId + '/callback');
|
||||
const { navigateTenant, tenants, currentTenant } = useContext(TenantsContext);
|
||||
|
||||
useEffect(() => {
|
||||
const validate = async (indicator: string) => {
|
||||
// Test fetching an access token for the current Tenant ID.
|
||||
// If failed, it means the user finishes the first auth, ands still needs to auth again to
|
||||
// fetch the full-scoped (with all available tenants) token.
|
||||
if (await trySafe(getAccessToken(indicator))) {
|
||||
navigateTenant(currentTenantId);
|
||||
} else {
|
||||
void signIn(new URL(href, window.location.origin).toString());
|
||||
}
|
||||
};
|
||||
|
||||
if (tenant) {
|
||||
void validate(tenant.indicator);
|
||||
}
|
||||
}, [currentTenantId, getAccessToken, href, navigateTenant, signIn, tenant]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tenant) {
|
||||
if (!currentTenant) {
|
||||
/** Fallback to another available tenant instead of showing `Forbidden`. */
|
||||
navigateTenant(tenants[0]?.id ?? '');
|
||||
}
|
||||
}, [navigateTenant, tenant, tenants]);
|
||||
}, [navigateTenant, currentTenant, tenants]);
|
||||
|
||||
return <AppLoading />;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { defaultTenantId, ossConsolePath } from '@logto/schemas';
|
||||
import { conditionalArray } from '@silverhand/essentials';
|
||||
|
||||
import { CloudRoute } from '@/cloud/types';
|
||||
|
||||
|
@ -36,6 +37,9 @@ export const getUserTenantId = () => {
|
|||
|
||||
export const getBasename = () => (isCloud ? '/' + getUserTenantId() : ossConsolePath);
|
||||
|
||||
export const getCallbackUrl = (tenantId?: string) =>
|
||||
new URL('/' + conditionalArray(tenantId, 'callback').join('/'), window.location.origin);
|
||||
|
||||
export const getSignOutRedirectPathname = () => (isCloud ? '/' : ossConsolePath);
|
||||
|
||||
export const maxFreeTenantNumbers = 3;
|
||||
|
|
|
@ -25,6 +25,7 @@ export default function TenantSelector() {
|
|||
appendTenant,
|
||||
currentTenant: currentTenantInfo,
|
||||
currentTenantId,
|
||||
navigateTenant,
|
||||
} = useContext(TenantsContext);
|
||||
|
||||
const isCreateButtonDisabled = useMemo(
|
||||
|
@ -75,7 +76,8 @@ export default function TenantSelector() {
|
|||
key={id}
|
||||
className={styles.dropdownItem}
|
||||
onClick={() => {
|
||||
window.open(new URL(`/${id}`, window.location.origin).toString(), '_self');
|
||||
navigateTenant(id);
|
||||
setShowDropdown(false);
|
||||
}}
|
||||
>
|
||||
<div className={styles.dropdownName}>{name}</div>
|
||||
|
@ -110,7 +112,7 @@ export default function TenantSelector() {
|
|||
onClose={async (tenant?: TenantInfo) => {
|
||||
if (tenant) {
|
||||
appendTenant(tenant);
|
||||
window.location.assign(new URL(`/${tenant.id}`, window.location.origin).toString());
|
||||
navigateTenant(tenant.id);
|
||||
}
|
||||
setShowCreateTenantModal(false);
|
||||
}}
|
||||
|
|
|
@ -1,16 +1,38 @@
|
|||
import { useContext } from 'react';
|
||||
import { useLogto } from '@logto/react';
|
||||
import { type TenantInfo } from '@logto/schemas/lib/models/tenants.js';
|
||||
import { trySafe } from '@silverhand/essentials';
|
||||
import { useContext, useEffect } from 'react';
|
||||
|
||||
import AppLoading from '@/components/AppLoading';
|
||||
import { getCallbackUrl } from '@/consts';
|
||||
import { isCloud } from '@/consts/env';
|
||||
import { AppEndpointsContext } from '@/contexts/AppEndpointsProvider';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import useTrackUserId from '@/hooks/use-track-user-id';
|
||||
import OnboardingApp from '@/onboarding/App';
|
||||
import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
|
||||
import ConsoleApp from '@/pages/Main';
|
||||
|
||||
function TenantAppContainer() {
|
||||
const { getAccessToken, signIn } = useLogto();
|
||||
const { userEndpoint } = useContext(AppEndpointsContext);
|
||||
const { isOnboarding, isLoaded } = useUserOnboardingData();
|
||||
const { currentTenant } = useContext(TenantsContext);
|
||||
|
||||
useEffect(() => {
|
||||
const validate = async ({ indicator, id }: TenantInfo) => {
|
||||
// Test fetching an access token for the current Tenant ID.
|
||||
// If failed, it means the user finishes the first auth, ands still needs to auth again to
|
||||
// fetch the full-scoped (with all available tenants) token.
|
||||
if (!(await trySafe(getAccessToken(indicator)))) {
|
||||
void signIn(getCallbackUrl(id).href);
|
||||
}
|
||||
};
|
||||
|
||||
if (currentTenant) {
|
||||
void validate(currentTenant);
|
||||
}
|
||||
}, [currentTenant, getAccessToken, signIn]);
|
||||
|
||||
useTrackUserId();
|
||||
|
||||
|
|
|
@ -76,19 +76,10 @@ function TenantsProvider({ children }: Props) {
|
|||
const [isInitComplete, setIsInitComplete] = useState(!isCloud);
|
||||
const [currentTenantId, setCurrentTenantId] = useState(getUserTenantId());
|
||||
|
||||
const navigateTenant = useCallback((tenantId: string, options?: NavigateOptions) => {
|
||||
const params = [
|
||||
options?.state ?? {},
|
||||
'',
|
||||
new URL(`/${tenantId}`, window.location.origin).toString(),
|
||||
] satisfies Parameters<typeof window.history.pushState>;
|
||||
|
||||
if (options?.replace) {
|
||||
window.history.replaceState(...params);
|
||||
return;
|
||||
}
|
||||
|
||||
window.history.pushState(...params);
|
||||
const navigateTenant = useCallback((tenantId: string) => {
|
||||
// Use `window.open()` to force page reload since we use `basename` for the router
|
||||
// which will not re-create the router instance when the URL changes.
|
||||
window.open(`/${tenantId}`, '_self');
|
||||
setCurrentTenantId(tenantId);
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -22,19 +22,28 @@ import HandleSocialCallback from '../Profile/containers/HandleSocialCallback';
|
|||
|
||||
function Main() {
|
||||
const swrOptions = useSwrOptions();
|
||||
|
||||
const router = useMemo(
|
||||
() =>
|
||||
createBrowserRouter(
|
||||
createRoutesFromElements(
|
||||
<>
|
||||
<Route path="/*">
|
||||
<Route path="callback" element={<Callback />} />
|
||||
<Route path="welcome" element={<Welcome />} />
|
||||
<Route path="handle-social" element={<HandleSocialCallback />} />
|
||||
<Route element={<AppContent />}>
|
||||
<Route path="/*" element={<ConsoleContent />} />
|
||||
<Route path="*" element={<ConsoleContent />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</>
|
||||
),
|
||||
// Currently we use `window.open()` to navigate between tenants so the `useMemo` hook
|
||||
// can have no dependency and the router will be created anyway. Consider integrating the
|
||||
// tenant ID into the router and remove basename here if we want to use `history.pushState()`
|
||||
// to navigate.
|
||||
//
|
||||
// Caveat: To use `history.pushState()`, we'd better to create a browser router in the upper
|
||||
// level of the component tree to make the tenant ID a part of the URL. Otherwise, we need
|
||||
// to handle `popstate` event to update the tenant ID when the user navigates back.
|
||||
{ basename: getBasename() }
|
||||
),
|
||||
[]
|
||||
|
|
|
@ -101,20 +101,6 @@ function TenantBasicSettings() {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Redirect to the first tenant if the current tenant is deleted;
|
||||
* Redirect to Cloud console landing page if there is no tenant.
|
||||
*/
|
||||
if (!tenants.some(({ id }) => id === currentTenantId)) {
|
||||
window.location.assign(
|
||||
tenants[0]?.id
|
||||
? new URL(`/${tenants[0]?.id}`, window.location.origin).toString()
|
||||
: new URL(window.location.origin).toString()
|
||||
);
|
||||
}
|
||||
}, [currentTenantId, tenants]);
|
||||
|
||||
if (error) {
|
||||
return <AppError errorMessage={error.message} callStack={error.stack} />;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ describe('smoke testing for cloud', () => {
|
|||
await expect(page).toClick('div[role=button][class$=item]');
|
||||
|
||||
// Click the next button
|
||||
await expect(page).toClick('div[class$=actions] button:first-child');
|
||||
await expect(page).toClick('div[class$=actions] button', { text: 'Next' });
|
||||
|
||||
// Wait for the next page to load
|
||||
await expect(page).toMatchElement('div[class$=config] div[class$=title]', {
|
||||
|
@ -127,7 +127,7 @@ describe('smoke testing for cloud', () => {
|
|||
|
||||
await page.waitForTimeout(500);
|
||||
const createTenantButton = await page.waitForSelector(
|
||||
'div[class$=ReactModalPortal] div[class$=dropdownContainer] > div[class$=dropdown] > div[class$=createTenantButton][role=button]:has(div)'
|
||||
'div[class$=ReactModalPortal] div[class$=dropdownContainer] > div[class$=dropdown] > button[class$=createTenantButton]:has(div)'
|
||||
);
|
||||
await expect(createTenantButton).toMatchElement('div', { text: 'Create tenant' });
|
||||
await createTenantButton.click();
|
||||
|
|
Loading…
Reference in a new issue