v1.6.2/admin dashboard
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
Generated
+14429
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "watch-party-admin",
|
||||
"version": "1.6.2",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.2.2",
|
||||
"antd": "^4.5.4",
|
||||
"firebase": "^7.18.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-scripts": "3.4.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Watch Party Admin Dashboard" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Watch Party | Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "WP | Admin",
|
||||
"name": "Watch Party Admin Dashboard",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#c0f300",
|
||||
"background_color": "#000000"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import firebase from 'firebase/app';
|
||||
import 'firebase/auth';
|
||||
|
||||
const API = 'http://localhost:5000/watch-party-crx/us-central1/admin';
|
||||
|
||||
const fetcher = async ({ endpoint, method, bodyData }) => {
|
||||
const idToken = await firebase.auth().currentUser.getIdToken();
|
||||
const body = JSON.stringify(bodyData);
|
||||
return fetch(`${API}/${endpoint}`, {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body,
|
||||
});
|
||||
};
|
||||
|
||||
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 });
|
||||
export const postUpdateVideoSelectors = (bodyData) =>
|
||||
fetcher({ endpoint: 'updateVideoSelectors', method: 'POST', bodyData });
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Component } from 'react';
|
||||
import Login from './Login';
|
||||
import Dashboard from './Dashboard';
|
||||
import firebase from 'firebase/app';
|
||||
import 'firebase/auth';
|
||||
|
||||
export default class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
auth: false,
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
firebase.auth().onAuthStateChanged((authUser) => {
|
||||
if (authUser && !authUser.isAnonymous) {
|
||||
this.setState({ auth: true });
|
||||
} else {
|
||||
this.setState({ auth: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const { auth } = this.state;
|
||||
return <div className="app-wrapper">{auth ? <Dashboard /> : <Login />}</div>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
import React, { Component } from 'react';
|
||||
import firebase from 'firebase/app';
|
||||
import 'firebase/auth';
|
||||
import 'firebase/firestore';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Table,
|
||||
Tabs,
|
||||
Tag,
|
||||
Switch,
|
||||
Input,
|
||||
notification,
|
||||
Divider,
|
||||
Row,
|
||||
Col,
|
||||
PageHeader,
|
||||
} from 'antd';
|
||||
import {
|
||||
getPartyDashboardData,
|
||||
getServerDashboardData,
|
||||
postUpdateVersionActive,
|
||||
postUpdateVideoSelectors,
|
||||
} from './Api';
|
||||
import {
|
||||
CheckCircleTwoTone,
|
||||
CloseCircleTwoTone,
|
||||
ReloadOutlined,
|
||||
LoadingOutlined,
|
||||
EnterOutlined,
|
||||
DeleteOutlined,
|
||||
PlusCircleFilled,
|
||||
SaveFilled,
|
||||
SaveOutlined,
|
||||
LogoutOutlined,
|
||||
SendOutlined,
|
||||
PlusCircleOutlined,
|
||||
} 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.' },
|
||||
];
|
||||
|
||||
const partiesColumns = [
|
||||
{
|
||||
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),
|
||||
filters: PLAYER_SOURCES,
|
||||
onFilter: (v, r) => r.source === v,
|
||||
render: (a) => PLAYER_SOURCES.filter((p) => p.value === a)[0].text,
|
||||
},
|
||||
{
|
||||
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',
|
||||
sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
|
||||
render: (a) => {
|
||||
const minutes = (Date.now() - Date.parse(a)) / 60000;
|
||||
const dS = new Date(a).toLocaleString();
|
||||
return (
|
||||
<Tag color={minutes <= 5 ? 'success' : minutes <= 15 ? 'processing' : 'default'}>{dS}</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'On Call',
|
||||
dataIndex: 'onCall',
|
||||
filters: [
|
||||
{
|
||||
text: 'On Call',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
text: 'Not On Call',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
render: (a) =>
|
||||
a ? (
|
||||
<CheckCircleTwoTone color="#c0f300" id="call-icon" />
|
||||
) : (
|
||||
<CloseCircleTwoTone id="call-icon" />
|
||||
),
|
||||
onFilter: (v, r) => r.onCall === v,
|
||||
},
|
||||
];
|
||||
|
||||
export default class Dashboard extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
partiesOverview: null,
|
||||
activeTab: '1',
|
||||
refreshing: true,
|
||||
selectedParties: [],
|
||||
nameSelectors: {},
|
||||
selectors: {},
|
||||
versions: [],
|
||||
};
|
||||
this.serverStatusListener = null;
|
||||
}
|
||||
componentDidMount() {
|
||||
this.getPartiesOverview();
|
||||
this.getVersionsOverview();
|
||||
}
|
||||
getPartiesOverview = async () => {
|
||||
getPartyDashboardData()
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
this.setState({ partiesOverview: res.partyData, refreshing: false });
|
||||
});
|
||||
};
|
||||
getVersionsOverview = () => {
|
||||
getServerDashboardData()
|
||||
.then((res) => res.json())
|
||||
.then((res) => this.setState({ ...res.serverData, refreshing: false }));
|
||||
};
|
||||
refreshData = () => {
|
||||
this.setState({ refreshing: true });
|
||||
const { activeTab } = this.state;
|
||||
switch (activeTab) {
|
||||
case '1':
|
||||
this.getPartiesOverview();
|
||||
break;
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
this.getVersionsOverview();
|
||||
break;
|
||||
}
|
||||
};
|
||||
tabChange = (activeTab) => {
|
||||
this.setState({ activeTab });
|
||||
};
|
||||
updateVersionMessage = (message, version) => {
|
||||
this.setState({ refreshing: true });
|
||||
const { versions } = this.state;
|
||||
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];
|
||||
versionData.active = active;
|
||||
this.updateVersion(version, versionData);
|
||||
};
|
||||
updateVersion = (version, versionData) => {
|
||||
postUpdateVersionActive({ version, versionData })
|
||||
.then(() => this.refreshData())
|
||||
.catch((err) =>
|
||||
notification.error({
|
||||
description: err.message,
|
||||
message: 'Unable To Update',
|
||||
})
|
||||
);
|
||||
};
|
||||
viewPartyDetails(party) {
|
||||
console.log(party);
|
||||
}
|
||||
getSourceName(source) {
|
||||
return PLAYER_SOURCES.filter((p) => p.value === source)[0].text;
|
||||
}
|
||||
handleVideoSelectorChange = (e, s) => {
|
||||
const { selectors } = this.state;
|
||||
selectors[s] = e.target.value;
|
||||
this.setState({ selectors });
|
||||
};
|
||||
updateVideoSelector = () => {
|
||||
this.setState({ refreshing: true });
|
||||
const { selectors } = this.state;
|
||||
postUpdateVideoSelectors({ selectors })
|
||||
.then(() => this.refreshData())
|
||||
.catch((err) => {
|
||||
notification.error({
|
||||
description: err.message,
|
||||
message: 'Unable To Update',
|
||||
});
|
||||
|
||||
this.setState({ refreshing: false });
|
||||
});
|
||||
};
|
||||
handleVersionMessageChange = (e, v) => {
|
||||
const { versions } = this.state;
|
||||
versions.filter((vs) => vs.version === v)[0].message = e.target.value;
|
||||
this.setState({ versions });
|
||||
};
|
||||
handleAlertTitleChange = (value, version, index) => {
|
||||
const { versions } = this.state;
|
||||
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;
|
||||
this.setState({ versions });
|
||||
};
|
||||
handleAlertLinkChange = (value, version, index) => {
|
||||
const { versions } = this.state;
|
||||
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;
|
||||
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].alerts.length < index
|
||||
) {
|
||||
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: '',
|
||||
});
|
||||
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);
|
||||
else {
|
||||
notification.error({
|
||||
message: 'Missing Alerts',
|
||||
description: 'Must have atleast one alert',
|
||||
});
|
||||
|
||||
this.setState({ refreshing: false });
|
||||
}
|
||||
};
|
||||
changeImportantAlert = (active, version, index) => {
|
||||
const { versions } = this.state;
|
||||
if (active === false) {
|
||||
versions.filter((vs) => vs.version === version)[0].importantAlert = undefined;
|
||||
} else {
|
||||
versions.filter((vs) => vs.version === version)[0].importantAlert = index;
|
||||
}
|
||||
this.setState({ versions });
|
||||
};
|
||||
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';
|
||||
}
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
partiesOverview,
|
||||
activeTab,
|
||||
refreshing,
|
||||
selectedParties,
|
||||
versions,
|
||||
nameSelectors,
|
||||
selectors,
|
||||
} = this.state;
|
||||
|
||||
const versionsColumns = [
|
||||
{
|
||||
title: 'Version',
|
||||
dataIndex: 'version',
|
||||
defaultSortOrder: 'descend',
|
||||
sorter: (a, b) =>
|
||||
parseInt(a.version.split('.').join('')) - parseInt(b.version.split('.').join('')),
|
||||
},
|
||||
{
|
||||
title: 'Active',
|
||||
dataIndex: 'active',
|
||||
filters: [
|
||||
{
|
||||
text: 'Active',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
text: 'Inactive',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
onFilter: (v, r) => r.active === v,
|
||||
render: (a, r) => (
|
||||
<Switch
|
||||
disabled={refreshing}
|
||||
onChange={(c) => this.updateVersionActive(c, r.version.split('.').join(''))}
|
||||
checked={a}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Inactive Message',
|
||||
dataIndex: 'message',
|
||||
render: (a, r) => (
|
||||
<Input.Search
|
||||
disabled={refreshing}
|
||||
onSearch={(v) => this.updateVersionMessage(v, r.version.split('.').join(''))}
|
||||
value={a}
|
||||
onChange={(e) => this.handleVersionMessageChange(e, r.version)}
|
||||
enterButton={<EnterOutlined />}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="dashboard-wrapper">
|
||||
<Card>
|
||||
<PageHeader
|
||||
extra={[
|
||||
<Button
|
||||
key="3"
|
||||
loading={refreshing}
|
||||
disabled={refreshing}
|
||||
onClick={this.refreshData}
|
||||
shape="circle"
|
||||
icon={refreshing ? <LoadingOutlined /> : <ReloadOutlined />}
|
||||
/>,
|
||||
<Button
|
||||
key="2"
|
||||
icon={activeTab === '1' ? <SendOutlined /> : <PlusCircleOutlined />}
|
||||
disabled={activeTab === '1' && selectedParties.length === 0}
|
||||
>
|
||||
{this.getAddMessageText()}
|
||||
</Button>,
|
||||
<Button
|
||||
onClick={() => firebase.auth().signOut()}
|
||||
key="1"
|
||||
type="primary"
|
||||
danger
|
||||
icon={<LogoutOutlined />}
|
||||
>
|
||||
Logout
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
<Tabs activeKey={activeTab} onChange={this.tabChange}>
|
||||
<Tabs.TabPane tab="Parties" key="1">
|
||||
<Table
|
||||
rowSelection={{
|
||||
selectedRowKeys: selectedParties,
|
||||
onChange: (selectedParties) => this.setState({ selectedParties }),
|
||||
}}
|
||||
loading={partiesOverview === null || refreshing}
|
||||
pagination={{
|
||||
position: ['bottomCenter'],
|
||||
total: partiesOverview ? partiesOverview.length : 0,
|
||||
showTotal: (t, r) => `${r[0]} to ${r[1]} of ${t}`,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: [10, 25, 50],
|
||||
}}
|
||||
rowKey={(p) => p.id}
|
||||
onRow={(r) => ({
|
||||
onClick: () => this.viewPartyDetails(r),
|
||||
})}
|
||||
dataSource={partiesOverview}
|
||||
columns={partiesColumns}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Versions" key="2">
|
||||
<Table
|
||||
loading={refreshing}
|
||||
pagination={{ position: ['bottomCenter'] }}
|
||||
rowKey={(p) => p.version}
|
||||
dataSource={versions}
|
||||
columns={versionsColumns}
|
||||
expandable={{
|
||||
rowExpandable: (r) => r.alerts,
|
||||
expandedRowClassName: () => 'alerts-wrapper',
|
||||
expandedRowRender: (r) => (
|
||||
<Row>
|
||||
{r.alerts.map((a, i) => (
|
||||
<Col key={`${r.version}-alerts-${i}`}>
|
||||
<Card
|
||||
actions={[
|
||||
<Button
|
||||
onClick={() => this.deleteAlert(r.version, i)}
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
shape="circle"
|
||||
color="danger"
|
||||
/>,
|
||||
<Switch
|
||||
onClick={(active) =>
|
||||
this.changeImportantAlert(active, r.version, i)
|
||||
}
|
||||
checked={r.importantAlert && r.importantAlert === i}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="title"
|
||||
value={a.title}
|
||||
onChange={(e) =>
|
||||
this.handleAlertTitleChange(e.target.value, r.version, i)
|
||||
}
|
||||
/>
|
||||
<Input.TextArea
|
||||
placeholder="Description"
|
||||
autoSize={{ minRows: 2, maxRows: 2 }}
|
||||
value={a.description}
|
||||
onChange={(e) =>
|
||||
this.handleAlertDescriptionChange(e.target.value, r.version, i)
|
||||
}
|
||||
/>
|
||||
<Divider orientation="left">Link</Divider>
|
||||
<Input.Group>
|
||||
<Input
|
||||
value={a.linkText}
|
||||
placeholder="Link Text"
|
||||
onChange={(e) =>
|
||||
this.handleAlertLinkTextChange(e.target.value, r.version, i)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
value={a.link}
|
||||
placeholder="Link"
|
||||
onChange={(e) =>
|
||||
this.handleAlertLinkChange(e.target.value, r.version, i)
|
||||
}
|
||||
/>
|
||||
</Input.Group>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
<Col>
|
||||
<Card
|
||||
onClick={() => this.addEmptyAlert(r.version)}
|
||||
className="add-alert-card"
|
||||
>
|
||||
<PlusCircleFilled />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card onClick={() => this.saveAlerts(r.version)} className="add-alert-card">
|
||||
<SaveFilled />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Video Selectors" key="3">
|
||||
{Object.keys(selectors)
|
||||
.sort()
|
||||
.map((source) => (
|
||||
<Input
|
||||
key={`video-selector-${source}`}
|
||||
className="video-selector-input"
|
||||
addonBefore={this.getSourceName(source)}
|
||||
value={selectors[source]}
|
||||
onChange={(e) => this.handleVideoSelectorChange(e, source)}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
block
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
className="save-video-selector"
|
||||
onClick={this.updateVideoSelector}
|
||||
loading={refreshing}
|
||||
>
|
||||
Save Video Selectors
|
||||
</Button>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Name Selectors" key="4">
|
||||
name selectors
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Card, Space, Input, Button, notification } from 'antd';
|
||||
import { UnlockFilled } from '@ant-design/icons';
|
||||
import firebase from 'firebase/app';
|
||||
import 'firebase/auth';
|
||||
|
||||
export default class Login extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
email: '',
|
||||
password: '',
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
login = async () => {
|
||||
const { email, password } = this.state;
|
||||
this.setState({ loading: true });
|
||||
await firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION);
|
||||
firebase
|
||||
.auth()
|
||||
.signInWithEmailAndPassword(email, password)
|
||||
.catch((err) => {
|
||||
this.setState({ loading: false });
|
||||
notification.error({
|
||||
message: 'Failed Login',
|
||||
description: err.message,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
});
|
||||
};
|
||||
render() {
|
||||
const { loading, email, password } = this.state;
|
||||
return (
|
||||
<div className="login-card-wrapper">
|
||||
<Card
|
||||
title="Login"
|
||||
actions={[
|
||||
<Button onClick={this.login} icon={<UnlockFilled key="login" />} loading={loading}>
|
||||
LOGIN
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Space direction="vertical">
|
||||
<Input
|
||||
value={email}
|
||||
onChange={(e) => this.setState({ email: e.target.value })}
|
||||
placeholder="E-Mail"
|
||||
type="email"
|
||||
/>
|
||||
<Input.Password
|
||||
onPressEnter={this.login}
|
||||
value={password}
|
||||
onChange={(e) => this.setState({ password: e.target.value })}
|
||||
placeholder="Password"
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
@import '~antd/dist/antd.css';
|
||||
@import '~antd/dist/antd.dark.css' screen and (prefers-color-scheme: dark);
|
||||
|
||||
#root,
|
||||
#root .app-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.login-card-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.login-card-wrapper .ant-card {
|
||||
border-radius: 26px;
|
||||
transition: all ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
.login-card-wrapper .ant-card:hover {
|
||||
filter: drop-shadow(0px 0px 50px rgba(170, 170, 170, 0.2));
|
||||
}
|
||||
|
||||
.login-card-wrapper .ant-card-actions {
|
||||
border-radius: 26px;
|
||||
border-top-right-radius: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
}
|
||||
|
||||
.dashboard-wrapper .ant-card {
|
||||
margin: 35px 70px;
|
||||
border-radius: 26px;
|
||||
filter: drop-shadow(0px 0px 5px rgba(221, 221, 221, 0.438));
|
||||
}
|
||||
|
||||
.ant-table-content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.video-selector-input {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.alerts-wrapper {
|
||||
max-height: 300px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.alerts-wrapper .ant-card {
|
||||
margin: 5px 2px !important;
|
||||
filter: none !important;
|
||||
border-radius: initial !important;
|
||||
}
|
||||
|
||||
.alerts-wrapper .ant-card-body input,
|
||||
.ant-card-body textarea {
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.alerts-wrapper .add-alert-card {
|
||||
width: calc(400px - 2px);
|
||||
height: calc(400px - 5px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.alerts-wrapper .add-alert-card svg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.save-video-selector {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
span#call-icon svg {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App.jsx';
|
||||
import firebase from 'firebase/app';
|
||||
import './index.css';
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: 'AIzaSyCJLuYpzXLDRmu2ttzFuSGmYcpFxqToWFU',
|
||||
authDomain: 'watch-party-crx.firebaseapp.com',
|
||||
databaseURL: 'https://watch-party-crx.firebaseio.com',
|
||||
projectId: 'watch-party-crx',
|
||||
storageBucket: 'watch-party-crx.appspot.com',
|
||||
messagingSenderId: '712242707190',
|
||||
appId: '1:712242707190:web:65fa7f772e30da364b0ad7',
|
||||
measurementId: 'G-EZS4PEVG4T',
|
||||
};
|
||||
|
||||
firebase.initializeApp(firebaseConfig);
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
Reference in New Issue
Block a user