diff --git a/web/components/chat/ChatContainer/ChatContainer.tsx b/web/components/chat/ChatContainer/ChatContainer.tsx
index 1f9c9940c..30c246340 100644
--- a/web/components/chat/ChatContainer/ChatContainer.tsx
+++ b/web/components/chat/ChatContainer/ChatContainer.tsx
@@ -2,10 +2,15 @@ import { Button } from 'antd';
import { Virtuoso } from 'react-virtuoso';
import { useState, useMemo, useRef } from 'react';
import { EditFilled, VerticalAlignBottomOutlined } from '@ant-design/icons';
-import { MessageType, NameChangeEvent } from '../../../interfaces/socket-events';
+import {
+ ConnectedClientInfoEvent,
+ MessageType,
+ NameChangeEvent,
+} from '../../../interfaces/socket-events';
import s from './ChatContainer.module.scss';
import { ChatMessage } from '../../../interfaces/chat-message.model';
import { ChatTextField, ChatUserMessage } from '..';
+import ChatModeratorNotification from '../ChatModeratorNotification/ChatModeratorNotification';
interface Props {
messages: ChatMessage[];
@@ -43,7 +48,20 @@ export default function ChatContainer(props: Props) {
);
};
- const getViewForMessage = (index: number, message: ChatMessage | NameChangeEvent) => {
+ const getConnectedInfoMessage = (message: ConnectedClientInfoEvent) => {
+ const modStatusUpdate = checkIsModerator(message);
+ if (!modStatusUpdate) {
+ return null;
+ }
+
+ // Alert the user that they are a moderator.
+ return ;
+ };
+
+ const getViewForMessage = (
+ index: number,
+ message: ChatMessage | NameChangeEvent | ConnectedClientInfoEvent,
+ ) => {
switch (message.type) {
case MessageType.CHAT:
return (
@@ -58,6 +76,9 @@ export default function ChatContainer(props: Props) {
);
case MessageType.NAME_CHANGE:
return getNameChangeViewForMessage(message as NameChangeEvent);
+ case MessageType.CONNECTED_USER_INFO:
+ return getConnectedInfoMessage(message);
+
default:
return null;
}
@@ -120,3 +141,14 @@ function isSameUserAsLast(messages: ChatMessage[], index: number) {
return id === lastMessage?.user.id;
}
+
+function checkIsModerator(message) {
+ const { user } = message;
+ const { scopes } = user;
+
+ if (!scopes || scopes.length === 0) {
+ return false;
+ }
+
+ return scopes.includes('MODERATOR');
+}
diff --git a/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.module.scss b/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.module.scss
new file mode 100644
index 000000000..a235f1f6c
--- /dev/null
+++ b/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.module.scss
@@ -0,0 +1,19 @@
+@import 'styles/mixins.scss';
+
+.chatModerationNotification {
+ background-color: var(--theme-background-primary);
+ margin: 5px;
+ border-radius: 15px;
+ border-color: rgba(0, 0, 0, 0.3);
+ border-width: 1px;
+ border-style: solid;
+ padding: 10px 10px;
+ max-width: 400px;
+ @include flexCenter;
+
+ .icon {
+ margin-right: 10px;
+ width: 20px;
+ height: 20px;
+ }
+}
diff --git a/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.tsx b/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.tsx
new file mode 100644
index 000000000..224e33e78
--- /dev/null
+++ b/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.tsx
@@ -0,0 +1,12 @@
+import s from './ChatModeratorNotification.module.scss';
+import Icon from '../../../assets/images/moderator.svg';
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export default function ModeratorNotification() {
+ return (
+
+
+ You are now a moderator.
+
+ );
+}
diff --git a/web/components/stores/ClientConfigStore.tsx b/web/components/stores/ClientConfigStore.tsx
index 0b70e6f04..0c7220b81 100644
--- a/web/components/stores/ClientConfigStore.tsx
+++ b/web/components/stores/ClientConfigStore.tsx
@@ -258,6 +258,7 @@ export function ClientConfigStore() {
setChatUserId,
setIsChatModerator,
);
+ setChatMessages(currentState => [...currentState, message as ChatEvent]);
break;
case MessageType.CHAT:
setChatMessages(currentState => [...currentState, message as ChatEvent]);
diff --git a/web/stories/ChatModeratorNotification.stories.tsx b/web/stories/ChatModeratorNotification.stories.tsx
new file mode 100644
index 000000000..1ecf35500
--- /dev/null
+++ b/web/stories/ChatModeratorNotification.stories.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+import ChatModeratorNotification from '../components/chat/ChatModeratorNotification/ChatModeratorNotification';
+
+export default {
+ title: 'owncast/Chat/Messages/Moderation Role Notification',
+ component: ChatModeratorNotification,
+ parameters: {},
+} as ComponentMeta;
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const Template: ComponentStory = args => (
+
+);
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export const Basic = Template.bind({});
diff --git a/web/styles/mixins.scss b/web/styles/mixins.scss
new file mode 100644
index 000000000..d7ff1da49
--- /dev/null
+++ b/web/styles/mixins.scss
@@ -0,0 +1,5 @@
+@mixin flexCenter {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}