realtime chat app ui update (#1101)
@ -127,8 +127,8 @@ class LandingPage extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-md-11 col-sm-11 col-xs-10 noPadd">
|
<div className="col-md-11 col-sm-11 col-xs-10 noPadd">
|
||||||
<div className="description removePaddBottom">
|
<div className="description removePaddBottom">
|
||||||
Explore the Hasura console and try out some queries &
|
Explore the Hasura GraphQL backend and try out some
|
||||||
mutations
|
queries & mutations
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -143,6 +143,7 @@ class LandingPage extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/*
|
||||||
<div className="footer">
|
<div className="footer">
|
||||||
Built with
|
Built with
|
||||||
<i className="fas fa-heart" />
|
<i className="fas fa-heart" />
|
||||||
@ -155,6 +156,7 @@ class LandingPage extends Component {
|
|||||||
Hasura
|
Hasura
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
<div className="tutorialImg col-md-6 col-sm-6 col-xs-12 hidden-xs noPadd">
|
<div className="tutorialImg col-md-6 col-sm-6 col-xs-12 hidden-xs noPadd">
|
||||||
<img className="img-responsive" src={rightImg} alt="View" />
|
<img className="img-responsive" src={rightImg} alt="View" />
|
||||||
|
@ -546,12 +546,13 @@ body {
|
|||||||
}
|
}
|
||||||
.headerWrapper {
|
.headerWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
padding: 15px 0;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
.mainWrapper {
|
.mainWrapper {
|
||||||
min-height: 80vh;
|
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
.minHeight {
|
.minHeight {
|
||||||
height: auto;
|
height: auto;
|
||||||
@ -559,6 +560,7 @@ body {
|
|||||||
}
|
}
|
||||||
.loginBtn {
|
.loginBtn {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
|
padding-bottom: 15px;
|
||||||
}
|
}
|
||||||
.appstackWrapper {
|
.appstackWrapper {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
11892
community/examples/realtime-chat/package-lock.json
generated
Normal file
@ -9,7 +9,13 @@
|
|||||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||||
|
<link href="https://afeld.github.io/emoji-css/emoji.css" rel="stylesheet">
|
||||||
|
<link href="https://use.fontawesome.com/releases/v5.0.7/css/all.css" rel="stylesheet" crossorigin="anonymous">
|
||||||
|
|
||||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.png">
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.png">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
@ -6,7 +6,249 @@ body {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
.noPadd
|
||||||
|
{
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.removePaddLeft {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.addPaddTop
|
||||||
|
{
|
||||||
|
padding-top: 10px;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
/* Landing section */
|
||||||
|
.wd10 {
|
||||||
|
width: 10%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.wd90 {
|
||||||
|
width: 90%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.removePaddBottom {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
.gradientBgColor {
|
||||||
|
background-color: #a0b4cc;
|
||||||
|
/* Safari 4-5, Chrome 1-9 */
|
||||||
|
background: -webkit-gradient(
|
||||||
|
linear,
|
||||||
|
0% 0%,
|
||||||
|
0% 100%,
|
||||||
|
from(#a0b4cc),
|
||||||
|
to(#c2a899)
|
||||||
|
);
|
||||||
|
/* Safari 5.1, Chrome 10+ */
|
||||||
|
background: -webkit-linear-gradient(top, #a0b4cc, #c2a899);
|
||||||
|
/* Firefox 3.6+ */
|
||||||
|
background: -moz-linear-gradient(top, #a0b4cc, #c2a899);
|
||||||
|
/* IE 10 */
|
||||||
|
background: -ms-linear-gradient(top, #a0b4cc, #c2a899);
|
||||||
|
/* Opera 11.10+ */
|
||||||
|
background: -o-linear-gradient(top, #a0b4cc, #c2a899);
|
||||||
|
}
|
||||||
|
.bgImage
|
||||||
|
{
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background-image: url('./images/chat-app-bg.jpg');
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 0 0;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
.bgImage::before
|
||||||
|
{
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background-image: linear-gradient(to bottom right,#000,#a92101);
|
||||||
|
opacity: .9;
|
||||||
|
}
|
||||||
|
.minHeight {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.headerWrapper {
|
||||||
|
padding: 30px 0;
|
||||||
|
padding-left: 75px;
|
||||||
|
min-height: 15vh
|
||||||
|
}
|
||||||
|
.headerDescription {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #fff;
|
||||||
|
font-family: "Raleway";
|
||||||
|
font-weight: 700;
|
||||||
|
z-index: 100;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.headerDescription a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.headerDescription a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid #fff;
|
||||||
|
}
|
||||||
|
.loginBtn {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 75px;
|
||||||
|
}
|
||||||
|
.loginBtn button {
|
||||||
|
background-color: #f93c18;
|
||||||
|
padding: 10px 30px;
|
||||||
|
border: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-family: "raleway";
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
border-radius: 25px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.loginBtn button:hover {
|
||||||
|
background-color: #e0270e;
|
||||||
|
}
|
||||||
|
.loginBtn button:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.mainWrapper {
|
||||||
|
padding-left: 75px;
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
font-size: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.appstackWrapper {
|
||||||
|
margin-top: 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 30px;
|
||||||
|
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
-moz-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
color: #606060;
|
||||||
|
}
|
||||||
|
.arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: -100px;
|
||||||
|
top: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.arrow img {
|
||||||
|
width: 120px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.appStack {
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appStack i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.checkBox
|
||||||
|
{
|
||||||
|
color: #00bc00;
|
||||||
|
font-size: 22px !important;
|
||||||
|
}
|
||||||
|
.appStackIconWrapper {
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.appStackIcon img {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
.formGroupWrapper
|
||||||
|
{
|
||||||
|
padding-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.inputGroup
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.inputGroup input
|
||||||
|
{
|
||||||
|
width: 68% !important;
|
||||||
|
display: inline-block;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.inputGroup .groupAppend
|
||||||
|
{
|
||||||
|
width: 32% !important;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.groupAppend button
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border: 0;
|
||||||
|
height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #f93c18;
|
||||||
|
color: #fff;
|
||||||
|
font-family: "raleway";
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.groupAppend button:hover
|
||||||
|
{
|
||||||
|
color: #fff;
|
||||||
|
background-color: #e0270e;
|
||||||
|
}
|
||||||
|
.groupAppend button:focus
|
||||||
|
{
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
padding-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
clear: both;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.footer a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid #fff;
|
||||||
|
}
|
||||||
|
.footer i {
|
||||||
|
color: #ed2908;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
.tutorialImg {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.tutorialImg img {
|
||||||
|
width: 95%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
/* Landing section */
|
||||||
.app {
|
.app {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
@ -305,3 +547,120 @@ body {
|
|||||||
.footer-small-text{
|
.footer-small-text{
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.headerDescription
|
||||||
|
{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.headerWrapper {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.000scription {
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.loginBtn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.wd75
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.message
|
||||||
|
{
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
.textboxWrapper
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.mobileview
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 152px;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
.mobileuserListHeading
|
||||||
|
{
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: #222;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.mobileuserListHeading i
|
||||||
|
{
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.mobileUserList
|
||||||
|
{
|
||||||
|
background-color: #4f5050;
|
||||||
|
padding-inline-start: 0px;
|
||||||
|
-webkit-padding-start: 0px;
|
||||||
|
-moz-padding-start: 0px;
|
||||||
|
-o-padding-start: 0px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.mobileUserList li
|
||||||
|
{
|
||||||
|
list-style-type: none;
|
||||||
|
color: #fff;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
}
|
||||||
|
.hasura-logo a {
|
||||||
|
padding: 0 0px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.headerWrapper {
|
||||||
|
display: flex;
|
||||||
|
padding-left: 0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.mainWrapper {
|
||||||
|
min-height: auto;
|
||||||
|
padding-left: 0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.minHeight {
|
||||||
|
height: auto;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.loginBtn {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.appstackWrapper {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.appStack {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.flexWidth {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.appStackIconWrapper {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.inputGroup input
|
||||||
|
{
|
||||||
|
width: 63% !important;
|
||||||
|
}
|
||||||
|
.inputGroup .groupAppend
|
||||||
|
{
|
||||||
|
width: 37% !important;
|
||||||
|
}
|
||||||
|
.groupAppend button {
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -23,7 +23,13 @@ export default class RenderMessagesProxy extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="chatWrapper">
|
<div className="chatWrapper">
|
||||||
<div className="wd25">
|
<div className="wd25 hidden-xs">
|
||||||
|
<OnlineUsers
|
||||||
|
userId={this.props.userId}
|
||||||
|
username={this.props.username}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mobileview visible-xs">
|
||||||
<OnlineUsers
|
<OnlineUsers
|
||||||
userId={this.props.userId}
|
userId={this.props.userId}
|
||||||
username={this.props.username}
|
username={this.props.username}
|
||||||
|
235
community/examples/realtime-chat/src/components/LandingPage.js
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
import { Mutation } from 'react-apollo';
|
||||||
|
import "../App.css";
|
||||||
|
|
||||||
|
const addUser = gql`
|
||||||
|
mutation ($username: String!) {
|
||||||
|
insert_user (
|
||||||
|
objects: [{
|
||||||
|
username: $username
|
||||||
|
}]
|
||||||
|
) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LandingPage = (props) => {
|
||||||
|
const reactLogo = require("../images/React-logo.png");
|
||||||
|
const graphql = require("../images/graphql.png");
|
||||||
|
const hasuraLogo = require("../images/green-logo-white.svg");
|
||||||
|
const apolloLogo = require("../images/apollo.png");
|
||||||
|
const rightImg = require("../images/chat-app.png");
|
||||||
|
const handleKeyPress = (key, mutate, loading) => {
|
||||||
|
if (!loading && key.charCode === 13) {
|
||||||
|
mutate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Mutation
|
||||||
|
mutation={addUser}
|
||||||
|
variables={{
|
||||||
|
username: props.username
|
||||||
|
}}
|
||||||
|
onCompleted={(data) => {
|
||||||
|
props.login(data.insert_user.returning[0].id);
|
||||||
|
}}
|
||||||
|
onError={() => {
|
||||||
|
alert('Please try again with a different username.')
|
||||||
|
props.setUsername('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
(insert_user, { data, loading, error}) => {
|
||||||
|
return (
|
||||||
|
<div className="container-fluid minHeight">
|
||||||
|
<div className="bgImage">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="headerWrapper">
|
||||||
|
<div className="headerDescription">
|
||||||
|
Realtime Chat App
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mainWrapper">
|
||||||
|
<div className="col-md-5 col-sm-6 col-xs-12 noPadd">
|
||||||
|
<div className="appstackWrapper">
|
||||||
|
<div className="appStack">
|
||||||
|
<div className="col-md-1 col-sm-1 col-xs-2 removePaddLeft flexWidth">
|
||||||
|
<i className="em em---1" />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-11 col-sm-11 col-xs-10 noPadd">
|
||||||
|
<div className="description">
|
||||||
|
Try out a realtime app that uses
|
||||||
|
</div>
|
||||||
|
<div className="appStackIconWrapper">
|
||||||
|
<div className="col-md-4 col-sm-4 col-xs-4 noPadd">
|
||||||
|
<div className="appStackIcon">
|
||||||
|
<img
|
||||||
|
className="img-responsive"
|
||||||
|
src={reactLogo}
|
||||||
|
alt="React logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-8 col-sm-8 col-xs-8 noPadd">
|
||||||
|
<div className="appStackIcon">
|
||||||
|
<img
|
||||||
|
className="img-responsive"
|
||||||
|
src={graphql}
|
||||||
|
alt="GraphQL logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="appStack">
|
||||||
|
<div className="col-md-1 col-sm-1 col-xs-2 removePaddLeft flexWidth">
|
||||||
|
<i className="em em-rocket" />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-11 col-sm-11 col-xs-10 noPadd">
|
||||||
|
<div className="description">Powered by</div>
|
||||||
|
<div className="appStackIconWrapper">
|
||||||
|
<div className="col-md-4 col-sm-4 col-xs-4 noPadd">
|
||||||
|
<div className="appStackIcon">
|
||||||
|
<img
|
||||||
|
className="img-responsive"
|
||||||
|
src={apolloLogo}
|
||||||
|
alt="apollo logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-4 col-sm-4 col-xs-4 noPadd">
|
||||||
|
<div className="appStackIcon">
|
||||||
|
<img
|
||||||
|
className="img-responsive"
|
||||||
|
src={hasuraLogo}
|
||||||
|
alt="Hasura logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="appStack">
|
||||||
|
<div className="col-md-1 col-sm-1 col-xs-2 removePaddLeft flexWidth">
|
||||||
|
<i className="em em-sunglasses" />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-11 col-sm-11 col-xs-10 noPadd">
|
||||||
|
<div className="description removePaddBottom">
|
||||||
|
Explore the Hasura GraphQL backend and try out some queries & mutations
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="appStack removePaddBottom">
|
||||||
|
<div className="col-md-1 col-sm-1 col-xs-2 removePaddLeft flexWidth">
|
||||||
|
<i className="fas fa-check-square checkBox"></i>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-11 col-sm-11 col-xs-10 noPadd">
|
||||||
|
<div className="description removePaddBottom">
|
||||||
|
What you get...
|
||||||
|
</div>
|
||||||
|
<div className="addPaddTop">
|
||||||
|
<div className="col-md-1 col-sm-1 col-xs-2 removePaddLeft flexWidth">
|
||||||
|
<i className="em em-hammer_and_wrench"></i>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-11 col-sm-11 col-xs-10 noPadd">
|
||||||
|
<div className="description removePaddBottom">
|
||||||
|
Source code
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="addPaddTop">
|
||||||
|
<div className="col-md-1 col-sm-1 col-xs-2 removePaddLeft flexWidth">
|
||||||
|
<i className="em em-closed_lock_with_key"></i>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-11 col-sm-11 col-xs-10 noPadd">
|
||||||
|
<div className="description removePaddBottom">
|
||||||
|
Access to GraphQL Backend
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="addPaddTop">
|
||||||
|
<div className="col-md-1 col-sm-1 col-xs-2 removePaddLeft flexWidth">
|
||||||
|
<i className="em em-zap" />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-11 col-sm-11 col-xs-10 noPadd">
|
||||||
|
<div className="description removePaddBottom">
|
||||||
|
Full Tutorial
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="formGroupWrapper">
|
||||||
|
<div className="input-group inputGroup">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Enter your username"
|
||||||
|
value={props.username}
|
||||||
|
onChange = {(e) => props.setUsername(e.target.value)}
|
||||||
|
onKeyPress={(key) => handleKeyPress(key, insert_user, loading)}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
<div className="input-group-append groupAppend">
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-secondary"
|
||||||
|
type="submit"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (props.username.match(/^[a-z0-9_-]{3,15}$/g)) {
|
||||||
|
insert_user();
|
||||||
|
} else {
|
||||||
|
alert("Invalid username. Spaces and special characters not allowed. Please try again");
|
||||||
|
props.setUsername('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={loading || props.username === ''}
|
||||||
|
>
|
||||||
|
{ loading ? 'Please wait ...' : 'Get Started'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/*
|
||||||
|
<div className="footer">
|
||||||
|
Built with
|
||||||
|
<i className="fas fa-heart" />
|
||||||
|
by{" "}
|
||||||
|
<a
|
||||||
|
href="https://hasura.io/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Hasura
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
*/}
|
||||||
|
</div>
|
||||||
|
<div className="tutorialImg col-md-6 col-sm-6 col-xs-12 hidden-xs noPadd">
|
||||||
|
<img className="img-responsive" src={rightImg} alt="View" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</Mutation>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LandingPage.propTypes = {
|
||||||
|
auth: PropTypes.object,
|
||||||
|
isAuthenticated: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LandingPage;
|
@ -1,83 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import '../App.css';
|
|
||||||
import { Mutation } from 'react-apollo';
|
|
||||||
import gql from 'graphql-tag'
|
|
||||||
|
|
||||||
const addUser = gql`
|
|
||||||
mutation ($username: String!) {
|
|
||||||
insert_user (
|
|
||||||
objects: [{
|
|
||||||
username: $username
|
|
||||||
}]
|
|
||||||
) {
|
|
||||||
returning {
|
|
||||||
id
|
|
||||||
username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LoginComponent = (props) => {
|
|
||||||
return (
|
|
||||||
<div className="loginWrapper">
|
|
||||||
<h2 className="loginHeading"> Welcome to sample chat app made with Hasura GraphQL Engine </h2>
|
|
||||||
<div className="login">
|
|
||||||
<Mutation
|
|
||||||
mutation={addUser}
|
|
||||||
variables={{
|
|
||||||
username: props.username
|
|
||||||
}}
|
|
||||||
onCompleted={(data) => {
|
|
||||||
props.login(data.insert_user.returning[0].id);
|
|
||||||
}}
|
|
||||||
onError={() => {
|
|
||||||
props.setUsername('');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
(insert_user, { data, error, loading}) => {
|
|
||||||
if (loading) { return "Loading"; }
|
|
||||||
const errorMessage = error ?
|
|
||||||
<div className="errorMessage"> Try again with a different username </div> :
|
|
||||||
null;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{ errorMessage}
|
|
||||||
<form>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="username"
|
|
||||||
className="loginTextbox"
|
|
||||||
placeholder="Username"
|
|
||||||
autoFocus={true}
|
|
||||||
value={props.username}
|
|
||||||
onChange={(e) => props.setUsername(e.target.value)}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="loginButton"
|
|
||||||
type="submit"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (props.username.match(/^[a-z0-9_-]{3,15}$/g)) {
|
|
||||||
insert_user();
|
|
||||||
} else {
|
|
||||||
alert("Invalid username. Spaces and special characters not allowed. Please try again");
|
|
||||||
props.setUsername('');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Enter
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</Mutation>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LoginComponent;
|
|
@ -1,7 +1,8 @@
|
|||||||
import { ApolloConsumer } from 'react-apollo';
|
import { ApolloConsumer } from 'react-apollo';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Chat from './Chat';
|
import Chat from './Chat';
|
||||||
import Login from './Login';
|
// import Login from './Login';
|
||||||
|
import LandingPage from './LandingPage';
|
||||||
import '../App.css';
|
import '../App.css';
|
||||||
|
|
||||||
export default class Main extends React.Component {
|
export default class Main extends React.Component {
|
||||||
@ -39,11 +40,10 @@ export default class Main extends React.Component {
|
|||||||
<div className="app">
|
<div className="app">
|
||||||
{
|
{
|
||||||
!isLoggedIn ? (
|
!isLoggedIn ? (
|
||||||
<Login
|
<LandingPage
|
||||||
username={username}
|
|
||||||
userId={userId}
|
|
||||||
setUsername={this.setUsername}
|
setUsername={this.setUsername}
|
||||||
login={this.login}
|
login={this.login}
|
||||||
|
username={username}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ApolloConsumer>
|
<ApolloConsumer>
|
||||||
@ -66,5 +66,3 @@ export default class Main extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,13 +20,21 @@ class OnlineUsers extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
time: moment().subtract(10, 'seconds').format(),
|
time: moment().subtract(10, 'seconds').format(),
|
||||||
refetch: null
|
refetch: null,
|
||||||
|
showMobileView: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleMobileView = () => {
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
showMobileView: !this.state.showMobileView
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
|
||||||
<div className="onlineUsers">
|
const subscriptionData = (isMobileView) => (
|
||||||
<Subscription
|
<Subscription
|
||||||
subscription={fetchOnlineUsersSubscription}
|
subscription={fetchOnlineUsersSubscription}
|
||||||
>
|
>
|
||||||
@ -38,19 +46,40 @@ class OnlineUsers extends React.Component {
|
|||||||
if (error) { return "Error loading online users"; }
|
if (error) { return "Error loading online users"; }
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="userListHeading"> Online Users ({!data.user_online ? 0 : data.user_online.length})</p>
|
<p
|
||||||
<ul className="userList">
|
className={ isMobileView ? "mobileuserListHeading" : "userListHeading"}
|
||||||
|
onClick={this.toggleMobileView}
|
||||||
|
>
|
||||||
|
Online Users ({!data.user_online ? 0 : data.user_online.length}) { isMobileView && (<i className="fa fa-angle-up"></i>)}
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
((isMobileView && this.state.showMobileView) || !isMobileView) &&
|
||||||
|
(
|
||||||
|
<ul className={isMobileView ? "mobileUserList" : "userList"}>
|
||||||
{
|
{
|
||||||
data.user_online.map((u) => {
|
data.user_online.map((u) => {
|
||||||
return <li key={u.id}>{u.username}</li>
|
return <li key={u.id}>{u.username}</li>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</Subscription>
|
</Subscription>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="onlineUsers hidden-xs">
|
||||||
|
{subscriptionData(false)}
|
||||||
|
</div>
|
||||||
|
<div className="mobileonlineUsers visible-xs">
|
||||||
|
{subscriptionData(true)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,9 @@ export default class Textbox extends React.Component {
|
|||||||
(insert_message, { data, loading, error, client}) => {
|
(insert_message, { data, loading, error, client}) => {
|
||||||
const sendMessage = (e) => {
|
const sendMessage = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (this.state.text === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
insert_message();
|
insert_message();
|
||||||
this.setState({
|
this.setState({
|
||||||
text: ""
|
text: ""
|
||||||
|
BIN
community/examples/realtime-chat/src/images/React-logo.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
community/examples/realtime-chat/src/images/apollo.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
community/examples/realtime-chat/src/images/auth.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
community/examples/realtime-chat/src/images/chat-app-bg.jpg
Normal file
After Width: | Height: | Size: 444 KiB |
BIN
community/examples/realtime-chat/src/images/chat-app.png
Normal file
After Width: | Height: | Size: 477 KiB |
BIN
community/examples/realtime-chat/src/images/graphql.png
Normal file
After Width: | Height: | Size: 42 KiB |
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 220 80" style="enable-background:new 0 0 220 80;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#102954;}
|
||||||
|
.st1{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M67.4,26.8c2.1-5.2,2.2-16-0.7-24.4l0,0c-0.7-1.5-3-1.1-3.1,0.7v0.6c-0.5,7.9-3.4,12.2-7.6,14.2
|
||||||
|
c-0.7,0.3-1.8,0.2-2.5-0.2c-5.1-3.2-11-5.1-17.5-5.1s-12.4,1.9-17.5,5.1c-0.7,0.4-1.5,0.5-2.2,0.2C12,16.3,8.9,11.5,8.4,3.6V3.1
|
||||||
|
c0-1.6-2.3-2.1-3.1-0.7c-3,8.3-2.9,19.1-0.7,24.4c1.1,2.6,1.1,5.6,0.2,8.3c-1.2,3.4-1.8,7.2-1.7,11c0.3,17.4,15.1,32.2,32.4,32.4
|
||||||
|
c18.3,0.2,33.3-14.6,33.3-32.9c0-3.7-0.6-7.2-1.7-10.5C66.3,32.4,66.4,29.4,67.4,26.8z"/>
|
||||||
|
</g>
|
||||||
|
<ellipse class="st1" cx="36" cy="45.5" rx="25" ry="25"/>
|
||||||
|
<path class="st0" d="M39.9,42.9L34,33.8c-1-1.5-2.9-1.9-4.4-1c-0.9,0.6-1.5,1.6-1.5,2.7c0,0.6,0.2,1.2,0.6,1.7l4,6.2
|
||||||
|
c0.3,0.5,0.2,1.1-0.1,1.5l-6.2,6.8c-1.1,1.3-1.1,3.3,0.2,4.5c0.6,0.6,1.4,0.8,2.2,0.8c0.9,0,1.7-0.4,2.3-1.1l4.6-5.4
|
||||||
|
c0.3-0.4,1-0.4,1.3,0.1l3.3,4.7c0.2,0.3,0.5,0.7,0.9,1c1.1,0.8,2.5,0.7,3.5,0.1l0,0c0.9-0.6,1.5-1.6,1.5-2.7
|
||||||
|
c0-0.6-0.2-1.2-0.5-1.7L39.9,42.9z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M96.8,45.6h-3.9c-0.6,0-1.1-0.5-1.1-1.1V32.2c0-0.6-0.5-1.1-1.1-1.1h-3.4c-0.6,0-1.1,0.5-1.1,1.1v31.3
|
||||||
|
c0,0.6,0.5,1.1,1.1,1.1h3.4c0.6,0,1.1-0.5,1.1-1.1V51.3c0-0.6,0.5-1.1,1.1-1.1h3.9c0.6,0,1.1,0.5,1.1,1.1v12.2
|
||||||
|
c0,0.6,0.5,1.1,1.1,1.1h3.4c0.6,0,1.1-0.5,1.1-1.1V32.2c0-0.6-0.5-1.1-1.1-1.1H99c-0.6,0-1.1,0.5-1.1,1.1v12.3
|
||||||
|
C97.9,45.1,97.4,45.6,96.8,45.6z"/>
|
||||||
|
<path class="st0" d="M114.2,32l-5.5,31.3c-0.1,0.6,0.4,1.2,1,1.2h3.4c0.5,0,1-0.4,1-0.9l0.9-5.7c0.1-0.5,0.5-0.9,1-0.9h4.3
|
||||||
|
c0.5,0,1,0.4,1,0.9l1,5.8c0.1,0.5,0.5,0.9,1,0.9h3.5c0.7,0,1.2-0.6,1-1.3L122,32c-0.1-0.5-0.5-0.9-1-0.9h-5.8
|
||||||
|
C114.7,31.1,114.3,31.5,114.2,32z M119.3,52.3h-2.2c-0.7,0-1.1-0.6-1-1.2l1-11.5c0.2-1.2,1.9-1.2,2.1,0l1.1,11.5
|
||||||
|
C120.4,51.7,119.9,52.3,119.3,52.3z"/>
|
||||||
|
<path class="st0" d="M143,45.2h-3.8c-0.7,0-1.1-0.3-1.1-1.1v-7.2c0-0.7,0.4-1.1,1.1-1.1h2.2c0.7,0,1.1,0.3,1.1,1.1v3.4
|
||||||
|
c0,0.6,0.5,1.1,1.1,1.1h3.5c0.6,0,1.1-0.5,1.1-1.1v-4.2c0-3.3-1.8-5-5.3-5h-5.1c-3.5,0-5.3,1.7-5.3,5v8.6c0,3.3,1.8,5.1,5.2,5.1
|
||||||
|
h3.8c0.7,0,1.1,0.3,1.1,1.1v8c0,0.7-0.3,1.1-1.1,1.1h-2.2c-0.7,0-1.1-0.3-1.1-1.1v-3.4c0-0.6-0.5-1.1-1.1-1.1h-3.5
|
||||||
|
c-0.6,0-1.1,0.5-1.1,1.1v4.2c0,3.3,1.8,5,5.3,5h5c3.5,0,5.3-1.7,5.3-5v-9.4C148.2,46.9,146.4,45.2,143,45.2z"/>
|
||||||
|
<path class="st0" d="M164,58.8c0,0.7-0.3,1.1-1.1,1.1h-3c-0.7,0-1.1-0.3-1.1-1.1V32.2c0-0.6-0.5-1.1-1.1-1.1h-3.5
|
||||||
|
c-0.6,0-1.1,0.5-1.1,1.1v27.3c0,3.3,1.8,5,5.3,5h5.8c3.5,0,5.3-1.7,5.3-5V32.2c0-0.6-0.5-1.1-1.1-1.1h-3.3c-0.6,0-1.1,0.5-1.1,1.1
|
||||||
|
L164,58.8L164,58.8z"/>
|
||||||
|
<path class="st0" d="M191.8,46.3V36.1c0-3.3-1.8-5-5.3-5h-10c-0.6,0-1.1,0.5-1.1,1.1v31.3c0,0.6,0.5,1.1,1.1,1.1h3.4
|
||||||
|
c0.6,0,1.1-0.5,1.1-1.1v-11c0-0.6,0.5-1.1,1.1-1.1l0,0c0.4,0,0.8,0.3,1,0.7l4.4,11.8c0.2,0.4,0.6,0.7,1,0.7h3.7
|
||||||
|
c0.7,0,1.3-0.7,1-1.4L189,52.3c-0.2-0.5,0.1-1.1,0.6-1.4C190.9,50.1,191.8,48.5,191.8,46.3z M186.2,36.9v8.8
|
||||||
|
c0,0.7-0.4,1.1-1.1,1.1H182c-0.6,0-1.1-0.5-1.1-1.1v-8.8c0-0.6,0.5-1.1,1.1-1.1h3.1C185.8,35.8,186.2,36.2,186.2,36.9z"/>
|
||||||
|
<path class="st0" d="M210.2,31.1h-5.8c-0.5,0-1,0.4-1,0.9l-5.5,31.3c-0.1,0.6,0.4,1.2,1,1.2h3.4c0.5,0,1-0.4,1-0.9l0.9-5.7
|
||||||
|
c0.1-0.5,0.5-0.9,1-0.9h4.3c0.5,0,1,0.4,1,0.9l1,5.8c0.1,0.5,0.5,0.9,1,0.9h3.5c0.7,0,1.2-0.6,1-1.3L211.2,32
|
||||||
|
C211.1,31.5,210.7,31.1,210.2,31.1z M208.4,52.3h-2.1c-0.7,0-1.1-0.6-1-1.2l1-10.5c0.2-1.2,1.9-1.2,2.1,0l1.1,10.5
|
||||||
|
C209.6,51.7,209.1,52.3,208.4,52.3z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.6 KiB |