Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/App.tsx
3607 views
1
/**
2
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3
* Licensed under the GNU Affero General Public License (AGPL).
4
* See License.AGPL.txt in the project root for license information.
5
*/
6
7
import { FC, Suspense, useEffect } from "react";
8
import { AppLoading } from "./app/AppLoading";
9
import { AppRoutes } from "./app/AppRoutes";
10
import { useCurrentOrg } from "./data/organizations/orgs-query";
11
import { useAnalyticsTracking } from "./hooks/use-analytics-tracking";
12
import { useUserLoader } from "./hooks/use-user-loader";
13
import { Login } from "./Login";
14
import { AppBlockingFlows } from "./app/AppBlockingFlows";
15
import { Route, Switch, useHistory, useLocation } from "react-router";
16
import { ErrorPages } from "./error-pages/ErrorPages";
17
import { LinkedInCallback } from "react-linkedin-login-oauth2";
18
import { useQueryParams } from "./hooks/use-query-params";
19
import { useTheme } from "./theme-context";
20
import QuickStart from "./components/QuickStart";
21
import { isGitpodIo, getURLHash } from "./utils";
22
23
export const StartWorkspaceModalKeyBinding = `${/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? "⌘" : "Ctrl﹢"}O`;
24
25
// Top level Dashboard App component
26
const App: FC = () => {
27
const { user, loading } = useUserLoader();
28
const currentOrgQuery = useCurrentOrg();
29
const history = useHistory();
30
const location = useLocation();
31
const search = useQueryParams();
32
const { isDark, setIsDark } = useTheme();
33
34
useEffect(() => {
35
const onKeyDown = (event: KeyboardEvent) => {
36
if ((event.metaKey || event.ctrlKey) && event.key === "o") {
37
event.preventDefault();
38
history.push("/new");
39
return;
40
} else if (event.metaKey && event.ctrlKey && event.shiftKey && event.key === "M") {
41
setIsDark(!isDark);
42
return;
43
}
44
};
45
window.addEventListener("keydown", onKeyDown);
46
return () => {
47
window.removeEventListener("keydown", onKeyDown);
48
};
49
}, [history, isDark, setIsDark]);
50
51
// Setup analytics/tracking
52
useAnalyticsTracking();
53
54
if (location.pathname === "/linkedin" && search.get("code") && search.get("state")) {
55
return <LinkedInCallback />;
56
}
57
58
// Page can be loaded even if user is not authenticated
59
// RegEx is used for accounting for trailing slash /
60
if (window.location.pathname.replace(/\/$/, "") === "/quickstart") {
61
return <QuickStart />;
62
}
63
64
if (loading) {
65
return <AppLoading />;
66
}
67
68
// Redirect non-signed-in Gitpod Classic PAYG users from gitpod.io/# to app.ona.com/#
69
// This provides redundancy with QueryErrorBoundary for better coverage
70
const hash = getURLHash();
71
if (!user && isGitpodIo() && location.pathname === "/" && hash !== "") {
72
// Preserve the hash fragment when redirecting
73
const hashFragment = window.location.hash;
74
window.location.href = `https://app.ona.com/${hashFragment}`;
75
return <AppLoading />; // Show loading while redirecting
76
}
77
78
// Technically this should get handled in the QueryErrorBoundary, but having it here doesn't hurt
79
// At this point if there's no user, they should Login
80
if (!user) {
81
return <Login />;
82
}
83
84
// At this point we want to make sure that we never render AppRoutes prematurely, e.g. without finishing loading the orgs
85
// This would cause us to re-render the whole App again soon after, creating havoc with all our "onMount" hooks.
86
if (currentOrgQuery.isLoading) {
87
return <AppLoading />;
88
}
89
90
// If we made it here, we have a logged in user w/ their teams. Yay.
91
return (
92
<Suspense fallback={<AppLoading />}>
93
{/* Any required onboarding flows will be handled here before rendering the main app layout & routes */}
94
<AppBlockingFlows>
95
{/* Use org id *and* user id as key to force re-render on org *or* user changes. */}
96
<AppRoutes key={`${currentOrgQuery?.data?.id ?? "no-org"}-${user.id}`} />
97
</AppBlockingFlows>
98
</Suspense>
99
);
100
};
101
102
// Routing level above main App component for any routes that don't need user/orgs loaded, such as addressable error pages
103
export const RootAppRouter: FC = () => {
104
return (
105
<Switch>
106
{/* Any route that starts w/ `/error` will render a specific error page if it matches a route, otherwise a generic error page */}
107
<Route path="/error" component={ErrorPages} />
108
<Route path="*" component={App} />
109
</Switch>
110
);
111
};
112
113