mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-13 12:33:25 +02:00
Use new frontend (#1941)
* Use new frontend * Remove whitespace * Remove more whitespace * Remove more whitespace * Add requests file * Try loading squeaks in home page * Got basic timeline working * Fix block time display * Show author pubkey in tweet card * Show profile name in tweet card * Show profile image in tweet card * Got tweet page working * Implement part of profile page * Show profile pubkey in pubkey page * Show profile name in profile page * User tweets now show in profile page * Show reply tweets * Load ancestor tweets, but show still not working * Got tweet reply thread working * Add partial implementation of make squeak * Add re-usable component for make squeak * Load signing profiles in middleware * Implement make squeak * Update favicon * Fix issue with updating state of replies on new squeak. * Remove author pubkey from squeak url path * Got reply squeak working * Fix update state with reducer on new reply * Show author profile image in make squeak component * Implement get more tweets in timeline * Fix load more tweets * Show loading state in timeline get mor * Clear timeline tweets on page load * Simplify props in make squeak component * Remove unused props from make squeak component * Fix params for tweet cards in profile page * Got follow and unfollow working * Fix tweets and replies tab in profile page * Got load more tweets working for profile page * Add delay to endpoints * Clear user tweets on profile page load * Remove delay from endpoint * Remove whitespace in explore page * Show signing profiles in explore page * Show contact profiles in explore page * Rename explore page to profiles * Remove trends from profiles page * Remove search tab from profiles page * Fix set follow or unfollow from profiles page * Change text of profile search box * Implement name change in edit profile * Simplify edit and follow buttons * Remove chat button in profile * Simplify user pubkey display in profile * Remove bio and location from profile edit * Remove banner from profile page * Got image change working * Remove log lines * Remove unused state from profile * Remove more state from profile page * Remove content of messages page * Remove unused state from profiles page * Remove unused import from profile page * Rename sats earned and sats spent tabs in profile page * Remove log line * Add create signing profile to profiles page * Remove banner from new profile modal * Add create contact profile to profiles page * Add comment for modal in profile page * Use separate modals in profile page for edit and spending * Add delete profile button to profile pag * Remove use of bookmark * Remove banner from delete profile modal * Implement delete tweet * Fix delete tweet for tweet card. * Add TODO comment * Got update tweet for like and unlike working * Simplify update tweet and delete tweet reducerts * Add comments to tweet component * Add comment to reducers * Remove check for user in tweet card. * Simplify props for tweet card. * Implement missing tweet component partially * Got display for missing tweet mostly working * Fix reducer for delete tweet from ancestors * Fix display of squeak with unknown author * Fix handling of unknown author in make squeak to reply * Got profile page working for missing profile * Implement create profile from missing profile page * Fix close make tweet modal on submit * Remove who to follow from feed * Fix close modal on submit new tweet in tweet card * Add new peers page * Got connected peers showing * Remove search bar from peers page * Show saved peers in peers page * Got save peer working * Remove input attach button from make squeak component * Got basic peer page working * Load peer connection info in peer page * Remove unused functions in peer page * Show correct host and port for peer and peers page * Got connect and disconnect working in peer page * Got connect and disconnect reducer working * Remove unused displays from peer page * Change word in connect button in peer page * Got tor toggle working * Add use tor option to create saved peer modal * Implement delete saved peer * Fix variable names in peers page * Fix issue with save peer * Fix action for save peer * Show block info and block explorer link * Get network in tweet page * Remove log line * Fix color scheme for missing tweet * Add download button to missing squeak * Show locked content in tweet and tweet card * Partial implementation of buy dialog * Got buy squeak working * Reload ancestors in download tweet middleware * Improve display of offer * Show payment summary in sidebar * Implemented beginning of payments page * Show sent payments in payments page * Implement clear sent payments * Fix link to payments in feed * Got received payments working * Fix action for load more received payments * Fix typo in get more received payments * Remove log lines * Add header title for peers page * Remove unused pages from nav * Show alert when payment fails * Use more menu in profile page * Remove icon from more button * Add export private key * Hide export private key for contact profile * Remove log lines * Got import signing profile working * Remove log lines * Improve display of locked content * Fix comment for edit moda * Add download squeaks action to profile page * Remove log lines * Show button for download action in profile page * Include time in payments display * Remove bookmark component * Remove list component * Remove chat component * Remove retweet action * Remove all use of account attributes * Change unit to sats for sell dialog * Fix display of peer page * Remove usage of account state * Try to implement logout * Include prefix in route path * Got redirect on logout working * Got logout working in prod * Remove log lines * Remove suggestions action * Show connection info * Got autoconnect toggle working * Rearrange peer page * Got custom icon working * Use custom icon * Fix path string match in nav * Add modal to show external address in peers page * Remove export button from external address modal * Update frontend build * Fix more menu in profile page * Update frontend build
This commit is contained in:
parent
ac3279c532
commit
74a0ee7079
372 changed files with 27872 additions and 64548 deletions
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Flatlogic LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -1,29 +1,17 @@
|
|||
# frontend
|
||||
# Twitter-Clone
|
||||
|
||||
## Run in dev mode
|
||||
A website I build based on twitter please enjoy. This website is Not for actual usage only learning purposes.
|
||||
|
||||
- Start bitcoin-core and lnd in docker-compose:
|
||||
```
|
||||
cd docker
|
||||
docker-compose up
|
||||
```
|
||||
- Start the backend with the `SQUEAKNODE_WEBADMIN_ENABLED`, `SQUEAKNODE_WEBADMIN_LOGIN_DISABLED`, and `SQUEAKNODE_WEBADMIN_ALLOW_CORS` environment variables:
|
||||
```
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install .
|
||||
SQUEAKNODE_WEBADMIN_ENABLED=TRUE SQUEAKNODE_WEBADMIN_LOGIN_DISABLED=TRUE SQUEAKNODE_WEBADMIN_ALLOW_CORS=TRUE SQUEAKNODE_NETWORK=testnet squeaknode --config config.ini
|
||||
```
|
||||
- Start the frontend in development mode:
|
||||
```
|
||||
cd frontend
|
||||
make rundev
|
||||
```
|
||||
[Website Link](https://twitterapp-clone.netlify.app)
|
||||
|
||||
## Build for production
|
||||
[Backend Repo](https://github.com/Ali-hd/TwitterClone-Backend)
|
||||
|
||||
- Run the build command:
|
||||
```
|
||||
cd frontend
|
||||
make build
|
||||
```
|
||||
|
||||
|
||||
## Technologies used
|
||||
- Reactjs
|
||||
- Context API
|
||||
- Node.js express
|
||||
- MongoDB mongoose
|
||||
- Image uploaded on Amazon S3
|
||||
- socket-io for real-time chatting
|
||||
|
|
|
|||
36592
frontend/package-lock.json
generated
36592
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,47 +1,35 @@
|
|||
{
|
||||
"name": "squeak-node-frontend",
|
||||
"version": "1.1.0",
|
||||
"name": "twitter-frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"resolutions": {
|
||||
"websocket-extensions": "^0.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.6.0",
|
||||
"@emotion/styled": "^11.6.0",
|
||||
"@grpc/grpc-js": "~1.4.4",
|
||||
"@grpc/proto-loader": "~0.6.7",
|
||||
"@material-ui/core": "^4.12.3",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||
"@material-ui/styles": "^4.11.4",
|
||||
"@mui/icons-material": "^5.1.1",
|
||||
"@mui/material": "^5.1.1",
|
||||
"@mui/styled-engine": "^5.1.1",
|
||||
"apexcharts": "^3.22.2",
|
||||
"async": "~3.2.2",
|
||||
"classnames": "^2.2.6",
|
||||
"font-awesome": "^4.7.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"axios": "^0.19.2",
|
||||
"darkreader": "^4.9.10",
|
||||
"dotenv": "^8.2.0",
|
||||
"google-protobuf": "^3.12.4",
|
||||
"grpc-web": "^1.2.1",
|
||||
"moment": "^2.29.1",
|
||||
"mui-datatables": "^4.0.0",
|
||||
"protobufjs": "^6.10.2",
|
||||
"react": "^17.0.2",
|
||||
"react-apexcharts": "^1.3.3",
|
||||
"react-copy-to-clipboard": "^5.0.4",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-syntax-highlighter": "^15.4.5",
|
||||
"react-toastify": "^8.1.0",
|
||||
"recharts": "^2.1.6",
|
||||
"tinycolor2": "^1.4.2"
|
||||
"jwt-decode": "^2.2.0",
|
||||
"moment": "^2.26.0",
|
||||
"react": "^16.13.1",
|
||||
"react-contenteditable": "^3.3.4",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-responsive": "^8.1.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "3.4.1",
|
||||
"react-select": "^5.2.2",
|
||||
"sass": "^1.49.7",
|
||||
"serve": "^11.3.2",
|
||||
"socket.io-client": "^2.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "react-scripts start",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"heroku-postbuild": "npm run build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
|
|
@ -57,9 +45,5 @@
|
|||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.4.1",
|
||||
"terser": "^5.10.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
frontend/public/_redirects
Normal file
1
frontend/public/_redirects
Normal file
|
|
@ -0,0 +1 @@
|
|||
/* /index.html 200
|
||||
22
frontend/public/index.html
Executable file → Normal file
22
frontend/public/index.html
Executable file → Normal file
|
|
@ -2,12 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#1da1f2" />
|
||||
<meta nam="description" content="Twitter clone!"/>
|
||||
<meta name="og:title" content="Twitter clone by Ali hd"/>
|
||||
<meta name="og:description" content="check out my the twitter clone I made"/>
|
||||
<!-- <meta name="viewport" content="minimum-scale=1"> -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Assistant&display=swap" rel="stylesheet">
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
|
|
@ -22,12 +25,9 @@
|
|||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Squeaknode</title>
|
||||
<meta name="description" content="Squeaknode is a frontend for accessing a squeak node">
|
||||
<meta name="keywords" content="squeak, bitcoin, lightning">
|
||||
<meta name="author" content="Flatlogic LLC.">
|
||||
<title>Twitter Clone</title>
|
||||
</head>
|
||||
<body style="font-family: 'Roboto', sans-serif;">
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
|
|
|
|||
12
frontend/public/manifest.json
Executable file → Normal file
12
frontend/public/manifest.json
Executable file → Normal file
|
|
@ -1,15 +1,11 @@
|
|||
{
|
||||
"short_name": "Squeaknode",
|
||||
"name": "Squeaknode is a frontend for accessing a squeak node",
|
||||
"short_name": "Twitter Clone",
|
||||
"name": "Twitter Clone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#536DFE",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
|
|
|
|||
3
frontend/public/robots.txt
Normal file
3
frontend/public/robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
89
frontend/src/App.js
Normal file
89
frontend/src/App.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import React, { Suspense, lazy } from 'react'
|
||||
import { Route, Switch, HashRouter, Redirect, withRouter } from 'react-router-dom'
|
||||
import { StoreProvider } from './store/store'
|
||||
import 'dotenv/config'
|
||||
import './App.scss'
|
||||
import Loader from './components/Loader'
|
||||
import Nav from './components/Nav'
|
||||
import Login from './components/Login'
|
||||
import Signup from './components/Signup'
|
||||
import Tweet from './components/Tweet'
|
||||
import Profiles from './components/Profiles'
|
||||
import Payments from './components/Payments'
|
||||
import Peers from './components/Peers'
|
||||
import Feed from './components/Feed'
|
||||
import Notifications from './components/Notifications'
|
||||
import Alerts from './components/Alerts'
|
||||
|
||||
const Home = lazy(() => import('./components/Home'))
|
||||
const Profile = lazy(() => import('./components/Profile'))
|
||||
const Peer = lazy(() => import('./components/Peer'))
|
||||
|
||||
const DefaultContainer = withRouter(({ history }) => {
|
||||
return (<div className="body-wrap">
|
||||
<main className="main">
|
||||
<div className={history.location.pathname.slice(0,9) !== '/messages' ? "middle-section ms-width" : "middle-section"}>
|
||||
<Route path="/" exact>
|
||||
<Redirect to="/app/home" />
|
||||
</Route>
|
||||
<Route path="/app/home" exact>
|
||||
<Home />
|
||||
</Route>
|
||||
<Route path="/app/profile/:username" exact>
|
||||
<Profile />
|
||||
</Route>
|
||||
<Route path="/app/peer/:network/:host/:port" exact>
|
||||
<Peer />
|
||||
</Route>
|
||||
<Route path="/app/tweet/:id" exact>
|
||||
<Tweet />
|
||||
</Route>
|
||||
<Route path="/app/profiles" exact>
|
||||
<Profiles/>
|
||||
</Route>
|
||||
<Route path="/app/payments" exact>
|
||||
<Payments/>
|
||||
</Route>
|
||||
<Route path="/app/peers" exact>
|
||||
<Peers/>
|
||||
</Route>
|
||||
<Route path="/app/notifications" exact>
|
||||
<Notifications/>
|
||||
</Route>
|
||||
</div>
|
||||
{history.location.pathname.slice(0,9) !== '/messages' &&
|
||||
<div className="right-section">
|
||||
<Feed/>
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
<nav className="header">
|
||||
<Nav />
|
||||
</nav>
|
||||
</div>)
|
||||
});
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="dark-mode">
|
||||
<StoreProvider>
|
||||
<HashRouter>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Alerts />
|
||||
<Switch>
|
||||
<Route path="/login" exact>
|
||||
<Login />
|
||||
</Route>
|
||||
<Route path="/signup" exact>
|
||||
<Signup />
|
||||
</Route>
|
||||
<Route component={DefaultContainer} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</HashRouter>
|
||||
</StoreProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
668
frontend/src/App.scss
Normal file
668
frontend/src/App.scss
Normal file
|
|
@ -0,0 +1,668 @@
|
|||
|
||||
.body-wrap{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.header{
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
position: relative;
|
||||
order: -1;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.main{
|
||||
width: 990px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.middle-section{
|
||||
max-width: 600px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.ms-width{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.right-section{
|
||||
width: 350px;
|
||||
margin-right: 10px;
|
||||
min-height: 1000px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal-edit{
|
||||
// display: none;
|
||||
position: fixed;
|
||||
z-index: 250;
|
||||
// padding-top: 100px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.modal-content{
|
||||
min-height: 400px;
|
||||
max-height: 90vh;
|
||||
height: 650px;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
border-radius: 14px;
|
||||
background-color: #fff;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 50;
|
||||
transform: translate(-50%, -50%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header{
|
||||
height: 53px;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
border-bottom: 1px solid rgb(204, 214, 221);
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
.modal-closeIcon{
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-left: 4px;
|
||||
align-items: center;
|
||||
min-width: 59px;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.modal-closeIcon-wrap{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: 0.2s ease-in-out;
|
||||
border: 1px solid rgba(0,0,0,0);
|
||||
border-radius: 9999px;
|
||||
width: 39px;
|
||||
height: 39px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-closeIcon-wrap:hover{
|
||||
background-color: rgba(29,161,242,0.1);
|
||||
}
|
||||
|
||||
.modal-closeIcon-wrap svg{
|
||||
fill: rgb(29, 161, 242);
|
||||
height: 22.5px;
|
||||
}
|
||||
|
||||
.modal-title{
|
||||
font-weight: bold;
|
||||
font-size: 19px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.save-modal-wrapper{
|
||||
margin-right: 8px;
|
||||
min-height: 39px;
|
||||
min-width: 66px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.save-modal-btn{
|
||||
// width: 48px;
|
||||
min-height: 30px;
|
||||
transition: 0.2s ease-in-out;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
background-color: rgb(29,161,242);
|
||||
border: 1px solid rgba(0,0,0,0);
|
||||
border-radius: 9999px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.save-modal-btn:hover{
|
||||
background-color: rgb(26, 145, 218);
|
||||
}
|
||||
|
||||
.modal-body{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
|
||||
.modal-banner{
|
||||
max-height: 200px;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border: 2px solid rgba(0, 0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-banner img{
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.modal-banner div{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-banner div input{
|
||||
width: 22.5px;
|
||||
min-width: 22.5px;
|
||||
height: 22.5px;
|
||||
overflow: hidden;
|
||||
z-index: 20;
|
||||
padding: 10px 0 10px 30px;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background-color: initial;
|
||||
outline: none;
|
||||
border: initial;
|
||||
}
|
||||
|
||||
|
||||
.modal-banner div svg{
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
fill: #fff;
|
||||
width: 22.5px;
|
||||
min-width: 22.5px;
|
||||
height: 22.5px;
|
||||
}
|
||||
|
||||
.modal-scroll{
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
margin-bottom: 55px;
|
||||
}
|
||||
|
||||
.modal-profile-pic{
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
border: 4px solid #fff;
|
||||
border-radius: 50%;
|
||||
margin-left: 16px;
|
||||
margin-top: -48px;
|
||||
z-index: 5;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-back-pic{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 50%;
|
||||
z-index: 5;
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-back-pic img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
opacity: 0.6;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.modal-back-pic div{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-back-pic div input{
|
||||
width: 22.5px;
|
||||
min-width: 22.5px;
|
||||
height: 22.5px;
|
||||
overflow: hidden;
|
||||
z-index: 20;
|
||||
padding: 10px 0 10px 30px;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background-color: initial;
|
||||
outline: none;
|
||||
border: initial;
|
||||
}
|
||||
|
||||
|
||||
.modal-back-pic div svg{
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
fill: #fff;
|
||||
width: 22.5px;
|
||||
min-width: 22.5px;
|
||||
height: 22.5px;
|
||||
}
|
||||
|
||||
.edit-form{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.edit-input{
|
||||
background-color: inherit;
|
||||
border: inherit;
|
||||
}
|
||||
|
||||
.edit-input:focus{
|
||||
background-color: inherit;
|
||||
border: inherit;
|
||||
}
|
||||
|
||||
.edit-input-wrap{
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.edit-input-content{
|
||||
border-bottom: 1px solid rgb(64, 67, 70);
|
||||
background-color: rgb(245, 248, 250);
|
||||
label{
|
||||
color: rgb(101, 119, 134);
|
||||
display: block;
|
||||
padding: 5px 10px 0 10px;
|
||||
}
|
||||
input{
|
||||
width: 100%;
|
||||
outline: none;
|
||||
font-size: 19px;
|
||||
padding: 2px 10px 5px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////from home
|
||||
|
||||
|
||||
.Tweet-input-wrapper{
|
||||
padding: 10px 15px 5px 15px;
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.Tweet-profile-wrapper{
|
||||
flex-basis: 49px;
|
||||
padding-top: 5px;
|
||||
margin-right: 10px;
|
||||
img{
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.Tweet-input-side{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: static;
|
||||
width: calc(100% - 49px);
|
||||
border: 2px solid rgba(0, 0, 0, 0);
|
||||
border-radius: 5px;
|
||||
padding-top: 5px;
|
||||
line-height: 1.3125;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.inner-input-box{
|
||||
padding: 10px 0;
|
||||
font-size: 19px;
|
||||
color: #9197a3;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inner-input-box div{
|
||||
outline: none;
|
||||
white-space: pre-wrap;
|
||||
max-width: 506px;
|
||||
}
|
||||
|
||||
.inner-input-box div:focus{
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.inner-input-links{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 2px;
|
||||
|
||||
}
|
||||
|
||||
.input-links-side{
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-attach-wrapper{
|
||||
width: 39px;
|
||||
height: 39px;
|
||||
cursor: pointer;
|
||||
padding: 8.3px;
|
||||
position: relative;
|
||||
input{
|
||||
position: absolute;
|
||||
width: 0.3px;
|
||||
height: 0.3px;
|
||||
overflow: hidden;
|
||||
z-index: 4;
|
||||
padding-top: 21px;
|
||||
padding-bottom: 15px;
|
||||
padding-right: 0px;
|
||||
padding-left: 32px;
|
||||
top: 2px;
|
||||
left: 3px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
color: inherit;
|
||||
background-color: initial;
|
||||
border: initial;
|
||||
text-align: start !important;
|
||||
}
|
||||
}
|
||||
.input-attach-wrapper:hover{
|
||||
border-radius: 50%;
|
||||
background-color: rgba(29, 161, 242,0.1);
|
||||
}
|
||||
|
||||
.tweet-btn-side{
|
||||
margin-left: 10px;
|
||||
min-height: 39px;
|
||||
min-width: calc(62.79px);
|
||||
background-color: rgb(29, 161, 242);
|
||||
padding: 0 1em;
|
||||
border: 1px solid rgba(0, 0, 0, 0);
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
// cursor: pointer;
|
||||
opacity: 0.5;
|
||||
transition: 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.tweet-btn-active{
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
&:hover{
|
||||
background-color: rgba(11, 137, 216, 0.876);
|
||||
}
|
||||
}
|
||||
|
||||
.Tweet-input-divider{
|
||||
min-height: 10px;
|
||||
height: 10px;
|
||||
background-color: rgb(230, 236, 240);
|
||||
content: '';
|
||||
}
|
||||
|
||||
[contenteditable=true]{display: inline-block;}
|
||||
|
||||
[contenteditable=true]:empty:before{
|
||||
content: attr(placeholder);
|
||||
pointer-events: none;
|
||||
display: block; /* For Firefox */
|
||||
}
|
||||
|
||||
/* */
|
||||
|
||||
[contenteditable=true]:empty:focus{
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.card-content-info{
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.tweet-input-active{
|
||||
color: rgb(20, 23, 26);
|
||||
}
|
||||
|
||||
.tweet-upload-image{
|
||||
margin-top: 10px;
|
||||
border-radius: 14px;
|
||||
max-height: 253px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.inner-image-box{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cancel-image{
|
||||
position: absolute;
|
||||
color: white;
|
||||
left: 9px;
|
||||
top: 18px;
|
||||
width: 33px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 23px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
height: 33px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 50%;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
padding-bottom: 3.5px;
|
||||
}
|
||||
|
||||
.workInProgress{
|
||||
max-width: 600px;
|
||||
border-right: 1px solid rgb(230, 236, 240);
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
padding-top: 20%;
|
||||
color: #657786;
|
||||
min-height: 2000px;
|
||||
}
|
||||
|
||||
// .dark-mode{
|
||||
// background-color: #1a1919 !important;
|
||||
// }
|
||||
|
||||
.alert-wrapper{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
}
|
||||
|
||||
.tweet-btn-holder{
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.header-back-wrapper{
|
||||
margin-left: -5px;
|
||||
width: 39px;
|
||||
height: 39px;
|
||||
transition: 0.2s ease-in-out;
|
||||
will-change: background-color;
|
||||
border: 1px solid rgba(0, 0, 0, 0);
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@media only screen and (max-width: 1196px) {
|
||||
.right-section{
|
||||
width: 290px !important;
|
||||
}
|
||||
.main{
|
||||
width: 920px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1005px) {
|
||||
.right-section{
|
||||
display: none;
|
||||
}
|
||||
.main{
|
||||
width: 100%;
|
||||
}
|
||||
//flex grow
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 888px){
|
||||
.chat-right{
|
||||
display: none;
|
||||
}
|
||||
.messages-wrapper{
|
||||
width: 100% !important;
|
||||
}
|
||||
.messages-header-wrapper{
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.middle-section{
|
||||
width: 100%;
|
||||
}
|
||||
.chat-height{
|
||||
height: 100vh !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 450px){
|
||||
|
||||
body{
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-height {
|
||||
height: calc(100vh - 46px) !important;
|
||||
}
|
||||
.chat-bottom-wrapper{
|
||||
bottom: 50px !important;
|
||||
}
|
||||
|
||||
.body-wrap{
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header{
|
||||
position: sticky;
|
||||
width: 100vw;
|
||||
bottom: 0;
|
||||
height: 53px;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.Nav-component{
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.Nav-width{
|
||||
width: 100vw !important;
|
||||
height: 53px;
|
||||
}
|
||||
|
||||
.Nav{
|
||||
top: auto !important;
|
||||
width: 100vw;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.Nav-Content{
|
||||
width: 100% !important;
|
||||
display: block;
|
||||
padding: 0 !important;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.Nav-wrapper{
|
||||
background-color: #fff;
|
||||
border-top: 2px solid rgb(245, 248, 250);
|
||||
display: flex;
|
||||
align-content: center;
|
||||
flex-direction: row !important;
|
||||
margin: 0;
|
||||
justify-content: space-evenly;
|
||||
|
||||
.Nav-link{
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a:nth-child(1){
|
||||
display: none;
|
||||
}
|
||||
a:nth-child(4){
|
||||
display: none;
|
||||
}
|
||||
a:nth-child(6){
|
||||
display: none;
|
||||
}
|
||||
a:nth-child(9){
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.Nav-tweet{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.more-menu-content{
|
||||
top: auto !important;
|
||||
bottom: 46px !important;
|
||||
left: 47% !important;
|
||||
overflow: hidden;
|
||||
height: 154px;
|
||||
}
|
||||
|
||||
.more-item{
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
9
frontend/src/App.test.js
Normal file
9
frontend/src/App.test.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
const { getByText } = render(<App />);
|
||||
const linkElement = getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
162
frontend/src/Icons.js
Normal file
162
frontend/src/Icons.js
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import React from 'react'
|
||||
|
||||
export const ICON_LOGO = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M23.643 4.937c-.835.37-1.732.62-2.675.733.962-.576 1.7-1.49 2.048-2.578-.9.534-1.897.922-2.958 1.13-.85-.904-2.06-1.47-3.4-1.47-2.572 0-4.658 2.086-4.658 4.66 0 .364.042.718.12 1.06-3.873-.195-7.304-2.05-9.602-4.868-.4.69-.63 1.49-.63 2.342 0 1.616.823 3.043 2.072 3.878-.764-.025-1.482-.234-2.11-.583v.06c0 2.257 1.605 4.14 3.737 4.568-.392.106-.803.162-1.227.162-.3 0-.593-.028-.877-.082.593 1.85 2.313 3.198 4.352 3.234-1.595 1.25-3.604 1.995-5.786 1.995-.376 0-.747-.022-1.112-.065 2.062 1.323 4.51 2.093 7.14 2.093 8.57 0 13.255-7.098 13.255-13.254 0-.2-.005-.402-.014-.602.91-.658 1.7-1.477 2.323-2.41z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_HOME = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M22.46 7.57L12.357 2.115c-.223-.12-.49-.12-.713 0L1.543 7.57c-.364.197-.5.652-.303 1.017.135.25.394.393.66.393.12 0 .243-.03.356-.09l.815-.44L4.7 19.963c.214 1.215 1.308 2.062 2.658 2.062h9.282c1.352 0 2.445-.848 2.663-2.087l1.626-11.49.818.442c.364.193.82.06 1.017-.304.196-.363.06-.818-.304-1.016zm-4.638 12.133c-.107.606-.703.822-1.18.822H7.36c-.48 0-1.075-.216-1.178-.798L4.48 7.69 12 3.628l7.522 4.06-1.7 12.015z"></path><path d="M8.22 12.184c0 2.084 1.695 3.78 3.78 3.78s3.78-1.696 3.78-3.78-1.695-3.78-3.78-3.78-3.78 1.696-3.78 3.78zm6.06 0c0 1.258-1.022 2.28-2.28 2.28s-2.28-1.022-2.28-2.28 1.022-2.28 2.28-2.28 2.28 1.022 2.28 2.28z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_HOMEFILL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M22.58 7.35L12.475 1.897c-.297-.16-.654-.16-.95 0L1.425 7.35c-.486.264-.667.87-.405 1.356.18.335.525.525.88.525.16 0 .324-.038.475-.12l.734-.396 1.59 11.25c.216 1.214 1.31 2.062 2.66 2.062h9.282c1.35 0 2.444-.848 2.662-2.088l1.588-11.225.737.398c.485.263 1.092.082 1.354-.404.263-.486.08-1.093-.404-1.355zM12 15.435c-1.795 0-3.25-1.455-3.25-3.25s1.455-3.25 3.25-3.25 3.25 1.455 3.25 3.25-1.455 3.25-3.25 3.25z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_HASH = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M21 7.337h-3.93l.372-4.272c.036-.412-.27-.775-.682-.812-.417-.03-.776.27-.812.683l-.383 4.4h-6.32l.37-4.27c.037-.413-.27-.776-.68-.813-.42-.03-.777.27-.813.683l-.382 4.4H3.782c-.414 0-.75.337-.75.75s.336.75.75.75H7.61l-.55 6.327H3c-.414 0-.75.336-.75.75s.336.75.75.75h3.93l-.372 4.272c-.036.412.27.775.682.812l.066.003c.385 0 .712-.295.746-.686l.383-4.4h6.32l-.37 4.27c-.036.413.27.776.682.813l.066.003c.385 0 .712-.295.746-.686l.382-4.4h3.957c.413 0 .75-.337.75-.75s-.337-.75-.75-.75H16.39l.55-6.327H21c.414 0 .75-.336.75-.75s-.336-.75-.75-.75zm-6.115 7.826h-6.32l.55-6.326h6.32l-.55 6.326z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_HASHFILL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M20.585 9.468c.66 0 1.2-.538 1.2-1.2 0-.662-.54-1.2-1.2-1.2h-3.22l.31-3.547c.027-.318-.07-.63-.277-.875-.206-.245-.495-.396-.822-.425-.65-.035-1.235.432-1.293 1.093l-.326 3.754H9.9l.308-3.545c.06-.658-.43-1.242-1.097-1.302-.665-.05-1.235.43-1.293 1.092l-.325 3.754h-3.33c-.663 0-1.2.538-1.2 1.2 0 .662.538 1.2 1.2 1.2h3.122l-.44 5.064H3.416c-.662 0-1.2.54-1.2 1.2s.538 1.202 1.2 1.202h3.22l-.31 3.548c-.057.657.432 1.24 1.09 1.3l.106.005c.626 0 1.14-.472 1.195-1.098l.327-3.753H14.1l-.308 3.544c-.06.658.43 1.242 1.09 1.302l.106.005c.617 0 1.142-.482 1.195-1.098l.325-3.753h3.33c.66 0 1.2-.54 1.2-1.2s-.54-1.202-1.2-1.202h-3.122l.44-5.064h3.43zm-5.838 0l-.44 5.063H9.253l.44-5.062h5.055z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_BELL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M21.697 16.468c-.02-.016-2.14-1.64-2.103-6.03.02-2.532-.812-4.782-2.347-6.335C15.872 2.71 14.01 1.94 12.005 1.93h-.013c-2.004.01-3.866.78-5.242 2.174-1.534 1.553-2.368 3.802-2.346 6.334.037 4.33-2.02 5.967-2.102 6.03-.26.193-.366.53-.265.838.102.308.39.515.712.515h4.92c.102 2.31 1.997 4.16 4.33 4.16s4.226-1.85 4.327-4.16h4.922c.322 0 .61-.206.71-.514.103-.307-.003-.645-.263-.838zM12 20.478c-1.505 0-2.73-1.177-2.828-2.658h5.656c-.1 1.48-1.323 2.66-2.828 2.66zM4.38 16.32c.74-1.132 1.548-3.028 1.524-5.896-.018-2.16.644-3.982 1.913-5.267C8.91 4.05 10.397 3.437 12 3.43c1.603.008 3.087.62 4.18 1.728 1.27 1.285 1.933 3.106 1.915 5.267-.024 2.868.785 4.765 1.525 5.896H4.38z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_BELLFILL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M21.697 16.468c-.02-.016-2.14-1.64-2.103-6.03.02-2.533-.812-4.782-2.347-6.334-1.375-1.393-3.237-2.164-5.242-2.172h-.013c-2.004.008-3.866.78-5.242 2.172-1.534 1.553-2.367 3.802-2.346 6.333.037 4.332-2.02 5.967-2.102 6.03-.26.194-.366.53-.265.838s.39.515.713.515h4.494c.1 2.544 2.188 4.587 4.756 4.587s4.655-2.043 4.756-4.587h4.494c.324 0 .61-.208.712-.515s-.005-.644-.265-.837zM12 20.408c-1.466 0-2.657-1.147-2.756-2.588h5.512c-.1 1.44-1.29 2.587-2.756 2.587z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_INBOX = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M19.25 3.018H4.75C3.233 3.018 2 4.252 2 5.77v12.495c0 1.518 1.233 2.753 2.75 2.753h14.5c1.517 0 2.75-1.235 2.75-2.753V5.77c0-1.518-1.233-2.752-2.75-2.752zm-14.5 1.5h14.5c.69 0 1.25.56 1.25 1.25v.714l-8.05 5.367c-.273.18-.626.182-.9-.002L3.5 6.482v-.714c0-.69.56-1.25 1.25-1.25zm14.5 14.998H4.75c-.69 0-1.25-.56-1.25-1.25V8.24l7.24 4.83c.383.256.822.384 1.26.384.44 0 .877-.128 1.26-.383l7.24-4.83v10.022c0 .69-.56 1.25-1.25 1.25z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_INBOXFILL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M11.55 12.082c.273.182.627.182.9 0L22 5.716V5.5c0-1.24-1.01-2.25-2.25-2.25H4.25C3.01 3.25 2 4.26 2 5.5v.197l9.55 6.385z"></path><path d="M13.26 13.295c-.383.255-.82.382-1.26.382s-.877-.127-1.26-.383L2 7.452v11.67c0 1.24 1.01 2.25 2.25 2.25h15.5c1.24 0 2.25-1.01 2.25-2.25V7.47l-8.74 5.823z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_BOOKMARK = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M19.9 23.5c-.157 0-.312-.05-.442-.144L12 17.928l-7.458 5.43c-.228.164-.53.19-.782.06-.25-.127-.41-.385-.41-.667V5.6c0-1.24 1.01-2.25 2.25-2.25h12.798c1.24 0 2.25 1.01 2.25 2.25v17.15c0 .282-.158.54-.41.668-.106.055-.223.082-.34.082zM12 16.25c.155 0 .31.048.44.144l6.71 4.883V5.6c0-.412-.337-.75-.75-.75H5.6c-.413 0-.75.338-.75.75v15.677l6.71-4.883c.13-.096.285-.144.44-.144z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_BOOKMARKFILL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M19.9 23.5c-.2 0-.3 0-.4-.1L12 17.9l-7.5 5.4c-.2.2-.5.2-.8.1-.2-.1-.4-.4-.4-.7V5.6c0-1.2 1-2.2 2.2-2.2h12.8c1.2 0 2.2 1 2.2 2.2v17.1c0 .3-.2.5-.4.7 0 .1-.1.1-.2.1z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_LIST = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M19.75 22H4.25C3.01 22 2 20.99 2 19.75V4.25C2 3.01 3.01 2 4.25 2h15.5C20.99 2 22 3.01 22 4.25v15.5c0 1.24-1.01 2.25-2.25 2.25zM4.25 3.5c-.414 0-.75.337-.75.75v15.5c0 .413.336.75.75.75h15.5c.414 0 .75-.337.75-.75V4.25c0-.413-.336-.75-.75-.75H4.25z"></path><path d="M17 8.64H7c-.414 0-.75-.337-.75-.75s.336-.75.75-.75h10c.414 0 .75.335.75.75s-.336.75-.75.75zm0 4.11H7c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h10c.414 0 .75.336.75.75s-.336.75-.75.75zm-5 4.11H7c-.414 0-.75-.335-.75-.75s.336-.75.75-.75h5c.414 0 .75.337.75.75s-.336.75-.75.75z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_LISTFILL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M19.75 2H4.25C3.013 2 2 3.013 2 4.25v15.5C2 20.987 3.013 22 4.25 22h15.5c1.237 0 2.25-1.013 2.25-2.25V4.25C22 3.013 20.987 2 19.75 2zM11 16.75H7c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h4c.414 0 .75.336.75.75s-.336.75-.75.75zm6-4H7c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h10c.414 0 .75.336.75.75s-.336.75-.75.75zm0-4H7c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h10c.414 0 .75.336.75.75s-.336.75-.75.75z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_USER = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M12 11.816c1.355 0 2.872-.15 3.84-1.256.814-.93 1.078-2.368.806-4.392-.38-2.825-2.117-4.512-4.646-4.512S7.734 3.343 7.354 6.17c-.272 2.022-.008 3.46.806 4.39.968 1.107 2.485 1.256 3.84 1.256zM8.84 6.368c.162-1.2.787-3.212 3.16-3.212s2.998 2.013 3.16 3.212c.207 1.55.057 2.627-.45 3.205-.455.52-1.266.743-2.71.743s-2.255-.223-2.71-.743c-.507-.578-.657-1.656-.45-3.205zm11.44 12.868c-.877-3.526-4.282-5.99-8.28-5.99s-7.403 2.464-8.28 5.99c-.172.692-.028 1.4.395 1.94.408.52 1.04.82 1.733.82h12.304c.693 0 1.325-.3 1.733-.82.424-.54.567-1.247.394-1.94zm-1.576 1.016c-.126.16-.316.246-.552.246H5.848c-.235 0-.426-.085-.552-.246-.137-.174-.18-.412-.12-.654.71-2.855 3.517-4.85 6.824-4.85s6.114 1.994 6.824 4.85c.06.242.017.48-.12.654z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_USERFILL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M12.225 12.165c-1.356 0-2.872-.15-3.84-1.256-.814-.93-1.077-2.368-.805-4.392.38-2.826 2.116-4.513 4.646-4.513s4.267 1.687 4.646 4.513c.272 2.024.008 3.46-.806 4.392-.97 1.106-2.485 1.255-3.84 1.255zm5.849 9.85H6.376c-.663 0-1.25-.28-1.65-.786-.422-.534-.576-1.27-.41-1.968.834-3.53 4.086-5.997 7.908-5.997s7.074 2.466 7.91 5.997c.164.698.01 1.434-.412 1.967-.4.505-.985.785-1.648.785z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_LAPTOP = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M13.5 3a.5.5 0 0 1 .5.5V11H2V3.5a.5.5 0 0 1 .5-.5h11zm-11-1A1.5 1.5 0 0 0 1 3.5V12h14V3.5A1.5 1.5 0 0 0 13.5 2h-11zM0 12.5h16a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 12.5z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_LAPTOPFILL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M2.5 2A1.5 1.5 0 0 0 1 3.5V12h14V3.5A1.5 1.5 0 0 0 13.5 2h-11zM0 12.5h16a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 12.5z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_LOCK = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM5 8h6a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_LOCKFILL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_SETTINGS = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M16.5 10.25c-.965 0-1.75.787-1.75 1.75s.784 1.75 1.75 1.75c.964 0 1.75-.786 1.75-1.75s-.786-1.75-1.75-1.75zm0 2.5c-.414 0-.75-.336-.75-.75 0-.413.337-.75.75-.75s.75.336.75.75c0 .413-.336.75-.75.75zm-4.5-2.5c-.966 0-1.75.787-1.75 1.75s.785 1.75 1.75 1.75 1.75-.786 1.75-1.75-.784-1.75-1.75-1.75zm0 2.5c-.414 0-.75-.336-.75-.75 0-.413.337-.75.75-.75s.75.336.75.75c0 .413-.336.75-.75.75zm-4.5-2.5c-.965 0-1.75.787-1.75 1.75s.785 1.75 1.75 1.75c.964 0 1.75-.786 1.75-1.75s-.787-1.75-1.75-1.75zm0 2.5c-.414 0-.75-.336-.75-.75 0-.413.337-.75.75-.75s.75.336.75.75c0 .413-.336.75-.75.75z"></path><path d="M12 22.75C6.072 22.75 1.25 17.928 1.25 12S6.072 1.25 12 1.25 22.75 6.072 22.75 12 17.928 22.75 12 22.75zm0-20C6.9 2.75 2.75 6.9 2.75 12S6.9 21.25 12 21.25s9.25-4.15 9.25-9.25S17.1 2.75 12 2.75z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_TWEET = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24" class="r-jwli3a r-4qtqp9 r-yyyyoo r-1q142lx r-50lct3 r-dnmrzs r-bnwqim r-1plcrui r-lrvibr"><g><path d="M8.8 7.2H5.6V3.9c0-.4-.3-.8-.8-.8s-.7.4-.7.8v3.3H.8c-.4 0-.8.3-.8.8s.3.8.8.8h3.3v3.3c0 .4.3.8.8.8s.8-.3.8-.8V8.7H9c.4 0 .8-.3.8-.8s-.5-.7-1-.7zm15-4.9v-.1h-.1c-.1 0-9.2 1.2-14.4 11.7-3.8 7.6-3.6 9.9-3.3 9.9.3.1 3.4-6.5 6.7-9.2 5.2-1.1 6.6-3.6 6.6-3.6s-1.5.2-2.1.2c-.8 0-1.4-.2-1.7-.3 1.3-1.2 2.4-1.5 3.5-1.7.9-.2 1.8-.4 3-1.2 2.2-1.6 1.9-5.5 1.8-5.7z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_IMGUPLOAD = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M19.75 2H4.25C3.01 2 2 3.01 2 4.25v15.5C2 20.99 3.01 22 4.25 22h15.5c1.24 0 2.25-1.01 2.25-2.25V4.25C22 3.01 20.99 2 19.75 2zM4.25 3.5h15.5c.413 0 .75.337.75.75v9.676l-3.858-3.858c-.14-.14-.33-.22-.53-.22h-.003c-.2 0-.393.08-.532.224l-4.317 4.384-1.813-1.806c-.14-.14-.33-.22-.53-.22-.193-.03-.395.08-.535.227L3.5 17.642V4.25c0-.413.337-.75.75-.75zm-.744 16.28l5.418-5.534 6.282 6.254H4.25c-.402 0-.727-.322-.744-.72zm16.244.72h-2.42l-5.007-4.987 3.792-3.85 4.385 4.384v3.703c0 .413-.337.75-.75.75z"></path><circle cx="8.868" cy="8.309" r="1.542"></circle></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_REPLY = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M14.046 2.242l-4.148-.01h-.002c-4.374 0-7.8 3.427-7.8 7.802 0 4.098 3.186 7.206 7.465 7.37v3.828c0 .108.044.286.12.403.142.225.384.347.632.347.138 0 .277-.038.402-.118.264-.168 6.473-4.14 8.088-5.506 1.902-1.61 3.04-3.97 3.043-6.312v-.017c-.006-4.367-3.43-7.787-7.8-7.788zm3.787 12.972c-1.134.96-4.862 3.405-6.772 4.643V16.67c0-.414-.335-.75-.75-.75h-.396c-3.66 0-6.318-2.476-6.318-5.886 0-3.534 2.768-6.302 6.3-6.302l4.147.01h.002c3.532 0 6.3 2.766 6.302 6.296-.003 1.91-.942 3.844-2.514 5.176z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_RETWEET = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M23.77 15.67c-.292-.293-.767-.293-1.06 0l-2.22 2.22V7.65c0-2.068-1.683-3.75-3.75-3.75h-5.85c-.414 0-.75.336-.75.75s.336.75.75.75h5.85c1.24 0 2.25 1.01 2.25 2.25v10.24l-2.22-2.22c-.293-.293-.768-.293-1.06 0s-.294.768 0 1.06l3.5 3.5c.145.147.337.22.53.22s.383-.072.53-.22l3.5-3.5c.294-.292.294-.767 0-1.06zm-10.66 3.28H7.26c-1.24 0-2.25-1.01-2.25-2.25V6.46l2.22 2.22c.148.147.34.22.532.22s.384-.073.53-.22c.293-.293.293-.768 0-1.06l-3.5-3.5c-.293-.294-.768-.294-1.06 0l-3.5 3.5c-.294.292-.294.767 0 1.06s.767.293 1.06 0l2.22-2.22V16.7c0 2.068 1.683 3.75 3.75 3.75h5.85c.414 0 .75-.336.75-.75s-.337-.75-.75-.75z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_HEART = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.814-1.148 2.354-2.73 4.645-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.376-7.454 13.11-10.037 13.157H12zM7.354 4.225c-2.08 0-3.903 1.988-3.903 4.255 0 5.74 7.034 11.596 8.55 11.658 1.518-.062 8.55-5.917 8.55-11.658 0-2.267-1.823-4.255-3.903-4.255-2.528 0-3.94 2.936-3.952 2.965-.23.562-1.156.562-1.387 0-.014-.03-1.425-2.965-3.954-2.965z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_HEARTFULL = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.814-1.148 2.354-2.73 4.645-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.376-7.454 13.11-10.037 13.157H12z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_SHARE = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M17.53 7.47l-5-5c-.293-.293-.768-.293-1.06 0l-5 5c-.294.293-.294.768 0 1.06s.767.294 1.06 0l3.72-3.72V15c0 .414.336.75.75.75s.75-.336.75-.75V4.81l3.72 3.72c.146.147.338.22.53.22s.384-.072.53-.22c.293-.293.293-.767 0-1.06z"></path><path d="M19.708 21.944H4.292C3.028 21.944 2 20.916 2 19.652V14c0-.414.336-.75.75-.75s.75.336.75.75v5.652c0 .437.355.792.792.792h15.416c.437 0 .792-.355.792-.792V14c0-.414.336-.75.75-.75s.75.336.75.75v5.652c0 1.264-1.028 2.292-2.292 2.292z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_ARROWBACK = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M20 11H7.414l4.293-4.293c.39-.39.39-1.023 0-1.414s-1.023-.39-1.414 0l-6 6c-.39.39-.39 1.023 0 1.414l6 6c.195.195.45.293.707.293s.512-.098.707-.293c.39-.39.39-1.023 0-1.414L7.414 13H20c.553 0 1-.447 1-1s-.447-1-1-1z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_MARKDOWN = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M12 14.315c-2.088 0-3.787-1.698-3.787-3.786S9.913 6.74 12 6.74s3.787 1.7 3.787 3.787-1.7 3.785-3.787 3.785zm0-6.073c-1.26 0-2.287 1.026-2.287 2.287S10.74 12.814 12 12.814s2.287-1.025 2.287-2.286S13.26 8.24 12 8.24z"></path><path d="M20.692 10.69C20.692 5.9 16.792 2 12 2s-8.692 3.9-8.692 8.69c0 1.902.603 3.708 1.743 5.223l.003-.002.007.015c1.628 2.07 6.278 5.757 6.475 5.912.138.11.302.163.465.163.163 0 .327-.053.465-.162.197-.155 4.847-3.84 6.475-5.912l.007-.014.002.002c1.14-1.516 1.742-3.32 1.742-5.223zM12 20.29c-1.224-.99-4.52-3.715-5.756-5.285-.94-1.25-1.436-2.742-1.436-4.312C4.808 6.727 8.035 3.5 12 3.5s7.192 3.226 7.192 7.19c0 1.57-.497 3.062-1.436 4.313-1.236 1.57-4.532 4.294-5.756 5.285z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_DATE = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M19.708 2H4.292C3.028 2 2 3.028 2 4.292v15.416C2 20.972 3.028 22 4.292 22h15.416C20.972 22 22 20.972 22 19.708V4.292C22 3.028 20.972 2 19.708 2zm.792 17.708c0 .437-.355.792-.792.792H4.292c-.437 0-.792-.355-.792-.792V6.418c0-.437.354-.79.79-.792h15.42c.436 0 .79.355.79.79V19.71z"></path><circle cx="7.032" cy="8.75" r="1.285"></circle><circle cx="7.032" cy="13.156" r="1.285"></circle><circle cx="16.968" cy="8.75" r="1.285"></circle><circle cx="16.968" cy="13.156" r="1.285"></circle><circle cx="12" cy="8.75" r="1.285"></circle><circle cx="12" cy="13.156" r="1.285"></circle><circle cx="7.032" cy="17.486" r="1.285"></circle><circle cx="12" cy="17.486" r="1.285"></circle></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_CLOSE = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M13.414 12l5.793-5.793c.39-.39.39-1.023 0-1.414s-1.023-.39-1.414 0L12 10.586 6.207 4.793c-.39-.39-1.023-.39-1.414 0s-.39 1.023 0 1.414L10.586 12l-5.793 5.793c-.39.39-.39 1.023 0 1.414.195.195.45.293.707.293s.512-.098.707-.293L12 13.414l5.793 5.793c.195.195.45.293.707.293s.512-.098.707-.293c.39-.39.39-1.023 0-1.414L13.414 12z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_DELETE = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M20.746 5.236h-3.75V4.25c0-1.24-1.01-2.25-2.25-2.25h-5.5c-1.24 0-2.25 1.01-2.25 2.25v.986h-3.75c-.414 0-.75.336-.75.75s.336.75.75.75h.368l1.583 13.262c.216 1.193 1.31 2.027 2.658 2.027h8.282c1.35 0 2.442-.834 2.664-2.072l1.577-13.217h.368c.414 0 .75-.336.75-.75s-.335-.75-.75-.75zM8.496 4.25c0-.413.337-.75.75-.75h5.5c.413 0 .75.337.75.75v.986h-7V4.25zm8.822 15.48c-.1.55-.664.795-1.18.795H7.854c-.517 0-1.083-.246-1.175-.75L5.126 6.735h13.74L17.32 19.732z"></path><path d="M10 17.75c.414 0 .75-.336.75-.75v-7c0-.414-.336-.75-.75-.75s-.75.336-.75.75v7c0 .414.336.75.75.75zm4 0c.414 0 .75-.336.75-.75v-7c0-.414-.336-.75-.75-.75s-.75.336-.75.75v7c0 .414.336.75.75.75z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_UPLOAD = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M19.708 22H4.292C3.028 22 2 20.972 2 19.708V7.375C2 6.11 3.028 5.083 4.292 5.083h2.146C7.633 3.17 9.722 2 12 2c2.277 0 4.367 1.17 5.562 3.083h2.146C20.972 5.083 22 6.11 22 7.375v12.333C22 20.972 20.972 22 19.708 22zM4.292 6.583c-.437 0-.792.355-.792.792v12.333c0 .437.355.792.792.792h15.416c.437 0 .792-.355.792-.792V7.375c0-.437-.355-.792-.792-.792h-2.45c-.317.05-.632-.095-.782-.382-.88-1.665-2.594-2.7-4.476-2.7-1.883 0-3.598 1.035-4.476 2.702-.16.302-.502.46-.833.38H4.293z"></path><path d="M12 8.167c-2.68 0-4.86 2.18-4.86 4.86s2.18 4.86 4.86 4.86 4.86-2.18 4.86-4.86-2.18-4.86-4.86-4.86zm2 5.583h-1.25V15c0 .414-.336.75-.75.75s-.75-.336-.75-.75v-1.25H10c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h1.25V11c0-.414.336-.75.75-.75s.75.336.75.75v1.25H14c.414 0 .75.336.75.75s-.336.75-.75.75z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_NEWLIST = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M23.25 3.25h-2.425V.825c0-.414-.336-.75-.75-.75s-.75.336-.75.75V3.25H16.9c-.414 0-.75.336-.75.75s.336.75.75.75h2.425v2.425c0 .414.336.75.75.75s.75-.336.75-.75V4.75h2.425c.414 0 .75-.336.75-.75s-.336-.75-.75-.75zM18.575 22H4.25C3.01 22 2 20.99 2 19.75V5.5c0-1.24 1.01-2.25 2.25-2.25h8.947c.414 0 .75.336.75.75s-.336.75-.75.75H4.25c-.413 0-.75.336-.75.75v14.25c0 .414.337.75.75.75h14.325c.413 0 .75-.336.75-.75v-8.872c0-.414.336-.75.75-.75s.75.336.75.75v8.872c0 1.24-1.01 2.25-2.25 2.25z"></path><path d="M16.078 9.583H6.673c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h9.405c.414 0 .75.336.75.75s-.336.75-.75.75zm0 3.867H6.673c-.414 0-.75-.337-.75-.75s.336-.75.75-.75h9.405c.414 0 .75.335.75.75s-.336.75-.75.75zm-4.703 3.866H6.673c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h4.702c.414 0 .75.336.75.75s-.336.75-.75.75z"></path></g></svg>
|
||||
}
|
||||
|
||||
|
||||
export const ICON_SEARCH = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M21.53 20.47l-3.66-3.66C19.195 15.24 20 13.214 20 11c0-4.97-4.03-9-9-9s-9 4.03-9 9 4.03 9 9 9c2.215 0 4.24-.804 5.808-2.13l3.66 3.66c.147.146.34.22.53.22s.385-.073.53-.22c.295-.293.295-.767.002-1.06zM3.5 11c0-4.135 3.365-7.5 7.5-7.5s7.5 3.365 7.5 7.5-3.365 7.5-7.5 7.5-7.5-3.365-7.5-7.5z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_FEATHER = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M8.8 7.2H5.6V3.9c0-.4-.3-.8-.8-.8s-.7.4-.7.8v3.3H.8c-.4 0-.8.3-.8.8s.3.8.8.8h3.3v3.3c0 .4.3.8.8.8s.8-.3.8-.8V8.7H9c.4 0 .8-.3.8-.8s-.5-.7-1-.7zm15-4.9v-.1h-.1c-.1 0-9.2 1.2-14.4 11.7-3.8 7.6-3.6 9.9-3.3 9.9.3.1 3.4-6.5 6.7-9.2 5.2-1.1 6.6-3.6 6.6-3.6s-1.5.2-2.1.2c-.8 0-1.4-.2-1.7-.3 1.3-1.2 2.4-1.5 3.5-1.7.9-.2 1.8-.4 3-1.2 2.2-1.6 1.9-5.5 1.8-5.7z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_LIGHT = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 512 512"><g><path fill="currentColor" d="M502.42 240.5l-94.7-47.3 33.5-100.4c4.5-13.6-8.4-26.5-21.9-21.9l-100.4 33.5-47.41-94.8a17.31 17.31 0 0 0-31 0l-47.3 94.7L92.7 70.8c-13.6-4.5-26.5 8.4-21.9 21.9l33.5 100.4-94.7 47.4a17.31 17.31 0 0 0 0 31l94.7 47.3-33.5 100.5c-4.5 13.6 8.4 26.5 21.9 21.9l100.41-33.5 47.3 94.7a17.31 17.31 0 0 0 31 0l47.31-94.7 100.4 33.5c13.6 4.5 26.5-8.4 21.9-21.9l-33.5-100.4 94.7-47.3a17.33 17.33 0 0 0 .2-31.1zm-155.9 106c-49.91 49.9-131.11 49.9-181 0a128.13 128.13 0 0 1 0-181c49.9-49.9 131.1-49.9 181 0a128.13 128.13 0 0 1 0 181z"></path><path fill="currentColor" d="M352 256a96 96 0 1 1-96-96 96.15 96.15 0 0 1 96 96z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_DARK = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 512 512"><g><path fill="currentColor" d="M320 32L304 0l-16 32-32 16 32 16 16 32 16-32 32-16zm138.7 149.3L432 128l-26.7 53.3L352 208l53.3 26.7L432 288l26.7-53.3L512 208z" ></path><path fill="currentColor" d="M332.2 426.4c8.1-1.6 13.9 8 8.6 14.5a191.18 191.18 0 0 1-149 71.1C85.8 512 0 426 0 320c0-120 108.7-210.6 227-188.8 8.2 1.6 10.1 12.6 2.8 16.7a150.3 150.3 0 0 0-76.1 130.8c0 94 85.4 165.4 178.5 147.7z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_NEWMSG = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M23.25 3.25h-2.425V.825c0-.414-.336-.75-.75-.75s-.75.336-.75.75V3.25H16.9c-.414 0-.75.336-.75.75s.336.75.75.75h2.425v2.425c0 .414.336.75.75.75s.75-.336.75-.75V4.75h2.425c.414 0 .75-.336.75-.75s-.336-.75-.75-.75zm-3.175 6.876c-.414 0-.75.336-.75.75v8.078c0 .414-.337.75-.75.75H4.095c-.412 0-.75-.336-.75-.75V8.298l6.778 4.518c.368.246.79.37 1.213.37.422 0 .844-.124 1.212-.37l4.53-3.013c.336-.223.428-.676.204-1.012-.223-.332-.675-.425-1.012-.2l-4.53 3.014c-.246.162-.563.163-.808 0l-7.586-5.06V5.5c0-.414.337-.75.75-.75h9.094c.414 0 .75-.336.75-.75s-.336-.75-.75-.75H4.096c-1.24 0-2.25 1.01-2.25 2.25v13.455c0 1.24 1.01 2.25 2.25 2.25h14.48c1.24 0 2.25-1.01 2.25-2.25v-8.078c0-.415-.337-.75-.75-.75z"></path></g></svg>
|
||||
}
|
||||
|
||||
export const ICON_SEND = (props) => {
|
||||
return <svg style={props.styles} viewBox="0 0 24 24"><g><path d="M21.13 11.358L3.614 2.108c-.29-.152-.64-.102-.873.126-.23.226-.293.577-.15.868l4.362 8.92-4.362 8.92c-.143.292-.08.643.15.868.145.14.333.212.523.212.12 0 .24-.028.35-.087l17.517-9.25c.245-.13.4-.386.4-.664s-.155-.532-.4-.662zM4.948 4.51l12.804 6.762H8.255l-3.307-6.76zm3.307 8.26h9.498L4.948 19.535l3.307-6.763z"></path></g></svg>
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
export function getBlockDetailUrl(blockHash, network) {
|
||||
switch (network) {
|
||||
case 'mainnet':
|
||||
return `https://blockstream.info/block/${blockHash}`;
|
||||
case 'testnet':
|
||||
return `https://blockstream.info/testnet/block/${blockHash}`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
TextField,
|
||||
DialogActions,
|
||||
Button,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
addTwitterAccountRequest,
|
||||
getSigningProfilesRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function AddTwitterAccountDialog({
|
||||
open,
|
||||
handleClose,
|
||||
reloadAccountsFn,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [twitterHandle, setTwitterHandle] = useState('');
|
||||
const [bearerToken, setBearerToken] = useState('');
|
||||
const [profileId, setProfileId] = useState(-1);
|
||||
const [signingProfiles, setSigningProfiles] = useState([]);
|
||||
|
||||
const resetFields = () => {
|
||||
setTwitterHandle('');
|
||||
setBearerToken('');
|
||||
setProfileId(-1);
|
||||
};
|
||||
|
||||
const handleChangeTwitterHandle = (event) => {
|
||||
setTwitterHandle(event.target.value);
|
||||
};
|
||||
|
||||
const handleChangeBearerToken = (event) => {
|
||||
setBearerToken(event.target.value);
|
||||
};
|
||||
|
||||
const handleChangeProfileId = (event) => {
|
||||
setProfileId(event.target.value);
|
||||
};
|
||||
|
||||
const handleResponse = (response) => {
|
||||
reloadAccountsFn();
|
||||
};
|
||||
|
||||
const handleErr = (err) => {
|
||||
alert(`Error adding twitter account: ${err}`);
|
||||
};
|
||||
|
||||
const addTwitterAccount = (twitterHandle, profileId, bearerToken) => {
|
||||
addTwitterAccountRequest(twitterHandle, profileId, bearerToken, handleResponse, handleErr);
|
||||
};
|
||||
|
||||
const loadSigningProfiles = () => {
|
||||
getSigningProfilesRequest(setSigningProfiles);
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('twitterHandle:', twitterHandle);
|
||||
console.log('profileId:', profileId);
|
||||
if (profileId === -1) {
|
||||
alert('Signing profile must be selected.');
|
||||
return;
|
||||
}
|
||||
if (!twitterHandle) {
|
||||
alert('twitterHandle cannot be empty.');
|
||||
return;
|
||||
}
|
||||
if (!bearerToken) {
|
||||
alert('bearerToken cannot be empty.');
|
||||
return;
|
||||
}
|
||||
addTwitterAccount(twitterHandle, profileId, bearerToken);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function load(event) {
|
||||
loadSigningProfiles();
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
event.stopPropagation();
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function ignore(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function AccountHandleInput() {
|
||||
return (
|
||||
<TextField
|
||||
id="standard-textarea"
|
||||
label="Twitter Handle"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
autoFocus
|
||||
value={twitterHandle}
|
||||
onChange={handleChangeTwitterHandle}
|
||||
fullWidth
|
||||
inputProps={{ maxLength: 128 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function BearerTokenInput() {
|
||||
return (
|
||||
<TextField
|
||||
id="standard-textarea-bearer-token"
|
||||
label="Bearer Token"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
value={bearerToken}
|
||||
onChange={handleChangeBearerToken}
|
||||
fullWidth
|
||||
inputProps={{ maxLength: 128 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectSigningProfile() {
|
||||
return (
|
||||
<FormControl className={classes.formControl} required style={{ minWidth: 120 }}>
|
||||
<InputLabel id="demo-simple-select-label">Signing Profile</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
value={profileId}
|
||||
onChange={handleChangeProfileId}
|
||||
>
|
||||
{signingProfiles.map((p) => <MenuItem key={p.getProfileId()} value={p.getProfileId()}>{p.getProfileName()}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function AddTwitterAccountButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Add Twitter Account
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onRendered={load} onEnter={resetFields} onClose={cancel} onClick={ignore} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Add Twitter Account</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{AccountHandleInput()}
|
||||
{BearerTokenInput()}
|
||||
{SelectSigningProfile()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{AddTwitterAccountButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "AddTwitterAccountDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "AddTwitterAccountDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
17
frontend/src/components/Alerts/index.js
Normal file
17
frontend/src/components/Alerts/index.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import React, {useContext} from 'react'
|
||||
import { StoreContext } from '../../store/store'
|
||||
import './style.scss'
|
||||
|
||||
const Alert = (props) => {
|
||||
const { state, actions } = useContext(StoreContext)
|
||||
|
||||
return(
|
||||
<div style={{top: state.top}} className="alert-wrapper">
|
||||
<div className="alert-content">
|
||||
{state.msg}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Alert
|
||||
23
frontend/src/components/Alerts/style.scss
Normal file
23
frontend/src/components/Alerts/style.scss
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
.alert-wrapper{
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
width: 150px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
text-align: center;
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
transition: top 0.4s ease-in;
|
||||
}
|
||||
|
||||
.alert-content{
|
||||
background-color: #1da1f2;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
border: 1px solid #e6ecf0;
|
||||
-webkit-box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
display:inline-block;
|
||||
padding: 8px 14px;
|
||||
border-radius: 200px;
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
HashRouter, Route, Switch, Redirect,
|
||||
} from 'react-router-dom';
|
||||
|
||||
// components
|
||||
import Layout from './Layout';
|
||||
|
||||
// pages
|
||||
import Error from '../pages/error';
|
||||
import Login from '../pages/login';
|
||||
|
||||
// context
|
||||
// import { useUserState } from '../context/UserContext';
|
||||
|
||||
export default function App() {
|
||||
// global
|
||||
// const { isAuthenticated } = useUserState();
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<Switch>
|
||||
<Route exact path="/" render={() => <Redirect to="/app/timeline" />} />
|
||||
<Route
|
||||
exact
|
||||
path="/app"
|
||||
render={() => <Redirect to="/app/timeline" />}
|
||||
/>
|
||||
<PublicRoute path="/app" component={Layout} />
|
||||
{' '}
|
||||
<PublicRoute path="/login" component={Login} />
|
||||
<Route component={Error} />
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
);
|
||||
|
||||
// #######################################################################
|
||||
|
||||
// function PrivateRoute({ component, ...rest }) {
|
||||
// return (
|
||||
// <Route
|
||||
// {...rest}
|
||||
// render={props =>
|
||||
// isAuthenticated ? (
|
||||
// React.createElement(component, props)
|
||||
// ) : (
|
||||
// <Redirect
|
||||
// to={{
|
||||
// pathname: "/login",
|
||||
// state: {
|
||||
// from: props.location,
|
||||
// },
|
||||
// }}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
function PublicRoute({ component, ...rest }) {
|
||||
return (
|
||||
<Route
|
||||
{...rest}
|
||||
render={(props) => React.createElement(component, props)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Typography,
|
||||
Grid,
|
||||
Box,
|
||||
Link,
|
||||
} from '@material-ui/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
// styles
|
||||
import moment from 'moment';
|
||||
|
||||
import {
|
||||
goToPeerAddressPage,
|
||||
goToLightningNodePage,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
export default function BuyOfferDetailItem({
|
||||
offer,
|
||||
...props
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
const onPeerClick = (event) => {
|
||||
event.preventDefault();
|
||||
goToPeerAddressPage(
|
||||
history,
|
||||
offer.getPeerAddress().getNetwork(),
|
||||
offer.getPeerAddress().getHost(),
|
||||
offer.getPeerAddress().getPort(),
|
||||
);
|
||||
};
|
||||
|
||||
const onLightningNodeClick = (event) => {
|
||||
event.preventDefault();
|
||||
goToLightningNodePage(
|
||||
history,
|
||||
offer.getNodePubkey(),
|
||||
offer.getNodeHost(),
|
||||
offer.getNodePort(),
|
||||
);
|
||||
};
|
||||
|
||||
function OfferContent() {
|
||||
return (
|
||||
<Typography
|
||||
variant="h4"
|
||||
>
|
||||
Price:
|
||||
{' '}
|
||||
{offer.getPriceMsat() / 1000}
|
||||
{' '}
|
||||
sats
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
function PeerInfoContent() {
|
||||
console.log(offer);
|
||||
const peerAddress = offer.getPeerAddress();
|
||||
const peerAddressText = `${peerAddress.getHost()}:${peerAddress.getPort()}`;
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
size="md"
|
||||
>
|
||||
Peer:
|
||||
{' '}
|
||||
<Link href="#" onClick={onPeerClick}>
|
||||
{peerAddressText}
|
||||
</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ExpiresInfoContent(offer) {
|
||||
const invoiceTime = offer.getInvoiceTimestamp();
|
||||
const invoiceExpiry = offer.getInvoiceExpiry();
|
||||
const expireTime = invoiceTime + invoiceExpiry;
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
size="md"
|
||||
>
|
||||
Expires:
|
||||
{' '}
|
||||
{moment(expireTime * 1000).fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function LightningPeerInfoContent(offer) {
|
||||
const lightningAddress = `${offer.getNodeHost()}:${offer.getNodePort()}`;
|
||||
const lightningPubkey = offer.getNodePubkey();
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
size="md"
|
||||
>
|
||||
Lightning Node:
|
||||
{' '}
|
||||
<Link href="#" onClick={onLightningNodeClick}>
|
||||
{`${lightningPubkey}@${lightningAddress}`}
|
||||
</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
p={1}
|
||||
m={0}
|
||||
style={{ backgroundColor: 'white' }}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item>
|
||||
{OfferContent()}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item>
|
||||
{PeerInfoContent()}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item>
|
||||
{LightningPeerInfoContent(offer)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item>
|
||||
{ExpiresInfoContent(offer)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "BuyOfferDetailItem",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "BuyOfferDetailItem.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Typography,
|
||||
Grid,
|
||||
Box,
|
||||
} from '@material-ui/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
// styles
|
||||
import moment from 'moment';
|
||||
import useStyles from './styles';
|
||||
|
||||
export default function BuyOfferItem({
|
||||
offer,
|
||||
handleOfferClick,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const onOfferClick = (event) => {
|
||||
event.preventDefault();
|
||||
console.log('Handling offer click...');
|
||||
if (handleOfferClick) {
|
||||
handleOfferClick();
|
||||
}
|
||||
};
|
||||
|
||||
function OfferContent() {
|
||||
return (
|
||||
<Typography
|
||||
size="md"
|
||||
>
|
||||
{offer.getPriceMsat()}
|
||||
{' '}
|
||||
msats (
|
||||
{offer.getPriceMsat() / 1000}
|
||||
{' '}
|
||||
sats)
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileInfoContent(peer) {
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
size="md"
|
||||
>
|
||||
{peer.getPeerName()}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ExpiresInfoContent(offer) {
|
||||
const invoiceTime = offer.getInvoiceTimestamp();
|
||||
const invoiceExpiry = offer.getInvoiceExpiry();
|
||||
const expireTime = invoiceTime + invoiceExpiry;
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
size="md"
|
||||
>
|
||||
Expires:
|
||||
{' '}
|
||||
{moment(expireTime * 1000).fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
p={1}
|
||||
m={0}
|
||||
style={{ backgroundColor: 'white' }}
|
||||
onClick={onOfferClick}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item>
|
||||
{OfferContent()}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item>
|
||||
{ProfileInfoContent(offer.getPeer())}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item>
|
||||
{ExpiresInfoContent(offer)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "BuyOfferItem",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "BuyOfferItem.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
MenuItem,
|
||||
Typography,
|
||||
Box,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
CircularProgress,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import BuyOfferDetailItem from '../BuyOfferDetailItem';
|
||||
import DownloadInProgressDialog from '../../components/DownloadInProgressDialog';
|
||||
|
||||
|
||||
import {
|
||||
downloadOffersRequest,
|
||||
getBuyOffersRequest,
|
||||
// subscribeBuyOffersRequest,
|
||||
payOfferRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function BuySqueakDialog({
|
||||
open,
|
||||
handleClose,
|
||||
handlePaymentComplete,
|
||||
hash,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [selectedOfferId, setSelectedOfferId] = useState('');
|
||||
const [offers, setOffers] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [waitingForDownloadOffers, setWaitingForDownloadOffers] = useState(false);
|
||||
|
||||
const resetFields = () => {
|
||||
setSelectedOfferId('');
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
setSelectedOfferId(event.target.value);
|
||||
};
|
||||
|
||||
const loadOffers = useCallback(() => {
|
||||
getBuyOffersRequest(hash, setOffers);
|
||||
},
|
||||
[hash, setOffers]);
|
||||
// const subscribeOffers = useCallback(() => subscribeBuyOffersRequest(hash, (offer) => {
|
||||
// setOffers((prevOffers) => prevOffers.concat([offer]));
|
||||
// }),
|
||||
// [hash, setOffers]);
|
||||
|
||||
const handleDownloadOffers = () => {
|
||||
console.log(`downloadOffersRequest with hash: ${hash}`);
|
||||
setWaitingForDownloadOffers(true);
|
||||
console.log(`downloadOffersRequest with hash: ${hash}`);
|
||||
downloadOffersRequest(hash, (response) => {
|
||||
setWaitingForDownloadOffers(false);
|
||||
const downloadResult = response.getDownloadResult();
|
||||
const numPeers = downloadResult.getNumberPeers();
|
||||
const numDownloaded = downloadResult.getNumberDownloaded();
|
||||
if (numPeers === 0) {
|
||||
alert("Unable to download because zero connected peers.");
|
||||
} else {
|
||||
alert(`Downloaded ${numDownloaded} offers from ${numPeers} connected peers.`);
|
||||
}
|
||||
getBuyOffersRequest(hash, setOffers);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCloseOffersDownloadInProgressDialog = () => {
|
||||
setWaitingForDownloadOffers(false);
|
||||
};
|
||||
|
||||
const handlePayResponse = (response) => {
|
||||
handlePaymentComplete();
|
||||
setLoading(false);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handlePayErr = (err) => {
|
||||
alert(`Payment failure: ${err}`);
|
||||
setLoading(false);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const pay = (offerId) => {
|
||||
setLoading(true);
|
||||
payOfferRequest(offerId, handlePayResponse, handlePayErr);
|
||||
};
|
||||
|
||||
const onDownloadClick = (event) => {
|
||||
event.preventDefault();
|
||||
console.log('Handling download click...');
|
||||
console.log(`downloadOffersRequest with hash: ${hash}`);
|
||||
// downloadOffers();
|
||||
handleDownloadOffers();
|
||||
};
|
||||
|
||||
const getSelectedOffer = () => {
|
||||
let offer;
|
||||
for (offer of offers) {
|
||||
if (offer.getOfferId() === selectedOfferId) {
|
||||
return offer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getPeerAddressText = (offer) => {
|
||||
const peerAddress = offer.getPeerAddress();
|
||||
return `${peerAddress.getHost()}:${peerAddress.getPort()}`;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadOffers();
|
||||
}, [loadOffers]);
|
||||
// useEffect(() => {
|
||||
// const stream = subscribeOffers();
|
||||
// return () => stream.cancel();
|
||||
// }, [subscribeOffers, hash]);
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('selectedOfferId:', selectedOfferId);
|
||||
if (selectedOfferId === '') {
|
||||
alert('Offer must be selected.');
|
||||
return;
|
||||
}
|
||||
pay(selectedOfferId);
|
||||
// handleClose();
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
event.stopPropagation();
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function ignore(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function SelectOfferContent() {
|
||||
return (
|
||||
<FormControl className={classes.formControl} required style={{ minWidth: 120 }}>
|
||||
<InputLabel id="offer-select-label">Offer</InputLabel>
|
||||
<Select
|
||||
labelId="offer-select-label"
|
||||
id="offer-select"
|
||||
value={selectedOfferId}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{offers.map((offer) => (
|
||||
<MenuItem key={offer.getOfferId()} value={offer.getOfferId()}>
|
||||
{getPeerAddressText(offer)}
|
||||
{' '}
|
||||
(
|
||||
{offer.getPriceMsat() / 1000}
|
||||
{' '}
|
||||
sats)
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
function LoadOffersButton() {
|
||||
return (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={onDownloadClick}
|
||||
>
|
||||
Re-download offers
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectedOfferContentInput() {
|
||||
const selectedOffer = getSelectedOffer();
|
||||
if (selectedOffer == null) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
p={1}
|
||||
>
|
||||
<BuyOfferDetailItem
|
||||
offer={selectedOffer}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelBuyButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function BuySqueakButton() {
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
>
|
||||
Buy Squeak
|
||||
</Button>
|
||||
{loading && <CircularProgress size={24} className={classes.buttonProgress} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OffersDownloadInProgressDialogContent() {
|
||||
return (
|
||||
<>
|
||||
<DownloadInProgressDialog
|
||||
open={waitingForDownloadOffers}
|
||||
handleClose={handleCloseOffersDownloadInProgressDialog}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onEnter={resetFields} onClose={cancel} onClick={ignore} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Buy Squeak
|
||||
</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
<Box>
|
||||
{LoadOffersButton()}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body1" color="textSecondary" component="p">
|
||||
{offers.length}
|
||||
{' '}
|
||||
offers
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
{SelectOfferContent()}
|
||||
</Box>
|
||||
{SelectedOfferContentInput()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelBuyButton()}
|
||||
{BuySqueakButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
{OffersDownloadInProgressDialogContent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "BuySqueakDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "BuySqueakDialog.js"
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
import { green } from '@material-ui/core/colors';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
root: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
wrapper: {
|
||||
margin: theme.spacing(1),
|
||||
position: 'relative',
|
||||
},
|
||||
buttonSuccess: {
|
||||
backgroundColor: green[500],
|
||||
'&:hover': {
|
||||
backgroundColor: green[700],
|
||||
},
|
||||
},
|
||||
fabProgress: {
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
top: -6,
|
||||
left: -6,
|
||||
zIndex: 1,
|
||||
},
|
||||
buttonProgress: {
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: -12,
|
||||
marginLeft: -12,
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
LinearProgress,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import useStyles from '../../pages/wallet/styles';
|
||||
|
||||
export default function ChannelBalanceBar({
|
||||
channel,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Box className={classes.channelBalanceBarContainer}>
|
||||
<Box className={classes.channelBalanceDetailsContainer}>
|
||||
<Box className={classes.balanceContainerCapacity}>
|
||||
<Typography className={classes.balanceLabel}>Capacity</Typography>
|
||||
<Typography className={classes.balanceValue}>{channel.getCapacity()}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className={classes.channelBalanceBarRow}>
|
||||
<Box className={classes.balanceContainerLocal}>
|
||||
<Typography className={classes.balanceLabel}>Local</Typography>
|
||||
<Typography className={classes.balanceValue} style={{ color: 'darkblue' }}>{channel.getLocalBalance()}</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="buffer"
|
||||
value={channel.getLocalBalance() / channel.getCapacity() * 100}
|
||||
valueBuffer={channel.getRemoteBalance() / channel.getCapacity() * 100}
|
||||
className={classes.channelBalanceProgressBar}
|
||||
classes={{
|
||||
buffer: classes.channelBalanceBuffer,
|
||||
dashed: classes.channelBalanceDashed,
|
||||
bar: classes.channelBalanceBar,
|
||||
bar1Buffer: classes.channelBalanceBar1Buffer,
|
||||
bar2Buffer: classes.channelBalanceBar2Buffer,
|
||||
}}
|
||||
/>
|
||||
<Box className={classes.balanceContainerRemote}>
|
||||
<Typography className={classes.balanceLabel}>Remote</Typography>
|
||||
<Typography className={classes.balanceValue} style={{ color: 'darkred' }}>{channel.getRemoteBalance()}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Typography,
|
||||
Box,
|
||||
Chip,
|
||||
Card,
|
||||
} from '@material-ui/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import moment from 'moment';
|
||||
import useStyles from '../../pages/wallet/styles';
|
||||
import ChannelBalanceBar from './ChannelBalanceBar';
|
||||
|
||||
import {
|
||||
goToChannelPage,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
export default function ChannelItem({
|
||||
channel,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles({
|
||||
channelStatus: 'open',
|
||||
clickable: true,
|
||||
});
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const getTxId = (channel) => {
|
||||
const channelPoint = channel.getChannelPoint();
|
||||
if (channelPoint == null) {
|
||||
return null;
|
||||
}
|
||||
const pieces = channelPoint.split(':');
|
||||
return pieces[0];
|
||||
};
|
||||
|
||||
const getOutputIndex = (channel) => {
|
||||
const channelPoint = channel.getChannelPoint();
|
||||
if (channelPoint == null) {
|
||||
return null;
|
||||
}
|
||||
const pieces = channelPoint.split(':');
|
||||
if (pieces.length < 2) {
|
||||
return null;
|
||||
}
|
||||
return pieces[1];
|
||||
};
|
||||
|
||||
const onChannelClick = (event) => {
|
||||
event.preventDefault();
|
||||
console.log('Handling channel click...');
|
||||
const txId = getTxId(channel);
|
||||
const outputIndex = getOutputIndex(channel);
|
||||
goToChannelPage(history, txId, outputIndex);
|
||||
};
|
||||
|
||||
function ChannelDetailItem(label, value) {
|
||||
return (
|
||||
<Box className={classes.detailItem}>
|
||||
<Typography className={classes.detailItemLabel}>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography className={classes.detailItemValue}>
|
||||
{value}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={classes.root}
|
||||
onClick={onChannelClick}
|
||||
>
|
||||
<Box className={classes.cardContentContainerVertical}>
|
||||
<Box className={classes.cardContentFlexColumn}>
|
||||
<Box className={classes.cardHeaderContainer}>
|
||||
<Box className={classes.cardHeaderRow}>
|
||||
<Chip
|
||||
label="Open"
|
||||
size="small"
|
||||
className={classes.channelStatusChip}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className={classes.cardContentSection}>
|
||||
{ChannelDetailItem('Pubkey', `${channel.getRemotePubkey()}`)}
|
||||
{ChannelDetailItem('Channel Point', `${channel.getChannelPoint()}`)}
|
||||
{/* {ChannelDetailItem("Total sats sent", `${channel.getTotalSatoshisSent()}`)} */}
|
||||
{/* {ChannelDetailItem("Total sats received", `${channel.getTotalSatoshisReceived()}`)} */}
|
||||
{ChannelDetailItem('Lifetime', `${moment.duration(channel.getLifetime(), 'seconds').humanize()}`)}
|
||||
{ChannelDetailItem('Uptime', `${moment.duration(channel.getUptime(), 'seconds').humanize()}`)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className={classes.cardContentFlexColumn}>
|
||||
<ChannelBalanceBar
|
||||
channel={channel}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "ChannelItem",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "ChannelItem.js"
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
clearSqueakProfileImageRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function ClearProfileImageDialog({
|
||||
open,
|
||||
handleClose,
|
||||
squeakProfile,
|
||||
reloadProfile,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const handleResponse = (response) => {
|
||||
reloadProfile();
|
||||
};
|
||||
|
||||
const handleErr = (err) => {
|
||||
alert(`Error clearing profile image: ${err}`);
|
||||
};
|
||||
|
||||
const clearProfileImage = () => {
|
||||
clearSqueakProfileImageRequest(
|
||||
squeakProfile.getProfileId(),
|
||||
handleResponse,
|
||||
handleErr,
|
||||
);
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
clearProfileImage();
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function ClearProfileImageButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Clear Profile Image
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Clear Profile Image</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
Are you sure you want to clear the image for this profile?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{ClearProfileImageButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "ClearProfileImageDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "ClearProfileImageDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
clearSellPriceRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function ClearSellPriceDialog({
|
||||
open,
|
||||
handleClose,
|
||||
reloadSellPriceFn,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const clearSellPrice = () => {
|
||||
clearSellPriceRequest(() => {
|
||||
reloadSellPriceFn();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('clear price msat');
|
||||
clearSellPrice();
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function SubmitButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Clear Sell Price
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function SellPriceForm() {
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Clear Sell Price</FormLabel>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Clear Sell Price</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{SellPriceForm()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{SubmitButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "ClearSellPriceDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "ClearSellPriceDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
TextField,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
lndCloseChannelRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function CloseChannelDialog({
|
||||
open,
|
||||
txId,
|
||||
outputIndex,
|
||||
handleClose,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const resetFields = () => {
|
||||
// TODO
|
||||
};
|
||||
|
||||
const handleResponse = (response) => {
|
||||
// TODO: handle streaming response.
|
||||
// TODO: go to channel page instead of showing alert.
|
||||
};
|
||||
|
||||
const handleErr = (err) => {
|
||||
alert(`Error closing channel: ${err}`);
|
||||
};
|
||||
|
||||
const closeChannel = (txId, outputIndex) => {
|
||||
lndCloseChannelRequest(txId, outputIndex, handleResponse, handleErr);
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('txId:', txId);
|
||||
console.log('outputIndex:', outputIndex);
|
||||
closeChannel(txId, outputIndex);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function TxIdInput() {
|
||||
return (
|
||||
<TextField
|
||||
id="txid-textarea"
|
||||
label="TxId"
|
||||
required
|
||||
autoFocus
|
||||
value={txId}
|
||||
fullWidth
|
||||
inputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function OutputIndexInput() {
|
||||
return (
|
||||
<TextField
|
||||
id="outputindex-textarea"
|
||||
label="Output Index"
|
||||
required
|
||||
autoFocus
|
||||
value={outputIndex}
|
||||
fullWidth
|
||||
inputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function CloseChannelButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Close Channel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onEnter={resetFields} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Close Channel</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{TxIdInput()}
|
||||
</DialogContent>
|
||||
<DialogContent>
|
||||
{OutputIndexInput()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{CloseChannelButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "CloseChannelDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "CloseChannelDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormGroup,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
setPeerAutoconnectRequest,
|
||||
setPeerShareForFreeRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function ConfigurePeerDialog({
|
||||
open,
|
||||
handleClose,
|
||||
savedPeer,
|
||||
reloadPeer,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const setAutoconnect = (id, autoconnect) => {
|
||||
setPeerAutoconnectRequest(id, autoconnect, () => {
|
||||
reloadPeer();
|
||||
});
|
||||
};
|
||||
|
||||
const setShareForFree = (id, shareForFree) => {
|
||||
setPeerShareForFreeRequest(id, shareForFree, () => {
|
||||
reloadPeer();
|
||||
});
|
||||
};
|
||||
|
||||
const handleSettingsAutoconnectChange = (event) => {
|
||||
console.log(`Autoconnect changed for peer id: ${savedPeer.getPeerId()}`);
|
||||
console.log(`Autoconnect changed to: ${event.target.checked}`);
|
||||
setAutoconnect(savedPeer.getPeerId(), event.target.checked);
|
||||
};
|
||||
|
||||
const handleSettingsShareForFreeChange = (event) => {
|
||||
console.log(`ShareForFree changed for peer id: ${savedPeer.getPeerId()}`);
|
||||
console.log(`ShareForFree changed to: ${event.target.checked}`);
|
||||
setShareForFree(savedPeer.getPeerId(), event.target.checked);
|
||||
};
|
||||
|
||||
function MakeCancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function PeerSettingsForm() {
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Peer settings</FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={savedPeer.getAutoconnect()} onChange={handleSettingsAutoconnectChange} />}
|
||||
label="Autoconnect"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={savedPeer.getShareForFree()} onChange={handleSettingsShareForFreeChange} />}
|
||||
label="Share for free"
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Configure Peer</DialogTitle>
|
||||
<form className={classes.root} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{savedPeer
|
||||
&& PeerSettingsForm()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{MakeCancelButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "ConfigurePeerDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "ConfigurePeerDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormGroup,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
setSqueakProfileFollowingRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function ConfigureProfileDialog({
|
||||
open,
|
||||
handleClose,
|
||||
squeakProfile,
|
||||
reloadProfile,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const setFollowing = (id, following) => {
|
||||
setSqueakProfileFollowingRequest(id, following, () => {
|
||||
reloadProfile();
|
||||
});
|
||||
};
|
||||
|
||||
const handleSettingsFollowingChange = (event) => {
|
||||
console.log(`Following changed for profile id: ${squeakProfile.getProfileId()}`);
|
||||
console.log(`Following changed to: ${event.target.checked}`);
|
||||
setFollowing(squeakProfile.getProfileId(), event.target.checked);
|
||||
};
|
||||
|
||||
function MakeCancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileSettingsForm() {
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Profile settings</FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={squeakProfile.getFollowing()} onChange={handleSettingsFollowingChange} />}
|
||||
label="Following"
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Configure Profile</DialogTitle>
|
||||
<form className={classes.root} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{squeakProfile
|
||||
&& ProfileSettingsForm()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{MakeCancelButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "ConfigureProfileDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "ConfigureProfileDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
TextField,
|
||||
CircularProgress,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import {
|
||||
getDefaultPeerPortRequest,
|
||||
connectSqueakPeerRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
export default function ConnectPeerDialog({
|
||||
open,
|
||||
handleClose,
|
||||
handlePeerConnected,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [defaultPeerPort, setDefaultPeerPort] = useState(null);
|
||||
const [host, setHost] = useState('');
|
||||
const [port, setPort] = useState('');
|
||||
const [customPortChecked, setCustomPortChecked] = useState(false);
|
||||
const [useTorChecked, setUseTorChecked] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const getDefaultPeerPort = () => {
|
||||
getDefaultPeerPortRequest(setDefaultPeerPort);
|
||||
};
|
||||
|
||||
const resetFields = () => {
|
||||
setHost('');
|
||||
setPort('');
|
||||
setCustomPortChecked(false);
|
||||
};
|
||||
|
||||
function load(event) {
|
||||
getDefaultPeerPort();
|
||||
resetFields();
|
||||
}
|
||||
|
||||
const handleChangeHost = (event) => {
|
||||
setHost(event.target.value);
|
||||
};
|
||||
|
||||
const handleChangeCustomPortChecked = (event) => {
|
||||
// setPort(
|
||||
// event.target.checked ? '' : defaultPeerPort,
|
||||
// );
|
||||
setPort('');
|
||||
setCustomPortChecked(event.target.checked);
|
||||
};
|
||||
|
||||
const handleChangePort = (event) => {
|
||||
setPort(event.target.value);
|
||||
};
|
||||
|
||||
const handleChangeUseTorChecked = (event) => {
|
||||
setUseTorChecked(event.target.checked);
|
||||
};
|
||||
|
||||
const getNetwork = (useTor) => {
|
||||
if (useTor) {
|
||||
return 'TORV3';
|
||||
}
|
||||
return 'IPV4';
|
||||
};
|
||||
|
||||
const connectPeer = (network, host, port) => {
|
||||
setLoading(true);
|
||||
console.log('Calling connectSqueakPeerRequest with:', network, host, port);
|
||||
connectSqueakPeerRequest(network, host, port, (response) => {
|
||||
// goToPeerPage(history, response.getPeerId());
|
||||
// handlePeerConnected();
|
||||
handlePeerConnectedResponse();
|
||||
},
|
||||
handlePeerConnectedErr);
|
||||
};
|
||||
|
||||
const handlePeerConnectedResponse = (response) => {
|
||||
handlePeerConnected();
|
||||
setLoading(false);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handlePeerConnectedErr = (err) => {
|
||||
alert(`Connect Peer failure: ${err}`);
|
||||
setLoading(false);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('useTorChecked:', useTorChecked);
|
||||
console.log('host:', host);
|
||||
console.log('port:', port);
|
||||
console.log('customPortChecked:', customPortChecked);
|
||||
const network = getNetwork(useTorChecked);
|
||||
if (!host) {
|
||||
alert('Host cannot be empty.');
|
||||
return;
|
||||
}
|
||||
if (!port && customPortChecked) {
|
||||
alert('port cannot be empty if using custom port.');
|
||||
return;
|
||||
}
|
||||
const portToUse = (customPortChecked ? port : defaultPeerPort);
|
||||
connectPeer(network, host, portToUse);
|
||||
// handleClose();
|
||||
}
|
||||
|
||||
function ConnectHostInput() {
|
||||
return (
|
||||
<TextField
|
||||
required
|
||||
variant="outlined"
|
||||
label="Host"
|
||||
autoFocus
|
||||
value={host}
|
||||
onChange={handleChangeHost}
|
||||
inputProps={{ maxLength: 128 }}
|
||||
margin="normal"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Explanation for value: https://stackoverflow.com/questions/50955603/react-material-ui-label-overlaps-with-text#comment123095620_54226170
|
||||
function ConnectPortInput() {
|
||||
return (
|
||||
<TextField
|
||||
required={customPortChecked}
|
||||
variant="outlined"
|
||||
label="Port"
|
||||
value={customPortChecked ? port : defaultPeerPort || ''}
|
||||
onChange={handleChangePort}
|
||||
inputProps={{ maxLength: 8 }}
|
||||
disabled={!customPortChecked}
|
||||
margin="normal"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomPortSwitch() {
|
||||
return (
|
||||
<FormControlLabel
|
||||
className={classes.formControlLabel}
|
||||
control={(
|
||||
<Switch
|
||||
checked={customPortChecked}
|
||||
onChange={handleChangeCustomPortChecked}
|
||||
name="use-custom-port"
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
label="Use custom port"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UseTorSwitch() {
|
||||
return (
|
||||
<FormControlLabel
|
||||
className={classes.formControlLabel}
|
||||
control={(
|
||||
<Switch
|
||||
checked={useTorChecked}
|
||||
onChange={handleChangeUseTorChecked}
|
||||
name="use-tor"
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
label="Use Tor"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function ConnectPeerButton() {
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
>
|
||||
Connect Peer
|
||||
</Button>
|
||||
{loading && <CircularProgress size={24} className={classes.buttonProgress} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onEnter={load}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Connect Peer
|
||||
</DialogTitle>
|
||||
<form
|
||||
className={classes.form}
|
||||
onSubmit={handleSubmit}
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
>
|
||||
<DialogContent>
|
||||
{ConnectHostInput()}
|
||||
{ConnectPortInput()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CustomPortSwitch()}
|
||||
</DialogActions>
|
||||
<DialogActions>
|
||||
{UseTorSwitch()}
|
||||
</DialogActions>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{ConnectPeerButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "ConnectPeerDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "ConnectPeerDialog.js"
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
import { green } from '@material-ui/core/colors';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
form: {
|
||||
margin: 'auto',
|
||||
width: 'fit-content',
|
||||
'& .MuiDialogContent-root': {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
'& .MuiTextField-root': {
|
||||
margin: theme.spacing(1),
|
||||
},
|
||||
'& .MuiDialogActions-root': {
|
||||
padding: '1rem',
|
||||
},
|
||||
},
|
||||
formControlLabel: {
|
||||
position: 'absolute',
|
||||
left: '2rem',
|
||||
},
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
root: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
wrapper: {
|
||||
margin: theme.spacing(1),
|
||||
position: 'relative',
|
||||
},
|
||||
buttonSuccess: {
|
||||
backgroundColor: green[500],
|
||||
'&:hover': {
|
||||
backgroundColor: green[700],
|
||||
},
|
||||
},
|
||||
fabProgress: {
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
top: -6,
|
||||
left: -6,
|
||||
zIndex: 1,
|
||||
},
|
||||
buttonProgress: {
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: -12,
|
||||
marginLeft: -12,
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
TextField,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
createContactProfileRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
import {
|
||||
goToProfilePage,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
export default function CreateContactProfileDialog({
|
||||
open,
|
||||
handleClose,
|
||||
initialPubkey = '',
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
|
||||
const [profileName, setProfileName] = useState('');
|
||||
const [pubkey, setPubkey] = useState(initialPubkey);
|
||||
|
||||
const resetFields = () => {
|
||||
setProfileName('');
|
||||
setPubkey(initialPubkey);
|
||||
};
|
||||
|
||||
const handleChangeProfileName = (event) => {
|
||||
setProfileName(event.target.value);
|
||||
};
|
||||
|
||||
const handleChangePubkey = (event) => {
|
||||
setPubkey(event.target.value);
|
||||
};
|
||||
|
||||
const handleResponse = (response) => {
|
||||
goToProfilePage(history, response.getProfileId());
|
||||
};
|
||||
|
||||
const handleErr = (err) => {
|
||||
alert(`Error creating contact profile: ${err}`);
|
||||
};
|
||||
|
||||
const createContactProfile = (profileName, squeakPubkey) => {
|
||||
createContactProfileRequest(profileName, squeakPubkey, handleResponse, handleErr);
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('profileName:', profileName);
|
||||
console.log('pubkey:', pubkey);
|
||||
if (!profileName) {
|
||||
alert('Profile Name cannot be empty.');
|
||||
return;
|
||||
}
|
||||
if (!pubkey) {
|
||||
alert('Pubkey Name cannot be empty.');
|
||||
return;
|
||||
}
|
||||
createContactProfile(profileName, pubkey);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function CreateContactProfileNameInput() {
|
||||
return (
|
||||
<TextField
|
||||
id="standard-textarea"
|
||||
label="Profile Name"
|
||||
variant="outlined"
|
||||
required
|
||||
autoFocus
|
||||
value={profileName}
|
||||
onChange={handleChangeProfileName}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
inputProps={{ maxLength: 64 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateContactPubkeyInput() {
|
||||
return (
|
||||
<TextField
|
||||
required
|
||||
id="standard-textarea"
|
||||
label="Pubkey"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
value={pubkey}
|
||||
onChange={handleChangePubkey}
|
||||
inputProps={{ maxLength: 66 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateContactProfilButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Create Contact Profile
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onEnter={resetFields} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Create Contact Profile</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{CreateContactProfileNameInput()}
|
||||
{CreateContactPubkeyInput()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{CreateContactProfilButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "CreateContactProfileDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "CreateContactProfileDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,285 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
TextField,
|
||||
} from '@material-ui/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
// styles
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
import {
|
||||
getDefaultPeerPortRequest,
|
||||
createPeerRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
import {
|
||||
goToPeerPage,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
form: {
|
||||
margin: 'auto',
|
||||
width: 'fit-content',
|
||||
'& .MuiDialogContent-root': {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
'& .MuiTextField-root': {
|
||||
margin: theme.spacing(1),
|
||||
},
|
||||
'& .MuiDialogActions-root': {
|
||||
padding: '1rem',
|
||||
},
|
||||
},
|
||||
formControlLabel: {
|
||||
position: 'absolute',
|
||||
left: '2rem',
|
||||
},
|
||||
}));
|
||||
|
||||
export default function CreatePeerDialog({
|
||||
open,
|
||||
handleClose,
|
||||
initialNetwork = '',
|
||||
initialHost = '',
|
||||
initialPort = '',
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
|
||||
const [defaultPeerPort, setDefaultPeerPort] = useState(null);
|
||||
const [peerName, setPeerName] = useState('');
|
||||
const [host, setHost] = useState('');
|
||||
const [port, setPort] = useState('');
|
||||
const [customPortChecked, setCustomPortChecked] = useState(false);
|
||||
const [useTorChecked, setUseTorChecked] = useState(false);
|
||||
|
||||
const getDefaultPeerPort = () => {
|
||||
getDefaultPeerPortRequest(setDefaultPeerPort);
|
||||
};
|
||||
|
||||
const resetFields = () => {
|
||||
setPeerName('');
|
||||
setHost('');
|
||||
if (initialNetwork === 'TORV3') {
|
||||
setUseTorChecked(true);
|
||||
}
|
||||
if (initialHost) {
|
||||
setHost(initialHost);
|
||||
}
|
||||
setPort('');
|
||||
setCustomPortChecked(false);
|
||||
if (initialPort) {
|
||||
setPort(initialPort);
|
||||
setCustomPortChecked(true);
|
||||
}
|
||||
};
|
||||
|
||||
function load(event) {
|
||||
getDefaultPeerPort();
|
||||
resetFields();
|
||||
}
|
||||
|
||||
const handleChangePeerName = (event) => {
|
||||
setPeerName(event.target.value);
|
||||
};
|
||||
|
||||
const handleChangeHost = (event) => {
|
||||
setHost(event.target.value);
|
||||
};
|
||||
|
||||
const handleChangeCustomPortChecked = (event) => {
|
||||
// setPort(
|
||||
// event.target.checked ? '' : portDefaultValue,
|
||||
// );
|
||||
setPort('');
|
||||
setCustomPortChecked(event.target.checked);
|
||||
};
|
||||
|
||||
const handleChangePort = (event) => {
|
||||
setPort(event.target.value);
|
||||
};
|
||||
|
||||
const handleChangeUseTorChecked = (event) => {
|
||||
setUseTorChecked(event.target.checked);
|
||||
};
|
||||
|
||||
const getNetwork = (useTor) => {
|
||||
if (useTor) {
|
||||
return 'TORV3';
|
||||
}
|
||||
return 'IPV4';
|
||||
};
|
||||
|
||||
const createPeer = (network, peerName, host, port) => {
|
||||
createPeerRequest(peerName, network, host, port, (response) => {
|
||||
goToPeerPage(history, response.getPeerId());
|
||||
});
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('peerName:', peerName);
|
||||
console.log('host:', host);
|
||||
console.log('port:', port);
|
||||
const network = getNetwork(useTorChecked);
|
||||
if (!host) {
|
||||
alert('Host cannot be empty.');
|
||||
return;
|
||||
}
|
||||
if (!port && customPortChecked) {
|
||||
alert('port cannot be empty if using custom port.');
|
||||
return;
|
||||
}
|
||||
const portToUse = (customPortChecked ? port : defaultPeerPort);
|
||||
createPeer(network, peerName, host, portToUse);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function CreatePeerNameInput() {
|
||||
return (
|
||||
<TextField
|
||||
required
|
||||
variant="outlined"
|
||||
label="Peer Name"
|
||||
autoFocus
|
||||
value={peerName}
|
||||
onChange={handleChangePeerName}
|
||||
fullWidth
|
||||
inputProps={{ maxLength: 64 }}
|
||||
margin="normal"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateHostInput() {
|
||||
return (
|
||||
<TextField
|
||||
required
|
||||
variant="outlined"
|
||||
label="Host"
|
||||
value={host}
|
||||
onChange={handleChangeHost}
|
||||
inputProps={{ maxLength: 128 }}
|
||||
margin="normal"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Explanation for value: https://stackoverflow.com/questions/50955603/react-material-ui-label-overlaps-with-text#comment123095620_54226170
|
||||
function CreatePortInput() {
|
||||
return (
|
||||
<TextField
|
||||
required={customPortChecked}
|
||||
variant="outlined"
|
||||
label="Port"
|
||||
value={customPortChecked ? port : defaultPeerPort || ''}
|
||||
onChange={handleChangePort}
|
||||
inputProps={{ maxLength: 8 }}
|
||||
disabled={!customPortChecked}
|
||||
margin="normal"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomPortSwitch() {
|
||||
return (
|
||||
<FormControlLabel
|
||||
className={classes.formControlLabel}
|
||||
control={(
|
||||
<Switch
|
||||
checked={customPortChecked}
|
||||
onChange={handleChangeCustomPortChecked}
|
||||
name="use-custom-port"
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
label="Use custom port"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UseTorSwitch() {
|
||||
return (
|
||||
<FormControlLabel
|
||||
className={classes.formControlLabel}
|
||||
control={(
|
||||
<Switch
|
||||
checked={useTorChecked}
|
||||
onChange={handleChangeUseTorChecked}
|
||||
name="use-tor"
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
label="Use Tor"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function CreatePeerButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Create Peer
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onEnter={load}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Create Peer
|
||||
</DialogTitle>
|
||||
<form
|
||||
className={classes.form}
|
||||
onSubmit={handleSubmit}
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
>
|
||||
<DialogContent>
|
||||
{CreatePeerNameInput()}
|
||||
{CreateHostInput()}
|
||||
{CreatePortInput()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CustomPortSwitch()}
|
||||
</DialogActions>
|
||||
<DialogActions>
|
||||
{UseTorSwitch()}
|
||||
</DialogActions>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{CreatePeerButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "CreatePeerDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "CreatePeerDialog.js"
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
TextField,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
createSigningProfileRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
import {
|
||||
goToProfilePage,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
export default function CreateSigningProfileDialog({
|
||||
open,
|
||||
handleClose,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
|
||||
const [profileName, setProfileName] = useState('');
|
||||
|
||||
const resetFields = () => {
|
||||
setProfileName('');
|
||||
};
|
||||
|
||||
const handleChangeProfileName = (event) => {
|
||||
setProfileName(event.target.value);
|
||||
};
|
||||
|
||||
const handleResponse = (response) => {
|
||||
goToProfilePage(history, response.getProfileId());
|
||||
};
|
||||
|
||||
const handleErr = (err) => {
|
||||
alert(`Error creating signing profile: ${err}`);
|
||||
};
|
||||
|
||||
const createSigningProfile = (profileName) => {
|
||||
createSigningProfileRequest(profileName, handleResponse, handleErr);
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('profileName:', profileName);
|
||||
if (!profileName) {
|
||||
alert('Profile Name cannot be empty.');
|
||||
return;
|
||||
}
|
||||
createSigningProfile(profileName);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function CreateSigningProfileNameInput() {
|
||||
return (
|
||||
<TextField
|
||||
id="standard-textarea"
|
||||
label="Profile Name"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
autoFocus
|
||||
value={profileName}
|
||||
onChange={handleChangeProfileName}
|
||||
fullWidth
|
||||
inputProps={{ maxLength: 64 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateSigningProfilButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Create Signing Profile
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onEnter={resetFields} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Create Signing Profile</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{CreateSigningProfileNameInput()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{CreateSigningProfilButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "CreateSigningProfileDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "CreateSigningProfileDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
MenuItem,
|
||||
Box,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
CircularProgress,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
getSigningProfilesRequest,
|
||||
// decryptSqueakRequest,
|
||||
decryptRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function DecryptSqueakDialog({
|
||||
open,
|
||||
handleClose,
|
||||
handleDecryptComplete,
|
||||
hash,
|
||||
squeak,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [profileId, setProfileId] = useState(-1);
|
||||
const [signingProfiles, setSigningProfiles] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const resetFields = () => {
|
||||
setProfileId('');
|
||||
};
|
||||
|
||||
const handleChangeProfileId = (event) => {
|
||||
setProfileId(event.target.value);
|
||||
};
|
||||
|
||||
const loadSigningProfiles = useCallback(() => {
|
||||
getSigningProfilesRequest(setSigningProfiles);
|
||||
},
|
||||
[setSigningProfiles]);
|
||||
|
||||
const handleDecryptResponse = (response) => {
|
||||
handleDecryptComplete();
|
||||
setLoading(false);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleDecryptErr = (err) => {
|
||||
alert(`Decryption failure: ${err}`);
|
||||
setLoading(false);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const decrypt = (profileId) => {
|
||||
const hasRecipient = profileId !== -1;
|
||||
setLoading(true);
|
||||
decryptRequest(hash, null, -1, hasRecipient, profileId, handleDecryptResponse, handleDecryptErr);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadSigningProfiles();
|
||||
}, [loadSigningProfiles]);
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('profileId:', profileId);
|
||||
if (profileId === '') {
|
||||
alert('Profile must be selected.');
|
||||
return;
|
||||
}
|
||||
decrypt(profileId);
|
||||
// handleClose();
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
event.stopPropagation();
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function ignore(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function SelectProfileContent() {
|
||||
return (
|
||||
<FormControl className={classes.formControl} required style={{ minWidth: 120 }}>
|
||||
<InputLabel id="demo-simple-select-label">Signing Profile</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
value={profileId}
|
||||
onChange={handleChangeProfileId}
|
||||
>
|
||||
{signingProfiles.map((p) => <MenuItem key={p.getProfileId()} value={p.getProfileId()}>{p.getProfileName()}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelDecryptButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function DecryptSqueakButton() {
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
>
|
||||
Decrypt Squeak
|
||||
</Button>
|
||||
{loading && <CircularProgress size={24} className={classes.buttonProgress} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onEnter={resetFields} onClose={cancel} onClick={ignore} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Decrypt Squeak
|
||||
</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
<Box>
|
||||
{SelectProfileContent()}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelDecryptButton()}
|
||||
{DecryptSqueakButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "DecryptSqueakDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "DecryptSqueakDialog.js"
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
import { green } from '@material-ui/core/colors';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
root: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
wrapper: {
|
||||
margin: theme.spacing(1),
|
||||
position: 'relative',
|
||||
},
|
||||
buttonSuccess: {
|
||||
backgroundColor: green[500],
|
||||
'&:hover': {
|
||||
backgroundColor: green[700],
|
||||
},
|
||||
},
|
||||
fabProgress: {
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
top: -6,
|
||||
left: -6,
|
||||
zIndex: 1,
|
||||
},
|
||||
buttonProgress: {
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: -12,
|
||||
marginLeft: -12,
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
deletePeerRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
|
||||
export default function DeletePeerDialog({
|
||||
open,
|
||||
handleClose,
|
||||
peer,
|
||||
reloadPeer,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const deletePeer = (peerId) => {
|
||||
deletePeerRequest(peerId, (response) => {
|
||||
reloadPeer();
|
||||
});
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('peer:', peer);
|
||||
const peerId = peer.getPeerId();
|
||||
console.log('peerId:', peerId);
|
||||
deletePeer(peerId);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function MakeCancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function DeletePeerButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Delete peer
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Delete peer</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
Are you sure you want to delete this peer?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{MakeCancelButton()}
|
||||
{DeletePeerButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "DeletePeerDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "DeletePeerDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
deleteProfileRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function DeleteProfileDialog({
|
||||
open,
|
||||
handleClose,
|
||||
profile,
|
||||
reloadProfile,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const deleteProfile = (profileId) => {
|
||||
deleteProfileRequest(profileId, (response) => {
|
||||
reloadProfile();
|
||||
});
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('profile:', profile);
|
||||
const profileId = profile.getProfileId();
|
||||
console.log('profileId:', profileId);
|
||||
deleteProfile(profileId);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function MakeCancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteProfileButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Delete Profile
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Delete Profile</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
Are you sure you want to delete this profile?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{MakeCancelButton()}
|
||||
{DeleteProfileButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "DeleteProfileDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "DeleteProfileDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
deleteSqueakRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
import {
|
||||
reloadRoute,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
export default function DeleteSqueakDialog({
|
||||
open,
|
||||
handleClose,
|
||||
squeakToDelete,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
|
||||
const deleteSqueak = (squeakHash) => {
|
||||
deleteSqueakRequest(squeakHash, (response) => {
|
||||
reloadRoute(history);
|
||||
});
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
console.log('squeakToDelete:', squeakToDelete);
|
||||
const squeakHash = squeakToDelete.getSqueakHash();
|
||||
console.log('squeakHash:', squeakHash);
|
||||
deleteSqueak(squeakHash);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
event.stopPropagation();
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function ignore(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function MakeCancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={cancel}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteSqueakButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Delete Squeak
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={cancel} onBackdropClick={cancel} onClick={ignore} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Delete Squeak</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
Are you sure you want to delete this squeak?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{MakeCancelButton()}
|
||||
{DeleteSqueakButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "DeleteSqueakDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "DeleteSqueakDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
deleteTwitterAccountRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function DeleteTwitterAccountDialog({
|
||||
open,
|
||||
handleClose,
|
||||
twitterAccount,
|
||||
reloadAccountsFn,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const deleteTwitterAccount = (twitterAccountId) => {
|
||||
deleteTwitterAccountRequest(twitterAccountId, (response) => {
|
||||
reloadAccountsFn();
|
||||
});
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('twitter account:', twitterAccount);
|
||||
const twitterAccountId = twitterAccount.getTwitterAccountId();
|
||||
console.log('twitterAccountId:', twitterAccountId);
|
||||
deleteTwitterAccount(twitterAccountId);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function MakeCancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteTwitterAccountButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Delete Twitter Account
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Delete Twitter Account Mapping</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
Are you sure you want to delete this twitter account mapping?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{MakeCancelButton()}
|
||||
{DeleteTwitterAccountButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "DeleteTwitterAccountDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "DeleteTwitterAccountDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
CircularProgress,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
export default function DownloadInProgressDialog({
|
||||
open,
|
||||
handleClose,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Download in progress</DialogTitle>
|
||||
<form className={classes.root} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
Downloading...
|
||||
</DialogContent>
|
||||
<DialogContent>
|
||||
<CircularProgress size={48} className={classes.buttonProgress} />
|
||||
</DialogContent>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "DownloadInProgressDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "DownloadInProgressDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
TextField,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
getSqueakProfilePrivateKey,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function ExportPrivateKeyDialog({
|
||||
open,
|
||||
handleClose,
|
||||
profile,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [privateKey, setPrivateKey] = useState('');
|
||||
|
||||
const resetFields = () => {
|
||||
setPrivateKey('');
|
||||
};
|
||||
|
||||
const getPrivateKey = () => {
|
||||
const profileId = profile.getProfileId();
|
||||
getSqueakProfilePrivateKey(profileId, (response) => {
|
||||
setPrivateKey(response.getPrivateKey());
|
||||
});
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
getPrivateKey();
|
||||
}
|
||||
|
||||
function DisplayPrivateKey() {
|
||||
return (
|
||||
<TextField
|
||||
id="standard-textarea"
|
||||
label="private-key"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
autoFocus
|
||||
value={privateKey}
|
||||
fullWidth
|
||||
inputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function ShowPrivateKeyButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Show private key
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onEnter={resetFields} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Export Private Key</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{DisplayPrivateKey()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{ShowPrivateKeyButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "ExportPrivateKeyDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "ExportPrivateKeyDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
51
frontend/src/components/Feed/index.js
Normal file
51
frontend/src/components/Feed/index.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import React , { useEffect, useContext } from 'react'
|
||||
import './style.scss'
|
||||
import { withRouter, Link } from 'react-router-dom'
|
||||
import { StoreContext } from '../../store/store'
|
||||
import Loader from '../Loader'
|
||||
|
||||
|
||||
const Feed = (props) => {
|
||||
|
||||
const { state, actions } = useContext(StoreContext)
|
||||
|
||||
const {paymentSummary, trends, session} = state
|
||||
// const userParam = props.match.params.username
|
||||
|
||||
useEffect(() => {
|
||||
actions.getPaymentSummary();
|
||||
actions.getTrend()
|
||||
}, [])
|
||||
|
||||
const goToUser = (id) => {
|
||||
props.history.push(`/app/profile/${id}`)
|
||||
}
|
||||
|
||||
const followUser = (e, id) => {
|
||||
e.stopPropagation()
|
||||
actions.followUser(id)
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="feed-wrapper">
|
||||
{paymentSummary ?
|
||||
<div className="feed-trending-card">
|
||||
<h3 className="feed-card-header">Payments</h3>
|
||||
<div onClick={()=>props.history.push('/app/payments')}className="feed-card-trend">
|
||||
<div>Amount Spent</div>
|
||||
<div>{paymentSummary.getAmountSpentMsat() / 1000} sats</div>
|
||||
<div>{paymentSummary.getNumSentPayments()} squeaks</div>
|
||||
</div>
|
||||
<div onClick={()=>props.history.push('/app/payments')}className="feed-card-trend">
|
||||
<div>Amount Earned</div>
|
||||
<div>{paymentSummary.getAmountEarnedMsat() / 1000} sats</div>
|
||||
<div>{paymentSummary.getNumReceivedPayments()} squeaks</div>
|
||||
</div>
|
||||
</div> :
|
||||
<Loader/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(Feed)
|
||||
133
frontend/src/components/Feed/style.scss
Normal file
133
frontend/src/components/Feed/style.scss
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
.feed-wrapper{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
position: sticky;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.feed-trending-card{
|
||||
width: 100%;
|
||||
background-color: rgb(245, 248, 250);
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid rgb(245, 248, 250);
|
||||
border-top-left-radius: 14px;
|
||||
border-top-right-radius: 14px;
|
||||
border-bottom-left-radius: 14px;
|
||||
border-bottom-right-radius: 14px;
|
||||
}
|
||||
|
||||
.feed-card-header{
|
||||
border-bottom: 1px solid rgb(230, 236, 240);
|
||||
padding: 10px 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.feed-card-trend{
|
||||
border-bottom: 1px solid rgb(230, 236, 240);
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease-in-out;
|
||||
&:hover{
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
div:nth-child(1){
|
||||
font-size: 13px;
|
||||
color: rgb(101, 119, 134);
|
||||
}
|
||||
div:nth-child(2){
|
||||
font-size: 15px;
|
||||
color: rgb(20, 23, 26);
|
||||
font-weight: bold;
|
||||
padding-top: 2px;
|
||||
}
|
||||
div:nth-child(3){
|
||||
font-size: 15px;
|
||||
color: rgb(101, 119, 134);
|
||||
padding-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.feed-more{
|
||||
padding: 15px;
|
||||
transition: 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
color: rgb(29,161,242);
|
||||
}
|
||||
|
||||
|
||||
////@extend
|
||||
|
||||
.sugg-result-wapper{
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
||||
}
|
||||
|
||||
.search-userPic-wrapper{
|
||||
flex-basis: 49px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.search-user-details{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-user-info{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-user-name{
|
||||
font-weight: bold;
|
||||
color: rgb(20, 23, 26) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.search-user-username{
|
||||
color: #657786 !important;
|
||||
font-weight: 400 !important;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.search-user-bio{
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.search-user-warp{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.follow-btn-wrap{
|
||||
min-height: 30px;
|
||||
min-width: 70px;
|
||||
transition: 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: 1px solid #1da1f2;
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
&:hover{
|
||||
background-color: rgba(29, 161, 242, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.follow-btn-wrap span{
|
||||
text-align: center;
|
||||
font-weight: 800;
|
||||
color: #1da1f2;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
IconButton,
|
||||
InputBase,
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@material-ui/core';
|
||||
import {
|
||||
Menu as MenuIcon,
|
||||
NotificationsNone as NotificationsIcon,
|
||||
Person as AccountIcon,
|
||||
Search as SearchIcon,
|
||||
ArrowBack as ArrowBackIcon,
|
||||
} from '@material-ui/icons';
|
||||
import classNames from 'classnames';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
// components
|
||||
import { Badge, Typography } from '../Wrappers/Wrappers';
|
||||
import Notification from '../Notification/Notification';
|
||||
|
||||
// context
|
||||
import {
|
||||
useLayoutState,
|
||||
useLayoutDispatch,
|
||||
toggleSidebar,
|
||||
} from '../../context/LayoutContext';
|
||||
// import { useUserDispatch } from '../../context/UserContext';
|
||||
|
||||
import {
|
||||
logoutRequest,
|
||||
getUserRequest,
|
||||
getNetworkRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
import {
|
||||
reloadRoute,
|
||||
goToSearchPage,
|
||||
goToSettingsPage,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
const notifications = [];
|
||||
|
||||
export default function Header(props) {
|
||||
const classes = useStyles();
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
// global
|
||||
const layoutState = useLayoutState();
|
||||
const layoutDispatch = useLayoutDispatch();
|
||||
// const userDispatch = useUserDispatch();
|
||||
|
||||
// local
|
||||
const [notificationsMenu, setNotificationsMenu] = useState(null);
|
||||
const [isNotificationsUnread, setIsNotificationsUnread] = useState(true);
|
||||
const [profileMenu, setProfileMenu] = useState(null);
|
||||
const [username, setUsername] = useState('bob smith');
|
||||
const [network, setNetwork] = useState('');
|
||||
const [value, setValue] = useState();
|
||||
|
||||
|
||||
const isSearchOpen = true;
|
||||
|
||||
const getUser = () => {
|
||||
getUserRequest(setUsername);
|
||||
};
|
||||
|
||||
const getNetwork = () => {
|
||||
getNetworkRequest(setNetwork);
|
||||
};
|
||||
|
||||
const handleChangeSearchText = (event) => {
|
||||
setValue(event.target.value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getUser();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
getNetwork();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<Toolbar className={classes.toolbar}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={() => toggleSidebar(layoutDispatch)}
|
||||
className={classNames(
|
||||
classes.headerMenuButton,
|
||||
classes.headerMenuButtonCollapse,
|
||||
)}
|
||||
>
|
||||
{layoutState.isSidebarOpened ? (
|
||||
<ArrowBackIcon
|
||||
classes={{
|
||||
root: classNames(
|
||||
classes.headerIcon,
|
||||
classes.headerIconCollapse,
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MenuIcon
|
||||
classes={{
|
||||
root: classNames(
|
||||
classes.headerIcon,
|
||||
classes.headerIconCollapse,
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
<Typography variant="h6" weight="medium" className={classes.logotype}>
|
||||
Squeaknode
|
||||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div
|
||||
className={classNames(classes.search, {
|
||||
[classes.searchFocused]: isSearchOpen,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames(classes.searchIcon, {
|
||||
[classes.searchIconOpened]: isSearchOpen,
|
||||
})}
|
||||
>
|
||||
<SearchIcon classes={{ root: classes.headerIcon }} />
|
||||
</div>
|
||||
<InputBase
|
||||
classes={{
|
||||
root: classes.inputRoot,
|
||||
input: classes.inputInput,
|
||||
}}
|
||||
value={value}
|
||||
placeholder="Search…"
|
||||
inputProps={{ 'aria-label': 'search google maps' }}
|
||||
onChange={handleChangeSearchText}
|
||||
onKeyPress={(ev) => {
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault();
|
||||
const encodedText = encodeURIComponent(ev.target.value);
|
||||
setValue('');
|
||||
goToSearchPage(history, encodedText);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-haspopup="true"
|
||||
aria-controls="mail-menu"
|
||||
onClick={(e) => {
|
||||
setNotificationsMenu(e.currentTarget);
|
||||
setIsNotificationsUnread(false);
|
||||
}}
|
||||
className={classes.headerMenuButton}
|
||||
>
|
||||
<Badge
|
||||
badgeContent={isNotificationsUnread ? notifications.length : null}
|
||||
color="warning"
|
||||
>
|
||||
<NotificationsIcon classes={{ root: classes.headerIcon }} />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-haspopup="true"
|
||||
color="inherit"
|
||||
className={classes.headerMenuButton}
|
||||
aria-controls="profile-menu"
|
||||
onClick={(e) => setProfileMenu(e.currentTarget)}
|
||||
>
|
||||
<AccountIcon classes={{ root: classes.headerIcon }} />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="notifications-menu"
|
||||
open={Boolean(notificationsMenu)}
|
||||
anchorEl={notificationsMenu}
|
||||
onClose={() => setNotificationsMenu(null)}
|
||||
className={classes.headerMenu}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
{notifications.map((notification) => (
|
||||
<MenuItem
|
||||
key={notification.id}
|
||||
onClick={() => setNotificationsMenu(null)}
|
||||
className={classes.headerMenuItem}
|
||||
>
|
||||
<Notification {...notification} typographyVariant="inherit" />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
|
||||
<Menu
|
||||
id="profile-menu"
|
||||
open={Boolean(profileMenu)}
|
||||
anchorEl={profileMenu}
|
||||
onClose={() => setProfileMenu(null)}
|
||||
className={classes.headerMenu}
|
||||
classes={{ paper: classes.profileMenu }}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography variant="h5" weight="medium">
|
||||
{username}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography variant="h6" weight="medium">
|
||||
{network}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography
|
||||
className={classes.settingsMenuLink}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
goToSettingsPage(history)
|
||||
setProfileMenu(null);
|
||||
}
|
||||
}
|
||||
>
|
||||
Settings
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
color="primary"
|
||||
onClick={() => logoutRequest(
|
||||
() => {
|
||||
reloadRoute(history);
|
||||
},
|
||||
)}
|
||||
>
|
||||
Sign Out
|
||||
</Typography>
|
||||
</div>
|
||||
</Menu>
|
||||
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,466 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
IconButton,
|
||||
InputBase,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Fab,
|
||||
withStyles,
|
||||
} from '@material-ui/core';
|
||||
import {
|
||||
Menu as MenuIcon,
|
||||
MailOutline as MailIcon,
|
||||
NotificationsNone as NotificationsIcon,
|
||||
Person as AccountIcon,
|
||||
Search as SearchIcon,
|
||||
Send as SendIcon,
|
||||
ArrowBack as ArrowBackIcon,
|
||||
} from '@material-ui/icons';
|
||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Badge, Typography } from '../Wrappers';
|
||||
import Notification from '../Notification';
|
||||
import UserAvatar from '../UserAvatar';
|
||||
|
||||
const messages = [
|
||||
{
|
||||
id: 0,
|
||||
variant: 'warning',
|
||||
name: 'Jane Hew',
|
||||
message: 'Hey! How is it going?',
|
||||
time: '9:32',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
variant: 'success',
|
||||
name: 'Lloyd Brown',
|
||||
message: 'Check out my new Dashboard',
|
||||
time: '9:18',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
variant: 'primary',
|
||||
name: 'Mark Winstein',
|
||||
message: 'I want rearrange the appointment',
|
||||
time: '9:15',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
variant: 'secondary',
|
||||
name: 'Liana Dutti',
|
||||
message: 'Good news from sale department',
|
||||
time: '9:09',
|
||||
},
|
||||
];
|
||||
|
||||
const notifications = [
|
||||
{ id: 0, color: 'warning', message: 'Check out this awesome ticket' },
|
||||
{
|
||||
id: 1,
|
||||
color: 'success',
|
||||
type: 'info',
|
||||
message: 'What is the best way to get ...',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
color: 'secondary',
|
||||
type: 'notification',
|
||||
message: 'This is just a simple notification',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
color: 'primary',
|
||||
type: 'e-commerce',
|
||||
message: '12 new orders has arrived today',
|
||||
},
|
||||
];
|
||||
|
||||
const Header = ({
|
||||
classes, isSidebarOpened, toggleSidebar, ...props
|
||||
}) => (
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<Toolbar className={classes.toolbar}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={toggleSidebar}
|
||||
className={classNames(
|
||||
classes.headerMenuButton,
|
||||
classes.headerMenuButtonCollapse,
|
||||
)}
|
||||
>
|
||||
{isSidebarOpened ? (
|
||||
<ArrowBackIcon
|
||||
classes={{
|
||||
root: classNames(classes.headerIcon, classes.headerIconCollapse),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MenuIcon
|
||||
classes={{
|
||||
root: classNames(classes.headerIcon, classes.headerIconCollapse),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
<Typography variant="h6" weight="medium" className={classes.logotype}>React Material Admin</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div
|
||||
className={classNames(classes.search, {
|
||||
[classes.searchFocused]: props.isSearchOpen,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames(classes.searchIcon, {
|
||||
[classes.searchIconOpened]: props.isSearchOpen,
|
||||
})}
|
||||
onClick={props.toggleSearch}
|
||||
>
|
||||
<SearchIcon classes={{ root: classes.headerIcon }} />
|
||||
</div>
|
||||
<InputBase
|
||||
placeholder="Search…"
|
||||
classes={{
|
||||
root: classes.inputRoot,
|
||||
input: classes.inputInput,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-haspopup="true"
|
||||
aria-controls="mail-menu"
|
||||
onClick={props.openNotificationsMenu}
|
||||
className={classes.headerMenuButton}
|
||||
>
|
||||
<Badge
|
||||
badgeContent={
|
||||
props.isNotificationsUnread ? notifications.length : null
|
||||
}
|
||||
colortheme="warning"
|
||||
>
|
||||
<NotificationsIcon classes={{ root: classes.headerIcon }} />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-haspopup="true"
|
||||
aria-controls="mail-menu"
|
||||
onClick={props.openMailMenu}
|
||||
className={classes.headerMenuButton}
|
||||
>
|
||||
<Badge
|
||||
badgeContent={props.isMailsUnread ? messages.length : null}
|
||||
color="secondary"
|
||||
>
|
||||
<MailIcon classes={{ root: classes.headerIcon }} />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-haspopup="true"
|
||||
color="inherit"
|
||||
className={classes.headerMenuButton}
|
||||
aria-controls="profile-menu"
|
||||
onClick={props.openProfileMenu}
|
||||
>
|
||||
<AccountIcon classes={{ root: classes.headerIcon }} />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="mail-menu"
|
||||
open={Boolean(props.mailMenu)}
|
||||
anchorEl={props.mailMenu}
|
||||
onClose={props.closeMailMenu}
|
||||
MenuListProps={{ className: classes.headerMenuList }}
|
||||
className={classes.headerMenu}
|
||||
classes={{ paper: classes.profileMenu }}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography variant="h4" weight="medium">
|
||||
New Messages
|
||||
</Typography>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
component="a"
|
||||
color="secondary"
|
||||
>
|
||||
{messages.length}
|
||||
{' '}
|
||||
New Messages
|
||||
</Typography>
|
||||
</div>
|
||||
{messages.map((message) => (
|
||||
<MenuItem key={message.id} className={classes.messageNotification}>
|
||||
<div className={classes.messageNotificationSide}>
|
||||
<UserAvatar color={message.variant} name={message.name} />
|
||||
<Typography size="sm" color="textSecondary">
|
||||
{message.time}
|
||||
</Typography>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
classes.messageNotificationSide,
|
||||
classes.messageNotificationBodySide,
|
||||
)}
|
||||
>
|
||||
<Typography weight="medium" gutterBottom>
|
||||
{message.name}
|
||||
</Typography>
|
||||
<Typography color="textSecondary">{message.message}</Typography>
|
||||
</div>
|
||||
</MenuItem>
|
||||
))}
|
||||
<Fab
|
||||
variant="extended"
|
||||
color="primary"
|
||||
aria-label="Add"
|
||||
className={classes.sendMessageButton}
|
||||
>
|
||||
Send New Message
|
||||
<SendIcon className={classes.sendButtonIcon} />
|
||||
</Fab>
|
||||
</Menu>
|
||||
<Menu
|
||||
id="notifications-menu"
|
||||
open={Boolean(props.notificationsMenu)}
|
||||
anchorEl={props.notificationsMenu}
|
||||
onClose={props.closeNotificationsMenu}
|
||||
className={classes.headerMenu}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
{notifications.map((notification) => (
|
||||
<MenuItem
|
||||
key={notification.id}
|
||||
onClick={props.closeNotificationsMenu}
|
||||
className={classes.headerMenuItem}
|
||||
>
|
||||
<Notification {...notification} typographyVariant="inherit" />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu
|
||||
id="profile-menu"
|
||||
open={Boolean(props.profileMenu)}
|
||||
anchorEl={props.profileMenu}
|
||||
onClose={props.closeProfileMenu}
|
||||
className={classes.headerMenu}
|
||||
classes={{ paper: classes.profileMenu }}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography variant="h4" weight="medium">
|
||||
John Smith
|
||||
</Typography>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
component="a"
|
||||
color="primary"
|
||||
href="https://flatlogic.com"
|
||||
>
|
||||
Flalogic.com
|
||||
</Typography>
|
||||
</div>
|
||||
<MenuItem
|
||||
className={classNames(
|
||||
classes.profileMenuItem,
|
||||
classes.headerMenuItem,
|
||||
)}
|
||||
>
|
||||
<AccountIcon className={classes.profileMenuIcon} />
|
||||
{' '}
|
||||
Profile
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={classNames(
|
||||
classes.profileMenuItem,
|
||||
classes.headerMenuItem,
|
||||
)}
|
||||
>
|
||||
<AccountIcon className={classes.profileMenuIcon} />
|
||||
{' '}
|
||||
Tasks
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={classNames(
|
||||
classes.profileMenuItem,
|
||||
classes.headerMenuItem,
|
||||
)}
|
||||
>
|
||||
<AccountIcon className={classes.profileMenuIcon} />
|
||||
{' '}
|
||||
Messages
|
||||
</MenuItem>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
color="primary"
|
||||
onClick={props.signOut}
|
||||
>
|
||||
Sign Out
|
||||
</Typography>
|
||||
</div>
|
||||
</Menu>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
|
||||
const styles = (theme) => ({
|
||||
logotype: {
|
||||
color: 'white',
|
||||
marginLeft: theme.spacing.unit * 2.5,
|
||||
marginRight: theme.spacing.unit * 2.5,
|
||||
fontWeight: 500,
|
||||
fontSize: 18,
|
||||
whiteSpace: 'nowrap',
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
appBar: {
|
||||
width: '100vw',
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(['margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
},
|
||||
toolbar: {
|
||||
paddingLeft: theme.spacing.unit * 2,
|
||||
paddingRight: theme.spacing.unit * 2,
|
||||
},
|
||||
hide: {
|
||||
display: 'none',
|
||||
},
|
||||
grow: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
search: {
|
||||
position: 'relative',
|
||||
borderRadius: 25,
|
||||
paddingLeft: theme.spacing.unit * 2.5,
|
||||
width: 36,
|
||||
backgroundColor: fade(theme.palette.common.black, 0),
|
||||
transition: theme.transitions.create(['background-color', 'width']),
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
backgroundColor: fade(theme.palette.common.black, 0.08),
|
||||
},
|
||||
},
|
||||
searchFocused: {
|
||||
backgroundColor: fade(theme.palette.common.black, 0.08),
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: 250,
|
||||
},
|
||||
},
|
||||
searchIcon: {
|
||||
width: 36,
|
||||
right: 0,
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: theme.transitions.create('right'),
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
searchIconOpened: {
|
||||
right: theme.spacing.unit * 1.25,
|
||||
},
|
||||
inputRoot: {
|
||||
color: 'inherit',
|
||||
width: '100%',
|
||||
},
|
||||
inputInput: {
|
||||
height: 36,
|
||||
padding: 0,
|
||||
paddingRight: 36 + theme.spacing.unit * 1.25,
|
||||
width: '100%',
|
||||
},
|
||||
messageContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
headerMenu: {
|
||||
marginTop: theme.spacing.unit * 7,
|
||||
},
|
||||
headerMenuList: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
headerMenuItem: {
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
headerMenuButton: {
|
||||
marginLeft: theme.spacing.unit * 2,
|
||||
padding: theme.spacing.unit / 2,
|
||||
},
|
||||
headerMenuButtonCollapse: {
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
},
|
||||
headerIcon: {
|
||||
fontSize: 28,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
headerIconCollapse: {
|
||||
color: 'white',
|
||||
},
|
||||
profileMenu: {
|
||||
minWidth: 265,
|
||||
},
|
||||
profileMenuUser: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing.unit * 2,
|
||||
},
|
||||
profileMenuItem: {
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
profileMenuIcon: {
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
profileMenuLink: {
|
||||
fontSize: 16,
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
messageNotification: {
|
||||
height: 'auto',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: theme.palette.background.light,
|
||||
},
|
||||
},
|
||||
messageNotificationSide: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
},
|
||||
messageNotificationBodySide: {
|
||||
alignItems: 'flex-start',
|
||||
marginRight: 0,
|
||||
},
|
||||
sendMessageButton: {
|
||||
margin: theme.spacing.unit * 4,
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
marginBottom: theme.spacing.unit * 2,
|
||||
textTransform: 'none',
|
||||
},
|
||||
sendButtonIcon: {
|
||||
marginLeft: theme.spacing.unit * 2,
|
||||
},
|
||||
});
|
||||
|
||||
export default withStyles(styles)(Header);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "Header",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "Header.js"
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
logotype: {
|
||||
color: 'white',
|
||||
marginLeft: theme.spacing(2.5),
|
||||
marginRight: theme.spacing(2.5),
|
||||
fontWeight: 500,
|
||||
fontSize: 18,
|
||||
whiteSpace: 'nowrap',
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
appBar: {
|
||||
width: '100vw',
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(['margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
},
|
||||
toolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
hide: {
|
||||
display: 'none',
|
||||
},
|
||||
grow: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
search: {
|
||||
position: 'relative',
|
||||
borderRadius: 25,
|
||||
paddingLeft: theme.spacing(2.5),
|
||||
width: 36,
|
||||
backgroundColor: fade(theme.palette.common.black, 0),
|
||||
transition: theme.transitions.create(['background-color', 'width']),
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
backgroundColor: fade(theme.palette.common.black, 0.08),
|
||||
},
|
||||
},
|
||||
searchFocused: {
|
||||
backgroundColor: fade(theme.palette.common.black, 0.08),
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: 250,
|
||||
},
|
||||
},
|
||||
searchIcon: {
|
||||
width: 36,
|
||||
right: 0,
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: theme.transitions.create('right'),
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
searchIconOpened: {
|
||||
right: theme.spacing(1.25),
|
||||
},
|
||||
inputRoot: {
|
||||
color: 'inherit',
|
||||
width: '100%',
|
||||
},
|
||||
inputInput: {
|
||||
height: 36,
|
||||
padding: 0,
|
||||
paddingRight: 36 + theme.spacing(1.25),
|
||||
width: '100%',
|
||||
},
|
||||
messageContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
headerMenu: {
|
||||
marginTop: theme.spacing(7),
|
||||
},
|
||||
headerMenuList: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
headerMenuItem: {
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
headerMenuButton: {
|
||||
marginLeft: theme.spacing(2),
|
||||
padding: theme.spacing(0.5),
|
||||
},
|
||||
headerMenuButtonCollapse: {
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
headerIcon: {
|
||||
fontSize: 28,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
headerIconCollapse: {
|
||||
color: 'white',
|
||||
},
|
||||
profileMenu: {
|
||||
minWidth: 265,
|
||||
},
|
||||
profileMenuUser: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
profileMenuItem: {
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
profileMenuIcon: {
|
||||
marginRight: theme.spacing(2),
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
profileMenuLink: {
|
||||
fontSize: 16,
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
settingsMenuLink: {
|
||||
fontSize: 16,
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
messageNotification: {
|
||||
height: 'auto',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: theme.palette.background.light,
|
||||
},
|
||||
},
|
||||
messageNotificationSide: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
messageNotificationBodySide: {
|
||||
alignItems: 'flex-start',
|
||||
marginRight: 0,
|
||||
},
|
||||
sendMessageButton: {
|
||||
margin: theme.spacing(4),
|
||||
marginTop: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
textTransform: 'none',
|
||||
},
|
||||
sendButtonIcon: {
|
||||
marginLeft: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
64
frontend/src/components/Home/index.js
Normal file
64
frontend/src/components/Home/index.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import React, { useEffect, useState, useContext, useRef } from 'react'
|
||||
import { StoreContext } from '../../store/store'
|
||||
import './style.scss'
|
||||
import axios from 'axios'
|
||||
import ContentEditable from 'react-contenteditable'
|
||||
import { ICON_IMGUPLOAD } from '../../Icons'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { API_URL } from '../../config'
|
||||
import Loader from '../Loader'
|
||||
import TweetCard from '../TweetCard'
|
||||
import MakeSqueak from '../MakeSqueak'
|
||||
|
||||
|
||||
const Home = () => {
|
||||
const { state, actions } = useContext(StoreContext)
|
||||
const { session } = state
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0)
|
||||
// actions.getTweets({lastTweet: null})
|
||||
reloadTweets();
|
||||
}, [])
|
||||
|
||||
const getLastSqueak = (squeakLst) => {
|
||||
if (squeakLst == null) {
|
||||
return null;
|
||||
} if (squeakLst.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return squeakLst.slice(-1)[0];
|
||||
};
|
||||
|
||||
const getMoreTweets = () => {
|
||||
let lastTweet = getLastSqueak(state.tweets);
|
||||
actions.getTweets({lastTweet: lastTweet});
|
||||
}
|
||||
|
||||
const reloadTweets = () => {
|
||||
actions.clearTweets();
|
||||
actions.getTweets({lastTweet: null});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Home-wrapper">
|
||||
<div className="Home-header-wrapper">
|
||||
<h2 className="Home-header">
|
||||
Latest Tweets
|
||||
</h2>
|
||||
</div>
|
||||
{session ? <MakeSqueak /> : null }
|
||||
<div className="Tweet-input-divider"></div>
|
||||
{state.tweets.map(t => {
|
||||
return <TweetCard tweet={t} key={t.getSqueakHash()} id={t.getSqueakHash()} user={t.getAuthor()} />
|
||||
})}
|
||||
|
||||
{/* TODO: fix get loading state by doing this: https://medium.com/stashaway-engineering/react-redux-tips-better-way-to-handle-loading-flags-in-your-reducers-afda42a804c6 */}
|
||||
{state.loading ? <Loader /> : <div onClick={() => getMoreTweets()} className='tweet-btn-side tweet-btn-active'>
|
||||
Load more
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
40
frontend/src/components/Home/style.scss
Normal file
40
frontend/src/components/Home/style.scss
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
.Home-wrapper{
|
||||
max-width: 600px;
|
||||
border-right: 1px solid rgb(230, 236, 240);
|
||||
width: 100%;
|
||||
//but doesnt show when there are less elements
|
||||
// height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 2000px;
|
||||
}
|
||||
|
||||
.Home-header-wrapper{
|
||||
position: sticky;
|
||||
border-bottom: 1px solid rgb(230, 236, 240);
|
||||
border-left: 1px solid rgb(230, 236, 240);
|
||||
background-color: rgb(255, 255, 255);
|
||||
z-index: 3;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
height: 53px;
|
||||
min-height: 53px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.Home-header{
|
||||
font-weight: 800;
|
||||
font-size: 19px;
|
||||
color: rgb(20, 23, 26);
|
||||
line-height: 1.3125;
|
||||
}
|
||||
|
||||
.blue{
|
||||
color: rgb(29, 161, 242);
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
TextField,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
importSigningProfileRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
import {
|
||||
goToProfilePage,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
export default function ImportSigningProfileDialog({
|
||||
open,
|
||||
handleClose,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
|
||||
const [profileName, setProfileName] = useState('');
|
||||
const [privateKey, setPrivateKey] = useState('');
|
||||
|
||||
const resetFields = () => {
|
||||
setProfileName('');
|
||||
};
|
||||
|
||||
const handleChangeProfileName = (event) => {
|
||||
setProfileName(event.target.value);
|
||||
};
|
||||
|
||||
const handleChangePrivateKey = (event) => {
|
||||
setPrivateKey(event.target.value);
|
||||
};
|
||||
|
||||
const handleResponse = (response) => {
|
||||
goToProfilePage(history, response.getProfileId());
|
||||
};
|
||||
|
||||
const handleErr = (err) => {
|
||||
alert(`Error creating contact profile: ${err}`);
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('profileName:', profileName);
|
||||
console.log('privateKey:', privateKey);
|
||||
if (!profileName) {
|
||||
alert('Profile Name cannot be empty.');
|
||||
return;
|
||||
}
|
||||
if (!privateKey) {
|
||||
alert('Private Key cannot be empty.');
|
||||
return;
|
||||
}
|
||||
importSigningProfileRequest(profileName, privateKey, handleResponse, handleErr);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function ImportSigningProfileNameInput() {
|
||||
return (
|
||||
<TextField
|
||||
id="standard-textarea"
|
||||
label="Profile Name"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
autoFocus
|
||||
value={profileName}
|
||||
onChange={handleChangeProfileName}
|
||||
fullWidth
|
||||
inputProps={{ maxLength: 64 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportSigningProfilePrivateKeyInput() {
|
||||
return (
|
||||
<TextField
|
||||
id="standard-textarea"
|
||||
label="Private Key"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
value={privateKey}
|
||||
onChange={handleChangePrivateKey}
|
||||
fullWidth
|
||||
inputProps={{ maxLength: 64 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportSigningProfilButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Import Signing Profile
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onEnter={resetFields} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Import Signing Profile</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{ImportSigningProfileNameInput()}
|
||||
</DialogContent>
|
||||
<DialogContent>
|
||||
{ImportSigningProfilePrivateKeyInput()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{ImportSigningProfilButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "ImportSigningProfileDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "ImportSigningProfileDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Route,
|
||||
Switch,
|
||||
Redirect,
|
||||
withRouter,
|
||||
} from 'react-router-dom';
|
||||
import classnames from 'classnames';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
// components
|
||||
import Header from '../Header';
|
||||
import Sidebar from '../Sidebar';
|
||||
|
||||
// pages
|
||||
import Timeline from '../../pages/timeline';
|
||||
import SqueakAddress from '../../pages/squeakaddress';
|
||||
import Search from '../../pages/search';
|
||||
import Squeak from '../../pages/squeak';
|
||||
import Profile from '../../pages/profile';
|
||||
import Wallet from '../../pages/wallet';
|
||||
import LightningNode from '../../pages/lightningnode';
|
||||
import Channel from '../../pages/channel';
|
||||
import Notifications from '../../pages/notifications';
|
||||
import Profiles from '../../pages/profiles';
|
||||
import Payments from '../../pages/payments';
|
||||
import SentPayments from '../../pages/sentpayments';
|
||||
import ReceivedPayments from '../../pages/receivedpayments';
|
||||
import Liked from '../../pages/liked';
|
||||
import Icons from '../../pages/icons';
|
||||
import Charts from '../../pages/charts';
|
||||
import Peers from '../../pages/peers';
|
||||
import Peer from '../../pages/peer';
|
||||
import PeerAddress from '../../pages/peeraddress';
|
||||
import Twitter from '../../pages/twitter';
|
||||
import Settings from '../../pages/settings';
|
||||
|
||||
// context
|
||||
import { useLayoutState } from '../../context/LayoutContext';
|
||||
|
||||
function Layout(props) {
|
||||
const classes = useStyles();
|
||||
|
||||
// global
|
||||
const layoutState = useLayoutState();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<>
|
||||
<Header history={props.history} />
|
||||
<Sidebar />
|
||||
<div
|
||||
className={classnames(classes.content, {
|
||||
[classes.contentShift]: layoutState.isSidebarOpened,
|
||||
})}
|
||||
>
|
||||
<div className={classes.fakeToolbar} />
|
||||
<Switch>
|
||||
<Route path="/app/timeline" component={Timeline} />
|
||||
<Route path="/app/pubkey/:pubkey" component={SqueakAddress} />
|
||||
<Route path="/app/search/:searchText" component={Search} />
|
||||
<Route path="/app/search" component={Search} />
|
||||
<Route path="/app/squeak/:hash" component={Squeak} />
|
||||
<Route path="/app/profile/:id" component={Profile} />
|
||||
<Route path="/app/profiles" component={Profiles} />
|
||||
<Route path="/app/payments" component={Payments} />
|
||||
<Route path="/app/sentpayments" component={SentPayments} />
|
||||
<Route path="/app/receivedpayments" component={ReceivedPayments} />
|
||||
<Route path="/app/liked" component={Liked} />
|
||||
<Route path="/app/wallet" component={Wallet} />
|
||||
<Route path="/app/lightningnode/:pubkey/:host/:port" component={LightningNode} />
|
||||
<Route path="/app/lightningnode/:pubkey/:host" component={LightningNode} />
|
||||
<Route path="/app/lightningnode/:pubkey" component={LightningNode} />
|
||||
<Route path="/app/channel/:txId/:outputIndex" component={Channel} />
|
||||
<Route path="/app/peers" component={Peers} />
|
||||
<Route path="/app/peer/:id" component={Peer} />
|
||||
<Route path="/app/peeraddress/:network/:host/:port" component={PeerAddress} />
|
||||
<Route path="/app/notifications" component={Notifications} />
|
||||
<Route path="/app/twitter" component={Twitter} />
|
||||
<Route path="/app/settings" component={Settings} />
|
||||
<Route
|
||||
exact
|
||||
path="/app/ui"
|
||||
render={() => <Redirect to="/app/ui/icons" />}
|
||||
/>
|
||||
<Route path="/app/ui/icons" component={Icons} />
|
||||
<Route path="/app/ui/charts" component={Charts} />
|
||||
</Switch>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(Layout);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "Layout",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "Layout.js"
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
maxWidth: '100vw',
|
||||
overflowX: 'hidden',
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing(3),
|
||||
width: 'calc(100vw - 240px)',
|
||||
minHeight: '100vh',
|
||||
},
|
||||
contentShift: {
|
||||
width: `calc(100vw - ${240 + theme.spacing(6)}px)`,
|
||||
transition: theme.transitions.create(['width', 'margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
fakeToolbar: {
|
||||
...theme.mixins.toolbar,
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardHeader from '@material-ui/core/CardHeader';
|
||||
|
||||
// icons
|
||||
import ComputerIcon from '@material-ui/icons/Computer';
|
||||
|
||||
import useStyles from '../../pages/wallet/styles';
|
||||
|
||||
import {
|
||||
goToLightningNodePage,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
export default function LightningPeerListItem({
|
||||
peer,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles({
|
||||
clickable: true,
|
||||
});
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const onPeerClick = (event) => {
|
||||
event.preventDefault();
|
||||
console.log('Handling peer click...');
|
||||
const pubkey = peer.getPubKey();
|
||||
const host = getPeerHost();
|
||||
const port = getPeerPort();
|
||||
goToLightningNodePage(history, pubkey, host, port);
|
||||
};
|
||||
|
||||
const getPeerHost = () => {
|
||||
const address = peer.getAddress();
|
||||
if (address == null) {
|
||||
return null;
|
||||
}
|
||||
const pieces = address.split(':');
|
||||
return pieces[0];
|
||||
};
|
||||
|
||||
const getPeerPort = () => {
|
||||
const address = peer.getAddress();
|
||||
if (address == null) {
|
||||
return null;
|
||||
}
|
||||
const pieces = address.split(':');
|
||||
if (pieces.length < 2) {
|
||||
return null;
|
||||
}
|
||||
return pieces[1];
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={classes.root}
|
||||
onClick={onPeerClick}
|
||||
>
|
||||
<CardHeader
|
||||
avatar={<ComputerIcon />}
|
||||
title={`Pubkey: ${peer.getPubKey()}`}
|
||||
subheader={`Address: ${peer.getAddress()}`}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "LightningPeerListItem",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "LightningPeerListItem.js"
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
} from '@material-ui/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
export default function LndUnavailableDialog({
|
||||
open,
|
||||
handleClose,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Lnd Unavailable</DialogTitle>
|
||||
Unable to connect to lnd node.
|
||||
Make sure that lnd node is avaialable, and then reload page.
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "LndUnavailableDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "LndUnavailableDialog.js"
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
23
frontend/src/components/Loader/index.js
Normal file
23
frontend/src/components/Loader/index.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react'
|
||||
import './style.css'
|
||||
|
||||
const Loader = () => {
|
||||
return(
|
||||
<div className="loader-wrapper">
|
||||
<svg className="loader_svg" version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="40px" height="40px" viewBox="0 0 50 50" xmlSpace="preserve">
|
||||
<path fill="#1da1f2" d="M25.251,6.461c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615V6.461z">
|
||||
<animateTransform attributeType="xml"
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 25 25"
|
||||
to="360 25 25"
|
||||
dur="0.6s"
|
||||
repeatCount="indefinite"/>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Loader
|
||||
15
frontend/src/components/Loader/style.css
Normal file
15
frontend/src/components/Loader/style.css
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
.loader-wrapper{
|
||||
position: relative;
|
||||
height: 50%;
|
||||
margin: 50px 0;
|
||||
}
|
||||
|
||||
.loader_svg{
|
||||
fill: yellow;
|
||||
enable-background:new 0 0 50 50;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 15%;
|
||||
transform: translate(-50%,50%);
|
||||
}
|
||||
58
frontend/src/components/Login/index.js
Normal file
58
frontend/src/components/Login/index.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import React, { useState, useContext } from 'react'
|
||||
import { StoreContext } from '../../store/store'
|
||||
import './style.scss'
|
||||
import { Link, Redirect } from 'react-router-dom'
|
||||
import { ICON_LOGO } from '../../Icons'
|
||||
|
||||
const LoginPage = () => {
|
||||
const { state, actions } = useContext(StoreContext)
|
||||
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
const Login = (e) => {
|
||||
e.preventDefault()
|
||||
if(username.length && password.length){
|
||||
const values = {
|
||||
username,
|
||||
password
|
||||
}
|
||||
actions.login(values)
|
||||
}
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="login-wrapper">
|
||||
{state.loggedin && <Redirect to="/home" />}
|
||||
<ICON_LOGO/>
|
||||
<h1 className="login-header">
|
||||
Log in to Twitter
|
||||
</h1>
|
||||
{state.msg === 'Incorrect email or password' && <p className="login-error"> The username/email or password you entered is incorrect. </p>}
|
||||
<form id="loginForm" onSubmit={(e)=>Login(e)} className="login-form">
|
||||
<div className="login-input-wrap">
|
||||
<div className="login-input-content">
|
||||
<label>Email or username</label>
|
||||
<input onChange={(e)=>setUsername(e.target.value)} type="text" name="username" className="login-input"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="login-input-wrap">
|
||||
<div className="login-input-content">
|
||||
<label>Password</label>
|
||||
<input onChange={(e)=>setPassword(e.target.value)} type="password" name="password" className="login-input"/>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" form="loginForm" className={username.length && password.length > 0 ? "login-btn-wrap button-active": "login-btn-wrap"}>
|
||||
Log in
|
||||
</button>
|
||||
</form>
|
||||
<p className="signup-option">
|
||||
<Link to="/app/signup">
|
||||
Sign up for Twitter
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginPage
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue