Skip to content

Commit 2ef05e4

Browse files
committed
[dashboard] What's New
1 parent 7515590 commit 2ef05e4

File tree

5 files changed

+129
-41
lines changed

5 files changed

+129
-41
lines changed

components/dashboard/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
The dashboard is written in TypeScript and React. For styling it uses TailwindCSS which is a bit nicer than inlining CSS as it supports pseudo classes and a is a little more abstract/reusable.
44

5-
The App.tsx is the entry point for the SPA and it uses React-Router to register all pages.
5+
The `App.tsx` is the entry point for the SPA and it uses React-Router to register all pages.
66

77
```ts
88
<Switch>

components/dashboard/src/App.tsx

+50-39
Original file line numberDiff line numberDiff line change
@@ -9,89 +9,100 @@ import Menu from './components/Menu';
99
import { BrowserRouter } from "react-router-dom";
1010
import { Route, Switch } from "react-router";
1111
import { Workspaces } from './workspaces/Workspaces';
12-
import { CreateWorkspace } from './start/CreateWorkspace';
13-
import StartWorkspace from './start/StartWorkspace';
12+
1413
import { Login } from './Login';
1514
import { UserContext } from './user-context';
1615
import { getGitpodService } from './service/service';
17-
import Header from './components/Header';
16+
import { shouldSeeWhatsNew, WhatsNew } from './WhatsNew';
1817

1918
const Account = React.lazy(() => import(/* webpackPrefetch: true */ './settings/Account'));
2019
const Notifications = React.lazy(() => import(/* webpackPrefetch: true */ './settings/Notifications'));
2120
const Plans = React.lazy(() => import(/* webpackPrefetch: true */ './settings/Plans'));
2221
const EnvironmentVariables = React.lazy(() => import(/* webpackPrefetch: true */ './settings/EnvironmentVariables'));
2322
const Integrations = React.lazy(() => import(/* webpackPrefetch: true */ './settings/Integrations'));
2423
const Preferences = React.lazy(() => import(/* webpackPrefetch: true */ './settings/Preferences'));
24+
const StartWorkspace = React.lazy(() => import(/* webpackPrefetch: true */ './start/StartWorkspace'));
25+
const CreateWorkspace = React.lazy(() => import(/* webpackPrefetch: true */ './start/CreateWorkspace'));
2526

2627
function Loading() {
2728
return <>
28-
<Header title="" subtitle="" />
2929
</>;
3030
}
3131

3232
function App() {
3333
const { user, setUser } = useContext(UserContext);
3434

3535
const [loading, setLoading] = useState<boolean>(true);
36+
const [isWhatsNewShown, setWhatsNewShown] = useState(false);
3637

3738
useEffect(() => {
3839
(async () => {
3940
try {
40-
setUser(await getGitpodService().server.getLoggedInUser());
41+
const usr = await getGitpodService().server.getLoggedInUser()
42+
setUser(usr);
4143
} catch (error) {
4244
console.log(error);
4345
}
4446
setLoading(false);
4547
})();
4648
}, []);
47-
48-
if (!loading && !user) {
49+
50+
if (loading) {
51+
return <Loading />
52+
}
53+
if (!user) {
4954
return (<Login />)
5055
};
51-
56+
const shouldWhatsNewShown = shouldSeeWhatsNew(user)
57+
if (shouldWhatsNewShown !== isWhatsNewShown) {
58+
setWhatsNewShown(shouldWhatsNewShown);
59+
}
60+
5261
window.addEventListener("hashchange", () => {
53-
// Refresh on hash change if the path is '/' (new context URL)
54-
if (window.location.pathname === '/') {
55-
window.location.reload(true);
56-
}
62+
// Refresh on hash change if the path is '/' (new context URL)
63+
if (window.location.pathname === '/') {
64+
window.location.reload(true);
65+
}
5766
}, false);
5867

68+
let toRender: React.ReactElement = <Route>
69+
<div className="container">
70+
{renderMenu()}
71+
<Switch>
72+
<Route path={["/", "/workspaces"]} exact render={
73+
() => <Workspaces />} />
74+
<Route path={["/account", "/settings"]} exact component={Account} />
75+
<Route path={["/integrations", "/access-control"]} exact component={Integrations} />
76+
<Route path="/notifications" exact component={Notifications} />
77+
<Route path="/plans" exact component={Plans} />
78+
<Route path="/variables" exact component={EnvironmentVariables} />
79+
<Route path="/preferences" exact component={Preferences} />
80+
</Switch>
81+
</div>
82+
</Route>;
83+
5984
const hash = getURLHash();
60-
if (window.location.pathname === '/' && hash !== '') {
61-
return <CreateWorkspace contextUrl={hash} />;
62-
}
63-
if (/\/start\/?/.test(window.location.pathname) && hash !== '') {
64-
return <StartWorkspace workspaceId={hash} />;
85+
const isCreation = window.location.pathname === '/' && hash !== '';
86+
const isWsStart = /\/start\/?/.test(window.location.pathname) && hash !== '';
87+
if (isWhatsNewShown) {
88+
toRender = <WhatsNew visible={true} onClose={() => setWhatsNewShown(false)} />;
89+
} else if (isCreation) {
90+
toRender = <CreateWorkspace contextUrl={hash} />;
91+
} else if (isWsStart) {
92+
<StartWorkspace workspaceId={hash} />;
6593
}
6694

6795
return (
6896
<BrowserRouter>
69-
<div className="container">
70-
{user && renderMenu()}
71-
72-
<Suspense fallback={<Loading />}>
73-
<Switch>
74-
{user && (
75-
<React.Fragment>
76-
<Route path={["/", "/workspaces"]} exact render={
77-
() => <Workspaces />} />
78-
<Route path={["/account", "/settings"]} exact component={Account} />
79-
<Route path={["/integrations", "/access-control"]} exact component={Integrations} />
80-
<Route path="/notifications" exact component={Notifications} />
81-
<Route path="/plans" exact component={Plans} />
82-
<Route path="/variables" exact component={EnvironmentVariables} />
83-
<Route path="/preferences" exact component={Preferences} />
84-
</React.Fragment>
85-
)}
86-
</Switch>
87-
</Suspense>
88-
</div>
97+
<Suspense fallback={<Loading />}>
98+
{toRender}
99+
</Suspense>
89100
</BrowserRouter>
90101
);
91102
}
92103

93-
function getURLHash () {
94-
return window.location.hash.replace(/^[#/]+/, '');
104+
function getURLHash() {
105+
return window.location.hash.replace(/^[#/]+/, '');
95106
}
96107

97108
const renderMenu = () => (

components/dashboard/src/WhatsNew.tsx

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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 { User } from "@gitpod/gitpod-protocol";
8+
import { useContext } from "react";
9+
import Modal from "./components/Modal";
10+
import { getGitpodService } from "./service/service";
11+
import { UserContext } from "./user-context";
12+
13+
const news = 'April-2021';
14+
export function shouldSeeWhatsNew(user: User): boolean {
15+
const whatsNewSeen = user?.additionalData?.whatsNewSeen;
16+
return user.creationDate <= '2021-04-08' && (!whatsNewSeen || !whatsNewSeen[news]);
17+
}
18+
19+
export function WhatsNew(props: { visible: boolean, onClose: () => void }) {
20+
const {user, setUser} = useContext(UserContext);
21+
const internalClose = async (switchToCode?: boolean) => {
22+
if (!user) {
23+
return;
24+
}
25+
const additionalData = user.additionalData || {};
26+
additionalData.whatsNewSeen = {
27+
...additionalData.whatsNewSeen,
28+
[news]: new Date().toISOString()
29+
}
30+
if (switchToCode) {
31+
const ideSettings = additionalData.ideSettings || {};
32+
ideSettings.defaultIde = 'code';
33+
}
34+
await getGitpodService().server.updateLoggedInUser({
35+
additionalData
36+
});
37+
setUser(user);
38+
props.onClose();
39+
}
40+
return <Modal visible={props.visible} onClose={internalClose}>
41+
<h3 className="pb-4">What's New 🎁</h3>
42+
<div className="border-t border-gray-200 -mx-6 px-6 py-4">
43+
<p className="pb-2 text-gray-900 text-base font-medium">New Dashboard</p>
44+
<p className="pb-2 text-gray-500 text-sm">We have made some layout changes on the dashboard to improve the overall user experience of the product.</p>
45+
</div>
46+
<div className="border-t border-b border-gray-200 -mx-6 px-6 py-4">
47+
<p className="pb-2 text-gray-900 text-base font-medium">VS Code</p>
48+
<p className="pb-4 text-gray-500 text-sm">We are changing the default IDE to VS Code.</p>
49+
<ol className="pb-2 text-gray-500 text-sm list-outside list-decimal space-y-2">
50+
<li className="ml-5">
51+
<div>
52+
<p className="text-gray-500 text-sm">We're preserving all <span className="font-bold">user settings and extensions</span>.</p>
53+
<p className="text-gray-400 text-sm">Extensions you have manually uploaded are not transfered. You'll need to search and install those extensions through the extension panel in VS Code.</p>
54+
</div>
55+
</li>
56+
<li className="ml-5">
57+
<div>
58+
<p className="text-gray-500 text-sm">We've reduced the number of <span className="font-bold">pre-installed extensions</span>.</p>
59+
<p className="text-gray-400 text-sm">The Theia-based editor included pre-installed extensions for the most popular programming languages which was convenienvt for starters but added additional bloat. You can now install any extensions you need and leave out those you don't.</p>
60+
</div>
61+
</li>
62+
<li className="ml-5">
63+
<div>
64+
<p className="text-gray-500 text-sm">You can still <span className="font-bold">switch the editor</span> to Theia.</p>
65+
<p className="text-gray-400 text-sm">In case you run into trouble with the new IDE, you can go to the settings and switch back to the Theia-based editor.</p>
66+
</div>
67+
</li>
68+
</ol>
69+
</div>
70+
<div className="flex justify-end mt-6">
71+
<button className="secondary" onClick={() => internalClose()}>Dismiss</button>
72+
<button className="ml-2" onClick={() => internalClose(true)} >Continue</button>
73+
</div>
74+
</Modal>
75+
}

components/dashboard/src/start/CreateWorkspace.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export interface CreateWorkspaceError {
3131
data?: any;
3232
}
3333

34-
export class CreateWorkspace extends React.Component<CreateWorkspaceProps, CreateWorkspaceState> {
34+
export default class CreateWorkspace extends React.Component<CreateWorkspaceProps, CreateWorkspaceState> {
3535

3636
constructor(props: CreateWorkspaceProps) {
3737
super(props);

components/gitpod-protocol/src/protocol.ts

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ export interface AdditionalUserData {
9898
emailNotificationSettings?: EmailNotificationSettings;
9999
featurePreview?: boolean;
100100
ideSettings?: IDESettings;
101+
// key is the name of the news, string the iso date when it was seen
102+
whatsNewSeen?: { [key: string]: string }
101103
}
102104

103105
export interface EmailNotificationSettings {

0 commit comments

Comments
 (0)