mirror of
https://github.com/apotdevin/thunderhub.git
synced 2025-02-22 06:21:37 +01:00
Feat/nextjs (#25)
* feat: initial nextjs commit * chore: general card styles changes * chore: add storybook * chore: small changes and fixes * fix: trading filter encoding * fix: add link to node * chore: set to correct version
This commit is contained in:
parent
d0f6a038a9
commit
aa60d618f9
577 changed files with 21212 additions and 28048 deletions
11
.babelrc
Normal file
11
.babelrc
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"presets": ["next/babel"],
|
||||||
|
"plugins": [
|
||||||
|
"emotion",
|
||||||
|
"inline-react-svg",
|
||||||
|
[
|
||||||
|
"styled-components",
|
||||||
|
{ "ssr": true, "displayName": true, "preprocess": false }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
3
.commitlintrc.json
Normal file
3
.commitlintrc.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": ["@commitlint/config-conventional"]
|
||||||
|
}
|
12
.dockerignore
Normal file
12
.dockerignore
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.cache
|
||||||
|
*.md
|
||||||
|
!README*.md
|
||||||
|
/node_modules
|
||||||
|
/.next
|
||||||
|
/docs
|
||||||
|
/.github
|
||||||
|
.env
|
||||||
|
.vscode
|
||||||
|
CHANGELOG.md
|
32
.gitignore
vendored
32
.gitignore
vendored
|
@ -1 +1,31 @@
|
||||||
node_modules
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
2
.prettierignore
Normal file
2
.prettierignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/.next
|
||||||
|
/.node_modules
|
7
.prettierrc
Normal file
7
.prettierrc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 80,
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
24
.storybook/config.js
Normal file
24
.storybook/config.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { configure, addDecorator, addParameters } from '@storybook/react';
|
||||||
|
import themeDecorator from './themeDecorator';
|
||||||
|
import { withKnobs } from '@storybook/addon-knobs';
|
||||||
|
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
|
||||||
|
|
||||||
|
const customViewports = {
|
||||||
|
smallScreen: {
|
||||||
|
name: 'Small Screen',
|
||||||
|
styles: {
|
||||||
|
width: '578px',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
addParameters({
|
||||||
|
viewport: {
|
||||||
|
viewports: { ...INITIAL_VIEWPORTS, ...customViewports },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
addDecorator(themeDecorator);
|
||||||
|
addDecorator(withKnobs);
|
||||||
|
|
||||||
|
configure(require.context('../src/', true, /\.stories\.tsx?$/), module);
|
7
.storybook/main.js
Normal file
7
.storybook/main.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-knobs/register',
|
||||||
|
'@storybook/addon-actions',
|
||||||
|
'@storybook/addon-viewport/register',
|
||||||
|
],
|
||||||
|
};
|
|
@ -2,6 +2,6 @@ import { addons } from '@storybook/addons';
|
||||||
import { themes } from '@storybook/theming';
|
import { themes } from '@storybook/theming';
|
||||||
|
|
||||||
addons.setConfig({
|
addons.setConfig({
|
||||||
theme: themes.dark,
|
theme: themes.dark,
|
||||||
panelPosition: 'right',
|
panelPosition: 'right',
|
||||||
});
|
});
|
35
.storybook/themeDecorator.js
Normal file
35
.storybook/themeDecorator.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled, { ThemeProvider, css } from 'styled-components';
|
||||||
|
import { select, boolean } from '@storybook/addon-knobs';
|
||||||
|
import { backgroundColor, cardColor } from '../src/styles/Themes';
|
||||||
|
|
||||||
|
const StyledBackground = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 100px 0;
|
||||||
|
${({ withBackground, cardBackground }) =>
|
||||||
|
withBackground &&
|
||||||
|
css`
|
||||||
|
background: ${cardBackground ? cardColor : backgroundColor};
|
||||||
|
`}
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ThemeDecorator = storyFn => {
|
||||||
|
const background = boolean('No Background', false);
|
||||||
|
const cardBackground = boolean('Card Background', true);
|
||||||
|
return (
|
||||||
|
<ThemeProvider theme={{ mode: select('Theme', ['dark', 'light'], 'dark') }}>
|
||||||
|
<StyledBackground
|
||||||
|
withBackground={!background}
|
||||||
|
cardBackground={cardBackground}
|
||||||
|
>
|
||||||
|
{storyFn()}
|
||||||
|
</StyledBackground>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeDecorator;
|
12
.storybook/webpack.config.js
Normal file
12
.storybook/webpack.config.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
module.exports = ({ config }) => {
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.(ts|tsx)$/,
|
||||||
|
loader: require.resolve('babel-loader'),
|
||||||
|
options: {
|
||||||
|
presets: [require.resolve('babel-preset-react-app')],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
config.resolve.extensions.push('.ts', '.tsx');
|
||||||
|
return config;
|
||||||
|
};
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
}
|
5
@types/index.d.ts
vendored
Normal file
5
@types/index.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
declare module '*.png';
|
||||||
|
declare module '*.jpg';
|
||||||
|
declare module '*.jpeg';
|
||||||
|
declare module '*.svg';
|
||||||
|
declare module '*.gif';
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
FROM node:alpine
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
# RUN mkdir -p /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Install app dependencies
|
||||||
|
COPY package.json /usr/src/app/
|
||||||
|
COPY yarn.lock /usr/src/app/
|
||||||
|
RUN yarn install --production=true
|
||||||
|
|
||||||
|
# Bundle app source
|
||||||
|
COPY . /usr/src/app
|
||||||
|
RUN yarn build
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD [ "yarn", "start" ]
|
52
README.md
52
README.md
|
@ -1,7 +1,7 @@
|
||||||
# **ThunderHub - Lightning Node Manager**
|
# **ThunderHub - Lightning Node Manager**
|
||||||
|
|
||||||

|

|
||||||
[](https://github.com/DAVFoundation/captain-n3m0/blob/master/LICENSE) [](https://snyk.io/test/github/apotdevin/thunderhub) [](https://snyk.io/test/github/apotdevin/thunderhub) [](https://lerna.js.org/)
|
[](https://github.com/DAVFoundation/captain-n3m0/blob/master/LICENSE)
|
||||||
|
|
||||||
## Table Of Contents
|
## Table Of Contents
|
||||||
|
|
||||||
|
@ -16,21 +16,13 @@ ThunderHub is an **open-source** LND node manager where you can manage and monit
|
||||||
|
|
||||||
### Tech Stack
|
### Tech Stack
|
||||||
|
|
||||||
The repository consists of two packages (client and server) and is maintained with LernaJS and Yarn Workspaces.
|
This repository consists of a **NextJS** server that handles both the backend **Graphql Server** and the frontend **React App**.
|
||||||
|
|
||||||
#### Client
|
|
||||||
|
|
||||||
[](https://snyk.io/test/github/apotdevin/thunderhub)
|
|
||||||
|
|
||||||
|
- NextJS
|
||||||
- ReactJS
|
- ReactJS
|
||||||
- Typescript
|
- Typescript
|
||||||
- Styled-Components
|
- Styled-Components
|
||||||
- Apollo
|
- Apollo
|
||||||
|
|
||||||
#### Server
|
|
||||||
|
|
||||||
[](https://snyk.io/test/github/apotdevin/thunderhub)
|
|
||||||
|
|
||||||
- Apollo-Server
|
- Apollo-Server
|
||||||
- GraphQL
|
- GraphQL
|
||||||
- Ln-Service
|
- Ln-Service
|
||||||
|
@ -99,47 +91,29 @@ git clone https://github.com/apotdevin/thunderhub.git
|
||||||
- Node installed
|
- Node installed
|
||||||
- Yarn installed
|
- Yarn installed
|
||||||
|
|
||||||
After cloning the repository run `yarn` to get all the necessary modules installed. Yarn workspaces will handle installing modules for both the client and the server.
|
After cloning the repository run `yarn` to get all the necessary modules installed.
|
||||||
|
|
||||||
### **ThunderHub - Server**
|
After `yarn` has finished installing all the dependencies you can proceed to build and run the app with the following commands.
|
||||||
|
|
||||||
To be able to use the HodlHodl integration create a `.env` file in the `/server` folder with `HODL_KEY='[YOUR API KEY]'` and replace `[YOUR API KEY]` with the one that HodlHodl provides you.
|
|
||||||
|
|
||||||
#### To get the server running use the following commands
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
yarn server:prod
|
|
||||||
yarn server:run
|
|
||||||
```
|
|
||||||
|
|
||||||
If the server starts succesfully, you should see `info [server.js]: Server ready at http://localhost:3001/` in the terminal
|
|
||||||
|
|
||||||
### **ThunderHub - Client**
|
|
||||||
|
|
||||||
#### To get the React frontend running use the following commands
|
|
||||||
|
|
||||||
##### This must be done in the `/client` folder
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
yarn build
|
||||||
yarn start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
If the frontend starts succesfully, you should see `Compiled successfully! You can now view app in the browser.` in the terminal and a browser window should have opened in your browser.
|
This will start the server on port 3000, so just head to `localhost:3000` to see the app running.
|
||||||
|
|
||||||
|
#### HodlHodl Integration
|
||||||
|
|
||||||
|
To be able to use the HodlHodl integration create a `.env` file in the root folder with `HODL_KEY='[YOUR API KEY]'` and replace `[YOUR API KEY]` with the one that HodlHodl provides you.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
If you want to develop on ThunderHub and want hot reloading when you do changes, use the following commands:
|
If you want to develop on ThunderHub and want hot reloading when you do changes, use the following commands:
|
||||||
|
|
||||||
### ThunderHub - Server
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
yarn server:dev
|
yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### ThunderHub - Client
|
|
||||||
|
|
||||||
Running the commands `yarn start` in the `client` folder works for development.
|
|
||||||
|
|
||||||
#### Storybook
|
#### Storybook
|
||||||
|
|
||||||
You can also get storybook running for quicker component development.
|
You can also get storybook running for quicker component development.
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
node_modules
|
|
||||||
.git
|
|
||||||
.gitignore
|
|
||||||
build
|
|
|
@ -1 +0,0 @@
|
||||||
REACT_APP_VERSION=$npm_package_version
|
|
27
client/.gitignore
vendored
27
client/.gitignore
vendored
|
@ -1,27 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
#webpack
|
|
||||||
/dist
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"printWidth": 80,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"tabWidth": 4,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
stories: ['../src/**/*.stories.tsx'],
|
|
||||||
addons: [
|
|
||||||
'@storybook/addon-knobs/register',
|
|
||||||
'@storybook/addon-actions',
|
|
||||||
'@storybook/preset-create-react-app',
|
|
||||||
'@storybook/addon-links',
|
|
||||||
'@storybook/addon-viewport/register',
|
|
||||||
],
|
|
||||||
webpackFinal: async (config) => {
|
|
||||||
config.module.rules.push({
|
|
||||||
test: /\.(ts|tsx)$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('awesome-typescript-loader'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: require.resolve('react-docgen-typescript-loader'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
config.resolve.extensions.push('.ts', '.tsx');
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { addDecorator, addParameters } from '@storybook/react';
|
|
||||||
import themeDecorator from './themeDecorator';
|
|
||||||
import { withKnobs } from '@storybook/addon-knobs';
|
|
||||||
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
|
|
||||||
|
|
||||||
const customViewports = {
|
|
||||||
smallScreen: {
|
|
||||||
name: 'Small Screen',
|
|
||||||
styles: {
|
|
||||||
width: '578px',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
addParameters({
|
|
||||||
viewport: {
|
|
||||||
viewports: { ...INITIAL_VIEWPORTS, ...customViewports },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
addDecorator(themeDecorator);
|
|
||||||
addDecorator(withKnobs);
|
|
|
@ -1,37 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled, { ThemeProvider, css } from 'styled-components';
|
|
||||||
import { select, boolean } from '@storybook/addon-knobs';
|
|
||||||
import { backgroundColor, cardColor } from '../src/styles/Themes';
|
|
||||||
|
|
||||||
const StyledBackground = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 100px 0;
|
|
||||||
${({ withBackground, cardBackground }) =>
|
|
||||||
withBackground &&
|
|
||||||
css`
|
|
||||||
background: ${cardBackground ? cardColor : backgroundColor};
|
|
||||||
`}
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ThemeDecorator = (storyFn) => {
|
|
||||||
const background = boolean('No Background', false);
|
|
||||||
const cardBackground = boolean('Card Background', true);
|
|
||||||
return (
|
|
||||||
<ThemeProvider
|
|
||||||
theme={{ mode: select('Theme', ['dark', 'light'], 'dark') }}
|
|
||||||
>
|
|
||||||
<StyledBackground
|
|
||||||
withBackground={!background}
|
|
||||||
cardBackground={cardBackground}
|
|
||||||
>
|
|
||||||
{storyFn()}
|
|
||||||
</StyledBackground>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ThemeDecorator;
|
|
|
@ -1,14 +0,0 @@
|
||||||
FROM node:11-alpine as build
|
|
||||||
|
|
||||||
WORKDIR /usr/src/client
|
|
||||||
|
|
||||||
COPY package.json /usr/src/client
|
|
||||||
COPY yarn.lock /usr/src/client
|
|
||||||
RUN yarn install --production=true
|
|
||||||
|
|
||||||
COPY . /usr/src/client
|
|
||||||
RUN yarn build
|
|
||||||
|
|
||||||
RUN yarn global add serve
|
|
||||||
|
|
||||||
CMD ["serve", "-s", "build"]
|
|
|
@ -1,15 +0,0 @@
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html index.htm;
|
|
||||||
try_files $uri /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
|
||||||
location = /50x.html {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@thunderhub/client",
|
|
||||||
"version": "0.2.1",
|
|
||||||
"description": "",
|
|
||||||
"scripts": {
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build",
|
|
||||||
"test": "react-scripts test",
|
|
||||||
"eject": "react-scripts eject",
|
|
||||||
"storybook": "start-storybook -p 9009 -s public",
|
|
||||||
"build-storybook": "build-storybook -s public",
|
|
||||||
"deploy": "yarn build && aws s3 --profile EBFullAccess sync build/ s3://thunderhub-client",
|
|
||||||
"precommit": "pretty-quick --staged"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/apotdevin/thunderhub.git"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "apotdevin",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@apollo/react-hooks": "^3.1.3",
|
|
||||||
"@types/crypto-js": "^3.1.44",
|
|
||||||
"@types/jest": "25.1.5",
|
|
||||||
"@types/lodash.debounce": "^4.0.6",
|
|
||||||
"@types/lodash.merge": "^4.6.6",
|
|
||||||
"@types/lodash.sortby": "^4.7.6",
|
|
||||||
"@types/node": "13.11.0",
|
|
||||||
"@types/numeral": "^0.0.26",
|
|
||||||
"@types/qrcode.react": "^1.0.0",
|
|
||||||
"@types/react": "16.9.32",
|
|
||||||
"@types/react-copy-to-clipboard": "^4.3.0",
|
|
||||||
"@types/react-dom": "16.9.6",
|
|
||||||
"@types/react-modal": "^3.10.5",
|
|
||||||
"@types/react-qr-reader": "^2.1.2",
|
|
||||||
"@types/react-router-dom": "^5.1.2",
|
|
||||||
"@types/react-tooltip": "^3.11.0",
|
|
||||||
"@types/styled-components": "^5.0.1",
|
|
||||||
"@types/styled-react-modal": "^1.2.0",
|
|
||||||
"@types/styled-theming": "^2.2.2",
|
|
||||||
"@types/uuid": "^7.0.2",
|
|
||||||
"@types/victory": "^33.1.4",
|
|
||||||
"@types/zxcvbn": "^4.4.0",
|
|
||||||
"apollo-boost": "^0.4.4",
|
|
||||||
"crypto-js": "^4.0.0",
|
|
||||||
"intersection-observer": "^0.7.0",
|
|
||||||
"lodash.debounce": "^4.0.8",
|
|
||||||
"lodash.merge": "^4.6.2",
|
|
||||||
"lodash.sortby": "^4.7.0",
|
|
||||||
"node-sass": "^4.13.0",
|
|
||||||
"numeral": "^2.0.6",
|
|
||||||
"qrcode.react": "^1.0.0",
|
|
||||||
"qs": "^6.9.3",
|
|
||||||
"react": "^16.13.0",
|
|
||||||
"react-copy-to-clipboard": "^5.0.2",
|
|
||||||
"react-dom": "^16.13.0",
|
|
||||||
"react-intersection-observer": "^8.26.1",
|
|
||||||
"react-qr-reader": "^2.2.1",
|
|
||||||
"react-router-dom": "^5.1.2",
|
|
||||||
"react-scripts": "3.4.1",
|
|
||||||
"react-spinners": "^0.8.1",
|
|
||||||
"react-spring": "^8.0.27",
|
|
||||||
"react-toastify": "^5.4.1",
|
|
||||||
"react-tooltip": "^4.1.3",
|
|
||||||
"snyk": "^1.305.0",
|
|
||||||
"styled-components": "^5.0.1",
|
|
||||||
"styled-react-modal": "^2.0.0",
|
|
||||||
"styled-theming": "^2.2.0",
|
|
||||||
"typescript": "^3.8.3",
|
|
||||||
"uuid": "^7.0.3",
|
|
||||||
"victory": "^34.1.3",
|
|
||||||
"zxcvbn": "^4.4.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@storybook/addon-actions": "^5.3.18",
|
|
||||||
"@storybook/addon-info": "^5.3.18",
|
|
||||||
"@storybook/addon-knobs": "^5.3.18",
|
|
||||||
"@storybook/addon-links": "^5.3.18",
|
|
||||||
"@storybook/addon-viewport": "^5.3.18",
|
|
||||||
"@storybook/addons": "^5.3.18",
|
|
||||||
"@storybook/preset-create-react-app": "^2.1.1",
|
|
||||||
"@storybook/react": "^5.3.18",
|
|
||||||
"awesome-typescript-loader": "^5.2.1",
|
|
||||||
"react-docgen-typescript-loader": "^3.7.2"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": "react-app"
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
|
@ -1,60 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
property="og:description"
|
|
||||||
content="Manage and monitor your lightning network node right inside your browser"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:title"
|
|
||||||
content="ThunderHub - Lightning Node Manager"
|
|
||||||
/>
|
|
||||||
<meta name="robots" content="index, follow" />
|
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon-152x152.png" />
|
|
||||||
<link rel="canonical" href="https://thunderhub.io/" />
|
|
||||||
<meta property="og:url" content="https://thunderhub.io" />
|
|
||||||
<meta
|
|
||||||
name="twitter:title"
|
|
||||||
content="ThunderHub - Lightning Node Manager"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="twitter:description"
|
|
||||||
content="Manage and monitor your lightning network node right inside your browser"
|
|
||||||
/>
|
|
||||||
<meta name="twitter:site" content="@thunderhubio" />
|
|
||||||
<meta name="twitter:creator" content="@thunderhubio" />
|
|
||||||
<!--
|
|
||||||
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/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
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>ThunderHub - Lightning Node Manager</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
--></body>
|
|
||||||
</html>
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"short_name": "ThunderHub",
|
|
||||||
"name": "ThunderHub - Lightning Node Manager",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "apple-touch-icon-152x152.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "152x152"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
|
|
@ -1,9 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
it('renders without crashing', () => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
ReactDOM.render(<App />, div);
|
|
||||||
ReactDOM.unmountComponentAtNode(div);
|
|
||||||
});
|
|
|
@ -1,80 +0,0 @@
|
||||||
import React, { Suspense } from 'react';
|
|
||||||
import { ThemeProvider } from 'styled-components';
|
|
||||||
import { GlobalStyles } from './styles/GlobalStyle';
|
|
||||||
import { ApolloProvider } from '@apollo/react-hooks';
|
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
|
||||||
import ApolloClient from 'apollo-boost';
|
|
||||||
import { useSettings } from './context/SettingsContext';
|
|
||||||
import { ModalProvider } from 'styled-react-modal';
|
|
||||||
import { useAccount } from './context/AccountContext';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
|
||||||
import { Header } from './sections/header/Header';
|
|
||||||
import { Footer } from './sections/footer/Footer';
|
|
||||||
import { LoadingCard } from './components/loading/LoadingCard';
|
|
||||||
import { ScrollToTop } from 'components/scrollToTop/ScrollToTop';
|
|
||||||
import { ContextProvider } from 'context/ContextProvider';
|
|
||||||
import { ConnectionCheck } from 'components/connectionCheck/ConnectionCheck';
|
|
||||||
import { StatusCheck } from 'components/statusCheck/StatusCheck';
|
|
||||||
import { BaseModalBackground } from 'styled-react-modal';
|
|
||||||
|
|
||||||
const EntryView = React.lazy(() => import('./views/entry/Entry'));
|
|
||||||
const ContentView = React.lazy(() => import('./sections/content/Content'));
|
|
||||||
|
|
||||||
toast.configure({ draggable: false });
|
|
||||||
|
|
||||||
const client = new ApolloClient({
|
|
||||||
uri:
|
|
||||||
process.env.REACT_APP_API_URL ?? process.env.NODE_ENV === 'production'
|
|
||||||
? 'https://api.thunderhub.io'
|
|
||||||
: 'http://localhost:3001',
|
|
||||||
});
|
|
||||||
|
|
||||||
const ContextApp: React.FC = () => {
|
|
||||||
const { theme } = useSettings();
|
|
||||||
const { loggedIn, admin, viewOnly, sessionAdmin } = useAccount();
|
|
||||||
|
|
||||||
const renderContent = () => (
|
|
||||||
<Suspense
|
|
||||||
fallback={<LoadingCard noCard={true} loadingHeight={'240px'} />}
|
|
||||||
>
|
|
||||||
{!loggedIn && admin === '' ? (
|
|
||||||
<EntryView />
|
|
||||||
) : admin !== '' && viewOnly === '' && sessionAdmin === '' ? (
|
|
||||||
<EntryView session={true} />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ConnectionCheck />
|
|
||||||
<StatusCheck />
|
|
||||||
<ContentView />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeProvider theme={{ mode: theme }}>
|
|
||||||
<ModalProvider backgroundComponent={BaseModalBackground}>
|
|
||||||
<ScrollToTop />
|
|
||||||
<GlobalStyles />
|
|
||||||
<Header />
|
|
||||||
{renderContent()}
|
|
||||||
<Footer />
|
|
||||||
</ModalProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<BrowserRouter>
|
|
||||||
<ApolloProvider client={client}>
|
|
||||||
<ContextProvider>
|
|
||||||
<ContextApp />
|
|
||||||
</ContextProvider>
|
|
||||||
</ApolloProvider>
|
|
||||||
</BrowserRouter>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
|
@ -1,49 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { useSpring, animated } from 'react-spring';
|
|
||||||
import { getValue } from '../../helpers/Helpers';
|
|
||||||
import { useSettings } from '../../context/SettingsContext';
|
|
||||||
import { usePriceState } from '../../context/PriceContext';
|
|
||||||
|
|
||||||
type PriceProps = {
|
|
||||||
price: number;
|
|
||||||
symbol: string;
|
|
||||||
currency: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type AnimatedProps = {
|
|
||||||
amount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AnimatedNumber = ({ amount }: AnimatedProps) => {
|
|
||||||
const { value } = useSpring({
|
|
||||||
from: { value: 0 },
|
|
||||||
value: amount,
|
|
||||||
});
|
|
||||||
const { currency } = useSettings();
|
|
||||||
const { prices } = usePriceState();
|
|
||||||
|
|
||||||
let priceProps: PriceProps = {
|
|
||||||
price: 0,
|
|
||||||
symbol: '',
|
|
||||||
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (prices) {
|
|
||||||
const current: { last: number; symbol: string } = prices[currency] ?? {
|
|
||||||
last: 0,
|
|
||||||
symbol: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
priceProps = {
|
|
||||||
price: current.last,
|
|
||||||
symbol: current.symbol,
|
|
||||||
currency,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<animated.div>
|
|
||||||
{value.interpolate((amount) => getValue({ amount, ...priceProps }))}
|
|
||||||
</animated.div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,43 +0,0 @@
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Sub4Title } from '../generic/Styled';
|
|
||||||
import { fontColors, textColor } from 'styles/Themes';
|
|
||||||
|
|
||||||
export const Line = styled.div`
|
|
||||||
margin: 16px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const StyledTitle = styled(Sub4Title)`
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CheckboxText = styled.div`
|
|
||||||
font-size: 13px;
|
|
||||||
color: ${fontColors.grey7};
|
|
||||||
text-align: justify;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const StyledContainer = styled.div`
|
|
||||||
color: ${textColor};
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
padding-right: 32px;
|
|
||||||
margin: 32px 0 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const FixedWidth = styled.div`
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
margin: 0px;
|
|
||||||
margin-right: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const QRTextWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
margin: 16px 0;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
`;
|
|
|
@ -1,43 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { GET_CAN_ADMIN } from 'graphql/query';
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import { SingleLine, Sub4Title } from 'components/generic/Styled';
|
|
||||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
|
||||||
import { themeColors } from 'styles/Themes';
|
|
||||||
import { XSvg, Check } from 'components/generic/Icons';
|
|
||||||
|
|
||||||
type AdminProps = {
|
|
||||||
host: string;
|
|
||||||
admin: string;
|
|
||||||
cert?: string;
|
|
||||||
setChecked: (state: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AdminCheck = ({ host, admin, cert, setChecked }: AdminProps) => {
|
|
||||||
const { data, loading } = useQuery(GET_CAN_ADMIN, {
|
|
||||||
skip: !admin,
|
|
||||||
variables: { auth: { host, macaroon: admin, cert } },
|
|
||||||
onError: () => {
|
|
||||||
setChecked(false);
|
|
||||||
},
|
|
||||||
onCompleted: () => {
|
|
||||||
setChecked(true);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const content = () => {
|
|
||||||
if (loading) {
|
|
||||||
return <ScaleLoader height={20} color={themeColors.blue3} />;
|
|
||||||
} else if (data?.adminCheck) {
|
|
||||||
return <Check />;
|
|
||||||
}
|
|
||||||
return <XSvg />;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Admin Macaroon</Sub4Title>
|
|
||||||
{content()}
|
|
||||||
</SingleLine>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,159 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import { GET_CAN_CONNECT } from 'graphql/query';
|
|
||||||
import { SingleLine, Sub4Title, Separation } from 'components/generic/Styled';
|
|
||||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
|
||||||
import { themeColors } from 'styles/Themes';
|
|
||||||
import { Check, XSvg } from 'components/generic/Icons';
|
|
||||||
import { ColorButton } from 'components/buttons/colorButton/ColorButton';
|
|
||||||
import { AdminCheck } from './AdminCheck';
|
|
||||||
import { Text } from 'views/other/OtherViews.styled';
|
|
||||||
|
|
||||||
type ViewProps = {
|
|
||||||
host: string;
|
|
||||||
admin?: string;
|
|
||||||
viewOnly?: string;
|
|
||||||
cert?: string;
|
|
||||||
adminChecked: boolean;
|
|
||||||
callback: () => void;
|
|
||||||
setAdminChecked: (state: boolean) => void;
|
|
||||||
handleConnect: () => void;
|
|
||||||
setName: (name: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ViewCheck = ({
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
adminChecked,
|
|
||||||
setAdminChecked,
|
|
||||||
handleConnect,
|
|
||||||
callback,
|
|
||||||
setName,
|
|
||||||
}: ViewProps) => {
|
|
||||||
const [confirmed, setConfirmed] = useState(false);
|
|
||||||
|
|
||||||
const { data, loading } = useQuery(GET_CAN_CONNECT, {
|
|
||||||
variables: { auth: { host, macaroon: viewOnly ?? admin ?? '', cert } },
|
|
||||||
onCompleted: () => setConfirmed(true),
|
|
||||||
onError: () => setConfirmed(false),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading && data && data.getNodeInfo) {
|
|
||||||
setName(data.getNodeInfo.alias);
|
|
||||||
}
|
|
||||||
}, [loading, data, setName]);
|
|
||||||
|
|
||||||
const content = () => {
|
|
||||||
if (loading) {
|
|
||||||
return <ScaleLoader height={20} color={themeColors.blue3} />;
|
|
||||||
} else if (data?.getNodeInfo.alias && viewOnly) {
|
|
||||||
return <Check />;
|
|
||||||
}
|
|
||||||
return <XSvg />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderInfo = () => {
|
|
||||||
if (!loading && data && data.getNodeInfo) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Alias</Sub4Title>
|
|
||||||
<Sub4Title>{data.getNodeInfo.alias}</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Synced To Chain</Sub4Title>
|
|
||||||
<Sub4Title>
|
|
||||||
{data.getNodeInfo.is_synced_to_chain ? 'Yes' : 'No'}
|
|
||||||
</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Version</Sub4Title>
|
|
||||||
<Sub4Title>
|
|
||||||
{data.getNodeInfo.version.split(' ')[0]}
|
|
||||||
</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Active Channels</Sub4Title>
|
|
||||||
<Sub4Title>
|
|
||||||
{data.getNodeInfo.active_channels_count}
|
|
||||||
</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Pending Channels</Sub4Title>
|
|
||||||
<Sub4Title>
|
|
||||||
{data.getNodeInfo.pending_channels_count}
|
|
||||||
</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Closed Channels</Sub4Title>
|
|
||||||
<Sub4Title>
|
|
||||||
{data.getNodeInfo.closed_channels_count}
|
|
||||||
</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<Separation />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderTitle = () => {
|
|
||||||
if (!confirmed) {
|
|
||||||
return 'Go Back';
|
|
||||||
} else if (adminChecked && !viewOnly && admin) {
|
|
||||||
return 'Connect (Admin-Only)';
|
|
||||||
} else if (!adminChecked && viewOnly) {
|
|
||||||
return 'Connect (View-Only)';
|
|
||||||
} else {
|
|
||||||
return 'Connect';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderButton = () => (
|
|
||||||
<ColorButton
|
|
||||||
fullWidth={true}
|
|
||||||
withMargin={'16px 0 0'}
|
|
||||||
disabled={loading}
|
|
||||||
loading={loading}
|
|
||||||
arrow={confirmed}
|
|
||||||
onClick={() => {
|
|
||||||
if (confirmed) {
|
|
||||||
handleConnect();
|
|
||||||
} else {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{renderTitle()}
|
|
||||||
</ColorButton>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderText = () => (
|
|
||||||
<Text>
|
|
||||||
Failed to connect to node. Please verify the information provided.
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{renderInfo()}
|
|
||||||
{!confirmed && !loading && renderText()}
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>View-Only Macaroon</Sub4Title>
|
|
||||||
{content()}
|
|
||||||
</SingleLine>
|
|
||||||
{admin && (
|
|
||||||
<AdminCheck
|
|
||||||
host={host}
|
|
||||||
admin={admin}
|
|
||||||
cert={cert}
|
|
||||||
setChecked={setAdminChecked}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{renderButton()}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,188 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { LoginForm } from './views/NormalLogin';
|
|
||||||
import { ConnectLoginForm } from './views/ConnectLogin';
|
|
||||||
import { BTCLoginForm } from './views/BTCLogin';
|
|
||||||
import { QRLogin } from './views/QRLogin';
|
|
||||||
import { ViewCheck } from './checks/ViewCheck';
|
|
||||||
import CryptoJS from 'crypto-js';
|
|
||||||
import { useAccount } from 'context/AccountContext';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import { saveUserAuth, getAccountId } from 'utils/auth';
|
|
||||||
import { PasswordInput } from './views/Password';
|
|
||||||
import { useConnectionDispatch } from 'context/ConnectionContext';
|
|
||||||
import { useStatusDispatch } from 'context/StatusContext';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
type AuthProps = {
|
|
||||||
type: string;
|
|
||||||
status: string;
|
|
||||||
callback: () => void;
|
|
||||||
setStatus: (state: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Auth = ({ type, status, callback, setStatus }: AuthProps) => {
|
|
||||||
const { changeAccount, accounts } = useAccount();
|
|
||||||
const { push } = useHistory();
|
|
||||||
|
|
||||||
const dispatch = useConnectionDispatch();
|
|
||||||
const dispatchState = useStatusDispatch();
|
|
||||||
|
|
||||||
const [name, setName] = useState<string>();
|
|
||||||
const [host, setHost] = useState<string>();
|
|
||||||
const [admin, setAdmin] = useState<string>();
|
|
||||||
const [viewOnly, setViewOnly] = useState<string>();
|
|
||||||
const [cert, setCert] = useState<string>();
|
|
||||||
const [password, setPassword] = useState<string>();
|
|
||||||
|
|
||||||
const [adminChecked, setAdminChecked] = useState(false);
|
|
||||||
|
|
||||||
const handleSet = ({
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
skipCheck,
|
|
||||||
}: {
|
|
||||||
host?: string;
|
|
||||||
admin?: string;
|
|
||||||
viewOnly?: string;
|
|
||||||
cert?: string;
|
|
||||||
skipCheck?: boolean;
|
|
||||||
}) => {
|
|
||||||
const id = getAccountId(
|
|
||||||
host ?? '',
|
|
||||||
viewOnly ?? '',
|
|
||||||
admin ?? '',
|
|
||||||
cert ?? '',
|
|
||||||
);
|
|
||||||
|
|
||||||
const accountExists =
|
|
||||||
accounts.findIndex((account) => account.id === id) > -1;
|
|
||||||
|
|
||||||
if (accountExists) {
|
|
||||||
toast.error('Account already exists.');
|
|
||||||
} else if (!host) {
|
|
||||||
toast.error('A host url is needed to connect.');
|
|
||||||
} else if (!admin && !viewOnly) {
|
|
||||||
toast.error('View-Only or Admin macaroon are needed to connect.');
|
|
||||||
} else if (skipCheck) {
|
|
||||||
quickSave({ name, cert, admin, viewOnly, host });
|
|
||||||
} else {
|
|
||||||
host && setHost(host);
|
|
||||||
admin && setAdmin(admin);
|
|
||||||
viewOnly && setViewOnly(viewOnly);
|
|
||||||
cert && setCert(cert);
|
|
||||||
|
|
||||||
setStatus('confirmNode');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const quickSave = ({
|
|
||||||
name = 'Unknown',
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
}: {
|
|
||||||
name?: string;
|
|
||||||
host?: string;
|
|
||||||
admin?: string;
|
|
||||||
viewOnly?: string;
|
|
||||||
cert?: string;
|
|
||||||
}) => {
|
|
||||||
saveUserAuth({
|
|
||||||
name,
|
|
||||||
host: host || '',
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
accounts,
|
|
||||||
});
|
|
||||||
|
|
||||||
const id = getAccountId(host, viewOnly, admin, cert);
|
|
||||||
|
|
||||||
dispatch({ type: 'disconnected' });
|
|
||||||
dispatchState({ type: 'disconnected' });
|
|
||||||
changeAccount(id);
|
|
||||||
|
|
||||||
push('/');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSave = () => {
|
|
||||||
if (!host) {
|
|
||||||
toast.error('A host url is needed to connect.');
|
|
||||||
} else if (!admin && !viewOnly) {
|
|
||||||
toast.error('View-Only or Admin macaroon are needed to connect.');
|
|
||||||
} else {
|
|
||||||
const encryptedAdmin =
|
|
||||||
admin && password
|
|
||||||
? CryptoJS.AES.encrypt(admin, password).toString()
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
saveUserAuth({
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin: encryptedAdmin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
accounts,
|
|
||||||
});
|
|
||||||
|
|
||||||
const id = getAccountId(host, viewOnly, admin, cert);
|
|
||||||
|
|
||||||
dispatch({ type: 'disconnected' });
|
|
||||||
dispatchState({ type: 'disconnected' });
|
|
||||||
changeAccount(id);
|
|
||||||
|
|
||||||
push('/');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConnect = () => {
|
|
||||||
if (adminChecked) {
|
|
||||||
setStatus('password');
|
|
||||||
} else {
|
|
||||||
handleSave();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderView = () => {
|
|
||||||
switch (type) {
|
|
||||||
case 'login':
|
|
||||||
return <LoginForm handleSet={handleSet} />;
|
|
||||||
case 'qrcode':
|
|
||||||
return <QRLogin handleSet={handleSet} />;
|
|
||||||
case 'connect':
|
|
||||||
return <ConnectLoginForm handleSet={handleSet} />;
|
|
||||||
default:
|
|
||||||
return <BTCLoginForm handleSet={handleSet} />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{status === 'none' && renderView()}
|
|
||||||
{status === 'confirmNode' && host && (
|
|
||||||
<ViewCheck
|
|
||||||
host={host}
|
|
||||||
admin={admin}
|
|
||||||
viewOnly={viewOnly}
|
|
||||||
cert={cert}
|
|
||||||
adminChecked={adminChecked}
|
|
||||||
setAdminChecked={setAdminChecked}
|
|
||||||
handleConnect={handleConnect}
|
|
||||||
callback={callback}
|
|
||||||
setName={setName}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{status === 'password' && (
|
|
||||||
<PasswordInput
|
|
||||||
isPass={password}
|
|
||||||
setPass={setPassword}
|
|
||||||
callback={handleSave}
|
|
||||||
loading={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,51 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { getConfigLnd } from '../../../utils/auth';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { Input } from 'components/input/Input';
|
|
||||||
import { Line, StyledTitle } from '../Auth.styled';
|
|
||||||
import { RiskCheckboxAndConfirm } from './Checkboxes';
|
|
||||||
|
|
||||||
interface AuthProps {
|
|
||||||
handleSet: ({
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
}: {
|
|
||||||
host?: string;
|
|
||||||
admin?: string;
|
|
||||||
viewOnly?: string;
|
|
||||||
cert?: string;
|
|
||||||
}) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BTCLoginForm = ({ handleSet }: AuthProps) => {
|
|
||||||
const [json, setJson] = useState('');
|
|
||||||
const [checked, setChecked] = useState(false);
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
try {
|
|
||||||
JSON.parse(json);
|
|
||||||
const { cert, admin, viewOnly, host } = getConfigLnd(json);
|
|
||||||
handleSet({ host, admin, viewOnly, cert });
|
|
||||||
} catch (error) {
|
|
||||||
toast.error('Invalid JSON');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const canConnect = json !== '' && checked;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Line>
|
|
||||||
<StyledTitle>BTCPayServer Connect JSON:</StyledTitle>
|
|
||||||
<Input onChange={(e) => setJson(e.target.value)} />
|
|
||||||
</Line>
|
|
||||||
<RiskCheckboxAndConfirm
|
|
||||||
disabled={!canConnect}
|
|
||||||
handleClick={handleClick}
|
|
||||||
checked={checked}
|
|
||||||
onChange={setChecked}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,55 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Checkbox } from 'components/checkbox/Checkbox';
|
|
||||||
import { CheckboxText, StyledContainer, FixedWidth } from '../Auth.styled';
|
|
||||||
import { AlertCircle } from 'components/generic/Icons';
|
|
||||||
import { fontColors } from 'styles/Themes';
|
|
||||||
import { ColorButton } from 'components/buttons/colorButton/ColorButton';
|
|
||||||
|
|
||||||
type CheckboxProps = {
|
|
||||||
handleClick: () => void;
|
|
||||||
disabled: boolean;
|
|
||||||
checked: boolean;
|
|
||||||
onChange: (state: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RiskCheckboxAndConfirm = ({
|
|
||||||
handleClick,
|
|
||||||
disabled,
|
|
||||||
checked,
|
|
||||||
onChange,
|
|
||||||
}: CheckboxProps) => (
|
|
||||||
<>
|
|
||||||
<Checkbox checked={checked} onChange={onChange}>
|
|
||||||
<CheckboxText>
|
|
||||||
I'm feeling reckless - I understand that Lightning, LND and
|
|
||||||
ThunderHub are under constant development and that there is
|
|
||||||
always a risk of losing funds.
|
|
||||||
</CheckboxText>
|
|
||||||
</Checkbox>
|
|
||||||
<ColorButton
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={handleClick}
|
|
||||||
withMargin={'32px 0 0'}
|
|
||||||
fullWidth={true}
|
|
||||||
arrow={true}
|
|
||||||
>
|
|
||||||
Connect
|
|
||||||
</ColorButton>
|
|
||||||
<WarningBox />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WarningBox = () => {
|
|
||||||
return (
|
|
||||||
<StyledContainer>
|
|
||||||
<FixedWidth>
|
|
||||||
<AlertCircle color={fontColors.grey7} />
|
|
||||||
</FixedWidth>
|
|
||||||
<CheckboxText>
|
|
||||||
Macaroons are handled by the ThunderHub server to connect to
|
|
||||||
your LND node but are never stored. Still, this involves a
|
|
||||||
certain degree of trust you must be aware of.
|
|
||||||
</CheckboxText>
|
|
||||||
</StyledContainer>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,52 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { getAuthLnd, getBase64CertfromDerFormat } from '../../../utils/auth';
|
|
||||||
import { Input } from 'components/input/Input';
|
|
||||||
import { Line, StyledTitle } from '../Auth.styled';
|
|
||||||
import { RiskCheckboxAndConfirm } from './Checkboxes';
|
|
||||||
|
|
||||||
interface AuthProps {
|
|
||||||
handleSet: ({
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
}: {
|
|
||||||
host?: string;
|
|
||||||
admin?: string;
|
|
||||||
viewOnly?: string;
|
|
||||||
cert?: string;
|
|
||||||
}) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ConnectLoginForm = ({ handleSet }: AuthProps) => {
|
|
||||||
const [url, setUrl] = useState('');
|
|
||||||
const [checked, setChecked] = useState(false);
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
const { cert, macaroon, socket } = getAuthLnd(url);
|
|
||||||
const base64Cert = getBase64CertfromDerFormat(cert) || '';
|
|
||||||
|
|
||||||
handleSet({
|
|
||||||
host: socket,
|
|
||||||
admin: macaroon,
|
|
||||||
cert: base64Cert,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const canConnect = url !== '' && checked;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Line>
|
|
||||||
<StyledTitle>LND Connect Url:</StyledTitle>
|
|
||||||
<Input onChange={(e) => setUrl(e.target.value)} />
|
|
||||||
</Line>
|
|
||||||
<RiskCheckboxAndConfirm
|
|
||||||
disabled={!canConnect}
|
|
||||||
handleClick={handleClick}
|
|
||||||
checked={checked}
|
|
||||||
onChange={setChecked}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,97 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { Input } from 'components/input/Input';
|
|
||||||
import { Line, StyledTitle } from '../Auth.styled';
|
|
||||||
import { SingleLine, Sub4Title } from 'components/generic/Styled';
|
|
||||||
import {
|
|
||||||
MultiButton,
|
|
||||||
SingleButton,
|
|
||||||
} from 'components/buttons/multiButton/MultiButton';
|
|
||||||
import { RiskCheckboxAndConfirm } from './Checkboxes';
|
|
||||||
|
|
||||||
interface AuthProps {
|
|
||||||
handleSet: ({
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
}: {
|
|
||||||
host?: string;
|
|
||||||
admin?: string;
|
|
||||||
viewOnly?: string;
|
|
||||||
cert?: string;
|
|
||||||
}) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoginForm = ({ handleSet }: AuthProps) => {
|
|
||||||
const [isViewOnly, setIsViewOnly] = useState(true);
|
|
||||||
const [checked, setChecked] = useState(false);
|
|
||||||
|
|
||||||
const [host, setHost] = useState('');
|
|
||||||
const [admin, setAdmin] = useState('');
|
|
||||||
const [viewOnly, setRead] = useState('');
|
|
||||||
const [cert, setCert] = useState('');
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
handleSet({ host, admin, viewOnly, cert });
|
|
||||||
};
|
|
||||||
|
|
||||||
const canConnect =
|
|
||||||
host !== '' && (admin !== '' || viewOnly !== '') && checked;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Type of Account:</Sub4Title>
|
|
||||||
<MultiButton>
|
|
||||||
<SingleButton
|
|
||||||
selected={isViewOnly}
|
|
||||||
onClick={() => setIsViewOnly(true)}
|
|
||||||
>
|
|
||||||
ViewOnly
|
|
||||||
</SingleButton>
|
|
||||||
<SingleButton
|
|
||||||
selected={!isViewOnly}
|
|
||||||
onClick={() => setIsViewOnly(false)}
|
|
||||||
>
|
|
||||||
Admin
|
|
||||||
</SingleButton>
|
|
||||||
</MultiButton>
|
|
||||||
</SingleLine>
|
|
||||||
<Line>
|
|
||||||
<StyledTitle>Host:</StyledTitle>
|
|
||||||
<Input
|
|
||||||
placeholder={'Url and port (e.g.: www.node.com:443)'}
|
|
||||||
onChange={(e) => setHost(e.target.value)}
|
|
||||||
/>
|
|
||||||
</Line>
|
|
||||||
{!isViewOnly && (
|
|
||||||
<Line>
|
|
||||||
<StyledTitle>Admin:</StyledTitle>
|
|
||||||
<Input
|
|
||||||
placeholder={'Base64 or HEX Admin macaroon'}
|
|
||||||
onChange={(e) => setAdmin(e.target.value)}
|
|
||||||
/>
|
|
||||||
</Line>
|
|
||||||
)}
|
|
||||||
<Line>
|
|
||||||
<StyledTitle>Readonly:</StyledTitle>
|
|
||||||
<Input
|
|
||||||
placeholder={'Base64 or HEX Readonly macaroon'}
|
|
||||||
onChange={(e) => setRead(e.target.value)}
|
|
||||||
/>
|
|
||||||
</Line>
|
|
||||||
<Line>
|
|
||||||
<StyledTitle>Certificate:</StyledTitle>
|
|
||||||
<Input
|
|
||||||
placeholder={'Base64 or HEX TLS Certificate'}
|
|
||||||
onChange={(e) => setCert(e.target.value)}
|
|
||||||
/>
|
|
||||||
</Line>
|
|
||||||
<RiskCheckboxAndConfirm
|
|
||||||
disabled={!canConnect}
|
|
||||||
handleClick={handleClick}
|
|
||||||
checked={checked}
|
|
||||||
onChange={setChecked}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,47 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Sub4Title, SubTitle } from '../../generic/Styled';
|
|
||||||
import zxcvbn from 'zxcvbn';
|
|
||||||
import { ColorButton } from '../../buttons/colorButton/ColorButton';
|
|
||||||
import { Input } from 'components/input/Input';
|
|
||||||
import { Line } from '../Auth.styled';
|
|
||||||
import { LoadingBar } from 'components/loadingBar/LoadingBar';
|
|
||||||
|
|
||||||
interface PasswordProps {
|
|
||||||
isPass?: string;
|
|
||||||
setPass: (pass: string) => void;
|
|
||||||
callback: () => void;
|
|
||||||
loading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PasswordInput = ({
|
|
||||||
isPass = '',
|
|
||||||
setPass,
|
|
||||||
callback,
|
|
||||||
loading = false,
|
|
||||||
}: PasswordProps) => {
|
|
||||||
const strength = (100 * Math.min(zxcvbn(isPass).guesses_log10, 40)) / 40;
|
|
||||||
const needed = process.env.NODE_ENV === 'development' ? 1 : 20;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SubTitle>Please Input a Password</SubTitle>
|
|
||||||
<Line>
|
|
||||||
<Sub4Title>Password:</Sub4Title>
|
|
||||||
<Input onChange={(e) => setPass(e.target.value)} />
|
|
||||||
</Line>
|
|
||||||
<Line>
|
|
||||||
<Sub4Title>Strength:</Sub4Title>
|
|
||||||
<LoadingBar percent={strength} />
|
|
||||||
</Line>
|
|
||||||
<ColorButton
|
|
||||||
disabled={strength < needed}
|
|
||||||
onClick={callback}
|
|
||||||
withMargin={'32px 0 0'}
|
|
||||||
fullWidth={true}
|
|
||||||
arrow={true}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
Connect
|
|
||||||
</ColorButton>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,163 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import QrReader from 'react-qr-reader';
|
|
||||||
import Modal from '../../../components/modal/ReactModal';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { getQRConfig } from 'utils/auth';
|
|
||||||
import { Line, QRTextWrapper } from '../Auth.styled';
|
|
||||||
import sortBy from 'lodash.sortby';
|
|
||||||
import { LoadingBar } from 'components/loadingBar/LoadingBar';
|
|
||||||
import { SubTitle } from 'components/generic/Styled';
|
|
||||||
import { ColorButton } from 'components/buttons/colorButton/ColorButton';
|
|
||||||
|
|
||||||
type QRLoginProps = {
|
|
||||||
handleSet: ({
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
skipCheck,
|
|
||||||
}: {
|
|
||||||
name?: string;
|
|
||||||
host?: string;
|
|
||||||
admin?: string;
|
|
||||||
viewOnly?: string;
|
|
||||||
cert?: string;
|
|
||||||
skipCheck?: boolean;
|
|
||||||
}) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const QRLogin = ({ handleSet }: QRLoginProps) => {
|
|
||||||
const [qrData, setQrData] = useState<any>([]);
|
|
||||||
const [modalOpen, setModalOpen] = useState(true);
|
|
||||||
const [modalClosed, setModalClosed] = useState('none');
|
|
||||||
|
|
||||||
const [total, setTotal] = useState(0);
|
|
||||||
const [missing, setMissing] = useState<number[]>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (qrData.length >= total && total !== 0) {
|
|
||||||
setModalOpen(false);
|
|
||||||
|
|
||||||
const sorted = sortBy(qrData, 'index');
|
|
||||||
const strings = sorted.map((code: { auth: string }) => code.auth);
|
|
||||||
const completeString = strings.join('');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { name, cert, admin, viewOnly, host } = getQRConfig(
|
|
||||||
completeString,
|
|
||||||
);
|
|
||||||
|
|
||||||
handleSet({
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
skipCheck: true,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
toast.error('Error reading QR codes.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [qrData, handleSet, total]);
|
|
||||||
|
|
||||||
const handleScan = (data: string | null) => {
|
|
||||||
if (data) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(data);
|
|
||||||
!total && setTotal(parsed.total);
|
|
||||||
!missing && setMissing([...Array(parsed.total).keys()]);
|
|
||||||
|
|
||||||
if (
|
|
||||||
missing &&
|
|
||||||
missing.length >= 0 &&
|
|
||||||
missing.includes(parsed.index)
|
|
||||||
) {
|
|
||||||
const remaining = missing.filter((value: number) => {
|
|
||||||
const number = parseInt(parsed.index);
|
|
||||||
return value !== number;
|
|
||||||
});
|
|
||||||
const data = [...qrData, parsed];
|
|
||||||
setQrData(data);
|
|
||||||
setMissing(remaining);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setModalOpen(false);
|
|
||||||
toast.error('Error reading QR codes.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleError = () => {
|
|
||||||
setModalOpen(false);
|
|
||||||
setModalClosed('error');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setModalClosed('forced');
|
|
||||||
setModalOpen(false);
|
|
||||||
setMissing(undefined);
|
|
||||||
setTotal(0);
|
|
||||||
setQrData([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderInfo = () => {
|
|
||||||
switch (modalClosed) {
|
|
||||||
case 'forced':
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<QRTextWrapper>
|
|
||||||
<SubTitle>
|
|
||||||
No information read from QR Codes.
|
|
||||||
</SubTitle>
|
|
||||||
</QRTextWrapper>
|
|
||||||
<ColorButton
|
|
||||||
fullWidth={true}
|
|
||||||
onClick={() => {
|
|
||||||
setModalClosed('none');
|
|
||||||
setModalOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Try Again
|
|
||||||
</ColorButton>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
case 'error':
|
|
||||||
return (
|
|
||||||
<QRTextWrapper>
|
|
||||||
<SubTitle>
|
|
||||||
Make sure you have a camara available and that you
|
|
||||||
have given ThunderHub the correct permissions to use
|
|
||||||
it.
|
|
||||||
</SubTitle>
|
|
||||||
</QRTextWrapper>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{renderInfo()}
|
|
||||||
<Modal isOpen={modalOpen} closeCallback={handleClose}>
|
|
||||||
<Line>
|
|
||||||
<LoadingBar
|
|
||||||
percent={
|
|
||||||
missing
|
|
||||||
? 100 * ((total - missing.length) / total)
|
|
||||||
: 0
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Line>
|
|
||||||
<QrReader
|
|
||||||
delay={500}
|
|
||||||
onError={handleError}
|
|
||||||
onScan={handleScan}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import { GET_BITCOIN_FEES } from '../../graphql/query';
|
|
||||||
import { useBitcoinDispatch } from '../../context/BitcoinContext';
|
|
||||||
|
|
||||||
export const BitcoinFees = () => {
|
|
||||||
const setInfo = useBitcoinDispatch();
|
|
||||||
const { loading, data, stopPolling } = useQuery(GET_BITCOIN_FEES, {
|
|
||||||
onError: () => {
|
|
||||||
setInfo({ type: 'error' });
|
|
||||||
stopPolling();
|
|
||||||
},
|
|
||||||
pollInterval: 60000,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading && data && data.getBitcoinFees) {
|
|
||||||
const { fast, halfHour, hour } = data.getBitcoinFees;
|
|
||||||
setInfo({
|
|
||||||
type: 'fetched',
|
|
||||||
state: { loading: false, error: false, fast, halfHour, hour },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data, loading, setInfo]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import { GET_BITCOIN_PRICE } from '../../graphql/query';
|
|
||||||
import { usePriceDispatch } from '../../context/PriceContext';
|
|
||||||
|
|
||||||
export const BitcoinPrice = () => {
|
|
||||||
const setPrices = usePriceDispatch();
|
|
||||||
const { loading, data, stopPolling } = useQuery(GET_BITCOIN_PRICE, {
|
|
||||||
onError: () => setPrices({ type: 'error' }),
|
|
||||||
pollInterval: 60000,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading && data && data.getBitcoinPrice) {
|
|
||||||
try {
|
|
||||||
const prices = JSON.parse(data.getBitcoinPrice);
|
|
||||||
setPrices({
|
|
||||||
type: 'fetched',
|
|
||||||
state: { loading: false, error: false, prices },
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
stopPolling();
|
|
||||||
setPrices({ type: 'error' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [data, loading, setPrices, stopPolling]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
|
@ -1,32 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import { burgerColor } from 'styles/Themes';
|
|
||||||
import { NodeInfo } from 'sections/navigation/nodeInfo/NodeInfo';
|
|
||||||
import { Navigation } from 'sections/navigation/Navigation';
|
|
||||||
import { SideSettings } from 'sections/navigation/sideSettings/SideSettings';
|
|
||||||
|
|
||||||
const StyledBurger = styled.div`
|
|
||||||
padding: 16px 16px 0;
|
|
||||||
background-color: ${burgerColor};
|
|
||||||
box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.1);
|
|
||||||
${({ open }: { open: boolean }) =>
|
|
||||||
open &&
|
|
||||||
css`
|
|
||||||
margin-bottom: 16px;
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface BurgerProps {
|
|
||||||
open: boolean;
|
|
||||||
setOpen: (state: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BurgerMenu = ({ open, setOpen }: BurgerProps) => {
|
|
||||||
return (
|
|
||||||
<StyledBurger open={open}>
|
|
||||||
<NodeInfo isBurger={true} />
|
|
||||||
<SideSettings isBurger={true} />
|
|
||||||
<Navigation isBurger={true} setOpen={setOpen} />
|
|
||||||
</StyledBurger>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,31 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
import { ColorButton } from './ColorButton';
|
|
||||||
import { text, boolean, color } from '@storybook/addon-knobs';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Color Button',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = () => {
|
|
||||||
const withColor = boolean('With Color', false);
|
|
||||||
|
|
||||||
const buttonColor = withColor ? { color: color('Color', 'yellow') } : {};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ColorButton
|
|
||||||
{...buttonColor}
|
|
||||||
loading={boolean('Loading', false)}
|
|
||||||
disabled={boolean('Disabled', false)}
|
|
||||||
arrow={boolean('With Arrow', false)}
|
|
||||||
selected={boolean('Selected', false)}
|
|
||||||
onClick={action('clicked')}
|
|
||||||
withMargin={text('Margin', '')}
|
|
||||||
withBorder={boolean('With Border', false)}
|
|
||||||
fullWidth={boolean('Full Width', false)}
|
|
||||||
width={text('Width', '')}
|
|
||||||
>
|
|
||||||
{text('Text', 'Button')}
|
|
||||||
</ColorButton>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,159 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import {
|
|
||||||
textColor,
|
|
||||||
colorButtonBackground,
|
|
||||||
disabledButtonBackground,
|
|
||||||
disabledButtonBorder,
|
|
||||||
disabledTextColor,
|
|
||||||
colorButtonBorder,
|
|
||||||
colorButtonBorderTwo,
|
|
||||||
hoverTextColor,
|
|
||||||
} from '../../../styles/Themes';
|
|
||||||
import { ChevronRight } from '../../generic/Icons';
|
|
||||||
import { themeColors } from '../../../styles/Themes';
|
|
||||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
|
||||||
|
|
||||||
interface GeneralProps {
|
|
||||||
fullWidth?: boolean;
|
|
||||||
buttonWidth?: string;
|
|
||||||
withMargin?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GeneralButton = styled.button`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 14px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: ${({ withMargin }) => (withMargin ? withMargin : '0')};
|
|
||||||
width: ${({ fullWidth, buttonWidth }: GeneralProps) =>
|
|
||||||
fullWidth ? '100%' : buttonWidth ? buttonWidth : 'auto'};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledArrow = styled.div`
|
|
||||||
margin: 0 -8px -5px 4px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface BorderProps {
|
|
||||||
borderColor?: string;
|
|
||||||
selected?: boolean;
|
|
||||||
withBorder?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BorderButton = styled(GeneralButton)`
|
|
||||||
${({ selected }) => selected && `cursor: default`};
|
|
||||||
${({ selected }) => selected && `font-weight: 900`};
|
|
||||||
background-color: ${colorButtonBackground};
|
|
||||||
color: ${textColor};
|
|
||||||
border: 1px solid
|
|
||||||
${({ borderColor, selected, withBorder }: BorderProps) =>
|
|
||||||
withBorder
|
|
||||||
? borderColor
|
|
||||||
? borderColor
|
|
||||||
: colorButtonBorder
|
|
||||||
: selected
|
|
||||||
? colorButtonBorder
|
|
||||||
: colorButtonBorderTwo};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
${({ borderColor, selected }: BorderProps) =>
|
|
||||||
!selected
|
|
||||||
? css`
|
|
||||||
border: 1px solid ${colorButtonBackground};
|
|
||||||
background-color: ${borderColor
|
|
||||||
? borderColor
|
|
||||||
: colorButtonBorder};
|
|
||||||
color: ${hoverTextColor};
|
|
||||||
`
|
|
||||||
: ''};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DisabledButton = styled(GeneralButton)`
|
|
||||||
border: none;
|
|
||||||
background-color: ${disabledButtonBackground};
|
|
||||||
color: ${disabledTextColor};
|
|
||||||
border: 1px solid ${disabledButtonBorder};
|
|
||||||
cursor: default;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const renderArrow = () => (
|
|
||||||
<StyledArrow>
|
|
||||||
<ChevronRight size={'18px'} />
|
|
||||||
</StyledArrow>
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface ColorButtonProps {
|
|
||||||
loading?: boolean;
|
|
||||||
color?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
children?: any;
|
|
||||||
selected?: boolean;
|
|
||||||
arrow?: boolean;
|
|
||||||
onClick?: any;
|
|
||||||
withMargin?: string;
|
|
||||||
withBorder?: boolean;
|
|
||||||
fullWidth?: boolean;
|
|
||||||
width?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ColorButton = ({
|
|
||||||
loading,
|
|
||||||
color,
|
|
||||||
disabled,
|
|
||||||
children,
|
|
||||||
selected,
|
|
||||||
arrow,
|
|
||||||
withMargin,
|
|
||||||
withBorder,
|
|
||||||
fullWidth,
|
|
||||||
width,
|
|
||||||
onClick,
|
|
||||||
}: ColorButtonProps) => {
|
|
||||||
if (disabled && !loading) {
|
|
||||||
return (
|
|
||||||
<DisabledButton
|
|
||||||
withMargin={withMargin}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
buttonWidth={width}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{arrow && renderArrow()}
|
|
||||||
</DisabledButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<DisabledButton
|
|
||||||
withMargin={withMargin}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
buttonWidth={width}
|
|
||||||
>
|
|
||||||
<ScaleLoader height={16} color={themeColors.blue2} />
|
|
||||||
</DisabledButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BorderButton
|
|
||||||
borderColor={color}
|
|
||||||
selected={selected}
|
|
||||||
onClick={onClick}
|
|
||||||
withMargin={withMargin}
|
|
||||||
withBorder={withBorder}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
buttonWidth={width}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{arrow && renderArrow()}
|
|
||||||
</BorderButton>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,51 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { boolean, color } from '@storybook/addon-knobs';
|
|
||||||
import { MultiButton, SingleButton } from './MultiButton';
|
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Multi Button',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = () => {
|
|
||||||
const withColor = boolean('With Color', false);
|
|
||||||
|
|
||||||
const buttonColor = withColor ? { color: color('Color', 'yellow') } : {};
|
|
||||||
|
|
||||||
const [selected, setSelected] = useState(0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MultiButton>
|
|
||||||
<SingleButton
|
|
||||||
{...buttonColor}
|
|
||||||
selected={selected === 0}
|
|
||||||
onClick={() => {
|
|
||||||
action('Button 1 clicked')();
|
|
||||||
setSelected(0);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Button 1
|
|
||||||
</SingleButton>
|
|
||||||
<SingleButton
|
|
||||||
{...buttonColor}
|
|
||||||
selected={selected === 1}
|
|
||||||
onClick={() => {
|
|
||||||
action('Button 2 clicked')();
|
|
||||||
setSelected(1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Button 2
|
|
||||||
</SingleButton>
|
|
||||||
<SingleButton
|
|
||||||
{...buttonColor}
|
|
||||||
selected={selected === 2}
|
|
||||||
onClick={() => {
|
|
||||||
action('Button 3 clicked')();
|
|
||||||
setSelected(2);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Button 3
|
|
||||||
</SingleButton>
|
|
||||||
</MultiButton>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,85 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import {
|
|
||||||
multiSelectColor,
|
|
||||||
colorButtonBorder,
|
|
||||||
multiButtonColor,
|
|
||||||
} from '../../../styles/Themes';
|
|
||||||
|
|
||||||
interface StyledSingleProps {
|
|
||||||
selected?: boolean;
|
|
||||||
buttonColor?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyledSingleButton = styled.button`
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
background-color: transparent;
|
|
||||||
color: ${multiSelectColor};
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
${({ selected, buttonColor }: StyledSingleProps) =>
|
|
||||||
selected
|
|
||||||
? css`
|
|
||||||
color: white;
|
|
||||||
background-color: ${buttonColor
|
|
||||||
? buttonColor
|
|
||||||
: colorButtonBorder};
|
|
||||||
`
|
|
||||||
: ''};
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface SingleButtonProps {
|
|
||||||
children: any;
|
|
||||||
selected?: boolean;
|
|
||||||
color?: string;
|
|
||||||
onClick?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SingleButton = ({
|
|
||||||
children,
|
|
||||||
selected,
|
|
||||||
color,
|
|
||||||
onClick,
|
|
||||||
}: SingleButtonProps) => {
|
|
||||||
return (
|
|
||||||
<StyledSingleButton
|
|
||||||
selected={selected}
|
|
||||||
buttonColor={color}
|
|
||||||
onClick={() => {
|
|
||||||
onClick && onClick();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</StyledSingleButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface MultiBackProps {
|
|
||||||
margin?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MultiBackground = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px;
|
|
||||||
background: ${multiButtonColor};
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
${({ margin }: MultiBackProps) => margin && `margin: ${margin}`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface MultiButtonProps {
|
|
||||||
children: any;
|
|
||||||
margin?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MultiButton = ({ children, margin }: MultiButtonProps) => {
|
|
||||||
return <MultiBackground margin={margin}>{children}</MultiBackground>;
|
|
||||||
};
|
|
|
@ -1,124 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import CryptoJS from 'crypto-js';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import {
|
|
||||||
Sub4Title,
|
|
||||||
NoWrapTitle,
|
|
||||||
SubTitle,
|
|
||||||
ResponsiveLine,
|
|
||||||
} from '../../generic/Styled';
|
|
||||||
import { Circle, ChevronRight } from '../../generic/Icons';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { useAccount } from '../../../context/AccountContext';
|
|
||||||
import { saveSessionAuth } from '../../../utils/auth';
|
|
||||||
import { useSettings } from '../../../context/SettingsContext';
|
|
||||||
import { textColorMap, mediaDimensions } from '../../../styles/Themes';
|
|
||||||
import { ColorButton } from '../colorButton/ColorButton';
|
|
||||||
import { Input } from '../../input/Input';
|
|
||||||
import { useSize } from 'hooks/UseSize';
|
|
||||||
|
|
||||||
const RadioText = styled.div`
|
|
||||||
margin-left: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ButtonRow = styled.div`
|
|
||||||
width: auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface LoginProps {
|
|
||||||
macaroon: string;
|
|
||||||
color?: string;
|
|
||||||
callback: any;
|
|
||||||
variables: {};
|
|
||||||
setModalOpen: (value: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoginModal = ({
|
|
||||||
macaroon,
|
|
||||||
color,
|
|
||||||
setModalOpen,
|
|
||||||
callback,
|
|
||||||
variables,
|
|
||||||
}: LoginProps) => {
|
|
||||||
const { width } = useSize();
|
|
||||||
const { theme } = useSettings();
|
|
||||||
|
|
||||||
const [pass, setPass] = useState<string>('');
|
|
||||||
const [storeSession, setStoreSession] = useState<boolean>(false);
|
|
||||||
const { host, cert, refreshAccount } = useAccount();
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
try {
|
|
||||||
const bytes = CryptoJS.AES.decrypt(macaroon, pass);
|
|
||||||
const decrypted = bytes.toString(CryptoJS.enc.Utf8);
|
|
||||||
|
|
||||||
if (storeSession) {
|
|
||||||
saveSessionAuth(decrypted);
|
|
||||||
refreshAccount();
|
|
||||||
}
|
|
||||||
const auth = { host, macaroon: decrypted, cert };
|
|
||||||
callback({ variables: { ...variables, auth } });
|
|
||||||
setModalOpen(false);
|
|
||||||
} catch (error) {
|
|
||||||
toast.error('Wrong Password');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderButton = (
|
|
||||||
onClick: () => void,
|
|
||||||
text: string,
|
|
||||||
selected: boolean,
|
|
||||||
) => (
|
|
||||||
<ColorButton color={color} onClick={onClick}>
|
|
||||||
<Circle
|
|
||||||
size={'10px'}
|
|
||||||
fillcolor={selected ? textColorMap[theme] : ''}
|
|
||||||
/>
|
|
||||||
<RadioText>{text}</RadioText>
|
|
||||||
</ColorButton>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SubTitle>Unlock your Account</SubTitle>
|
|
||||||
<ResponsiveLine>
|
|
||||||
<Sub4Title>Password:</Sub4Title>
|
|
||||||
<Input
|
|
||||||
withMargin={
|
|
||||||
width <= mediaDimensions.mobile ? '0' : '0 0 0 16px'
|
|
||||||
}
|
|
||||||
type={'password'}
|
|
||||||
onChange={(e) => setPass(e.target.value)}
|
|
||||||
/>
|
|
||||||
</ResponsiveLine>
|
|
||||||
<ResponsiveLine>
|
|
||||||
<NoWrapTitle>Don't ask me again this session:</NoWrapTitle>
|
|
||||||
<ButtonRow>
|
|
||||||
{renderButton(
|
|
||||||
() => setStoreSession(true),
|
|
||||||
'Yes',
|
|
||||||
storeSession,
|
|
||||||
)}
|
|
||||||
{renderButton(
|
|
||||||
() => setStoreSession(false),
|
|
||||||
'No',
|
|
||||||
!storeSession,
|
|
||||||
)}
|
|
||||||
</ButtonRow>
|
|
||||||
</ResponsiveLine>
|
|
||||||
<ColorButton
|
|
||||||
disabled={pass === ''}
|
|
||||||
onClick={handleClick}
|
|
||||||
color={color}
|
|
||||||
fullWidth={true}
|
|
||||||
withMargin={'16px 0 0'}
|
|
||||||
>
|
|
||||||
Unlock
|
|
||||||
<ChevronRight />
|
|
||||||
</ColorButton>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,63 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import Modal from '../../modal/ReactModal';
|
|
||||||
import { LoginModal } from './LoginModal';
|
|
||||||
import { useAccount } from '../../../context/AccountContext';
|
|
||||||
import { ColorButton } from '../colorButton/ColorButton';
|
|
||||||
import { ColorButtonProps } from '../colorButton/ColorButton';
|
|
||||||
|
|
||||||
interface SecureButtonProps extends ColorButtonProps {
|
|
||||||
callback: any;
|
|
||||||
disabled: boolean;
|
|
||||||
children: any;
|
|
||||||
variables: {};
|
|
||||||
color?: string;
|
|
||||||
withMargin?: string;
|
|
||||||
arrow?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SecureButton = ({
|
|
||||||
callback,
|
|
||||||
color,
|
|
||||||
disabled,
|
|
||||||
children,
|
|
||||||
variables,
|
|
||||||
...props
|
|
||||||
}: SecureButtonProps) => {
|
|
||||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const { host, cert, admin, sessionAdmin } = useAccount();
|
|
||||||
|
|
||||||
if (!admin && !sessionAdmin) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auth = { host, macaroon: sessionAdmin, cert };
|
|
||||||
|
|
||||||
const handleClick = () => setModalOpen(true);
|
|
||||||
|
|
||||||
const onClick = sessionAdmin
|
|
||||||
? () => callback({ variables: { ...variables, auth } })
|
|
||||||
: handleClick;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ColorButton
|
|
||||||
color={color}
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={onClick}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ColorButton>
|
|
||||||
<Modal isOpen={modalOpen} closeCallback={() => setModalOpen(false)}>
|
|
||||||
<LoginModal
|
|
||||||
color={color}
|
|
||||||
macaroon={admin}
|
|
||||||
setModalOpen={setModalOpen}
|
|
||||||
callback={callback}
|
|
||||||
variables={variables}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { Checkbox } from './Checkbox';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Checkbox',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = () => {
|
|
||||||
const [checked, set] = useState<boolean>(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Checkbox checked={checked} onChange={set}>
|
|
||||||
This is a checkbox
|
|
||||||
</Checkbox>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,58 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import {
|
|
||||||
colorButtonBackground,
|
|
||||||
buttonBorderColor,
|
|
||||||
themeColors,
|
|
||||||
} from '../../styles/Themes';
|
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
padding-right: 32px;
|
|
||||||
cursor: pointer;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FixedWidth = styled.div`
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
margin: 0px;
|
|
||||||
margin-right: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledCheckbox = styled.div`
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
margin: 0;
|
|
||||||
border: 1px solid ${buttonBorderColor};
|
|
||||||
border-radius: 4px;
|
|
||||||
outline: none;
|
|
||||||
transition-duration: 0.3s;
|
|
||||||
background-color: ${colorButtonBackground};
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
${({ checked }: { checked: boolean }) =>
|
|
||||||
checked && `background-color: ${themeColors.blue2}`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type CheckboxProps = {
|
|
||||||
checked: boolean;
|
|
||||||
onChange: (state: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Checkbox: React.FC<CheckboxProps> = ({
|
|
||||||
children,
|
|
||||||
checked,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<StyledContainer onClick={() => onChange(!checked)}>
|
|
||||||
<FixedWidth>
|
|
||||||
<StyledCheckbox checked={checked} />
|
|
||||||
</FixedWidth>
|
|
||||||
{children}
|
|
||||||
</StyledContainer>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { useEffect } from 'react';
|
|
||||||
import {
|
|
||||||
useConnectionState,
|
|
||||||
useConnectionDispatch,
|
|
||||||
} from 'context/ConnectionContext';
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import { GET_CAN_CONNECT } from 'graphql/query';
|
|
||||||
import { useAccount } from 'context/AccountContext';
|
|
||||||
|
|
||||||
export const ConnectionCheck = () => {
|
|
||||||
const { connected } = useConnectionState();
|
|
||||||
const dispatch = useConnectionDispatch();
|
|
||||||
|
|
||||||
const { loggedIn, host, viewOnly, cert, sessionAdmin } = useAccount();
|
|
||||||
const auth = {
|
|
||||||
host,
|
|
||||||
macaroon: viewOnly !== '' ? viewOnly : sessionAdmin,
|
|
||||||
cert,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data, loading } = useQuery(GET_CAN_CONNECT, {
|
|
||||||
variables: { auth },
|
|
||||||
skip: connected || !loggedIn,
|
|
||||||
onError: () => {
|
|
||||||
dispatch({ type: 'error' });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading && data && data.getNodeInfo) {
|
|
||||||
dispatch({ type: 'connected' });
|
|
||||||
}
|
|
||||||
}, [data, loading, dispatch]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
|
@ -1,17 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface EmojiProps {
|
|
||||||
symbol: string;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Emoji = ({ label, symbol }: EmojiProps) => (
|
|
||||||
<span
|
|
||||||
className="emoji"
|
|
||||||
role="img"
|
|
||||||
aria-label={label ? label : ''}
|
|
||||||
aria-hidden={label ? 'false' : 'true'}
|
|
||||||
>
|
|
||||||
{symbol}
|
|
||||||
</span>
|
|
||||||
);
|
|
|
@ -1,74 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { SmallLink, DarkSubTitle, OverflowText, SingleLine } from './Styled';
|
|
||||||
import { StatusDot, DetailLine } from '../../views/channels/Channels.style';
|
|
||||||
import { formatDistanceStrict, format } from 'date-fns';
|
|
||||||
import { XSvg } from './Icons';
|
|
||||||
|
|
||||||
export const getTransactionLink = (transaction: string) => {
|
|
||||||
const link = `https://www.blockchain.com/btc/tx/${transaction}`;
|
|
||||||
return (
|
|
||||||
<SmallLink href={link} target="_blank">
|
|
||||||
{transaction}
|
|
||||||
</SmallLink>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getNodeLink = (publicKey: string) => {
|
|
||||||
const link = `https://1ml.com/node/${publicKey}`;
|
|
||||||
return (
|
|
||||||
<SmallLink href={link} target="_blank">
|
|
||||||
{publicKey}
|
|
||||||
</SmallLink>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDateDif = (date: string) => {
|
|
||||||
return formatDistanceStrict(new Date(date), new Date());
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFormatDate = (date: string) => {
|
|
||||||
return format(new Date(date), 'dd-MM-yyyy - HH:mm:ss');
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTooltipType = (theme: string) => {
|
|
||||||
return theme === 'dark' ? 'light' : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getStatusDot = (status: boolean, type: string) => {
|
|
||||||
if (type === 'active') {
|
|
||||||
return status ? (
|
|
||||||
<StatusDot color="#95de64" />
|
|
||||||
) : (
|
|
||||||
<StatusDot color="#ff4d4f" />
|
|
||||||
);
|
|
||||||
} else if (type === 'opening') {
|
|
||||||
return status ? <StatusDot color="#13c2c2" /> : null;
|
|
||||||
} else {
|
|
||||||
return status ? <StatusDot color="#ff4d4f" /> : null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const renderLine = (
|
|
||||||
title: string,
|
|
||||||
content: any,
|
|
||||||
key?: string | number,
|
|
||||||
deleteCallback?: () => void,
|
|
||||||
) => {
|
|
||||||
if (!content) return null;
|
|
||||||
return (
|
|
||||||
<DetailLine key={key}>
|
|
||||||
<DarkSubTitle>{title}</DarkSubTitle>
|
|
||||||
<SingleLine>
|
|
||||||
<OverflowText>{content}</OverflowText>
|
|
||||||
{deleteCallback && (
|
|
||||||
<div
|
|
||||||
style={{ margin: '0 0 -4px 4px' }}
|
|
||||||
onClick={deleteCallback}
|
|
||||||
>
|
|
||||||
<XSvg />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</SingleLine>
|
|
||||||
</DetailLine>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,124 +0,0 @@
|
||||||
import { FunctionComponent } from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import { ReactComponent as UpIcon } from '../../assets/icons/arrow-up.svg';
|
|
||||||
import { ReactComponent as DownIcon } from '../../assets/icons/arrow-down.svg';
|
|
||||||
import { ReactComponent as ZapIcon } from '../../assets/icons/zap.svg';
|
|
||||||
import { ReactComponent as ZapOffIcon } from '../../assets/icons/zap-off.svg';
|
|
||||||
import { ReactComponent as HelpIcon } from '../../assets/icons/help-circle.svg';
|
|
||||||
import { ReactComponent as SunIcon } from '../../assets/icons/sun.svg';
|
|
||||||
import { ReactComponent as MoonIcon } from '../../assets/icons/moon.svg';
|
|
||||||
import { ReactComponent as EyeIcon } from '../../assets/icons/eye.svg';
|
|
||||||
import { ReactComponent as EyeOffIcon } from '../../assets/icons/eye-off.svg';
|
|
||||||
import { ReactComponent as ChevronsUpIcon } from '../../assets/icons/chevrons-up.svg';
|
|
||||||
import { ReactComponent as ChevronsDownIcon } from '../../assets/icons/chevrons-down.svg';
|
|
||||||
import { ReactComponent as ChevronLeftIcon } from '../../assets/icons/chevron-left.svg';
|
|
||||||
import { ReactComponent as ChevronRightIcon } from '../../assets/icons/chevron-right.svg';
|
|
||||||
import { ReactComponent as ChevronUpIcon } from '../../assets/icons/chevron-up.svg';
|
|
||||||
import { ReactComponent as ChevronDownIcon } from '../../assets/icons/chevron-down.svg';
|
|
||||||
import { ReactComponent as HomeIcon } from '../../assets/icons/home.svg';
|
|
||||||
import { ReactComponent as CpuIcon } from '../../assets/icons/cpu.svg';
|
|
||||||
import { ReactComponent as SendIcon } from '../../assets/icons/send.svg';
|
|
||||||
import { ReactComponent as ServerIcon } from '../../assets/icons/server.svg';
|
|
||||||
import { ReactComponent as SettingsIcon } from '../../assets/icons/settings.svg';
|
|
||||||
import { ReactComponent as EditIcon } from '../../assets/icons/edit.svg';
|
|
||||||
import { ReactComponent as MoreVerticalIcon } from '../../assets/icons/more-vertical.svg';
|
|
||||||
import { ReactComponent as AnchorIcon } from '../../assets/icons/anchor.svg';
|
|
||||||
import { ReactComponent as PocketIcon } from '../../assets/icons/pocket.svg';
|
|
||||||
import { ReactComponent as GlobeIcon } from '../../assets/icons/globe.svg';
|
|
||||||
import { ReactComponent as XIcon } from '../../assets/icons/x.svg';
|
|
||||||
import { ReactComponent as LayersIcon } from '../../assets/icons/layers.svg';
|
|
||||||
import { ReactComponent as LoaderIcon } from '../../assets/icons/loader.svg';
|
|
||||||
import { ReactComponent as CircleIcon } from '../../assets/icons/circle.svg';
|
|
||||||
import { ReactComponent as AlertTriangleIcon } from '../../assets/icons/alert-triangle.svg';
|
|
||||||
import { ReactComponent as AlertCircleIcon } from '../../assets/icons/alert-circle.svg';
|
|
||||||
import { ReactComponent as GitCommitIcon } from '../../assets/icons/git-commit.svg';
|
|
||||||
import { ReactComponent as GitBranchIcon } from '../../assets/icons/git-branch.svg';
|
|
||||||
import { ReactComponent as RadioIcon } from '../../assets/icons/radio.svg';
|
|
||||||
import { ReactComponent as CopyIcon } from '../../assets/icons/copy.svg';
|
|
||||||
import { ReactComponent as ShieldIcon } from '../../assets/icons/shield.svg';
|
|
||||||
import { ReactComponent as CrosshairIcon } from '../../assets/icons/crosshair.svg';
|
|
||||||
import { ReactComponent as KeyIcon } from '../../assets/icons/key.svg';
|
|
||||||
import { ReactComponent as SlidersIcon } from '../../assets/icons/sliders.svg';
|
|
||||||
import { ReactComponent as UsersIcon } from '../../assets/icons/users.svg';
|
|
||||||
import { ReactComponent as GitPullRequestIcon } from '../../assets/icons/git-pull-request.svg';
|
|
||||||
import { ReactComponent as Link } from '../../assets/icons/link.svg';
|
|
||||||
import { ReactComponent as Menu } from '../../assets/icons/menu.svg';
|
|
||||||
import { ReactComponent as Mail } from '../../assets/icons/mail.svg';
|
|
||||||
import { ReactComponent as Github } from '../../assets/icons/github.svg';
|
|
||||||
import { ReactComponent as Repeat } from '../../assets/icons/repeat.svg';
|
|
||||||
import { ReactComponent as CheckIcon } from '../../assets/icons/check.svg';
|
|
||||||
import { ReactComponent as StarIcon } from '../../assets/icons/star.svg';
|
|
||||||
import { ReactComponent as HalfStarIcon } from '../../assets/icons/half-star.svg';
|
|
||||||
import { ReactComponent as CreditCardIcon } from '../../assets/icons/credit-card.svg';
|
|
||||||
|
|
||||||
export interface IconProps {
|
|
||||||
color?: string;
|
|
||||||
size?: string;
|
|
||||||
fillcolor?: string;
|
|
||||||
strokeWidth?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GenericStyles = css`
|
|
||||||
height: ${({ size }: IconProps) => (size ? size : '18px')};
|
|
||||||
width: ${({ size }: IconProps) => (size ? size : '18px')};
|
|
||||||
color: ${({ color }: IconProps) => (color ? color : '')};
|
|
||||||
fill: ${({ fillcolor }: IconProps) => (fillcolor ? fillcolor : '')};
|
|
||||||
stroke-width: ${({ strokeWidth }: IconProps) =>
|
|
||||||
strokeWidth ? strokeWidth : '2px'};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const styleIcon = (icon: FunctionComponent) =>
|
|
||||||
styled(icon)`
|
|
||||||
${GenericStyles}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const QuestionIcon = styleIcon(HelpIcon);
|
|
||||||
export const Zap = styleIcon(ZapIcon);
|
|
||||||
export const ZapOff = styleIcon(ZapOffIcon);
|
|
||||||
export const Anchor = styleIcon(AnchorIcon);
|
|
||||||
export const Pocket = styleIcon(PocketIcon);
|
|
||||||
export const Globe = styleIcon(GlobeIcon);
|
|
||||||
export const UpArrow = styleIcon(UpIcon);
|
|
||||||
export const DownArrow = styleIcon(DownIcon);
|
|
||||||
export const Sun = styleIcon(SunIcon);
|
|
||||||
export const Moon = styleIcon(MoonIcon);
|
|
||||||
export const Eye = styleIcon(EyeIcon);
|
|
||||||
export const EyeOff = styleIcon(EyeOffIcon);
|
|
||||||
export const ChevronsDown = styleIcon(ChevronsDownIcon);
|
|
||||||
export const ChevronsUp = styleIcon(ChevronsUpIcon);
|
|
||||||
export const ChevronLeft = styleIcon(ChevronLeftIcon);
|
|
||||||
export const ChevronRight = styleIcon(ChevronRightIcon);
|
|
||||||
export const ChevronUp = styleIcon(ChevronUpIcon);
|
|
||||||
export const ChevronDown = styleIcon(ChevronDownIcon);
|
|
||||||
export const Home = styleIcon(HomeIcon);
|
|
||||||
export const Cpu = styleIcon(CpuIcon);
|
|
||||||
export const Send = styleIcon(SendIcon);
|
|
||||||
export const Server = styleIcon(ServerIcon);
|
|
||||||
export const Settings = styleIcon(SettingsIcon);
|
|
||||||
export const Edit = styleIcon(EditIcon);
|
|
||||||
export const MoreVertical = styleIcon(MoreVerticalIcon);
|
|
||||||
export const XSvg = styleIcon(XIcon);
|
|
||||||
export const Layers = styleIcon(LayersIcon);
|
|
||||||
export const Loader = styleIcon(LoaderIcon);
|
|
||||||
export const Circle = styleIcon(CircleIcon);
|
|
||||||
export const AlertTriangle = styleIcon(AlertTriangleIcon);
|
|
||||||
export const AlertCircle = styleIcon(AlertCircleIcon);
|
|
||||||
export const GitCommit = styleIcon(GitCommitIcon);
|
|
||||||
export const GitBranch = styleIcon(GitBranchIcon);
|
|
||||||
export const Radio = styleIcon(RadioIcon);
|
|
||||||
export const Copy = styleIcon(CopyIcon);
|
|
||||||
export const Shield = styleIcon(ShieldIcon);
|
|
||||||
export const Crosshair = styleIcon(CrosshairIcon);
|
|
||||||
export const Key = styleIcon(KeyIcon);
|
|
||||||
export const Sliders = styleIcon(SlidersIcon);
|
|
||||||
export const Users = styleIcon(UsersIcon);
|
|
||||||
export const GitPullRequest = styleIcon(GitPullRequestIcon);
|
|
||||||
export const LinkIcon = styleIcon(Link);
|
|
||||||
export const MenuIcon = styleIcon(Menu);
|
|
||||||
export const MailIcon = styleIcon(Mail);
|
|
||||||
export const GithubIcon = styleIcon(Github);
|
|
||||||
export const RepeatIcon = styleIcon(Repeat);
|
|
||||||
export const Check = styleIcon(CheckIcon);
|
|
||||||
export const Star = styleIcon(StarIcon);
|
|
||||||
export const HalfStar = styleIcon(HalfStarIcon);
|
|
||||||
export const CreditCard = styleIcon(CreditCardIcon);
|
|
|
@ -1,225 +0,0 @@
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import {
|
|
||||||
cardColor,
|
|
||||||
cardBorderColor,
|
|
||||||
subCardColor,
|
|
||||||
smallLinkColor,
|
|
||||||
unSelectedNavButton,
|
|
||||||
textColor,
|
|
||||||
buttonBorderColor,
|
|
||||||
chartLinkColor,
|
|
||||||
inverseTextColor,
|
|
||||||
separationColor,
|
|
||||||
mediaWidths,
|
|
||||||
} from '../../styles/Themes';
|
|
||||||
import { ThemeSet } from 'styled-theming';
|
|
||||||
|
|
||||||
export const CardWithTitle = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CardTitle = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export interface CardProps {
|
|
||||||
bottom?: string;
|
|
||||||
cardPadding?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Card = styled.div`
|
|
||||||
padding: ${({ cardPadding }: CardProps) => cardPadding ?? '16px'};
|
|
||||||
background: ${cardColor};
|
|
||||||
box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid ${cardBorderColor};
|
|
||||||
margin-bottom: ${({ bottom }: CardProps) => (bottom ? bottom : '25px')};
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface SeparationProps {
|
|
||||||
height?: number;
|
|
||||||
lineColor?: string | ThemeSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Separation = styled.div`
|
|
||||||
height: ${({ height }: SeparationProps) => (height ? height : '1')}px;
|
|
||||||
background-color: ${({ lineColor }: SeparationProps) =>
|
|
||||||
lineColor ?? separationColor};
|
|
||||||
width: 100%;
|
|
||||||
margin: 16px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface SubCardProps {
|
|
||||||
color?: string;
|
|
||||||
padding?: string;
|
|
||||||
withMargin?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SubCard = styled.div`
|
|
||||||
margin: ${({ withMargin }) => (withMargin ? withMargin : '0 0 10px 0')};
|
|
||||||
padding: ${({ padding }) => (padding ? padding : '16px')};
|
|
||||||
background: ${subCardColor};
|
|
||||||
border: 1px solid ${cardBorderColor};
|
|
||||||
border-left: ${({ color }: SubCardProps) =>
|
|
||||||
color ? `2px solid ${color}` : ''};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SmallLink = styled.a`
|
|
||||||
text-decoration: none;
|
|
||||||
color: ${smallLinkColor};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type SubTitleProps = {
|
|
||||||
subtitleColor?: string | ThemeSet;
|
|
||||||
fontWeight?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SubTitle = styled.h4`
|
|
||||||
margin: 5px 0;
|
|
||||||
${({ subtitleColor }: SubTitleProps) =>
|
|
||||||
subtitleColor &&
|
|
||||||
css`
|
|
||||||
color: ${subtitleColor};
|
|
||||||
`}
|
|
||||||
font-weight: ${({ fontWeight }: SubTitleProps) =>
|
|
||||||
fontWeight ? fontWeight : '500'};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const InverseSubtitle = styled(SubTitle)`
|
|
||||||
color: ${inverseTextColor};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Sub4Title = styled.h5`
|
|
||||||
margin: 10px 0;
|
|
||||||
font-weight: 500;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const NoWrapTitle = styled(Sub4Title)`
|
|
||||||
white-space: nowrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SingleLine = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RightAlign = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ColumnLine = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SimpleButton = styled.button`
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
padding: 5px;
|
|
||||||
margin: 5px;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: transparent;
|
|
||||||
color: ${({ enabled = true }: { enabled?: boolean }) =>
|
|
||||||
enabled ? textColor : unSelectedNavButton};
|
|
||||||
border: 1px solid ${buttonBorderColor};
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 5px;
|
|
||||||
white-space: nowrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SimpleInverseButton = styled(SimpleButton)`
|
|
||||||
color: ${({ enabled = true }: { enabled?: boolean }) =>
|
|
||||||
enabled ? inverseTextColor : unSelectedNavButton};
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface DarkProps {
|
|
||||||
fontSize?: string;
|
|
||||||
bottom?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DarkSubTitle = styled.div`
|
|
||||||
font-size: ${({ fontSize }: DarkProps) => (fontSize ? fontSize : '14px')};
|
|
||||||
color: ${unSelectedNavButton};
|
|
||||||
margin-bottom: ${({ bottom }: DarkProps) => (bottom ? bottom : '0px')};
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface ColorProps {
|
|
||||||
color: string;
|
|
||||||
selected?: boolean;
|
|
||||||
}
|
|
||||||
export const ColorButton = styled(SimpleButton)`
|
|
||||||
color: ${({ selected }) => (selected ? textColor : chartLinkColor)};
|
|
||||||
border: ${({ selected, color }: ColorProps) =>
|
|
||||||
selected ? `1px solid ${color}` : ''};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid ${({ color }: ColorProps) => color};
|
|
||||||
color: ${textColor};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const OverflowText = styled.div`
|
|
||||||
margin-left: 16px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
word-wrap: break-word;
|
|
||||||
-ms-word-break: break-all;
|
|
||||||
word-break: break-all;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ResponsiveLine = styled(SingleLine)`
|
|
||||||
width: 100%;
|
|
||||||
${({ withWrap }: { withWrap?: boolean }) =>
|
|
||||||
withWrap &&
|
|
||||||
css`
|
|
||||||
flex-wrap: wrap;
|
|
||||||
`}
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ResponsiveCol = styled.div`
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ResponsiveSingle = styled(SingleLine)`
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 200px;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
`;
|
|
|
@ -1,24 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
import { select, color, boolean, text } from '@storybook/addon-knobs';
|
|
||||||
import { Input } from './Input';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Input',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = () => {
|
|
||||||
const withColor = boolean('With Color', false);
|
|
||||||
|
|
||||||
const buttonColor = withColor ? { color: color('Color', 'yellow') } : {};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
{...buttonColor}
|
|
||||||
placeholder={text('Placeholder', 'placeholder')}
|
|
||||||
fullWidth={boolean('Full Width', false)}
|
|
||||||
type={select('Type', ['normal', 'number'], 'normal')}
|
|
||||||
onChange={action('change')}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,85 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import {
|
|
||||||
textColor,
|
|
||||||
colorButtonBorder,
|
|
||||||
inputBackgroundColor,
|
|
||||||
inputBorderColor,
|
|
||||||
} from '../../styles/Themes';
|
|
||||||
|
|
||||||
interface InputProps {
|
|
||||||
color?: string;
|
|
||||||
withMargin?: string;
|
|
||||||
fullWidth?: boolean;
|
|
||||||
inputWidth?: string;
|
|
||||||
maxWidth?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const StyledInput = styled.input`
|
|
||||||
padding: 5px;
|
|
||||||
height: 30px;
|
|
||||||
margin: 8px 0;
|
|
||||||
border: 1px solid ${inputBorderColor};
|
|
||||||
background: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
color: ${textColor};
|
|
||||||
transition: all 0.5s ease;
|
|
||||||
background-color: ${inputBackgroundColor};
|
|
||||||
${({ maxWidth }: InputProps) =>
|
|
||||||
maxWidth &&
|
|
||||||
css`
|
|
||||||
max-width: ${maxWidth};
|
|
||||||
`}
|
|
||||||
width: ${({ fullWidth, inputWidth }: InputProps) =>
|
|
||||||
fullWidth ? '100%' : inputWidth ? inputWidth : 'auto'};
|
|
||||||
margin: ${({ withMargin }) => (withMargin ? withMargin : '0')};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid
|
|
||||||
${({ color }: InputProps) => (color ? color : colorButtonBorder)};
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border: 1px solid
|
|
||||||
${({ color }: InputProps) => (color ? color : colorButtonBorder)};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface InputCompProps {
|
|
||||||
type?: string;
|
|
||||||
value?: number | string;
|
|
||||||
placeholder?: string;
|
|
||||||
color?: string;
|
|
||||||
withMargin?: string;
|
|
||||||
fullWidth?: boolean;
|
|
||||||
width?: string;
|
|
||||||
maxWidth?: string;
|
|
||||||
onChange: (e: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Input = ({
|
|
||||||
type,
|
|
||||||
value,
|
|
||||||
placeholder,
|
|
||||||
color,
|
|
||||||
withMargin,
|
|
||||||
fullWidth = true,
|
|
||||||
width,
|
|
||||||
maxWidth,
|
|
||||||
onChange,
|
|
||||||
}: InputCompProps) => {
|
|
||||||
return (
|
|
||||||
<StyledInput
|
|
||||||
type={type}
|
|
||||||
placeholder={placeholder}
|
|
||||||
value={value}
|
|
||||||
color={color}
|
|
||||||
withMargin={withMargin}
|
|
||||||
onChange={(e) => onChange(e)}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
inputWidth={width}
|
|
||||||
maxWidth={maxWidth}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,13 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { text } from '@storybook/addon-knobs';
|
|
||||||
import { Link } from './Link';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Link',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = () => {
|
|
||||||
const linkText = text('Link Text', 'This is a link');
|
|
||||||
|
|
||||||
return <Link to={'google.com'}>{linkText}</Link>;
|
|
||||||
};
|
|
|
@ -1,82 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import { textColor, linkHighlight } from '../../styles/Themes';
|
|
||||||
import { ThemeSet } from 'styled-theming';
|
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
|
||||||
|
|
||||||
interface StyledProps {
|
|
||||||
fontColor?: string | ThemeSet;
|
|
||||||
underline?: string | ThemeSet;
|
|
||||||
inheritColor?: boolean;
|
|
||||||
fullWidth?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const styledCss = css`
|
|
||||||
color: ${({ fontColor, inheritColor }: StyledProps) =>
|
|
||||||
inheritColor ? 'inherit' : fontColor ?? textColor};
|
|
||||||
text-decoration: none;
|
|
||||||
${({ fullWidth }: StyledProps) =>
|
|
||||||
fullWidth &&
|
|
||||||
css`
|
|
||||||
width: 100%;
|
|
||||||
`};
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
background: linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
${({ underline }: StyledProps) => underline ?? linkHighlight} 0%,
|
|
||||||
${({ underline }: StyledProps) => underline ?? linkHighlight} 100%
|
|
||||||
);
|
|
||||||
background-position: 0 100%;
|
|
||||||
background-size: 2px 2px;
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledLink = styled(
|
|
||||||
({ inheritColor, fontColor, underline, fullWidth, ...rest }) => (
|
|
||||||
<RouterLink {...rest} />
|
|
||||||
),
|
|
||||||
)(() => styledCss);
|
|
||||||
|
|
||||||
const StyledALink = styled.a`
|
|
||||||
${styledCss}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface LinkProps {
|
|
||||||
children: any;
|
|
||||||
to?: string;
|
|
||||||
href?: string;
|
|
||||||
color?: string | ThemeSet;
|
|
||||||
underline?: string | ThemeSet;
|
|
||||||
inheritColor?: boolean;
|
|
||||||
fullWidth?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Link = ({
|
|
||||||
children,
|
|
||||||
to,
|
|
||||||
href,
|
|
||||||
color,
|
|
||||||
underline,
|
|
||||||
inheritColor,
|
|
||||||
fullWidth,
|
|
||||||
}: LinkProps) => {
|
|
||||||
const props = { fontColor: color, underline, inheritColor, fullWidth };
|
|
||||||
|
|
||||||
if (!to && !href) return null;
|
|
||||||
|
|
||||||
if (to) {
|
|
||||||
return (
|
|
||||||
<StyledLink to={to} {...props}>
|
|
||||||
{children}
|
|
||||||
</StyledLink>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<StyledALink href={href} {...props}>
|
|
||||||
{children}
|
|
||||||
</StyledALink>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,63 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { CardWithTitle, CardTitle, SubTitle, Card } from '../generic/Styled';
|
|
||||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { themeColors } from '../../styles/Themes';
|
|
||||||
|
|
||||||
const Loading = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
height: ${({ loadingHeight }: { loadingHeight?: string }) =>
|
|
||||||
loadingHeight ? loadingHeight : 'auto'};
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface LoadingCardProps {
|
|
||||||
title?: string;
|
|
||||||
noCard?: boolean;
|
|
||||||
color?: string;
|
|
||||||
noTitle?: boolean;
|
|
||||||
loadingHeight?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoadingCard = ({
|
|
||||||
title = '',
|
|
||||||
color,
|
|
||||||
noCard = false,
|
|
||||||
noTitle = false,
|
|
||||||
loadingHeight,
|
|
||||||
}: LoadingCardProps) => {
|
|
||||||
const loadingColor = color ? color : themeColors.blue3;
|
|
||||||
|
|
||||||
if (noCard) {
|
|
||||||
return (
|
|
||||||
<Loading loadingHeight={loadingHeight}>
|
|
||||||
<ScaleLoader height={20} color={loadingColor} />
|
|
||||||
</Loading>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noTitle) {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<Loading loadingHeight={loadingHeight}>
|
|
||||||
<ScaleLoader height={20} color={loadingColor} />
|
|
||||||
</Loading>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CardWithTitle>
|
|
||||||
<CardTitle>
|
|
||||||
<SubTitle>{title}</SubTitle>
|
|
||||||
</CardTitle>
|
|
||||||
<Card>
|
|
||||||
<Loading loadingHeight={loadingHeight}>
|
|
||||||
<ScaleLoader height={20} color={loadingColor} />
|
|
||||||
</Loading>
|
|
||||||
</Card>
|
|
||||||
</CardWithTitle>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,43 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { progressBackground } from 'styles/Themes';
|
|
||||||
|
|
||||||
const Progress = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
background: ${progressBackground};
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface ProgressBar {
|
|
||||||
percent: number;
|
|
||||||
barColor?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProgressBar = styled.div`
|
|
||||||
height: 10px;
|
|
||||||
background-color: ${({ barColor }: ProgressBar) =>
|
|
||||||
barColor ? barColor : 'blue'};
|
|
||||||
width: ${({ percent }: ProgressBar) => `${percent}%`};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const getColor = (percent: number) => {
|
|
||||||
switch (true) {
|
|
||||||
case percent < 20:
|
|
||||||
return '#ff4d4f';
|
|
||||||
case percent < 40:
|
|
||||||
return '#ff7a45';
|
|
||||||
case percent < 60:
|
|
||||||
return '#ffa940';
|
|
||||||
case percent < 80:
|
|
||||||
return '#bae637';
|
|
||||||
case percent <= 100:
|
|
||||||
return '#73d13d';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LoadingBar = ({ percent }: { percent: number }) => (
|
|
||||||
<Progress>
|
|
||||||
<ProgressBar percent={percent} barColor={getColor(percent)} />
|
|
||||||
</Progress>
|
|
||||||
);
|
|
|
@ -1,61 +0,0 @@
|
||||||
import React, { ReactNode } from 'react';
|
|
||||||
import { css } from 'styled-components';
|
|
||||||
import { cardColor, mediaWidths, themeColors } from '../../styles/Themes';
|
|
||||||
import ReactModal from 'styled-react-modal';
|
|
||||||
|
|
||||||
interface ModalProps {
|
|
||||||
children: ReactNode;
|
|
||||||
isOpen: boolean;
|
|
||||||
noMinWidth?: boolean;
|
|
||||||
closeCallback: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const generalCSS = css`
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateY(-50%) translateX(-50%);
|
|
||||||
background-color: ${cardColor};
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
top: 100%;
|
|
||||||
border-radius: 0px;
|
|
||||||
transform: translateY(-100%) translateX(-50%);
|
|
||||||
width: 100%;
|
|
||||||
min-width: 325px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyleModal = ReactModal.styled`
|
|
||||||
${generalCSS}
|
|
||||||
min-width: 578px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyleModalSmall = ReactModal.styled`
|
|
||||||
${generalCSS}
|
|
||||||
background-color: ${themeColors.white};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Modal = ({
|
|
||||||
children,
|
|
||||||
isOpen,
|
|
||||||
noMinWidth = false,
|
|
||||||
closeCallback,
|
|
||||||
}: ModalProps) => {
|
|
||||||
const Styled = noMinWidth ? StyleModalSmall : StyleModal;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Styled
|
|
||||||
isOpen={isOpen}
|
|
||||||
onBackgroundClick={closeCallback}
|
|
||||||
onEscapeKeydown={closeCallback}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Styled>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Modal;
|
|
|
@ -1,222 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { CLOSE_CHANNEL } from '../../../graphql/mutation';
|
|
||||||
import { useMutation, useQuery } from '@apollo/react-hooks';
|
|
||||||
import {
|
|
||||||
Separation,
|
|
||||||
SingleLine,
|
|
||||||
SubTitle,
|
|
||||||
Sub4Title,
|
|
||||||
} from '../../generic/Styled';
|
|
||||||
import { AlertTriangle } from '../../generic/Icons';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { getErrorContent } from '../../../utils/error';
|
|
||||||
import { GET_BITCOIN_FEES } from '../../../graphql/query';
|
|
||||||
import { SecureButton } from '../../buttons/secureButton/SecureButton';
|
|
||||||
import { ColorButton } from '../../buttons/colorButton/ColorButton';
|
|
||||||
import {
|
|
||||||
MultiButton,
|
|
||||||
SingleButton,
|
|
||||||
} from 'components/buttons/multiButton/MultiButton';
|
|
||||||
import { Input } from 'components/input/Input';
|
|
||||||
|
|
||||||
interface CloseChannelProps {
|
|
||||||
setModalOpen: (status: boolean) => void;
|
|
||||||
channelId: string;
|
|
||||||
channelName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WarningCard = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CenterLine = styled(SingleLine)`
|
|
||||||
justify-content: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CloseChannel = ({
|
|
||||||
setModalOpen,
|
|
||||||
channelId,
|
|
||||||
channelName,
|
|
||||||
}: CloseChannelProps) => {
|
|
||||||
const [isForce, setIsForce] = useState<boolean>(false);
|
|
||||||
const [isType, setIsType] = useState<string>('none');
|
|
||||||
const [amount, setAmount] = useState<number>(0);
|
|
||||||
const [isConfirmed, setIsConfirmed] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [fast, setFast] = useState(0);
|
|
||||||
const [halfHour, setHalfHour] = useState(0);
|
|
||||||
const [hour, setHour] = useState(0);
|
|
||||||
|
|
||||||
const { data: feeData } = useQuery(GET_BITCOIN_FEES, {
|
|
||||||
onError: (error) => toast.error(getErrorContent(error)),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (feeData && feeData.getBitcoinFees) {
|
|
||||||
const { fast, halfHour, hour } = feeData.getBitcoinFees;
|
|
||||||
setAmount(fast);
|
|
||||||
setFast(fast);
|
|
||||||
setHalfHour(halfHour);
|
|
||||||
setHour(hour);
|
|
||||||
}
|
|
||||||
}, [feeData]);
|
|
||||||
|
|
||||||
const [closeChannel] = useMutation(CLOSE_CHANNEL, {
|
|
||||||
onCompleted: (data) => {
|
|
||||||
if (data.closeChannel) {
|
|
||||||
toast.success('Channel Closed');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: (error) => toast.error(getErrorContent(error)),
|
|
||||||
refetchQueries: [
|
|
||||||
'GetChannels',
|
|
||||||
'GetPendingChannels',
|
|
||||||
'GetClosedChannels',
|
|
||||||
'GetChannelAmountInfo',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleOnlyClose = () => setModalOpen(false);
|
|
||||||
|
|
||||||
const renderButton = (
|
|
||||||
onClick: () => void,
|
|
||||||
text: string,
|
|
||||||
selected: boolean,
|
|
||||||
) => (
|
|
||||||
<SingleButton selected={selected} onClick={onClick}>
|
|
||||||
{text}
|
|
||||||
</SingleButton>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderWarning = () => (
|
|
||||||
<WarningCard>
|
|
||||||
<AlertTriangle size={'32px'} color={'red'} />
|
|
||||||
<SubTitle>Are you sure you want to close the channel?</SubTitle>
|
|
||||||
<SecureButton
|
|
||||||
callback={closeChannel}
|
|
||||||
variables={{
|
|
||||||
id: channelId,
|
|
||||||
forceClose: isForce,
|
|
||||||
...(isType !== 'none'
|
|
||||||
? isType === 'fee'
|
|
||||||
? { tokens: amount }
|
|
||||||
: { target: amount }
|
|
||||||
: {}),
|
|
||||||
}}
|
|
||||||
color={'red'}
|
|
||||||
disabled={false}
|
|
||||||
withMargin={'4px'}
|
|
||||||
>
|
|
||||||
{`Close Channel [ ${channelName}/${channelId} ]`}
|
|
||||||
</SecureButton>
|
|
||||||
<ColorButton withMargin={'4px'} onClick={handleOnlyClose}>
|
|
||||||
Cancel
|
|
||||||
</ColorButton>
|
|
||||||
</WarningCard>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderContent = () => (
|
|
||||||
<>
|
|
||||||
<SingleLine>
|
|
||||||
<SubTitle>{`Close Channel`}</SubTitle>
|
|
||||||
<Sub4Title>{`${channelName} [${channelId}]`}</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<Separation />
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Fee:</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<MultiButton>
|
|
||||||
{renderButton(
|
|
||||||
() => setIsType('none'),
|
|
||||||
'Auto',
|
|
||||||
isType === 'none',
|
|
||||||
)}
|
|
||||||
{renderButton(() => setIsType('fee'), 'Fee', isType === 'fee')}
|
|
||||||
{renderButton(
|
|
||||||
() => setIsType('target'),
|
|
||||||
'Target',
|
|
||||||
isType === 'target',
|
|
||||||
)}
|
|
||||||
</MultiButton>
|
|
||||||
{isType === 'none' && (
|
|
||||||
<>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Fee Amount:</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<MultiButton>
|
|
||||||
{renderButton(
|
|
||||||
() => setAmount(fast),
|
|
||||||
`Fastest (${fast} sats)`,
|
|
||||||
amount === fast,
|
|
||||||
)}
|
|
||||||
{halfHour !== fast &&
|
|
||||||
renderButton(
|
|
||||||
() => setAmount(halfHour),
|
|
||||||
`Half Hour (${halfHour} sats)`,
|
|
||||||
amount === halfHour,
|
|
||||||
)}
|
|
||||||
{renderButton(
|
|
||||||
() => setAmount(hour),
|
|
||||||
`Hour (${hour} sats)`,
|
|
||||||
amount === hour,
|
|
||||||
)}
|
|
||||||
</MultiButton>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{isType !== 'none' && (
|
|
||||||
<>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>
|
|
||||||
{isType === 'target'
|
|
||||||
? 'Target Blocks:'
|
|
||||||
: 'Fee (Sats/Byte)'}
|
|
||||||
</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<Input
|
|
||||||
placeholder={
|
|
||||||
isType === 'target' ? 'Blocks' : 'Sats/Byte'
|
|
||||||
}
|
|
||||||
type={'number'}
|
|
||||||
onChange={(e) =>
|
|
||||||
setAmount(parseInt(e.target.value))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</SingleLine>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Force Close Channel:</Sub4Title>
|
|
||||||
</SingleLine>
|
|
||||||
<MultiButton>
|
|
||||||
{renderButton(() => setIsForce(true), `Yes`, isForce)}
|
|
||||||
{renderButton(() => setIsForce(false), `No`, !isForce)}
|
|
||||||
</MultiButton>
|
|
||||||
<Separation />
|
|
||||||
<CenterLine>
|
|
||||||
<ColorButton
|
|
||||||
withMargin={'4px'}
|
|
||||||
withBorder={true}
|
|
||||||
onClick={handleOnlyClose}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</ColorButton>
|
|
||||||
<ColorButton
|
|
||||||
arrow={true}
|
|
||||||
withMargin={'4px'}
|
|
||||||
withBorder={true}
|
|
||||||
color={'red'}
|
|
||||||
onClick={() => setIsConfirmed(true)}
|
|
||||||
>
|
|
||||||
Close Channel
|
|
||||||
</ColorButton>
|
|
||||||
</CenterLine>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return isConfirmed ? renderWarning() : renderContent();
|
|
||||||
};
|
|
|
@ -1,62 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { REMOVE_PEER } from '../../../graphql/mutation';
|
|
||||||
import { useMutation } from '@apollo/react-hooks';
|
|
||||||
import { SubTitle } from '../../generic/Styled';
|
|
||||||
import { AlertTriangle } from '../../generic/Icons';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { getErrorContent } from '../../../utils/error';
|
|
||||||
import { SecureButton } from '../../buttons/secureButton/SecureButton';
|
|
||||||
import { ColorButton } from '../../buttons/colorButton/ColorButton';
|
|
||||||
|
|
||||||
interface RemovePeerProps {
|
|
||||||
setModalOpen: (status: boolean) => void;
|
|
||||||
publicKey: string;
|
|
||||||
peerAlias: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WarningCard = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RemovePeerModal = ({
|
|
||||||
setModalOpen,
|
|
||||||
publicKey,
|
|
||||||
peerAlias,
|
|
||||||
}: RemovePeerProps) => {
|
|
||||||
const [removePeer, { loading }] = useMutation(REMOVE_PEER, {
|
|
||||||
onCompleted: (data) => {
|
|
||||||
toast.success('Peer Removed');
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(getErrorContent(error));
|
|
||||||
},
|
|
||||||
refetchQueries: ['GetPeers'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleOnlyClose = () => setModalOpen(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WarningCard>
|
|
||||||
<AlertTriangle size={'32px'} color={'red'} />
|
|
||||||
<SubTitle>Are you sure you want to remove this peer?</SubTitle>
|
|
||||||
<SecureButton
|
|
||||||
callback={removePeer}
|
|
||||||
variables={{
|
|
||||||
publicKey: publicKey,
|
|
||||||
}}
|
|
||||||
color={'red'}
|
|
||||||
disabled={loading}
|
|
||||||
withMargin={'4px'}
|
|
||||||
>
|
|
||||||
{`Remove Peer [${peerAlias ?? 'Unknown'}]`}
|
|
||||||
</SecureButton>
|
|
||||||
<ColorButton withMargin={'4px'} onClick={handleOnlyClose}>
|
|
||||||
Cancel
|
|
||||||
</ColorButton>
|
|
||||||
</WarningCard>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,91 +0,0 @@
|
||||||
import React, { useRef } from 'react';
|
|
||||||
import { useAccount } from 'context/AccountContext';
|
|
||||||
import { NodeCard } from './NodeCard';
|
|
||||||
import { CardWithTitle, SubTitle } from 'components/generic/Styled';
|
|
||||||
import {
|
|
||||||
ArrowLeft,
|
|
||||||
ArrowRight,
|
|
||||||
StyledNodeBar,
|
|
||||||
NodeBarContainer,
|
|
||||||
} from './NodeInfo.styled';
|
|
||||||
import { QuestionIcon } from 'components/generic/Icons';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import ReactTooltip from 'react-tooltip';
|
|
||||||
import { useSettings } from 'context/SettingsContext';
|
|
||||||
import { getTooltipType } from 'components/generic/Helpers';
|
|
||||||
|
|
||||||
const StyledQuestion = styled(QuestionIcon)`
|
|
||||||
margin-left: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const NodeBar = () => {
|
|
||||||
const { accounts } = useAccount();
|
|
||||||
const { nodeInfo } = useSettings();
|
|
||||||
const slider = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const { theme } = useSettings();
|
|
||||||
const tooltipType = getTooltipType(theme);
|
|
||||||
|
|
||||||
const viewOnlyAccounts = accounts.filter(
|
|
||||||
(account) => account.viewOnly !== '',
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleScroll = (decrease?: boolean) => {
|
|
||||||
if (slider.current !== null) {
|
|
||||||
if (decrease) {
|
|
||||||
slider.current.scrollLeft -= 240;
|
|
||||||
} else {
|
|
||||||
slider.current.scrollLeft += 240;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (viewOnlyAccounts.length <= 1 || !nodeInfo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CardWithTitle>
|
|
||||||
<SubTitle>
|
|
||||||
Your Nodes
|
|
||||||
<span data-tip data-for="node_info_question">
|
|
||||||
<StyledQuestion size={'14px'} />
|
|
||||||
</span>
|
|
||||||
</SubTitle>
|
|
||||||
<NodeBarContainer>
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
handleScroll(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowLeft />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
handleScroll();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowRight />
|
|
||||||
</div>
|
|
||||||
<StyledNodeBar ref={slider}>
|
|
||||||
{viewOnlyAccounts.map((account, index) => (
|
|
||||||
<div key={account.id}>
|
|
||||||
<NodeCard
|
|
||||||
account={account}
|
|
||||||
accountId={account.id}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</StyledNodeBar>
|
|
||||||
</NodeBarContainer>
|
|
||||||
<ReactTooltip
|
|
||||||
id={'node_info_question'}
|
|
||||||
effect={'solid'}
|
|
||||||
place={'right'}
|
|
||||||
type={tooltipType}
|
|
||||||
>
|
|
||||||
Only accounts with a view-only macaroon will appear here.
|
|
||||||
</ReactTooltip>
|
|
||||||
</CardWithTitle>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,144 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { useInView } from 'react-intersection-observer';
|
|
||||||
import 'intersection-observer'; // Polyfill
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import { GET_NODE_INFO } from 'graphql/query';
|
|
||||||
import {
|
|
||||||
SingleLine,
|
|
||||||
DarkSubTitle,
|
|
||||||
ResponsiveLine,
|
|
||||||
} from 'components/generic/Styled';
|
|
||||||
import { themeColors } from '../../styles/Themes';
|
|
||||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
|
||||||
import { Price } from 'components/price/Price';
|
|
||||||
import Modal from '../modal/ReactModal';
|
|
||||||
import { StatusDot, StatusLine, QuickCard } from './NodeInfo.styled';
|
|
||||||
import { NodeInfoModal } from './NodeInfoModal';
|
|
||||||
|
|
||||||
export const getStatusDot = (status: boolean) => {
|
|
||||||
return status ? (
|
|
||||||
<StatusDot color="#95de64" />
|
|
||||||
) : (
|
|
||||||
<StatusDot color="#ff4d4f" />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface NodeCardProps {
|
|
||||||
account: any;
|
|
||||||
accountId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NodeCard = ({ account, accountId }: NodeCardProps) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
const { host, viewOnly, cert } = account;
|
|
||||||
const [ref, inView] = useInView({
|
|
||||||
threshold: 0,
|
|
||||||
triggerOnce: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const auth = {
|
|
||||||
host,
|
|
||||||
macaroon: viewOnly,
|
|
||||||
cert,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data, loading, error } = useQuery(GET_NODE_INFO, {
|
|
||||||
variables: { auth },
|
|
||||||
skip: !inView,
|
|
||||||
pollInterval: 10000,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderContent = () => {
|
|
||||||
if (!inView) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StatusLine>{getStatusDot(false)}</StatusLine>
|
|
||||||
<div>-</div>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Lightning</DarkSubTitle>
|
|
||||||
<div>-</div>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Bitcoin</DarkSubTitle>
|
|
||||||
<div>-</div>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Channels</DarkSubTitle>
|
|
||||||
<div>-</div>
|
|
||||||
</SingleLine>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
loading ||
|
|
||||||
!data ||
|
|
||||||
!data.getNodeInfo ||
|
|
||||||
!data.getChannelBalance
|
|
||||||
) {
|
|
||||||
return <ScaleLoader height={20} color={themeColors.blue3} />;
|
|
||||||
} else {
|
|
||||||
const {
|
|
||||||
active_channels_count,
|
|
||||||
closed_channels_count,
|
|
||||||
alias,
|
|
||||||
pending_channels_count,
|
|
||||||
is_synced_to_chain,
|
|
||||||
} = data.getNodeInfo;
|
|
||||||
|
|
||||||
const { confirmedBalance, pendingBalance } = data.getChannelBalance;
|
|
||||||
|
|
||||||
const chainBalance = data.getChainBalance;
|
|
||||||
const pendingChainBalance = data.getPendingChainBalance;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StatusLine>{getStatusDot(is_synced_to_chain)}</StatusLine>
|
|
||||||
<div>{alias}</div>
|
|
||||||
<ResponsiveLine>
|
|
||||||
<DarkSubTitle>Lightning</DarkSubTitle>
|
|
||||||
<Price amount={confirmedBalance + pendingBalance} />
|
|
||||||
</ResponsiveLine>
|
|
||||||
<ResponsiveLine>
|
|
||||||
<DarkSubTitle>Bitcoin</DarkSubTitle>
|
|
||||||
<Price amount={chainBalance + pendingChainBalance} />
|
|
||||||
</ResponsiveLine>
|
|
||||||
<ResponsiveLine>
|
|
||||||
<DarkSubTitle>Channels</DarkSubTitle>
|
|
||||||
<div>{`${active_channels_count} / ${pending_channels_count} / ${closed_channels_count}`}</div>
|
|
||||||
</ResponsiveLine>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<QuickCard
|
|
||||||
onClick={() => {
|
|
||||||
setIsOpen(true);
|
|
||||||
}}
|
|
||||||
ref={ref}
|
|
||||||
key={account.id}
|
|
||||||
>
|
|
||||||
{renderContent()}
|
|
||||||
</QuickCard>
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
closeCallback={() => {
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<NodeInfoModal
|
|
||||||
account={{
|
|
||||||
...data,
|
|
||||||
}}
|
|
||||||
accountId={accountId}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,105 +0,0 @@
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import { Card } from 'components/generic/Styled';
|
|
||||||
import { ChevronLeft, ChevronRight } from 'components/generic/Icons';
|
|
||||||
import {
|
|
||||||
inverseTextColor,
|
|
||||||
buttonBorderColor,
|
|
||||||
textColor,
|
|
||||||
mediaWidths,
|
|
||||||
} from 'styles/Themes';
|
|
||||||
|
|
||||||
const arrowCSS = css`
|
|
||||||
background-color: ${inverseTextColor};
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
top: 50%;
|
|
||||||
display: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.1);
|
|
||||||
border: 1px solid ${buttonBorderColor};
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid ${textColor};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ArrowLeft = styled(ChevronLeft)`
|
|
||||||
${arrowCSS}
|
|
||||||
transform: translate(-30%, -50%);
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ArrowRight = styled(ChevronRight)`
|
|
||||||
${arrowCSS}
|
|
||||||
transform: translate(30%, -50%);
|
|
||||||
right: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const NodeBarContainer = styled.div`
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
&:hover {
|
|
||||||
${ArrowLeft} {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
${ArrowRight} {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const StyledNodeBar = styled.div`
|
|
||||||
display: flex;
|
|
||||||
overflow-x: scroll;
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
cursor: pointer;
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const sectionColor = '#69c0ff';
|
|
||||||
|
|
||||||
export const QuickCard = styled(Card)`
|
|
||||||
height: 120px;
|
|
||||||
width: 240px;
|
|
||||||
min-width: 240px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: stretch;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
padding: 10px;
|
|
||||||
margin-right: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
height: unset;
|
|
||||||
width: 160px;
|
|
||||||
min-width: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid ${sectionColor};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const StatusLine = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
right: -8px;
|
|
||||||
top: -8px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin: 0 0 -8px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const StatusDot = styled.div`
|
|
||||||
margin: 0 2px;
|
|
||||||
height: 8px;
|
|
||||||
width: 8px;
|
|
||||||
border-radius: 100%;
|
|
||||||
background-color: ${({ color }: { color: string }) => color};
|
|
||||||
`;
|
|
|
@ -1,102 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
SubTitle,
|
|
||||||
SingleLine,
|
|
||||||
DarkSubTitle,
|
|
||||||
Sub4Title,
|
|
||||||
Separation,
|
|
||||||
} from 'components/generic/Styled';
|
|
||||||
import { Price } from 'components/price/Price';
|
|
||||||
import { ColorButton } from 'components/buttons/colorButton/ColorButton';
|
|
||||||
import { useConnectionDispatch } from 'context/ConnectionContext';
|
|
||||||
import { useStatusDispatch } from 'context/StatusContext';
|
|
||||||
import { useAccount } from 'context/AccountContext';
|
|
||||||
|
|
||||||
interface NodeInfoModalProps {
|
|
||||||
account: any;
|
|
||||||
accountId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NodeInfoModal = ({ account, accountId }: NodeInfoModalProps) => {
|
|
||||||
const dispatch = useConnectionDispatch();
|
|
||||||
const dispatchState = useStatusDispatch();
|
|
||||||
|
|
||||||
const { changeAccount } = useAccount();
|
|
||||||
|
|
||||||
const {
|
|
||||||
active_channels_count,
|
|
||||||
closed_channels_count,
|
|
||||||
alias,
|
|
||||||
pending_channels_count,
|
|
||||||
is_synced_to_chain,
|
|
||||||
peers_count,
|
|
||||||
version,
|
|
||||||
} = account.getNodeInfo;
|
|
||||||
|
|
||||||
const { confirmedBalance, pendingBalance } = account.getChannelBalance;
|
|
||||||
|
|
||||||
const chainBalance = account.getChainBalance;
|
|
||||||
const pendingChainBalance = account.getPendingChainBalance;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SubTitle>{alias}</SubTitle>
|
|
||||||
<Separation />
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Version:</DarkSubTitle>
|
|
||||||
<div>{version.split(' ')[0]}</div>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Is Synced:</DarkSubTitle>
|
|
||||||
<div>{is_synced_to_chain ? 'True' : 'False'}</div>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Peer Count:</DarkSubTitle>
|
|
||||||
<div>{peers_count}</div>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Active Channels:</DarkSubTitle>
|
|
||||||
<div>{active_channels_count}</div>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Pending Channels:</DarkSubTitle>
|
|
||||||
<div>{pending_channels_count}</div>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Closed Channels:</DarkSubTitle>
|
|
||||||
<div>{closed_channels_count}</div>
|
|
||||||
</SingleLine>
|
|
||||||
<Sub4Title>Lightning</Sub4Title>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Balance:</DarkSubTitle>
|
|
||||||
<Price amount={confirmedBalance} />
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Pending:</DarkSubTitle>
|
|
||||||
<Price amount={pendingBalance} />
|
|
||||||
</SingleLine>
|
|
||||||
<Sub4Title>Bitcoin</Sub4Title>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Balance:</DarkSubTitle>
|
|
||||||
<Price amount={chainBalance} />
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<DarkSubTitle>Pending:</DarkSubTitle>
|
|
||||||
<Price amount={pendingChainBalance} />
|
|
||||||
</SingleLine>
|
|
||||||
<ColorButton
|
|
||||||
withMargin={'16px 0 0'}
|
|
||||||
fullWidth={true}
|
|
||||||
onClick={() => {
|
|
||||||
dispatch({ type: 'disconnected' });
|
|
||||||
dispatchState({
|
|
||||||
type: 'disconnected',
|
|
||||||
});
|
|
||||||
changeAccount(accountId);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Change to this Account
|
|
||||||
</ColorButton>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,86 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { useSettings } from 'context/SettingsContext';
|
|
||||||
import { getValue } from 'helpers/Helpers';
|
|
||||||
import { usePriceState } from 'context/PriceContext';
|
|
||||||
|
|
||||||
type PriceProps = {
|
|
||||||
price: number;
|
|
||||||
symbol: string;
|
|
||||||
currency: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Price = ({
|
|
||||||
amount,
|
|
||||||
breakNumber = false,
|
|
||||||
}: {
|
|
||||||
amount: number;
|
|
||||||
breakNumber?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { currency } = useSettings();
|
|
||||||
const { prices, loading, error } = usePriceState();
|
|
||||||
|
|
||||||
let priceProps: PriceProps = {
|
|
||||||
price: 0,
|
|
||||||
symbol: '',
|
|
||||||
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (prices && !loading && !error) {
|
|
||||||
const current: { last: number; symbol: string } = prices[currency] ?? {
|
|
||||||
last: 0,
|
|
||||||
symbol: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
priceProps = {
|
|
||||||
price: current.last,
|
|
||||||
symbol: current.symbol,
|
|
||||||
currency,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const getFormat = (amount: number) =>
|
|
||||||
getValue({ amount, ...priceProps, breakNumber });
|
|
||||||
|
|
||||||
return <>{getFormat(amount)}</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPrice = (
|
|
||||||
currency: string,
|
|
||||||
priceContext: {
|
|
||||||
error: boolean;
|
|
||||||
loading: boolean;
|
|
||||||
prices?: { [key: string]: { last: number; symbol: string } };
|
|
||||||
},
|
|
||||||
) => ({
|
|
||||||
amount,
|
|
||||||
breakNumber = false,
|
|
||||||
}: {
|
|
||||||
amount: number;
|
|
||||||
breakNumber?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { prices, loading, error } = priceContext;
|
|
||||||
|
|
||||||
let priceProps: PriceProps = {
|
|
||||||
price: 0,
|
|
||||||
symbol: '',
|
|
||||||
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (prices && !loading && !error) {
|
|
||||||
const current: { last: number; symbol: string } = prices[currency] ?? {
|
|
||||||
last: 0,
|
|
||||||
symbol: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
priceProps = {
|
|
||||||
price: current.last,
|
|
||||||
symbol: current.symbol,
|
|
||||||
currency,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const getFormat = (amount: number) =>
|
|
||||||
getValue({ amount, ...priceProps, breakNumber });
|
|
||||||
|
|
||||||
return getFormat(amount);
|
|
||||||
};
|
|
|
@ -1,20 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { number } from '@storybook/addon-knobs';
|
|
||||||
import { Rating } from './Rating';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Ratings',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = () => {
|
|
||||||
const options = {
|
|
||||||
range: true,
|
|
||||||
min: 0,
|
|
||||||
max: 10,
|
|
||||||
step: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const rating = number('Rating', 5, options);
|
|
||||||
|
|
||||||
return <Rating rating={rating / 10} />;
|
|
||||||
};
|
|
|
@ -1,62 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Star, HalfStar } from '../../components/generic/Icons';
|
|
||||||
import { themeColors } from '../../styles/Themes';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const StyledStar = styled(Star)`
|
|
||||||
margin-bottom: -1px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledHalfStar = styled(HalfStar)`
|
|
||||||
margin-bottom: -1px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledRatings = styled.div`
|
|
||||||
display: flex;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface RatingProps {
|
|
||||||
rating: number | null;
|
|
||||||
size?: string;
|
|
||||||
color?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Rating = ({
|
|
||||||
rating,
|
|
||||||
size = '14px',
|
|
||||||
color = themeColors.blue3,
|
|
||||||
}: RatingProps) => {
|
|
||||||
if (!rating) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const correctRating = Math.min(Math.max(Math.round(rating * 10), 0), 10);
|
|
||||||
|
|
||||||
const amount = (correctRating - (correctRating % 2)) / 2;
|
|
||||||
const hasHalf = correctRating % 2 > 0 ? true : false;
|
|
||||||
|
|
||||||
let stars = [];
|
|
||||||
|
|
||||||
const starConfig = {
|
|
||||||
size,
|
|
||||||
color,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
if (i < amount) {
|
|
||||||
stars.push(
|
|
||||||
<StyledStar
|
|
||||||
key={i}
|
|
||||||
{...starConfig}
|
|
||||||
fillcolor={themeColors.blue3}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
} else if (hasHalf && i === amount) {
|
|
||||||
stars.push(<StyledHalfStar key={i} {...starConfig} />);
|
|
||||||
} else {
|
|
||||||
stars.push(<StyledStar key={i} {...starConfig} />);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <StyledRatings>{stars.map((star) => star)}</StyledRatings>;
|
|
||||||
};
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
export const ScrollToTop = () => {
|
|
||||||
let history = useHistory();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unlisten = history.listen(() => {
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
unlisten();
|
|
||||||
};
|
|
||||||
}, [history]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
|
@ -1,69 +0,0 @@
|
||||||
import React, { ReactNode } from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import { ThemeSet } from 'styled-theming';
|
|
||||||
import { backgroundColor, mediaWidths } from 'styles/Themes';
|
|
||||||
|
|
||||||
interface FullWidthProps {
|
|
||||||
padding?: string;
|
|
||||||
withColor?: boolean;
|
|
||||||
sectionColor?: string | ThemeSet;
|
|
||||||
textColor?: string | ThemeSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FullWidth = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
${({ padding }: FullWidthProps) =>
|
|
||||||
padding &&
|
|
||||||
css`
|
|
||||||
padding: ${padding};
|
|
||||||
`}
|
|
||||||
${({ textColor }: FullWidthProps) =>
|
|
||||||
textColor &&
|
|
||||||
css`
|
|
||||||
color: ${textColor};
|
|
||||||
`}
|
|
||||||
background-color: ${({ withColor, sectionColor }: FullWidthProps) =>
|
|
||||||
withColor && (sectionColor ? sectionColor : backgroundColor)};
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
padding: 16px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FixedWidth = styled.div`
|
|
||||||
max-width: 1000px;
|
|
||||||
margin: 0 auto 0 auto;
|
|
||||||
|
|
||||||
@media (max-width: 1035px) {
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Section = ({
|
|
||||||
fixedWidth = true,
|
|
||||||
withColor = true,
|
|
||||||
children,
|
|
||||||
color,
|
|
||||||
textColor,
|
|
||||||
padding,
|
|
||||||
}: {
|
|
||||||
fixedWidth?: boolean;
|
|
||||||
withColor?: boolean;
|
|
||||||
color?: any;
|
|
||||||
textColor?: any;
|
|
||||||
padding?: string;
|
|
||||||
children: ReactNode;
|
|
||||||
}) => {
|
|
||||||
const Fixed = fixedWidth ? FixedWidth : React.Fragment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FullWidth
|
|
||||||
padding={padding}
|
|
||||||
withColor={withColor}
|
|
||||||
sectionColor={color}
|
|
||||||
textColor={textColor}
|
|
||||||
>
|
|
||||||
<Fixed>{children}</Fixed>
|
|
||||||
</FullWidth>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,68 +0,0 @@
|
||||||
import { useConnectionState } from 'context/ConnectionContext';
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import { GET_NODE_INFO } from 'graphql/query';
|
|
||||||
import { useAccount } from 'context/AccountContext';
|
|
||||||
import { useStatusDispatch } from 'context/StatusContext';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { getErrorContent } from 'utils/error';
|
|
||||||
|
|
||||||
export const StatusCheck = () => {
|
|
||||||
const { connected } = useConnectionState();
|
|
||||||
const dispatch = useStatusDispatch();
|
|
||||||
|
|
||||||
const { loggedIn, host, viewOnly, cert, sessionAdmin } = useAccount();
|
|
||||||
const auth = {
|
|
||||||
host,
|
|
||||||
macaroon: viewOnly !== '' ? viewOnly : sessionAdmin,
|
|
||||||
cert,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data, loading, error, stopPolling } = useQuery(GET_NODE_INFO, {
|
|
||||||
variables: { auth },
|
|
||||||
skip: !connected || !loggedIn,
|
|
||||||
pollInterval: 10000,
|
|
||||||
onError: (error) => toast.error(getErrorContent(error)),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!connected || !loggedIn) {
|
|
||||||
stopPolling();
|
|
||||||
}
|
|
||||||
}, [connected, loggedIn, stopPolling]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data && !loading && !error) {
|
|
||||||
const {
|
|
||||||
getChainBalance,
|
|
||||||
getPendingChainBalance,
|
|
||||||
getChannelBalance,
|
|
||||||
getNodeInfo,
|
|
||||||
} = data;
|
|
||||||
const { alias, is_synced_to_chain, version } = getNodeInfo;
|
|
||||||
const { confirmedBalance, pendingBalance } = getChannelBalance;
|
|
||||||
|
|
||||||
const versionNumber = version.split(' ');
|
|
||||||
const onlyVersion = versionNumber[0].split('-');
|
|
||||||
const numbers = onlyVersion[0].split('.');
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
loading: false,
|
|
||||||
alias,
|
|
||||||
syncedToChain: is_synced_to_chain,
|
|
||||||
version: versionNumber[0],
|
|
||||||
mayorVersion: numbers[0],
|
|
||||||
minorVersion: numbers[1],
|
|
||||||
revision: numbers[2],
|
|
||||||
chainBalance: getChainBalance,
|
|
||||||
chainPending: getPendingChainBalance,
|
|
||||||
channelBalance: confirmedBalance,
|
|
||||||
channelPending: pendingBalance,
|
|
||||||
};
|
|
||||||
|
|
||||||
dispatch({ type: 'connected', state });
|
|
||||||
}
|
|
||||||
}, [data, dispatch, error, loading]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
|
@ -1,202 +0,0 @@
|
||||||
import React, { createContext, useState, useContext } from 'react';
|
|
||||||
import merge from 'lodash.merge';
|
|
||||||
import { getAuth } from 'utils/auth';
|
|
||||||
import { saveAccounts } from 'utils/storage';
|
|
||||||
|
|
||||||
interface SingleAccountProps {
|
|
||||||
name: string;
|
|
||||||
host: string;
|
|
||||||
admin: string;
|
|
||||||
viewOnly: string;
|
|
||||||
cert: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChangeProps {
|
|
||||||
loggedIn?: boolean;
|
|
||||||
name?: string;
|
|
||||||
host?: string;
|
|
||||||
admin?: string;
|
|
||||||
sessionAdmin?: string;
|
|
||||||
viewOnly?: string;
|
|
||||||
cert?: string;
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AccountProps {
|
|
||||||
loggedIn: boolean;
|
|
||||||
name: string;
|
|
||||||
host: string;
|
|
||||||
admin: string;
|
|
||||||
sessionAdmin: string;
|
|
||||||
viewOnly: string;
|
|
||||||
cert: string;
|
|
||||||
id: string;
|
|
||||||
accounts: SingleAccountProps[];
|
|
||||||
setAccount: (newProps: ChangeProps) => void;
|
|
||||||
changeAccount: (account: string) => void;
|
|
||||||
deleteAccount: (account: string) => void;
|
|
||||||
refreshAccount: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AccountContext = createContext<AccountProps>({
|
|
||||||
loggedIn: false,
|
|
||||||
name: '',
|
|
||||||
host: '',
|
|
||||||
admin: '',
|
|
||||||
sessionAdmin: '',
|
|
||||||
viewOnly: '',
|
|
||||||
cert: '',
|
|
||||||
id: '',
|
|
||||||
accounts: [],
|
|
||||||
setAccount: () => {},
|
|
||||||
changeAccount: () => {},
|
|
||||||
deleteAccount: () => {},
|
|
||||||
refreshAccount: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const AccountProvider = ({ children }: any) => {
|
|
||||||
const sessionAdmin = sessionStorage.getItem('session') || '';
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
id,
|
|
||||||
accounts,
|
|
||||||
loggedIn,
|
|
||||||
} = getAuth();
|
|
||||||
|
|
||||||
const setAccount = ({
|
|
||||||
loggedIn,
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
sessionAdmin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
id,
|
|
||||||
}: ChangeProps) => {
|
|
||||||
updateAccount((prevState: any) => {
|
|
||||||
const newState = { ...prevState };
|
|
||||||
return merge(newState, {
|
|
||||||
loggedIn,
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
sessionAdmin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeAccount = (changeToId: string) => {
|
|
||||||
const currentAccounts = JSON.parse(
|
|
||||||
localStorage.getItem('accounts') || '[]',
|
|
||||||
);
|
|
||||||
const index = currentAccounts.findIndex(
|
|
||||||
(account: any) => account.id === changeToId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index < 0) return;
|
|
||||||
|
|
||||||
sessionStorage.removeItem('session');
|
|
||||||
localStorage.setItem('active', `${index}`);
|
|
||||||
|
|
||||||
refreshAccount(`${index}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteAccount = (deleteId: string) => {
|
|
||||||
const currentAccounts = JSON.parse(
|
|
||||||
localStorage.getItem('accounts') || '[]',
|
|
||||||
);
|
|
||||||
const current = currentAccounts.find(
|
|
||||||
(account: any) => account.id === deleteId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!current) return;
|
|
||||||
|
|
||||||
const isCurrentAccount = current.id === id;
|
|
||||||
|
|
||||||
const changedAccounts = [...currentAccounts].filter(
|
|
||||||
(account) => account.id !== deleteId,
|
|
||||||
);
|
|
||||||
const length = changedAccounts.length;
|
|
||||||
|
|
||||||
if (isCurrentAccount) {
|
|
||||||
sessionStorage.removeItem('session');
|
|
||||||
localStorage.setItem('active', `${length - 1}`);
|
|
||||||
} else {
|
|
||||||
const newIndex = changedAccounts.findIndex(
|
|
||||||
(account: any) => account.id === id,
|
|
||||||
);
|
|
||||||
localStorage.setItem('active', `${newIndex}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveAccounts(changedAccounts);
|
|
||||||
|
|
||||||
refreshAccount();
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshAccount = (account?: string) => {
|
|
||||||
const sessionAdmin = sessionStorage.getItem('session') || '';
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
id,
|
|
||||||
accounts,
|
|
||||||
loggedIn,
|
|
||||||
} = getAuth(account);
|
|
||||||
|
|
||||||
updateAccount((prevState: any) => {
|
|
||||||
const newState = { ...prevState };
|
|
||||||
|
|
||||||
const merged = merge(newState, {
|
|
||||||
loggedIn,
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
sessionAdmin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { ...merged, accounts };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const accountState = {
|
|
||||||
loggedIn,
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
sessionAdmin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
id,
|
|
||||||
accounts,
|
|
||||||
setAccount,
|
|
||||||
changeAccount,
|
|
||||||
deleteAccount,
|
|
||||||
refreshAccount,
|
|
||||||
};
|
|
||||||
|
|
||||||
const [settings, updateAccount] = useState(accountState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AccountContext.Provider value={settings}>
|
|
||||||
{children}
|
|
||||||
</AccountContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useAccount = () => useContext(AccountContext);
|
|
||||||
|
|
||||||
export { AccountProvider, useAccount };
|
|
|
@ -1,76 +0,0 @@
|
||||||
import React, { createContext, useContext, useReducer } from 'react';
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
loading: boolean;
|
|
||||||
error: boolean;
|
|
||||||
fast: number;
|
|
||||||
halfHour: number;
|
|
||||||
hour: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ActionType = {
|
|
||||||
type: 'fetched' | 'error';
|
|
||||||
state?: State;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Dispatch = (action: ActionType) => void;
|
|
||||||
|
|
||||||
export const StateContext = createContext<State | undefined>(undefined);
|
|
||||||
export const DispatchContext = createContext<Dispatch | undefined>(undefined);
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
loading: true,
|
|
||||||
error: false,
|
|
||||||
fast: 0,
|
|
||||||
halfHour: 0,
|
|
||||||
hour: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const stateReducer = (state: State, action: ActionType): State => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'fetched':
|
|
||||||
return action.state || initialState;
|
|
||||||
case 'error':
|
|
||||||
return {
|
|
||||||
...initialState,
|
|
||||||
loading: false,
|
|
||||||
error: true,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return initialState;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const BitcoinInfoProvider = ({ children }: any) => {
|
|
||||||
const [state, dispatch] = useReducer(stateReducer, initialState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DispatchContext.Provider value={dispatch}>
|
|
||||||
<StateContext.Provider value={state}>
|
|
||||||
{children}
|
|
||||||
</StateContext.Provider>
|
|
||||||
</DispatchContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useBitcoinState = () => {
|
|
||||||
const context = useContext(StateContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
'useBitcoinState must be used within a BitcoinInfoProvider',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useBitcoinDispatch = () => {
|
|
||||||
const context = useContext(DispatchContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
'useBitcoinDispatch must be used within a BitcoinInfoProvider',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { BitcoinInfoProvider, useBitcoinState, useBitcoinDispatch };
|
|
|
@ -1,68 +0,0 @@
|
||||||
import React, { createContext, useContext, useReducer } from 'react';
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
connected: boolean;
|
|
||||||
loading: boolean;
|
|
||||||
error: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ActionType = {
|
|
||||||
type: 'connected' | 'loading' | 'error' | 'disconnected';
|
|
||||||
};
|
|
||||||
|
|
||||||
type Dispatch = (action: ActionType) => void;
|
|
||||||
|
|
||||||
const StateContext = createContext<State | undefined>(undefined);
|
|
||||||
const DispatchContext = createContext<Dispatch | undefined>(undefined);
|
|
||||||
|
|
||||||
const stateReducer = (state: State, action: ActionType) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'connected':
|
|
||||||
return { connected: true, loading: false, error: false };
|
|
||||||
case 'loading':
|
|
||||||
case 'disconnected':
|
|
||||||
return { connected: false, loading: true, error: false };
|
|
||||||
default:
|
|
||||||
return { connected: false, loading: false, error: true };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
connected: false,
|
|
||||||
loading: true,
|
|
||||||
error: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConnectionProvider = ({ children }: any) => {
|
|
||||||
const [state, dispatch] = useReducer(stateReducer, initialState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StateContext.Provider value={state}>
|
|
||||||
<DispatchContext.Provider value={dispatch}>
|
|
||||||
{children}
|
|
||||||
</DispatchContext.Provider>
|
|
||||||
</StateContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useConnectionState = () => {
|
|
||||||
const context = useContext(StateContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
'useConnectionState must be used within a ConnectionProvider',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useConnectionDispatch = () => {
|
|
||||||
const context = useContext(DispatchContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
'useConnectionDispatch must be used within a ConnectionProvider',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { ConnectionProvider, useConnectionState, useConnectionDispatch };
|
|
|
@ -1,73 +0,0 @@
|
||||||
import React, { createContext, useContext, useReducer } from 'react';
|
|
||||||
|
|
||||||
type PriceProps = {
|
|
||||||
last: number;
|
|
||||||
symbol: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
loading: boolean;
|
|
||||||
error: boolean;
|
|
||||||
prices?: { [key: string]: PriceProps };
|
|
||||||
};
|
|
||||||
|
|
||||||
type ActionType = {
|
|
||||||
type: 'fetched' | 'error';
|
|
||||||
state?: State;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Dispatch = (action: ActionType) => void;
|
|
||||||
|
|
||||||
export const StateContext = createContext<State | undefined>(undefined);
|
|
||||||
export const DispatchContext = createContext<Dispatch | undefined>(undefined);
|
|
||||||
|
|
||||||
const initialState: State = {
|
|
||||||
loading: true,
|
|
||||||
error: false,
|
|
||||||
prices: { EUR: { last: 0, symbol: '€' } },
|
|
||||||
};
|
|
||||||
|
|
||||||
const stateReducer = (state: State, action: ActionType): State => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'fetched':
|
|
||||||
return action.state || initialState;
|
|
||||||
case 'error':
|
|
||||||
return {
|
|
||||||
...initialState,
|
|
||||||
loading: false,
|
|
||||||
error: true,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return initialState;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const PriceProvider = ({ children }: any) => {
|
|
||||||
const [state, dispatch] = useReducer(stateReducer, initialState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DispatchContext.Provider value={dispatch}>
|
|
||||||
<StateContext.Provider value={state}>
|
|
||||||
{children}
|
|
||||||
</StateContext.Provider>
|
|
||||||
</DispatchContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const usePriceState = () => {
|
|
||||||
const context = useContext(StateContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('usePriceState must be used within a PriceProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
const usePriceDispatch = () => {
|
|
||||||
const context = useContext(DispatchContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('usePriceDispatch must be used within a PriceProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { PriceProvider, usePriceState, usePriceDispatch };
|
|
|
@ -1,90 +0,0 @@
|
||||||
import React, { createContext, useState, useContext } from 'react';
|
|
||||||
import merge from 'lodash.merge';
|
|
||||||
|
|
||||||
interface ChangeProps {
|
|
||||||
theme?: string;
|
|
||||||
sidebar?: boolean;
|
|
||||||
currency?: string;
|
|
||||||
nodeInfo?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SettingsProps {
|
|
||||||
currency: string;
|
|
||||||
theme: string;
|
|
||||||
sidebar: boolean;
|
|
||||||
nodeInfo: boolean;
|
|
||||||
setSettings: (newProps: ChangeProps) => void;
|
|
||||||
refreshSettings: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SettingsContext = createContext<SettingsProps>({
|
|
||||||
currency: '',
|
|
||||||
theme: '',
|
|
||||||
sidebar: true,
|
|
||||||
nodeInfo: false,
|
|
||||||
setSettings: () => {},
|
|
||||||
refreshSettings: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const SettingsProvider = ({ children }: any) => {
|
|
||||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
||||||
const savedSidebar =
|
|
||||||
localStorage.getItem('sidebar') === 'false' ? false : true;
|
|
||||||
const savedCurrency = localStorage.getItem('currency') || 'sat';
|
|
||||||
const savedNodeInfo =
|
|
||||||
localStorage.getItem('nodeInfo') === 'true' ? true : false;
|
|
||||||
|
|
||||||
const refreshSettings = (account?: string) => {
|
|
||||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
||||||
const savedSidebar =
|
|
||||||
localStorage.getItem('sidebar') === 'false' ? false : true;
|
|
||||||
const savedCurrency = localStorage.getItem('currency') || 'sat';
|
|
||||||
const savedNodeInfo =
|
|
||||||
localStorage.getItem('nodeInfo') === 'true' ? true : false;
|
|
||||||
|
|
||||||
updateSettings((prevState: any) => {
|
|
||||||
const newState = { ...prevState };
|
|
||||||
return merge(newState, {
|
|
||||||
currency: savedCurrency,
|
|
||||||
theme: savedTheme,
|
|
||||||
sidebar: savedSidebar,
|
|
||||||
nodeInfo: savedNodeInfo,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setSettings = ({ currency, theme, sidebar }: ChangeProps) => {
|
|
||||||
updateSettings((prevState: any) => {
|
|
||||||
const newState = { ...prevState };
|
|
||||||
return merge(newState, {
|
|
||||||
currency,
|
|
||||||
theme,
|
|
||||||
sidebar,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const settingsState = {
|
|
||||||
prices: { EUR: { last: 0, symbol: '€' } },
|
|
||||||
price: 0,
|
|
||||||
symbol: '',
|
|
||||||
currency: savedCurrency,
|
|
||||||
theme: savedTheme,
|
|
||||||
sidebar: savedSidebar,
|
|
||||||
nodeInfo: savedNodeInfo,
|
|
||||||
setSettings,
|
|
||||||
refreshSettings,
|
|
||||||
};
|
|
||||||
|
|
||||||
const [settings, updateSettings] = useState(settingsState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsContext.Provider value={settings}>
|
|
||||||
{children}
|
|
||||||
</SettingsContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useSettings = () => useContext(SettingsContext);
|
|
||||||
|
|
||||||
export { SettingsProvider, useSettings };
|
|
|
@ -1,82 +0,0 @@
|
||||||
import React, { createContext, useContext, useReducer } from 'react';
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
loading: boolean;
|
|
||||||
alias: string;
|
|
||||||
syncedToChain: boolean;
|
|
||||||
version: string;
|
|
||||||
mayorVersion: number;
|
|
||||||
minorVersion: number;
|
|
||||||
revision: number;
|
|
||||||
chainBalance: number;
|
|
||||||
chainPending: number;
|
|
||||||
channelBalance: number;
|
|
||||||
channelPending: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ActionType = {
|
|
||||||
type: 'connected' | 'disconnected';
|
|
||||||
state?: State;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Dispatch = (action: ActionType) => void;
|
|
||||||
|
|
||||||
const StateContext = createContext<State | undefined>(undefined);
|
|
||||||
const DispatchContext = createContext<Dispatch | undefined>(undefined);
|
|
||||||
|
|
||||||
const stateReducer = (state: State, action: ActionType): State => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'connected':
|
|
||||||
return action.state || initialState;
|
|
||||||
case 'disconnected':
|
|
||||||
return initialState;
|
|
||||||
default:
|
|
||||||
return initialState;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
loading: true,
|
|
||||||
alias: '',
|
|
||||||
syncedToChain: false,
|
|
||||||
version: '',
|
|
||||||
mayorVersion: 0,
|
|
||||||
minorVersion: 0,
|
|
||||||
revision: 0,
|
|
||||||
chainBalance: 0,
|
|
||||||
chainPending: 0,
|
|
||||||
channelBalance: 0,
|
|
||||||
channelPending: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const StatusProvider = ({ children }: any) => {
|
|
||||||
const [state, dispatch] = useReducer(stateReducer, initialState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DispatchContext.Provider value={dispatch}>
|
|
||||||
<StateContext.Provider value={state}>
|
|
||||||
{children}
|
|
||||||
</StateContext.Provider>
|
|
||||||
</DispatchContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useStatusState = () => {
|
|
||||||
const context = useContext(StateContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('useStatusState must be used within a StatusProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useStatusDispatch = () => {
|
|
||||||
const context = useContext(DispatchContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
'useStatusDispatch must be used within a StatusProvider',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { StatusProvider, useStatusState, useStatusDispatch };
|
|
|
@ -1,71 +0,0 @@
|
||||||
import gql from 'graphql-tag';
|
|
||||||
|
|
||||||
export const GET_HODL_COUNTRIES = gql`
|
|
||||||
query GetCountries {
|
|
||||||
getCountries {
|
|
||||||
code
|
|
||||||
name
|
|
||||||
native_name
|
|
||||||
currency_code
|
|
||||||
currency_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_HODL_CURRENCIES = gql`
|
|
||||||
query GetCurrencies {
|
|
||||||
getCurrencies {
|
|
||||||
code
|
|
||||||
name
|
|
||||||
type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_HODL_OFFERS = gql`
|
|
||||||
query GetOffers($filter: String) {
|
|
||||||
getOffers(filter: $filter) {
|
|
||||||
id
|
|
||||||
asset_code
|
|
||||||
country
|
|
||||||
country_code
|
|
||||||
working_now
|
|
||||||
side
|
|
||||||
title
|
|
||||||
description
|
|
||||||
currency_code
|
|
||||||
price
|
|
||||||
min_amount
|
|
||||||
max_amount
|
|
||||||
first_trade_limit
|
|
||||||
fee {
|
|
||||||
author_fee_rate
|
|
||||||
}
|
|
||||||
balance
|
|
||||||
payment_window_minutes
|
|
||||||
confirmations
|
|
||||||
payment_method_instructions {
|
|
||||||
id
|
|
||||||
version
|
|
||||||
payment_method_id
|
|
||||||
payment_method_type
|
|
||||||
payment_method_name
|
|
||||||
}
|
|
||||||
trader {
|
|
||||||
login
|
|
||||||
online_status
|
|
||||||
rating
|
|
||||||
trades_count
|
|
||||||
url
|
|
||||||
verified
|
|
||||||
verified_by
|
|
||||||
strong_hodler
|
|
||||||
country
|
|
||||||
country_code
|
|
||||||
average_payment_time_minutes
|
|
||||||
average_release_time_minutes
|
|
||||||
days_since_last_trade
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
|
@ -1,159 +0,0 @@
|
||||||
import gql from 'graphql-tag';
|
|
||||||
|
|
||||||
export const CLOSE_CHANNEL = gql`
|
|
||||||
mutation CloseChannel(
|
|
||||||
$id: String!
|
|
||||||
$auth: authType!
|
|
||||||
$forceClose: Boolean
|
|
||||||
$target: Int
|
|
||||||
$tokens: Int
|
|
||||||
) {
|
|
||||||
closeChannel(
|
|
||||||
id: $id
|
|
||||||
forceClose: $forceClose
|
|
||||||
targetConfirmations: $target
|
|
||||||
tokensPerVByte: $tokens
|
|
||||||
auth: $auth
|
|
||||||
) {
|
|
||||||
transactionId
|
|
||||||
transactionOutputIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const OPEN_CHANNEL = gql`
|
|
||||||
mutation openChannel(
|
|
||||||
$amount: Int!
|
|
||||||
$partnerPublicKey: String!
|
|
||||||
$auth: authType!
|
|
||||||
$tokensPerVByte: Int
|
|
||||||
$isPrivate: Boolean
|
|
||||||
) {
|
|
||||||
openChannel(
|
|
||||||
amount: $amount
|
|
||||||
partnerPublicKey: $partnerPublicKey
|
|
||||||
auth: $auth
|
|
||||||
tokensPerVByte: $tokensPerVByte
|
|
||||||
isPrivate: $isPrivate
|
|
||||||
) {
|
|
||||||
transactionId
|
|
||||||
transactionOutputIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const PAY_INVOICE = gql`
|
|
||||||
mutation PayInvoice($request: String!, $auth: authType!) {
|
|
||||||
pay(request: $request, auth: $auth) {
|
|
||||||
isConfirmed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CREATE_INVOICE = gql`
|
|
||||||
mutation PayInvoice($amount: Int!, $auth: authType!) {
|
|
||||||
createInvoice(amount: $amount, auth: $auth) {
|
|
||||||
request
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CREATE_ADDRESS = gql`
|
|
||||||
mutation CreateAddress($nested: Boolean, $auth: authType!) {
|
|
||||||
createAddress(nested: $nested, auth: $auth)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const PAY_ADDRESS = gql`
|
|
||||||
mutation PayAddress(
|
|
||||||
$auth: authType!
|
|
||||||
$address: String!
|
|
||||||
$tokens: Int
|
|
||||||
$fee: Int
|
|
||||||
$target: Int
|
|
||||||
$sendAll: Boolean
|
|
||||||
) {
|
|
||||||
sendToAddress(
|
|
||||||
auth: $auth
|
|
||||||
address: $address
|
|
||||||
tokens: $tokens
|
|
||||||
fee: $fee
|
|
||||||
target: $target
|
|
||||||
sendAll: $sendAll
|
|
||||||
) {
|
|
||||||
confirmationCount
|
|
||||||
id
|
|
||||||
isConfirmed
|
|
||||||
isOutgoing
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const DECODE_REQUEST = gql`
|
|
||||||
mutation decodeRequest($auth: authType!, $request: String!) {
|
|
||||||
decodeRequest(auth: $auth, request: $request) {
|
|
||||||
chainAddress
|
|
||||||
cltvDelta
|
|
||||||
description
|
|
||||||
descriptionHash
|
|
||||||
destination
|
|
||||||
expiresAt
|
|
||||||
id
|
|
||||||
routes {
|
|
||||||
baseFeeMTokens
|
|
||||||
channel
|
|
||||||
cltvDelta
|
|
||||||
feeRate
|
|
||||||
publicKey
|
|
||||||
}
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const UPDATE_FEES = gql`
|
|
||||||
mutation updateFees(
|
|
||||||
$auth: authType!
|
|
||||||
$transactionId: String
|
|
||||||
$transactionVout: Int
|
|
||||||
$baseFee: Int
|
|
||||||
$feeRate: Int
|
|
||||||
) {
|
|
||||||
updateFees(
|
|
||||||
auth: $auth
|
|
||||||
transactionId: $transactionId
|
|
||||||
transactionVout: $transactionVout
|
|
||||||
baseFee: $baseFee
|
|
||||||
feeRate: $feeRate
|
|
||||||
)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const PAY_VIA_ROUTE = gql`
|
|
||||||
mutation PayViaRoute($auth: authType!, $route: String!) {
|
|
||||||
payViaRoute(auth: $auth, route: $route)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const REMOVE_PEER = gql`
|
|
||||||
mutation RemovePeer($auth: authType!, $publicKey: String!) {
|
|
||||||
removePeer(auth: $auth, publicKey: $publicKey)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ADD_PEER = gql`
|
|
||||||
mutation AddPeer(
|
|
||||||
$auth: authType!
|
|
||||||
$publicKey: String!
|
|
||||||
$socket: String!
|
|
||||||
$isTemporary: Boolean
|
|
||||||
) {
|
|
||||||
addPeer(
|
|
||||||
auth: $auth
|
|
||||||
publicKey: $publicKey
|
|
||||||
socket: $socket
|
|
||||||
isTemporary: $isTemporary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
`;
|
|
|
@ -1,378 +0,0 @@
|
||||||
import gql from 'graphql-tag';
|
|
||||||
|
|
||||||
export const GET_NETWORK_INFO = gql`
|
|
||||||
query GetNetworkInfo($auth: authType!) {
|
|
||||||
getNetworkInfo(auth: $auth) {
|
|
||||||
averageChannelSize
|
|
||||||
channelCount
|
|
||||||
maxChannelSize
|
|
||||||
medianChannelSize
|
|
||||||
minChannelSize
|
|
||||||
nodeCount
|
|
||||||
notRecentlyUpdatedPolicyCount
|
|
||||||
totalCapacity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_CAN_CONNECT = gql`
|
|
||||||
query GetNodeInfo($auth: authType!) {
|
|
||||||
getNodeInfo(auth: $auth) {
|
|
||||||
chains
|
|
||||||
color
|
|
||||||
active_channels_count
|
|
||||||
closed_channels_count
|
|
||||||
alias
|
|
||||||
is_synced_to_chain
|
|
||||||
peers_count
|
|
||||||
pending_channels_count
|
|
||||||
version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_CAN_ADMIN = gql`
|
|
||||||
query AdminCheck($auth: authType!) {
|
|
||||||
adminCheck(auth: $auth)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_NODE_INFO = gql`
|
|
||||||
query GetNodeInfo($auth: authType!) {
|
|
||||||
getNodeInfo(auth: $auth) {
|
|
||||||
chains
|
|
||||||
color
|
|
||||||
active_channels_count
|
|
||||||
closed_channels_count
|
|
||||||
alias
|
|
||||||
is_synced_to_chain
|
|
||||||
peers_count
|
|
||||||
pending_channels_count
|
|
||||||
version
|
|
||||||
}
|
|
||||||
getChainBalance(auth: $auth)
|
|
||||||
getPendingChainBalance(auth: $auth)
|
|
||||||
getChannelBalance(auth: $auth) {
|
|
||||||
confirmedBalance
|
|
||||||
pendingBalance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_CHANNEL_AMOUNT_INFO = gql`
|
|
||||||
query GetChannelAmountInfo($auth: authType!) {
|
|
||||||
getNodeInfo(auth: $auth) {
|
|
||||||
active_channels_count
|
|
||||||
closed_channels_count
|
|
||||||
pending_channels_count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_CHANNELS = gql`
|
|
||||||
query GetChannels($auth: authType!, $active: Boolean) {
|
|
||||||
getChannels(auth: $auth, active: $active) {
|
|
||||||
capacity
|
|
||||||
commit_transaction_fee
|
|
||||||
commit_transaction_weight
|
|
||||||
id
|
|
||||||
is_active
|
|
||||||
is_closing
|
|
||||||
is_opening
|
|
||||||
is_partner_initiated
|
|
||||||
is_private
|
|
||||||
is_static_remote_key
|
|
||||||
local_balance
|
|
||||||
local_reserve
|
|
||||||
partner_public_key
|
|
||||||
received
|
|
||||||
remote_balance
|
|
||||||
remote_reserve
|
|
||||||
sent
|
|
||||||
time_offline
|
|
||||||
time_online
|
|
||||||
transaction_id
|
|
||||||
transaction_vout
|
|
||||||
unsettled_balance
|
|
||||||
partner_node_info {
|
|
||||||
alias
|
|
||||||
capacity
|
|
||||||
channel_count
|
|
||||||
color
|
|
||||||
updated_at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_PENDING_CHANNELS = gql`
|
|
||||||
query GetPendingChannels($auth: authType!) {
|
|
||||||
getPendingChannels(auth: $auth) {
|
|
||||||
close_transaction_id
|
|
||||||
is_active
|
|
||||||
is_closing
|
|
||||||
is_opening
|
|
||||||
local_balance
|
|
||||||
local_reserve
|
|
||||||
partner_public_key
|
|
||||||
received
|
|
||||||
remote_balance
|
|
||||||
remote_reserve
|
|
||||||
sent
|
|
||||||
transaction_fee
|
|
||||||
transaction_id
|
|
||||||
transaction_vout
|
|
||||||
partner_node_info {
|
|
||||||
alias
|
|
||||||
capacity
|
|
||||||
channel_count
|
|
||||||
color
|
|
||||||
updated_at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_CLOSED_CHANNELS = gql`
|
|
||||||
query GetClosedChannels($auth: authType!) {
|
|
||||||
getClosedChannels(auth: $auth) {
|
|
||||||
capacity
|
|
||||||
close_confirm_height
|
|
||||||
close_transaction_id
|
|
||||||
final_local_balance
|
|
||||||
final_time_locked_balance
|
|
||||||
id
|
|
||||||
is_breach_close
|
|
||||||
is_cooperative_close
|
|
||||||
is_funding_cancel
|
|
||||||
is_local_force_close
|
|
||||||
is_remote_force_close
|
|
||||||
partner_public_key
|
|
||||||
transaction_id
|
|
||||||
transaction_vout
|
|
||||||
partner_node_info {
|
|
||||||
alias
|
|
||||||
capacity
|
|
||||||
channel_count
|
|
||||||
color
|
|
||||||
updated_at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_RESUME = gql`
|
|
||||||
query GetResume($auth: authType!, $token: String) {
|
|
||||||
getResume(auth: $auth, token: $token) {
|
|
||||||
token
|
|
||||||
resume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_BITCOIN_PRICE = gql`
|
|
||||||
query GetBitcoinPrice {
|
|
||||||
getBitcoinPrice
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_BITCOIN_FEES = gql`
|
|
||||||
query GetBitcoinFees {
|
|
||||||
getBitcoinFees {
|
|
||||||
fast
|
|
||||||
halfHour
|
|
||||||
hour
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_FORWARD_REPORT = gql`
|
|
||||||
query GetForwardReport($time: String, $auth: authType!) {
|
|
||||||
getForwardReport(time: $time, auth: $auth)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_LIQUID_REPORT = gql`
|
|
||||||
query GetLiquidReport($auth: authType!) {
|
|
||||||
getChannelReport(auth: $auth) {
|
|
||||||
local
|
|
||||||
remote
|
|
||||||
maxIn
|
|
||||||
maxOut
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_FORWARD_CHANNELS_REPORT = gql`
|
|
||||||
query GetForwardChannelsReport(
|
|
||||||
$time: String
|
|
||||||
$order: String
|
|
||||||
$type: String
|
|
||||||
$auth: authType!
|
|
||||||
) {
|
|
||||||
getForwardChannelsReport(
|
|
||||||
time: $time
|
|
||||||
order: $order
|
|
||||||
auth: $auth
|
|
||||||
type: $type
|
|
||||||
)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_IN_OUT = gql`
|
|
||||||
query GetInOut($auth: authType!, $time: String) {
|
|
||||||
getInOut(auth: $auth, time: $time) {
|
|
||||||
invoices
|
|
||||||
payments
|
|
||||||
confirmedInvoices
|
|
||||||
unConfirmedInvoices
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_CHAIN_TRANSACTIONS = gql`
|
|
||||||
query GetChainTransactions($auth: authType!) {
|
|
||||||
getChainTransactions(auth: $auth) {
|
|
||||||
block_id
|
|
||||||
confirmation_count
|
|
||||||
confirmation_height
|
|
||||||
created_at
|
|
||||||
fee
|
|
||||||
id
|
|
||||||
output_addresses
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_FORWARDS = gql`
|
|
||||||
query GetForwards($auth: authType!, $time: String) {
|
|
||||||
getForwards(auth: $auth, time: $time) {
|
|
||||||
forwards {
|
|
||||||
created_at
|
|
||||||
fee
|
|
||||||
fee_mtokens
|
|
||||||
incoming_channel
|
|
||||||
incoming_alias
|
|
||||||
incoming_color
|
|
||||||
mtokens
|
|
||||||
outgoing_channel
|
|
||||||
outgoing_alias
|
|
||||||
outgoing_color
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_CONNECT_INFO = gql`
|
|
||||||
query GetNodeInfo($auth: authType!) {
|
|
||||||
getNodeInfo(auth: $auth) {
|
|
||||||
public_key
|
|
||||||
uris
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_BACKUPS = gql`
|
|
||||||
query GetBackups($auth: authType!) {
|
|
||||||
getBackups(auth: $auth)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const VERIFY_BACKUPS = gql`
|
|
||||||
query VerifyBackups($auth: authType!, $backup: String!) {
|
|
||||||
verifyBackups(auth: $auth, backup: $backup)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SIGN_MESSAGE = gql`
|
|
||||||
query SignMessage($auth: authType!, $message: String!) {
|
|
||||||
signMessage(auth: $auth, message: $message)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const VERIFY_MESSAGE = gql`
|
|
||||||
query VerifyMessage(
|
|
||||||
$auth: authType!
|
|
||||||
$message: String!
|
|
||||||
$signature: String!
|
|
||||||
) {
|
|
||||||
verifyMessage(auth: $auth, message: $message, signature: $signature)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RECOVER_FUNDS = gql`
|
|
||||||
query RecoverFunds($auth: authType!, $backup: String!) {
|
|
||||||
recoverFunds(auth: $auth, backup: $backup)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CHANNEL_FEES = gql`
|
|
||||||
query GetChannelFees($auth: authType!) {
|
|
||||||
getChannelFees(auth: $auth) {
|
|
||||||
alias
|
|
||||||
color
|
|
||||||
baseFee
|
|
||||||
feeRate
|
|
||||||
transactionId
|
|
||||||
transactionVout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_ROUTES = gql`
|
|
||||||
query GetRoutes(
|
|
||||||
$auth: authType!
|
|
||||||
$outgoing: String!
|
|
||||||
$incoming: String!
|
|
||||||
$tokens: Int!
|
|
||||||
$maxFee: Int
|
|
||||||
) {
|
|
||||||
getRoutes(
|
|
||||||
auth: $auth
|
|
||||||
outgoing: $outgoing
|
|
||||||
incoming: $incoming
|
|
||||||
tokens: $tokens
|
|
||||||
maxFee: $maxFee
|
|
||||||
)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_PEERS = gql`
|
|
||||||
query GetPeers($auth: authType!) {
|
|
||||||
getPeers(auth: $auth) {
|
|
||||||
bytes_received
|
|
||||||
bytes_sent
|
|
||||||
is_inbound
|
|
||||||
is_sync_peer
|
|
||||||
ping_time
|
|
||||||
public_key
|
|
||||||
socket
|
|
||||||
tokens_received
|
|
||||||
tokens_sent
|
|
||||||
partner_node_info {
|
|
||||||
alias
|
|
||||||
capacity
|
|
||||||
channel_count
|
|
||||||
color
|
|
||||||
updated_at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_UTXOS = gql`
|
|
||||||
query GetUtxos($auth: authType!) {
|
|
||||||
getUtxos(auth: $auth) {
|
|
||||||
address
|
|
||||||
address_format
|
|
||||||
confirmation_count
|
|
||||||
output_script
|
|
||||||
tokens
|
|
||||||
transaction_id
|
|
||||||
transaction_vout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
|
@ -1,76 +0,0 @@
|
||||||
import numeral from 'numeral';
|
|
||||||
|
|
||||||
const getValueString = (amount: number): string => {
|
|
||||||
if (amount >= 100000) {
|
|
||||||
return `${amount / 1000000}m`;
|
|
||||||
} else if (amount >= 1000) {
|
|
||||||
return `${amount / 1000}k`;
|
|
||||||
}
|
|
||||||
return `${amount}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface GetNumberProps {
|
|
||||||
amount: string | number;
|
|
||||||
price: number;
|
|
||||||
symbol: string;
|
|
||||||
currency: string;
|
|
||||||
breakNumber?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getValue = ({
|
|
||||||
amount,
|
|
||||||
price,
|
|
||||||
symbol,
|
|
||||||
currency,
|
|
||||||
breakNumber,
|
|
||||||
}: GetNumberProps): string => {
|
|
||||||
let value: number = 0;
|
|
||||||
if (typeof amount === 'string') {
|
|
||||||
value = parseInt(amount);
|
|
||||||
} else {
|
|
||||||
value = amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currency === 'btc') {
|
|
||||||
if (!value) return `₿0.0`;
|
|
||||||
const amountInBtc = value / 100000000;
|
|
||||||
return `₿${amountInBtc}`;
|
|
||||||
} else if (currency === 'sat') {
|
|
||||||
const breakAmount = breakNumber
|
|
||||||
? getValueString(value)
|
|
||||||
: numeral(value).format('0,0');
|
|
||||||
return `${breakAmount} sats`;
|
|
||||||
} else {
|
|
||||||
const amountInFiat = (value / 100000000) * price;
|
|
||||||
return `${symbol}${numeral(amountInFiat).format('0,0.00')}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPercent = (
|
|
||||||
local: number,
|
|
||||||
remote: number,
|
|
||||||
withDecimals?: boolean,
|
|
||||||
): number => {
|
|
||||||
const total = remote + local;
|
|
||||||
const percent = (local / total) * 100;
|
|
||||||
|
|
||||||
if (remote === 0 && local === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (withDecimals) {
|
|
||||||
return Math.round(percent * 100) / 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(percent);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const saveToPc = (jsonData: string, filename: string) => {
|
|
||||||
const fileData = jsonData;
|
|
||||||
const blob = new Blob([fileData], { type: 'text/plain' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.download = `${filename}.txt`;
|
|
||||||
link.href = url;
|
|
||||||
link.click();
|
|
||||||
};
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
export const useInterval = (callback: any, delay: number) => {
|
|
||||||
const savedCallback = useRef(callback);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
savedCallback.current = callback;
|
|
||||||
}, [callback]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const tick = () => {
|
|
||||||
savedCallback.current();
|
|
||||||
};
|
|
||||||
let id = setInterval(tick, delay);
|
|
||||||
return () => clearInterval(id);
|
|
||||||
}, [delay]);
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import debounce from 'lodash.debounce';
|
|
||||||
|
|
||||||
const getSize = () => {
|
|
||||||
const isClient = typeof window === 'object';
|
|
||||||
return {
|
|
||||||
width: isClient ? window.innerWidth : 0,
|
|
||||||
height: isClient ? window.innerHeight : 0,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSize = () => {
|
|
||||||
const [windowSize, setWindowSize] = useState(getSize());
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleResize = () => {
|
|
||||||
setWindowSize(getSize());
|
|
||||||
};
|
|
||||||
|
|
||||||
handleResize();
|
|
||||||
|
|
||||||
const debouncedHandle = debounce(handleResize, 250);
|
|
||||||
window.addEventListener('resize', debouncedHandle);
|
|
||||||
return () => window.removeEventListener('resize', debouncedHandle);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return windowSize;
|
|
||||||
};
|
|
|
@ -1,12 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import App from './App';
|
|
||||||
import './styles/FontStyles.css';
|
|
||||||
import * as serviceWorker from './serviceWorker';
|
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById('root'));
|
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
|
||||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
|
||||||
serviceWorker.unregister();
|
|
1
client/src/react-app-env.d.ts
vendored
1
client/src/react-app-env.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
/// <reference types="react-scripts" />
|
|
|
@ -1,104 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Navigation } from '../../sections/navigation/Navigation';
|
|
||||||
import { Switch, Route } from 'react-router';
|
|
||||||
|
|
||||||
import { Home } from '../../views/home/Home';
|
|
||||||
import { NotFound } from '../../views/notFound/NotFound';
|
|
||||||
import { ChannelView } from '../../views/channels/ChannelView';
|
|
||||||
import { SettingsView } from '../../views/settings/Settings';
|
|
||||||
import { TransactionList } from '../../views/transactions/TransactionList';
|
|
||||||
import { FeesView } from '../../views/fees/Fees';
|
|
||||||
import { ForwardsList } from '../../views/forwards/ForwardList';
|
|
||||||
import { TermsView } from '../../views/other/terms/TermsView';
|
|
||||||
import { PrivacyView } from '../../views/other/privacy/PrivacyView';
|
|
||||||
import { FaqView } from '../../views/other/faq/FaqView';
|
|
||||||
import { Section } from 'components/section/Section';
|
|
||||||
import { BitcoinPrice } from '../../components/bitcoinInfo/BitcoinPrice';
|
|
||||||
import { BitcoinFees } from '../../components/bitcoinInfo/BitcoinFees';
|
|
||||||
import { mediaWidths } from 'styles/Themes';
|
|
||||||
import { useConnectionState } from 'context/ConnectionContext';
|
|
||||||
import { LoadingView, ErrorView } from 'views/stateViews/StateCards';
|
|
||||||
import { BalanceView } from 'views/balance/Balance';
|
|
||||||
import { PeersList } from 'views/peers/PeersList';
|
|
||||||
import { ToolsView } from 'views/tools';
|
|
||||||
import { ChainView } from 'views/chain/ChainView';
|
|
||||||
import { TraderView } from 'views/trader/TraderView';
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas: 'nav content content';
|
|
||||||
grid-template-columns: auto 1fr 200px;
|
|
||||||
gap: 16px;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ContentStyle = styled.div`
|
|
||||||
grid-area: content;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Content = () => {
|
|
||||||
const { loading, error } = useConnectionState();
|
|
||||||
|
|
||||||
const renderSettings = (type: string) => (
|
|
||||||
<Switch>
|
|
||||||
<Route path="/settings" render={() => getGrid(SettingsView)} />
|
|
||||||
<Route
|
|
||||||
path="*"
|
|
||||||
render={() =>
|
|
||||||
getGrid(type === 'error' ? ErrorView : LoadingView)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading) return renderSettings('loading');
|
|
||||||
if (error) return renderSettings('error');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BitcoinPrice />
|
|
||||||
<BitcoinFees />
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/" render={() => getGrid(Home)} />
|
|
||||||
<Route path="/peers" render={() => getGrid(PeersList)} />
|
|
||||||
<Route path="/channels" render={() => getGrid(ChannelView)} />
|
|
||||||
<Route path="/balance" render={() => getGrid(BalanceView)} />
|
|
||||||
<Route path="/tools" render={() => getGrid(ToolsView)} />
|
|
||||||
<Route
|
|
||||||
path="/transactions"
|
|
||||||
render={() => getGrid(TransactionList)}
|
|
||||||
/>
|
|
||||||
<Route path="/forwards" render={() => getGrid(ForwardsList)} />
|
|
||||||
<Route
|
|
||||||
path="/chaintransactions"
|
|
||||||
render={() => getGrid(ChainView)}
|
|
||||||
/>
|
|
||||||
<Route path="/settings" render={() => getGrid(SettingsView)} />
|
|
||||||
<Route path="/fees" render={() => getGrid(FeesView)} />
|
|
||||||
<Route path="/trading" render={() => getGrid(TraderView)} />
|
|
||||||
<Route path="/terms" render={() => <TermsView />} />
|
|
||||||
<Route path="/privacy" render={() => <PrivacyView />} />
|
|
||||||
<Route path="/faq" render={() => <FaqView />} />
|
|
||||||
<Route path="*" render={() => <NotFound />} />
|
|
||||||
</Switch>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getGrid = (Content: any) => (
|
|
||||||
<Section padding={'16px 0 32px'}>
|
|
||||||
<Container>
|
|
||||||
<Navigation />
|
|
||||||
<ContentStyle>
|
|
||||||
<Content />
|
|
||||||
</ContentStyle>
|
|
||||||
</Container>
|
|
||||||
</Section>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Content;
|
|
|
@ -1,158 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import {
|
|
||||||
headerColor,
|
|
||||||
headerTextColor,
|
|
||||||
fontColors,
|
|
||||||
mediaWidths,
|
|
||||||
} from 'styles/Themes';
|
|
||||||
import { Section } from 'components/section/Section';
|
|
||||||
import { Link } from 'components/link/Link';
|
|
||||||
import { Emoji } from 'components/emoji/Emoji';
|
|
||||||
import { useAccount } from 'context/AccountContext';
|
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
|
||||||
import { HomeButton } from 'views/entry/homepage/HomePage.styled';
|
|
||||||
import { Zap } from 'components/generic/Icons';
|
|
||||||
|
|
||||||
const FooterStyle = styled.div`
|
|
||||||
padding: 40px 0;
|
|
||||||
min-height: 300px;
|
|
||||||
color: ${headerTextColor};
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 0 0 40px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SideFooter = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const RightFooter = styled(SideFooter)`
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-end;
|
|
||||||
width: 80%;
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
margin-top: 32px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Title = styled.div`
|
|
||||||
font-weight: 900;
|
|
||||||
color: ${headerTextColor};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SideText = styled.p`
|
|
||||||
font-size: 14px;
|
|
||||||
color: ${fontColors.grey7};
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CopyrightText = styled(SideText)`
|
|
||||||
font-size: 12px;
|
|
||||||
color: ${fontColors.blue};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledRouter = styled(RouterLink)`
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
${HomeButton} {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Line = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-end;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Version = styled.div`
|
|
||||||
font-size: 12px;
|
|
||||||
margin-left: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const APP_VERSION = process.env.REACT_APP_VERSION || '0.0.0';
|
|
||||||
|
|
||||||
export const Footer = () => {
|
|
||||||
const { loggedIn } = useAccount();
|
|
||||||
return (
|
|
||||||
<Section withColor={true} color={headerColor}>
|
|
||||||
<FooterStyle>
|
|
||||||
<SideFooter>
|
|
||||||
<Line>
|
|
||||||
<RouterLink to="/" style={{ textDecoration: 'none' }}>
|
|
||||||
<Title>ThunderHub</Title>
|
|
||||||
</RouterLink>
|
|
||||||
<Version>{`v${APP_VERSION}`}</Version>
|
|
||||||
</Line>
|
|
||||||
<SideText>
|
|
||||||
Open-source lightning node manager to control and
|
|
||||||
monitor your LND node.
|
|
||||||
</SideText>
|
|
||||||
<SideText>
|
|
||||||
Made in Munich with{' '}
|
|
||||||
<Emoji symbol={'🧡'} label={'heart'} /> and{' '}
|
|
||||||
<Emoji symbol={'⚡'} label={'lightning'} />.
|
|
||||||
</SideText>
|
|
||||||
<CopyrightText>
|
|
||||||
Copyright © 2020. All rights reserved. ThunderHub
|
|
||||||
</CopyrightText>
|
|
||||||
</SideFooter>
|
|
||||||
<RightFooter>
|
|
||||||
<Link to={'/faq'} color={fontColors.blue}>
|
|
||||||
FAQ
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href={'https://github.com/apotdevin/thunderhub'}
|
|
||||||
color={fontColors.blue}
|
|
||||||
>
|
|
||||||
Github
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href={'https://twitter.com/thunderhubio'}
|
|
||||||
color={fontColors.blue}
|
|
||||||
>
|
|
||||||
Twitter
|
|
||||||
</Link>
|
|
||||||
<Link to={'/terms'} color={fontColors.blue}>
|
|
||||||
Terms of Use
|
|
||||||
</Link>
|
|
||||||
<Link to={'/privacy'} color={fontColors.blue}>
|
|
||||||
Privacy Policy
|
|
||||||
</Link>
|
|
||||||
{!loggedIn && (
|
|
||||||
<StyledRouter
|
|
||||||
to="/login"
|
|
||||||
style={{ textDecoration: 'none' }}
|
|
||||||
>
|
|
||||||
<HomeButton>
|
|
||||||
<Zap fillcolor={'white'} color={'white'} />
|
|
||||||
LOGIN
|
|
||||||
</HomeButton>
|
|
||||||
</StyledRouter>
|
|
||||||
)}
|
|
||||||
</RightFooter>
|
|
||||||
</FooterStyle>
|
|
||||||
</Section>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,173 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import {
|
|
||||||
headerColor,
|
|
||||||
headerTextColor,
|
|
||||||
themeColors,
|
|
||||||
mediaWidths,
|
|
||||||
mediaDimensions,
|
|
||||||
} from '../../styles/Themes';
|
|
||||||
import { HomeButton } from '../../views/entry/homepage/HomePage.styled';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { useAccount } from '../../context/AccountContext';
|
|
||||||
import { SingleLine, ResponsiveLine } from '../../components/generic/Styled';
|
|
||||||
import {
|
|
||||||
Cpu,
|
|
||||||
MenuIcon,
|
|
||||||
XSvg,
|
|
||||||
Zap,
|
|
||||||
Circle,
|
|
||||||
} from '../../components/generic/Icons';
|
|
||||||
import { BurgerMenu } from 'components/burgerMenu/BurgerMenu';
|
|
||||||
import { useSize } from 'hooks/UseSize';
|
|
||||||
import { useTransition, animated } from 'react-spring';
|
|
||||||
import { Section } from 'components/section/Section';
|
|
||||||
import { useStatusState } from 'context/StatusContext';
|
|
||||||
|
|
||||||
const HeaderStyle = styled.div`
|
|
||||||
padding: 16px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const IconPadding = styled.div`
|
|
||||||
padding-right: 6px;
|
|
||||||
margin-bottom: -4px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HeaderTitle = styled.div`
|
|
||||||
color: ${headerTextColor};
|
|
||||||
font-weight: 900;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
${({ withPadding }: { withPadding: boolean }) =>
|
|
||||||
withPadding &&
|
|
||||||
css`
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const IconWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LinkWrapper = styled.div`
|
|
||||||
color: ${headerTextColor};
|
|
||||||
margin: ${({ last }: { last?: boolean }) =>
|
|
||||||
last ? '0 16px 0 4px' : '0 4px'};
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
color: ${themeColors.blue2};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const AnimatedBurger = animated(MenuIcon);
|
|
||||||
const AnimatedClose = animated(XSvg);
|
|
||||||
|
|
||||||
export const Header = () => {
|
|
||||||
const { width } = useSize();
|
|
||||||
const { loggedIn } = useAccount();
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const { syncedToChain } = useStatusState();
|
|
||||||
|
|
||||||
const transitions = useTransition(open, null, {
|
|
||||||
from: { position: 'absolute', opacity: 0 },
|
|
||||||
enter: { opacity: 1 },
|
|
||||||
leave: { opacity: 0 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderLoggedIn = () => {
|
|
||||||
if (width <= mediaDimensions.mobile) {
|
|
||||||
return (
|
|
||||||
<IconWrapper onClick={() => setOpen((prev) => !prev)}>
|
|
||||||
{transitions.map(({ item, key, props }) =>
|
|
||||||
item ? (
|
|
||||||
<AnimatedClose
|
|
||||||
key={key}
|
|
||||||
style={props}
|
|
||||||
size={'24px'}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<AnimatedBurger
|
|
||||||
key={key}
|
|
||||||
style={props}
|
|
||||||
size={'24px'}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</IconWrapper>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Circle
|
|
||||||
size={'12px'}
|
|
||||||
strokeWidth={'0'}
|
|
||||||
fillcolor={syncedToChain ? '#95de64' : '#ff7875'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderLoggedOut = () => (
|
|
||||||
<>
|
|
||||||
<Link to="/faq" style={{ textDecoration: 'none' }}>
|
|
||||||
<LinkWrapper>Faq</LinkWrapper>
|
|
||||||
</Link>
|
|
||||||
<Link to="/terms" style={{ textDecoration: 'none' }}>
|
|
||||||
<LinkWrapper>Terms</LinkWrapper>
|
|
||||||
</Link>
|
|
||||||
<Link to="/privacy" style={{ textDecoration: 'none' }}>
|
|
||||||
<LinkWrapper last={true}>Privacy</LinkWrapper>
|
|
||||||
</Link>
|
|
||||||
<Link to="/login" style={{ textDecoration: 'none' }}>
|
|
||||||
<HomeButton>
|
|
||||||
<Zap fillcolor={'white'} color={'white'} />
|
|
||||||
</HomeButton>
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const HeaderWrapper =
|
|
||||||
width <= mediaDimensions.mobile && !loggedIn
|
|
||||||
? ResponsiveLine
|
|
||||||
: SingleLine;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Section
|
|
||||||
withColor={true}
|
|
||||||
color={headerColor}
|
|
||||||
textColor={headerTextColor}
|
|
||||||
>
|
|
||||||
<HeaderStyle>
|
|
||||||
<HeaderWrapper>
|
|
||||||
<Link to="/" style={{ textDecoration: 'none' }}>
|
|
||||||
<HeaderTitle
|
|
||||||
withPadding={
|
|
||||||
width <= mediaDimensions.mobile && !loggedIn
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<IconPadding>
|
|
||||||
<Cpu color={'white'} />
|
|
||||||
</IconPadding>
|
|
||||||
ThunderHub
|
|
||||||
</HeaderTitle>
|
|
||||||
</Link>
|
|
||||||
<SingleLine>
|
|
||||||
{loggedIn ? renderLoggedIn() : renderLoggedOut()}
|
|
||||||
</SingleLine>
|
|
||||||
</HeaderWrapper>
|
|
||||||
</HeaderStyle>
|
|
||||||
</Section>
|
|
||||||
{open && width <= mediaDimensions.mobile && (
|
|
||||||
<BurgerMenu open={open} setOpen={setOpen} />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,211 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
|
||||||
import { NodeInfo } from './nodeInfo/NodeInfo';
|
|
||||||
import { SideSettings } from './sideSettings/SideSettings';
|
|
||||||
import {
|
|
||||||
unSelectedNavButton,
|
|
||||||
navBackgroundColor,
|
|
||||||
navTextColor,
|
|
||||||
subCardColor,
|
|
||||||
cardBorderColor,
|
|
||||||
mediaWidths,
|
|
||||||
} from '../../styles/Themes';
|
|
||||||
import {
|
|
||||||
Home,
|
|
||||||
Cpu,
|
|
||||||
Server,
|
|
||||||
Settings,
|
|
||||||
Shield,
|
|
||||||
Crosshair,
|
|
||||||
GitPullRequest,
|
|
||||||
LinkIcon,
|
|
||||||
RepeatIcon,
|
|
||||||
Users,
|
|
||||||
CreditCard,
|
|
||||||
} from '../../components/generic/Icons';
|
|
||||||
import { useSettings } from '../../context/SettingsContext';
|
|
||||||
import { useConnectionState } from 'context/ConnectionContext';
|
|
||||||
|
|
||||||
const NavigationStyle = styled.div`
|
|
||||||
grid-area: nav;
|
|
||||||
width: ${({ isOpen }: { isOpen: boolean }) => (isOpen ? '200px' : '60px')};
|
|
||||||
|
|
||||||
@media (${mediaWidths.mobile}) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StickyCard = styled.div`
|
|
||||||
position: -webkit-sticky;
|
|
||||||
position: sticky;
|
|
||||||
top: 16px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LinkView = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 8px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ButtonSection = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
${({ isOpen }: { isOpen: boolean }) =>
|
|
||||||
!isOpen &&
|
|
||||||
css`
|
|
||||||
margin: 8px 0;
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const NavSeparation = styled.div`
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface NavProps {
|
|
||||||
selected: boolean;
|
|
||||||
isOpen?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NavButton = styled(({ isOpen, ...rest }) => <Link {...rest} />)(
|
|
||||||
() => css`
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: ${({ selected }: NavProps) =>
|
|
||||||
selected && navBackgroundColor};
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
${({ isOpen }: NavProps) => !isOpen && 'justify-content: center'};
|
|
||||||
width: 100%;
|
|
||||||
text-decoration: none;
|
|
||||||
margin: 4px 0;
|
|
||||||
color: ${({ selected }: NavProps) =>
|
|
||||||
selected ? navTextColor : unSelectedNavButton};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: ${navTextColor};
|
|
||||||
background: ${navBackgroundColor};
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const BurgerRow = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
overflow: scroll;
|
|
||||||
background: ${cardBorderColor};
|
|
||||||
margin: 0 -16px;
|
|
||||||
padding: 16px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const BurgerNav = styled(({ selectedColor, ...rest }) => <Link {...rest} />)(
|
|
||||||
() => css`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 16px 16px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
background: ${({ selected }: NavProps) => selected && subCardColor};
|
|
||||||
${({ isOpen }: NavProps) => !isOpen && 'justify-content: center'};
|
|
||||||
color: ${({ selected }: NavProps) =>
|
|
||||||
selected ? navTextColor : unSelectedNavButton};
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const HOME = '/';
|
|
||||||
const PEERS = '/peers';
|
|
||||||
const CHANNEL = '/channels';
|
|
||||||
const BALANCE = '/balance';
|
|
||||||
const TRANS = '/transactions';
|
|
||||||
const FORWARDS = '/forwards';
|
|
||||||
const CHAIN_TRANS = '/chainTransactions';
|
|
||||||
const TOOLS = '/tools';
|
|
||||||
const SETTINGS = '/settings';
|
|
||||||
const FEES = '/fees';
|
|
||||||
const TRADER = '/trading';
|
|
||||||
|
|
||||||
interface NavigationProps {
|
|
||||||
isBurger?: boolean;
|
|
||||||
setOpen?: (state: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
|
|
||||||
const { pathname } = useLocation();
|
|
||||||
const { sidebar, setSettings } = useSettings();
|
|
||||||
const { connected } = useConnectionState();
|
|
||||||
|
|
||||||
const renderNavButton = (
|
|
||||||
title: string,
|
|
||||||
link: string,
|
|
||||||
NavIcon: any,
|
|
||||||
open: boolean = true,
|
|
||||||
) => (
|
|
||||||
<NavButton isOpen={sidebar} selected={pathname === link} to={link}>
|
|
||||||
<NavIcon />
|
|
||||||
{open && <NavSeparation>{title}</NavSeparation>}
|
|
||||||
</NavButton>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderBurgerNav = (title: string, link: string, NavIcon: any) => (
|
|
||||||
<BurgerNav
|
|
||||||
selected={pathname === link}
|
|
||||||
to={link}
|
|
||||||
onClick={() => setOpen && setOpen(false)}
|
|
||||||
>
|
|
||||||
<NavIcon />
|
|
||||||
{title}
|
|
||||||
</BurgerNav>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderLinks = () => (
|
|
||||||
<ButtonSection isOpen={sidebar}>
|
|
||||||
{renderNavButton('Home', HOME, Home, sidebar)}
|
|
||||||
{renderNavButton('Peers', PEERS, Users, sidebar)}
|
|
||||||
{renderNavButton('Channels', CHANNEL, Cpu, sidebar)}
|
|
||||||
{renderNavButton('Balance', BALANCE, RepeatIcon, sidebar)}
|
|
||||||
{renderNavButton('Fees', FEES, Crosshair, sidebar)}
|
|
||||||
{renderNavButton('Transactions', TRANS, Server, sidebar)}
|
|
||||||
{renderNavButton('Forwards', FORWARDS, GitPullRequest, sidebar)}
|
|
||||||
{renderNavButton('Chain', CHAIN_TRANS, LinkIcon, sidebar)}
|
|
||||||
{renderNavButton('Tools', TOOLS, Shield, sidebar)}
|
|
||||||
{renderNavButton('P2P Trading', TRADER, CreditCard, sidebar)}
|
|
||||||
{renderNavButton('Settings', SETTINGS, Settings, sidebar)}
|
|
||||||
</ButtonSection>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderBurger = () => (
|
|
||||||
<BurgerRow>
|
|
||||||
{renderBurgerNav('Home', HOME, Home)}
|
|
||||||
{renderBurgerNav('Peers', PEERS, Users)}
|
|
||||||
{renderBurgerNav('Channels', CHANNEL, Cpu)}
|
|
||||||
{renderBurgerNav('Balance', BALANCE, RepeatIcon)}
|
|
||||||
{renderBurgerNav('Fees', FEES, Crosshair)}
|
|
||||||
{renderBurgerNav('Transactions', TRANS, Server)}
|
|
||||||
{renderBurgerNav('Forwards', FORWARDS, GitPullRequest)}
|
|
||||||
{renderBurgerNav('Chain', CHAIN_TRANS, LinkIcon)}
|
|
||||||
{renderBurgerNav('Tools', TOOLS, Shield)}
|
|
||||||
{renderBurgerNav('Trading', TRADER, CreditCard)}
|
|
||||||
{renderBurgerNav('Settings', SETTINGS, Settings)}
|
|
||||||
</BurgerRow>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isBurger) {
|
|
||||||
return renderBurger();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NavigationStyle isOpen={sidebar}>
|
|
||||||
<StickyCard>
|
|
||||||
<LinkView>
|
|
||||||
{connected && <NodeInfo isOpen={sidebar} />}
|
|
||||||
{renderLinks()}
|
|
||||||
<SideSettings isOpen={sidebar} setIsOpen={setSettings} />
|
|
||||||
</LinkView>
|
|
||||||
</StickyCard>
|
|
||||||
</NavigationStyle>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,295 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import { GET_NODE_INFO } from '../../../graphql/query';
|
|
||||||
import { useSettings } from '../../../context/SettingsContext';
|
|
||||||
import {
|
|
||||||
Separation,
|
|
||||||
SingleLine,
|
|
||||||
SubTitle,
|
|
||||||
Sub4Title,
|
|
||||||
} from '../../../components/generic/Styled';
|
|
||||||
import {
|
|
||||||
QuestionIcon,
|
|
||||||
Zap,
|
|
||||||
Anchor,
|
|
||||||
Circle,
|
|
||||||
} from '../../../components/generic/Icons';
|
|
||||||
import { getTooltipType } from '../../../components/generic/Helpers';
|
|
||||||
import { useAccount } from '../../../context/AccountContext';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { getErrorContent } from '../../../utils/error';
|
|
||||||
import { textColorMap, unSelectedNavButton } from '../../../styles/Themes';
|
|
||||||
import ReactTooltip from 'react-tooltip';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
|
||||||
import { getPrice } from 'components/price/Price';
|
|
||||||
import { AnimatedNumber } from 'components/animated/AnimatedNumber';
|
|
||||||
import { useStatusState } from 'context/StatusContext';
|
|
||||||
import { usePriceState } from 'context/PriceContext';
|
|
||||||
|
|
||||||
const Closed = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Margin = styled.div`
|
|
||||||
margin: 8px 0 2px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Title = styled.div`
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Info = styled.div`
|
|
||||||
font-size: 14px;
|
|
||||||
color: #bfbfbf;
|
|
||||||
border-bottom: 2px solid
|
|
||||||
${({ bottomColor }: { bottomColor: string }) => bottomColor};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Balance = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
margin: 2px 0;
|
|
||||||
padding: 0 5px;
|
|
||||||
cursor: default;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Alias = styled.div`
|
|
||||||
border-bottom: 2px solid
|
|
||||||
${({ bottomColor }: { bottomColor: string }) => bottomColor};
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface NodeInfoProps {
|
|
||||||
isOpen?: boolean;
|
|
||||||
isBurger?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NodeInfo = ({ isOpen, isBurger }: NodeInfoProps) => {
|
|
||||||
const {
|
|
||||||
syncedToChain,
|
|
||||||
chainBalance,
|
|
||||||
chainPending,
|
|
||||||
channelBalance,
|
|
||||||
channelPending,
|
|
||||||
} = useStatusState();
|
|
||||||
|
|
||||||
const { host, viewOnly, cert, sessionAdmin } = useAccount();
|
|
||||||
const auth = {
|
|
||||||
host,
|
|
||||||
macaroon: viewOnly !== '' ? viewOnly : sessionAdmin,
|
|
||||||
cert,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { loading, data } = useQuery(GET_NODE_INFO, {
|
|
||||||
variables: { auth },
|
|
||||||
onError: (error) => toast.error(getErrorContent(error)),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { theme, currency } = useSettings();
|
|
||||||
const priceContext = usePriceState();
|
|
||||||
const format = getPrice(currency, priceContext);
|
|
||||||
|
|
||||||
const tooltipType = getTooltipType(theme);
|
|
||||||
|
|
||||||
if (loading || !data || !data.getNodeInfo) {
|
|
||||||
return (
|
|
||||||
<Closed>
|
|
||||||
<ScaleLoader
|
|
||||||
height={10}
|
|
||||||
width={2}
|
|
||||||
color={textColorMap[theme]}
|
|
||||||
/>
|
|
||||||
</Closed>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
color,
|
|
||||||
active_channels_count,
|
|
||||||
closed_channels_count,
|
|
||||||
alias,
|
|
||||||
peers_count,
|
|
||||||
pending_channels_count,
|
|
||||||
version,
|
|
||||||
} = data.getNodeInfo;
|
|
||||||
|
|
||||||
const formatCB = format({ amount: chainBalance });
|
|
||||||
const formatPB = format({ amount: chainPending });
|
|
||||||
const formatCCB = format({ amount: channelBalance });
|
|
||||||
const formatPCB = format({ amount: channelPending });
|
|
||||||
|
|
||||||
if (isBurger) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SingleLine>
|
|
||||||
<SubTitle>{alias}</SubTitle>
|
|
||||||
<Circle
|
|
||||||
strokeWidth={'0'}
|
|
||||||
fillcolor={syncedToChain ? '#95de64' : '#ff7875'}
|
|
||||||
/>
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<Sub4Title>Channels</Sub4Title>
|
|
||||||
{`${active_channels_count} / ${pending_channels_count} / ${closed_channels_count} / ${peers_count}`}
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<Zap
|
|
||||||
color={channelPending === 0 ? '#FFD300' : '#652EC7'}
|
|
||||||
fillcolor={channelPending === 0 ? '#FFD300' : '#652EC7'}
|
|
||||||
/>
|
|
||||||
{channelPending > 0 ? (
|
|
||||||
`${formatCCB} / ${formatPCB}`
|
|
||||||
) : (
|
|
||||||
<AnimatedNumber amount={channelBalance} />
|
|
||||||
)}
|
|
||||||
</SingleLine>
|
|
||||||
<SingleLine>
|
|
||||||
<Anchor
|
|
||||||
color={chainPending === 0 ? '#FFD300' : '#652EC7'}
|
|
||||||
/>
|
|
||||||
{chainPending > 0 ? (
|
|
||||||
`${formatCB} / ${formatPB}`
|
|
||||||
) : (
|
|
||||||
<AnimatedNumber amount={chainBalance} />
|
|
||||||
)}
|
|
||||||
</SingleLine>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isOpen) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Closed>
|
|
||||||
<div data-tip data-for="full_balance_tip">
|
|
||||||
<Circle
|
|
||||||
strokeWidth={'0'}
|
|
||||||
fillcolor={syncedToChain ? '#95de64' : '#ff7875'}
|
|
||||||
/>
|
|
||||||
{(channelPending > 0 || chainPending > 0) && (
|
|
||||||
<div>
|
|
||||||
<Circle
|
|
||||||
fillcolor={'#652EC7'}
|
|
||||||
strokeWidth={'0'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Margin>
|
|
||||||
<Zap
|
|
||||||
fillcolor={
|
|
||||||
channelPending === 0 ? '#FFD300' : '#652EC7'
|
|
||||||
}
|
|
||||||
color={
|
|
||||||
channelPending === 0 ? '#FFD300' : '#652EC7'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Margin>
|
|
||||||
<Anchor
|
|
||||||
color={chainPending === 0 ? '#FFD300' : '#652EC7'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div data-tip data-for="full_node_tip">
|
|
||||||
<SingleLine>{active_channels_count}</SingleLine>
|
|
||||||
<SingleLine>{pending_channels_count}</SingleLine>
|
|
||||||
<SingleLine>{closed_channels_count}</SingleLine>
|
|
||||||
<SingleLine>{peers_count}</SingleLine>
|
|
||||||
</div>
|
|
||||||
</Closed>
|
|
||||||
<Separation lineColor={unSelectedNavButton} />
|
|
||||||
<ReactTooltip
|
|
||||||
id={'full_balance_tip'}
|
|
||||||
effect={'solid'}
|
|
||||||
place={'right'}
|
|
||||||
type={tooltipType}
|
|
||||||
>
|
|
||||||
<div>{`Channel Balance: ${formatCCB}`}</div>
|
|
||||||
<div>{`Pending Channel Balance: ${formatPCB}`}</div>
|
|
||||||
<div>{`Chain Balance: ${formatCB}`}</div>
|
|
||||||
<div>{`Pending Chain Balance: ${formatPB}`}</div>
|
|
||||||
</ReactTooltip>
|
|
||||||
<ReactTooltip
|
|
||||||
id={'full_node_tip'}
|
|
||||||
effect={'solid'}
|
|
||||||
place={'right'}
|
|
||||||
type={tooltipType}
|
|
||||||
>
|
|
||||||
<div>{`Active Channels: ${active_channels_count}`}</div>
|
|
||||||
<div>{`Pending Channels: ${pending_channels_count}`}</div>
|
|
||||||
<div>{`Closed Channels: ${closed_channels_count}`}</div>
|
|
||||||
<div>{`Peers: ${peers_count}`}</div>
|
|
||||||
</ReactTooltip>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Title>
|
|
||||||
<Alias bottomColor={color}>{alias}</Alias>
|
|
||||||
{isOpen && (
|
|
||||||
<QuestionIcon
|
|
||||||
data-tip={`Version: ${version.split(' ')[0]}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Title>
|
|
||||||
<Separation lineColor={unSelectedNavButton} />
|
|
||||||
<Balance data-tip data-for="balance_tip">
|
|
||||||
<Zap color={channelPending === 0 ? '#FFD300' : '#652EC7'} />
|
|
||||||
<AnimatedNumber amount={channelBalance} />
|
|
||||||
</Balance>
|
|
||||||
<Balance data-tip data-for="chain_balance_tip">
|
|
||||||
<Anchor color={chainPending === 0 ? '#FFD300' : '#652EC7'} />
|
|
||||||
<AnimatedNumber amount={chainBalance} />
|
|
||||||
</Balance>
|
|
||||||
<Balance
|
|
||||||
data-tip
|
|
||||||
data-for="node_tip"
|
|
||||||
>{`${active_channels_count} / ${pending_channels_count} / ${closed_channels_count} / ${peers_count}`}</Balance>
|
|
||||||
<Balance>
|
|
||||||
<Info bottomColor={syncedToChain ? '#95de64' : '#ff7875'}>
|
|
||||||
{syncedToChain ? 'Synced' : 'Not Synced'}
|
|
||||||
</Info>
|
|
||||||
</Balance>
|
|
||||||
<Separation lineColor={unSelectedNavButton} />
|
|
||||||
<ReactTooltip effect={'solid'} place={'right'} type={tooltipType} />
|
|
||||||
<ReactTooltip
|
|
||||||
id={'balance_tip'}
|
|
||||||
effect={'solid'}
|
|
||||||
place={'right'}
|
|
||||||
type={tooltipType}
|
|
||||||
>
|
|
||||||
<div>{`Channel Balance: ${formatCCB}`}</div>
|
|
||||||
<div>{`Pending Channel Balance: ${formatPCB}`}</div>
|
|
||||||
</ReactTooltip>
|
|
||||||
<ReactTooltip
|
|
||||||
id={'chain_balance_tip'}
|
|
||||||
effect={'solid'}
|
|
||||||
place={'right'}
|
|
||||||
type={tooltipType}
|
|
||||||
>
|
|
||||||
<div>{`Chain Balance: ${formatCB}`}</div>
|
|
||||||
<div>{`Pending Chain Balance: ${formatPB}`}</div>
|
|
||||||
</ReactTooltip>
|
|
||||||
<ReactTooltip
|
|
||||||
id={'node_tip'}
|
|
||||||
effect={'solid'}
|
|
||||||
place={'right'}
|
|
||||||
type={tooltipType}
|
|
||||||
>
|
|
||||||
<div>{`Active Channels: ${active_channels_count}`}</div>
|
|
||||||
<div>{`Pending Channels: ${pending_channels_count}`}</div>
|
|
||||||
<div>{`Closed Channels: ${closed_channels_count}`}</div>
|
|
||||||
<div>{`Peers: ${peers_count}`}</div>
|
|
||||||
</ReactTooltip>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,206 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Separation, SingleLine } from '../../../components/generic/Styled';
|
|
||||||
import { useSettings } from '../../../context/SettingsContext';
|
|
||||||
import {
|
|
||||||
Sun,
|
|
||||||
Moon,
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
} from '../../../components/generic/Icons';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import {
|
|
||||||
progressBackground,
|
|
||||||
iconButtonHover,
|
|
||||||
inverseTextColor,
|
|
||||||
unSelectedNavButton,
|
|
||||||
} from '../../../styles/Themes';
|
|
||||||
|
|
||||||
const SelectedIcon = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
outline: none;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 100%;
|
|
||||||
margin: 0 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
@media (min-width: 579px) {
|
|
||||||
&:hover {
|
|
||||||
background-color: ${iconButtonHover};
|
|
||||||
color: ${inverseTextColor};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background-color: ${({ selected }: { selected: boolean }) =>
|
|
||||||
selected ? progressBackground : ''};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Symbol = styled.div`
|
|
||||||
margin-top: 2px;
|
|
||||||
font-weight: bold;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const IconRow = styled.div`
|
|
||||||
margin: 5px 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
${({ center }: { center?: boolean }) => center && 'width: 100%'}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const BurgerPadding = styled(SingleLine)`
|
|
||||||
margin: 16px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const currencyArray = ['sat', 'btc', 'EUR', 'USD'];
|
|
||||||
const themeArray = ['light', 'dark'];
|
|
||||||
const currencyMap: { [key: string]: string } = {
|
|
||||||
sat: 'S',
|
|
||||||
btc: '₿',
|
|
||||||
EUR: '€',
|
|
||||||
USD: '$',
|
|
||||||
};
|
|
||||||
const themeMap: { [key: string]: string } = {
|
|
||||||
light: Sun,
|
|
||||||
dark: Moon,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNextValue = (array: string[], current: string): string => {
|
|
||||||
const length = array.length;
|
|
||||||
const index = array.indexOf(current);
|
|
||||||
|
|
||||||
let value = '';
|
|
||||||
if (index + 1 === length) {
|
|
||||||
value = array[0];
|
|
||||||
} else {
|
|
||||||
value = array[index + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface SideSettingsProps {
|
|
||||||
isOpen?: boolean;
|
|
||||||
isBurger?: boolean;
|
|
||||||
setIsOpen?: (state: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SideSettings = ({
|
|
||||||
isOpen,
|
|
||||||
isBurger,
|
|
||||||
setIsOpen,
|
|
||||||
}: SideSettingsProps) => {
|
|
||||||
const { theme, currency, setSettings } = useSettings();
|
|
||||||
|
|
||||||
const renderIcon = (
|
|
||||||
type: string,
|
|
||||||
value: string,
|
|
||||||
text: string,
|
|
||||||
on: boolean = false,
|
|
||||||
Icon?: any,
|
|
||||||
) => (
|
|
||||||
<SelectedIcon
|
|
||||||
selected={
|
|
||||||
(type === 'currency' ? currency === value : theme === value) ||
|
|
||||||
on
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
localStorage.setItem(type, value);
|
|
||||||
type === 'currency' &&
|
|
||||||
setSettings({
|
|
||||||
currency:
|
|
||||||
isOpen || isBurger
|
|
||||||
? value
|
|
||||||
: getNextValue(currencyArray, value),
|
|
||||||
});
|
|
||||||
type === 'theme' && setSettings({ theme: value });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{type === 'currency' && <Symbol>{text}</Symbol>}
|
|
||||||
{type === 'theme' && <Icon />}
|
|
||||||
</SelectedIcon>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderContent = () => {
|
|
||||||
if (!isOpen) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Separation lineColor={unSelectedNavButton} />
|
|
||||||
<IconRow center={true}>
|
|
||||||
{renderIcon(
|
|
||||||
'currency',
|
|
||||||
currency,
|
|
||||||
currencyMap[currency],
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
</IconRow>
|
|
||||||
<IconRow center={true}>
|
|
||||||
{renderIcon(
|
|
||||||
'theme',
|
|
||||||
getNextValue(themeArray, theme),
|
|
||||||
'',
|
|
||||||
true,
|
|
||||||
themeMap[getNextValue(themeArray, theme)],
|
|
||||||
)}
|
|
||||||
</IconRow>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Separation lineColor={unSelectedNavButton} />
|
|
||||||
<IconRow>
|
|
||||||
{renderIcon('currency', 'sat', 'S')}
|
|
||||||
{renderIcon('currency', 'btc', '₿')}
|
|
||||||
{renderIcon('currency', 'EUR', '€')}
|
|
||||||
{renderIcon('currency', 'USD', '$')}
|
|
||||||
</IconRow>
|
|
||||||
<IconRow>
|
|
||||||
{renderIcon('theme', 'light', '', false, Sun)}
|
|
||||||
{renderIcon('theme', 'dark', '', false, Moon)}
|
|
||||||
</IconRow>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isBurger) {
|
|
||||||
return (
|
|
||||||
<BurgerPadding>
|
|
||||||
<IconRow>
|
|
||||||
{renderIcon('currency', 'sat', 'S')}
|
|
||||||
{renderIcon('currency', 'btc', '₿')}
|
|
||||||
{renderIcon('currency', 'EUR', '€')}
|
|
||||||
{renderIcon('currency', 'USD', '$')}
|
|
||||||
</IconRow>
|
|
||||||
<IconRow>
|
|
||||||
{renderIcon('theme', 'light', '', false, Sun)}
|
|
||||||
{renderIcon('theme', 'dark', '', false, Moon)}
|
|
||||||
</IconRow>
|
|
||||||
</BurgerPadding>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{renderContent()}
|
|
||||||
{setIsOpen && (
|
|
||||||
<IconRow center={!isOpen}>
|
|
||||||
<SelectedIcon
|
|
||||||
selected={true}
|
|
||||||
onClick={() => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'sidebar',
|
|
||||||
(!isOpen).toString(),
|
|
||||||
);
|
|
||||||
setIsOpen({ sidebar: !isOpen });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isOpen ? <ChevronLeft /> : <ChevronRight />}
|
|
||||||
</SelectedIcon>
|
|
||||||
</IconRow>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,144 +0,0 @@
|
||||||
// This optional code is used to register a service worker.
|
|
||||||
// register() is not called by default.
|
|
||||||
|
|
||||||
// This lets the app load faster on subsequent visits in production, and gives
|
|
||||||
// it offline capabilities. However, it also means that developers (and users)
|
|
||||||
// will only see deployed updates on subsequent visits to a page, after all the
|
|
||||||
// existing tabs open on the page have been closed, since previously cached
|
|
||||||
// resources are updated in the background.
|
|
||||||
|
|
||||||
// To learn more about the benefits of this model and instructions on how to
|
|
||||||
// opt-in, read https://bit.ly/CRA-PWA
|
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
|
||||||
window.location.hostname === 'localhost' ||
|
|
||||||
// [::1] is the IPv6 localhost address.
|
|
||||||
window.location.hostname === '[::1]' ||
|
|
||||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
|
||||||
window.location.hostname.match(
|
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
type Config = {
|
|
||||||
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
|
||||||
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function register(config?: Config) {
|
|
||||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
|
||||||
// The URL constructor is available in all browsers that support SW.
|
|
||||||
const publicUrl = new URL(
|
|
||||||
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
|
|
||||||
window.location.href,
|
|
||||||
);
|
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
|
||||||
// from what our page is served on. This might happen if a CDN is used to
|
|
||||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
|
||||||
|
|
||||||
if (isLocalhost) {
|
|
||||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
|
||||||
checkValidServiceWorker(swUrl, config);
|
|
||||||
|
|
||||||
// Add some additional logging to localhost, pointing developers to the
|
|
||||||
// service worker/PWA documentation.
|
|
||||||
navigator.serviceWorker.ready.then(() => {
|
|
||||||
console.log(
|
|
||||||
'This web app is being served cache-first by a service ' +
|
|
||||||
'worker. To learn more, visit https://bit.ly/CRA-PWA',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Is not localhost. Just register service worker
|
|
||||||
registerValidSW(swUrl, config);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerValidSW(swUrl: string, config?: Config) {
|
|
||||||
navigator.serviceWorker
|
|
||||||
.register(swUrl)
|
|
||||||
.then((registration) => {
|
|
||||||
registration.onupdatefound = () => {
|
|
||||||
const installingWorker = registration.installing;
|
|
||||||
if (installingWorker == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
installingWorker.onstatechange = () => {
|
|
||||||
if (installingWorker.state === 'installed') {
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
// At this point, the updated precached content has been fetched,
|
|
||||||
// but the previous service worker will still serve the older
|
|
||||||
// content until all client tabs are closed.
|
|
||||||
console.log(
|
|
||||||
'New content is available and will be used when all ' +
|
|
||||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onUpdate) {
|
|
||||||
config.onUpdate(registration);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// At this point, everything has been precached.
|
|
||||||
// It's the perfect time to display a
|
|
||||||
// "Content is cached for offline use." message.
|
|
||||||
console.log('Content is cached for offline use.');
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onSuccess) {
|
|
||||||
config.onSuccess(registration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error during service worker registration:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
|
||||||
fetch(swUrl)
|
|
||||||
.then((response) => {
|
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
|
||||||
const contentType = response.headers.get('content-type');
|
|
||||||
if (
|
|
||||||
response.status === 404 ||
|
|
||||||
(contentType != null &&
|
|
||||||
contentType.indexOf('javascript') === -1)
|
|
||||||
) {
|
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
|
||||||
navigator.serviceWorker.ready.then((registration) => {
|
|
||||||
registration.unregister().then(() => {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Service worker found. Proceed as normal.
|
|
||||||
registerValidSW(swUrl, config);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log(
|
|
||||||
'No internet connection found. App is running in offline mode.',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unregister() {
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.ready.then((registration) => {
|
|
||||||
registration.unregister();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
@font-face {
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
src: url('../assets/fonts/Manrope/otf/Manrope-ExtraBold.otf')
|
|
||||||
format('opentype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: url('../assets/fonts/Manrope/otf/Manrope-Bold.otf') format('opentype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
src: url('../assets/fonts/Manrope/otf/Manrope-SemiBold.otf')
|
|
||||||
format('opentype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
src: url('../assets/fonts/Manrope/otf/Manrope-Medium.otf')
|
|
||||||
format('opentype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url('../assets/fonts/Manrope/otf/Manrope-Regular.otf')
|
|
||||||
format('opentype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 200;
|
|
||||||
src: url('../assets/fonts/Manrope/otf/Manrope-Light.otf') format('opentype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100;
|
|
||||||
src: url('../assets/fonts/Manrope/otf/Manrope-ExtraLight.otf')
|
|
||||||
format('opentype');
|
|
||||||
}
|
|
|
@ -1,187 +0,0 @@
|
||||||
import base64url from 'base64url';
|
|
||||||
import { saveAccounts } from './storage';
|
|
||||||
import { v5 as uuidv5 } from 'uuid';
|
|
||||||
|
|
||||||
const THUNDERHUB_NAMESPACE = '00000000-0000-0000-0000-000000000000';
|
|
||||||
|
|
||||||
interface BuildProps {
|
|
||||||
name?: string;
|
|
||||||
host: string;
|
|
||||||
admin?: string;
|
|
||||||
viewOnly?: string;
|
|
||||||
cert?: string;
|
|
||||||
accounts: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const saveUserAuth = ({
|
|
||||||
name = '',
|
|
||||||
host,
|
|
||||||
admin = '',
|
|
||||||
viewOnly = '',
|
|
||||||
cert = '',
|
|
||||||
accounts,
|
|
||||||
}: BuildProps) => {
|
|
||||||
const id = getAccountId(host, viewOnly, admin, cert);
|
|
||||||
const newAccount = {
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const newAccounts = [...accounts, newAccount];
|
|
||||||
saveAccounts(newAccounts);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAccountId = (
|
|
||||||
host: string = '',
|
|
||||||
viewOnly: string = '',
|
|
||||||
admin: string = '',
|
|
||||||
cert: string = '',
|
|
||||||
) =>
|
|
||||||
uuidv5(
|
|
||||||
`${host}-${viewOnly}-${admin !== '' ? 1 : 0}-${cert}`,
|
|
||||||
THUNDERHUB_NAMESPACE,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const saveSessionAuth = (sessionAdmin: string) =>
|
|
||||||
sessionStorage.setItem('session', sessionAdmin);
|
|
||||||
|
|
||||||
export const getAuth = (account?: string) => {
|
|
||||||
const accounts = JSON.parse(localStorage.getItem('accounts') || '[]');
|
|
||||||
const currentActive = Math.max(
|
|
||||||
parseInt(account ?? (localStorage.getItem('active') || '0')),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
const sessionAdmin = sessionStorage.getItem('session') || '';
|
|
||||||
|
|
||||||
const accountsLength = accounts.length;
|
|
||||||
|
|
||||||
const active =
|
|
||||||
accountsLength > 0 && currentActive >= accountsLength
|
|
||||||
? 0
|
|
||||||
: currentActive;
|
|
||||||
|
|
||||||
const defaultAccount = {
|
|
||||||
name: '',
|
|
||||||
host: '',
|
|
||||||
admin: '',
|
|
||||||
viewOnly: '',
|
|
||||||
cert: '',
|
|
||||||
id: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeAccount =
|
|
||||||
accountsLength > 0 && active < accountsLength
|
|
||||||
? accounts[active]
|
|
||||||
: defaultAccount;
|
|
||||||
|
|
||||||
const { name, host, admin, viewOnly, cert, id } = activeAccount;
|
|
||||||
const currentId =
|
|
||||||
id ??
|
|
||||||
uuidv5(
|
|
||||||
`${host}-${viewOnly}-${admin !== '' ? 1 : 0}-${cert}`,
|
|
||||||
THUNDERHUB_NAMESPACE,
|
|
||||||
);
|
|
||||||
const loggedIn = host !== '' && (viewOnly !== '' || sessionAdmin !== '');
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
cert,
|
|
||||||
id: currentId,
|
|
||||||
accounts,
|
|
||||||
loggedIn,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAuthLnd = (lndconnect: string) => {
|
|
||||||
const auth = lndconnect.replace('lndconnect', 'https');
|
|
||||||
|
|
||||||
let url;
|
|
||||||
|
|
||||||
try {
|
|
||||||
url = new URL(auth);
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
cert: '',
|
|
||||||
macaroon: '',
|
|
||||||
socket: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const cert = url.searchParams.get('cert') || '';
|
|
||||||
const macaroon = url.searchParams.get('macaroon') || '';
|
|
||||||
const socket = url.host;
|
|
||||||
|
|
||||||
return {
|
|
||||||
cert: base64url.toBase64(cert),
|
|
||||||
macaroon: base64url.toBase64(macaroon),
|
|
||||||
socket,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getBase64CertfromDerFormat = (base64: string) => {
|
|
||||||
if (!base64) return null;
|
|
||||||
|
|
||||||
const prefix = '-----BEGIN CERTIFICATE-----\n';
|
|
||||||
const postfix = '-----END CERTIFICATE-----';
|
|
||||||
const pem = base64.match(/.{0,64}/g) || [];
|
|
||||||
const pemString = pem.join('\n');
|
|
||||||
const pemComplete = prefix + pemString + postfix;
|
|
||||||
const pemText = base64url.encode(pemComplete);
|
|
||||||
|
|
||||||
return pemText;
|
|
||||||
};
|
|
||||||
|
|
||||||
const emptyObject = {
|
|
||||||
cert: undefined,
|
|
||||||
admin: undefined,
|
|
||||||
viewOnly: undefined,
|
|
||||||
host: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getConfigLnd = (json: string) => {
|
|
||||||
const parsedJson = JSON.parse(json);
|
|
||||||
|
|
||||||
const config = parsedJson.configurations;
|
|
||||||
|
|
||||||
if (config && config.length >= 1) {
|
|
||||||
const cert = config[0].certificateThumbprint || '';
|
|
||||||
const admin = config[0].adminMacaroon;
|
|
||||||
const viewOnly = config[0].readonlyMacaroon;
|
|
||||||
const host = config[0].host;
|
|
||||||
const port = config[0].port;
|
|
||||||
|
|
||||||
return {
|
|
||||||
cert,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
host: `${host}:${port}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return emptyObject;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getQRConfig = (json: string) => {
|
|
||||||
const config = JSON.parse(json);
|
|
||||||
|
|
||||||
if (config) {
|
|
||||||
const { name = '', cert = '', admin, viewOnly, host } = config;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
cert,
|
|
||||||
admin,
|
|
||||||
viewOnly,
|
|
||||||
host,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...emptyObject, name: undefined };
|
|
||||||
};
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue