Skip to content

Commit 1a04fdd

Browse files
committed
Add support for a system notification that can be configured in the admin
1 parent 9fe7af7 commit 1a04fdd

File tree

4 files changed

+164
-15
lines changed

4 files changed

+164
-15
lines changed

src/ui/EditorContainer.js

+31-14
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import defaultTemplateUrl from "./../assets/templates/crater.spoke";
4242
import tutorialTemplateUrl from "./../assets/templates/tutorial.spoke";
4343

4444
import { TERMS, PRIVACY } from "../constants";
45+
import NotificationDialog from "./dialogs/NotificationDialog";
4546

4647
const StyledEditorContainer = styled.div`
4748
display: flex;
@@ -104,23 +105,39 @@ class EditorContainer extends Component {
104105
const projectId = match.params.projectId;
105106
const queryParams = new URLSearchParams(location.search);
106107

107-
if (projectId === "new") {
108-
if (queryParams.has("template")) {
109-
this.loadProjectTemplate(queryParams.get("template"));
110-
} else if (queryParams.has("sceneId")) {
111-
this.loadScene(queryParams.get("sceneId"));
108+
const load = () => {
109+
if (projectId === "new") {
110+
if (queryParams.has("template")) {
111+
this.loadProjectTemplate(queryParams.get("template"));
112+
} else if (queryParams.has("sceneId")) {
113+
this.loadScene(queryParams.get("sceneId"));
114+
} else {
115+
this.loadProjectTemplate(defaultTemplateUrl);
116+
}
117+
} else if (projectId === "tutorial") {
118+
this.loadProjectTemplate(tutorialTemplateUrl, true);
112119
} else {
113-
this.loadProjectTemplate(defaultTemplateUrl);
120+
this.loadProject(projectId);
114121
}
115-
} else if (projectId === "tutorial") {
116-
this.loadProjectTemplate(tutorialTemplateUrl, true);
117-
} else {
118-
this.loadProject(projectId);
119-
}
120122

121-
if (projectId === "tutorial") {
122-
trackEvent("Tutorial Start");
123-
this.setState({ onboardingContext: { enabled: true } });
123+
if (projectId === "tutorial") {
124+
trackEvent("Tutorial Start");
125+
this.setState({ onboardingContext: { enabled: true } });
126+
}
127+
};
128+
129+
const features = window.APP_CONFIG;
130+
if (features["show_global_notification"]) {
131+
this.showDialog(NotificationDialog, {
132+
title: "Admin notification",
133+
message: features["global_notification_body"],
134+
link: features["global_notification_link"],
135+
onClosed: load,
136+
onConfirm: load,
137+
onCancel: null
138+
});
139+
} else {
140+
load();
124141
}
125142
}
126143

src/ui/dialogs/NotificationDialog.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, { Component } from "react";
2+
import PropTypes from "prop-types";
3+
import Dialog, { DialogContent } from "./Dialog";
4+
import styled from "styled-components";
5+
import { Button } from "../inputs/Button";
6+
7+
const NotificationDialogContainer = styled(Dialog)`
8+
max-width: 600px;
9+
10+
${DialogContent} {
11+
padding: 0;
12+
}
13+
`;
14+
15+
const NotificationMessage = styled.code`
16+
white-space: pre-wrap;
17+
overflow-wrap: break-word;
18+
overflow-x: hidden;
19+
overflow-y: auto;
20+
padding: 16px;
21+
color: ${props => props.theme.red};
22+
`;
23+
24+
export default class NotificationDialog extends Component {
25+
componentDidMount() {}
26+
27+
openLink = () => {
28+
window.open(this.props.link);
29+
this.props.onClosed();
30+
};
31+
32+
renderBottomNav() {
33+
return this.props.link ? <Button onClick={this.openLink}>Learn More</Button> : null;
34+
}
35+
36+
render() {
37+
const { message, onClosed, ...props } = this.props;
38+
39+
return (
40+
<NotificationDialogContainer {...props} bottomNav={this.renderBottomNav()}>
41+
<NotificationMessage>{message}</NotificationMessage>
42+
</NotificationDialogContainer>
43+
);
44+
}
45+
}
46+
47+
NotificationDialog.propTypes = {
48+
title: PropTypes.string.isRequired,
49+
message: PropTypes.string.isRequired,
50+
link: PropTypes.string,
51+
onClosed: PropTypes.func
52+
};
53+
54+
NotificationDialog.defaultProps = {
55+
title: "Notification"
56+
};

src/ui/layout/Notification.js

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, { Component } from "react";
2+
import PropTypes from "prop-types";
3+
import styled from "styled-components";
4+
5+
const NotificationContainer = styled.div`
6+
display: flex;
7+
align-items: center;
8+
justify-content: center;
9+
`;
10+
11+
const StyledNotification = styled.div`
12+
min-height: 24px;
13+
margin: 1em 20px;
14+
display: flex;
15+
justify-content: center;
16+
align-items: center;
17+
font-size: 1.1em;
18+
padding: 1em;
19+
border-radius: 6px;
20+
background-color: ${props => props.theme.red};
21+
`;
22+
23+
const Content = styled.span`
24+
max-width: 50vw;
25+
overflow: hidden;
26+
white-space: nowrap;
27+
text-overflow: ellipsis;
28+
`;
29+
30+
const ViewMore = styled.div`
31+
text-align: center;
32+
margin-left: 1em;
33+
a {
34+
color: ${props => props.theme.blue};
35+
}
36+
`;
37+
38+
export default class Notification extends Component {
39+
render() {
40+
const { body, link } = this.props;
41+
return (
42+
<NotificationContainer>
43+
<StyledNotification>
44+
<Content>{body}</Content>
45+
<ViewMore>
46+
<a href={link} target="_blank" rel="noopener noreferrer">
47+
Learn more
48+
</a>
49+
</ViewMore>
50+
</StyledNotification>
51+
</NotificationContainer>
52+
);
53+
}
54+
}
55+
56+
Notification.propTypes = {
57+
body: PropTypes.string.isRequired,
58+
link: PropTypes.string,
59+
onClosed: PropTypes.func
60+
};

src/ui/projects/ProjectsPage.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { Link } from "react-router-dom";
1818
import LatestUpdate from "../whats-new/LatestUpdate";
1919
import { connectMenu, ContextMenu, MenuItem } from "../layout/ContextMenu";
2020
import styled from "styled-components";
21+
import Notification from "../layout/Notification";
2122

2223
export const ProjectsSection = styled.section`
2324
padding-bottom: 100px;
@@ -87,7 +88,10 @@ class ProjectsPage extends Component {
8788
scenes: [],
8889
loading: isAuthenticated,
8990
isAuthenticated,
90-
error: null
91+
error: null,
92+
showGlobalNotification: false,
93+
globalNotificationBody: null,
94+
globalNotificationLink: null
9195
};
9296
}
9397

@@ -122,6 +126,15 @@ class ProjectsPage extends Component {
122126
this.setState({ error, loading: false });
123127
});
124128
}
129+
130+
const features = window.APP_CONFIG && window.APP_CONFIG["features"];
131+
if (features) {
132+
this.setState({
133+
showGlobalNotification: features["show_global_notification"],
134+
globalNotificationBody: features["global_notification_body"],
135+
globalNotificationLink: features["global_notification_link"]
136+
});
137+
}
125138
}
126139

127140
onDeleteProject = project => {
@@ -172,6 +185,9 @@ class ProjectsPage extends Component {
172185
<h1>Projects</h1>
173186
</ProjectsHeader>
174187
<ProjectGridContainer>
188+
{this.state.showGlobalNotification && (
189+
<Notification body={this.state.globalNotificationBody} link={this.state.globalNotificationLink} />
190+
)}
175191
<ProjectGridHeader>
176192
<ProjectGridHeaderRow></ProjectGridHeaderRow>
177193
<ProjectGridHeaderRow>

0 commit comments

Comments
 (0)