Skip to content

Commit 93f2069

Browse files
committed
[dashboard] What's New
1 parent a8dbed9 commit 93f2069

File tree

5 files changed

+129
-41
lines changed

5 files changed

+129
-41
lines changed

components/dashboard/README.md

Lines changed: 1 addition & 1 deletion
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

Lines changed: 50 additions & 39 deletions
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

Lines changed: 75 additions & 0 deletions
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">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">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-xs">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-xs">The old editor would come with extensions for the most popular programming languages pre-installed. You are now freed from that additional bloat but should consider installing any extensions you miss.</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 back</span> to the old one.</p>
65+
<p className="text-gray-400 text-xs">In case you run into trouble with the new IDE, you can go to the settings and switch back to the old editor (Theia).</p>
66+
</div>
67+
</li>
68+
</ol>
69+
</div>
70+
<div className="flex justify-end mt-6">
71+
<button className="text-gray-900 border-white bg-white hover:border-gray-200" 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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 0 deletions
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)