diff --git a/admin/src/Api.js b/admin/src/Api.js index 35a5b13..802cc54 100644 --- a/admin/src/Api.js +++ b/admin/src/Api.js @@ -1,9 +1,9 @@ -import firebase from 'firebase/app'; -import 'firebase/auth'; +import firebase from "firebase/app"; +import "firebase/auth"; -const API = window.location.href.includes('localhost') - ? 'http://localhost:5001/watch-party-crx/us-central1/admin' - : 'https://us-central1-watch-party-crx.cloudfunctions.net/admin'; +const API = window.location.href.includes("localhost") + ? "http://localhost:5001/watch-party-crx/us-central1/admin" + : "https://us-central1-watch-party-crx.cloudfunctions.net/admin"; const fetcher = async ({ endpoint, method, bodyData }) => { const idToken = await firebase.auth().currentUser.getIdToken(); @@ -12,19 +12,21 @@ const fetcher = async ({ endpoint, method, bodyData }) => { method, headers: { Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`, - 'Access-Control-Allow-Origin': '*', + "Access-Control-Allow-Origin": "*", }, body, }); }; -export const getPartyDashboardData = () => fetcher({ endpoint: 'partyDashboard', method: 'GET' }); -export const getServerDashboardData = () => fetcher({ endpoint: 'serverDashboard', method: 'GET' }); +export const getPartyDashboardData = () => + fetcher({ endpoint: "partyDashboard", method: "GET" }); +export const getServerDashboardData = () => + fetcher({ endpoint: "serverDashboard", method: "GET" }); export const postUpdateVersionActive = (bodyData) => - fetcher({ endpoint: 'updateVersionActive', method: 'POST', bodyData }); + fetcher({ endpoint: "updateVersionActive", method: "POST", bodyData }); export const postUpdateVideoSelectors = (bodyData) => - fetcher({ endpoint: 'updateVideoSelectors', method: 'POST', bodyData }); + fetcher({ endpoint: "updateVideoSelectors", method: "POST", bodyData }); export const postUpdateVideoNameSelectors = (bodyData) => - fetcher({ endpoint: 'updateVideoNameSelectors', method: 'POST', bodyData }); + fetcher({ endpoint: "updateVideoNameSelectors", method: "POST", bodyData }); export const postSendMessage = (bodyData) => - fetcher({ endpoint: 'sendMessage', method: 'POST', bodyData }); + fetcher({ endpoint: "sendMessage", method: "POST", bodyData }); diff --git a/admin/src/Dashboard.jsx b/admin/src/Dashboard.jsx index deaf165..9eab1ad 100644 --- a/admin/src/Dashboard.jsx +++ b/admin/src/Dashboard.jsx @@ -1,7 +1,7 @@ -import React, { Component } from 'react'; -import firebase from 'firebase/app'; -import 'firebase/auth'; -import 'firebase/firestore'; +import React, { Component } from "react"; +import firebase from "firebase/app"; +import "firebase/auth"; +import "firebase/firestore"; import { Button, Card, @@ -21,7 +21,7 @@ import { Collapse, Modal, Select, -} from 'antd'; +} from "antd"; import { getPartyDashboardData, getServerDashboardData, @@ -29,7 +29,7 @@ import { postUpdateVideoSelectors, postUpdateVideoNameSelectors, postSendMessage, -} from './Api'; +} from "./Api"; import { CheckCircleTwoTone, CloseCircleTwoTone, @@ -43,61 +43,87 @@ import { LogoutOutlined, SendOutlined, PlusCircleOutlined, -} from '@ant-design/icons'; +} from "@ant-design/icons"; const PLAYER_SOURCES = [ - { text: 'Netflix', value: 'netflix.' }, - { text: 'Amazon', value: 'amazon.' }, - { text: 'Hulu', value: 'hulu.' }, - { text: 'Disney+', value: 'disneyplus.' }, - { text: 'YouTube', value: 'youtube.' }, - { text: 'HBO Now', value: 'hbonow.' }, - { text: 'HBO Max', value: 'hbomax.' }, - { text: 'Peacock', value: 'peacocktv.' }, - { text: 'Google Play', value: 'play.google.' }, - { text: 'Crunchyroll', value: 'crunchyroll.' }, - { text: 'Apple TV+', value: 'tv.apple.' }, - { text: 'Viki', value: 'viki.' }, - { text: 'Funimation', value: 'funimation.' }, - { text: 'Rooster Teeth', value: 'roosterteeth.' }, - { text: 'VRV', value: 'vrv.' }, - { text: 'Stan', value: 'stan.' }, - { text: 'Hotstar', value: 'hotstar.' }, - { text: 'Airtel', value: 'airtelxstream.' }, - { text: 'Vimeo', value: 'vimeo.' }, - { text: 'Dailymotion', value: 'dailymotion.' }, + { text: "Netflix", value: "netflix." }, + { text: "Amazon", value: "amazon." }, + { text: "Hulu", value: "hulu." }, + { text: "Disney+", value: "disneyplus." }, + { text: "YouTube", value: "youtube." }, + { text: "HBO Now", value: "hbonow." }, + { text: "HBO Max", value: "hbomax." }, + { text: "Peacock", value: "peacocktv." }, + { text: "Google Play", value: "play.google." }, + { text: "Crunchyroll", value: "crunchyroll." }, + { text: "Apple TV+", value: "tv.apple." }, + { text: "Viki", value: "viki." }, + { text: "Funimation", value: "funimation." }, + { text: "Rooster Teeth", value: "roosterteeth." }, + { text: "VRV", value: "vrv." }, + { text: "Stan", value: "stan." }, + { text: "Hotstar", value: "hotstar." }, + { text: "Airtel", value: "airtelxstream." }, + { text: "Vimeo", value: "vimeo." }, + { text: "Dailymotion", value: "dailymotion." }, ]; const MESSAGE_TYPES = [ { - text: 'REPLY', - value: 'interactive/text', + text: "REPLY", + value: "interactive/text", }, { - text: 'LINK', - value: 'interactive/link', + text: "LINK", + value: "interactive/link", }, { - text: 'MESSAGE', - value: '', + text: "MESSAGE", + value: "", }, ]; const emptyMessage = { - type: '', - text: '', - uid: '', - name: '', - endpoint: '', + type: "", + text: "", + uid: "", + name: "", + endpoint: "", // time set on send, }; +const emptyAlert = { + title: "", + description: "", + link: "", + linkText: "", +}; + +const emptyVersion = { + v: "", + active: true, + alerts: new Array(1).fill(Object.assign({}, emptyAlert)), + callEnabled: true, + message: "All good!", +}; + +const emptyNameSelector = { + service: '', + show: '', + episode: '', +}; + +const emptyVideoSelector = { + service: '', + selector: '', +}; + export default class Dashboard extends Component { constructor(props) { super(props); this.state = { partiesOverview: null, - activeTab: '1', + activeTab: "1", refreshing: true, selectedParties: [], nameSelectors: {}, @@ -106,7 +132,13 @@ export default class Dashboard extends Component { activeLimit: 5, afkLimit: 10, messageModel: false, + versionModal: false, + nameSelectorModal: false, + videoSelectorModal: false, newMessage: emptyMessage, + newVersion: emptyVersion, + newNameSelector: emptyNameSelector, + newVideoSelector: emptyVideoSelector }; this.serverStatusListener = null; } @@ -130,13 +162,18 @@ export default class Dashboard extends Component { this.setState({ refreshing: true }); const { activeTab } = this.state; switch (activeTab) { - case '1': + case "1": this.getPartiesOverview(); break; - case '2': - case '3': - case '4': + case "2": + case "3": + case "4": this.getVersionsOverview(); + this.setState({ + newVersion: emptyVersion, + newNameSelector: emptyNameSelector, + newVideoSelector: emptyVideoSelector + }); break; default: break; @@ -148,14 +185,18 @@ export default class Dashboard extends Component { updateVersionMessage = (message, version) => { this.setState({ refreshing: true }); const { versions } = this.state; - const versionData = versions.filter((v) => v.version.split('.').join('') === version)[0]; + const versionData = versions.filter( + (v) => v.version.split(".").join("") === version + )[0]; versionData.message = message; this.updateVersion(version, versionData); }; updateVersionActive = (active, version) => { this.setState({ refreshing: true }); const { versions } = this.state; - const versionData = versions.filter((v) => v.version.split('.').join('') === version)[0]; + const versionData = versions.filter( + (v) => v.version.split(".").join("") === version + )[0]; versionData.active = active; this.updateVersion(version, versionData); }; @@ -165,13 +206,15 @@ export default class Dashboard extends Component { .catch((err) => notification.error({ description: err.message, - message: 'Unable To Update', + message: "Unable To Update", }) ); }; getSourceName(source) { const source_detail = PLAYER_SOURCES.filter((p) => p.value === source); - return source_detail && source_detail.length > 0 ? source_detail[0].text : 'source'; + return source_detail && source_detail.length > 0 + ? source_detail[0].text + : source; } handleVideoSelectorChange = (e, s) => { const { selectors } = this.state; @@ -186,7 +229,7 @@ export default class Dashboard extends Component { .catch((err) => { notification.error({ description: err.message, - message: 'Unable To Update', + message: "Unable To Update", }); this.setState({ refreshing: false }); @@ -199,56 +242,61 @@ export default class Dashboard extends Component { }; handleAlertTitleChange = (value, version, index) => { const { versions } = this.state; - versions.filter((vs) => vs.version === version)[0].alerts[index].title = value; + versions.filter((vs) => vs.version === version)[0].alerts[ + index + ].title = value; this.setState({ versions }); }; handleAlertDescriptionChange = (value, version, index) => { const { versions } = this.state; - versions.filter((vs) => vs.version === version)[0].alerts[index].description = value; + versions.filter((vs) => vs.version === version)[0].alerts[ + index + ].description = value; this.setState({ versions }); }; handleAlertLinkChange = (value, version, index) => { const { versions } = this.state; - versions.filter((vs) => vs.version === version)[0].alerts[index].link = value; + versions.filter((vs) => vs.version === version)[0].alerts[ + index + ].link = value; this.setState({ versions }); }; handleAlertLinkTextChange = (value, version, index) => { const { versions } = this.state; - versions.filter((vs) => vs.version === version)[0].alerts[index].linkText = value; + versions.filter((vs) => vs.version === version)[0].alerts[ + index + ].linkText = value; this.setState({ versions }); }; deleteAlert = (version, index) => { const { versions } = this.state; versions.filter((vs) => vs.version === version)[0].alerts.splice(index, 1); if ( - versions.filter((vs) => vs.version === version)[0].importantAlert === index || + versions.filter((vs) => vs.version === version)[0].importantAlert === + index || versions.filter((vs) => vs.version === version)[0].alerts.length < index ) { - versions.filter((vs) => vs.version === version)[0].importantAlert = undefined; + versions.filter( + (vs) => vs.version === version + )[0].importantAlert = undefined; } this.setState({ versions }); }; addEmptyAlert = (version) => { const { versions } = this.state; - versions - .filter((vs) => vs.version === version)[0] - .alerts.push({ - title: '', - description: '', - link: '', - linkText: '', - }); + versions.filter((vs) => vs.version === version)[0].alerts.push(emptyAlert); this.setState({ versions }); }; saveAlerts = (version) => { this.setState({ refreshing: true }); const { versions } = this.state; const versionData = versions.filter((v) => v.version === version)[0]; - if (versionData.alerts.length > 0) this.updateVersion(version.split('.').join(''), versionData); + if (versionData.alerts.length > 0) + this.updateVersion(version.split(".").join(""), versionData); else { notification.error({ - message: 'Missing Alerts', - description: 'Must have atleast one alert', + message: "Missing Alerts", + description: "Must have atleast one alert", }); this.setState({ refreshing: false }); @@ -257,7 +305,9 @@ export default class Dashboard extends Component { changeImportantAlert = (active, version, index) => { const { versions } = this.state; if (active === false) { - versions.filter((vs) => vs.version === version)[0].importantAlert = undefined; + versions.filter( + (vs) => vs.version === version + )[0].importantAlert = undefined; } else { versions.filter((vs) => vs.version === version)[0].importantAlert = index; } @@ -266,16 +316,16 @@ export default class Dashboard extends Component { getAddMessageText = () => { const { activeTab } = this.state; switch (activeTab) { - case '1': - return 'Send Message'; - case '2': - return 'Add Version'; - case '3': - return 'Add Video Selector'; - case '4': - return 'Add Name Selector'; + case "1": + return "Send Message"; + case "2": + return "Add Version"; + case "3": + return "Add Video Selector"; + case "4": + return "Add Name Selector"; default: - return ''; + return ""; } }; getPrettyTime = (t) => { @@ -296,7 +346,7 @@ export default class Dashboard extends Component { .catch((err) => { notification.error({ description: err.message, - message: 'Unable To Update', + message: "Unable To Update", }); this.setState({ refreshing: false }); @@ -305,9 +355,18 @@ export default class Dashboard extends Component { sendOrAdd = () => { const { activeTab } = this.state; switch (activeTab) { - case '1': + case "1": this.setState({ messageModel: true }); break; + case "2": + this.setState({ versionModal: true }); + break; + case "3": + this.setState({ videoSelectorModal: true }); + break; + case "4": + this.setState({ nameSelectorModal: true }); + break; default: break; } @@ -324,21 +383,115 @@ export default class Dashboard extends Component { await postSendMessage({ selectedParties, newMessage }).catch((err) => { notification.error({ description: err.message, - message: 'Unable To Send Message', + message: "Unable To Send Message", }); }); this.setState({ refreshing: false, newMessage: { - type: '', - text: '', - uid: '', - name: '', - endpoint: '', + type: "", + text: "", + uid: "", + name: "", + endpoint: "", }, messageModel: false, }); }; + addVersion = () => { + this.setState({ refreshing: true }); + const { newVersion } = this.state; + const { v, active, callEnabled, alerts, message } = newVersion; + this.updateVersion(newVersion.v.split(".").join(""), { + v, + active, + callEnabled, + alerts, + message + }); + }; + handleNewVersionNumberChange = (v) => { + const newVersion = Object.assign({}, this.state.newVersion, { + v, + }); + this.setState({ + newVersion, + }); + }; + changeNewVersionActive = (active) => { + const newVersion = Object.assign({}, this.state.newVersion, { + active, + }); + this.setState({ + newVersion, + }); + }; + changeNewVersionCallEnabled = (callEnabled) => { + const newVersion = Object.assign({}, this.state.newVersion, { + callEnabled, + }); + this.setState({ + newVersion, + }); + }; + handleNewVersionMessageChange = (message) => { + const newVersion = Object.assign({}, this.state.newVersion, { + message, + }); + this.setState({ + newVersion, + }); + }; + handleNewVersionAlertTitleChange = (title) => { + const newVersionAlert = Object.assign({},this.state.newVersion.alerts[0],{title}); + const newVersion = Object.assign({}, this.state.newVersion, {alerts:[newVersionAlert]}); + this.setState({ + newVersion + }); + } + handleNewVersionAlertDescriptionChange = (description) => { + const newVersionAlert = Object.assign({},this.state.newVersion.alerts[0],{description}); + const newVersion = Object.assign({}, this.state.newVersion, {alerts:[newVersionAlert]}); + this.setState({ + newVersion + }); + } + handleNewVersionAlertLinkTextChange = (linkText) => { + const newVersionAlert = Object.assign({},this.state.newVersion.alerts[0],{linkText}); + const newVersion = Object.assign({}, this.state.newVersion, {alerts:[newVersionAlert]}); + this.setState({ + newVersion + }); + } + handleNewVersionAlertLinkChange = (link) => { + const newVersionAlert = Object.assign({},this.state.newVersion.alerts[0],{link}); + const newVersion = Object.assign({}, this.state.newVersion, {alerts:[newVersionAlert]}); + this.setState({ + newVersion + }); + } + handleNewNameSelectorChange = (o) => { + const newNameSelector = Object.assign({}, this.state.newNameSelector, o); + this.setState({ newNameSelector }); + } + addNameSelector = () => { + const { newNameSelector } = this.state; + const { service, show, episode } = newNameSelector; + const videoNameSelector = { [service] : {show, episode}}; + const nameSelectors = Object.assign({}, this.state.nameSelectors, videoNameSelector); + this.setState({ nameSelectors }); + } + handleNewVideoSelectorChange = (o) => { + const newVideoSelector = Object.assign({}, this.state.newVideoSelector, o); + this.setState({ newVideoSelector }); + } + addVideoSelector = () => { + const { newVideoSelector } = this.state; + const { service, selector } = newVideoSelector; + const videoSelector = { [service] : selector}; + const selectors = Object.assign({}, this.state.selectors, videoSelector); + this.setState({ selectors }); + } render() { const { partiesOverview, @@ -352,61 +505,72 @@ export default class Dashboard extends Component { afkLimit, messageModel, newMessage, + versionModal, + newVersion, + nameSelectorModal, + newNameSelector, + videoSelectorModal, + newVideoSelector } = this.state; const partiesColumns = [ { - title: 'Party Name', - dataIndex: 'id', + title: "Party Name", + dataIndex: "id", sorter: (a, b) => (a.id.toLowerCase() < b.id.toLowerCase() ? -1 : 1), }, { - title: 'Source', - dataIndex: 'source', - sorter: (a, b) => (a.source.toLowerCase() < b.source.toLowerCase() ? -1 : 1), + title: "Source", + dataIndex: "source", + sorter: (a, b) => + a.source.toLowerCase() < b.source.toLowerCase() ? -1 : 1, filters: [ ...PLAYER_SOURCES, { - text: 'Is Playing', - value: 'PLAYING', + text: "Is Playing", + value: "PLAYING", }, { - text: 'Is Paused', - value: 'PAUSED', + text: "Is Paused", + value: "PAUSED", }, ], onFilter: (v, r) => r.source === v || r.status === v, render: (a, r) => ( ), }, { - title: 'Created At', - dataIndex: 'createTime', - defaultSortOrder: 'descend', + title: "Created At", + dataIndex: "createTime", + defaultSortOrder: "descend", sorter: (a, b) => Date.parse(a.createTime) - Date.parse(b.createTime), render: (a) => new Date(Date.parse(a)).toLocaleString(), }, { - title: 'Updated At', - dataIndex: 'updateTime', + title: "Updated At", + dataIndex: "updateTime", filters: [ { - text: 'Active', - value: 'a', + text: "Active", + value: "a", }, { - text: 'Inactive', - value: 'i', + text: "Inactive", + value: "i", }, ], onFilter: (v, r) => - v === 'a' + v === "a" ? (Date.now() - Date.parse(r.updateTime)) / 60000 < afkLimit : (Date.now() - Date.parse(r.updateTime)) / 60000 > afkLimit, sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime), @@ -416,7 +580,11 @@ export default class Dashboard extends Component { return ( {dS} @@ -425,15 +593,15 @@ export default class Dashboard extends Component { }, }, { - title: 'On Call', - dataIndex: 'onCall', + title: "On Call", + dataIndex: "onCall", filters: [ { - text: 'On Call', + text: "On Call", value: true, }, { - text: 'Not On Call', + text: "Not On Call", value: false, }, ], @@ -449,22 +617,23 @@ export default class Dashboard extends Component { const versionsColumns = [ { - title: 'Version', - dataIndex: 'version', - defaultSortOrder: 'descend', + title: "Version", + dataIndex: "version", + defaultSortOrder: "descend", sorter: (a, b) => - parseInt(a.version.split('.').join('')) - parseInt(b.version.split('.').join('')), + parseInt(a.version.split(".").join("")) - + parseInt(b.version.split(".").join("")), }, { - title: 'Active', - dataIndex: 'active', + title: "Active", + dataIndex: "active", filters: [ { - text: 'Active', + text: "Active", value: true, }, { - text: 'Inactive', + text: "Inactive", value: false, }, ], @@ -472,18 +641,22 @@ export default class Dashboard extends Component { render: (a, r) => ( this.updateVersionActive(c, r.version.split('.').join(''))} + onChange={(c) => + this.updateVersionActive(c, r.version.split(".").join("")) + } checked={a} /> ), }, { - title: 'Inactive Message', - dataIndex: 'message', + title: "Inactive Message", + dataIndex: "message", render: (a, r) => ( this.updateVersionMessage(v, r.version.split('.').join(''))} + onSearch={(v) => + this.updateVersionMessage(v, r.version.split(".").join("")) + } value={a} onChange={(e) => this.handleVersionMessageChange(e, r.version)} enterButton={} @@ -509,6 +682,145 @@ export default class Dashboard extends Component { , ]} /> + this.setState({ videoSelectorModal: false }), + }} + okButtonProps={{ + disabled: refreshing, + icon: , + onClick: this.addVideoSelector, + }} + okText="Add Version" + > + this.handleNewVideoSelectorChange({ service: e.target.value })} + /> + this.handleNewVideoSelectorChange({ selector: e.target.value })} + /> + + this.setState({ nameSelectorModal: false }), + }} + okButtonProps={{ + disabled: refreshing, + icon: , + onClick: this.addNameSelector, + }} + okText="Add Version" + > + this.handleNewNameSelectorChange({ service: e.target.value })} + /> + this.handleNewNameSelectorChange({ show: e.target.value })} + /> + this.handleNewNameSelectorChange({ episode: e.target.value })} + /> + + this.setState({ versionModal: false }), + }} + okButtonProps={{ + disabled: refreshing, + icon: , + onClick: this.addVersion, + }} + okText="Add Version" + > + this.handleNewVersionNumberChange(e.target.value)} + /> + this.handleNewVersionMessageChange(e.target.value)} + /> + + + + + this.handleNewVersionAlertTitleChange(e.target.value) + } + /> + + this.handleNewVersionAlertDescriptionChange(e.target.value) + } + /> + Link + + + this.handleNewVersionAlertLinkTextChange(e.target.value) + } + /> + + this.handleNewVersionAlertLinkChange(e.target.value) + } + /> + + + this.editMessage(e.target.value, 'endpoint')} - placeholder={newMessage.type === 'interactive/link' ? 'Link' : 'Endpoint'} + onChange={(e) => this.editMessage(e.target.value, "endpoint")} + placeholder={ + newMessage.type === "interactive/link" ? "Link" : "Endpoint" + } /> )} - {newMessage.type === 'interactive/link' && ( + {newMessage.type === "interactive/link" && ( this.editMessage(e.target.value, 'name')} + onChange={(e) => this.editMessage(e.target.value, "name")} placeholder="Link Text" /> )} @@ -563,7 +878,7 @@ export default class Dashboard extends Component { ), - activeTab === '1' && ( + activeTab === "1" && ( : } - disabled={activeTab === '1' && selectedParties.length === 0} + icon={ + activeTab === "1" ? : + } + disabled={activeTab === "1" && selectedParties.length === 0} > {this.getAddMessageText()} , - activeTab === '3' && ( + activeTab === "3" && (