Finished demo app
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
"@types/istanbul-lib-coverage": "^2.0.1",
|
"@types/istanbul-lib-coverage": "^2.0.1",
|
||||||
"@types/jest": "^24.9.1",
|
"@types/jest": "^24.9.1",
|
||||||
"@types/mocha": "^7.0.1",
|
"@types/mocha": "^7.0.1",
|
||||||
|
"@types/moment": "^2.13.0",
|
||||||
"@types/node": "^12.12.26",
|
"@types/node": "^12.12.26",
|
||||||
"@types/prop-types": "^15.7.3",
|
"@types/prop-types": "^15.7.3",
|
||||||
"@types/react": "^16.9.19",
|
"@types/react": "^16.9.19",
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"bootstrap": "^4.4.1",
|
"bootstrap": "^4.4.1",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
|
"moment": "^2.24.0",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-bootstrap": "^1.0.0-beta.16",
|
"react-bootstrap": "^1.0.0-beta.16",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export default class Dashboard extends React.Component<{
|
|||||||
this.getResponseDTO = this.getResponseDTO.bind(this);
|
this.getResponseDTO = this.getResponseDTO.bind(this);
|
||||||
this.getGraphEnabled = this.getGraphEnabled.bind(this);
|
this.getGraphEnabled = this.getGraphEnabled.bind(this);
|
||||||
this.setGraph = this.setGraph.bind(this);
|
this.setGraph = this.setGraph.bind(this);
|
||||||
|
this.logOut = this.logOut.bind(this);
|
||||||
this.getItemsFromApi();
|
this.getItemsFromApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +70,6 @@ export default class Dashboard extends React.Component<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
getItemsFromApi() {
|
getItemsFromApi() {
|
||||||
// TODO: Connect to ReportingRepository.
|
|
||||||
this.state.ReportingRepository.Read(this.state.dto).then((res) => {
|
this.state.ReportingRepository.Read(this.state.dto).then((res) => {
|
||||||
const response = new RESPONSE_DTO();
|
const response = new RESPONSE_DTO();
|
||||||
response.room_id = res.room_id;
|
response.room_id = res.room_id;
|
||||||
@@ -86,11 +86,27 @@ export default class Dashboard extends React.Component<{
|
|||||||
response.total_user_message_count = res.total_user_message_count;
|
response.total_user_message_count = res.total_user_message_count;
|
||||||
response.total_visitor_message_count = res.total_visitor_message_count;
|
response.total_visitor_message_count = res.total_visitor_message_count;
|
||||||
response.total_missed_chat_count = res.total_missed_chat_count;
|
response.total_missed_chat_count = res.total_missed_chat_count;
|
||||||
response.by_date = res.by_date;
|
response.by_date = this.sortData(res.by_date);
|
||||||
this.setState({response: response});
|
this.setState({response: response});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortData(arr: Array<Object>): Object[] {
|
||||||
|
// Sort data to coherently display in the Graph & Table.
|
||||||
|
// eslint-disable-next-line
|
||||||
|
arr.sort((a: any, b: any) => {
|
||||||
|
let dateA = new Date(a.date);
|
||||||
|
let dateB = new Date(b.date);
|
||||||
|
// Date A is greater than Date B.
|
||||||
|
if (dateA > dateB) return 1;
|
||||||
|
// Date A is lesser than Date B.
|
||||||
|
if (dateA < dateB) return -1;
|
||||||
|
// Date is eql, for stable sorting.
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
getResponseDTO() {
|
getResponseDTO() {
|
||||||
return this.state.response;
|
return this.state.response;
|
||||||
}
|
}
|
||||||
@@ -103,6 +119,10 @@ export default class Dashboard extends React.Component<{
|
|||||||
this.setState({ showGraph: !this.state.showGraph });
|
this.setState({ showGraph: !this.state.showGraph });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logOut() {
|
||||||
|
this.props.history.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (<DashboardView
|
return (<DashboardView
|
||||||
checkFirstDate={this.checkFirstDate}
|
checkFirstDate={this.checkFirstDate}
|
||||||
@@ -111,6 +131,7 @@ export default class Dashboard extends React.Component<{
|
|||||||
getResponseDTO={this.getResponseDTO}
|
getResponseDTO={this.getResponseDTO}
|
||||||
getGraphEnabled={this.getGraphEnabled}
|
getGraphEnabled={this.getGraphEnabled}
|
||||||
setGraph={this.setGraph}
|
setGraph={this.setGraph}
|
||||||
|
logOut={this.logOut}
|
||||||
/>)
|
/>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import LoginView from '../../Views/LoginView';
|
import LoginView from '../../Views/LoginView';
|
||||||
import API_DTO from '../../DTOs/api.dto';
|
import API_DTO from '../../DTOs/api.dto';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
export default class Login extends React.Component<{
|
export default class Login extends React.Component<{
|
||||||
history: any
|
history: any
|
||||||
@@ -25,7 +26,7 @@ export default class Login extends React.Component<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
verifyLogin() {
|
verifyLogin() {
|
||||||
localStorage.setItem("lastLoggedIn", new Date().toUTCString());
|
localStorage.setItem("lastLoggedIn", moment().format('LLLL'));
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
this.props.history.push({
|
this.props.history.push({
|
||||||
pathname: '/dashboard',
|
pathname: '/dashboard',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default class ReportingRepository {
|
|||||||
headers: {
|
headers: {
|
||||||
'Authorization': 'Token ' + t.getApiToken(),
|
'Authorization': 'Token ' + t.getApiToken(),
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card } from 'react-bootstrap';
|
||||||
|
|
||||||
|
export default class CardView extends React.Component<{
|
||||||
|
title: any,
|
||||||
|
content: any,
|
||||||
|
cssClassName?: any
|
||||||
|
}> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Card>
|
||||||
|
<Card.Header className={this.props.cssClassName} as="h6">{this.props.title}</Card.Header>
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Text>
|
||||||
|
{ this.props.content }
|
||||||
|
</Card.Text>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-21
@@ -1,9 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Container, Row, Col, FormControl, Button, InputGroup, Card } from 'react-bootstrap';
|
import { Container, Row, Col, FormControl, Button, InputGroup } from 'react-bootstrap';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faSync, faChartLine } from '@fortawesome/free-solid-svg-icons';
|
import { faSync, faChartLine } from '@fortawesome/free-solid-svg-icons';
|
||||||
import TableView from "./TableView";
|
import TableView from "./TableView";
|
||||||
import GraphView from "./GraphView";
|
import GraphView from "./GraphView";
|
||||||
|
import CardView from "./CardView";
|
||||||
|
import UserView from "./UserView";
|
||||||
|
|
||||||
export default class DashboardView extends React.Component<{
|
export default class DashboardView extends React.Component<{
|
||||||
checkFirstDate: any,
|
checkFirstDate: any,
|
||||||
@@ -12,6 +14,7 @@ export default class DashboardView extends React.Component<{
|
|||||||
getResponseDTO: any,
|
getResponseDTO: any,
|
||||||
getGraphEnabled: any,
|
getGraphEnabled: any,
|
||||||
setGraph: any
|
setGraph: any
|
||||||
|
logOut: any
|
||||||
}> {
|
}> {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
@@ -20,7 +23,10 @@ export default class DashboardView extends React.Component<{
|
|||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<br />
|
<br />
|
||||||
<h3>DASHBOARD</h3>
|
<h3 className="center">DASHBOARD</h3>
|
||||||
|
<UserView
|
||||||
|
logOut={this.props.logOut}
|
||||||
|
/>
|
||||||
<hr />
|
<hr />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -63,28 +69,25 @@ export default class DashboardView extends React.Component<{
|
|||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<Card>
|
<CardView
|
||||||
<Card.Header as="h6">Total Conversation Count</Card.Header>
|
title="Total Conversation Count"
|
||||||
<Card.Body>
|
content={ this.props.getResponseDTO().total_conversation_count }
|
||||||
<Card.Text>{ this.props.getResponseDTO().total_conversation_count }</Card.Text>
|
cssClassName="bg-card-1"
|
||||||
</Card.Body>
|
/>
|
||||||
</Card>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<Card>
|
<CardView
|
||||||
<Card.Header as="h6">Total User Message Count</Card.Header>
|
title="Total User Message Count"
|
||||||
<Card.Body>
|
content={ this.props.getResponseDTO().total_user_message_count }
|
||||||
<Card.Text>{ this.props.getResponseDTO().total_user_message_count }</Card.Text>
|
cssClassName="bg-card-2"
|
||||||
</Card.Body>
|
/>
|
||||||
</Card>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<Card>
|
<CardView
|
||||||
<Card.Header as="h6">Total Visitor Message Count</Card.Header>
|
title="Total Visitor Message Count"
|
||||||
<Card.Body>
|
content={ this.props.getResponseDTO().total_visitor_message_count }
|
||||||
<Card.Text>{ this.props.getResponseDTO().total_visitor_message_count }</Card.Text>
|
cssClassName="bg-card-3"
|
||||||
</Card.Body>
|
/>
|
||||||
</Card>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
@@ -123,7 +126,7 @@ export default class DashboardView extends React.Component<{
|
|||||||
<Col>
|
<Col>
|
||||||
<br />
|
<br />
|
||||||
<hr />
|
<hr />
|
||||||
<h6>Made with ❤ by Jeroen Vijgen </h6>
|
<h6 className="center">Made with ❤ by Jeroen Vijgen </h6>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -18,20 +18,6 @@ export default class GraphView extends React.Component<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseData(): Array<IData>{
|
parseData(): Array<IData>{
|
||||||
// Sort data to coherently display in the Graph.
|
|
||||||
// eslint-disable-next-line
|
|
||||||
this.props.data.sort((a: any, b: any) => {
|
|
||||||
let dateA = new Date(a.date);
|
|
||||||
let dateB = new Date(b.date);
|
|
||||||
let same = dateA.getTime() === dateB.getTime();
|
|
||||||
// Date is eql, ignore sorting to stablize sorting.
|
|
||||||
if (same) return 0;
|
|
||||||
// Date A is greater than Date B.
|
|
||||||
if (dateA > dateB) return 1;
|
|
||||||
// Date A is lesser than Date B.
|
|
||||||
if (dateA < dateB) return -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Object needing to be returned:
|
// Object needing to be returned:
|
||||||
// { id: "missed_chat_count", data: [{x: int (DATE), y: int (Count)}] }
|
// { id: "missed_chat_count", data: [{x: int (DATE), y: int (Count)}] }
|
||||||
// { id: "conversation_count", data: [{x: int (DATE), y: int (Count)}] }
|
// { id: "conversation_count", data: [{x: int (DATE), y: int (Count)}] }
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, Button, FormControl } from 'react-bootstrap';
|
import { Card, Button, FormControl } from 'react-bootstrap';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
export default class LoginView extends React.Component<{
|
export default class LoginView extends React.Component<{
|
||||||
persistApiLink: any,
|
persistApiLink: any,
|
||||||
@@ -31,7 +33,7 @@ export default class LoginView extends React.Component<{
|
|||||||
onChange={(e: any) => this.props.persistAccessToken(e)}
|
onChange={(e: any) => this.props.persistAccessToken(e)}
|
||||||
/>
|
/>
|
||||||
</Card.Text>
|
</Card.Text>
|
||||||
<Button variant="primary" onClick={this.props.verifyLogin}>Login </Button>
|
<Button variant="primary" onClick={this.props.verifyLogin}>Login <FontAwesomeIcon icon={faSignInAlt} /></Button>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
{ localStorage.getItem("lastLoggedIn") !== null &&
|
{ localStorage.getItem("lastLoggedIn") !== null &&
|
||||||
<Card.Footer className="text-muted">Your last login was { localStorage.getItem("lastLoggedIn") }</Card.Footer>
|
<Card.Footer className="text-muted">Your last login was { localStorage.getItem("lastLoggedIn") }</Card.Footer>
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export default class TableView extends React.Component<{
|
|||||||
keyField='date'
|
keyField='date'
|
||||||
columns={ this.props.columns }
|
columns={ this.props.columns }
|
||||||
data={ this.props.data }
|
data={ this.props.data }
|
||||||
|
striped
|
||||||
|
hover
|
||||||
|
condensed
|
||||||
pagination={ paginationFactory({
|
pagination={ paginationFactory({
|
||||||
pageStartIndex: 0,
|
pageStartIndex: 0,
|
||||||
showTotal: true,
|
showTotal: true,
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from 'react-bootstrap';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export default class UserView extends React.Component<{
|
||||||
|
logOut: any
|
||||||
|
}> {
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<span className="center-span">
|
||||||
|
<b> Hi user123 </b> | Your last login was on: { localStorage.getItem("lastLoggedIn") || "" } | <Button variant="danger" onClick={this.props.logOut}> Log out <FontAwesomeIcon icon={faSignOutAlt} /> </Button>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-1
@@ -17,7 +17,7 @@ body {
|
|||||||
|
|
||||||
.LoginComponent,
|
.LoginComponent,
|
||||||
.DashboardComponent {
|
.DashboardComponent {
|
||||||
background-color: white;
|
background-color: whitesmoke;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: #FD4114;
|
border-color: #FD4114;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
@@ -30,3 +30,24 @@ body {
|
|||||||
.GraphView {
|
.GraphView {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-card-1 {
|
||||||
|
background-color: #ffc1074d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-card-2 {
|
||||||
|
background-color: #dc35455c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-card-3 {
|
||||||
|
background-color: #28a74559;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-span {
|
||||||
|
display: table;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
@@ -1384,6 +1384,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.3.0"
|
"@babel/types" "^7.3.0"
|
||||||
|
|
||||||
|
"@types/datejs@^0.0.30":
|
||||||
|
version "0.0.30"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/datejs/-/datejs-0.0.30.tgz#590c625c8a962b253f4b8059f96891c957d30bde"
|
||||||
|
integrity sha512-UM5Nf0spXBPoT0NXJvkLyTAyRKTVNF5n8eSQoo1XA4qNAFa7lIC48O0JK4nkzIrWi9f2TmJUvxP5G1yrjGRVHQ==
|
||||||
|
|
||||||
"@types/eslint-visitor-keys@^1.0.0":
|
"@types/eslint-visitor-keys@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||||
@@ -1450,6 +1455,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.1.tgz#5d7ec2a789a1f77c59b7ad071b9d50bf1abbfc9e"
|
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.1.tgz#5d7ec2a789a1f77c59b7ad071b9d50bf1abbfc9e"
|
||||||
integrity sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q==
|
integrity sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q==
|
||||||
|
|
||||||
|
"@types/moment@^2.13.0":
|
||||||
|
version "2.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/moment/-/moment-2.13.0.tgz#604ebd189bc3bc34a1548689404e61a2a4aac896"
|
||||||
|
integrity sha1-YE69GJvDvDShVIaJQE5hoqSqyJY=
|
||||||
|
dependencies:
|
||||||
|
moment "*"
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "13.7.4"
|
version "13.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.4.tgz#76c3cb3a12909510f52e5dc04a6298cdf9504ffd"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.4.tgz#76c3cb3a12909510f52e5dc04a6298cdf9504ffd"
|
||||||
@@ -3506,6 +3518,11 @@ data-urls@^1.0.0, data-urls@^1.1.0:
|
|||||||
whatwg-mimetype "^2.2.0"
|
whatwg-mimetype "^2.2.0"
|
||||||
whatwg-url "^7.0.0"
|
whatwg-url "^7.0.0"
|
||||||
|
|
||||||
|
datejs@^1.0.0-rc3:
|
||||||
|
version "1.0.0-rc3"
|
||||||
|
resolved "https://registry.yarnpkg.com/datejs/-/datejs-1.0.0-rc3.tgz#bffa1efedefeb41fdd8a242af55afa01fb58de57"
|
||||||
|
integrity sha1-v/oe/t7+tB/diiQq9Vr6AftY3lc=
|
||||||
|
|
||||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
|
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
@@ -6958,6 +6975,11 @@ mkdirp@0.5.1, mkdirp@^0.5.1, mkdirp@~0.5.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
|
moment@*, moment@^2.24.0:
|
||||||
|
version "2.24.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||||
|
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||||
|
|
||||||
move-concurrently@^1.0.1:
|
move-concurrently@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||||
|
|||||||
Reference in New Issue
Block a user