Skip to content

Commit

Permalink
feat(Messaging): default thread notif audience settings
Browse files Browse the repository at this point in the history
  • Loading branch information
jakeaturner committed Jul 18, 2023
1 parent d1a208b commit 28a2e45
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 85 deletions.
101 changes: 41 additions & 60 deletions client/src/components/Chat/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import TextArea from '../TextArea';
import { isEmptyString } from '../util/HelperFunctions.js';
import useGlobalError from '../error/ErrorHooks';
import './Chat.css';
import { THREADS_NOTIFY_OPTIONS, getThreadNotifyOption } from '../../utils/threadsHelpers';

/**
* A reusable chat/message thread interface.
Expand All @@ -30,6 +31,7 @@ const Chat = ({
kind,
activeThread,
activeThreadTitle,
activeThreadDefaultNotifSubject,
activeThreadMsgs,
loadedThreadMsgs,
getThreads,
Expand All @@ -53,10 +55,36 @@ const Chat = ({
const [messageSending, setMessageSending] = useState(false);

// Notify Settings
const [notifySetting, setNotifySetting] = useState('all');
const [showNotifyPicker, setShowNotifyPicker] = useState(false);
const [projectTeam, setProjectTeam] = useState([]);
const [teamToNotify, setTeamToNotify] = useState([]);
const [loadingTeam, setLoadingTeam] = useState(false);
const [notificationOptions, setNotificationOptions] = useState([...THREADS_NOTIFY_OPTIONS]);

// Set the available notification options based on the thread type
useEffect(() => {
if (kind === "task") {
setNotificationOptions([
{ key: "assigned", text: "Notify assignees", value: "assigned" },
...THREADS_NOTIFY_OPTIONS,
]);
}
}, [kind]);

// Set the default notification setting based on the thread type & settings
useEffect(() => {
if (kind === "task") {
handleNotifySettingChange('assigned')
return;
}
if (activeThreadDefaultNotifSubject) {
handleNotifySettingChange(activeThreadDefaultNotifSubject)
return;
}

handleNotifySettingChange('all')
}, [activeThread, activeThreadDefaultNotifSubject]);

/**
* Retrieves a list of team members in the current Project from the server and saves it to state.
Expand Down Expand Up @@ -93,57 +121,6 @@ const Chat = ({
setShowNotifyPicker(true);
}, [getProjectTeam]);

const notificationOptions = useMemo(() => {
const NOTIFY_OPTIONS = [
{
key: 'all',
text: 'Notify entire team',
value: 'all',
},
{
key: 'specific',
text: 'Notify specific people...',
value: 'specific',
onClick: handleOpenNotifyPicker,
},
{
key: 'support',
text: 'Notify LibreTexts Support',
value: 'support',
},
{
key: 'none',
text: `Don't notify anyone`,
value: 'none',
},
];
if (kind === 'task') {
return [
{ key: 'assigned', text: 'Notify assignees', value: 'assigned' },
...NOTIFY_OPTIONS,
]
}
return NOTIFY_OPTIONS;
}, [kind, handleOpenNotifyPicker]);

const defaultNotificationSetting = useMemo(() => {
if (kind === 'task') {
return 'assigned';
}
return 'all';
}, [kind]);

const [notifySetting, setNotifySetting] = useState(defaultNotificationSetting);

const notifySettingDropdownText = useMemo(() => {
if (notifySetting === 'specific') {
const modifier = teamToNotify.length > 1 ? 'people' : 'person';
return `Notify ${teamToNotify.length} ${modifier}`;
}
const foundOption = notificationOptions.find((item) => item.value === notifySetting);
return foundOption.text;
}, [notifySetting, notificationOptions, teamToNotify]);

/**
* Register plugins on load.
*/
Expand Down Expand Up @@ -281,13 +258,12 @@ const Chat = ({
* Saves changes in the selected notification setting to state. If the new setting
* is `specific`, the Notify People Picker is opened.
*
* @param {object} e - Event that activated the handler.
* @param {object} data - Data passed from the UI component.
* @param {string} data.value - The new notification setting.
*/
function handleNotifySettingChange(_e, { value }) {
if (value !== 'specific') {
setNotifySetting(value);
function handleNotifySettingChange(value) {
setNotifySetting(value);
if (value === "specific") {
handleOpenNotifyPicker();
}
};

Expand Down Expand Up @@ -384,23 +360,28 @@ const Chat = ({
fluid
>
<Icon name="send" />
Send
{
(notifySetting === 'specific') ? (
<span>Send to {teamToNotify.length} {teamToNotify.length === 1 ? 'person' : 'people'}</span>
) : (
<span>Send</span>
)
}
</Button>
<Dropdown
id="replycontainer-notifydropdown"
options={notificationOptions}
selection
value={notifySetting}
onChange={handleNotifySettingChange}
text={notifySettingDropdownText}
onChange={(_e, {value}) => handleNotifySettingChange(value ?? 'all')}
/>
</div>
</div>
{/* Notify People Picker */}
<Modal open={showNotifyPicker} onClose={handleCloseNotifyPicker}>
<Modal.Header>Choose People to Notify</Modal.Header>
<Modal.Content>
<p>Choose which team members to notify</p>
<p>Choose which team members to notify about this message</p>
<Dropdown
placeholder="Team members..."
fluid
Expand Down
97 changes: 97 additions & 0 deletions client/src/components/Messaging/EditThreadModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Modal, Form, Dropdown, Button, Icon, Input } from "semantic-ui-react";
import { Thread } from "../../types";
import { useState, useEffect } from "react";
import { THREADS_NOTIFY_OPTIONS } from "../../utils/threadsHelpers";
import useGlobalError from "../error/ErrorHooks";
import axios from "axios";

type EditThreadModalProps = {
show: boolean;
onClose: () => void;
onSave: () => void;
thread: Partial<Thread>;
};
const EditThreadModal: React.FC<EditThreadModalProps> = ({
show,
onClose,
onSave,
thread,
}) => {
const { handleGlobalError } = useGlobalError();
const [threadTitle, setThreadTitle] = useState("");
const [threadDefaultNotifSubject, setThreadDefaultNotifSubject] =
useState("");
const [loading, setLoading] = useState(false);

useEffect(() => {
if (show && thread.threadID) {
setThreadTitle(thread.title ?? "");
setThreadDefaultNotifSubject(thread.defaultNotifSubject ?? "");
}
}, [show, thread]);

async function save() {
try {
if (!thread.threadID || !threadTitle || !threadDefaultNotifSubject) {
return;
}
setLoading(true);

const updateRes = await axios.patch("/project/thread", {
threadID: thread.threadID,
title: threadTitle,
defaultNotifSubject: threadDefaultNotifSubject,
});

if (updateRes.data.err) {
throw new Error(updateRes.data.errMsg);
}
onSave();
} catch (err) {
handleGlobalError(err);
} finally {
setLoading(false);
}
}
return (
<Modal open={show} onClose={onClose}>
<Modal.Header>Create a Thread</Modal.Header>
<Modal.Content>
<Form noValidate>
<Form.Field>
<label>Thread Title</label>
<Input
type="text"
icon="comments"
iconPosition="left"
placeholder="Enter thread title or topic..."
onChange={(e) => setThreadTitle(e.target.value)}
value={threadTitle}
/>
</Form.Field>
<Form.Field>
<label>Default Notification Setting</label>
<Dropdown
placeholder="Select a default notification setting"
selection
options={THREADS_NOTIFY_OPTIONS}
onChange={(_e, { value }) => {
setThreadDefaultNotifSubject(value?.toString() ?? "");
}}
value={threadDefaultNotifSubject}
/>
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions>
<Button onClick={onClose}>Cancel</Button>
<Button color="green" loading={loading} onClick={save}>
<Icon name="save" />
Save
</Button>
</Modal.Actions>
</Modal>
);
};

export default EditThreadModal;
Loading

0 comments on commit 28a2e45

Please sign in to comment.