[CORL 547] org-wide announcements (#2813)

* CRUD announcements

* only show announcement if not disabled

* make announcements dismissable

* add announcement mutations

* update announcement form logic

* style announcements on stream

* update snap

* localize strings

* close form if announcement is removed

* move announcement config below sitewide commenting config

* move date calculation inside useMemo

* move announcementconfig code to announcementconfigcontainer

* use coralContext for localStorage

* fix type of announcement createdAt

* move announcement form to modal

* remove payload pruning from configure route

* simplify announcement display logic

* make validation message full width

Co-authored-by: Kim Gardner <kgardnr@gmail.com>
This commit is contained in:
Tessa Thornton
2020-02-03 13:12:25 -05:00
committed by GitHub
co-authored by Kim Gardner
parent a7b2af85fc
commit a1a8652f7e
29 changed files with 855 additions and 1 deletions
+5
View File
@@ -9,6 +9,11 @@ const CLASSES = {
*/
guidelines: "coral coral-guidelines",
/**
* guidlines represents the box containing the guidlines.
*/
announcement: "coral coral-announcement",
/**
* closedSitewide represents the box containing the message when comments
* are closed sitewide.
@@ -0,0 +1,11 @@
.root {
background-color: #2b7eb5;
color: white;
padding: var(--v2-spacing-3);
border-radius: 2px;
}
.text {
@mixin bodyCopy;
color: white;
}
@@ -0,0 +1,30 @@
import cn from "classnames";
import React, { FunctionComponent } from "react";
import CLASSES from "coral-stream/classes";
import { Button, Flex, Icon } from "coral-ui/components";
import styles from "./Announcement.css";
interface Props {
children: string;
onClose: () => void;
}
const Announcement: FunctionComponent<Props> = props => {
return (
<div className={cn(styles.root, CLASSES.announcement)}>
<Flex justifyContent="space-between" alignItems="center">
<Flex itemGutter>
<Icon size="lg">notifications</Icon>
<span className={styles.text}>{props.children}</span>
</Flex>
<Button color="light" onClick={props.onClose}>
<Icon>close</Icon>
</Button>
</Flex>
</div>
);
};
export default Announcement;
@@ -0,0 +1,67 @@
import React, {
FunctionComponent,
useCallback,
useEffect,
useState,
} from "react";
import { graphql } from "react-relay";
import { ANNOUNEMENT_DISMISSED_KEY } from "coral-framework/constants";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { AnnouncementContainer_settings as SettingsData } from "coral-stream/__generated__/AnnouncementContainer_settings.graphql";
import Announcement from "./Announcement";
interface Props {
settings: SettingsData;
}
export const AnnouncementContainer: FunctionComponent<Props> = ({
settings,
}) => {
const { localStorage } = useCoralContext();
const [dismissed, setDismissed] = useState(false);
useEffect(() => {
async function getDismissedStatus() {
if (settings.announcement) {
const key = await localStorage.getItem(ANNOUNEMENT_DISMISSED_KEY);
if (key && key === settings.announcement.id) {
setDismissed(true);
}
}
}
getDismissedStatus();
}, [settings]);
const dismissAnnouncement = useCallback(async () => {
if (settings.announcement) {
await localStorage.setItem(
ANNOUNEMENT_DISMISSED_KEY,
settings.announcement.id
);
setDismissed(true);
}
}, [settings]);
if (!settings.announcement || dismissed) {
return null;
}
return (
<Announcement onClose={dismissAnnouncement}>
{settings.announcement.content}
</Announcement>
);
};
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment AnnouncementContainer_settings on Settings {
announcement {
id
content
}
}
`,
})(AnnouncementContainer);
export default enhanced;
@@ -0,0 +1,4 @@
export {
default,
default as AnnouncementContainer,
} from "./AnnouncementContainer";
@@ -32,6 +32,7 @@ import {
} from "coral-stream/__generated__/StreamContainerLocal.graphql";
import AllCommentsTab from "./AllCommentsTab";
import AnnouncementContainer from "./Announcement";
import BannedInfo from "./BannedInfo";
import { CommunityGuidelinesContainer } from "./CommunityGuidelines";
import StreamDeletionRequestCalloutContainer from "./DeleteAccount/StreamDeletionRequestCalloutContainer";
@@ -152,6 +153,7 @@ export const StreamContainer: FunctionComponent<Props> = props => {
size="double"
>
<UserBoxContainer viewer={props.viewer} settings={props.settings} />
<AnnouncementContainer settings={props.settings} />
{props.viewer && (
<StreamDeletionRequestCalloutContainer viewer={props.viewer} />
)}
@@ -296,6 +298,7 @@ const enhanced = withFragmentContainer<Props>({
...UserBoxContainer_settings
...CommunityGuidelinesContainer_settings
...SuspendedInfoContainer_settings
...AnnouncementContainer_settings
}
`,
})(StreamContainer);