Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/App.tsx
2498 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
22
export const StartWorkspaceModalKeyBinding = `${/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? "⌘" : "Ctrl﹢"}O`;
23
24
// Top level Dashboard App component
25
const App: FC = () => {
26
const { user, loading } = useUserLoader();
27
const currentOrgQuery = useCurrentOrg();
28
const history = useHistory();
29
const location = useLocation();
30
const search = useQueryParams();
31
const { isDark, setIsDark } = useTheme();
32
33
useEffect(() => {
34
const onKeyDown = (event: KeyboardEvent) => {
35
if ((event.metaKey || event.ctrlKey) && event.key === "o") {
36
event.preventDefault();
37
history.push("/new");
38
return;
39
} else if (event.metaKey && event.ctrlKey && event.shiftKey && event.key === "M") {
40
setIsDark(!isDark);
41
return;
42
}
43
};
44
window.addEventListener("keydown", onKeyDown);
45
return () => {
46
window.removeEventListener("keydown", onKeyDown);
47
};
48
}, [history, isDark, setIsDark]);
49
50
// Setup analytics/tracking
51
useAnalyticsTracking();
52
53
if (location.pathname === "/linkedin" && search.get("code") && search.get("state")) {
54
return <LinkedInCallback />;
55
}
56
57
// Page can be loaded even if user is not authenticated
58
// RegEx is used for accounting for trailing slash /
59
if (window.location.pathname.replace(/\/$/, "") === "/quickstart") {
60
return <QuickStart />;
61
}
62
63
if (loading) {
64
return <AppLoading />;
65
}
66
67
// Technically this should get handled in the QueryErrorBoundary, but having it here doesn't hurt
68
// At this point if there's no user, they should Login
69
if (!user) {
70
return <Login />;
71
}
72
73
// At this point we want to make sure that we never render AppRoutes prematurely, e.g. without finishing loading the orgs
74
// This would cause us to re-render the whole App again soon after, creating havoc with all our "onMount" hooks.
75
if (currentOrgQuery.isLoading) {
76
return <AppLoading />;
77
}
78
79
// If we made it here, we have a logged in user w/ their teams. Yay.
80
return (
81
<Suspense fallback={<AppLoading />}>
82
{/* Any required onboarding flows will be handled here before rendering the main app layout & routes */}
83
<AppBlockingFlows>
84
{/* Use org id *and* user id as key to force re-render on org *or* user changes. */}
85
<AppRoutes key={`${currentOrgQuery?.data?.id ?? "no-org"}-${user.id}`} />
86
</AppBlockingFlows>
87
</Suspense>
88
);
89
};
90
91
// Routing level above main App component for any routes that don't need user/orgs loaded, such as addressable error pages
92
export const RootAppRouter: FC = () => {
93
return (
94
<Switch>
95
{/* Any route that starts w/ `/error` will render a specific error page if it matches a route, otherwise a generic error page */}
96
<Route path="/error" component={ErrorPages} />
97
<Route path="*" component={App} />
98
</Switch>
99
);
100
};
101
102