Skip to content

Commit fec9898

Browse files
Show loading screen during organization switch in console
1 parent f56db81 commit fec9898

File tree

6 files changed

+130
-49
lines changed

6 files changed

+130
-49
lines changed

Diff for: apps/console/src/protected-app.tsx

+57-25
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
useAuthContext
2525
} from "@asgardeo/auth-react";
2626
import useSignIn from "@wso2is/admin.authentication.v1/hooks/use-sign-in";
27+
import LogoImage from "@wso2is/admin.core.v1/components/logo-image";
2728
import { PreLoader } from "@wso2is/admin.core.v1/components/pre-loader";
2829
import { Config } from "@wso2is/admin.core.v1/configs/app";
2930
import { AppConstants } from "@wso2is/admin.core.v1/constants/app-constants";
@@ -114,6 +115,8 @@ export const ProtectedApp: FunctionComponent<AppPropsInterface> = (): ReactEleme
114115
const [ routesFiltered, setRoutesFiltered ] = useState<boolean>(false);
115116
const [ isUserTenantless, setIsUserTenantless ] = useState(undefined);
116117

118+
const [ switchingOrganization, setSwitchingOrganization ] = useState(null);
119+
117120
useEffect(() => {
118121
dispatch(
119122
setDeploymentConfigs<DeploymentConfigInterface>(
@@ -353,33 +356,62 @@ export const ProtectedApp: FunctionComponent<AppPropsInterface> = (): ReactEleme
353356
filterRoutes(() => setRoutesFiltered(true), isUserTenantless, isFirstLevelOrg);
354357
}, [ filterRoutes, state.isAuthenticated, isFirstLevelOrg, isUserTenantless ]);
355358

356-
return (
357-
<SecureApp
358-
fallback={ <PreLoader /> }
359-
overrideSignIn={ async () => {
360-
const prompt: string = new URL(location.href).searchParams.get("prompt");
361-
const fidp: string = new URL(location.href).searchParams.get("fidp");
362-
363-
// This is to prompt the SSO page if a user tries to sign in
364-
// through a federated IdP using an existing email address.
365-
if (prompt) {
366-
await signIn({ prompt: "login" });
367-
} else {
368-
const authParams: { fidp?: string; } = {};
359+
useEffect(() => {
360+
document.addEventListener("orgSwitchStart", (event: CustomEvent) => {
361+
setSwitchingOrganization(event?.detail?.organization);
362+
});
369363

370-
if (fidp) {
371-
authParams["fidp"] = fidp;
364+
document.addEventListener("orgSwitchEnd", (_event: CustomEvent) => {
365+
setSwitchingOrganization(null);
366+
});
367+
}, []);
368+
369+
return (
370+
<>
371+
{ switchingOrganization && (<div
372+
className="ui modal transition"
373+
style={ {
374+
alignContent: "center",
375+
alignItems: "center",
376+
borderRadius: 0,
377+
display: "flex",
378+
flexDirection: "column",
379+
height: "100vh",
380+
justifyContent: "center",
381+
width: "100vw"
382+
} }>
383+
<LogoImage />
384+
<p className="mt-5">Switching to <strong>{ switchingOrganization?.name }</strong> organization</p>
385+
</div>) }
386+
<SecureApp
387+
fallback={ <PreLoader /> }
388+
overrideSignIn={ async () => {
389+
const prompt: string = new URL(location.href).searchParams.get("prompt");
390+
const fidp: string = new URL(location.href).searchParams.get("fidp");
391+
392+
// This is to prompt the SSO page if a user tries to sign in
393+
// through a federated IdP using an existing email address.
394+
if (prompt) {
395+
await signIn({ prompt: "login" });
396+
} else {
397+
const authParams: { fidp?: string; } = {};
398+
399+
if (fidp) {
400+
authParams["fidp"] = fidp;
401+
}
402+
403+
await signIn(authParams);
372404
}
405+
} }
406+
>
407+
<I18nextProvider i18n={ I18n.instance }>
408+
<SubscriptionProvider tierName={ tenantTier?.tierName ?? TenantTier.FREE }>
409+
{ renderApp && routesFiltered ? <App /> : <PreLoader /> }
410+
</SubscriptionProvider>
411+
</I18nextProvider>
412+
</SecureApp>
413+
414+
</>
373415

374-
await signIn(authParams);
375-
}
376-
} }
377-
>
378-
<I18nextProvider i18n={ I18n.instance }>
379-
<SubscriptionProvider tierName={ tenantTier?.tierName ?? TenantTier.FREE }>
380-
{ renderApp && routesFiltered ? <App /> : <PreLoader /> }
381-
</SubscriptionProvider>
382-
</I18nextProvider>
383-
</SecureApp>
384416
);
385417
};

Diff for: features/admin.core.v1/components/header.tsx

+3-24
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import Button from "@oxygen-ui/react/Button";
2121
import Chip from "@oxygen-ui/react/Chip";
2222
import Divider from "@oxygen-ui/react/Divider";
2323
import OxygenHeader, { HeaderProps } from "@oxygen-ui/react/Header";
24-
import Image from "@oxygen-ui/react/Image";
2524
import Link from "@oxygen-ui/react/Link";
2625
import ListItemIcon from "@oxygen-ui/react/ListItemIcon";
2726
import ListItemText from "@oxygen-ui/react/ListItemText";
@@ -39,10 +38,8 @@ import { OrganizationSwitchBreadcrumb } from "@wso2is/admin.organizations.v1/com
3938
import { useGetCurrentOrganizationType } from "@wso2is/admin.organizations.v1/hooks/use-get-organization-type";
4039
import useSubscription, { UseSubscriptionInterface } from "@wso2is/admin.subscription.v1/hooks/use-subscription";
4140
import { TenantTier } from "@wso2is/admin.subscription.v1/models/tenant-tier";
42-
import { resolveAppLogoFilePath } from "@wso2is/core/helpers";
4341
import { IdentifiableComponentInterface, ProfileInfoInterface } from "@wso2is/core/models";
4442
import { FeatureAccessConfigInterface } from "@wso2is/core/src/models";
45-
import { StringUtils } from "@wso2is/core/utils";
4643
import { I18n } from "@wso2is/i18n";
4744
import { useDocumentation } from "@wso2is/react-components";
4845
import React, { FunctionComponent, ReactElement, ReactNode, useEffect, useMemo, useState } from "react";
@@ -64,6 +61,7 @@ import { AppState } from "../store";
6461
import { CommonUtils } from "../utils/common-utils";
6562
import { EventPublisher } from "../utils/event-publisher";
6663
import "./header.scss";
64+
import LogoImage from "./logo-image";
6765

6866
/**
6967
* Dashboard layout Prop types.
@@ -353,33 +351,14 @@ const Header: FunctionComponent<HeaderPropsInterface> = ({
353351
return accountAppURL;
354352
};
355353

356-
const LOGO_IMAGE = () => {
357-
return (
358-
<Image
359-
src={ resolveAppLogoFilePath(
360-
window["AppUtils"].getConfig().ui.appLogo?.defaultLogoPath ??
361-
window["AppUtils"].getConfig().ui.appLogoPath,
362-
`${window["AppUtils"].getConfig().clientOrigin}/` +
363-
`${
364-
StringUtils.removeSlashesFromPath(window["AppUtils"].getConfig().appBase) !== ""
365-
? StringUtils.removeSlashesFromPath(window["AppUtils"].getConfig().appBase) + "/"
366-
: ""
367-
}libs/themes/` +
368-
config.ui.theme.name
369-
) }
370-
alt="logo"
371-
/>
372-
);
373-
};
374-
375354
return (
376355
<>
377356
<OxygenHeader
378357
className="is-header"
379358
brand={ {
380359
logo: {
381-
desktop: <LOGO_IMAGE />,
382-
mobile: <LOGO_IMAGE />
360+
desktop: <LogoImage />,
361+
mobile: <LogoImage />
383362
},
384363
onClick: () =>
385364
hasGettingStartedViewPermission &&

Diff for: features/admin.core.v1/components/logo-image.tsx

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
import Image from "@oxygen-ui/react/Image";
20+
import { resolveAppLogoFilePath } from "@wso2is/core/helpers";
21+
import { StringUtils } from "@wso2is/core/utils";
22+
import React from "react";
23+
import { useSelector } from "react-redux";
24+
import { ConfigReducerStateInterface } from "../models/reducer-state";
25+
import { AppState } from "../store";
26+
27+
const LogoImage = () => {
28+
const config: ConfigReducerStateInterface = useSelector((state: AppState) => state.config);
29+
30+
return (
31+
<Image
32+
src={ resolveAppLogoFilePath(
33+
window["AppUtils"].getConfig().ui.appLogo?.defaultLogoPath ??
34+
window["AppUtils"].getConfig().ui.appLogoPath,
35+
`${window["AppUtils"].getConfig().clientOrigin}/` +
36+
`${
37+
StringUtils.removeSlashesFromPath(window["AppUtils"].getConfig().appBase) !== ""
38+
? StringUtils.removeSlashesFromPath(window["AppUtils"].getConfig().appBase) + "/"
39+
: ""
40+
}libs/themes/` +
41+
config.ui.theme.name
42+
) }
43+
alt="logo"
44+
/>
45+
);
46+
};
47+
48+
export default LogoImage;

Diff for: features/admin.organizations.v1/components/organization-list.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ export const OrganizationList: FunctionComponent<OrganizationListPropsInterface>
269269
const handleOrganizationSwitch = async (
270270
organization: GenericOrganization
271271
): Promise<void> => {
272+
document.dispatchEvent(new CustomEvent("orgSwitchStart", {
273+
detail: { organization }
274+
}));
272275

273276
let response: BasicUserInfo = null;
274277

@@ -280,6 +283,10 @@ export const OrganizationList: FunctionComponent<OrganizationListPropsInterface>
280283
history.push(AppConstants.getPaths().get("GETTING_STARTED"));
281284
} catch(e) {
282285
// TODO: Handle error
286+
} finally {
287+
document.dispatchEvent(new CustomEvent("orgSwitchEnd", {
288+
detail: { organization: null }
289+
}));
283290
}
284291
};
285292

Diff for: features/admin.organizations.v1/components/organization-switch/organization-switch-breadcrumb.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ export const OrganizationSwitchBreadcrumb: FunctionComponent<OrganizationSwitchD
160160
organization: GenericOrganization,
161161
redirectToStart: boolean = true
162162
): Promise<void> => {
163+
document.dispatchEvent(new CustomEvent("orgSwitchStart", {
164+
detail: { organization }
165+
}));
163166
let response: BasicUserInfo = null;
164167

165168
try {
@@ -182,6 +185,10 @@ export const OrganizationSwitchBreadcrumb: FunctionComponent<OrganizationSwitchD
182185
}
183186
} catch(e) {
184187
// TODO: Handle error
188+
} finally {
189+
document.dispatchEvent(new CustomEvent("orgSwitchEnd", {
190+
detail: { organization: null }
191+
}));
185192
}
186193
};
187194

Diff for: features/admin.organizations.v1/pages/organization-edit.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ const OrganizationEditPage: FunctionComponent<OrganizationEditPagePropsInterface
177177
* Method that handles the organization switch.
178178
*/
179179
const handleOrganizationSwitch = async (): Promise<void> => {
180+
document.dispatchEvent(new CustomEvent("orgSwitchStart", {
181+
detail: { organization }
182+
}));
183+
180184
let response: BasicUserInfo = null;
181185

182186
try {
@@ -198,6 +202,10 @@ const OrganizationEditPage: FunctionComponent<OrganizationEditPagePropsInterface
198202
)
199203
})
200204
);
205+
} finally {
206+
document.dispatchEvent(new CustomEvent("orgSwitchEnd", {
207+
detail: { organization: null }
208+
}));
201209
}
202210
};
203211

0 commit comments

Comments
 (0)