mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
community: convert realtime poll sample app to hooks (close #7675)
GITHUB_PR_NUMBER: 7694 GITHUB_PR_URL: https://github.com/hasura/graphql-engine/pull/7694 PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2620 Co-authored-by: andykcom <532952+andykcom@users.noreply.github.com> GitOrigin-RevId: 428d9a17468f18376957963a152afb11d176a39c
This commit is contained in:
parent
44977bdf9d
commit
0e6bcfdba4
@ -16,12 +16,13 @@ hosted on GitHub pages and the Postgres+GraphQL Engine is running on Postgres.
|
|||||||
- Checkout the [live app](https://realtime-poll.demo.hasura.app/).
|
- Checkout the [live app](https://realtime-poll.demo.hasura.app/).
|
||||||
- Explore the backend using [Hasura
|
- Explore the backend using [Hasura
|
||||||
Console](https://realtime-poll.hasura.app/console).
|
Console](https://realtime-poll.hasura.app/console).
|
||||||
|
|
||||||
# Running the app yourself
|
# Running the app yourself
|
||||||
|
|
||||||
- Deploy GraphQL Engine on Hasura Cloud and setup PostgreSQL via Heroku:
|
- Deploy GraphQL Engine on Hasura Cloud and setup PostgreSQL via Heroku:
|
||||||
|
|
||||||
[![Deploy to Hasura Cloud](https://graphql-engine-cdn.hasura.io/img/deploy_to_hasura.png)](https://cloud.hasura.io/signup)
|
[![Deploy to Hasura Cloud](https://graphql-engine-cdn.hasura.io/img/deploy_to_hasura.png)](https://cloud.hasura.io/signup)
|
||||||
|
|
||||||
- Get the Hasura app URL (say `realtime-poll.hasura.app`)
|
- Get the Hasura app URL (say `realtime-poll.hasura.app`)
|
||||||
- Clone this repo:
|
- Clone this repo:
|
||||||
```bash
|
```bash
|
||||||
@ -39,10 +40,10 @@ hosted on GitHub pages and the Postgres+GraphQL Engine is running on Postgres.
|
|||||||
hasura migrate apply
|
hasura migrate apply
|
||||||
hasura metadata reload
|
hasura metadata reload
|
||||||
```
|
```
|
||||||
- Edit `HASURA_GRAPHQL_ENGINE_HOSTNAME` in `src/apollo.js` and set it to the
|
- Edit `GRAPHQL_ENDPOINT` in `src/apollo.js` and set it to the
|
||||||
Hasura app URL:
|
Hasura app URL:
|
||||||
```js
|
```js
|
||||||
export const HASURA_GRAPHQL_ENGINE_HOSTNAME = 'realtime-poll.hasura.app';
|
export const GRAPHQL_ENDPOINT = "realtime-poll.hasura.app";
|
||||||
```
|
```
|
||||||
- Run the app (go the root of repo):
|
- Run the app (go the root of repo):
|
||||||
```bash
|
```bash
|
||||||
|
@ -3,21 +3,14 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apollo-boost": "^0.4.7",
|
"@apollo/client": "^3.4.16",
|
||||||
"apollo-cache-inmemory": "^1.2.7",
|
"graphql": "^15.6.0",
|
||||||
"apollo-client": "^2.3.8",
|
"react": "^17.0.2",
|
||||||
"apollo-link": "^1.2.2",
|
|
||||||
"apollo-link-http": "^1.5.4",
|
|
||||||
"apollo-link-ws": "^1.0.8",
|
|
||||||
"apollo-utilities": "^1.0.18",
|
|
||||||
"graphql": "^15.0.0",
|
|
||||||
"react": "^16.4.2",
|
|
||||||
"react-apollo": "^3.1.4",
|
|
||||||
"react-bootstrap": "^1.0.0",
|
"react-bootstrap": "^1.0.0",
|
||||||
"react-dom": "^16.4.2",
|
"react-dom": "^16.4.2",
|
||||||
"react-google-charts": "^3.0.5",
|
"react-google-charts": "^3.0.5",
|
||||||
"react-scripts": "^3.4.1",
|
"react-scripts": "^3.4.1",
|
||||||
"subscriptions-transport-ws": "^0.9.14"
|
"subscriptions-transport-ws": "^0.9.19"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
@ -3,22 +3,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.App-logo {
|
.App-logo {
|
||||||
/* animation: App-logo-spin infinite 20s linear;
|
|
||||||
height: 80px; */
|
|
||||||
width: 90px;
|
width: 90px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 50px;
|
margin-right: 50px;
|
||||||
}
|
}
|
||||||
.displayFlex
|
.displayFlex {
|
||||||
{
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.hasura-logo a:hover {
|
.hasura-logo a:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.hasura-logo img {
|
.hasura-logo img {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
.App-header {
|
.App-header {
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
@ -71,13 +68,17 @@ pre {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@keyframes App-logo-spin {
|
@keyframes App-logo-spin {
|
||||||
from { transform: rotate(0deg); }
|
from {
|
||||||
to { transform: rotate(360deg); }
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-small-text {
|
.footer-small-text {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.online-users {
|
.online-users {
|
||||||
@ -87,22 +88,22 @@ pre {
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
.online-users .alert {
|
.online-users .alert {
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-result-chart-container {
|
.poll-result-chart-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 350px;
|
min-height: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.displayFlex {
|
.displayFlex {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasura-logo img {
|
.hasura-logo img {
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import logo from './img/logo-white.svg';
|
|
||||||
import './App.css';
|
|
||||||
import { ApolloProvider } from 'react-apollo';
|
|
||||||
import client from './apollo';
|
|
||||||
import Poll from './Poll';
|
|
||||||
import { getUserId } from './session';
|
|
||||||
import { GraphQL } from './GraphQL';
|
|
||||||
import { Users } from './Users';
|
|
||||||
|
|
||||||
|
|
||||||
class App extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = { loading: true, userId: '' };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
getUserId().then((userId) => {
|
|
||||||
this.setState({ loading: false, userId });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
// if (this.state.loading) return <p>Loading...</p>;
|
|
||||||
return (
|
|
||||||
<ApolloProvider client={client}>
|
|
||||||
<div className="App">
|
|
||||||
|
|
||||||
<header className="App-header displayFlex">
|
|
||||||
<div className="container displayFlex">
|
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
|
||||||
<h1 className="App-title">Realtime Poll</h1>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<Users />
|
|
||||||
|
|
||||||
<Poll userId={this.state.userId} />
|
|
||||||
|
|
||||||
<GraphQL />
|
|
||||||
|
|
||||||
<footer className="App-footer displayFlex">
|
|
||||||
<div className="container hasura-logo">
|
|
||||||
<a href="https://hasura.io" target="_blank" rel="noopener noreferrer">
|
|
||||||
<img className="hasura-logo" alt="hasura logo" src="https://graphql-engine-cdn.hasura.io/img/powered_by_hasura_black_200px.png" />
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href="https://realtime-poll.hasura.app/console" target="_blank">
|
|
||||||
Backend
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href="https://github.com/hasura/graphql-engine/tree/master/community/sample-apps/realtime-poll" target="_blank" rel="noopener noreferrer">
|
|
||||||
Source
|
|
||||||
</a>
|
|
||||||
<div className="footer-small-text"><span>(The database resets every 24 hours)</span></div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</ApolloProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
37
community/sample-apps/realtime-poll/src/App.jsx
Normal file
37
community/sample-apps/realtime-poll/src/App.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ApolloProvider } from "@apollo/client";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import client from "./apollo";
|
||||||
|
import "./App.css";
|
||||||
|
import { Footer, Header } from "./Components";
|
||||||
|
import { GraphQLQueryList } from "./GraphQL";
|
||||||
|
import { Poll } from "./Poll";
|
||||||
|
import { getUserId } from "./session";
|
||||||
|
import { Users } from "./Users";
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const defaultState = { loading: true, userId: "" };
|
||||||
|
const [state, setState] = useState(defaultState);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchUserId = async () => {
|
||||||
|
const userId = await getUserId();
|
||||||
|
setState({ loading: false, userId });
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchUserId();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ApolloProvider client={client}>
|
||||||
|
<div className="App">
|
||||||
|
<Header />
|
||||||
|
<Users />
|
||||||
|
<Poll userId={state.userId} />
|
||||||
|
<GraphQLQueryList />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</ApolloProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
49
community/sample-apps/realtime-poll/src/Components.jsx
Normal file
49
community/sample-apps/realtime-poll/src/Components.jsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from "react";
|
||||||
|
import logo from "./img/logo-white.svg";
|
||||||
|
|
||||||
|
// State indicator components
|
||||||
|
export const Loading = () => <div>Loading...</div>;
|
||||||
|
export const Error = ({ message }) => (
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<b>Error:</b> {message}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Layout components
|
||||||
|
export const Header = () => (
|
||||||
|
<header className="App-header displayFlex">
|
||||||
|
<div className="container displayFlex">
|
||||||
|
<img src={logo} className="App-logo" alt="logo" />
|
||||||
|
<h1 className="App-title">Realtime Poll</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Footer = () => (
|
||||||
|
<footer className="App-footer displayFlex">
|
||||||
|
<div className="container hasura-logo">
|
||||||
|
<a href="https://hasura.io" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img
|
||||||
|
className="hasura-logo"
|
||||||
|
alt="hasura logo"
|
||||||
|
src="https://graphql-engine-cdn.hasura.io/img/powered_by_hasura_black_200px.png"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
|
||||||
|
<a href="https://realtime-poll.hasura.app/console" target="_blank">
|
||||||
|
Backend
|
||||||
|
</a>
|
||||||
|
|
|
||||||
|
<a
|
||||||
|
href="https://github.com/hasura/graphql-engine/tree/master/community/sample-apps/realtime-poll"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Source
|
||||||
|
</a>
|
||||||
|
<div className="footer-small-text">
|
||||||
|
<span>(The database resets every 24 hours)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
@ -1,115 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card } from 'react-bootstrap';
|
|
||||||
|
|
||||||
const QUERY_GET_POLL = `
|
|
||||||
query {
|
|
||||||
poll (limit: 10) {
|
|
||||||
id
|
|
||||||
question
|
|
||||||
options (order_by: {id:desc}){
|
|
||||||
id
|
|
||||||
text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const MUTATION_VOTE = `
|
|
||||||
mutation vote($optionId: uuid!, $userId: uuid!) {
|
|
||||||
insert_vote(objects:[{
|
|
||||||
option_id: $optionId,
|
|
||||||
created_by_user_id: $userId
|
|
||||||
}]) {
|
|
||||||
returning {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const SUBSCRIPTION_RESULT = `
|
|
||||||
subscription getResult($pollId: uuid!) {
|
|
||||||
poll_results (
|
|
||||||
order_by: {option_id:desc},
|
|
||||||
where: { poll_id: {_eq: $pollId} }
|
|
||||||
) {
|
|
||||||
option_id
|
|
||||||
option { id text }
|
|
||||||
votes
|
|
||||||
}
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const SUBSCRIPTION_ONLINE_USERS = `
|
|
||||||
subscription getOnlineUsersCount {
|
|
||||||
online_users {
|
|
||||||
count
|
|
||||||
}
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const MUTATION_MARK_USER_ONLINE = `
|
|
||||||
mutation userOnline($uuid: uuid) {
|
|
||||||
update_user(
|
|
||||||
where: {id: {_eq: $uuid}},
|
|
||||||
_set : { online_ping: true }
|
|
||||||
) {
|
|
||||||
affected_rows
|
|
||||||
returning {
|
|
||||||
last_seen_at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const MUTATION_NEW_USER = `
|
|
||||||
mutation newUser($uuid: uuid) {
|
|
||||||
insert_user (
|
|
||||||
objects:[{ id: $uuid }]
|
|
||||||
) {
|
|
||||||
returning {
|
|
||||||
id
|
|
||||||
created_at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const GraphQL = () => (
|
|
||||||
<div className="container">
|
|
||||||
<div className="col-md-12 cardGraphQL">
|
|
||||||
<Card>
|
|
||||||
<Card.Header>GraphQL Queries/Mutations/Subscriptions in this page</Card.Header>
|
|
||||||
<Card.Body>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-md-4">
|
|
||||||
Get the Poll question and options:
|
|
||||||
<pre>{QUERY_GET_POLL}</pre>
|
|
||||||
|
|
||||||
Create a new user:
|
|
||||||
<pre>{MUTATION_NEW_USER}</pre>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-4">
|
|
||||||
Cast a vote:
|
|
||||||
<pre>{MUTATION_VOTE}</pre>
|
|
||||||
|
|
||||||
Mark user online:
|
|
||||||
<pre>{MUTATION_MARK_USER_ONLINE}</pre>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-4">
|
|
||||||
Show live results:
|
|
||||||
<pre>{SUBSCRIPTION_RESULT}</pre>
|
|
||||||
|
|
||||||
Get real-time number of users:
|
|
||||||
<pre>{SUBSCRIPTION_ONLINE_USERS}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card.Body>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export {
|
|
||||||
GraphQL,
|
|
||||||
QUERY_GET_POLL,
|
|
||||||
MUTATION_VOTE,
|
|
||||||
SUBSCRIPTION_RESULT,
|
|
||||||
SUBSCRIPTION_ONLINE_USERS,
|
|
||||||
MUTATION_MARK_USER_ONLINE,
|
|
||||||
MUTATION_NEW_USER,
|
|
||||||
};
|
|
118
community/sample-apps/realtime-poll/src/GraphQL.jsx
Normal file
118
community/sample-apps/realtime-poll/src/GraphQL.jsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import gql from "graphql-tag";
|
||||||
|
import React from "react";
|
||||||
|
import { Card } from "react-bootstrap";
|
||||||
|
|
||||||
|
const QUERY_GET_POLL = gql`
|
||||||
|
query {
|
||||||
|
poll(limit: 10) {
|
||||||
|
id
|
||||||
|
question
|
||||||
|
options(order_by: { id: desc }) {
|
||||||
|
id
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MUTATION_VOTE = gql`
|
||||||
|
mutation vote($optionId: uuid!, $userId: uuid!) {
|
||||||
|
insert_vote(
|
||||||
|
objects: [{ option_id: $optionId, created_by_user_id: $userId }]
|
||||||
|
) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SUBSCRIPTION_RESULT = gql`
|
||||||
|
subscription getResult($pollId: uuid!) {
|
||||||
|
poll_results(
|
||||||
|
order_by: { option_id: desc }
|
||||||
|
where: { poll_id: { _eq: $pollId } }
|
||||||
|
) {
|
||||||
|
option_id
|
||||||
|
option {
|
||||||
|
id
|
||||||
|
text
|
||||||
|
}
|
||||||
|
votes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SUBSCRIPTION_ONLINE_USERS = gql`
|
||||||
|
subscription getOnlineUsersCount {
|
||||||
|
online_users {
|
||||||
|
count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MUTATION_MARK_USER_ONLINE = gql`
|
||||||
|
mutation userOnline($uuid: uuid) {
|
||||||
|
update_user(where: { id: { _eq: $uuid } }, _set: { online_ping: true }) {
|
||||||
|
affected_rows
|
||||||
|
returning {
|
||||||
|
last_seen_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MUTATION_NEW_USER = gql`
|
||||||
|
mutation newUser($uuid: uuid) {
|
||||||
|
insert_user(objects: [{ id: $uuid }]) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
created_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const GraphQLQueryList = () => (
|
||||||
|
<div className="container">
|
||||||
|
<div className="col-md-12 cardGraphQL">
|
||||||
|
<Card>
|
||||||
|
<Card.Header>
|
||||||
|
GraphQL Queries/Mutations/Subscriptions in this page
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Body>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-4">
|
||||||
|
Get the Poll question and options:
|
||||||
|
<pre>{QUERY_GET_POLL.loc.source.body}</pre>
|
||||||
|
Create a new user:
|
||||||
|
<pre>{MUTATION_NEW_USER.loc.source.body}</pre>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-4">
|
||||||
|
Cast a vote:
|
||||||
|
<pre>{MUTATION_VOTE.loc.source.body}</pre>
|
||||||
|
Mark user online:
|
||||||
|
<pre>{MUTATION_MARK_USER_ONLINE.loc.source.body}</pre>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-4">
|
||||||
|
Show live results:
|
||||||
|
<pre>{SUBSCRIPTION_RESULT.loc.source.body}</pre>
|
||||||
|
Get real-time number of users:
|
||||||
|
<pre>{SUBSCRIPTION_ONLINE_USERS.loc.source.body}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export {
|
||||||
|
GraphQLQueryList,
|
||||||
|
QUERY_GET_POLL,
|
||||||
|
MUTATION_VOTE,
|
||||||
|
SUBSCRIPTION_RESULT,
|
||||||
|
SUBSCRIPTION_ONLINE_USERS,
|
||||||
|
MUTATION_MARK_USER_ONLINE,
|
||||||
|
MUTATION_NEW_USER,
|
||||||
|
};
|
@ -1,137 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import { Query, Mutation } from 'react-apollo';
|
|
||||||
import gql from 'graphql-tag';
|
|
||||||
import { Button, Form } from 'react-bootstrap';
|
|
||||||
import { Result } from './Result';
|
|
||||||
import { QUERY_GET_POLL, MUTATION_VOTE } from './GraphQL';
|
|
||||||
|
|
||||||
class PollQuestion extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
optionId: '',
|
|
||||||
pollId: props.poll.id,
|
|
||||||
voteBtnText: '🗳 Vote',
|
|
||||||
voteBtnStyle: 'primary'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOptionChange = (e) => {
|
|
||||||
this.setState({
|
|
||||||
optionId: e.currentTarget.value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMutationCompleted = () => {
|
|
||||||
this.setState({
|
|
||||||
voteBtnText: '👍 Done',
|
|
||||||
voteBtnStyle: 'success'
|
|
||||||
});
|
|
||||||
// re-authorize to vote after 5 seconds
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.setState({
|
|
||||||
voteBtnText: '🗳️ Vote',
|
|
||||||
voteBtnStyle: 'primary'
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMutationError = () => {
|
|
||||||
this.setState({
|
|
||||||
voteBtnText: 'Error 😞 Try again',
|
|
||||||
voteBtnStyle: 'danger'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlesubmitVote = (e, vote) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!this.state.optionId) {
|
|
||||||
this.setState({
|
|
||||||
voteBtnText: '✋ Select an option and try again',
|
|
||||||
voteBtnStyle: 'warning'
|
|
||||||
});
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
voteBtnText: '🗳️ Submitting',
|
|
||||||
voteBtnStyle: 'info'
|
|
||||||
});
|
|
||||||
vote({
|
|
||||||
variables: {
|
|
||||||
optionId: this.state.optionId,
|
|
||||||
userId: this.props.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Mutation
|
|
||||||
mutation={gql`${MUTATION_VOTE}`}
|
|
||||||
onCompleted={this.onMutationCompleted}
|
|
||||||
onError={this.onMutationError}
|
|
||||||
>
|
|
||||||
{(vote) => (
|
|
||||||
<div className="textLeft">
|
|
||||||
<h3>{this.props.poll.question}</h3>
|
|
||||||
<Form className="pollForm textLeft" onSubmit={e => { this.handlesubmitVote(e, vote) }}>
|
|
||||||
{
|
|
||||||
this.props.poll.options.map(option => (
|
|
||||||
|
|
||||||
<Form.Check
|
|
||||||
custom
|
|
||||||
type="radio"
|
|
||||||
name="voteCandidate"
|
|
||||||
id={option.id}
|
|
||||||
key={option.id}
|
|
||||||
value={option.id}
|
|
||||||
label={option.text}
|
|
||||||
onChange={this.handleOptionChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
))
|
|
||||||
}
|
|
||||||
<Button className="voteBtn info" variant={this.state.voteBtnStyle} type="submit">
|
|
||||||
{this.state.voteBtnText}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Mutation>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const Poll = ({ userId }) => (
|
|
||||||
<div>
|
|
||||||
<Query query={gql`${QUERY_GET_POLL}`}>
|
|
||||||
{({ loading, error, data }) => {
|
|
||||||
if (loading) return <p>Loading...</p>;
|
|
||||||
if (error) {
|
|
||||||
return <div class="alert alert-danger" role="alert"><b>Error:</b> ${error.message}</div>;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="container">
|
|
||||||
{
|
|
||||||
data.poll.map(poll => (
|
|
||||||
<div key={poll.id} className="pollWrapper wd100">
|
|
||||||
<div className="displayFlex">
|
|
||||||
<div className="col-md-4 pollSlider">
|
|
||||||
<PollQuestion poll={poll} userId={userId} />
|
|
||||||
</div>
|
|
||||||
<div className="col-md-8 pollresult">
|
|
||||||
<Result pollId={poll.id} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Query>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Poll;
|
|
128
community/sample-apps/realtime-poll/src/Poll.jsx
Normal file
128
community/sample-apps/realtime-poll/src/Poll.jsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Button, Form } from "react-bootstrap";
|
||||||
|
import { Error, Loading } from "./Components";
|
||||||
|
import { MUTATION_VOTE, QUERY_GET_POLL } from "./GraphQL";
|
||||||
|
import { Result } from "./Result";
|
||||||
|
|
||||||
|
const PollQuestion = ({ poll, userId }) => {
|
||||||
|
const defaultState = {
|
||||||
|
optionId: "",
|
||||||
|
pollId: poll.id,
|
||||||
|
voteBtnText: "🗳 Vote",
|
||||||
|
voteBtnStyle: "primary",
|
||||||
|
};
|
||||||
|
const [state, setState] = useState(defaultState);
|
||||||
|
const [vote, { data, loading, error }] = useMutation(MUTATION_VOTE);
|
||||||
|
|
||||||
|
const handlesubmitVote = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!state.optionId) {
|
||||||
|
setState({
|
||||||
|
voteBtnText: "✋ Select an option and try again",
|
||||||
|
voteBtnStyle: "warning",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState({
|
||||||
|
voteBtnText: "🗳️ Submitting",
|
||||||
|
voteBtnStyle: "info",
|
||||||
|
});
|
||||||
|
|
||||||
|
vote({
|
||||||
|
variables: {
|
||||||
|
optionId: state.optionId,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// To-do: use cleanup
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
setState({
|
||||||
|
voteBtnText: "👍 Done",
|
||||||
|
voteBtnStyle: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-authorize to vote after 5 seconds
|
||||||
|
let timer = setTimeout(() => {
|
||||||
|
setState({
|
||||||
|
voteBtnText: "🗳️ Vote",
|
||||||
|
voteBtnStyle: "primary",
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setState({
|
||||||
|
voteBtnText: "Error 😞 Try again",
|
||||||
|
voteBtnStyle: "danger",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data, error]);
|
||||||
|
|
||||||
|
const handleOptionChange = (e) => {
|
||||||
|
const optionId = e.currentTarget.value;
|
||||||
|
setState((prev) => ({ ...prev, optionId }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="textLeft">
|
||||||
|
<h3>{poll.question}</h3>
|
||||||
|
<Form
|
||||||
|
className="pollForm textLeft"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
handlesubmitVote(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{poll.options.map(({ id, text }) => (
|
||||||
|
<Form.Check
|
||||||
|
custom
|
||||||
|
type="radio"
|
||||||
|
name="voteCandidate"
|
||||||
|
id={id}
|
||||||
|
key={id}
|
||||||
|
value={id}
|
||||||
|
label={text}
|
||||||
|
onChange={handleOptionChange}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
className="voteBtn info"
|
||||||
|
variant={state.voteBtnStyle}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{state.voteBtnText}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Poll = ({ userId }) => {
|
||||||
|
const { data, loading, error } = useQuery(QUERY_GET_POLL);
|
||||||
|
|
||||||
|
if (loading) return <Loading />;
|
||||||
|
if (error) return <Error message={error.message} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
{data?.poll.map((poll) => (
|
||||||
|
<div key={poll.id} className="pollWrapper wd100">
|
||||||
|
<div className="displayFlex">
|
||||||
|
<div className="col-md-4 pollSlider">
|
||||||
|
<PollQuestion poll={poll} userId={userId} />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-8 pollresult">
|
||||||
|
<Result pollId={poll.id} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,58 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Subscription,
|
|
||||||
} from 'react-apollo';
|
|
||||||
import { Chart } from 'react-google-charts';
|
|
||||||
import gql from 'graphql-tag';
|
|
||||||
import { SUBSCRIPTION_RESULT } from './GraphQL'
|
|
||||||
|
|
||||||
const renderChart = (data) => {
|
|
||||||
const d = [
|
|
||||||
['Option', 'No. of votes', { role: 'annotation' }, { role: 'style' }],
|
|
||||||
];
|
|
||||||
for (var r of data.poll_results) {
|
|
||||||
console.log(r);
|
|
||||||
d.push([r.option.text, parseInt(r.votes, 10), parseInt(r.votes, 10), 'color: #4285f4']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Chart className="poll-result-chart-container"
|
|
||||||
chartType="BarChart"
|
|
||||||
loader={<div>Loading Chart</div>}
|
|
||||||
data={d}
|
|
||||||
options={{
|
|
||||||
height: '100%',
|
|
||||||
chart: {
|
|
||||||
title: 'Realtime results',
|
|
||||||
},
|
|
||||||
legend: { position: 'none' },
|
|
||||||
animation: {
|
|
||||||
duration: 1000,
|
|
||||||
easing: 'out',
|
|
||||||
startup: true,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Result = (pollId) => (
|
|
||||||
<Subscription subscription={gql`${SUBSCRIPTION_RESULT}`} variables={pollId}>
|
|
||||||
{({ loading, error, data }) => {
|
|
||||||
if (loading) return <p>Loading...</p>;
|
|
||||||
if (error) return <p>Error :</p>;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{data.poll_results.length > 0
|
|
||||||
?
|
|
||||||
<div>
|
|
||||||
{renderChart(data)}
|
|
||||||
</div>
|
|
||||||
:
|
|
||||||
<p>No result</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Subscription>
|
|
||||||
)
|
|
56
community/sample-apps/realtime-poll/src/Result.jsx
Normal file
56
community/sample-apps/realtime-poll/src/Result.jsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { useSubscription } from "@apollo/client";
|
||||||
|
import React from "react";
|
||||||
|
import { Chart } from "react-google-charts";
|
||||||
|
import { Error, Loading } from "./Components";
|
||||||
|
import { SUBSCRIPTION_RESULT } from "./GraphQL";
|
||||||
|
|
||||||
|
export const Result = ({ pollId }) => {
|
||||||
|
const { data, loading, error } = useSubscription(SUBSCRIPTION_RESULT, {
|
||||||
|
variables: { pollId },
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasResults = data?.poll_results.length > 0;
|
||||||
|
|
||||||
|
if (loading) return <Loading />;
|
||||||
|
if (error) return <Error message={error.message} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{hasResults ? <PollChart data={data?.poll_results} /> : <p>No result</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PollChart = ({ data }) => {
|
||||||
|
const COLOR = "color: #4285f4";
|
||||||
|
const d = [
|
||||||
|
["Option", "No. of votes", { role: "annotation" }, { role: "style" }],
|
||||||
|
];
|
||||||
|
|
||||||
|
data.forEach(({ option, votes }) =>
|
||||||
|
d.push([option.text, parseInt(votes), parseInt(votes), COLOR])
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
className="poll-result-chart-container"
|
||||||
|
chartType="BarChart"
|
||||||
|
loader={<div>Loading Chart</div>}
|
||||||
|
data={d}
|
||||||
|
options={chartOptions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartOptions = {
|
||||||
|
height: "100%",
|
||||||
|
chart: {
|
||||||
|
title: "Realtime results",
|
||||||
|
},
|
||||||
|
legend: { position: "none" },
|
||||||
|
animation: {
|
||||||
|
duration: 1000,
|
||||||
|
easing: "out",
|
||||||
|
startup: true,
|
||||||
|
},
|
||||||
|
};
|
@ -1,27 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import gql from 'graphql-tag';
|
|
||||||
import { Subscription } from 'react-apollo';
|
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
} from 'react-bootstrap';
|
|
||||||
import { SUBSCRIPTION_ONLINE_USERS } from './GraphQL';
|
|
||||||
|
|
||||||
export const Users = () => (
|
|
||||||
<Subscription subscription={gql`${SUBSCRIPTION_ONLINE_USERS}`}>
|
|
||||||
{({ loading, error, data }) => {
|
|
||||||
if (loading) return <span>Loading...</span>;
|
|
||||||
if (error) {
|
|
||||||
return <div class="alert alert-danger" role="alert"><b>Error:</b> ${error.message}</div>;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="displayFlex online-users">
|
|
||||||
<div className="col-md-6">
|
|
||||||
<Alert variant="info">
|
|
||||||
<span role="img" aria-label="online users">👥</span> Online users: {data.online_users[0].count}
|
|
||||||
</Alert>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Subscription>
|
|
||||||
)
|
|
26
community/sample-apps/realtime-poll/src/Users.jsx
Normal file
26
community/sample-apps/realtime-poll/src/Users.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useSubscription } from "@apollo/client";
|
||||||
|
import React from "react";
|
||||||
|
import { Alert } from "react-bootstrap";
|
||||||
|
import { Error, Loading } from "./Components";
|
||||||
|
import { SUBSCRIPTION_ONLINE_USERS } from "./GraphQL";
|
||||||
|
|
||||||
|
export const Users = () => {
|
||||||
|
const { data, loading, error } = useSubscription(SUBSCRIPTION_ONLINE_USERS);
|
||||||
|
const { count } = data?.online_users[0] || {};
|
||||||
|
|
||||||
|
if (loading) return <Loading />;
|
||||||
|
if (error) return <Error message={error.message} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="displayFlex online-users">
|
||||||
|
<div className="col-md-6">
|
||||||
|
<Alert variant="info">
|
||||||
|
<span role="img" aria-label="online users">
|
||||||
|
👥
|
||||||
|
</span>{" "}
|
||||||
|
Online users: {count}
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,52 +1,26 @@
|
|||||||
// Remove the apollo-boost import and change to this:
|
import { ApolloClient, HttpLink, InMemoryCache, split } from "@apollo/client";
|
||||||
import ApolloClient from "apollo-client";
|
import { WebSocketLink } from "@apollo/client/link/ws";
|
||||||
|
import { getMainDefinition } from "@apollo/client/utilities";
|
||||||
|
|
||||||
// Setup the network "links"
|
const scheme = (proto) =>
|
||||||
import { WebSocketLink } from 'apollo-link-ws';
|
window.location.protocol === "https:" ? `${proto}s` : proto;
|
||||||
import { HttpLink } from 'apollo-link-http';
|
|
||||||
import { split } from 'apollo-link';
|
|
||||||
import { getMainDefinition } from 'apollo-utilities';
|
|
||||||
|
|
||||||
import { InMemoryCache } from 'apollo-cache-inmemory';
|
const splitter = ({ query }) => {
|
||||||
|
const { kind, operation } = getMainDefinition(query) || {};
|
||||||
export const HASURA_GRAPHQL_ENGINE_HOSTNAME = 'realtime-poll.hasura.app';
|
const isSubscription =
|
||||||
|
kind === "OperationDefinition" && operation === "subscription";
|
||||||
const scheme = (proto) => {
|
return isSubscription;
|
||||||
return window.location.protocol === 'https:' ? `${proto}s` : proto;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsurl = `${scheme('ws')}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/v1/graphql`;
|
|
||||||
const httpurl = `${scheme('http')}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/v1/graphql`;
|
|
||||||
|
|
||||||
const wsLink = new WebSocketLink({
|
|
||||||
uri: wsurl,
|
|
||||||
options: {
|
|
||||||
reconnect: true,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const httpLink = new HttpLink({
|
|
||||||
uri: httpurl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const link = split(
|
|
||||||
// split based on operation type
|
|
||||||
({ query }) => {
|
|
||||||
const { kind, operation } = getMainDefinition(query);
|
|
||||||
return kind === 'OperationDefinition' && operation === 'subscription';
|
|
||||||
},
|
|
||||||
wsLink,
|
|
||||||
httpLink,
|
|
||||||
);
|
|
||||||
|
|
||||||
const createApolloClient = () => {
|
|
||||||
return new ApolloClient({
|
|
||||||
link,
|
|
||||||
cache: new InMemoryCache()
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GRAPHQL_ENDPOINT = "realtime-poll.hasura.app";
|
||||||
|
const cache = new InMemoryCache();
|
||||||
|
const options = { reconnect: true };
|
||||||
|
|
||||||
const client = createApolloClient();
|
const wsURI = `${scheme("ws")}://${GRAPHQL_ENDPOINT}/v1/graphql`;
|
||||||
|
const httpurl = `${scheme("https")}://${GRAPHQL_ENDPOINT}/v1/graphql`;
|
||||||
|
|
||||||
|
const wsLink = new WebSocketLink({ uri: wsURI, options });
|
||||||
|
const httpLink = new HttpLink({ uri: httpurl });
|
||||||
|
const link = split(splitter, wsLink, httpLink);
|
||||||
|
const client = new ApolloClient({ link, cache });
|
||||||
export default client;
|
export default client;
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import gql from 'graphql-tag';
|
import client from "./apollo";
|
||||||
import client from './apollo';
|
import { MUTATION_MARK_USER_ONLINE, MUTATION_NEW_USER } from "./GraphQL";
|
||||||
import {
|
|
||||||
MUTATION_MARK_USER_ONLINE,
|
|
||||||
MUTATION_NEW_USER,
|
|
||||||
} from './GraphQL';
|
|
||||||
|
|
||||||
const newUUID = () => {
|
const newUUID = () => {
|
||||||
const p8 = (s) => {
|
const p8 = (s) => {
|
||||||
@ -13,49 +9,40 @@ const newUUID = () => {
|
|||||||
return p8() + p8(true) + p8(true) + p8();
|
return p8() + p8(true) + p8(true) + p8();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getUserId = async () => {
|
||||||
const getUserId = () => {
|
const { data } = await client.mutate({
|
||||||
return new Promise((resolve, reject) => {
|
mutation: MUTATION_NEW_USER,
|
||||||
// let uid = window.localStorage.getItem('uid');
|
variables: { uuid: newUUID() },
|
||||||
// if (!uid) {
|
|
||||||
client.mutate({
|
|
||||||
mutation: gql`${MUTATION_NEW_USER}`,
|
|
||||||
variables: {
|
|
||||||
uuid: newUUID(),
|
|
||||||
}
|
|
||||||
}).then(({ data }) => {
|
|
||||||
if (data.insert_user.returning.length > 0) {
|
|
||||||
const user = data.insert_user.returning[0];
|
|
||||||
console.log("getUserId user.id:", user.id);
|
|
||||||
// window.localStorage.setItem('uid', user.id);
|
|
||||||
// window.localStorage.setItem('createdAt', user.created_at);
|
|
||||||
reportUserOnline(user.id);
|
|
||||||
resolve(user.id);
|
|
||||||
} else {
|
|
||||||
reject('no data');
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
// } else {
|
|
||||||
// reportUserOnline(uid);
|
|
||||||
// resolve(uid);
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (data?.insert_user.returning.length > 0) {
|
||||||
|
const { id } = data.insert_user.returning[0] || {};
|
||||||
|
|
||||||
|
reportUserOnline(id);
|
||||||
|
return id;
|
||||||
|
} else {
|
||||||
|
throw new Error(400);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw new Error(400);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const reportUserOnline = (userId) => {
|
const reportUserOnline = (userId) => {
|
||||||
window.setInterval(() => {
|
window.setInterval(async () => {
|
||||||
client.mutate({
|
try {
|
||||||
mutation: gql`${MUTATION_MARK_USER_ONLINE}`,
|
await client.mutate({
|
||||||
variables: {
|
mutation: MUTATION_MARK_USER_ONLINE,
|
||||||
uuid: userId,
|
variables: {
|
||||||
},
|
uuid: userId,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
}, 10000);
|
}, 10000);
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export { getUserId };
|
||||||
getUserId
|
|
||||||
};
|
|
||||||
|
Loading…
Reference in New Issue
Block a user