mirror of
https://github.com/apotdevin/thunderhub.git
synced 2024-11-19 09:50:03 +01:00
chore: 🔧 add more currencies
This commit is contained in:
parent
b33242e7cd
commit
dddf895f04
@ -136,8 +136,8 @@ BASE_PATH = '[Base path where you want to have thunderhub running i.e. '/btcpay'
|
||||
# -----------
|
||||
# Interface Configs
|
||||
# -----------
|
||||
THEME = 'dark' | 'light'; // Default: 'dark'
|
||||
CURRENCY = 'sat' | 'btc' | 'eur' | 'usd'; // Default: 'sat'
|
||||
THEME = 'dark' | 'light' // Default: 'dark'
|
||||
CURRENCY = 'sat' | 'btc' | 'fiat' // Default: 'sat'
|
||||
|
||||
# -----------
|
||||
# Privacy Configs
|
||||
|
5
package-lock.json
generated
5
package-lock.json
generated
@ -18456,6 +18456,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"lodash.omit": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
|
||||
"integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA="
|
||||
},
|
||||
"lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
|
@ -63,6 +63,7 @@
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.groupby": "^4.6.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"next": "^9.4.4",
|
||||
"numeral": "^2.0.6",
|
||||
"qrcode.react": "^1.0.0",
|
||||
|
@ -56,14 +56,14 @@ const App = ({ Component, pageProps, initialConfig }) => (
|
||||
App.getInitialProps = async props => {
|
||||
const cookies = parseCookies(props.ctx.req);
|
||||
|
||||
if (!cookies?.config) {
|
||||
return { initialConfig: {} };
|
||||
if (!cookies?.theme) {
|
||||
return { initialConfig: 'dark' };
|
||||
}
|
||||
try {
|
||||
const initialConfig = JSON.parse(cookies.config);
|
||||
const initialConfig = cookies.theme || 'dark';
|
||||
return { initialConfig };
|
||||
} catch (error) {
|
||||
return { initialConfig: {} };
|
||||
return { initialConfig: 'dark' };
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -20,7 +20,7 @@ export const AnimatedNumber = ({ amount = 0 }: AnimatedProps) => {
|
||||
value: amount,
|
||||
});
|
||||
const { currency, displayValues } = useConfigState();
|
||||
const { prices, dontShow } = usePriceState();
|
||||
const { fiat, prices, dontShow } = usePriceState();
|
||||
|
||||
if (!displayValues) {
|
||||
return <>-</>;
|
||||
@ -32,8 +32,8 @@ export const AnimatedNumber = ({ amount = 0 }: AnimatedProps) => {
|
||||
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
|
||||
};
|
||||
|
||||
if (prices && !dontShow) {
|
||||
const current: { last: number; symbol: string } = prices[currency] ?? {
|
||||
if (currency === 'fiat' && prices && !dontShow) {
|
||||
const current: { last: number; symbol: string } = prices[fiat] ?? {
|
||||
last: 0,
|
||||
symbol: '',
|
||||
};
|
||||
|
@ -19,6 +19,8 @@ const generalCSS = css`
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
|
||||
@media (${mediaWidths.mobile}) {
|
||||
top: 100%;
|
||||
@ -27,7 +29,6 @@ const generalCSS = css`
|
||||
width: 100%;
|
||||
min-width: 325px;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -19,7 +19,7 @@ export const Price = ({
|
||||
override?: string;
|
||||
}): JSX.Element => {
|
||||
const { currency, displayValues } = useConfigState();
|
||||
const { prices, dontShow } = usePriceState();
|
||||
const { fiat, prices, dontShow } = usePriceState();
|
||||
|
||||
if (!displayValues) {
|
||||
return <>-</>;
|
||||
@ -31,8 +31,8 @@ export const Price = ({
|
||||
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
|
||||
};
|
||||
|
||||
if (prices && !dontShow) {
|
||||
const current: { last: number; symbol: string } = prices[currency] ?? {
|
||||
if (currency === 'fiat' && prices && !dontShow) {
|
||||
const current: { last: number; symbol: string } = prices[fiat] ?? {
|
||||
last: 0,
|
||||
symbol: '',
|
||||
};
|
||||
@ -57,11 +57,12 @@ export const getPrice = (
|
||||
currency: string,
|
||||
displayValues: boolean,
|
||||
priceContext: {
|
||||
fiat: string;
|
||||
dontShow: boolean;
|
||||
prices?: { [key: string]: { last: number; symbol: string } };
|
||||
}
|
||||
) => ({ amount, breakNumber = false, override }: GetPriceProps): string => {
|
||||
const { prices, dontShow } = priceContext;
|
||||
const { prices, dontShow, fiat } = priceContext;
|
||||
|
||||
if (!displayValues) {
|
||||
return '-';
|
||||
@ -73,8 +74,8 @@ export const getPrice = (
|
||||
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
|
||||
};
|
||||
|
||||
if (prices && !dontShow) {
|
||||
const current: { last: number; symbol: string } = prices[currency] ?? {
|
||||
if (currency === 'fiat' && prices && !dontShow) {
|
||||
const current: { last: number; symbol: string } = prices[fiat] ?? {
|
||||
last: 0,
|
||||
symbol: '',
|
||||
};
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React, { createContext, useContext, useReducer, useEffect } from 'react';
|
||||
import getConfig from 'next/config';
|
||||
import Cookies from 'js-cookie';
|
||||
import omit from 'lodash.omit';
|
||||
|
||||
const themeTypes = ['dark', 'light'];
|
||||
const currencyTypes = ['sat', 'btc', 'EUR', 'USD'];
|
||||
const currencyTypes = ['sat', 'btc', 'fiat'];
|
||||
|
||||
export type channelBarStyleTypes = 'normal' | 'compact' | 'ultracompact';
|
||||
export type channelBarTypeTypes = 'balance' | 'fees' | 'size' | 'proportional';
|
||||
@ -29,27 +30,29 @@ type State = {
|
||||
};
|
||||
|
||||
type ConfigInitProps = {
|
||||
initialConfig: State;
|
||||
initialConfig: string;
|
||||
};
|
||||
|
||||
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;
|
||||
channelBarStyle?: channelBarStyleTypes;
|
||||
channelBarType?: channelBarTypeTypes;
|
||||
channelSort?: channelSortTypes;
|
||||
sortDirection?: sortDirectionTypes;
|
||||
};
|
||||
type ActionType =
|
||||
| {
|
||||
type: 'change' | 'initChange';
|
||||
currency?: string;
|
||||
theme?: string;
|
||||
sidebar?: boolean;
|
||||
multiNodeInfo?: boolean;
|
||||
fetchFees?: boolean;
|
||||
fetchPrices?: boolean;
|
||||
displayValues?: boolean;
|
||||
hideFee?: boolean;
|
||||
hideNonVerified?: boolean;
|
||||
maxFee?: number;
|
||||
chatPollingSpeed?: number;
|
||||
channelBarStyle?: channelBarStyleTypes;
|
||||
channelBarType?: channelBarTypeTypes;
|
||||
channelSort?: channelSortTypes;
|
||||
sortDirection?: sortDirectionTypes;
|
||||
}
|
||||
| { type: 'themeChange'; theme: string };
|
||||
|
||||
type Dispatch = (action: ActionType) => void;
|
||||
|
||||
@ -85,11 +88,27 @@ const initialState: State = {
|
||||
const stateReducer = (state: State, action: ActionType): State => {
|
||||
const { type, ...settings } = action;
|
||||
switch (type) {
|
||||
case 'change':
|
||||
case 'initChange': {
|
||||
return {
|
||||
...state,
|
||||
...settings,
|
||||
};
|
||||
}
|
||||
case 'change': {
|
||||
const newState = {
|
||||
...state,
|
||||
...settings,
|
||||
};
|
||||
localStorage.setItem('config', JSON.stringify(omit(newState, 'theme')));
|
||||
return newState;
|
||||
}
|
||||
case 'themeChange': {
|
||||
Cookies.set('theme', action.theme, { expires: 365, sameSite: 'strict' });
|
||||
return {
|
||||
...state,
|
||||
theme: action.theme,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@ -101,12 +120,13 @@ const ConfigProvider: React.FC<ConfigInitProps> = ({
|
||||
}) => {
|
||||
const [state, dispatch] = useReducer(stateReducer, {
|
||||
...initialState,
|
||||
...initialConfig,
|
||||
theme: themeTypes.indexOf(initialConfig) > -1 ? initialConfig : 'dark',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
Cookies.set('config', state, { expires: 365, sameSite: 'strict' });
|
||||
}, [state]);
|
||||
const savedConfig = JSON.parse(localStorage.getItem('config') || '{}');
|
||||
dispatch({ type: 'initChange', ...savedConfig });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DispatchContext.Provider value={dispatch}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { createContext, useContext, useReducer } from 'react';
|
||||
import React, { createContext, useContext, useReducer, useEffect } from 'react';
|
||||
|
||||
type PriceProps = {
|
||||
last: number;
|
||||
@ -7,6 +7,7 @@ type PriceProps = {
|
||||
|
||||
type State = {
|
||||
dontShow: boolean;
|
||||
fiat: string;
|
||||
prices?: { [key: string]: PriceProps };
|
||||
};
|
||||
|
||||
@ -19,6 +20,10 @@ type ActionType =
|
||||
type: 'fetched';
|
||||
state: ChangeState;
|
||||
}
|
||||
| {
|
||||
type: 'change' | 'initChange';
|
||||
fiat: string;
|
||||
}
|
||||
| {
|
||||
type: 'dontShow';
|
||||
};
|
||||
@ -29,6 +34,7 @@ export const StateContext = createContext<State | undefined>(undefined);
|
||||
export const DispatchContext = createContext<Dispatch | undefined>(undefined);
|
||||
|
||||
const initialState: State = {
|
||||
fiat: 'EUR',
|
||||
dontShow: true,
|
||||
prices: { EUR: { last: 0, symbol: '€' } },
|
||||
};
|
||||
@ -38,7 +44,14 @@ const stateReducer = (state: State, action: ActionType): State => {
|
||||
case 'dontShow':
|
||||
return { ...initialState, dontShow: true };
|
||||
case 'fetched':
|
||||
return { ...initialState, ...action.state, dontShow: false };
|
||||
return { ...state, ...action.state, dontShow: false };
|
||||
case 'change': {
|
||||
localStorage.setItem('fiat', action.fiat);
|
||||
return { ...state, fiat: action.fiat };
|
||||
}
|
||||
case 'initChange': {
|
||||
return { ...state, fiat: action.fiat };
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@ -47,6 +60,11 @@ const stateReducer = (state: State, action: ActionType): State => {
|
||||
const PriceProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(stateReducer, initialState);
|
||||
|
||||
useEffect(() => {
|
||||
const fiat = localStorage.getItem('fiat') || 'EUR';
|
||||
dispatch({ type: 'initChange', fiat });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DispatchContext.Provider value={dispatch}>
|
||||
<StateContext.Provider value={state}>{children}</StateContext.Provider>
|
||||
|
@ -110,6 +110,7 @@ export const NodeInfo = ({ isOpen, isBurger }: NodeInfoProps) => {
|
||||
</SingleLine>
|
||||
<SingleLine>
|
||||
<Zap
|
||||
size={18}
|
||||
color={channelPending === 0 ? '#FFD300' : '#652EC7'}
|
||||
fill={channelPending === 0 ? '#FFD300' : '#652EC7'}
|
||||
/>
|
||||
|
@ -112,7 +112,7 @@ export const SideSettings = ({ isBurger }: SideSettingsProps) => {
|
||||
currency:
|
||||
sidebar || isBurger ? value : getNextValue(correctArray, value),
|
||||
});
|
||||
type === 'theme' && dispatch({ type: 'change', theme: value });
|
||||
type === 'theme' && dispatch({ type: 'themeChange', theme: value });
|
||||
}}
|
||||
>
|
||||
{type === 'currency' && <Symbol>{text}</Symbol>}
|
||||
@ -146,8 +146,7 @@ export const SideSettings = ({ isBurger }: SideSettingsProps) => {
|
||||
<IconRow>
|
||||
{renderIcon('currency', 'sat', 'S')}
|
||||
{renderIcon('currency', 'btc', 'â‚¿')}
|
||||
{!dontShow && renderIcon('currency', 'EUR', '€')}
|
||||
{!dontShow && renderIcon('currency', 'USD', '$')}
|
||||
{!dontShow && renderIcon('currency', 'fiat', 'F')}
|
||||
</IconRow>
|
||||
<IconRow>
|
||||
{renderIcon('theme', 'light', '', false, Sun)}
|
||||
@ -163,8 +162,7 @@ export const SideSettings = ({ isBurger }: SideSettingsProps) => {
|
||||
<IconRow>
|
||||
{renderIcon('currency', 'sat', 'S')}
|
||||
{renderIcon('currency', 'btc', 'â‚¿')}
|
||||
{!dontShow && renderIcon('currency', 'EUR', '€')}
|
||||
{!dontShow && renderIcon('currency', 'USD', '$')}
|
||||
{!dontShow && renderIcon('currency', 'fiat', 'F')}
|
||||
</IconRow>
|
||||
<IconRow>
|
||||
{renderIcon('theme', 'light', '', false, Sun)}
|
||||
|
@ -1,24 +1,32 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useAccountState, CLIENT_ACCOUNT } from 'src/context/AccountContext';
|
||||
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
|
||||
import Modal from 'src/components/modal/ReactModal';
|
||||
import numeral from 'numeral';
|
||||
import { themeColors } from 'src/styles/Themes';
|
||||
import {
|
||||
CardWithTitle,
|
||||
SubTitle,
|
||||
Card,
|
||||
Sub4Title,
|
||||
SingleLine,
|
||||
SubCard,
|
||||
DarkSubTitle,
|
||||
} from '../../components/generic/Styled';
|
||||
import { SettingsLine } from '../../../pages/settings';
|
||||
import { useConfigState, useConfigDispatch } from '../../context/ConfigContext';
|
||||
|
||||
import {
|
||||
MultiButton,
|
||||
SingleButton,
|
||||
} from '../../components/buttons/multiButton/MultiButton';
|
||||
import { usePriceState } from '../../context/PriceContext';
|
||||
import { usePriceState, usePriceDispatch } from '../../context/PriceContext';
|
||||
|
||||
export const InterfaceSettings = () => {
|
||||
const { dontShow } = usePriceState();
|
||||
const [changeFiat, changeFiatSet] = useState(false);
|
||||
const { fiat, prices, dontShow } = usePriceState();
|
||||
const { theme, currency, multiNodeInfo } = useConfigState();
|
||||
const dispatch = useConfigDispatch();
|
||||
const priceDispatch = usePriceDispatch();
|
||||
|
||||
const { accounts } = useAccountState();
|
||||
|
||||
@ -36,7 +44,7 @@ export const InterfaceSettings = () => {
|
||||
selected={current === value}
|
||||
onClick={() => {
|
||||
localStorage.setItem(type, value);
|
||||
type === 'theme' && dispatch({ type: 'change', theme: value });
|
||||
type === 'theme' && dispatch({ type: 'themeChange', theme: value });
|
||||
type === 'currency' && dispatch({ type: 'change', currency: value });
|
||||
type === 'nodeInfo' &&
|
||||
dispatch({
|
||||
@ -49,36 +57,93 @@ export const InterfaceSettings = () => {
|
||||
</SingleButton>
|
||||
);
|
||||
|
||||
const handleFiatClick = (fiatCurrency: string) => {
|
||||
changeFiatSet(false);
|
||||
priceDispatch({ type: 'change', fiat: fiatCurrency });
|
||||
};
|
||||
|
||||
const renderFiat = () => {
|
||||
const cards = [];
|
||||
for (const key in prices) {
|
||||
if (Object.prototype.hasOwnProperty.call(prices, key)) {
|
||||
const element = prices[key];
|
||||
if (!element || !element.last || !element.symbol) return;
|
||||
const isCurrent = fiat === key;
|
||||
cards.push(
|
||||
<SubCard color={isCurrent ? themeColors.blue2 : undefined} key={key}>
|
||||
<SingleLine>
|
||||
{key}
|
||||
<DarkSubTitle>{`${element.symbol} ${numeral(element.last).format(
|
||||
'0,0'
|
||||
)}`}</DarkSubTitle>
|
||||
<ColorButton
|
||||
onClick={() => handleFiatClick(key)}
|
||||
disabled={isCurrent}
|
||||
arrow={true}
|
||||
>
|
||||
Select
|
||||
</ColorButton>
|
||||
</SingleLine>
|
||||
</SubCard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return cards;
|
||||
};
|
||||
|
||||
return (
|
||||
<CardWithTitle>
|
||||
<SubTitle>Interface</SubTitle>
|
||||
<Card>
|
||||
<SettingsLine>
|
||||
<Sub4Title>Theme:</Sub4Title>
|
||||
<MultiButton>
|
||||
{renderButton('Light', 'light', 'theme', theme)}
|
||||
{renderButton('Dark', 'dark', 'theme', theme)}
|
||||
</MultiButton>
|
||||
</SettingsLine>
|
||||
{viewOnlyAccounts.length > 1 && (
|
||||
<>
|
||||
<CardWithTitle>
|
||||
<SubTitle>Interface</SubTitle>
|
||||
<Card>
|
||||
<SettingsLine>
|
||||
<Sub4Title>Show all accounts on homepage:</Sub4Title>
|
||||
<Sub4Title>Theme</Sub4Title>
|
||||
<MultiButton>
|
||||
{renderButton('Yes', 'true', 'nodeInfo', `${multiNodeInfo}`)}
|
||||
{renderButton('No', 'false', 'nodeInfo', `${multiNodeInfo}`)}
|
||||
{renderButton('Light', 'light', 'theme', theme)}
|
||||
{renderButton('Dark', 'dark', 'theme', theme)}
|
||||
</MultiButton>
|
||||
</SettingsLine>
|
||||
)}
|
||||
<SettingsLine>
|
||||
<Sub4Title>Currency:</Sub4Title>
|
||||
<MultiButton margin={'0 0 0 16px'}>
|
||||
{renderButton('Satoshis', 'sat', 'currency', currency)}
|
||||
{renderButton('Bitcoin', 'btc', 'currency', currency)}
|
||||
{!dontShow && renderButton('Euro', 'EUR', 'currency', currency)}
|
||||
{!dontShow && renderButton('USD', 'USD', 'currency', currency)}
|
||||
</MultiButton>
|
||||
</SettingsLine>
|
||||
</Card>
|
||||
</CardWithTitle>
|
||||
{viewOnlyAccounts.length > 1 && (
|
||||
<SettingsLine>
|
||||
<Sub4Title>Show all accounts on homepage</Sub4Title>
|
||||
<MultiButton>
|
||||
{renderButton('Yes', 'true', 'nodeInfo', `${multiNodeInfo}`)}
|
||||
{renderButton('No', 'false', 'nodeInfo', `${multiNodeInfo}`)}
|
||||
</MultiButton>
|
||||
</SettingsLine>
|
||||
)}
|
||||
<SettingsLine>
|
||||
<Sub4Title>Currency</Sub4Title>
|
||||
<MultiButton margin={'0 0 0 16px'}>
|
||||
{renderButton('Satoshis', 'sat', 'currency', currency)}
|
||||
{renderButton('Bitcoin', 'btc', 'currency', currency)}
|
||||
{!dontShow && renderButton('Fiat', 'fiat', 'currency', currency)}
|
||||
</MultiButton>
|
||||
</SettingsLine>
|
||||
{currency === 'fiat' && !dontShow && (
|
||||
<SettingsLine>
|
||||
<SingleLine>
|
||||
<Sub4Title>Fiat</Sub4Title>
|
||||
<DarkSubTitle
|
||||
withMargin={'0 0 0 8px'}
|
||||
>{`(${fiat})`}</DarkSubTitle>
|
||||
</SingleLine>
|
||||
<ColorButton onClick={() => changeFiatSet(true)} arrow={true}>
|
||||
Change
|
||||
</ColorButton>
|
||||
</SettingsLine>
|
||||
)}
|
||||
</Card>
|
||||
</CardWithTitle>
|
||||
<Modal
|
||||
isOpen={changeFiat}
|
||||
closeCallback={() => {
|
||||
changeFiatSet(false);
|
||||
}}
|
||||
>
|
||||
{renderFiat()}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user