From 05b9634180342a03fe96c008e1814e437d86a6cf Mon Sep 17 00:00:00 2001 From: gingervitis Date: Tue, 22 Dec 2020 22:05:17 -0800 Subject: [PATCH 01/17] add nav item and page for chat; set up data table of messages --- web/pages/chat.tsx | 134 +++++++++++++++++++++++++++ web/pages/components/main-layout.tsx | 3 + web/utils/apis.ts | 21 ++++- 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 web/pages/chat.tsx diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx new file mode 100644 index 000000000..f5b90d12c --- /dev/null +++ b/web/pages/chat.tsx @@ -0,0 +1,134 @@ +import React, { useState, useEffect } from "react"; +import { Table, Typography } from "antd"; +import { ColumnsType } from 'antd/es/table'; +import format from 'date-fns/format' + +import { + CHAT_HISTORY, + fetchDataFromMain, +} from "../utils/apis"; + +const { Title } = Typography; + +interface Message { + key: string; + author: string; + body: string; + id: string; + name: string; + timestamp: string; + type: string; + visible: boolean; +} + + +function createUserNameFilters(messages) { + const filtered = messages.reduce((acc, curItem) => { + const curAuthor = curItem.author; + if (!acc.some(item => item.text === curAuthor)) { + acc.push({ text: curAuthor, value: curAuthor }); + } + return acc; + }, []); + + // sort by name + return filtered.sort((a, b) => { + const nameA = a.text.toUpperCase(); // ignore upper and lowercase + const nameB = b.text.toUpperCase(); // ignore upper and lowercase + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + // names must be equal + return 0; + }); +} + +export default function Chat() { + const [messages, setMessages] = useState([]); + + const getInfo = async () => { + try { + const result = await fetchDataFromMain(CHAT_HISTORY); + setMessages(result); + } catch (error) { + console.log("==== error", error); + } + }; + + useEffect(() => { + getInfo(); + }, []); + + + const nameFilters = createUserNameFilters(messages); + const rowSelection = { + onChange: (selectedRowKeys, selectedRows) => { + console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); + }, + getCheckboxProps: record => ({ + disabled: record.name === 'Disabled User', // Column configuration not to be checked + name: record.name, + }), + }; + + const chatColumns: ColumnsType = [ + { + title: 'Time', + dataIndex: 'timestamp', + key: 'timestamp', + defaultSortOrder: 'descend', + render: (timestamp) => { + const dateObject = new Date(timestamp); + return format(dateObject, 'P pp'); + }, + sorter: (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), + }, + { + title: 'User', + dataIndex: 'author', + key: 'author', + className: 'name-col', + filters: nameFilters, + onFilter: (value, record) => record.name.indexOf(value) === 0, + sorter: (a, b) => a.name.toUppercase() - b.name.toUppercase(), + sortDirections: ['ascend', 'descend'], + }, + { + title: 'Message', + dataIndex: 'body', + key: 'body', + className: 'message-col', + }, + { + title: 'Show/Hide', + dataIndex: 'visible', + key: 'visible', + }, + ]; + + + return ( +
+ Chat Messages + !record.visible ? 'hidden' : ''} + dataSource={messages} + columns={chatColumns} + rowKey={(row) => row.id} + + rowSelection={{ + type: "checkbox", + ...rowSelection, + }} + + /> + ) +} + + diff --git a/web/pages/components/main-layout.tsx b/web/pages/components/main-layout.tsx index 6d2f9fcaf..eea871a0a 100644 --- a/web/pages/components/main-layout.tsx +++ b/web/pages/components/main-layout.tsx @@ -128,6 +128,9 @@ export default function MainLayout(props) { icon={} title="Utilities" > + + Chat Moderation + Hardware diff --git a/web/utils/apis.ts b/web/utils/apis.ts index a480a5493..736b41463 100644 --- a/web/utils/apis.ts +++ b/web/utils/apis.ts @@ -34,10 +34,14 @@ export const LOGS_ALL = `${API_LOCATION}logs`; // Get warnings + errors export const LOGS_WARN = `${API_LOCATION}logs/warnings`; +// Chat history +export const CHAT_HISTORY = `${NEXT_PUBLIC_API_HOST}api/chat`; + + const GITHUB_RELEASE_URL = "https://api.github.com/repos/owncast/owncast/releases/latest"; export async function fetchData(url) { - let options: RequestInit = {}; + const options: RequestInit = {}; if (ADMIN_USERNAME && ADMIN_STREAMKEY) { const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`); @@ -62,6 +66,21 @@ export async function fetchData(url) { return {}; } +export async function fetchDataFromMain(url) { + try { + const response = await fetch(url); + if (!response.ok) { + const message = `An error has occured: ${response.status}`; + throw new Error(message); + } + const json = await response.json(); + return json; + } catch (error) { + console.log(error) + } + return {}; +} + export async function getGithubRelease() { try { const response = await fetch(GITHUB_RELEASE_URL); From 88deced9f223d02629d03235abbdd69ee85a4679 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Tue, 22 Dec 2020 23:15:37 -0800 Subject: [PATCH 02/17] super basic table sorting and stylings --- web/pages/_app.tsx | 1 + web/pages/chat.tsx | 35 ++++++++++++++++++++++++++++------ web/pages/components/chart.tsx | 4 ++-- web/styles/chat.scss | 20 +++++++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 web/styles/chat.scss diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index c427e14da..96a334ff2 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -4,6 +4,7 @@ import '../styles/globals.scss'; // GW: I can't override ant design styles through components using NextJS's built-in CSS modules. So I'll just import styles here for now and figure out enabling SASS modules later. import '../styles/home.scss'; +import '../styles/chat.scss'; import { AppProps } from 'next/app'; import ServerStatusProvider from '../utils/server-status-context'; diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index f5b90d12c..8e910df1b 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { Table, Typography } from "antd"; +import { Table, Typography, Tooltip, Switch } from "antd"; import { ColumnsType } from 'antd/es/table'; import format from 'date-fns/format' @@ -79,12 +79,14 @@ export default function Chat() { title: 'Time', dataIndex: 'timestamp', key: 'timestamp', + className: 'timestamp-col', defaultSortOrder: 'descend', render: (timestamp) => { const dateObject = new Date(timestamp); - return format(dateObject, 'P pp'); + return format(dateObject, 'PP pp'); }, sorter: (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), + width: 80, }, { title: 'User', @@ -92,29 +94,50 @@ export default function Chat() { key: 'author', className: 'name-col', filters: nameFilters, - onFilter: (value, record) => record.name.indexOf(value) === 0, - sorter: (a, b) => a.name.toUppercase() - b.name.toUppercase(), + onFilter: (value, record) => record.author === value, + sorter: (a, b) => a.author.localeCompare(b.author), sortDirections: ['ascend', 'descend'], + ellipsis: true, + render: author => ( + + {author} + + ), + width: 80, }, { title: 'Message', dataIndex: 'body', key: 'body', className: 'message-col', + width: 230, }, { - title: 'Show/Hide', + title: 'Show/ Hide', dataIndex: 'visible', key: 'visible', + filters: [{ text: 'visible', value: true }, { text: 'hidden', value: false }], + onFilter: (value, record) => record.visible === value, + render: visible => ( + { + console.log(`switch to ${checked}`); + }} + defaultChecked={visible} + /> + ), + width: 60, }, ]; return ( -
+
Chat Messages
!record.visible ? 'hidden' : ''} diff --git a/web/pages/components/chart.tsx b/web/pages/components/chart.tsx index 019411d1f..e6cbeab3b 100644 --- a/web/pages/components/chart.tsx +++ b/web/pages/components/chart.tsx @@ -1,7 +1,7 @@ import { LineChart } from 'react-chartkick' -import styles from '../../styles/styles.module.scss'; import 'chart.js'; -import format from 'date-fns/format' +import format from 'date-fns/format'; +import styles from '../../styles/styles.module.scss'; interface TimedValue { time: Date; diff --git a/web/styles/chat.scss b/web/styles/chat.scss new file mode 100644 index 000000000..0e3e0aba5 --- /dev/null +++ b/web/styles/chat.scss @@ -0,0 +1,20 @@ +.chat-messages { + .ant-table-small .ant-table-selection-column { + width: 20px; + min-width: 20px; + } + .ant-table-tbody > tr > td { + transition: background 0.15s; + } + .ant-table-cell { + font-size: 12px; + + &.name-col { + text-overflow: ellipsis; + overflow: hidden; + } + } +} +.ant-table-filter-dropdown { + max-width: 250px; +} \ No newline at end of file From 6cb8cee8b7aaa82e66ae3fb24029ee65ea935ffa Mon Sep 17 00:00:00 2001 From: gingervitis Date: Fri, 25 Dec 2020 11:03:18 -0800 Subject: [PATCH 03/17] call update message api on toggle switch and update ui state --- web/pages/chat.tsx | 61 +++++++++++++++++++++++++++++++++++++--------- web/utils/apis.ts | 46 +++++++++++++++++----------------- 2 files changed, 73 insertions(+), 34 deletions(-) diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 8e910df1b..2922c6fb2 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -5,7 +5,8 @@ import format from 'date-fns/format' import { CHAT_HISTORY, - fetchDataFromMain, + UPDATE_CHAT_MESSGAE_VIZ, + fetchData, } from "../utils/apis"; const { Title } = Typography; @@ -21,8 +22,13 @@ interface Message { visible: boolean; } +interface MessageToggleProps { + isVisible: boolean; + message: Message; + setMessage: (message: Message) => {}, +}; -function createUserNameFilters(messages) { +function createUserNameFilters(messages: Message[]) { const filtered = messages.reduce((acc, curItem) => { const curAuthor = curItem.author; if (!acc.some(item => item.text === curAuthor)) { @@ -46,23 +52,58 @@ function createUserNameFilters(messages) { }); } +function MessageToggle({ isVisible, message, setMessage }: MessageToggleProps) { + const { id: messageId } = message; + + const updateChatMessage = async () => { + const result = await fetchData(UPDATE_CHAT_MESSGAE_VIZ, { + auth: true, + method: 'POST', + data: { + visible: !isVisible, + id: messageId, + }, + }); + + if (result.success && result.message === "changed") { + setMessage({ + ...message, + visible: !isVisible, + }); + } + } + + return ( + + ); +} + export default function Chat() { const [messages, setMessages] = useState([]); const getInfo = async () => { try { - const result = await fetchDataFromMain(CHAT_HISTORY); + const result = await fetchData(CHAT_HISTORY, { auth: false }); setMessages(result); } catch (error) { console.log("==== error", error); } }; + const updateMessage = message => { + const messageIndex = messages.findIndex(m => m.id === message.id); + messages.splice(messageIndex, 1, message) + setMessages([...messages]); + }; + useEffect(() => { getInfo(); }, []); - const nameFilters = createUserNameFilters(messages); const rowSelection = { onChange: (selectedRowKeys, selectedRows) => { @@ -118,13 +159,11 @@ export default function Chat() { key: 'visible', filters: [{ text: 'visible', value: true }, { text: 'hidden', value: false }], onFilter: (value, record) => record.visible === value, - render: visible => ( - { - console.log(`switch to ${checked}`); - }} - defaultChecked={visible} + render: (visible, record) => ( + ), width: 60, diff --git a/web/utils/apis.ts b/web/utils/apis.ts index 736b41463..062785555 100644 --- a/web/utils/apis.ts +++ b/web/utils/apis.ts @@ -34,41 +34,41 @@ export const LOGS_ALL = `${API_LOCATION}logs`; // Get warnings + errors export const LOGS_WARN = `${API_LOCATION}logs/warnings`; -// Chat history +// Get chat history export const CHAT_HISTORY = `${NEXT_PUBLIC_API_HOST}api/chat`; +// Get chat history +export const UPDATE_CHAT_MESSGAE_VIZ = `${NEXT_PUBLIC_API_HOST}api/admin/chat/updatemessagevisibility`; + const GITHUB_RELEASE_URL = "https://api.github.com/repos/owncast/owncast/releases/latest"; -export async function fetchData(url) { - const options: RequestInit = {}; +export async function fetchData(url: string, options?: object) { + const { + data, + method = 'GET', + auth = true, + } = options || {}; - if (ADMIN_USERNAME && ADMIN_STREAMKEY) { + const requestOptions: RequestInit = { + method, + }; + + if (data) { + requestOptions.body = JSON.stringify(data) + } + + if (auth && ADMIN_USERNAME && ADMIN_STREAMKEY) { const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`); - options.headers = { + requestOptions.headers = { 'Authorization': `Basic ${encoded}` } - options.mode = 'cors'; - options.credentials = 'include' + requestOptions.mode = 'cors'; + requestOptions.credentials = 'include'; } try { - const response = await fetch(url, options); - if (!response.ok) { - const message = `An error has occured: ${response.status}`; - throw new Error(message); - } - const json = await response.json(); - return json; - } catch (error) { - console.log(error) - } - return {}; -} - -export async function fetchDataFromMain(url) { - try { - const response = await fetch(url); + const response = await fetch(url, requestOptions); if (!response.ok) { const message = `An error has occured: ${response.status}`; throw new Error(message); From 310c6573d3c99e7c68f2f744ba5982939a0c26c3 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Fri, 25 Dec 2020 20:29:15 -0800 Subject: [PATCH 04/17] make toggle a separate component so we can display an outcome icon beside it --- web/pages/chat.tsx | 98 ++++++++++----------------------- web/pages/components/toggle.tsx | 75 +++++++++++++++++++++++++ web/styles/chat.scss | 12 ++++ web/styles/colors.scss | 6 ++ web/styles/globals.scss | 2 +- web/types/chat.ts | 10 ++++ 6 files changed, 133 insertions(+), 70 deletions(-) create mode 100644 web/pages/components/toggle.tsx create mode 100644 web/types/chat.ts diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 2922c6fb2..8d573dba1 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -1,34 +1,18 @@ import React, { useState, useEffect } from "react"; -import { Table, Typography, Tooltip, Switch } from "antd"; +import { Table, Typography, Tooltip, Switch, Button, Result } from "antd"; +import { RowSelectionType } from "antd/es/table/interface"; import { ColumnsType } from 'antd/es/table'; import format from 'date-fns/format' -import { - CHAT_HISTORY, - UPDATE_CHAT_MESSGAE_VIZ, - fetchData, -} from "../utils/apis"; +import ToggleSwitch from './components/toggle'; + +import { CHAT_HISTORY, fetchData } from "../utils/apis"; +import { MessageType } from '../types/chat'; + const { Title } = Typography; -interface Message { - key: string; - author: string; - body: string; - id: string; - name: string; - timestamp: string; - type: string; - visible: boolean; -} - -interface MessageToggleProps { - isVisible: boolean; - message: Message; - setMessage: (message: Message) => {}, -}; - -function createUserNameFilters(messages: Message[]) { +function createUserNameFilters(messages: MessageType[]) { const filtered = messages.reduce((acc, curItem) => { const curAuthor = curItem.author; if (!acc.some(item => item.text === curAuthor)) { @@ -52,38 +36,9 @@ function createUserNameFilters(messages: Message[]) { }); } -function MessageToggle({ isVisible, message, setMessage }: MessageToggleProps) { - const { id: messageId } = message; - - const updateChatMessage = async () => { - const result = await fetchData(UPDATE_CHAT_MESSGAE_VIZ, { - auth: true, - method: 'POST', - data: { - visible: !isVisible, - id: messageId, - }, - }); - - if (result.success && result.message === "changed") { - setMessage({ - ...message, - visible: !isVisible, - }); - } - } - - return ( - - ); -} - export default function Chat() { const [messages, setMessages] = useState([]); + const [selectedRowKeys, setSelectedRows] = useState([]); const getInfo = async () => { try { @@ -96,6 +51,7 @@ export default function Chat() { const updateMessage = message => { const messageIndex = messages.findIndex(m => m.id === message.id); + console.log("====update?", message, messages[messageIndex]) messages.splice(messageIndex, 1, message) setMessages([...messages]); }; @@ -105,17 +61,16 @@ export default function Chat() { }, []); const nameFilters = createUserNameFilters(messages); - const rowSelection = { - onChange: (selectedRowKeys, selectedRows) => { - console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); + + const rowSelection: RowSelectionType = { + selectedRowKeys, + onChange: selectedKeys => { + setSelectedRows(selectedKeys); }, - getCheckboxProps: record => ({ - disabled: record.name === 'Disabled User', // Column configuration not to be checked - name: record.name, - }), }; - const chatColumns: ColumnsType = [ + + const chatColumns: ColumnsType = [ { title: 'Time', dataIndex: 'timestamp', @@ -160,7 +115,7 @@ export default function Chat() { filters: [{ text: 'visible', value: true }, { text: 'hidden', value: false }], onFilter: (value, record) => record.visible === value, render: (visible, record) => ( - Chat Messages +

click things and stuff

+ +
row.id} - - rowSelection={{ - type: "checkbox", - ...rowSelection, - }} - + rowSelection={rowSelection} /> ) } diff --git a/web/pages/components/toggle.tsx b/web/pages/components/toggle.tsx new file mode 100644 index 000000000..c867f3152 --- /dev/null +++ b/web/pages/components/toggle.tsx @@ -0,0 +1,75 @@ +// Wrapper for AntDesign Switch that makes an api call, then displays a confirmation icon upon +// TODO: make it more generic, maybe. This one is currently just for message visiblity updates. + +import React, { useState, useEffect } from "react"; +import { Switch } from "antd"; +import { CheckCircleFilled, ExclamationCircleFilled } from "@ant-design/icons"; +import { fetchData, UPDATE_CHAT_MESSGAE_VIZ } from "../../utils/apis"; +import { MessageType } from '../../types/chat'; + +interface MessageToggleProps { + isVisible: boolean; + message: MessageType; + setMessage: (message: MessageType) => {}, +}; + +const OUTCOME_TIMEOUT = 3000; + +export default function ToggleSwitch({ isVisible, message, setMessage }: MessageToggleProps) { + let outcomeTimeout = null; + const [outcome, setOutcome] = useState(0); + + const { id: messageId } = message; + + const resetOutcome = () => { + outcomeTimeout = setTimeout(() => { setOutcome(0)}, OUTCOME_TIMEOUT); + }; + + useEffect(() => { + return () => { + clearTimeout(outcomeTimeout); + }; + }); + + + const updateChatMessage = async () => { + clearTimeout(outcomeTimeout); + setOutcome(0); + const result = await fetchData(UPDATE_CHAT_MESSGAE_VIZ, { + auth: true, + method: 'POST', + data: { + visible: !isVisible, + id: messageId, + }, + }); + + if (result.success && result.message === "changed") { + setMessage({ ...message, visible: !isVisible }); + setOutcome(1); + } else { + setMessage({ ...message, visible: isVisible }); + setOutcome(-1); + } + resetOutcome(); + } + + + let outcomeIcon = ; + if (outcome) { + outcomeIcon = outcome > 0 ? + : + ; + } + + return ( +
+ {outcomeIcon} + +
+ ); +} \ No newline at end of file diff --git a/web/styles/chat.scss b/web/styles/chat.scss index 0e3e0aba5..df54dfed0 100644 --- a/web/styles/chat.scss +++ b/web/styles/chat.scss @@ -17,4 +17,16 @@ } .ant-table-filter-dropdown { max-width: 250px; +} + +.toggle-switch { + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: nowrap; + justify-content: flex-end; + + .outcome-icon { + margin-right: .5rem; + } } \ No newline at end of file diff --git a/web/styles/colors.scss b/web/styles/colors.scss index 22aa1d407..5c93e506d 100644 --- a/web/styles/colors.scss +++ b/web/styles/colors.scss @@ -3,4 +3,10 @@ --owncast-purple-highlight: #ccd; --online-color: #73dd3f; + + + --ant-error: #ff4d4f; + --ant-success: #52c41a; + --ant-warning: #faad14; + } diff --git a/web/styles/globals.scss b/web/styles/globals.scss index a1278937a..49e7e41d8 100644 --- a/web/styles/globals.scss +++ b/web/styles/globals.scss @@ -41,4 +41,4 @@ pre { background-color: rgb(44, 44, 44); color:lightgrey; } -} \ No newline at end of file +} diff --git a/web/types/chat.ts b/web/types/chat.ts new file mode 100644 index 000000000..096839b86 --- /dev/null +++ b/web/types/chat.ts @@ -0,0 +1,10 @@ +export interface MessageType { + author: string; + body: string; + id: string; + key: string; + name: string; + timestamp: string; + type: string; + visible: boolean; +} From 7c06b74324d223e39e05be9e777fb63289fb427b Mon Sep 17 00:00:00 2001 From: gingervitis Date: Fri, 25 Dec 2020 23:14:27 -0800 Subject: [PATCH 05/17] set up bulkprocessing states and ux --- web/pages/chat.tsx | 87 +++++++++++++++++++++++++++------ web/pages/components/toggle.tsx | 2 +- web/styles/chat.scss | 20 ++++++++ 3 files changed, 94 insertions(+), 15 deletions(-) diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 8d573dba1..376f54600 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -1,12 +1,13 @@ import React, { useState, useEffect } from "react"; -import { Table, Typography, Tooltip, Switch, Button, Result } from "antd"; +import { Table, Typography, Tooltip, Switch, Button } from "antd"; +import { CheckCircleFilled, ExclamationCircleFilled } from "@ant-design/icons"; import { RowSelectionType } from "antd/es/table/interface"; import { ColumnsType } from 'antd/es/table'; import format from 'date-fns/format' import ToggleSwitch from './components/toggle'; -import { CHAT_HISTORY, fetchData } from "../utils/apis"; +import { CHAT_HISTORY, fetchData, UPDATE_CHAT_MESSGAE_VIZ } from "../utils/apis"; import { MessageType } from '../types/chat'; @@ -35,10 +36,15 @@ function createUserNameFilters(messages: MessageType[]) { return 0; }); } +export const OUTCOME_TIMEOUT = 3000; export default function Chat() { const [messages, setMessages] = useState([]); const [selectedRowKeys, setSelectedRows] = useState([]); + const [bulkVisibility, setBulkVisibility] = useState(false); + const [bulkProcessing, setBulkProcessing] = useState(false); + const [bulkOutcome, setBulkOutcome] = useState(null); + let outcomeTimeout = null; const getInfo = async () => { try { @@ -51,13 +57,15 @@ export default function Chat() { const updateMessage = message => { const messageIndex = messages.findIndex(m => m.id === message.id); - console.log("====update?", message, messages[messageIndex]) messages.splice(messageIndex, 1, message) setMessages([...messages]); }; useEffect(() => { getInfo(); + return () => { + clearTimeout(outcomeTimeout); + }; }, []); const nameFilters = createUserNameFilters(messages); @@ -69,6 +77,44 @@ export default function Chat() { }, }; + const handleBulkToggle = checked => { + setBulkVisibility(checked); + }; + const resetBulkOutcome = () => { + outcomeTimeout = setTimeout(() => { setBulkOutcome(null)}, OUTCOME_TIMEOUT); + }; + const handleSubmitBulk = async () => { + setBulkProcessing(true); + const result = await fetchData(UPDATE_CHAT_MESSGAE_VIZ, { + auth: true, + method: 'POST', + data: { + visible: bulkVisibility, + idArray: selectedRowKeys, + }, + }); + + if (result.success && result.message === "changed") { + setBulkOutcome(); + resetBulkOutcome(); + + // update messages + const updatedList = [...messages]; + selectedRowKeys.map(key => { + const messageIndex = updatedList.findIndex(m => m.id === key); + const newMessage = {...messages[messageIndex], visible: bulkVisibility }; + updatedList.splice(messageIndex, 1, newMessage); + return null; + }); + setMessages(updatedList); + } else { + setBulkOutcome(); + resetBulkOutcome(); + } + setBulkProcessing(false); + + } + const chatColumns: ColumnsType = [ { @@ -109,7 +155,7 @@ export default function Chat() { width: 230, }, { - title: 'Show/ Hide', + title: 'Show / Hide', dataIndex: 'visible', key: 'visible', filters: [{ text: 'visible', value: true }, { text: 'hidden', value: false }], @@ -129,16 +175,29 @@ export default function Chat() { return (
Chat Messages -

click things and stuff

- - +
+ Check multiple messages to change their visibility to: + + + + +
{}, }; -const OUTCOME_TIMEOUT = 3000; export default function ToggleSwitch({ isVisible, message, setMessage }: MessageToggleProps) { let outcomeTimeout = null; diff --git a/web/styles/chat.scss b/web/styles/chat.scss index df54dfed0..f9c1e94ee 100644 --- a/web/styles/chat.scss +++ b/web/styles/chat.scss @@ -14,6 +14,26 @@ overflow: hidden; } } + + .bulk-editor { + margin: .5rem 0; + padding: .5rem; + border: 1px solid #333; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + + .label { + font-size: .75rem; + color: #666; + } + + .toggler { + margin: 0 1rem 0 .5rem; + } + + } } .ant-table-filter-dropdown { max-width: 250px; From e978789c8b838628cbe657969150e6205478aa0f Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sat, 26 Dec 2020 00:21:50 -0800 Subject: [PATCH 06/17] send id as an array --- web/pages/components/toggle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/pages/components/toggle.tsx b/web/pages/components/toggle.tsx index cb6b30d12..af1c93229 100644 --- a/web/pages/components/toggle.tsx +++ b/web/pages/components/toggle.tsx @@ -40,7 +40,7 @@ export default function ToggleSwitch({ isVisible, message, setMessage }: Message method: 'POST', data: { visible: !isVisible, - id: messageId, + idArray: [messageId], }, }); From e6124f3d61aade4ffdec575fbbe1da062e529d78 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sat, 26 Dec 2020 00:58:30 -0800 Subject: [PATCH 07/17] rename comp for clarity --- web/pages/chat.tsx | 4 ++-- .../components/{toggle.tsx => message-visiblity-toggle.tsx} | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) rename web/pages/components/{toggle.tsx => message-visiblity-toggle.tsx} (88%) diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 376f54600..8f09a2cad 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -5,7 +5,7 @@ import { RowSelectionType } from "antd/es/table/interface"; import { ColumnsType } from 'antd/es/table'; import format from 'date-fns/format' -import ToggleSwitch from './components/toggle'; +import MessageVisiblityToggle from './components/message-visiblity-toggle'; import { CHAT_HISTORY, fetchData, UPDATE_CHAT_MESSGAE_VIZ } from "../utils/apis"; import { MessageType } from '../types/chat'; @@ -161,7 +161,7 @@ export default function Chat() { filters: [{ text: 'visible', value: true }, { text: 'hidden', value: false }], onFilter: (value, record) => record.visible === value, render: (visible, record) => ( - {}, + setMessage: (message: MessageType) => void, }; -export default function ToggleSwitch({ isVisible, message, setMessage }: MessageToggleProps) { +export default function MessageVisiblityToggle({ isVisible, message, setMessage }: MessageToggleProps) { let outcomeTimeout = null; const [outcome, setOutcome] = useState(0); From 71b91a7fe08af648d16cf287365ca0e913528b28 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sat, 26 Dec 2020 01:01:51 -0800 Subject: [PATCH 08/17] reset selected after processing --- web/pages/chat.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 8f09a2cad..4bcaff6f9 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -107,6 +107,7 @@ export default function Chat() { return null; }); setMessages(updatedList); + setSelectedRows([]); } else { setBulkOutcome(); resetBulkOutcome(); From 5345c124aae664b0a6e3ba9503ffe706bedaf3cd Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Sat, 26 Dec 2020 17:36:46 -0800 Subject: [PATCH 09/17] Use auth'ed admin chat messages endpoint to get history --- web/pages/chat.tsx | 2 +- web/utils/apis.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 4bcaff6f9..992e7e59e 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -48,7 +48,7 @@ export default function Chat() { const getInfo = async () => { try { - const result = await fetchData(CHAT_HISTORY, { auth: false }); + const result = await fetchData(CHAT_HISTORY, { auth: true }); setMessages(result); } catch (error) { console.log("==== error", error); diff --git a/web/utils/apis.ts b/web/utils/apis.ts index 062785555..5e1be34ad 100644 --- a/web/utils/apis.ts +++ b/web/utils/apis.ts @@ -35,7 +35,7 @@ export const LOGS_ALL = `${API_LOCATION}logs`; export const LOGS_WARN = `${API_LOCATION}logs/warnings`; // Get chat history -export const CHAT_HISTORY = `${NEXT_PUBLIC_API_HOST}api/chat`; +export const CHAT_HISTORY = `${API_LOCATION}chat/messages`; // Get chat history export const UPDATE_CHAT_MESSGAE_VIZ = `${NEXT_PUBLIC_API_HOST}api/admin/chat/updatemessagevisibility`; From f6814565ded83cfc939655865ec5a36083bf2d70 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sat, 26 Dec 2020 21:36:48 -0800 Subject: [PATCH 10/17] rmeove switches;update styles; --- web/pages/chat.tsx | 97 ++++++++++--------- .../components/message-visiblity-toggle.tsx | 74 -------------- web/styles/chat.scss | 26 ++++- 3 files changed, 73 insertions(+), 124 deletions(-) delete mode 100644 web/pages/components/message-visiblity-toggle.tsx diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 992e7e59e..22a4080ff 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -1,16 +1,14 @@ import React, { useState, useEffect } from "react"; -import { Table, Typography, Tooltip, Switch, Button } from "antd"; -import { CheckCircleFilled, ExclamationCircleFilled } from "@ant-design/icons"; +import { Table, Typography, Tooltip, Button } from "antd"; +import { CheckCircleFilled, ExclamationCircleFilled, StopOutlined } from "@ant-design/icons"; +import classNames from 'classnames'; import { RowSelectionType } from "antd/es/table/interface"; import { ColumnsType } from 'antd/es/table'; import format from 'date-fns/format' -import MessageVisiblityToggle from './components/message-visiblity-toggle'; - import { CHAT_HISTORY, fetchData, UPDATE_CHAT_MESSGAE_VIZ } from "../utils/apis"; import { MessageType } from '../types/chat'; - const { Title } = Typography; function createUserNameFilters(messages: MessageType[]) { @@ -41,9 +39,9 @@ export const OUTCOME_TIMEOUT = 3000; export default function Chat() { const [messages, setMessages] = useState([]); const [selectedRowKeys, setSelectedRows] = useState([]); - const [bulkVisibility, setBulkVisibility] = useState(false); const [bulkProcessing, setBulkProcessing] = useState(false); const [bulkOutcome, setBulkOutcome] = useState(null); + const [bulkAction, setBulkAction] = useState(''); let outcomeTimeout = null; const getInfo = async () => { @@ -55,12 +53,6 @@ export default function Chat() { } }; - const updateMessage = message => { - const messageIndex = messages.findIndex(m => m.id === message.id); - messages.splice(messageIndex, 1, message) - setMessages([...messages]); - }; - useEffect(() => { getInfo(); return () => { @@ -77,13 +69,14 @@ export default function Chat() { }, }; - const handleBulkToggle = checked => { - setBulkVisibility(checked); - }; + const resetBulkOutcome = () => { - outcomeTimeout = setTimeout(() => { setBulkOutcome(null)}, OUTCOME_TIMEOUT); + outcomeTimeout = setTimeout(() => { + setBulkOutcome(null); + setBulkAction(''); + }, OUTCOME_TIMEOUT); }; - const handleSubmitBulk = async () => { + const handleSubmitBulk = async (bulkVisibility) => { setBulkProcessing(true); const result = await fetchData(UPDATE_CHAT_MESSGAE_VIZ, { auth: true, @@ -113,9 +106,15 @@ export default function Chat() { resetBulkOutcome(); } setBulkProcessing(false); - } - + const handleSubmitBulkShow = () => { + setBulkAction('show'); + handleSubmitBulk(true); + } + const handleSubmitBulkHide = () => { + setBulkAction('hide'); + handleSubmitBulk(false); + } const chatColumns: ColumnsType = [ { @@ -129,7 +128,7 @@ export default function Chat() { return format(dateObject, 'PP pp'); }, sorter: (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), - width: 80, + width: 90, }, { title: 'User', @@ -146,57 +145,61 @@ export default function Chat() { {author} ), - width: 80, + width: 110, }, { title: 'Message', dataIndex: 'body', key: 'body', className: 'message-col', - width: 230, + width: 320, }, { - title: 'Show / Hide', + title: '', dataIndex: 'visible', key: 'visible', - filters: [{ text: 'visible', value: true }, { text: 'hidden', value: false }], + className: 'toggle-col', + filters: [{ text: 'Visible messages', value: true }, { text: 'Hidden messages', value: false }], onFilter: (value, record) => record.visible === value, - render: (visible, record) => ( - - ), - width: 60, + render: visible => visible ? null : , + width: 30, }, ]; + const bulkDivClasses = classNames({ + 'bulk-editor': true, + active: selectedRowKeys.length, + }); return (
Chat Messages -
+
Check multiple messages to change their visibility to: - - +
void, -}; - - -export default function MessageVisiblityToggle({ isVisible, message, setMessage }: MessageToggleProps) { - let outcomeTimeout = null; - const [outcome, setOutcome] = useState(0); - - const { id: messageId } = message; - - const resetOutcome = () => { - outcomeTimeout = setTimeout(() => { setOutcome(0)}, OUTCOME_TIMEOUT); - }; - - useEffect(() => { - return () => { - clearTimeout(outcomeTimeout); - }; - }); - - - const updateChatMessage = async () => { - clearTimeout(outcomeTimeout); - setOutcome(0); - const result = await fetchData(UPDATE_CHAT_MESSGAE_VIZ, { - auth: true, - method: 'POST', - data: { - visible: !isVisible, - idArray: [messageId], - }, - }); - - if (result.success && result.message === "changed") { - setMessage({ ...message, visible: !isVisible }); - setOutcome(1); - } else { - setMessage({ ...message, visible: isVisible }); - setOutcome(-1); - } - resetOutcome(); - } - - - let outcomeIcon = ; - if (outcome) { - outcomeIcon = outcome > 0 ? - : - ; - } - - return ( -
- {outcomeIcon} - -
- ); -} \ No newline at end of file diff --git a/web/styles/chat.scss b/web/styles/chat.scss index f9c1e94ee..e2c28d76c 100644 --- a/web/styles/chat.scss +++ b/web/styles/chat.scss @@ -6,6 +6,12 @@ .ant-table-tbody > tr > td { transition: background 0.15s; } + .ant-table-row.hidden { + .ant-table-cell { + color: #444450; + } + + } .ant-table-cell { font-size: 12px; @@ -13,6 +19,11 @@ text-overflow: ellipsis; overflow: hidden; } + &.toggle-col { + label { + font-size: 11px; + } + } } .bulk-editor { @@ -23,14 +34,23 @@ flex-direction: row; align-items: center; justify-content: flex-end; + border-radius: 4px; + + &.active { + .label { + color: #ccc; + } + } .label { font-size: .75rem; color: #666; + margin-right: .5rem; } - .toggler { - margin: 0 1rem 0 .5rem; + button { + margin: 0 .2rem; + font-size: .75rem; } } @@ -49,4 +69,4 @@ .outcome-icon { margin-right: .5rem; } -} \ No newline at end of file +} From 3e959fe2d46ec525e0350b7ca4be936c17aa6f66 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sat, 26 Dec 2020 21:46:40 -0800 Subject: [PATCH 11/17] render message in cell --- web/pages/chat.tsx | 7 +++++++ web/styles/chat.scss | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 22a4080ff..0a550d303 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -153,6 +153,13 @@ export default function Chat() { key: 'body', className: 'message-col', width: 320, + render: body => ( +
+ ) }, { title: '', diff --git a/web/styles/chat.scss b/web/styles/chat.scss index e2c28d76c..4f7d04f98 100644 --- a/web/styles/chat.scss +++ b/web/styles/chat.scss @@ -24,6 +24,16 @@ font-size: 11px; } } + + .message-contents { + overflow: hidden; + img { + position: relative; + margin-top: -5px; + width: 3rem; + padding: 0.25rem; + } + } } .bulk-editor { From 2e3737646c5eae860e8aa72944a4e71fbf888089 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sun, 27 Dec 2020 01:15:17 -0800 Subject: [PATCH 12/17] style update --- web/pages/chat.tsx | 2 +- web/styles/chat.scss | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 0a550d303..6f5cfaff0 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -168,7 +168,7 @@ export default function Chat() { className: 'toggle-col', filters: [{ text: 'Visible messages', value: true }, { text: 'Hidden messages', value: false }], onFilter: (value, record) => record.visible === value, - render: visible => visible ? null : , + render: visible => visible ? null : , width: 30, }, ]; diff --git a/web/styles/chat.scss b/web/styles/chat.scss index 4f7d04f98..57d1b9cc3 100644 --- a/web/styles/chat.scss +++ b/web/styles/chat.scss @@ -26,7 +26,8 @@ } .message-contents { - overflow: hidden; + overflow: auto; + max-height: 200px; img { position: relative; margin-top: -5px; From 422f37c47792766d97e821e58df6cc1a1dc1e79f Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sun, 27 Dec 2020 01:20:09 -0800 Subject: [PATCH 13/17] move chat to its own menu in sidenav --- web/pages/chat.tsx | 1 + web/pages/components/main-layout.tsx | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index 6f5cfaff0..f82eb9ef9 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -181,6 +181,7 @@ export default function Chat() { return (
Chat Messages +

Manage the messages from viewers that show up on your stream.

Check multiple messages to change their visibility to: diff --git a/web/pages/components/main-layout.tsx b/web/pages/components/main-layout.tsx index eea871a0a..cd031be3c 100644 --- a/web/pages/components/main-layout.tsx +++ b/web/pages/components/main-layout.tsx @@ -13,6 +13,7 @@ import { ToolOutlined, PlayCircleFilled, MinusSquareFilled, + MessageOutlined, } from '@ant-design/icons'; import classNames from 'classnames'; import { upgradeVersionAvailable } from "../../utils/apis"; @@ -107,6 +108,14 @@ export default function MainLayout(props) { Viewers + } + title="Chat utilities" + > + Chat + + } title="Utilities" > - - Chat Moderation - Hardware From f31a2deb821d83686c760e4e76d0a84d5b1ca1d1 Mon Sep 17 00:00:00 2001 From: Sean Heintz Date: Tue, 29 Dec 2020 02:13:39 -0700 Subject: [PATCH 14/17] Implement sorting by "Connected Time" --- web/pages/viewer-info.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/pages/viewer-info.tsx b/web/pages/viewer-info.tsx index 70edf11fd..5786e887f 100644 --- a/web/pages/viewer-info.tsx +++ b/web/pages/viewer-info.tsx @@ -86,6 +86,8 @@ export default function ViewersOverTime() { dataIndex: "connectedAt", key: "connectedAt", render: (time) => formatDistanceToNow(new Date(time)), + sorter: (a, b) => new Date(a.connectedAt).getTime() - new Date(b.connectedAt).getTime(), + sortDirections: ["descend", "ascend"] as SortOrder[], }, { title: "User Agent", From 12201d00883496d50e6750dd3fa4ba407993fc9a Mon Sep 17 00:00:00 2001 From: gingervitis Date: Tue, 29 Dec 2020 14:59:43 -0800 Subject: [PATCH 15/17] addressing type warnings; account for no-messages returned --- web/package-lock.json | 6 ++++++ web/package.json | 1 + web/pages/chat.tsx | 12 ++++++++---- web/utils/format.ts | 3 ++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 9ba0310bd..0fc497d0b 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1575,6 +1575,12 @@ "moment": "^2.10.2" } }, + "@types/classnames": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz", + "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==", + "dev": true + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", diff --git a/web/package.json b/web/package.json index 2e7d684aa..f61e05f81 100644 --- a/web/package.json +++ b/web/package.json @@ -24,6 +24,7 @@ }, "devDependencies": { "@types/chart.js": "^2.9.28", + "@types/classnames": "^2.2.11", "@types/node": "^14.11.2", "@types/prop-types": "^15.7.3", "@types/react": "^16.9.49", diff --git a/web/pages/chat.tsx b/web/pages/chat.tsx index f82eb9ef9..fe7d1bdff 100644 --- a/web/pages/chat.tsx +++ b/web/pages/chat.tsx @@ -2,12 +2,12 @@ import React, { useState, useEffect } from "react"; import { Table, Typography, Tooltip, Button } from "antd"; import { CheckCircleFilled, ExclamationCircleFilled, StopOutlined } from "@ant-design/icons"; import classNames from 'classnames'; -import { RowSelectionType } from "antd/es/table/interface"; import { ColumnsType } from 'antd/es/table'; import format from 'date-fns/format' import { CHAT_HISTORY, fetchData, UPDATE_CHAT_MESSGAE_VIZ } from "../utils/apis"; import { MessageType } from '../types/chat'; +import { isEmptyObject } from "../utils/format"; const { Title } = Typography; @@ -47,7 +47,11 @@ export default function Chat() { const getInfo = async () => { try { const result = await fetchData(CHAT_HISTORY, { auth: true }); - setMessages(result); + if (isEmptyObject(result)) { + setMessages([]); + } else { + setMessages(result); + } } catch (error) { console.log("==== error", error); } @@ -62,9 +66,9 @@ export default function Chat() { const nameFilters = createUserNameFilters(messages); - const rowSelection: RowSelectionType = { + const rowSelection = { selectedRowKeys, - onChange: selectedKeys => { + onChange: (selectedKeys: string[]) => { setSelectedRows(selectedKeys); }, }; diff --git a/web/utils/format.ts b/web/utils/format.ts index a34ce1773..549f8b826 100644 --- a/web/utils/format.ts +++ b/web/utils/format.ts @@ -15,9 +15,10 @@ export function formatIPAddress(ipAddress: string): string { // check if obj is {} export function isEmptyObject(obj) { - return !obj || Object.keys(obj).length === 0; + return !obj || (Object.keys(obj).length === 0 && obj.constructor === Object); } + export function parseSecondsToDurationString(seconds = 0) { const finiteSeconds = Number.isFinite(+seconds) ? Math.abs(seconds) : 0; From 1d30530afdbd2e6f2a64c24ab6ee2ccae9ec8908 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Tue, 29 Dec 2020 15:35:54 -0800 Subject: [PATCH 16/17] other TS cleanup --- web/utils/apis.ts | 10 +++++++++- web/utils/format.ts | 7 +++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/web/utils/apis.ts b/web/utils/apis.ts index 5e1be34ad..a1c3596c3 100644 --- a/web/utils/apis.ts +++ b/web/utils/apis.ts @@ -1,3 +1,5 @@ +import { RequestInit } from 'node_modules/typescript/lib/lib.dom.d'; + /* eslint-disable prefer-destructuring */ const ADMIN_USERNAME = process.env.NEXT_PUBLIC_ADMIN_USERNAME; const ADMIN_STREAMKEY = process.env.NEXT_PUBLIC_ADMIN_STREAMKEY; @@ -43,7 +45,13 @@ export const UPDATE_CHAT_MESSGAE_VIZ = `${NEXT_PUBLIC_API_HOST}api/admin/chat/up const GITHUB_RELEASE_URL = "https://api.github.com/repos/owncast/owncast/releases/latest"; -export async function fetchData(url: string, options?: object) { +interface FetchOptions { + data?: any; + method?: string; + auth?: boolean; +}; + +export async function fetchData(url: string, options?: FetchOptions) { const { data, method = 'GET', diff --git a/web/utils/format.ts b/web/utils/format.ts index 549f8b826..c7db93cb0 100644 --- a/web/utils/format.ts +++ b/web/utils/format.ts @@ -18,6 +18,9 @@ export function isEmptyObject(obj) { return !obj || (Object.keys(obj).length === 0 && obj.constructor === Object); } +export function padLeft(text, pad, size) { + return String(pad.repeat(size) + text).slice(-size); +} export function parseSecondsToDurationString(seconds = 0) { const finiteSeconds = Number.isFinite(+seconds) ? Math.abs(seconds) : 0; @@ -36,7 +39,3 @@ export function parseSecondsToDurationString(seconds = 0) { return daysString + hoursString + minString + secsString; } - -export function padLeft(text, pad, size) { - return String(pad.repeat(size) + text).slice(-size); -} \ No newline at end of file From 55ca050575d28fdaeb5689cee4bacfadb452d1d0 Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Tue, 29 Dec 2020 15:50:47 -0800 Subject: [PATCH 17/17] This seems to build? --- web/utils/apis.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/utils/apis.ts b/web/utils/apis.ts index a1c3596c3..0c60790c1 100644 --- a/web/utils/apis.ts +++ b/web/utils/apis.ts @@ -1,5 +1,3 @@ -import { RequestInit } from 'node_modules/typescript/lib/lib.dom.d'; - /* eslint-disable prefer-destructuring */ const ADMIN_USERNAME = process.env.NEXT_PUBLIC_ADMIN_USERNAME; const ADMIN_STREAMKEY = process.env.NEXT_PUBLIC_ADMIN_STREAMKEY;