mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
community: update streaming-subscriptions-chat ui
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6082 Co-authored-by: arjunyel <11153289+arjunyel@users.noreply.github.com> Co-authored-by: Praveen Durairaju <14110316+praveenweb@users.noreply.github.com> GitOrigin-RevId: 48a02077900571686529c52e835569ad9dbc6b1c
This commit is contained in:
parent
31c94f3180
commit
bf991be955
@ -7,7 +7,7 @@ services:
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgrespassword
|
||||
graphql-engine:
|
||||
image: hasura/graphql-engine:v2.10.1.cli-migrations-v3
|
||||
image: hasura/graphql-engine:v2.12.0.cli-migrations-v3
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,10 +8,13 @@
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"date-fns": "^2.29.2",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-ws": "^5.10.0",
|
||||
"project-name-generator": "^2.1.9",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"styled-components": "^5.3.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
@ -20,9 +23,6 @@
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-plugin-graphql": "^4.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
|
@ -1,20 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
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="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" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
@ -27,19 +46,22 @@
|
||||
-->
|
||||
<title>Realtime group chat | Powered by Hasura</title>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-129818961-1"></script>
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=UA-129818961-1"
|
||||
></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-129818961-1');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
|
@ -1,21 +1,19 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Raleway:400,600');
|
||||
@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400');
|
||||
|
||||
body {
|
||||
font-family: 'Open Sans';
|
||||
font-family: 'IBM Plex Sans';
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
font-display: swap;
|
||||
}
|
||||
.noPadd
|
||||
{
|
||||
|
||||
.noPadd {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.removePaddLeft {
|
||||
padding-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
.addPaddTop
|
||||
{
|
||||
.addPaddTop {
|
||||
padding-top: 10px;
|
||||
clear: both;
|
||||
}
|
||||
@ -50,8 +48,7 @@ body {
|
||||
/* Opera 11.10+ */
|
||||
background: -o-linear-gradient(top, #a0b4cc, #c2a899);
|
||||
}
|
||||
.bgImage
|
||||
{
|
||||
.bgImage {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@ -62,16 +59,15 @@ body {
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.bgImage::before
|
||||
{
|
||||
.bgImage::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-image: linear-gradient(to bottom right,#000,#a92101);
|
||||
opacity: .9;
|
||||
background-image: linear-gradient(to bottom right, #000, #a92101);
|
||||
opacity: 0.9;
|
||||
}
|
||||
.minHeight {
|
||||
width: 100%;
|
||||
@ -80,12 +76,12 @@ body {
|
||||
.headerWrapper {
|
||||
padding: 30px 0;
|
||||
padding-left: 75px;
|
||||
min-height: 15vh
|
||||
min-height: 15vh;
|
||||
}
|
||||
.headerDescription {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
font-family: "Raleway";
|
||||
font-family: 'Raleway';
|
||||
font-weight: 700;
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
@ -106,7 +102,7 @@ body {
|
||||
padding: 10px 30px;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
font-family: "raleway";
|
||||
font-family: 'raleway';
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
@ -161,8 +157,7 @@ body {
|
||||
.appStack i {
|
||||
font-size: 16px;
|
||||
}
|
||||
.checkBox
|
||||
{
|
||||
.checkBox {
|
||||
color: #00bc00;
|
||||
font-size: 22px !important;
|
||||
}
|
||||
@ -175,29 +170,24 @@ body {
|
||||
.appStackIcon img {
|
||||
width: 70%;
|
||||
}
|
||||
.formGroupWrapper
|
||||
{
|
||||
.formGroupWrapper {
|
||||
padding-top: 20px;
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
.inputGroup
|
||||
{
|
||||
.inputGroup {
|
||||
width: 100%;
|
||||
}
|
||||
.inputGroup input
|
||||
{
|
||||
.inputGroup input {
|
||||
width: 68% !important;
|
||||
display: inline-block;
|
||||
height: 40px;
|
||||
}
|
||||
.inputGroup .groupAppend
|
||||
{
|
||||
.inputGroup .groupAppend {
|
||||
width: 32% !important;
|
||||
display: inline-block;
|
||||
}
|
||||
.groupAppend button
|
||||
{
|
||||
.groupAppend button {
|
||||
width: 100%;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
@ -206,19 +196,17 @@ body {
|
||||
text-align: center;
|
||||
background-color: #f93c18;
|
||||
color: #fff;
|
||||
font-family: "raleway";
|
||||
font-family: 'raleway';
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.groupAppend button:hover
|
||||
{
|
||||
.groupAppend button:hover {
|
||||
color: #fff;
|
||||
background-color: #e0270e;
|
||||
}
|
||||
.groupAppend button:focus
|
||||
{
|
||||
.groupAppend button:focus {
|
||||
outline: none;
|
||||
}
|
||||
.footer {
|
||||
@ -258,16 +246,6 @@ body {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: 16px;
|
||||
background-color: #fff;
|
||||
padding-left: 5px;
|
||||
margin: 20px;
|
||||
border-radius: 5px;
|
||||
width: 50%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.selfMessage {
|
||||
font-size: 16px;
|
||||
background-color: #eee;
|
||||
@ -281,33 +259,18 @@ body {
|
||||
|
||||
.newMessageEven {
|
||||
font-size: 18px;
|
||||
background-color: #98FB98;
|
||||
background-color: #98fb98;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.newMessageOdd {
|
||||
font-size: 18px;
|
||||
background-color: #8FBC8F;
|
||||
background-color: #8fbc8f;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.messageWrapperNew {
|
||||
padding-bottom: 75px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
align-self: flex-start;
|
||||
background-color: #20c40f;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
font-weight: 400;
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
font-family: 'raleway';
|
||||
color: #fff;
|
||||
padding-bottom: 35px;
|
||||
}
|
||||
|
||||
.oldNewSeparator {
|
||||
@ -316,26 +279,9 @@ body {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#chatbox {
|
||||
overflow: auto;
|
||||
height: calc(100vh - 90px);
|
||||
background-color: #f8f9f9;
|
||||
}
|
||||
|
||||
.textboxWrapper {
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
bottom: 87px;
|
||||
width: 75%;
|
||||
background-color: #fff;
|
||||
padding: 1%;
|
||||
}
|
||||
|
||||
.textbox {}
|
||||
|
||||
.sendButton {
|
||||
width: 20%;
|
||||
background-color:'green';
|
||||
background-color: 'green';
|
||||
}
|
||||
|
||||
.login {
|
||||
@ -353,7 +299,6 @@ body {
|
||||
padding: 0;
|
||||
padding-left: 10px;
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
|
||||
.typoTextbox {
|
||||
@ -370,7 +315,8 @@ body {
|
||||
background-color: #f6f6f7;
|
||||
}
|
||||
|
||||
.loginTextbox:focus, .typoTextbox:focus {
|
||||
.loginTextbox:focus,
|
||||
.typoTextbox:focus {
|
||||
outline: none;
|
||||
border-color: #016d95;
|
||||
}
|
||||
@ -408,7 +354,8 @@ body {
|
||||
background-color: #dba203;
|
||||
}
|
||||
|
||||
.loginButton:focus, .typoButton:focus {
|
||||
.loginButton:focus,
|
||||
.typoButton:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@ -452,13 +399,18 @@ body {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.onlineUsers {
|
||||
background-color: #4f5050;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
.wd45 {
|
||||
width: 45%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.messageName, .messsageTime {
|
||||
.onlineUsers {
|
||||
/* background-color: #4f5050; */
|
||||
/* height: 100vh; */
|
||||
}
|
||||
|
||||
.messageName,
|
||||
.messsageTime {
|
||||
width: 49%;
|
||||
display: inline-block;
|
||||
}
|
||||
@ -482,32 +434,17 @@ body {
|
||||
|
||||
.userList li {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.chatWrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.userListHeading {
|
||||
font-weight: 600;
|
||||
padding: 15px 10px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.typingIndicator {
|
||||
text-align: left;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 1%;
|
||||
}
|
||||
|
||||
.displayFlex
|
||||
{
|
||||
.displayFlex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@ -533,7 +470,7 @@ body {
|
||||
color: white;
|
||||
text-align: left;
|
||||
}
|
||||
.App-footer{
|
||||
.App-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
@ -544,12 +481,11 @@ body {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.footer-small-text{
|
||||
.footer-small-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.headerDescription
|
||||
{
|
||||
.headerDescription {
|
||||
text-align: center;
|
||||
}
|
||||
.headerWrapper {
|
||||
@ -563,27 +499,17 @@ body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.wd75
|
||||
{
|
||||
.wd75 {
|
||||
width: 100%;
|
||||
}
|
||||
.message
|
||||
{
|
||||
width: 90%;
|
||||
}
|
||||
.textboxWrapper
|
||||
{
|
||||
width: 100%;
|
||||
}
|
||||
.mobileview
|
||||
{
|
||||
|
||||
.mobileview {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 152px;
|
||||
width: 50%;
|
||||
}
|
||||
.mobileuserListHeading
|
||||
{
|
||||
.mobileuserListHeading {
|
||||
font-size: 14px;
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
@ -591,12 +517,10 @@ body {
|
||||
margin-bottom: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
.mobileuserListHeading i
|
||||
{
|
||||
.mobileuserListHeading i {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.mobileUserList
|
||||
{
|
||||
.mobileUserList {
|
||||
background-color: #4f5050;
|
||||
padding-inline-start: 0px;
|
||||
-webkit-padding-start: 0px;
|
||||
@ -604,19 +528,19 @@ body {
|
||||
-o-padding-start: 0px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.mobileUserList li
|
||||
{
|
||||
.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;
|
||||
@ -629,8 +553,7 @@ body {
|
||||
height: auto;
|
||||
}
|
||||
.minHeight {
|
||||
height: auto;
|
||||
min-height: 100vh;
|
||||
/* min-height: 100vh; */
|
||||
}
|
||||
.loginBtn {
|
||||
padding-right: 0;
|
||||
@ -650,17 +573,24 @@ body {
|
||||
.appStackIconWrapper {
|
||||
padding-left: 10px;
|
||||
}
|
||||
.inputGroup input
|
||||
{
|
||||
.inputGroup input {
|
||||
width: 63% !important;
|
||||
}
|
||||
.inputGroup .groupAppend
|
||||
{
|
||||
.inputGroup .groupAppend {
|
||||
width: 37% !important;
|
||||
}
|
||||
.groupAppend button {
|
||||
font-size: 13px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.chatWrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import Main from './components/Main';
|
||||
import './App.css' ;
|
||||
import './App.css';
|
||||
|
||||
const App = () => {
|
||||
return <div className="app"> <Main/> </div>;
|
||||
}
|
||||
return <Main />;
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
@ -1,10 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import { StyledBanner } from '../styles/StyledChatApp';
|
||||
import '../App.css';
|
||||
|
||||
const Banner = (props) => {
|
||||
return (
|
||||
<div className="banner" onClick={props.scrollToNewMessage}>
|
||||
<StyledBanner onClick={props.scrollToNewMessage}>
|
||||
You have {props.numOfNewMessages} new message(s)
|
||||
</div>
|
||||
</StyledBanner>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,54 @@
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { StyledBetaAccessForm } from '../styles/StyledChatApp';
|
||||
import { ExternalLinkIcon } from './ExternalLinkIcon';
|
||||
|
||||
export const BetaAccessForm = (props) => {
|
||||
useEffect(() => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://paperform.co/__embed.min.js';
|
||||
document.body.appendChild(script);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StyledBetaAccessForm>
|
||||
<div className="flex header-div">
|
||||
<img
|
||||
loading="lazy"
|
||||
alt="Hasura"
|
||||
src={
|
||||
props?.isDarkThemeActive
|
||||
? 'https://graphql-engine-cdn.hasura.io/assets/main-site/logo_primary_light.svg'
|
||||
: 'https://graphql-engine-cdn.hasura.io/assets/main-site/logo_primary_dark.svg'
|
||||
}
|
||||
className="hasura-logo-img"
|
||||
/>
|
||||
<a
|
||||
href="https://github.com/hasura/graphql-engine/tree/master/community/sample-apps/streaming-subscriptions-chat"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View Source
|
||||
<ExternalLinkIcon
|
||||
color={props.isDarkThemeActive ? '#fff' : '#2C64F4'}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<h2>Streaming Subscriptions</h2>
|
||||
<p>
|
||||
Hasura now allows you to instantly create a secure API for clients to fetch
|
||||
large amounts of data in Postgres as a continuous stream. Subscribe to Hasura Newsletter
|
||||
for updates about new features.
|
||||
</p>
|
||||
<div
|
||||
data-paperform-id="hf-streaming-chat-app"
|
||||
data-spinner="1"
|
||||
className="paperform"
|
||||
/>
|
||||
{/* <form>
|
||||
<input placeholder="Your email address" type="email" />
|
||||
<button>Get updates</button>
|
||||
</form> */}
|
||||
</StyledBetaAccessForm>
|
||||
);
|
||||
};
|
@ -29,8 +29,8 @@ function Chat(props) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ChatWrapper userId={props.userId} username={props.username} />
|
||||
<footer className="App-footer">
|
||||
<ChatWrapper userId={props.userId} username={props.username} {...props} />
|
||||
{/* <footer className="App-footer">
|
||||
<div className="hasura-logo">
|
||||
<img
|
||||
src="https://hasura.io/brand-assets/powered-by-hasura-primary-dark.svg"
|
||||
@ -57,7 +57,7 @@ function Chat(props) {
|
||||
<div className="footer-small-text">
|
||||
<span>(The database resets every 24 hours)</span>
|
||||
</div>
|
||||
</footer>
|
||||
</footer> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,32 +1,80 @@
|
||||
import { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
ThemeSwitch,
|
||||
StyledChatBox,
|
||||
StyledChatBoxFormDiv,
|
||||
} from '../styles/StyledChatApp';
|
||||
import { BetaAccessForm } from './BetaAccessForm';
|
||||
import RenderMessages from './RenderMessages';
|
||||
import Textbox from './Textbox';
|
||||
import OnlineUsers from './OnlineUsers';
|
||||
import '../App.css';
|
||||
|
||||
const StyledRightSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 30%;
|
||||
height: 100%;
|
||||
padding: 24px 0;
|
||||
justify-content: space-between;
|
||||
|
||||
@media (max-width: 1010px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const RightSection = (props) => {
|
||||
const handleOnChange = () => {
|
||||
props.toggleDarkTheme();
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledRightSection>
|
||||
<ThemeSwitch>
|
||||
<label id="switch" className="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="slider"
|
||||
onChange={handleOnChange}
|
||||
checked={props?.isDarkThemeActive ? false : true}
|
||||
/>
|
||||
<span className="slider round"></span>
|
||||
</label>
|
||||
</ThemeSwitch>
|
||||
<BetaAccessForm {...props} />
|
||||
</StyledRightSection>
|
||||
);
|
||||
};
|
||||
|
||||
export default function RenderMessagesProxy(props) {
|
||||
const [mutationCallback, setMutationCallback] = useState(null);
|
||||
const [dataStream, setDataStream] = useState(null);
|
||||
|
||||
return (
|
||||
<div className="chatWrapper">
|
||||
<div className="wd25 hidden-xs">
|
||||
<OnlineUsers userId={props.userId} username={props.username} />
|
||||
</div>
|
||||
<div className="mobileview visible-xs">
|
||||
<OnlineUsers userId={props.userId} username={props.username} />
|
||||
</div>
|
||||
<div className="wd75">
|
||||
<RenderMessages
|
||||
setMutationCallback={setMutationCallback}
|
||||
username={props.username}
|
||||
userId={props.userId}
|
||||
/>
|
||||
<Textbox
|
||||
username={props.username}
|
||||
mutationCallback={mutationCallback}
|
||||
userId={props.userId}
|
||||
/>
|
||||
</div>
|
||||
<OnlineUsers
|
||||
userId={props.userId}
|
||||
username={props.username}
|
||||
dataStream={dataStream}
|
||||
/>
|
||||
<StyledChatBox className="wd45">
|
||||
<StyledChatBoxFormDiv>
|
||||
<RenderMessages
|
||||
setMutationCallback={setMutationCallback}
|
||||
username={props.username}
|
||||
userId={props.userId}
|
||||
setDataStream={setDataStream}
|
||||
/>
|
||||
<Textbox
|
||||
username={props.username}
|
||||
mutationCallback={mutationCallback}
|
||||
userId={props.userId}
|
||||
/>
|
||||
</StyledChatBoxFormDiv>
|
||||
</StyledChatBox>
|
||||
<RightSection {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
export const ExternalLinkIcon = (props) => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.22032 14.78C5.36094 14.9205 5.55157 14.9993 5.75032 14.9993C5.94907 14.9993 6.13969 14.9205 6.28032 14.78L13.5003 7.56V13.25C13.5003 13.4489 13.5793 13.6397 13.72 13.7803C13.8606 13.921 14.0514 14 14.2503 14C14.4492 14 14.64 13.921 14.7806 13.7803C14.9213 13.6397 15.0003 13.4489 15.0003 13.25V5.75C15.0003 5.55109 14.9213 5.36032 14.7806 5.21967C14.64 5.07902 14.4492 5 14.2503 5H6.75032C6.5514 5 6.36064 5.07902 6.21999 5.21967C6.07933 5.36032 6.00032 5.55109 6.00032 5.75C6.00032 5.94891 6.07933 6.13968 6.21999 6.28033C6.36064 6.42098 6.5514 6.5 6.75032 6.5H12.4403L5.22032 13.72C5.07987 13.8606 5.00098 14.0512 5.00098 14.25C5.00098 14.4488 5.07987 14.6394 5.22032 14.78Z"
|
||||
fill={props.color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -1,12 +1,12 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
import { useRef } from "react";
|
||||
import "../App.css";
|
||||
import PropTypes from 'prop-types';
|
||||
import { gql, useMutation } from '@apollo/client';
|
||||
import { useRef } from 'react';
|
||||
import '../App.css';
|
||||
|
||||
import reactLogo from "../images/React-logo.png";
|
||||
import graphql from "../images/graphql.png";
|
||||
import hasuraLogo from "../images/green-logo-white.svg";
|
||||
import rightImg from "../images/chat-app.png";
|
||||
import reactLogo from '../images/React-logo.png';
|
||||
import graphql from '../images/graphql.png';
|
||||
import hasuraLogo from '../images/green-logo-white.svg';
|
||||
import rightImg from '../images/chat-app.png';
|
||||
|
||||
const addUser = gql`
|
||||
mutation ($username: String!) {
|
||||
@ -29,17 +29,19 @@ const LandingPage = (props) => {
|
||||
},
|
||||
onError: (err) => {
|
||||
console.log(err);
|
||||
alert("Please try again with a different username.");
|
||||
props.setUsername("");
|
||||
alert('Please try again with a different username.');
|
||||
props.setUsername('');
|
||||
},
|
||||
});
|
||||
|
||||
const handleKeyPress = (key, mutate, loading) => {
|
||||
if (!loading && key.charCode === 13) {
|
||||
mutate();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container-fluid minHeight">
|
||||
<div className="container-fluid">
|
||||
<div className="bgImage"></div>
|
||||
<div>
|
||||
<div className="headerWrapper">
|
||||
@ -167,7 +169,7 @@ const LandingPage = (props) => {
|
||||
disabled={loading}
|
||||
minLength={3}
|
||||
maxLength={15}
|
||||
pattern={"^[a-z0-9_-]{3,15}$"}
|
||||
pattern={'^[a-z0-9_-]{3,15}$'}
|
||||
ref={usernameInput}
|
||||
/>
|
||||
<div className="input-group-append groupAppend">
|
||||
@ -180,18 +182,18 @@ const LandingPage = (props) => {
|
||||
addUserHandler();
|
||||
} else {
|
||||
alert(
|
||||
"Invalid username. Spaces and special characters not allowed. Please try again"
|
||||
'Invalid username. Spaces and special characters not allowed. Please try again'
|
||||
);
|
||||
props.setUsername("");
|
||||
props.setUsername('');
|
||||
}
|
||||
}}
|
||||
disabled={
|
||||
loading ||
|
||||
props.username === "" ||
|
||||
props.username === '' ||
|
||||
!usernameInput.current.validity.valid
|
||||
}
|
||||
>
|
||||
{loading ? "Please wait ..." : "Get Started"}
|
||||
{loading ? 'Please wait ...' : 'Get Started'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,35 +1,142 @@
|
||||
import { useState } from 'react';
|
||||
import { ApolloConsumer } from '@apollo/client';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { ApolloConsumer, gql, useMutation } from '@apollo/client';
|
||||
import generateUsername from 'project-name-generator';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { darkTheme, lightTheme } from '../styles/theme';
|
||||
import Chat from './Chat';
|
||||
import LandingPage from './LandingPage';
|
||||
// import LandingPage from './LandingPage';
|
||||
import '../App.css';
|
||||
|
||||
export default function Main() {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const [username, setUsername] = useState('');
|
||||
const StyledApp = styled.div`
|
||||
background: ${({ theme }) => theme.colors.background};
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
padding: 0 24px;
|
||||
overflow-y: auto;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
background: ${({ theme }) =>
|
||||
theme.name === 'dark' ? '#1c262f' : '#FAFAFA'};
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: ${({ theme }) =>
|
||||
theme.name === 'dark' ? '#394e60' : '#A6B6C4'};
|
||||
border-radius: 80px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
@media (min-width: 1250px) {
|
||||
background-image: url('https://graphql-engine-cdn.hasura.io/assets/main-site/hasura_sf_illus.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 95% 10%;
|
||||
}
|
||||
|
||||
@media (min-width: 1250px) and (max-width: 1450px) {
|
||||
background-position: 92% 10%;
|
||||
background-size: 250px;
|
||||
}
|
||||
|
||||
@media (max-width: 590px) {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const addUser = gql`
|
||||
mutation ($username: String!) {
|
||||
insert_user_one(
|
||||
object: { username: $username }
|
||||
on_conflict: { constraint: user_username_key, update_columns: [] }
|
||||
) {
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Main(props) {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(true);
|
||||
const [username, setUsername] = useState(generateUsername().dashed);
|
||||
const [userId, setUserId] = useState(null);
|
||||
|
||||
const [isDarkThemeActive, toggleActiveTheme] = useState(true);
|
||||
|
||||
const [newUserHandler] = useMutation(addUser, {
|
||||
variables: {
|
||||
username,
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (data.insert_user_one?.id) {
|
||||
setUserId(data.insert_user_one.id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// check usernme and perform login
|
||||
const login = (id) => {
|
||||
setIsLoggedIn(true);
|
||||
setUserId(id);
|
||||
};
|
||||
|
||||
const toggleDarkTheme = () => {
|
||||
toggleActiveTheme(!isDarkThemeActive);
|
||||
|
||||
window.localStorage.setItem(
|
||||
'isDarkThemeActive',
|
||||
JSON.stringify(!isDarkThemeActive)
|
||||
);
|
||||
};
|
||||
|
||||
// const retrieveActiveTheme = () => {
|
||||
// const isDarkThemeActive = JSON.parse(
|
||||
// window.localStorage.getItem('isDarkThemeActive')
|
||||
// );
|
||||
|
||||
// // toggleDarkTheme({ isDarkThemeActive });
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// retrieveActiveTheme();
|
||||
// }, []);
|
||||
|
||||
const currentActiveTheme = isDarkThemeActive ? darkTheme : lightTheme;
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId) {
|
||||
newUserHandler();
|
||||
}
|
||||
}, [newUserHandler, userId]);
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
{!isLoggedIn ? (
|
||||
<LandingPage
|
||||
setUsername={setUsername}
|
||||
login={login}
|
||||
username={username}
|
||||
/>
|
||||
) : (
|
||||
<ApolloConsumer>
|
||||
{(client) => {
|
||||
return <Chat userId={userId} username={username} client={client} />;
|
||||
}}
|
||||
</ApolloConsumer>
|
||||
)}
|
||||
</div>
|
||||
<ThemeProvider theme={currentActiveTheme}>
|
||||
<StyledApp className="app">
|
||||
{!userId ? (
|
||||
<div>Loading</div>
|
||||
) : (
|
||||
// <LandingPage
|
||||
// setUsername={setUsername}
|
||||
// login={login}
|
||||
// username={username}
|
||||
// />
|
||||
<ApolloConsumer>
|
||||
{(client) => {
|
||||
return (
|
||||
<Chat
|
||||
userId={userId}
|
||||
username={username}
|
||||
client={client}
|
||||
isDarkThemeActive={isDarkThemeActive}
|
||||
toggleDarkTheme={toggleDarkTheme}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</ApolloConsumer>
|
||||
)}
|
||||
</StyledApp>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,27 +1,88 @@
|
||||
import "../App.js";
|
||||
import "../App.css";
|
||||
import formatDistanceToNow from "date-fns/formatDistanceToNow";
|
||||
import '../App.js';
|
||||
import '../App.css';
|
||||
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
||||
|
||||
import { StyledMessage } from '../styles/StyledChatApp.js';
|
||||
|
||||
const alphabetsArr = [
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm',
|
||||
'n',
|
||||
'o',
|
||||
'p',
|
||||
'q',
|
||||
'r',
|
||||
's',
|
||||
't',
|
||||
'u',
|
||||
'v',
|
||||
'w',
|
||||
'x',
|
||||
'y',
|
||||
'z',
|
||||
];
|
||||
|
||||
export const getUserBgColor = (username) => {
|
||||
if (username && username?.constructor?.name === 'String') {
|
||||
const firstChar = username.charAt(0).toLowerCase();
|
||||
|
||||
const charIndex = alphabetsArr.indexOf(firstChar);
|
||||
|
||||
if (charIndex <= 4) {
|
||||
return '#638FFF';
|
||||
}
|
||||
|
||||
if (charIndex <= 8) {
|
||||
return '#F669A1';
|
||||
}
|
||||
|
||||
if (charIndex <= 12) {
|
||||
return '#A36FF8';
|
||||
}
|
||||
|
||||
if (charIndex <= 16) {
|
||||
return '#FFC960';
|
||||
}
|
||||
|
||||
if (charIndex <= 20) {
|
||||
return '#39DAAA';
|
||||
}
|
||||
|
||||
if (charIndex <= 27) {
|
||||
return '#45D7F6';
|
||||
}
|
||||
}
|
||||
|
||||
return '#4F6C86';
|
||||
};
|
||||
|
||||
export default function MessageList(props) {
|
||||
return (
|
||||
<div className={props.isNew ? "messageWrapperNew" : "messageWrapper"}>
|
||||
<div className={props.isNew ? 'messageWrapperNew' : 'messageWrapper'}>
|
||||
{props.messages.map((m) => {
|
||||
return (
|
||||
<div key={m.id} className="message">
|
||||
<StyledMessage key={m.id} bgColor={getUserBgColor(m.username)}>
|
||||
<div className="messageNameTime">
|
||||
<div className="messageName">
|
||||
<b>{m.username}</b>
|
||||
</div>
|
||||
<div className="messsageTime">
|
||||
<i>
|
||||
{formatDistanceToNow(new Date(m.timestamp), {
|
||||
addSuffix: true,
|
||||
})}{" "}
|
||||
</i>
|
||||
</div>
|
||||
<div className="messageName">{m.username.substring(0, 2)}</div>
|
||||
<div className="messageText">{m.text}</div>
|
||||
</div>
|
||||
<div className="messageText">{m.text}</div>
|
||||
</div>
|
||||
<div className="time_stamp">
|
||||
{formatDistanceToNow(new Date(m.timestamp), {
|
||||
addSuffix: true,
|
||||
})}{' '}
|
||||
</div>
|
||||
</StyledMessage>
|
||||
);
|
||||
})}
|
||||
<div style={{ height: 0 }} id="lastMessage"></div>
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { gql, useSubscription } from '@apollo/client';
|
||||
|
||||
import {
|
||||
StyledLeftSection,
|
||||
StyledOnlineUsers,
|
||||
StyledOnlineUserCircle,
|
||||
} from '../styles/StyledChatApp';
|
||||
import { getUserBgColor } from './MessageList';
|
||||
|
||||
const fetchOnlineUsersSubscription = gql`
|
||||
subscription {
|
||||
user_online(order_by: { username: asc }) {
|
||||
@ -10,17 +17,42 @@ const fetchOnlineUsersSubscription = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
function OnlineUsers() {
|
||||
function OnlineUsers({ dataStream }) {
|
||||
const [showMobileView, setMobileView] = useState(false);
|
||||
|
||||
const [showMobileMenu, toggleMobileMenu] = useState(false);
|
||||
|
||||
const { data } = useSubscription(fetchOnlineUsersSubscription);
|
||||
|
||||
const toggleMobileView = () => {
|
||||
setMobileView(!showMobileView);
|
||||
};
|
||||
|
||||
const subscriptionStreamData = (isMobileView) => (
|
||||
<StyledOnlineUsers>
|
||||
<p
|
||||
className={isMobileView ? 'mobileuserListHeading' : 'userListHeading'}
|
||||
onClick={toggleMobileView}
|
||||
>
|
||||
Subscription Stream
|
||||
{isMobileView && <i className="fa fa-angle-up"></i>}
|
||||
</p>
|
||||
{((isMobileView && showMobileView) || !isMobileView) && (
|
||||
<ul
|
||||
className={
|
||||
isMobileView ? 'mobileUserList' : 'userList subscription-stream'
|
||||
}
|
||||
>
|
||||
<p className="subscription-stream">
|
||||
{JSON.stringify(dataStream, undefined, 4)}
|
||||
</p>
|
||||
</ul>
|
||||
)}
|
||||
</StyledOnlineUsers>
|
||||
);
|
||||
|
||||
const subscriptionData = (isMobileView) => (
|
||||
<div>
|
||||
<StyledOnlineUsers>
|
||||
<p
|
||||
className={isMobileView ? 'mobileuserListHeading' : 'userListHeading'}
|
||||
onClick={toggleMobileView}
|
||||
@ -31,20 +63,59 @@ function OnlineUsers() {
|
||||
{((isMobileView && showMobileView) || !isMobileView) && (
|
||||
<ul className={isMobileView ? 'mobileUserList' : 'userList'}>
|
||||
{data?.user_online.map((u) => {
|
||||
return <li key={u.id}>{u.username}</li>;
|
||||
return (
|
||||
<StyledOnlineUserCircle
|
||||
bgColor={getUserBgColor(u.username)}
|
||||
key={u.id}
|
||||
>
|
||||
{u.username.substring(0, 2)}
|
||||
</StyledOnlineUserCircle>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</StyledOnlineUsers>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="onlineUsers hidden-xs">{subscriptionData(false)}</div>
|
||||
<div className="mobileonlineUsers visible-xs">
|
||||
{subscriptionData(true)}
|
||||
<StyledLeftSection showMobileMenu={showMobileMenu}>
|
||||
{/* Mobile View */}
|
||||
<div className="mobile-header hide-on-desk">
|
||||
<a href="https://hasura.io/">
|
||||
<img
|
||||
loading="lazy"
|
||||
alt="Hasura"
|
||||
src="https://graphql-engine-cdn.hasura.io/assets/main-site/logo_primary_light.svg"
|
||||
className="hasura-logo-img"
|
||||
/>
|
||||
</a>
|
||||
{!showMobileMenu && (
|
||||
<div className="flex-div">
|
||||
<p onClick={() => toggleMobileMenu(true)}>
|
||||
Online Users (
|
||||
{!data?.user_online ? 0 : data?.user_online.length}){' '}
|
||||
</p>
|
||||
<i
|
||||
className={showMobileMenu ? 'fa fa-angle-down' : 'fa fa-angle-up'}
|
||||
></i>
|
||||
</div>
|
||||
)}
|
||||
{showMobileMenu && (
|
||||
<div className="mobile-data-wrapper">
|
||||
<p className="close-btn" onClick={() => toggleMobileMenu(false)}>
|
||||
<i className="fa fa-times"></i>
|
||||
</p>
|
||||
<div className="onlineUsers">{subscriptionData(false)}</div>
|
||||
<div className="onlineUsers">{subscriptionStreamData(false)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* ************ */}
|
||||
<div className="onlineUsers hideOnMobile">
|
||||
{subscriptionStreamData(false)}
|
||||
</div>
|
||||
<div className="onlineUsers hideOnMobile">{subscriptionData(false)}</div>
|
||||
</StyledLeftSection>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { gql, useLazyQuery, useQuery, useSubscription } from "@apollo/client";
|
||||
import "../App.js";
|
||||
import Banner from "./Banner";
|
||||
import MessageList from "./MessageList";
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { gql, useLazyQuery, useQuery, useSubscription } from '@apollo/client';
|
||||
|
||||
import '../App.js';
|
||||
import Banner from './Banner';
|
||||
import MessageList from './MessageList';
|
||||
import { StyledMessagesList } from '../styles/StyledChatApp.js';
|
||||
|
||||
const fetchOldMessages = gql`
|
||||
query ($last_received_ts: timestamptz) {
|
||||
@ -48,13 +50,14 @@ export default function RenderMessages({
|
||||
setMutationCallback,
|
||||
username,
|
||||
userId,
|
||||
setDataStream,
|
||||
}) {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [newMessages, setNewMessages] = useState([]);
|
||||
const [bottom, setBottom] = useState(true);
|
||||
const [initialLoad, setInitialLoad] = useState(false);
|
||||
const [initialTimestamp, setInitialTimestamp] = useState(
|
||||
"2018-08-21T19:58:46.987552+00:00"
|
||||
'2018-08-21T19:58:46.987552+00:00'
|
||||
);
|
||||
|
||||
const listInnerRef = useRef();
|
||||
@ -71,7 +74,7 @@ export default function RenderMessages({
|
||||
addOldMessages(data.message);
|
||||
setInitialLoad(true);
|
||||
setInitialTimestamp(
|
||||
data.message[0]?.timestamp || "2018-08-21T19:58:46.987552+00:00"
|
||||
data.message[0]?.timestamp || '2018-08-21T19:58:46.987552+00:00'
|
||||
);
|
||||
},
|
||||
});
|
||||
@ -93,6 +96,7 @@ export default function RenderMessages({
|
||||
onSubscriptionData: ({ subscriptionData }) => {
|
||||
if (!loading) {
|
||||
if (subscriptionData.data) {
|
||||
setDataStream(subscriptionData.data);
|
||||
if (!isViewScrollable()) {
|
||||
addOldMessages(subscriptionData.data.message_stream);
|
||||
} else {
|
||||
@ -110,15 +114,15 @@ export default function RenderMessages({
|
||||
// scroll to bottom
|
||||
const scrollToBottom = () => {
|
||||
document
|
||||
?.getElementById("lastMessage")
|
||||
?.scrollIntoView({ behavior: "instant" });
|
||||
?.getElementById('lastMessage')
|
||||
?.scrollIntoView({ behavior: 'instant' });
|
||||
};
|
||||
|
||||
// scroll to the new message
|
||||
const scrollToNewMessage = () => {
|
||||
document
|
||||
?.getElementById("newMessage")
|
||||
?.scrollIntoView({ behavior: "instant" });
|
||||
?.getElementById('newMessage')
|
||||
?.scrollIntoView({ behavior: 'instant' });
|
||||
};
|
||||
|
||||
if (newMessages.length === 0 && bottom) {
|
||||
@ -177,26 +181,26 @@ export default function RenderMessages({
|
||||
(window.innerWidth || document.documentElement.clientWidth)
|
||||
);
|
||||
};
|
||||
if (document.getElementById("lastMessage")) {
|
||||
return !isInViewport(document.getElementById("lastMessage"));
|
||||
if (document.getElementById('lastMessage')) {
|
||||
return !isInViewport(document.getElementById('lastMessage'));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="chatbox" onScroll={handleScroll} ref={listInnerRef}>
|
||||
<StyledMessagesList onScroll={handleScroll} ref={listInnerRef}>
|
||||
{/* show "unread messages" banner if not at bottom */}
|
||||
{!bottom && newMessages.length > 0 && isViewScrollable() ? (
|
||||
{!bottom && newMessages.length > 0 && isViewScrollable() && (
|
||||
<Banner
|
||||
scrollToNewMessage={scrollToNewMessage}
|
||||
numOfNewMessages={newMessages.length}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
|
||||
<div
|
||||
{/* <div
|
||||
style={{
|
||||
margin: "auto",
|
||||
textAlign: "center",
|
||||
margin: 'auto',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
@ -207,20 +211,20 @@ export default function RenderMessages({
|
||||
}
|
||||
disabled={loadingOldMessages}
|
||||
>
|
||||
{loadingOldMessages === true ? "Loading..." : "Load More"}{" "}
|
||||
{loadingOldMessages === true ? 'Loading...' : 'Load More'}{' '}
|
||||
</button>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* Render old messages */}
|
||||
<MessageList messages={messages} isNew={false} username={username} />
|
||||
{/* Show old/new message separation */}
|
||||
<div id="newMessage" className="oldNewSeparator">
|
||||
{newMessages.length !== 0 ? "New messages" : null}
|
||||
{newMessages.length !== 0 ? 'New messages' : null}
|
||||
</div>
|
||||
|
||||
{/* render new messages */}
|
||||
<MessageList messages={newMessages} isNew={true} username={username} />
|
||||
{/* Bottom div to scroll to */}
|
||||
</div>
|
||||
</StyledMessagesList>
|
||||
);
|
||||
}
|
||||
|
@ -57,22 +57,21 @@ export default function Textbox(props) {
|
||||
return (
|
||||
<form onSubmit={sendMessage}>
|
||||
<div className="textboxWrapper">
|
||||
<TypingIndicator userId={props.userId} />
|
||||
<input
|
||||
id="textbox"
|
||||
className="textbox typoTextbox"
|
||||
value={text}
|
||||
autoFocus={true}
|
||||
placeholder="Message your friends here.."
|
||||
onChange={(e) => {
|
||||
handleTyping(e.target.value, client.mutate);
|
||||
}}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<button className="sendButton typoButton" onClick={sendMessage}>
|
||||
{' '}
|
||||
Send{' '}
|
||||
</button>
|
||||
<div className="form-btn-div">
|
||||
<button onClick={sendMessage}> Send </button>
|
||||
</div>
|
||||
</div>
|
||||
<TypingIndicator userId={props.userId} />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@ -95,5 +94,6 @@ export default function Textbox(props) {
|
||||
insertMessageHandler();
|
||||
setText('');
|
||||
};
|
||||
|
||||
return form(sendMessage, client);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import React from 'react';
|
||||
import { gql, useSubscription } from '@apollo/client';
|
||||
import { StyledTypingIndicator } from '../styles/StyledChatApp';
|
||||
import '../App.css';
|
||||
|
||||
const getUserTyping = gql`
|
||||
@ -28,11 +30,11 @@ function TypingIndicator(props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="typingIndicator">
|
||||
<StyledTypingIndicator>
|
||||
{data?.user_typing?.length === 0
|
||||
? ''
|
||||
? ``
|
||||
: `${data.user_typing[0].username} is typing ...`}
|
||||
</div>
|
||||
</StyledTypingIndicator>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,588 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ThemeSwitch = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
margin-right: 10%;
|
||||
margin-bottom: 10px;
|
||||
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 72px;
|
||||
height: 36px;
|
||||
padding: 2.5px;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #1c262f;
|
||||
-webkit-transition: 0.4s;
|
||||
background: #1c262f;
|
||||
/* box-shadow: 3.21429px 1.28571px 8.35714px -1.28571px rgba(0, 0, 0, 0.3),
|
||||
0px 2.57143px 3.85714px -0.642857px rgba(0, 0, 0, 0.1); */
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
left: 3px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
box-shadow: 3.21429px 1.28571px 8.35714px -1.28571px rgba(0, 0, 0, 0.3),
|
||||
0px 2.57143px 3.85714px -0.642857px rgba(0, 0, 0, 0.1);
|
||||
background: #344658
|
||||
url('https://graphql-engine-cdn.hasura.io/assets/main-site/bulb_night.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
box-shadow: inset 0px 0px 1.28571px rgba(0, 0, 0, 0.15);
|
||||
background: #e7eef3;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(36px);
|
||||
-ms-transform: translateX(36px);
|
||||
transform: translateX(36px);
|
||||
background: #fff
|
||||
url('https://graphql-engine-cdn.hasura.io/assets/main-site/bulb_day.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledBetaAccessForm = styled.div`
|
||||
border-radius: 8px;
|
||||
padding: 7%;
|
||||
margin-left: 24px;
|
||||
|
||||
background: ${({ theme }) => theme.colors.sectionBg};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow};
|
||||
|
||||
.header-div {
|
||||
min-height: 45px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.colors.anchor};
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
|
||||
svg {
|
||||
min-width: 20px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: 31px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
input {
|
||||
border: 1px solid;
|
||||
outline: none;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
background: ${({ theme }) => theme.colors.sectionBg};
|
||||
border-color: ${({ theme }) => theme.colors.border};
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background: #1eb4d4;
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
margin-top: 16px;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.hasura-logo-img {
|
||||
min-width: 137px;
|
||||
width: 137px;
|
||||
max-width: 137px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 40px;
|
||||
font-size: 24px;
|
||||
line-height: 1.25;
|
||||
font-weight: 700;
|
||||
color: ${({ theme }) => theme.colors.heading};
|
||||
/* margin-bottom: 8px; */
|
||||
}
|
||||
|
||||
p {
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.hasura-logo-img {
|
||||
min-width: 100px;
|
||||
width: 100px;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.header-div {
|
||||
a {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
svg {
|
||||
min-width: 15px;
|
||||
width: 15px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.paperform {
|
||||
color: white !important;
|
||||
|
||||
input {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.Paperform__Container {
|
||||
.LiveField__answer {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
input {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledChatBox = styled.div`
|
||||
height: 100vh;
|
||||
padding: 24px 0;
|
||||
overflow: hidden;
|
||||
|
||||
@media (min-width: 600px) and (max-width: 1010px) {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
@media (max-width: 590px) {
|
||||
width: 100% !important;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledChatBoxFormDiv = styled.div`
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
/* padding: 32px; */
|
||||
box-shadow: ${({ theme }) => theme.boxShadow};
|
||||
display: flex;
|
||||
overflow-y: auto;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
background: ${({ theme }) => theme.colors.sectionBg};
|
||||
|
||||
.textboxWrapper {
|
||||
display: flex;
|
||||
border: 1px solid;
|
||||
border-color: ${({ theme }) => theme.colors.border};
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
form {
|
||||
background: ${({ theme }) => theme.colors.headerBg};
|
||||
min-height: 128px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24px 24px 32px 24px;
|
||||
|
||||
input {
|
||||
height: 72px;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
background: ${({ theme }) => theme.colors.formInput};
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 8px 12px;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
}
|
||||
|
||||
.form-btn-div {
|
||||
min-width: 120px;
|
||||
height: 72px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button {
|
||||
min-width: 88px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
background: #344658;
|
||||
color: #fff;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.textboxWrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 590px) {
|
||||
form {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledLeftSection = styled.div`
|
||||
width: 25%;
|
||||
padding-top: 24px;
|
||||
height: 100vh;
|
||||
/* overflow-y: auto; */
|
||||
margin-right: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
@media (min-width: 591px) {
|
||||
.hide-on-desk {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 590px) {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
|
||||
.hideOnMobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-header {
|
||||
width: 100%;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
/* background: ${({ theme }) => theme.colors.background}; */
|
||||
background: #23303d;
|
||||
|
||||
z-index: 100;
|
||||
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
flex-direction: ${(props) => (props?.showMobileMenu ? 'column' : '')};
|
||||
|
||||
justify-content: space-between;
|
||||
padding: 24px 24px 0;
|
||||
|
||||
min-height: ${(props) => (props?.showMobileMenu ? '100vh' : '80px')};
|
||||
height: ${(props) => (props?.showMobileMenu ? '100%' : '80px')};
|
||||
overflow-y: scroll;
|
||||
|
||||
.mobile-data-wrapper {
|
||||
width: 100%;
|
||||
margin-top: 35px;
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 35px;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.userList {
|
||||
min-height: 30vh;
|
||||
max-height: 30vh;
|
||||
}
|
||||
}
|
||||
|
||||
.hasura-logo-img {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.flex-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
|
||||
p {
|
||||
margin-top: -2px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
margin-left: 6px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledOnlineUsers = styled.div`
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
margin-bottom: 7%;
|
||||
|
||||
.userListHeading {
|
||||
font-weight: 500;
|
||||
padding: 20px 13px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
background: ${({ theme }) => theme.colors.headerBg};
|
||||
border-radius: 12px 12px 0px 0px;
|
||||
color: ${({ theme }) => theme.colors.heading};
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.userList {
|
||||
padding: 25px 20px;
|
||||
display: flex;
|
||||
height: 40%;
|
||||
min-height: 38.2vh;
|
||||
max-height: 38.2vh;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
background: ${({ theme }) => theme.colors.sectionBg};
|
||||
border-radius: 0px 0px 12px 12px;
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
min-width: 48px;
|
||||
width: 48px;
|
||||
min-height: 48px;
|
||||
height: 48px;
|
||||
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
background: ${({ theme }) =>
|
||||
theme.name === 'dark' ? '#1c262f' : '#FAFAFA'};
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: ${({ theme }) =>
|
||||
theme.name === 'dark' ? '#394e60' : '#A6B6C4'};
|
||||
border-radius: 80px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.subscription-stream {
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (min-width: 1560px) {
|
||||
.userList {
|
||||
min-height: 40vh;
|
||||
max-height: 40vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
.userList {
|
||||
min-height: 35vh;
|
||||
max-height: 35vh;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledMessage = styled.div`
|
||||
width: 92%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
min-height: 48px;
|
||||
height: auto;
|
||||
align-items: center;
|
||||
|
||||
font-size: 16px;
|
||||
margin: 20px;
|
||||
border-radius: 5px;
|
||||
|
||||
.time_stamp {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: ${({ theme }) => theme.colors.timeStamp};
|
||||
font-style: normal;
|
||||
/* line-height: 1.7; */
|
||||
}
|
||||
|
||||
.messageNameTime {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.messageName {
|
||||
min-width: 48px;
|
||||
width: 48px;
|
||||
min-height: 48px;
|
||||
height: 48px;
|
||||
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #f669a1;
|
||||
background: ${(props) => props?.bgColor};
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.messageText {
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledMessagesList = styled.div`
|
||||
height: calc(100% - 120px);
|
||||
overflow-y: auto;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
background: ${({ theme }) =>
|
||||
theme.name === 'dark' ? '#1c262f' : '#FAFAFA'};
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: ${({ theme }) =>
|
||||
theme.name === 'dark' ? '#394e60' : '#A6B6C4'};
|
||||
border-radius: 80px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
#newMessage {
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledTypingIndicator = styled.div`
|
||||
font-style: italic;
|
||||
color: #4f6c86;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 32px;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
bottom: 25px;
|
||||
|
||||
@media (min-width: 1450px) {
|
||||
bottom: 30px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledBanner = styled.div`
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 24px;
|
||||
background-color: #0c9a70;
|
||||
cursor: pointer;
|
||||
font-weight: 400;
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
min-width: 213px;
|
||||
width: 40%;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
margin: 24px auto 0;
|
||||
box-shadow: 0px 8px 10px -6px rgba(0, 0, 0, 0.1),
|
||||
0px 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 68px;
|
||||
`;
|
||||
|
||||
export const StyledOnlineUserCircle = styled.li`
|
||||
background: ${(props) => props?.bgColor};
|
||||
`;
|
@ -0,0 +1,38 @@
|
||||
// Light Theme
|
||||
|
||||
export const lightTheme = {
|
||||
colors: {
|
||||
background: '#F3F5F7',
|
||||
sectionBg: '#fff',
|
||||
heading: '#141C22',
|
||||
text: '#141C22',
|
||||
border: '#C4CED7',
|
||||
anchor: '#2C64F4',
|
||||
headerBg: '#E7EEF3',
|
||||
formInput: '#fff',
|
||||
timeStamp: '#A6B6C4',
|
||||
},
|
||||
name: 'light',
|
||||
boxShadow:
|
||||
'0px 1px 2px -1px rgba(0, 0, 0, 0.1), 0px 1px 3px rgba(0, 0, 0, 0.1)',
|
||||
};
|
||||
|
||||
// **************************************** //
|
||||
|
||||
// Dark Theme
|
||||
|
||||
export const darkTheme = {
|
||||
colors: {
|
||||
background: '#0C1015',
|
||||
sectionBg: '#141C22',
|
||||
headerBg: '#1C262F',
|
||||
heading: '#fff',
|
||||
text: '#A6B6C4',
|
||||
border: '#344658',
|
||||
anchor: '#fff',
|
||||
formInput: '#23303D',
|
||||
timeStamp: '#4F6C86',
|
||||
},
|
||||
name: 'dark',
|
||||
boxShadow: '',
|
||||
};
|
Loading…
Reference in New Issue
Block a user