feat: privacy settings (#37)

* feat:  privacy configs

* chore: 🔧 chat polling speed

* refactor: ♻️ chat state

* chore: 🔧 move config to cookie
This commit is contained in:
Anthony Potdevin 2020-05-11 06:21:16 +02:00 committed by GitHub
parent ef5c2d16f4
commit 3d29616300
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 664 additions and 434 deletions

View file

@ -4,6 +4,11 @@ module.exports = {
parserOptions: { parserOptions: {
ecmaFeatures: { jsx: true }, ecmaFeatures: { jsx: true },
}, },
env: {
browser: true,
amd: true,
node: true,
},
plugins: ['react', 'jest', 'import', 'prettier'], plugins: ['react', 'jest', 'import', 'prettier'],
settings: { settings: {
react: { react: {
@ -37,7 +42,12 @@ module.exports = {
'import/no-unresolved': 'off', 'import/no-unresolved': 'off',
camelcase: 'off', camelcase: 'off',
'@typescript-eslint/camelcase': 'off', '@typescript-eslint/camelcase': 'off',
'prettier/prettier': 'error',
'react/prop-types': 'off', 'react/prop-types': 'off',
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
}, },
}; };

View file

@ -1,6 +1,3 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true
"editor.codeActionsOnSave": { }
"source.fixAll.eslint": true
}
}

127
README.md
View file

@ -1,22 +1,32 @@
# **ThunderHub - Lightning Node Manager** # **ThunderHub - Lightning Node Manager**
![Home Screenshot](./docs/Home.png) ![Home Screenshot](/docs/Home.png)
[![license](https://img.shields.io/github/license/DAVFoundation/captain-n3m0.svg?style=flat-square)](https://github.com/DAVFoundation/captain-n3m0/blob/master/LICENSE) [![license](https://img.shields.io/github/license/DAVFoundation/captain-n3m0.svg?style=flat-square)](https://github.com/DAVFoundation/captain-n3m0/blob/master/LICENSE)
## Table Of Contents ## Table Of Contents
- [Introduction](#introduction) - [Introduction](#introduction)
- [Integrations](#integrations)
- [Features](#features) - [Features](#features)
- [Installation](#installation) - [Installation](#installation)
- [Development](#development) - [Development](#development)
- [Docker deployment](#docker)
## Introduction ## Introduction
ThunderHub is an **open-source** LND node manager where you can manage and monitor your node on any device or browser. It allows you to take control of the lightning network with a simple and intuitive UX and the most up-to-date tech stack. ThunderHub is an **open-source** LND node manager where you can manage and monitor your node on any device or browser. It allows you to take control of the lightning network with a simple and intuitive UX and the most up-to-date tech stack.
### Integrations
**BTCPay Server**
ThunderHub is currently integrated into BTCPay for easier deployment. If you already have a BTCPay server and want to add ThunderHub or even want to start a BTCPay server from zero, be sure to check out this [tutorial](https://apotdevin.com/blog/thunderhub-btcpay)
**Raspiblitz**
For Raspiblitz users you can also get ThunderHub running by following this [gist](https://gist.github.com/openoms/8ba963915c786ce01892f2c9fa2707bc)
### Tech Stack ### Tech Stack
This repository consists of a **NextJS** server that handles both the backend **Graphql Server** and the frontend **React App**. This repository consists of a **NextJS** server that handles both the backend **Graphql Server** and the frontend **React App**. ThunderHub connects to your Lightning Network node by using the gRPC ports.
- NextJS - NextJS
- ReactJS - ReactJS
@ -50,7 +60,7 @@ This repository consists of a **NextJS** server that handles both the backend **
- Send and Receive Bitcoin payments. - Send and Receive Bitcoin payments.
- Decode lightning payment requests. - Decode lightning payment requests.
- Open and close channels. - Open and close channels.
- Balance your channels through circular payments. ([Check out the Tutorial](https://medium.com/coinmonks/lightning-network-channel-balancing-with-thunderhub-972b41bf9243)) - Balance your channels through circular payments. ([Check out the Tutorial](https://apotdevin.com/blog/thunderhub-balancing))
- Update your all your channels fees or individual ones. - Update your all your channels fees or individual ones.
- Backup, verify and recover all your channels. - Backup, verify and recover all your channels.
- Sign and verify messages. - Sign and verify messages.
@ -70,54 +80,135 @@ This repository consists of a **NextJS** server that handles both the backend **
### Deployment ### Deployment
- Docker images for easier deployment (WIP) - Docker images for easier deployment
### Future Features ### Future Features
- Channel health/recommendations view
- Loop In and Out to provide liquidity or remove it from your channels. - Loop In and Out to provide liquidity or remove it from your channels.
- Integration with HodlHodl
- Storefront interface - Storefront interface
## **Requirements**
- Yarn/npm installed
- Node installed (Version 12.16.0 or higher)
**Older Versions of Node**
Earlier versions of Node can be used if you replace the following commands:
```js
//Yarn
yarn start -> yarn start:compatible
yarn dev -> yarn dev:compatible
//NPM
npm start -> npm start:compatible
npm run dev -> npm run dev:compatible
```
**HodlHodl integration will not work with older versions of Node!**
## Config
You can define some environment variables that ThunderHub can start with. To do this create a `.env` file in the root directory with the following parameters:
```js
THEME = 'dark' | 'light'; // Default: 'dark'
CURRENCY = 'sat' | 'btc' | 'eur' | 'usd'; // Default: 'sat'
FETCH_PRICES = true | false // Default: true
FETCH_FEES = true | false // Default: true
HODL_KEY='[Key provided by HodlHodl]' //Default: ''
BASE_PATH='[Base path where you want to have thunderhub running i.e. '/btcpay']' //Default: '/'
```
### Fetching prices and fees
ThunderHub fetches fiat prices from [Blockchain.com](https://blockchain.info/ticker)'s api and bitcoin on chain fees from [Earn.com](https://bitcoinfees.earn.com/api/v1/fees/recommended)'s api.
If you want to deactivate these requests you can set `FETCH_PRICES=false` and `FETCH_FEES=false` in your `.env` file or manually change them inside the settings view of ThunderHub.
### Running on different base path
Adding a BASE_PATH will run the ThunderHub server on a different base path.
For example:
- default base path of `/` runs ThunderHub on `http://localhost:3000`
- base path of `/thub` runs ThunderHub on `http://localhost:3000/thub`
To run on a base path, ThunderHub needs to be behind a proxy with the following configuration (NGINX example):
```nginx
location /thub/ {
rewrite ^/thub(.*)$ $1 break;
proxy_pass http://localhost:3000/;
}
```
## Installation ## Installation
To run ThunderHub you first need to clone this repository. To run ThunderHub you first need to clone this repository.
```javascript ```js
git clone https://github.com/apotdevin/thunderhub.git git clone https://github.com/apotdevin/thunderhub.git
``` ```
### **Requirements** After cloning the repository run `yarn` or `npm install` to get all the necessary modules installed.
- Node installed After all the dependencies have finished installing, you can proceed to build and run the app with the following commands.
- Yarn installed
After cloning the repository run `yarn` to get all the necessary modules installed.
After `yarn` has finished installing all the dependencies you can proceed to build and run the app with the following commands.
```javascript ```javascript
//Yarn
yarn build yarn build
yarn start yarn start
//NPM
npm run build
npm start
``` ```
This will start the server on port 3000, so just head to `localhost:3000` to see the app running. This will start the server on port 3000, so just go to `localhost:3000` to see the app running.
#### HodlHodl Integration If you want to specify a different port (for example port `4000`) run with:
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. ```js
// Yarn
yarn start -p 4000
// NPM
npm start -p 4000
```
## 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:
```javascript ```js
//Yarn
yarn dev yarn dev
//NPM
npm run dev
``` ```
#### Storybook #### Storybook
You can also get storybook running for quicker component development. You can also get storybook running for quicker component development.
```javascript ```js
//Yarn
yarn storybook yarn storybook
//NPM
npm run storybook
``` ```
## Docker
ThunderHub also provides docker images for easier deployment. [Docker Hub](https://hub.docker.com/repository/docker/apotdevin/thunderhub)
To get ThunderHub running with docker follow these steps:
1. `docker pull apotdevin/thunderhub:v0.5.5` (Or the latest version you find)
2. `docker run --rm -it -p 3000:3000/tcp apotdevin/thunderhub:v0.5.5`
You can now go to `localhost:3000` to see your running instance of ThunderHub

View file

@ -1,3 +1,4 @@
/* eslint @typescript-eslint/no-var-requires: 0 */
require('dotenv').config(); require('dotenv').config();
const withBundleAnalyzer = require('@next/bundle-analyzer')({ const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true', enabled: process.env.ANALYZE === 'true',
@ -17,6 +18,9 @@ module.exports = withBundleAnalyzer({
apiBaseUrl: `${process.env.API_BASE_URL || ''}/api/v1`, apiBaseUrl: `${process.env.API_BASE_URL || ''}/api/v1`,
basePath: process.env.BASE_PATH || '', basePath: process.env.BASE_PATH || '',
npmVersion: process.env.npm_package_version || '0.0.0', npmVersion: process.env.npm_package_version || '0.0.0',
trustNeeded: process.env.TRUST || false, defaultTheme: process.env.THEME || 'dark',
defaultCurrency: process.env.CURRENCY || 'sat',
fetchPrices: process.env.FETCH_PRICES === 'true' ? true : false,
fetchFees: process.env.FETCH_FEES === 'true' ? true : false,
}, },
}); });

View file

@ -5,8 +5,10 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"dev": "cross-env NODE_OPTIONS='--insecure-http-parser' next", "dev": "cross-env NODE_OPTIONS='--insecure-http-parser' next",
"dev:compatible": "next",
"build": "next build", "build": "next build",
"start": "cross-env NODE_OPTIONS='--insecure-http-parser' next start", "start": "cross-env NODE_OPTIONS='--insecure-http-parser' next start",
"start:compatible": "next start",
"lint": "eslint */**/*.{js,ts,tsx} --quiet --fix", "lint": "eslint */**/*.{js,ts,tsx} --quiet --fix",
"prettier": "prettier --write **/*.{ts,tsx,js,css,html}", "prettier": "prettier --write **/*.{ts,tsx,js,css,html}",
"release": "standard-version", "release": "standard-version",
@ -33,6 +35,7 @@
"apollo-boost": "^0.4.7", "apollo-boost": "^0.4.7",
"apollo-server-micro": "^2.12.0", "apollo-server-micro": "^2.12.0",
"base64url": "^3.0.1", "base64url": "^3.0.1",
"cookie": "^0.4.1",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"date-fns": "^2.12.0", "date-fns": "^2.12.0",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
@ -42,6 +45,7 @@
"graphql-tag": "^2.10.3", "graphql-tag": "^2.10.3",
"intersection-observer": "^0.10.0", "intersection-observer": "^0.10.0",
"isomorphic-unfetch": "^3.0.0", "isomorphic-unfetch": "^3.0.0",
"js-cookie": "^2.2.1",
"ln-service": "^48.0.5", "ln-service": "^48.0.5",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.groupby": "^4.6.0", "lodash.groupby": "^4.6.0",

View file

@ -1,8 +1,7 @@
import App from 'next/app'; import * as React from 'react';
import React from 'react';
import { ContextProvider } from '../src/context/ContextProvider'; import { ContextProvider } from '../src/context/ContextProvider';
import { ThemeProvider } from 'styled-components'; import { ThemeProvider } from 'styled-components';
import { useSettings } from '../src/context/SettingsContext'; import { useConfigState, ConfigProvider } from '../src/context/ConfigContext';
import { ModalProvider, BaseModalBackground } from 'styled-react-modal'; import { ModalProvider, BaseModalBackground } from 'styled-react-modal';
import { GlobalStyles } from '../src/styles/GlobalStyle'; import { GlobalStyles } from '../src/styles/GlobalStyle';
import { Header } from '../src/layouts/header/Header'; import { Header } from '../src/layouts/header/Header';
@ -20,11 +19,12 @@ import { PageWrapper, HeaderBodyWrapper } from '../src/layouts/Layout.styled';
import { useStatusState } from '../src/context/StatusContext'; import { useStatusState } from '../src/context/StatusContext';
import { ChatFetcher } from '../src/components/chat/ChatFetcher'; import { ChatFetcher } from '../src/components/chat/ChatFetcher';
import { ChatInit } from '../src/components/chat/ChatInit'; import { ChatInit } from '../src/components/chat/ChatInit';
import { parseCookies } from '../src/utils/cookies';
toast.configure({ draggable: false, pauseOnFocusLoss: false }); toast.configure({ draggable: false, pauseOnFocusLoss: false });
const Wrapper: React.FC = ({ children }) => { const Wrapper: React.FC = ({ children }) => {
const { theme } = useSettings(); const { theme } = useConfigState();
const { pathname } = useRouter(); const { pathname } = useRouter();
const { connected } = useStatusState(); const { connected } = useStatusState();
@ -63,24 +63,36 @@ const Wrapper: React.FC = ({ children }) => {
); );
}; };
class MyApp extends App<any> { const App = ({ Component, pageProps, apollo, initialConfig }: any) => (
render() { <>
const { Component, pageProps, apollo } = this.props; <Head>
return ( <title>ThunderHub - Lightning Node Manager</title>
<> </Head>
<Head> <ApolloProvider client={apollo}>
<title>ThunderHub - Lightning Node Manager</title> <ConfigProvider initialConfig={initialConfig}>
</Head> <ContextProvider>
<ApolloProvider client={apollo}> <Wrapper>
<ContextProvider> <Component {...pageProps} />
<Wrapper> </Wrapper>
<Component {...pageProps} /> </ContextProvider>
</Wrapper> </ConfigProvider>
</ContextProvider> </ApolloProvider>
</ApolloProvider> </>
</> );
);
}
}
export default withApollo(MyApp); App.getInitialProps = async props => {
const cookies = parseCookies(props.ctx.req);
if (!cookies?.config) {
return { initialConfig: {} };
}
try {
const config = JSON.parse(cookies.config);
return {
initialConfig: config,
};
} catch (error) {
return { initialConfig: {} };
}
};
export default withApollo(App);

View file

@ -10,7 +10,7 @@ import {
ColorButton, ColorButton,
} from '../src/components/generic/Styled'; } from '../src/components/generic/Styled';
import { useAccount } from '../src/context/AccountContext'; import { useAccount } from '../src/context/AccountContext';
import { useSettings } from '../src/context/SettingsContext'; import { useConfigState } from '../src/context/ConfigContext';
import { textColorMap } from '../src/styles/Themes'; import { textColorMap } from '../src/styles/Themes';
import { useGetChannelAmountInfoQuery } from '../src/generated/graphql'; import { useGetChannelAmountInfoQuery } from '../src/generated/graphql';
@ -22,7 +22,7 @@ const ChannelView = () => {
closed: 0, closed: 0,
}); });
const { theme } = useSettings(); const { theme } = useConfigState();
const { auth } = useAccount(); const { auth } = useAccount();
const { data } = useGetChannelAmountInfoQuery({ const { data } = useGetChannelAmountInfoQuery({

View file

@ -48,7 +48,7 @@ const FeesView = () => {
? toast.success('Fees Updated') ? toast.success('Fees Updated')
: toast.error('Error updating fees'); : toast.error('Error updating fees');
}, },
refetchQueries: ['GetChannelFees'], refetchQueries: ['ChannelFees'],
}); });
if (loading || !data || !data.getChannelFees) { if (loading || !data || !data.getChannelFees) {

View file

@ -13,7 +13,7 @@ import { getErrorContent } from '../src/utils/error';
import { LoadingCard } from '../src/components/loading/LoadingCard'; import { LoadingCard } from '../src/components/loading/LoadingCard';
import { ForwardCard } from '../src/views/forwards/ForwardsCard'; import { ForwardCard } from '../src/views/forwards/ForwardsCard';
import { textColorMap } from '../src/styles/Themes'; import { textColorMap } from '../src/styles/Themes';
import { useSettings } from '../src/context/SettingsContext'; import { useConfigState } from '../src/context/ConfigContext';
import { ForwardBox } from '../src/views/home/reports/forwardReport'; import { ForwardBox } from '../src/views/home/reports/forwardReport';
import { useGetForwardsQuery } from '../src/generated/graphql'; import { useGetForwardsQuery } from '../src/generated/graphql';
@ -28,7 +28,7 @@ const ForwardsView = () => {
const [time, setTime] = useState('week'); const [time, setTime] = useState('week');
const [indexOpen, setIndexOpen] = useState(0); const [indexOpen, setIndexOpen] = useState(0);
const { theme } = useSettings(); const { theme } = useConfigState();
const { auth } = useAccount(); const { auth } = useAccount();
const { loading, data } = useGetForwardsQuery({ const { loading, data } = useGetForwardsQuery({

View file

@ -8,6 +8,7 @@ import { DangerView } from '../src/views/settings/Danger';
import { CurrentSettings } from '../src/views/settings/Current'; import { CurrentSettings } from '../src/views/settings/Current';
import { SyncSettings } from '../src/views/settings/Sync'; import { SyncSettings } from '../src/views/settings/Sync';
import { ChatSettings } from '../src/views/settings/Chat'; import { ChatSettings } from '../src/views/settings/Chat';
import { PrivacySettings } from '../src/views/settings/Privacy';
export const ButtonRow = styled.div` export const ButtonRow = styled.div`
width: auto; width: auto;
@ -30,6 +31,7 @@ const SettingsView = () => {
return ( return (
<> <>
<InterfaceSettings /> <InterfaceSettings />
<PrivacySettings />
<ChatSettings /> <ChatSettings />
<SyncSettings /> <SyncSettings />
<CurrentSettings /> <CurrentSettings />

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useSpring, animated } from 'react-spring'; import { useSpring, animated } from 'react-spring';
import { getValue } from '../../utils/helpers'; import { getValue } from '../../utils/helpers';
import { useSettings } from '../../context/SettingsContext'; import { useConfigState } from '../../context/ConfigContext';
import { usePriceState } from '../../context/PriceContext'; import { usePriceState } from '../../context/PriceContext';
type PriceProps = { type PriceProps = {
@ -19,8 +19,12 @@ export const AnimatedNumber = ({ amount = 0 }: AnimatedProps) => {
from: { value: 0 }, from: { value: 0 },
value: amount, value: amount,
}); });
const { currency } = useSettings(); const { currency, displayValues } = useConfigState();
const { prices } = usePriceState(); const { prices, dontShow } = usePriceState();
if (!displayValues) {
return <>-</>;
}
let priceProps: PriceProps = { let priceProps: PriceProps = {
price: 0, price: 0,
@ -28,7 +32,7 @@ export const AnimatedNumber = ({ amount = 0 }: AnimatedProps) => {
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency, currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
}; };
if (prices) { if (prices && !dontShow) {
const current: { last: number; symbol: string } = prices[currency] ?? { const current: { last: number; symbol: string } = prices[currency] ?? {
last: 0, last: 0,
symbol: '', symbol: '',

View file

@ -1,13 +1,7 @@
import React from 'react'; import React from 'react';
import { Checkbox } from '../../checkbox/Checkbox'; import { Checkbox } from '../../checkbox/Checkbox';
import { CheckboxText, StyledContainer, FixedWidth } from '../Auth.styled'; import { CheckboxText } from '../Auth.styled';
import { AlertCircle } from 'react-feather';
import { fontColors } from '../../../styles/Themes';
import { ColorButton } from '../../buttons/colorButton/ColorButton'; import { ColorButton } from '../../buttons/colorButton/ColorButton';
import getConfig from 'next/config';
const { publicRuntimeConfig } = getConfig();
const { trustNeeded } = publicRuntimeConfig;
type CheckboxProps = { type CheckboxProps = {
handleClick: () => void; handleClick: () => void;
@ -37,24 +31,5 @@ export const RiskCheckboxAndConfirm = ({
> >
Connect Connect
</ColorButton> </ColorButton>
<WarningBox />
</> </>
); );
export const WarningBox = () => {
if (!trustNeeded) {
return null;
}
return (
<StyledContainer>
<FixedWidth>
<AlertCircle size={18} 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>
);
};

View file

@ -1,27 +1,34 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useBitcoinDispatch } from '../../context/BitcoinContext'; import { useBitcoinDispatch } from '../../context/BitcoinContext';
import { useGetBitcoinFeesQuery } from '../../generated/graphql'; import { useGetBitcoinFeesQuery } from '../../generated/graphql';
import { useConfigState } from '../../context/ConfigContext';
export const BitcoinFees = () => { export const BitcoinFees = () => {
const { fetchFees } = useConfigState();
const setInfo = useBitcoinDispatch(); const setInfo = useBitcoinDispatch();
const { loading, data, stopPolling } = useGetBitcoinFeesQuery({ const { loading, data, stopPolling } = useGetBitcoinFeesQuery({
skip: !fetchFees,
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
onError: () => { onError: () => {
setInfo({ type: 'error' }); setInfo({ type: 'dontShow' });
stopPolling(); stopPolling();
}, },
pollInterval: 60000, pollInterval: 60000,
}); });
useEffect(() => { useEffect(() => {
if (!loading && data && data.getBitcoinFees) { if (!fetchFees) {
const { fast, halfHour, hour } = data.getBitcoinFees; setInfo({ type: 'dontShow' });
setInfo({
type: 'fetched',
state: { loading: false, error: false, fast, halfHour, hour },
});
} }
}, [data, loading, setInfo]); }, [fetchFees, setInfo]);
useEffect(() => {
if (!loading && data && data.getBitcoinFees && fetchFees) {
const { fast, halfHour, hour } = data.getBitcoinFees;
setInfo({ type: 'fetched', state: { fast, halfHour, hour } });
}
}, [data, loading, setInfo, fetchFees]);
return null; return null;
}; };

View file

@ -1,29 +1,38 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { usePriceDispatch } from '../../context/PriceContext'; import { usePriceDispatch } from '../../context/PriceContext';
import { useGetBitcoinPriceQuery } from '../../generated/graphql'; import { useGetBitcoinPriceQuery } from '../../generated/graphql';
import { useConfigState } from '../../context/ConfigContext';
export const BitcoinPrice = () => { export const BitcoinPrice = () => {
const { fetchPrices } = useConfigState();
const setPrices = usePriceDispatch(); const setPrices = usePriceDispatch();
const { loading, data, stopPolling } = useGetBitcoinPriceQuery({ const { loading, data, stopPolling } = useGetBitcoinPriceQuery({
skip: !fetchPrices,
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
onError: () => setPrices({ type: 'error' }), onError: () => {
setPrices({ type: 'dontShow' });
stopPolling();
},
pollInterval: 60000, pollInterval: 60000,
}); });
useEffect(() => { useEffect(() => {
if (!loading && data && data.getBitcoinPrice) { if (!fetchPrices) {
setPrices({ type: 'dontShow' });
}
}, [fetchPrices, setPrices]);
useEffect(() => {
if (!loading && data && data.getBitcoinPrice && fetchPrices) {
try { try {
const prices = JSON.parse(data.getBitcoinPrice); const prices = JSON.parse(data.getBitcoinPrice);
setPrices({ setPrices({ type: 'fetched', state: { prices } });
type: 'fetched',
state: { loading: false, error: false, prices },
});
} catch (error) { } catch (error) {
setPrices({ type: 'dontShow' });
stopPolling(); stopPolling();
setPrices({ type: 'error' });
} }
} }
}, [data, loading, setPrices, stopPolling]); }, [data, loading, setPrices, stopPolling, fetchPrices]);
return null; return null;
}; };

View file

@ -5,10 +5,13 @@ import { useAccount } from '../../context/AccountContext';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { getErrorContent } from '../../utils/error'; import { getErrorContent } from '../../utils/error';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useConfigState } from '../../context/ConfigContext';
export const ChatFetcher = () => { export const ChatFetcher = () => {
const newChatToastId = 'newChatToastId'; const newChatToastId = 'newChatToastId';
const { chatPollingSpeed } = useConfigState();
const { auth } = useAccount(); const { auth } = useAccount();
const { pathname } = useRouter(); const { pathname } = useRouter();
const { lastChat, chats, sentChats, initialized } = useChatState(); const { lastChat, chats, sentChats, initialized } = useChatState();
@ -18,7 +21,7 @@ export const ChatFetcher = () => {
const { data, loading, error } = useGetMessagesQuery({ const { data, loading, error } = useGetMessagesQuery({
skip: !auth || initialized || noChatsAvailable, skip: !auth || initialized || noChatsAvailable,
pollInterval: 1000, pollInterval: chatPollingSpeed,
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
variables: { auth, initialize: !noChatsAvailable }, variables: { auth, initialize: !noChatsAvailable },
onError: error => toast.error(getErrorContent(error)), onError: error => toast.error(getErrorContent(error)),
@ -58,7 +61,7 @@ export const ChatFetcher = () => {
const last = newMessages[0]?.id; const last = newMessages[0]?.id;
dispatch({ type: 'additional', chats: newMessages, lastChat: last }); dispatch({ type: 'additional', chats: newMessages, lastChat: last });
} }
}, [data, loading, error]); }, [data, loading, error, dispatch, lastChat, pathname]);
return null; return null;
}; };

View file

@ -19,10 +19,6 @@ export const ChatInit = () => {
React.useEffect(() => { React.useEffect(() => {
const storageChats = localStorage.getItem(`${id}-sentChats`) || ''; const storageChats = localStorage.getItem(`${id}-sentChats`) || '';
const hideFee = localStorage.getItem('hideFee') === 'true' ? true : false;
const hideNonVerified =
localStorage.getItem('hideNonVerified') === 'true' ? true : false;
const maxFee = Number(localStorage.getItem('maxChatFee')) || 20;
if (storageChats !== '') { if (storageChats !== '') {
try { try {
@ -33,9 +29,6 @@ export const ChatInit = () => {
type: 'initialized', type: 'initialized',
sentChats: savedChats, sentChats: savedChats,
sender, sender,
hideFee,
hideNonVerified,
maxFee,
}); });
} }
} catch (error) { } catch (error) {
@ -43,7 +36,7 @@ export const ChatInit = () => {
} }
} }
getMessages(); getMessages();
}, []); }, [dispatch, getMessages, id]);
React.useEffect(() => { React.useEffect(() => {
if (!initLoading && !initError && initData?.getMessages) { if (!initLoading && !initError && initData?.getMessages) {
@ -64,7 +57,7 @@ export const ChatInit = () => {
sender, sender,
}); });
} }
}, [initLoading, initError, initData]); }, [initLoading, initError, initData, dispatch]);
return null; return null;
}; };

View file

@ -41,13 +41,13 @@ export const CloseChannel = ({
channelId, channelId,
channelName, channelName,
}: CloseChannelProps) => { }: CloseChannelProps) => {
const { fast, halfHour, hour, dontShow } = useBitcoinState();
const [isForce, setIsForce] = useState<boolean>(false); const [isForce, setIsForce] = useState<boolean>(false);
const [isType, setIsType] = useState<string>('none'); const [isType, setIsType] = useState<string>(dontShow ? 'fee' : 'none');
const [amount, setAmount] = useState<number>(0); const [amount, setAmount] = useState<number>(0);
const [isConfirmed, setIsConfirmed] = useState<boolean>(false); const [isConfirmed, setIsConfirmed] = useState<boolean>(false);
const { fast, halfHour, hour } = useBitcoinState();
const [closeChannel] = useCloseChannelMutation({ const [closeChannel] = useCloseChannelMutation({
onCompleted: data => { onCompleted: data => {
if (data.closeChannel) { if (data.closeChannel) {
@ -113,7 +113,8 @@ export const CloseChannel = ({
<Sub4Title>Fee:</Sub4Title> <Sub4Title>Fee:</Sub4Title>
</SingleLine> </SingleLine>
<MultiButton> <MultiButton>
{renderButton(() => setIsType('none'), 'Auto', isType === 'none')} {!dontShow &&
renderButton(() => setIsType('none'), 'Auto', isType === 'none')}
{renderButton(() => setIsType('fee'), 'Fee', isType === 'fee')} {renderButton(() => setIsType('fee'), 'Fee', isType === 'fee')}
{renderButton(() => setIsType('target'), 'Target', isType === 'target')} {renderButton(() => setIsType('target'), 'Target', isType === 'target')}
</MultiButton> </MultiButton>

View file

@ -11,7 +11,7 @@ import {
import { HelpCircle } from 'react-feather'; import { HelpCircle } from 'react-feather';
import styled from 'styled-components'; import styled from 'styled-components';
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
import { useSettings } from '../../context/SettingsContext'; import { useConfigState } from '../../context/ConfigContext';
import { getTooltipType } from '../generic/helpers'; import { getTooltipType } from '../generic/helpers';
const StyledQuestion = styled(HelpCircle)` const StyledQuestion = styled(HelpCircle)`
@ -20,10 +20,9 @@ const StyledQuestion = styled(HelpCircle)`
export const NodeBar = () => { export const NodeBar = () => {
const { accounts } = useAccount(); const { accounts } = useAccount();
const { nodeInfo } = useSettings(); const { multiNodeInfo, theme } = useConfigState();
const slider = React.useRef<HTMLDivElement>(null); const slider = React.useRef<HTMLDivElement>(null);
const { theme } = useSettings();
const tooltipType: any = getTooltipType(theme); const tooltipType: any = getTooltipType(theme);
const viewOnlyAccounts = accounts.filter(account => account.viewOnly !== ''); const viewOnlyAccounts = accounts.filter(account => account.viewOnly !== '');
@ -38,7 +37,7 @@ export const NodeBar = () => {
} }
}; };
if (viewOnlyAccounts.length <= 1 || !nodeInfo) { if (viewOnlyAccounts.length <= 1 || !multiNodeInfo) {
return null; return null;
} }

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { useSettings } from '../../context/SettingsContext'; import { useConfigState } from '../../context/ConfigContext';
import { getValue } from '../../utils/helpers'; import { getValue } from '../../utils/helpers';
import { usePriceState } from '../../context/PriceContext'; import { usePriceState } from '../../context/PriceContext';
@ -16,8 +16,12 @@ export const Price = ({
amount: number | string; amount: number | string;
breakNumber?: boolean; breakNumber?: boolean;
}) => { }) => {
const { currency } = useSettings(); const { currency, displayValues } = useConfigState();
const { prices, loading, error } = usePriceState(); const { prices, dontShow } = usePriceState();
if (!displayValues) {
return <>-</>;
}
let priceProps: PriceProps = { let priceProps: PriceProps = {
price: 0, price: 0,
@ -25,7 +29,7 @@ export const Price = ({
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency, currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
}; };
if (prices && !loading && !error) { if (prices && !dontShow) {
const current: { last: number; symbol: string } = prices[currency] ?? { const current: { last: number; symbol: string } = prices[currency] ?? {
last: 0, last: 0,
symbol: '', symbol: '',
@ -48,13 +52,17 @@ interface GetPriceProps {
export const getPrice = ( export const getPrice = (
currency: string, currency: string,
displayValues: boolean,
priceContext: { priceContext: {
error: boolean; dontShow: boolean;
loading: boolean;
prices?: { [key: string]: { last: number; symbol: string } }; prices?: { [key: string]: { last: number; symbol: string } };
} }
) => ({ amount, breakNumber = false }: GetPriceProps): string => { ) => ({ amount, breakNumber = false }: GetPriceProps): string => {
const { prices, loading, error } = priceContext; const { prices, dontShow } = priceContext;
if (!displayValues) {
return '-';
}
let priceProps: PriceProps = { let priceProps: PriceProps = {
price: 0, price: 0,
@ -62,7 +70,7 @@ export const getPrice = (
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency, currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
}; };
if (prices && !loading && !error) { if (prices && !dontShow) {
const current: { last: number; symbol: string } = prices[currency] ?? { const current: { last: number; symbol: string } = prices[currency] ?? {
last: 0, last: 0,
symbol: '', symbol: '',

View file

@ -1,16 +1,21 @@
import React, { createContext, useContext, useReducer } from 'react'; import React, { createContext, useContext, useReducer } from 'react';
type State = { type State = {
loading: boolean; dontShow: boolean;
error: boolean; fast: number;
halfHour: number;
hour: number;
};
type ChangeState = {
fast: number; fast: number;
halfHour: number; halfHour: number;
hour: number; hour: number;
}; };
type ActionType = { type ActionType = {
type: 'fetched' | 'error'; type: 'fetched' | 'dontShow';
state?: State; state?: ChangeState;
}; };
type Dispatch = (action: ActionType) => void; type Dispatch = (action: ActionType) => void;
@ -19,8 +24,7 @@ export const StateContext = createContext<State | undefined>(undefined);
export const DispatchContext = createContext<Dispatch | undefined>(undefined); export const DispatchContext = createContext<Dispatch | undefined>(undefined);
const initialState = { const initialState = {
loading: true, dontShow: true,
error: false,
fast: 0, fast: 0,
halfHour: 0, halfHour: 0,
hour: 0, hour: 0,
@ -28,16 +32,12 @@ const initialState = {
const stateReducer = (state: State, action: ActionType): State => { const stateReducer = (state: State, action: ActionType): State => {
switch (action.type) { switch (action.type) {
case 'dontShow':
return { ...initialState, dontShow: true };
case 'fetched': case 'fetched':
return action.state || initialState; return { ...initialState, ...action.state, dontShow: false };
case 'error':
return {
...initialState,
loading: false,
error: true,
};
default: default:
return initialState; return state;
} }
}; };

View file

@ -28,9 +28,6 @@ type State = {
sentChats: SentChatProps[]; sentChats: SentChatProps[];
lastChat: string; lastChat: string;
sender: string; sender: string;
hideFee: boolean;
hideNonVerified: boolean;
maxFee: number;
}; };
type ActionType = { type ActionType = {
@ -39,9 +36,6 @@ type ActionType = {
| 'additional' | 'additional'
| 'changeActive' | 'changeActive'
| 'newChat' | 'newChat'
| 'hideNonVerified'
| 'hideFee'
| 'changeFee'
| 'disconnected'; | 'disconnected';
chats?: ChatProps[]; chats?: ChatProps[];
sentChats?: SentChatProps[]; sentChats?: SentChatProps[];
@ -49,9 +43,6 @@ type ActionType = {
lastChat?: string; lastChat?: string;
sender?: string; sender?: string;
userId?: string; userId?: string;
hideFee?: boolean;
hideNonVerified?: boolean;
maxFee?: number;
}; };
type Dispatch = (action: ActionType) => void; type Dispatch = (action: ActionType) => void;
@ -59,15 +50,12 @@ type Dispatch = (action: ActionType) => void;
const StateContext = createContext<State | undefined>(undefined); const StateContext = createContext<State | undefined>(undefined);
const DispatchContext = createContext<Dispatch | undefined>(undefined); const DispatchContext = createContext<Dispatch | undefined>(undefined);
const initialState = { const initialState: State = {
initialized: false, initialized: false,
chats: [], chats: [],
lastChat: '', lastChat: '',
sender: '', sender: '',
sentChats: [], sentChats: [],
hideFee: false,
hideNonVerified: false,
maxFee: 20,
}; };
const stateReducer = (state: State, action: ActionType): State => { const stateReducer = (state: State, action: ActionType): State => {
@ -100,29 +88,8 @@ const stateReducer = (state: State, action: ActionType): State => {
sentChats: [...state.sentChats, action.newChat], sentChats: [...state.sentChats, action.newChat],
...(action.sender && { sender: action.sender }), ...(action.sender && { sender: action.sender }),
}; };
case 'hideFee':
localStorage.setItem('hideFee', JSON.stringify(action.hideFee));
return {
...state,
hideFee: action.hideFee,
};
case 'hideNonVerified':
localStorage.setItem(
'hideNonVerified',
JSON.stringify(action.hideNonVerified)
);
return {
...state,
hideNonVerified: action.hideNonVerified,
};
case 'changeFee':
localStorage.setItem('maxChatFee', JSON.stringify(action.maxFee));
return {
...state,
maxFee: action.maxFee,
};
default: default:
return initialState; return state;
} }
}; };

View file

@ -0,0 +1,117 @@
import React, { createContext, useContext, useReducer, useEffect } from 'react';
import getConfig from 'next/config';
import Cookies from 'js-cookie';
const themeTypes = ['dark', 'light'];
const currencyTypes = ['sat', 'btc', 'EUR', 'USD'];
type State = {
currency: string;
theme: string;
sidebar: boolean;
multiNodeInfo: boolean;
fetchFees: boolean;
fetchPrices: boolean;
displayValues: boolean;
hideFee: boolean;
hideNonVerified: boolean;
maxFee: number;
chatPollingSpeed: number;
};
type ConfigInitProps = {
initialConfig: State;
};
type ActionType = {
type: 'change';
currency?: string;
theme?: string;
sidebar?: boolean;
multiNodeInfo?: boolean;
fetchFees?: boolean;
fetchPrices?: boolean;
displayValues?: boolean;
hideFee?: boolean;
hideNonVerified?: boolean;
maxFee?: number;
chatPollingSpeed?: number;
};
type Dispatch = (action: ActionType) => void;
const StateContext = createContext<State | undefined>(undefined);
const DispatchContext = createContext<Dispatch | undefined>(undefined);
const { publicRuntimeConfig } = getConfig();
const {
defaultTheme: defT,
defaultCurrency: defC,
fetchPrices,
fetchFees,
} = publicRuntimeConfig;
const initialState: State = {
currency: currencyTypes.indexOf(defC) > -1 ? defC : 'sat',
theme: themeTypes.indexOf(defT) > -1 ? defT : 'dark',
sidebar: true,
multiNodeInfo: false,
fetchFees,
fetchPrices,
displayValues: true,
hideFee: false,
hideNonVerified: false,
maxFee: 20,
chatPollingSpeed: 1000,
};
const stateReducer = (state: State, action: ActionType): State => {
const { type, ...settings } = action;
switch (type) {
case 'change':
return {
...state,
...settings,
};
default:
return state;
}
};
const ConfigProvider: React.FC<ConfigInitProps> = ({
children,
initialConfig,
}) => {
const [state, dispatch] = useReducer(stateReducer, {
...initialState,
...initialConfig,
});
useEffect(() => {
Cookies.set('config', state, { expires: 365, sameSite: 'strict' });
}, [state]);
return (
<DispatchContext.Provider value={dispatch}>
<StateContext.Provider value={state}>{children}</StateContext.Provider>
</DispatchContext.Provider>
);
};
const useConfigState = () => {
const context = useContext(StateContext);
if (context === undefined) {
throw new Error('useConfigState must be used within a ConfigProvider');
}
return context;
};
const useConfigDispatch = () => {
const context = useContext(DispatchContext);
if (context === undefined) {
throw new Error('useConfigDispatch must be used within a ConfigProvider');
}
return context;
};
export { ConfigProvider, useConfigState, useConfigDispatch };

View file

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { AccountProvider } from './AccountContext'; import { AccountProvider } from './AccountContext';
import { SettingsProvider } from './SettingsContext';
import { BitcoinInfoProvider } from './BitcoinContext'; import { BitcoinInfoProvider } from './BitcoinContext';
import { StatusProvider } from './StatusContext'; import { StatusProvider } from './StatusContext';
import { PriceProvider } from './PriceContext'; import { PriceProvider } from './PriceContext';
@ -8,14 +7,12 @@ import { ChatProvider } from './ChatContext';
export const ContextProvider: React.FC = ({ children }) => ( export const ContextProvider: React.FC = ({ children }) => (
<AccountProvider> <AccountProvider>
<SettingsProvider> <BitcoinInfoProvider>
<BitcoinInfoProvider> <PriceProvider>
<PriceProvider> <ChatProvider>
<ChatProvider> <StatusProvider>{children}</StatusProvider>
<StatusProvider>{children}</StatusProvider> </ChatProvider>
</ChatProvider> </PriceProvider>
</PriceProvider> </BitcoinInfoProvider>
</BitcoinInfoProvider>
</SettingsProvider>
</AccountProvider> </AccountProvider>
); );

View file

@ -6,14 +6,17 @@ type PriceProps = {
}; };
type State = { type State = {
loading: boolean; dontShow: boolean;
error: boolean; prices?: { [key: string]: PriceProps };
};
type ChangeState = {
prices?: { [key: string]: PriceProps }; prices?: { [key: string]: PriceProps };
}; };
type ActionType = { type ActionType = {
type: 'fetched' | 'error'; type: 'fetched' | 'dontShow';
state?: State; state?: ChangeState;
}; };
type Dispatch = (action: ActionType) => void; type Dispatch = (action: ActionType) => void;
@ -22,23 +25,18 @@ export const StateContext = createContext<State | undefined>(undefined);
export const DispatchContext = createContext<Dispatch | undefined>(undefined); export const DispatchContext = createContext<Dispatch | undefined>(undefined);
const initialState: State = { const initialState: State = {
loading: true, dontShow: true,
error: false,
prices: { EUR: { last: 0, symbol: '€' } }, prices: { EUR: { last: 0, symbol: '€' } },
}; };
const stateReducer = (state: State, action: ActionType): State => { const stateReducer = (state: State, action: ActionType): State => {
switch (action.type) { switch (action.type) {
case 'dontShow':
return { ...initialState, dontShow: true };
case 'fetched': case 'fetched':
return action.state || initialState; return { ...initialState, ...action.state, dontShow: false };
case 'error':
return {
...initialState,
loading: false,
error: true,
};
default: default:
return initialState; return state;
} }
}; };

View file

@ -1,93 +0,0 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import React, { createContext, useState, useContext, useEffect } 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;
useEffect(() => {
refreshSettings();
}, []);
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: 'sat',
theme: 'dark',
sidebar: true,
nodeInfo: false,
setSettings,
refreshSettings,
};
const [settings, updateSettings] = useState(settingsState);
return (
<SettingsContext.Provider value={settings}>
{children}
</SettingsContext.Provider>
);
};
const useSettings = () => useContext(SettingsContext);
export { SettingsProvider, useSettings };

View file

@ -24,7 +24,7 @@ import {
CreditCard, CreditCard,
MessageCircle, MessageCircle,
} from 'react-feather'; } from 'react-feather';
import { useSettings } from '../../context/SettingsContext'; import { useConfigState } from '../../context/ConfigContext';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Link } from '../../components/link/Link'; import { Link } from '../../components/link/Link';
import { useStatusState } from '../../context/StatusContext'; import { useStatusState } from '../../context/StatusContext';
@ -131,7 +131,7 @@ interface NavigationProps {
export const Navigation = ({ isBurger, setOpen }: NavigationProps) => { export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
const { pathname } = useRouter(); const { pathname } = useRouter();
const { sidebar, setSettings } = useSettings(); const { sidebar } = useConfigState();
const { connected } = useStatusState(); const { connected } = useStatusState();
const renderNavButton = ( const renderNavButton = (
@ -204,7 +204,7 @@ export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
<LinkView> <LinkView>
{connected && <NodeInfo isOpen={sidebar} />} {connected && <NodeInfo isOpen={sidebar} />}
{renderLinks()} {renderLinks()}
<SideSettings isOpen={sidebar} setIsOpen={setSettings} /> <SideSettings />
</LinkView> </LinkView>
</StickyCard> </StickyCard>
</NavigationStyle> </NavigationStyle>

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { useSettings } from '../../../context/SettingsContext'; import { useConfigState } from '../../../context/ConfigContext';
import { import {
Separation, Separation,
SingleLine, SingleLine,
@ -84,9 +84,9 @@ export const NodeInfo = ({ isOpen, isBurger }: NodeInfoProps) => {
onError: error => toast.error(getErrorContent(error)), onError: error => toast.error(getErrorContent(error)),
}); });
const { theme, currency } = useSettings(); const { theme, currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const tooltipType: any = getTooltipType(theme); const tooltipType: any = getTooltipType(theme);

View file

@ -1,6 +1,9 @@
import React from 'react'; import React from 'react';
import { Separation, SingleLine } from '../../../components/generic/Styled'; import { Separation, SingleLine } from '../../../components/generic/Styled';
import { useSettings } from '../../../context/SettingsContext'; import {
useConfigState,
useConfigDispatch,
} from '../../../context/ConfigContext';
import { Sun, Moon, ChevronLeft, ChevronRight } from 'react-feather'; import { Sun, Moon, ChevronLeft, ChevronRight } from 'react-feather';
import styled from 'styled-components'; import styled from 'styled-components';
import { import {
@ -9,6 +12,7 @@ import {
inverseTextColor, inverseTextColor,
unSelectedNavButton, unSelectedNavButton,
} from '../../../styles/Themes'; } from '../../../styles/Themes';
import { usePriceState } from '../../../context/PriceContext';
const SelectedIcon = styled.div<{ selected: boolean }>` const SelectedIcon = styled.div<{ selected: boolean }>`
display: flex; display: flex;
@ -48,13 +52,20 @@ const BurgerPadding = styled(SingleLine)`
`; `;
const currencyArray = ['sat', 'btc', 'EUR', 'USD']; const currencyArray = ['sat', 'btc', 'EUR', 'USD'];
const currencyNoFiatArray = ['sat', 'btc'];
const themeArray = ['light', 'dark']; const themeArray = ['light', 'dark'];
const currencyMap: { [key: string]: string } = { const currencyMap: { [key: string]: string } = {
sat: 'S', sat: 'S',
btc: '₿', btc: '₿',
EUR: '€', EUR: '€',
USD: '$', USD: '$',
}; };
const currencyNoFiatMap: { [key: string]: string } = {
sat: 'S',
btc: '₿',
};
const getNextValue = (array: string[], current: string): string => { const getNextValue = (array: string[], current: string): string => {
const length = array.length; const length = array.length;
@ -71,17 +82,16 @@ const getNextValue = (array: string[], current: string): string => {
}; };
interface SideSettingsProps { interface SideSettingsProps {
isOpen?: boolean;
isBurger?: boolean; isBurger?: boolean;
setIsOpen?: (state: any) => void;
} }
export const SideSettings = ({ export const SideSettings = ({ isBurger }: SideSettingsProps) => {
isOpen, const { dontShow } = usePriceState();
isBurger, const { theme, currency, sidebar } = useConfigState();
setIsOpen, const dispatch = useConfigDispatch();
}: SideSettingsProps) => {
const { theme, currency, setSettings } = useSettings(); const correctMap = dontShow ? currencyNoFiatMap : currencyMap;
const correctArray = dontShow ? currencyNoFiatArray : currencyArray;
const renderIcon = ( const renderIcon = (
type: string, type: string,
@ -97,11 +107,12 @@ export const SideSettings = ({
onClick={() => { onClick={() => {
localStorage.setItem(type, value); localStorage.setItem(type, value);
type === 'currency' && type === 'currency' &&
setSettings({ dispatch({
type: 'change',
currency: currency:
isOpen || isBurger ? value : getNextValue(currencyArray, value), sidebar || isBurger ? value : getNextValue(correctArray, value),
}); });
type === 'theme' && setSettings({ theme: value }); type === 'theme' && dispatch({ type: 'change', theme: value });
}} }}
> >
{type === 'currency' && <Symbol>{text}</Symbol>} {type === 'currency' && <Symbol>{text}</Symbol>}
@ -110,12 +121,12 @@ export const SideSettings = ({
); );
const renderContent = () => { const renderContent = () => {
if (!isOpen) { if (!sidebar) {
return ( return (
<> <>
<Separation lineColor={unSelectedNavButton} /> <Separation lineColor={unSelectedNavButton} />
<IconRow center={true}> <IconRow center={true}>
{renderIcon('currency', currency, currencyMap[currency], true)} {renderIcon('currency', currency, correctMap[currency], true)}
</IconRow> </IconRow>
<IconRow center={true}> <IconRow center={true}>
{renderIcon( {renderIcon(
@ -135,8 +146,8 @@ export const SideSettings = ({
<IconRow> <IconRow>
{renderIcon('currency', 'sat', 'S')} {renderIcon('currency', 'sat', 'S')}
{renderIcon('currency', 'btc', '₿')} {renderIcon('currency', 'btc', '₿')}
{renderIcon('currency', 'EUR', '€')} {!dontShow && renderIcon('currency', 'EUR', '€')}
{renderIcon('currency', 'USD', '$')} {!dontShow && renderIcon('currency', 'USD', '$')}
</IconRow> </IconRow>
<IconRow> <IconRow>
{renderIcon('theme', 'light', '', false, Sun)} {renderIcon('theme', 'light', '', false, Sun)}
@ -152,8 +163,8 @@ export const SideSettings = ({
<IconRow> <IconRow>
{renderIcon('currency', 'sat', 'S')} {renderIcon('currency', 'sat', 'S')}
{renderIcon('currency', 'btc', '₿')} {renderIcon('currency', 'btc', '₿')}
{renderIcon('currency', 'EUR', '€')} {!dontShow && renderIcon('currency', 'EUR', '€')}
{renderIcon('currency', 'USD', '$')} {!dontShow && renderIcon('currency', 'USD', '$')}
</IconRow> </IconRow>
<IconRow> <IconRow>
{renderIcon('theme', 'light', '', false, Sun)} {renderIcon('theme', 'light', '', false, Sun)}
@ -166,19 +177,17 @@ export const SideSettings = ({
return ( return (
<> <>
{renderContent()} {renderContent()}
{setIsOpen && ( <IconRow center={!sidebar}>
<IconRow center={!isOpen}> <SelectedIcon
<SelectedIcon selected={true}
selected={true} onClick={() => {
onClick={() => { localStorage.setItem('sidebar', (!sidebar).toString());
localStorage.setItem('sidebar', (!isOpen).toString()); dispatch({ type: 'change', sidebar: !sidebar });
setIsOpen({ sidebar: !isOpen }); }}
}} >
> {sidebar ? <ChevronLeft size={18} /> : <ChevronRight size={18} />}
{isOpen ? <ChevronLeft size={18} /> : <ChevronRight size={18} />} </SelectedIcon>
</SelectedIcon> </IconRow>
</IconRow>
)}
</> </>
); );
}; };

5
src/utils/cookies.ts Normal file
View file

@ -0,0 +1,5 @@
import cookie from 'cookie';
export const parseCookies = req => {
return cookie.parse(req ? req.headers.cookie || '' : document.cookie);
};

View file

@ -14,7 +14,7 @@ import {
} from '../../../components/generic/helpers'; } from '../../../components/generic/helpers';
import styled from 'styled-components'; import styled from 'styled-components';
import { getPrice } from '../../../components/price/Price'; import { getPrice } from '../../../components/price/Price';
import { useSettings } from '../../../context/SettingsContext'; import { useConfigState } from '../../../context/ConfigContext';
import { usePriceState } from '../../../context/PriceContext'; import { usePriceState } from '../../../context/PriceContext';
const AddMargin = styled.div` const AddMargin = styled.div`
@ -34,9 +34,9 @@ export const TransactionsCard = ({
setIndexOpen, setIndexOpen,
indexOpen, indexOpen,
}: TransactionsCardProps) => { }: TransactionsCardProps) => {
const { currency } = useSettings(); const { currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const { const {
block_id, block_id,

View file

@ -3,7 +3,7 @@ import { Separation, SubCard } from '../../../components/generic/Styled';
import { MainInfo } from '../../../components/generic/CardGeneric'; import { MainInfo } from '../../../components/generic/CardGeneric';
import { renderLine } from '../../../components/generic/helpers'; import { renderLine } from '../../../components/generic/helpers';
import { getPrice } from '../../../components/price/Price'; import { getPrice } from '../../../components/price/Price';
import { useSettings } from '../../../context/SettingsContext'; import { useConfigState } from '../../../context/ConfigContext';
import { usePriceState } from '../../../context/PriceContext'; import { usePriceState } from '../../../context/PriceContext';
interface TransactionsCardProps { interface TransactionsCardProps {
@ -19,9 +19,9 @@ export const UtxoCard = ({
setIndexOpen, setIndexOpen,
indexOpen, indexOpen,
}: TransactionsCardProps) => { }: TransactionsCardProps) => {
const { currency } = useSettings(); const { currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const { const {
address, address,

View file

@ -17,7 +17,7 @@ import {
ResponsiveSingle, ResponsiveSingle,
ResponsiveCol, ResponsiveCol,
} from '../../../components/generic/Styled'; } from '../../../components/generic/Styled';
import { useSettings } from '../../../context/SettingsContext'; import { useConfigState } from '../../../context/ConfigContext';
import { import {
getStatusDot, getStatusDot,
getTooltipType, getTooltipType,
@ -64,9 +64,9 @@ export const ChannelCard = ({
}: ChannelCardProps) => { }: ChannelCardProps) => {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const { theme, currency } = useSettings(); const { theme, currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const tooltipType: any = getTooltipType(theme); const tooltipType: any = getTooltipType(theme);

View file

@ -16,7 +16,7 @@ import {
ResponsiveSingle, ResponsiveSingle,
ResponsiveCol, ResponsiveCol,
} from '../../../components/generic/Styled'; } from '../../../components/generic/Styled';
import { useSettings } from '../../../context/SettingsContext'; import { useConfigState } from '../../../context/ConfigContext';
import { import {
getStatusDot, getStatusDot,
getTooltipType, getTooltipType,
@ -66,9 +66,9 @@ export const PendingCard = ({
setIndexOpen, setIndexOpen,
indexOpen, indexOpen,
}: PendingCardProps) => { }: PendingCardProps) => {
const { theme, currency } = useSettings(); const { theme, currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const tooltipType: any = getTooltipType(theme); const tooltipType: any = getTooltipType(theme);

View file

@ -19,7 +19,7 @@ import {
ChatBoxTopAlias, ChatBoxTopAlias,
} from './Chat.styled'; } from './Chat.styled';
import { ChatBubble } from './ChatBubble'; import { ChatBubble } from './ChatBubble';
import { useChatState } from '../../context/ChatContext'; import { useConfigState } from '../../context/ConfigContext';
export const MessageCard = ({ export const MessageCard = ({
message, message,
@ -28,7 +28,7 @@ export const MessageCard = ({
message: MessageType; message: MessageType;
key?: string; key?: string;
}) => { }) => {
const { hideFee, hideNonVerified } = useChatState(); const { hideFee, hideNonVerified } = useConfigState();
if (!message.message && message.contentType === 'text') { if (!message.message && message.contentType === 'text') {
return null; return null;
} }

View file

@ -20,7 +20,7 @@ import { useChatState, useChatDispatch } from '../../context/ChatContext';
import { useAccount } from '../../context/AccountContext'; import { useAccount } from '../../context/AccountContext';
import { Circle } from 'react-feather'; import { Circle } from 'react-feather';
import ScaleLoader from 'react-spinners/ScaleLoader'; import ScaleLoader from 'react-spinners/ScaleLoader';
import { useSettings } from '../../context/SettingsContext'; import { useConfigState } from '../../context/ConfigContext';
import { usePriceState } from '../../context/PriceContext'; import { usePriceState } from '../../context/PriceContext';
import { getPrice } from '../../components/price/Price'; import { getPrice } from '../../components/price/Price';
@ -29,7 +29,8 @@ interface SendButtonProps {
} }
const SendButton = ({ amount }: SendButtonProps) => { const SendButton = ({ amount }: SendButtonProps) => {
const { sender, maxFee } = useChatState(); const { maxFee } = useConfigState();
const { sender } = useChatState();
const dispatch = useChatDispatch(); const dispatch = useChatDispatch();
const { id } = useAccount(); const { id } = useAccount();
@ -54,7 +55,7 @@ const SendButton = ({ amount }: SendButtonProps) => {
sender, sender,
}); });
} }
}, [loading, data]); }, [loading, data, amount, dispatch, sender, id]);
return ( return (
<SecureWrapper <SecureWrapper
@ -80,9 +81,9 @@ interface ChatBubbleProps {
} }
export const ChatBubble = ({ message }: ChatBubbleProps) => { export const ChatBubble = ({ message }: ChatBubbleProps) => {
const { currency } = useSettings(); const { currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const { const {
contentType, contentType,

View file

@ -8,6 +8,7 @@ import { toast } from 'react-toastify';
import { getErrorContent } from '../../utils/error'; import { getErrorContent } from '../../utils/error';
import { useAccount } from '../../context/AccountContext'; import { useAccount } from '../../context/AccountContext';
import { handleMessage } from './helpers/chatHelpers'; import { handleMessage } from './helpers/chatHelpers';
import { useConfigState } from '../../context/ConfigContext';
export const ChatInput = ({ export const ChatInput = ({
alias, alias,
@ -21,7 +22,8 @@ export const ChatInput = ({
const [message, setMessage] = React.useState(''); const [message, setMessage] = React.useState('');
const { id } = useAccount(); const { id } = useAccount();
const { sender, maxFee } = useChatState(); const { maxFee } = useConfigState();
const { sender } = useChatState();
const dispatch = useChatDispatch(); const dispatch = useChatDispatch();
const [sendMessage, { loading, data }] = useSendMessageMutation({ const [sendMessage, { loading, data }] = useSendMessageMutation({
@ -50,7 +52,17 @@ export const ChatInput = ({
sender: customSender || sender, sender: customSender || sender,
}); });
} }
}, [loading, data]); }, [
loading,
data,
formattedMessage,
customSender,
sender,
contentType,
tokens,
id,
dispatch,
]);
return ( return (
<SingleLine> <SingleLine>

View file

@ -17,7 +17,7 @@ import { toast } from 'react-toastify';
import { getErrorContent } from '../../utils/error'; import { getErrorContent } from '../../utils/error';
import { ChevronRight } from 'react-feather'; import { ChevronRight } from 'react-feather';
import { SecureButton } from '../../components/buttons/secureButton/SecureButton'; import { SecureButton } from '../../components/buttons/secureButton/SecureButton';
import { useSettings } from '../../context/SettingsContext'; import { useConfigState } from '../../context/ConfigContext';
import { textColorMap } from '../../styles/Themes'; import { textColorMap } from '../../styles/Themes';
import { Input } from '../../components/input/Input'; import { Input } from '../../components/input/Input';
import { AdminSwitch } from '../../components/adminSwitch/AdminSwitch'; import { AdminSwitch } from '../../components/adminSwitch/AdminSwitch';
@ -39,7 +39,7 @@ export const FeeCard = ({
const [newBaseFee, setBaseFee] = useState(0); const [newBaseFee, setBaseFee] = useState(0);
const [newFeeRate, setFeeRate] = useState(0); const [newFeeRate, setFeeRate] = useState(0);
const { theme } = useSettings(); const { theme } = useConfigState();
const { const {
alias, alias,
@ -59,7 +59,7 @@ export const FeeCard = ({
? toast.success('Channel fees updated') ? toast.success('Channel fees updated')
: toast.error('Error updating channel fees'); : toast.error('Error updating channel fees');
}, },
refetchQueries: ['GetChannelFees'], refetchQueries: ['ChannelFees'],
}); });
const handleClick = () => { const handleClick = () => {

View file

@ -38,6 +38,7 @@ export const PayCard = ({ setOpen }: { setOpen: () => void }) => {
setModalType('none'); setModalType('none');
setOpen(); setOpen();
}, },
refetchQueries: ['GetInOut', 'GetNodeInfo', 'GetBalances'],
}); });
const handleClick = () => { const handleClick = () => {

View file

@ -19,7 +19,7 @@ import {
} from '../../../../components/buttons/multiButton/MultiButton'; } from '../../../../components/buttons/multiButton/MultiButton';
import { Price, getPrice } from '../../../../components/price/Price'; import { Price, getPrice } from '../../../../components/price/Price';
import { mediaWidths } from '../../../../styles/Themes'; import { mediaWidths } from '../../../../styles/Themes';
import { useSettings } from '../../../../context/SettingsContext'; import { useConfigState } from '../../../../context/ConfigContext';
import Modal from '../../../../components/modal/ReactModal'; import Modal from '../../../../components/modal/ReactModal';
import { ColorButton } from '../../../../components/buttons/colorButton/ColorButton'; import { ColorButton } from '../../../../components/buttons/colorButton/ColorButton';
import { renderLine } from '../../../../components/generic/helpers'; import { renderLine } from '../../../../components/generic/helpers';
@ -43,22 +43,21 @@ const Margin = styled.div`
`; `;
export const SendOnChainCard = ({ setOpen }: { setOpen: () => void }) => { export const SendOnChainCard = ({ setOpen }: { setOpen: () => void }) => {
const { currency } = useSettings(); const { fast, halfHour, hour, dontShow } = useBitcoinState();
const { currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [address, setAddress] = useState(''); const [address, setAddress] = useState('');
const [tokens, setTokens] = useState(0); const [tokens, setTokens] = useState(0);
const [type, setType] = useState('none'); const [type, setType] = useState(dontShow ? 'fee' : 'none');
const [amount, setAmount] = useState(0); const [amount, setAmount] = useState(0);
const [sendAll, setSendAll] = useState(false); const [sendAll, setSendAll] = useState(false);
const canSend = address !== '' && (sendAll || tokens > 0) && amount > 0; const canSend = address !== '' && (sendAll || tokens > 0) && amount > 0;
const { fast, halfHour, hour } = useBitcoinState();
const [payAddress, { loading }] = usePayAddressMutation({ const [payAddress, { loading }] = usePayAddressMutation({
onError: error => toast.error(getErrorContent(error)), onError: error => toast.error(getErrorContent(error)),
onCompleted: () => { onCompleted: () => {
@ -143,14 +142,15 @@ export const SendOnChainCard = ({ setOpen }: { setOpen: () => void }) => {
<SingleLine> <SingleLine>
<NoWrapTitle>Fee:</NoWrapTitle> <NoWrapTitle>Fee:</NoWrapTitle>
<MultiButton margin={'8px 0 8px 16px'}> <MultiButton margin={'8px 0 8px 16px'}>
{renderButton( {!dontShow &&
() => { renderButton(
setType('none'); () => {
setAmount(fast); setType('none');
}, setAmount(fast);
'Auto', },
type === 'none' 'Auto',
)} type === 'none'
)}
{renderButton( {renderButton(
() => { () => {
setType('fee'); setType('fee');

View file

@ -34,13 +34,12 @@ interface OpenChannelProps {
} }
export const OpenChannelCard = ({ color, setOpenCard }: OpenChannelProps) => { export const OpenChannelCard = ({ color, setOpenCard }: OpenChannelProps) => {
const { fast, halfHour, hour, dontShow } = useBitcoinState();
const [size, setSize] = useState(0); const [size, setSize] = useState(0);
const [fee, setFee] = useState(0); const [fee, setFee] = useState(0);
const [publicKey, setPublicKey] = useState(''); const [publicKey, setPublicKey] = useState('');
const [privateChannel, setPrivateChannel] = useState(false); const [privateChannel, setPrivateChannel] = useState(false);
const [type, setType] = useState('none'); const [type, setType] = useState(dontShow ? 'fee' : 'none');
const { fast, halfHour, hour } = useBitcoinState();
const [openChannel] = useOpenChannelMutation({ const [openChannel] = useOpenChannelMutation({
onError: error => toast.error(getErrorContent(error)), onError: error => toast.error(getErrorContent(error)),
@ -108,24 +107,26 @@ export const OpenChannelCard = ({ color, setOpenCard }: OpenChannelProps) => {
</MultiButton> </MultiButton>
</SingleLine> </SingleLine>
<Separation /> <Separation />
<SingleLine> {!dontShow && (
<NoWrapTitle>Fee:</NoWrapTitle> <SingleLine>
<MultiButton margin={'8px 0 8px 16px'}> <NoWrapTitle>Fee:</NoWrapTitle>
{renderButton( <MultiButton margin={'8px 0 8px 16px'}>
() => { {renderButton(
setType('none'); () => {
setFee(fast); setType('none');
}, setFee(fast);
'Auto', },
type === 'none' 'Auto',
)} type === 'none'
{renderButton( )}
() => setType('fee'), {renderButton(
'Fee (Sats/Byte)', () => setType('fee'),
type === 'fee' 'Fee (Sats/Byte)',
)} type === 'fee'
</MultiButton> )}
</SingleLine> </MultiButton>
</SingleLine>
)}
<SingleLine> <SingleLine>
<ResponsiveWrap> <ResponsiveWrap>
<NoWrapTitle>Fee Amount:</NoWrapTitle> <NoWrapTitle>Fee Amount:</NoWrapTitle>
@ -141,7 +142,6 @@ export const OpenChannelCard = ({ color, setOpenCard }: OpenChannelProps) => {
type={'number'} type={'number'}
onChange={e => setFee(Number(e.target.value))} onChange={e => setFee(Number(e.target.value))}
/> />
// </MultiButton>
)} )}
{type === 'none' && ( {type === 'none' && (
<MultiButton margin={'8px 0 8px 16px'}> <MultiButton margin={'8px 0 8px 16px'}>

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { DarkSubTitle } from '../../../../components/generic/Styled'; import { DarkSubTitle } from '../../../../components/generic/Styled';
import { useSettings } from '../../../../context/SettingsContext'; import { useConfigState } from '../../../../context/ConfigContext';
import { VictoryPie } from 'victory'; import { VictoryPie } from 'victory';
import { chartAxisColor } from '../../../../styles/Themes'; import { chartAxisColor } from '../../../../styles/Themes';
import { Row, Col, PieRow } from '.'; import { Row, Col, PieRow } from '.';
@ -13,9 +13,9 @@ interface Props {
} }
export const FlowPie = ({ flowPie, isType }: Props) => { export const FlowPie = ({ flowPie, isType }: Props) => {
const { theme, currency } = useSettings(); const { theme, currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
return ( return (
<Row> <Row>

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import numeral from 'numeral'; import numeral from 'numeral';
import { useSettings } from '../../../../context/SettingsContext'; import { useConfigState } from '../../../../context/ConfigContext';
import { import {
VictoryBar, VictoryBar,
VictoryChart, VictoryChart,
@ -41,9 +41,9 @@ export const FlowReport = ({
parsedData2, parsedData2,
}: // waterfall, }: // waterfall,
Props) => { Props) => {
const { theme, currency } = useSettings(); const { theme, currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
let domain = 24; let domain = 24;
let barWidth = 3; let barWidth = 3;

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { DarkSubTitle } from '../../../../components/generic/Styled'; import { DarkSubTitle } from '../../../../components/generic/Styled';
import { useSettings } from '../../../../context/SettingsContext'; import { useConfigState } from '../../../../context/ConfigContext';
import { VictoryPie } from 'victory'; import { VictoryPie } from 'victory';
import { chartAxisColor } from '../../../../styles/Themes'; import { chartAxisColor } from '../../../../styles/Themes';
import { Row, Col, PieRow } from '.'; import { Row, Col, PieRow } from '.';
@ -10,7 +10,7 @@ interface Props {
} }
export const InvoicePie = ({ invoicePie }: Props) => { export const InvoicePie = ({ invoicePie }: Props) => {
const { theme } = useSettings(); const { theme } = useConfigState();
return ( return (
<Row> <Row>

View file

@ -12,7 +12,7 @@ import { GitCommit, ArrowDown, ArrowUp } from 'react-feather';
import styled from 'styled-components'; import styled from 'styled-components';
import { LoadingCard } from '../../../../components/loading/LoadingCard'; import { LoadingCard } from '../../../../components/loading/LoadingCard';
import { getPrice } from '../../../../components/price/Price'; import { getPrice } from '../../../../components/price/Price';
import { useSettings } from '../../../../context/SettingsContext'; import { useConfigState } from '../../../../context/ConfigContext';
import { usePriceState } from '../../../../context/PriceContext'; import { usePriceState } from '../../../../context/PriceContext';
import { useGetForwardChannelsReportQuery } from '../../../../generated/graphql'; import { useGetForwardChannelsReportQuery } from '../../../../generated/graphql';
@ -64,9 +64,9 @@ interface Props {
export const ForwardChannelsReport = ({ isTime, isType, color }: Props) => { export const ForwardChannelsReport = ({ isTime, isType, color }: Props) => {
const [type, setType] = useState('route'); const [type, setType] = useState('route');
const { currency } = useSettings(); const { currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const { auth } = useAccount(); const { auth } = useAccount();

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Sub4Title } from '../../../../components/generic/Styled'; import { Sub4Title } from '../../../../components/generic/Styled';
import numeral from 'numeral'; import numeral from 'numeral';
import { useSettings } from '../../../../context/SettingsContext'; import { useConfigState } from '../../../../context/ConfigContext';
import { useAccount } from '../../../../context/AccountContext'; import { useAccount } from '../../../../context/AccountContext';
import { import {
VictoryBar, VictoryBar,
@ -34,9 +34,9 @@ const timeMap: { [key: string]: string } = {
}; };
export const ForwardReport = ({ isTime, isType }: Props) => { export const ForwardReport = ({ isTime, isType }: Props) => {
const { theme, currency } = useSettings(); const { theme, currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const { auth } = useAccount(); const { auth } = useAccount();

View file

@ -12,7 +12,7 @@ import {
VictoryVoronoiContainer, VictoryVoronoiContainer,
VictoryTooltip, VictoryTooltip,
} from 'victory'; } from 'victory';
import { useSettings } from '../../../../context/SettingsContext'; import { useConfigState } from '../../../../context/ConfigContext';
import { import {
chartGridColor, chartGridColor,
chartAxisColor, chartAxisColor,
@ -26,9 +26,9 @@ import { useGetLiquidReportQuery } from '../../../../generated/graphql';
export const LiquidReport = () => { export const LiquidReport = () => {
const { auth } = useAccount(); const { auth } = useAccount();
const { theme, currency } = useSettings(); const { theme, currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const { data, loading } = useGetLiquidReportQuery({ const { data, loading } = useGetLiquidReportQuery({
skip: !auth, skip: !auth,

View file

@ -24,7 +24,7 @@ import {
MainInfo, MainInfo,
} from '../../components/generic/CardGeneric'; } from '../../components/generic/CardGeneric';
import { getPercent } from '../../utils/helpers'; import { getPercent } from '../../utils/helpers';
import { useSettings } from '../../context/SettingsContext'; import { useConfigState } from '../../context/ConfigContext';
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
import { usePriceState } from '../../context/PriceContext'; import { usePriceState } from '../../context/PriceContext';
import { getPrice } from '../../components/price/Price'; import { getPrice } from '../../components/price/Price';
@ -57,10 +57,10 @@ export const PeersCard = ({
}: PeerProps) => { }: PeerProps) => {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const { theme, currency } = useSettings(); const { theme, currency, displayValues } = useConfigState();
const priceContext = usePriceState(); const priceContext = usePriceState();
const format = getPrice(currency, priceContext); const format = getPrice(currency, displayValues, priceContext);
const tooltipType: any = getTooltipType(theme); const tooltipType: any = getTooltipType(theme);
const { const {

View file

@ -11,11 +11,16 @@ import {
MultiButton, MultiButton,
SingleButton, SingleButton,
} from '../../components/buttons/multiButton/MultiButton'; } from '../../components/buttons/multiButton/MultiButton';
import { useChatState, useChatDispatch } from '../../context/ChatContext'; import { useConfigState, useConfigDispatch } from '../../context/ConfigContext';
export const ChatSettings = () => { export const ChatSettings = () => {
const { hideFee, hideNonVerified, maxFee } = useChatState(); const {
const dispatch = useChatDispatch(); hideFee,
hideNonVerified,
maxFee,
chatPollingSpeed: cps,
} = useConfigState();
const dispatch = useConfigDispatch();
const renderButton = ( const renderButton = (
title: string, title: string,
@ -29,15 +34,19 @@ export const ChatSettings = () => {
switch (type) { switch (type) {
case 'fee': case 'fee':
typeof value === 'boolean' && typeof value === 'boolean' &&
dispatch({ type: 'hideFee', hideFee: value }); dispatch({ type: 'change', hideFee: value });
break; break;
case 'nonverified': case 'nonverified':
typeof value === 'boolean' && typeof value === 'boolean' &&
dispatch({ type: 'hideNonVerified', hideNonVerified: value }); dispatch({ type: 'change', hideNonVerified: value });
break;
case 'pollingSpeed':
typeof value === 'number' &&
dispatch({ type: 'change', chatPollingSpeed: value });
break; break;
default: default:
typeof value === 'number' && typeof value === 'number' &&
dispatch({ type: 'changeFee', maxFee: value }); dispatch({ type: 'change', maxFee: value });
break; break;
} }
}} }}
@ -74,6 +83,18 @@ export const ChatSettings = () => {
{renderButton('100', 'maxFee', maxFee === 100, 100)} {renderButton('100', 'maxFee', maxFee === 100, 100)}
</MultiButton> </MultiButton>
</SettingsLine> </SettingsLine>
<SettingsLine>
<Sub4Title>{'Polling Speed:'}</Sub4Title>
<MultiButton>
{renderButton('1s', 'pollingSpeed', cps === 1000, 1000)}
{renderButton('5s', 'pollingSpeed', cps === 5000, 5000)}
{renderButton('10s', 'pollingSpeed', cps === 10000, 10000)}
{renderButton('1m', 'pollingSpeed', cps === 60000, 60000)}
{renderButton('10m', 'pollingSpeed', cps === 600000, 600000)}
{renderButton('30m', 'pollingSpeed', cps === 1800000, 1800000)}
{renderButton('None', 'pollingSpeed', cps === 0, 0)}
</MultiButton>
</SettingsLine>
</Card> </Card>
</CardWithTitle> </CardWithTitle>
); );

View file

@ -21,6 +21,7 @@ import { useStatusDispatch } from '../../context/StatusContext';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { appendBasePath } from '../../utils/basePath'; import { appendBasePath } from '../../utils/basePath';
import { useChatDispatch } from '../../context/ChatContext'; import { useChatDispatch } from '../../context/ChatContext';
import Cookies from 'js-cookie';
export const ButtonRow = styled.div` export const ButtonRow = styled.div`
width: auto; width: auto;
@ -90,6 +91,7 @@ export const DangerView = () => {
chatDispatch({ type: 'disconnected' }); chatDispatch({ type: 'disconnected' });
deleteStorage(); deleteStorage();
refreshAccount(); refreshAccount();
Cookies.remove('config');
push(appendBasePath('/')); push(appendBasePath('/'));
}; };

View file

@ -6,22 +6,19 @@ import {
Sub4Title, Sub4Title,
} from '../../components/generic/Styled'; } from '../../components/generic/Styled';
import { SettingsLine } from '../../../pages/settings'; import { SettingsLine } from '../../../pages/settings';
import { useSettings } from '../../context/SettingsContext'; import { useConfigState, useConfigDispatch } from '../../context/ConfigContext';
import { import {
MultiButton, MultiButton,
SingleButton, SingleButton,
} from '../../components/buttons/multiButton/MultiButton'; } from '../../components/buttons/multiButton/MultiButton';
import { useAccount } from '../../context/AccountContext'; import { useAccount } from '../../context/AccountContext';
import { usePriceState } from '../../context/PriceContext';
export const InterfaceSettings = () => { export const InterfaceSettings = () => {
const { const { dontShow } = usePriceState();
theme, const { theme, currency, multiNodeInfo } = useConfigState();
currency, const dispatch = useConfigDispatch();
nodeInfo,
setSettings,
refreshSettings,
} = useSettings();
const { accounts } = useAccount(); const { accounts } = useAccount();
@ -37,11 +34,13 @@ export const InterfaceSettings = () => {
selected={current === value} selected={current === value}
onClick={() => { onClick={() => {
localStorage.setItem(type, value); localStorage.setItem(type, value);
type === 'theme' && setSettings({ theme: value }); type === 'theme' && dispatch({ type: 'change', theme: value });
type === 'currency' && setSettings({ currency: value }); type === 'currency' && dispatch({ type: 'change', currency: value });
type === 'nodeInfo' && type === 'nodeInfo' &&
setSettings({ nodeInfo: value === 'true' ? true : false }); dispatch({
refreshSettings(); type: 'change',
multiNodeInfo: value === 'true' ? true : false,
});
}} }}
> >
{title} {title}
@ -63,18 +62,18 @@ export const InterfaceSettings = () => {
<SettingsLine> <SettingsLine>
<Sub4Title>Show all accounts on homepage:</Sub4Title> <Sub4Title>Show all accounts on homepage:</Sub4Title>
<MultiButton> <MultiButton>
{renderButton('Yes', 'true', 'nodeInfo', `${nodeInfo}`)} {renderButton('Yes', 'true', 'nodeInfo', `${multiNodeInfo}`)}
{renderButton('No', 'false', 'nodeInfo', `${nodeInfo}`)} {renderButton('No', 'false', 'nodeInfo', `${multiNodeInfo}`)}
</MultiButton> </MultiButton>
</SettingsLine> </SettingsLine>
)} )}
<SettingsLine> <SettingsLine>
<Sub4Title>Currency:</Sub4Title> <Sub4Title>Currency:</Sub4Title>
<MultiButton margin={'0 0 0 16px'}> <MultiButton margin={'0 0 0 16px'}>
{renderButton('Bitcoin', 'btc', 'currency', currency)}
{renderButton('Satoshis', 'sat', 'currency', currency)} {renderButton('Satoshis', 'sat', 'currency', currency)}
{renderButton('Euro', 'EUR', 'currency', currency)} {renderButton('Bitcoin', 'btc', 'currency', currency)}
{renderButton('US Dollar', 'USD', 'currency', currency)} {!dontShow && renderButton('Euro', 'EUR', 'currency', currency)}
{!dontShow && renderButton('USD', 'USD', 'currency', currency)}
</MultiButton> </MultiButton>
</SettingsLine> </SettingsLine>
</Card> </Card>

View file

@ -0,0 +1,65 @@
import React from 'react';
import {
CardWithTitle,
SubTitle,
Card,
Sub4Title,
} from '../../components/generic/Styled';
import { SettingsLine } from '../../../pages/settings';
import { useConfigState, useConfigDispatch } from '../../context/ConfigContext';
import {
MultiButton,
SingleButton,
} from '../../components/buttons/multiButton/MultiButton';
export const PrivacySettings = () => {
const { fetchFees, fetchPrices, displayValues } = useConfigState();
const dispatch = useConfigDispatch();
const renderButton = (
title: string,
value: boolean,
type: string,
current: boolean
) => (
<SingleButton
selected={current === value}
onClick={() => {
localStorage.setItem(type, JSON.stringify(value));
dispatch({ type: 'change', [type]: value });
}}
>
{title}
</SingleButton>
);
return (
<CardWithTitle>
<SubTitle>Privacy</SubTitle>
<Card>
<SettingsLine>
<Sub4Title>Fetch Bitcoin Fees:</Sub4Title>
<MultiButton>
{renderButton('On', true, 'fetchFees', fetchFees)}
{renderButton('Off', false, 'fetchFees', fetchFees)}
</MultiButton>
</SettingsLine>
<SettingsLine>
<Sub4Title>Fetch Fiat Prices:</Sub4Title>
<MultiButton margin={'0 0 0 16px'}>
{renderButton('On', true, 'fetchPrices', fetchPrices)}
{renderButton('Off', false, 'fetchPrices', fetchPrices)}
</MultiButton>
</SettingsLine>
<SettingsLine>
<Sub4Title>Values:</Sub4Title>
<MultiButton margin={'0 0 0 16px'}>
{renderButton('Show', true, 'displayValues', displayValues)}
{renderButton('Hide', false, 'displayValues', displayValues)}
</MultiButton>
</SettingsLine>
</Card>
</CardWithTitle>
);
};

View file

@ -16,5 +16,5 @@
"downlevelIteration": true "downlevelIteration": true
}, },
"exclude": ["node_modules", ".next"], "exclude": ["node_modules", ".next"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js"]
} }

View file

@ -6106,6 +6106,11 @@ cookie@0.4.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
cookie@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
copy-concurrently@^1.0.0: copy-concurrently@^1.0.0:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
@ -10327,6 +10332,11 @@ jest@^25.5.4:
import-local "^3.0.2" import-local "^3.0.2"
jest-cli "^25.5.4" jest-cli "^25.5.4"
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
js-levenshtein@^1.1.3: js-levenshtein@^1.1.3:
version "1.1.6" version "1.1.6"
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"