This commit is contained in:
McKayla Washburn 2024-05-01 19:24:37 +00:00
parent 094238634f
commit 0a3a01d603
7 changed files with 250 additions and 16 deletions

View File

@ -7,6 +7,7 @@ import { Outlet } from "react-router-dom";
import { Loader } from "components/Loader/Loader";
import { useAuthenticated } from "contexts/auth/RequireAuth";
import { LicenseBanner } from "modules/dashboard/LicenseBanner/LicenseBanner";
import { NotificationBanners } from "modules/dashboard/NotificationBanners/NotificationBanners";
import { ServiceBanner } from "modules/dashboard/ServiceBanner/ServiceBanner";
import { dashboardContentBottomPadding } from "theme/constants";
import { docs } from "utils/docs";
@ -23,6 +24,7 @@ export const DashboardLayout: FC = () => {
<>
{canViewDeployment && <LicenseBanner />}
<ServiceBanner />
<NotificationBanners />
<div
css={{

View File

@ -0,0 +1,24 @@
import type { Meta, StoryObj } from "@storybook/react";
import { NotificationBannerView } from "./NotificationBannerView";
const meta: Meta<typeof NotificationBannerView> = {
title: "modules/dashboard/NotificationBannerView",
component: NotificationBannerView,
};
export default meta;
type Story = StoryObj<typeof NotificationBannerView>;
export const Production: Story = {
args: {
message: "weeeee",
backgroundColor: "#FFFFFF",
},
};
export const Preview: Story = {
args: {
message: "weeeee",
backgroundColor: "#000000",
},
};

View File

@ -0,0 +1,48 @@
import { css, type Interpolation, type Theme } from "@emotion/react";
import type { FC } from "react";
import { InlineMarkdown } from "components/Markdown/Markdown";
import { readableForegroundColor } from "utils/colors";
export interface NotificationBannerViewProps {
message?: string;
backgroundColor?: string;
}
export const NotificationBannerView: FC<NotificationBannerViewProps> = ({
message,
backgroundColor,
}) => {
if (message === undefined || backgroundColor === undefined) {
return null;
}
return (
<div css={[styles.banner, { backgroundColor }]} className="service-banner">
<div
css={[
styles.wrapper,
{ color: readableForegroundColor(backgroundColor) },
]}
>
<InlineMarkdown>{message}</InlineMarkdown>
</div>
</div>
);
};
const styles = {
banner: css`
padding: 12px;
display: flex;
align-items: center;
`,
wrapper: css`
margin-right: auto;
margin-left: auto;
font-weight: 400;
& a {
color: inherit;
}
`,
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -0,0 +1,22 @@
import type { FC } from "react";
import { useDashboard } from "modules/dashboard/useDashboard";
import { NotificationBannerView } from "./NotificationBannerView";
export const NotificationBanners: FC = () => {
const dashboard = useDashboard();
const notificationBanners = dashboard.appearance.config.notification_banners;
return (
<>
{notificationBanners
.filter((banner) => banner.enabled)
.map(({ message, background_color }) => (
<NotificationBannerView
key={message}
message={message}
backgroundColor={background_color}
/>
))}
</>
);
};

View File

@ -30,7 +30,7 @@ const AppearanceSettingsPage: FC = () => {
try {
await updateAppearanceMutation.mutateAsync(newAppearance);
queryClient.invalidateQueries(appearanceConfigKey);
await queryClient.invalidateQueries(appearanceConfigKey);
displaySuccess("Successfully updated appearance settings!");
} catch (error) {
displayError(

View File

@ -8,7 +8,7 @@ import TextField from "@mui/material/TextField";
import { useFormik } from "formik";
import { type FC, useState } from "react";
import { BlockPicker } from "react-color";
import type { UpdateAppearanceConfig } from "api/typesGenerated";
import type { BannerConfig, UpdateAppearanceConfig } from "api/typesGenerated";
import {
Badges,
DeprecatedBadge,
@ -21,6 +21,7 @@ import colors from "theme/tailwindColors";
import { getFormHelpers } from "utils/formUtils";
import { Fieldset } from "../Fieldset";
import { Header } from "../Header";
import { NotificationBannerItem } from "./NotificationBannerItem";
export type AppearanceSettingsPageViewProps = {
appearance: UpdateAppearanceConfig;
@ -81,6 +82,36 @@ export const AppearanceSettingsPageView: FC<
serviceBannerForm.values.background_color,
);
const [notificationBanners, setNotificationBanners] = useState(
appearance.notification_banners,
);
const addNotificationBannerItem = () => {
setNotificationBanners((banners) => [
...banners,
{ enabled: true, message: "foob", background_color: "#004852" },
]);
};
const updateNotificationBannerItem = (
i: number,
banner: Partial<BannerConfig>,
) => {
setNotificationBanners((banners) => {
const newBanners = [...banners];
newBanners[i] = { ...banners[i], ...banner };
return newBanners;
});
};
const removeNotificationBannerItem = (i: number) => {
setNotificationBanners((banners) => {
const newBanners = [...banners];
newBanners.splice(i, 1);
return newBanners;
});
};
return (
<>
<Header
@ -286,24 +317,34 @@ export const AppearanceSettingsPageView: FC<
title="Notification Banners"
onSubmit={() =>
onSaveAppearance({
notification_banners: [
{
enabled: true,
message: "Just a real nice test :)",
background_color: "#ffaff3",
},
],
notification_banners: notificationBanners,
})
}
>
<>
{appearance.notification_banners
.filter((banner) => banner.enabled)
.map((banner) => (
<div css={{ backgroundColor: banner.background_color }}>
{banner.message}
</div>
))}
<Button onClick={() => addNotificationBannerItem()}>+</Button>
<Stack spacing={4}>
{notificationBanners
.filter((banner) => banner.enabled)
.map((banner, i) => (
<NotificationBannerItem
key={i}
enabled={banner.enabled}
backgroundColor={banner.background_color}
message={banner.message}
onRemove={() => removeNotificationBannerItem(i)}
onUpdate={(banner: Partial<BannerConfig>) => {
const shouldPersist = "enabled" in banner;
updateNotificationBannerItem(i, banner);
if (shouldPersist) {
onSaveAppearance({
notification_banners: notificationBanners,
});
}
}}
/>
))}
</Stack>
</>
</Fieldset>
</>

View File

@ -0,0 +1,97 @@
import { useTheme } from "@emotion/react";
import Delete from "@mui/icons-material/Delete";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import type { FC } from "react";
import { BlockPicker } from "react-color";
import type { BannerConfig } from "api/typesGenerated";
import Switch from "@mui/material/Switch";
interface NotificationBannerItemProps {
enabled: boolean;
backgroundColor?: string;
message?: string;
onRemove: () => void;
onUpdate: (banner: Partial<BannerConfig>) => void;
}
export const NotificationBannerItem: FC<NotificationBannerItemProps> = ({
enabled,
backgroundColor,
message,
onRemove,
onUpdate,
}) => {
const theme = useTheme();
return (
<div>
<div>
<Switch
checked={enabled}
onChange={() => onUpdate({ enabled: !enabled })}
data-testid="switch-service-banner"
/>
<Button onClick={onRemove}>
<Delete />
</Button>
</div>
<div css={{ backgroundColor }}>{message}</div>
<TextField
// {...serviceBannerFieldHelpers("message", {
// helperText:
// ,
// })}
onChange={(event) => onUpdate({ message: event.target.value })}
defaultValue={message}
helperText="Markdown bold, italics, and links are supported."
fullWidth
label="Message"
multiline
inputProps={{
"aria-label": "Message",
}}
/>
<button
type="button"
css={{
backgroundColor,
width: 24,
height: 24,
outline: "none",
border: "none",
borderRadius: 4,
}}
></button>
<details>
<summary>Background Color</summary>
<BlockPicker
color={backgroundColor}
onChange={async (color) => {
// TODO: preview the color?
onUpdate({ background_color: color.hex });
}}
triangle="hide"
colors={["#004852", "#D65D0F", "#4CD473", "#D94A5D", "#5A00CF"]}
styles={{
default: {
input: {
color: "white",
backgroundColor: theme.palette.background.default,
},
body: {
backgroundColor: "black",
color: "white",
},
card: {
backgroundColor: "black",
},
},
}}
/>
</details>
</div>
);
};