Skip to content

Commit 24c7552

Browse files
committed
Display notifications under the pills
1 parent a78d4cf commit 24c7552

File tree

3 files changed

+143
-18
lines changed

3 files changed

+143
-18
lines changed

site/src/pages/WorkspacePage/Workspace.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ export const Workspace: FC<WorkspaceProps> = ({
188188
latestVersion={latestVersion}
189189
permissions={permissions}
190190
onRestartWorkspace={handleRestart}
191+
onUpdateWorkspace={handleUpdate}
192+
onActivateWorkspace={handleDormantActivate}
191193
/>
192194

193195
{workspace.latest_build.status === "deleted" && (

site/src/pages/WorkspacePage/WorkspaceNotifications.tsx

Lines changed: 140 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,30 @@ import { useIsWorkspaceActionsEnabled } from "components/Dashboard/DashboardProv
99
import formatDistanceToNow from "date-fns/formatDistanceToNow";
1010
import { Pill } from "components/Pill/Pill";
1111
import InfoOutlined from "@mui/icons-material/InfoOutlined";
12-
import ErrorOutline from "@mui/icons-material/ErrorOutline";
12+
import {
13+
Popover,
14+
PopoverContent,
15+
PopoverTrigger,
16+
} from "components/Popover/Popover";
17+
import { Interpolation, Theme, useTheme } from "@emotion/react";
18+
import Button, { ButtonProps } from "@mui/material/Button";
19+
import { ThemeRole } from "theme/experimental";
20+
import WarningRounded from "@mui/icons-material/WarningRounded";
1321

1422
type Notification = {
1523
title: string;
1624
severity: AlertProps["severity"];
1725
detail?: ReactNode;
18-
actions?: { label: string; onClick: () => void }[];
26+
actions?: ReactNode;
1927
};
2028

2129
type WorkspaceNotificationsProps = {
2230
workspace: Workspace;
2331
template: Template;
2432
permissions: WorkspacePermissions;
2533
onRestartWorkspace: () => void;
34+
onUpdateWorkspace: () => void;
35+
onActivateWorkspace: () => void;
2636
latestVersion?: TemplateVersion;
2737
};
2838

@@ -35,6 +45,8 @@ export const WorkspaceNotifications: FC<WorkspaceNotificationsProps> = (
3545
latestVersion,
3646
permissions,
3747
onRestartWorkspace,
48+
onUpdateWorkspace,
49+
onActivateWorkspace,
3850
} = props;
3951
const notifications: Notification[] = [];
4052

@@ -51,18 +63,26 @@ export const WorkspaceNotifications: FC<WorkspaceNotificationsProps> = (
5163
const requiresManualUpdate = updateRequired && autoStartFailing;
5264

5365
if (workspace.outdated && latestVersion) {
66+
const actions = (
67+
<NotificationActionButton onClick={onUpdateWorkspace}>
68+
Update
69+
</NotificationActionButton>
70+
);
5471
if (requiresManualUpdate) {
5572
notifications.push({
5673
title: "Autostart has been disabled for your workspace.",
5774
severity: "warning",
5875
detail:
5976
"Autostart is unable to automatically update your workspace. Manually update your workspace to reenable Autostart.",
77+
78+
actions,
6079
});
6180
} else {
6281
notifications.push({
6382
title: "An update is available for your workspace",
6483
severity: "info",
6584
detail: latestVersion.message,
85+
actions,
6686
});
6787
}
6888
}
@@ -84,14 +104,11 @@ export const WorkspaceNotifications: FC<WorkspaceNotificationsProps> = (
84104
.
85105
</>
86106
),
87-
actions: permissions.updateWorkspace
88-
? [
89-
{
90-
label: "Restart",
91-
onClick: onRestartWorkspace,
92-
},
93-
]
94-
: undefined,
107+
actions: permissions.updateWorkspace ? (
108+
<NotificationActionButton onClick={onRestartWorkspace}>
109+
Restart
110+
</NotificationActionButton>
111+
) : undefined,
95112
});
96113
}
97114

@@ -107,7 +124,13 @@ export const WorkspaceNotifications: FC<WorkspaceNotificationsProps> = (
107124
...(timestamp ? { hour: "numeric", minute: "numeric" } : {}),
108125
});
109126
};
127+
const actions = (
128+
<NotificationActionButton onClick={onActivateWorkspace}>
129+
Activate
130+
</NotificationActionButton>
131+
);
110132
notifications.push({
133+
actions,
111134
title: "Workspace is dormant",
112135
severity: "warning",
113136
detail: workspace.deleting_at ? (
@@ -197,24 +220,124 @@ export const WorkspaceNotifications: FC<WorkspaceNotificationsProps> = (
197220
});
198221
}
199222

223+
const infoNotifications = notifications.filter((n) => n.severity === "info");
224+
const warningNotifications = notifications.filter(
225+
(n) => n.severity === "warning",
226+
);
227+
200228
return (
201229
<div
202230
css={{
203231
display: "flex",
204232
alignItems: "center",
205-
gap: 8,
233+
gap: 12,
206234
position: "fixed",
207235
bottom: 48,
208236
right: 48,
209237
zIndex: 10,
210238
}}
211239
>
212-
<Pill type="info" icon={<InfoOutlined />}>
213-
2
214-
</Pill>
215-
<Pill type="warning" icon={<ErrorOutline />}>
216-
4
217-
</Pill>
240+
{infoNotifications.length > 0 && (
241+
<NotificationPill
242+
notifications={infoNotifications}
243+
type="info"
244+
icon={<InfoOutlined />}
245+
/>
246+
)}
247+
248+
{warningNotifications.length > 0 && (
249+
<NotificationPill
250+
notifications={warningNotifications}
251+
type="warning"
252+
icon={<WarningRounded />}
253+
/>
254+
)}
218255
</div>
219256
);
220257
};
258+
259+
type NotificationPillProps = {
260+
notifications: Notification[];
261+
type: ThemeRole;
262+
icon: ReactNode;
263+
};
264+
265+
const NotificationPill: FC<NotificationPillProps> = (props) => {
266+
const { notifications, type, icon } = props;
267+
const theme = useTheme();
268+
269+
return (
270+
<Popover mode="hover">
271+
<PopoverTrigger>
272+
<div css={[styles.pillContainer]}>
273+
<Pill type={type} icon={icon}>
274+
{notifications.length}
275+
</Pill>
276+
</div>
277+
</PopoverTrigger>
278+
<PopoverContent
279+
transformOrigin={{
280+
horizontal: "right",
281+
vertical: "bottom",
282+
}}
283+
anchorOrigin={{ horizontal: "right", vertical: "top" }}
284+
css={{
285+
"& .MuiPaper-root": {
286+
borderColor: theme.experimental.roles[type].outline,
287+
maxWidth: 400,
288+
},
289+
}}
290+
>
291+
{notifications.map((n) => (
292+
<NotificationItem notification={n} key={n.title} />
293+
))}
294+
</PopoverContent>
295+
</Popover>
296+
);
297+
};
298+
299+
const NotificationItem: FC<{ notification: Notification }> = (props) => {
300+
const { notification } = props;
301+
const theme = useTheme();
302+
303+
return (
304+
<article css={{ padding: 16 }}>
305+
<h4 css={{ margin: 0, fontWeight: 500 }}>{notification.title}</h4>
306+
{notification.detail && (
307+
<p
308+
css={{
309+
margin: 0,
310+
color: theme.palette.text.secondary,
311+
lineHeight: 1.6,
312+
}}
313+
>
314+
{notification.detail}
315+
</p>
316+
)}
317+
<div css={{ marginTop: 8 }}>{notification.actions}</div>
318+
</article>
319+
);
320+
};
321+
322+
const NotificationActionButton: FC<ButtonProps> = (props) => {
323+
return (
324+
<Button
325+
variant="text"
326+
css={{
327+
textDecoration: "underline",
328+
padding: 0,
329+
height: "auto",
330+
minWidth: "auto",
331+
"&:hover": { background: "none", textDecoration: "underline" },
332+
}}
333+
{...props}
334+
/>
335+
);
336+
};
337+
338+
const styles = {
339+
// Adds some spacing from the popover content
340+
pillContainer: {
341+
paddingTop: 8,
342+
},
343+
} satisfies Record<string, Interpolation<Theme>>;

site/src/pages/WorkspacePage/WorkspacePage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export const WorkspacePage: FC = () => {
104104
const isLoading = !workspace || !template || !permissions;
105105

106106
return (
107-
<div css={{ height: "100%" }}>
107+
<div css={{ height: "100%", display: "flex", flexDirection: "column" }}>
108108
<Navbar />
109109
{pageError ? (
110110
<Margins>

0 commit comments

Comments
 (0)