mirror of
https://github.com/apotdevin/thunderhub.git
synced 2025-02-22 14:22:33 +01:00
feat: adminSwitch and secureButton
This commit is contained in:
parent
be7c4efb3c
commit
22e79529c0
24 changed files with 506 additions and 134 deletions
|
@ -23,6 +23,7 @@ import { CreateInvoiceCard } from './createInvoice/CreateInvoice';
|
|||
import { SendOnChainCard } from './sendOnChain/SendOnChain';
|
||||
import { ReceiveOnChainCard } from './receiveOnChain/ReceiveOnChain';
|
||||
import { LoadingCard } from '../loading/LoadingCard';
|
||||
import { AdminSwitch } from '../adminSwitch/AdminSwitch';
|
||||
|
||||
const Tile = styled.div`
|
||||
display: flex;
|
||||
|
@ -124,32 +125,34 @@ export const AccountInfo = () => {
|
|||
<DarkSubTitle>Pending Balance</DarkSubTitle>
|
||||
<div>{formatPCB}</div>
|
||||
</Tile>
|
||||
<ButtonRow>
|
||||
{showLn && showChain && (
|
||||
<>
|
||||
<AdminSwitch>
|
||||
<ButtonRow>
|
||||
{showLn && showChain && (
|
||||
<>
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('send_ln')}
|
||||
>
|
||||
<Send />
|
||||
</ColorButton>
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('receive_ln')}
|
||||
>
|
||||
<DownArrow />
|
||||
</ColorButton>
|
||||
</>
|
||||
)}
|
||||
{showLn && !showChain && (
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('send_ln')}
|
||||
onClick={() => setState('none')}
|
||||
>
|
||||
<Send />
|
||||
<XSvg />
|
||||
</ColorButton>
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('receive_ln')}
|
||||
>
|
||||
<DownArrow />
|
||||
</ColorButton>
|
||||
</>
|
||||
)}
|
||||
{showLn && !showChain && (
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('none')}
|
||||
>
|
||||
<XSvg />
|
||||
</ColorButton>
|
||||
)}
|
||||
</ButtonRow>
|
||||
)}
|
||||
</ButtonRow>
|
||||
</AdminSwitch>
|
||||
</SingleLine>
|
||||
);
|
||||
|
||||
|
@ -170,32 +173,34 @@ export const AccountInfo = () => {
|
|||
<DarkSubTitle>Pending Balance</DarkSubTitle>
|
||||
<div>{formatPB}</div>
|
||||
</Tile>
|
||||
<ButtonRow>
|
||||
{showLn && showChain && (
|
||||
<>
|
||||
<AdminSwitch>
|
||||
<ButtonRow>
|
||||
{showLn && showChain && (
|
||||
<>
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('send_chain')}
|
||||
>
|
||||
<Send />
|
||||
</ColorButton>
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('receive_chain')}
|
||||
>
|
||||
<DownArrow />
|
||||
</ColorButton>
|
||||
</>
|
||||
)}
|
||||
{!showLn && showChain && (
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('send_chain')}
|
||||
onClick={() => setState('none')}
|
||||
>
|
||||
<Send />
|
||||
<XSvg />
|
||||
</ColorButton>
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('receive_chain')}
|
||||
>
|
||||
<DownArrow />
|
||||
</ColorButton>
|
||||
</>
|
||||
)}
|
||||
{!showLn && showChain && (
|
||||
<ColorButton
|
||||
color={sectionColor}
|
||||
onClick={() => setState('none')}
|
||||
>
|
||||
<XSvg />
|
||||
</ColorButton>
|
||||
)}
|
||||
</ButtonRow>
|
||||
)}
|
||||
</ButtonRow>
|
||||
</AdminSwitch>
|
||||
</SingleLine>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Input, ColorButton, NoWrapTitle } from '../../generic/Styled';
|
||||
import { Input, NoWrapTitle } from '../../generic/Styled';
|
||||
import { useMutation } from '@apollo/react-hooks';
|
||||
import { CREATE_INVOICE } from '../../../graphql/mutation';
|
||||
import { Edit } from '../../generic/Icons';
|
||||
import styled from 'styled-components';
|
||||
import { toast } from 'react-toastify';
|
||||
import { getErrorContent } from '../../../utils/error';
|
||||
import { useAccount } from '../../../context/AccountContext';
|
||||
import { getAuthString } from '../../../utils/auth';
|
||||
import { SecureButton } from '../../secureButton/SecureButton';
|
||||
|
||||
const SingleLine = styled.div`
|
||||
display: flex;
|
||||
|
@ -18,9 +17,6 @@ const SingleLine = styled.div`
|
|||
export const CreateInvoiceCard = ({ color }: { color: string }) => {
|
||||
const [amount, setAmount] = useState(0);
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
|
||||
const [createInvoice, { data, loading }] = useMutation(CREATE_INVOICE, {
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
});
|
||||
|
@ -35,17 +31,16 @@ export const CreateInvoiceCard = ({ color }: { color: string }) => {
|
|||
type={'number'}
|
||||
onChange={e => setAmount(parseInt(e.target.value))}
|
||||
/>
|
||||
<ColorButton
|
||||
<SecureButton
|
||||
callback={createInvoice}
|
||||
variables={{ amount }}
|
||||
color={color}
|
||||
disabled={amount === 0}
|
||||
enabled={amount > 0}
|
||||
onClick={() => {
|
||||
createInvoice({ variables: { amount, auth } });
|
||||
}}
|
||||
>
|
||||
<Edit />
|
||||
Create Invoice
|
||||
</ColorButton>
|
||||
</SecureButton>
|
||||
</SingleLine>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,24 +1,15 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Sub4Title,
|
||||
Input,
|
||||
SingleLine,
|
||||
ColorButton,
|
||||
} from '../../generic/Styled';
|
||||
import { Sub4Title, Input, SingleLine } from '../../generic/Styled';
|
||||
import { useMutation } from '@apollo/react-hooks';
|
||||
import { PAY_INVOICE } from '../../../graphql/mutation';
|
||||
import { Send } from '../../generic/Icons';
|
||||
import { useAccount } from '../../../context/AccountContext';
|
||||
import { getAuthString } from '../../../utils/auth';
|
||||
import { toast } from 'react-toastify';
|
||||
import { getErrorContent } from '../../../utils/error';
|
||||
import { SecureButton } from '../../secureButton/SecureButton';
|
||||
|
||||
export const PayCard = ({ color }: { color: string }) => {
|
||||
const [request, setRequest] = useState('');
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
|
||||
const [makePayment] = useMutation(PAY_INVOICE, {
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
onCompleted: () => toast.success('Payment Sent!'),
|
||||
|
@ -28,17 +19,16 @@ export const PayCard = ({ color }: { color: string }) => {
|
|||
<SingleLine>
|
||||
<Sub4Title>Request:</Sub4Title>
|
||||
<Input color={color} onChange={e => setRequest(e.target.value)} />
|
||||
<ColorButton
|
||||
<SecureButton
|
||||
callback={makePayment}
|
||||
variables={{ request }}
|
||||
color={color}
|
||||
disabled={request === ''}
|
||||
enabled={request !== ''}
|
||||
onClick={() => {
|
||||
makePayment({ variables: { request, auth } });
|
||||
}}
|
||||
disabled={request === ''}
|
||||
>
|
||||
<Send />
|
||||
Send Sats
|
||||
</ColorButton>
|
||||
</SecureButton>
|
||||
</SingleLine>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,8 +11,7 @@ import { Edit, Circle } from '../../generic/Icons';
|
|||
import styled from 'styled-components';
|
||||
import { toast } from 'react-toastify';
|
||||
import { getErrorContent } from '../../../utils/error';
|
||||
import { useAccount } from '../../../context/AccountContext';
|
||||
import { getAuthString } from '../../../utils/auth';
|
||||
import { SecureButton } from '../../secureButton/SecureButton';
|
||||
|
||||
const SingleLine = styled.div`
|
||||
display: flex;
|
||||
|
@ -37,9 +36,6 @@ export const ReceiveOnChainCard = ({ color }: { color: string }) => {
|
|||
const [nested, setNested] = useState(false);
|
||||
const [received, setReceived] = useState(false);
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
|
||||
const [createAddress, { data }] = useMutation(CREATE_ADDRESS, {
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
});
|
||||
|
@ -78,16 +74,16 @@ export const ReceiveOnChainCard = ({ color }: { color: string }) => {
|
|||
<RadioText>NP2WPKH</RadioText>
|
||||
</ColorButton>
|
||||
</ButtonRow>
|
||||
<ColorButton
|
||||
<SecureButton
|
||||
callback={createAddress}
|
||||
variables={{ nested }}
|
||||
color={color}
|
||||
enabled={!received}
|
||||
disabled={received}
|
||||
onClick={() => {
|
||||
createAddress({ variables: { auth, nested } });
|
||||
}}
|
||||
>
|
||||
<Edit />
|
||||
Create Address
|
||||
</ColorButton>
|
||||
</SecureButton>
|
||||
</SingleLine>
|
||||
{data && data.createAddress && (
|
||||
<>
|
||||
|
|
|
@ -14,11 +14,10 @@ import { Circle, ChevronRight } from '../../generic/Icons';
|
|||
import styled from 'styled-components';
|
||||
import { toast } from 'react-toastify';
|
||||
import { getErrorContent } from '../../../utils/error';
|
||||
import { useAccount } from '../../../context/AccountContext';
|
||||
import { getAuthString } from '../../../utils/auth';
|
||||
import { useSettings } from '../../../context/SettingsContext';
|
||||
import { getValue } from '../../../helpers/Helpers';
|
||||
import { useBitcoinInfo } from '../../../context/BitcoinContext';
|
||||
import { SecureButton } from '../../secureButton/SecureButton';
|
||||
|
||||
const RadioText = styled.div`
|
||||
margin-left: 10px;
|
||||
|
@ -35,10 +34,6 @@ const SmallInput = styled(Input)`
|
|||
max-width: 150px;
|
||||
`;
|
||||
|
||||
const RightButton = styled(ColorButton)`
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
export const SendOnChainCard = ({ color }: { color: string }) => {
|
||||
const [address, setAddress] = useState('');
|
||||
const [tokens, setTokens] = useState(0);
|
||||
|
@ -47,13 +42,11 @@ export const SendOnChainCard = ({ color }: { color: string }) => {
|
|||
const [sendAll, setSendAll] = useState(false);
|
||||
const [isSent, setIsSent] = useState(false);
|
||||
|
||||
const canSend = address !== '' && tokens > 0 && amount > 0;
|
||||
|
||||
const { price, symbol, currency } = useSettings();
|
||||
|
||||
const { fast, halfHour, hour } = useBitcoinInfo();
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
|
||||
useEffect(() => {
|
||||
if (type === 'none' && amount === 0) {
|
||||
setAmount(fast);
|
||||
|
@ -210,22 +203,16 @@ export const SendOnChainCard = ({ color }: { color: string }) => {
|
|||
)}
|
||||
</SingleLine>
|
||||
<Separation />
|
||||
<RightButton
|
||||
<SecureButton
|
||||
callback={payAddress}
|
||||
variables={{ address, ...typeAmount, ...tokenAmount }}
|
||||
color={color}
|
||||
onClick={() => {
|
||||
payAddress({
|
||||
variables: {
|
||||
auth,
|
||||
address,
|
||||
...typeAmount,
|
||||
...tokenAmount,
|
||||
},
|
||||
});
|
||||
}}
|
||||
enabled={canSend}
|
||||
disabled={!canSend}
|
||||
>
|
||||
Send To Address
|
||||
<ChevronRight />
|
||||
</RightButton>
|
||||
</SecureButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
15
src/components/adminSwitch/AdminSwitch.tsx
Normal file
15
src/components/adminSwitch/AdminSwitch.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
interface AdminSwitchProps {
|
||||
children: any;
|
||||
}
|
||||
|
||||
export const AdminSwitch = ({ children }: AdminSwitchProps) => {
|
||||
const currentAuth = localStorage.getItem('account') || 'auth1';
|
||||
const adminMacaroon = localStorage.getItem(`${currentAuth}-admin`) || '';
|
||||
const sessionAdmin = sessionStorage.getItem('session') || '';
|
||||
|
||||
if (!adminMacaroon && !sessionAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
|
@ -29,6 +29,7 @@ import { getTransactionLink, getNodeLink } from '../../generic/Helpers';
|
|||
import Modal from '../../modal/ReactModal';
|
||||
import { CloseChannel } from '../../closeChannel/CloseChannel';
|
||||
import styled from 'styled-components';
|
||||
import { AdminSwitch } from '../../adminSwitch/AdminSwitch';
|
||||
|
||||
const CloseButton = styled(ColorButton)`
|
||||
margin-left: auto;
|
||||
|
@ -139,10 +140,15 @@ export const ChannelCard = ({
|
|||
lastUpdate,
|
||||
)})`,
|
||||
)}
|
||||
<Separation />
|
||||
<CloseButton color={'red'} onClick={() => setModalOpen(true)}>
|
||||
Close Channel
|
||||
</CloseButton>
|
||||
<AdminSwitch>
|
||||
<Separation />
|
||||
<CloseButton
|
||||
color={'red'}
|
||||
onClick={() => setModalOpen(true)}
|
||||
>
|
||||
Close Channel
|
||||
</CloseButton>
|
||||
</AdminSwitch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import { useAccount } from '../../../context/AccountContext';
|
|||
import { getAuthString } from '../../../utils/auth';
|
||||
import { toast } from 'react-toastify';
|
||||
import { getErrorContent } from '../../../utils/error';
|
||||
import { LoadingCard } from '../../loading/LoadingCard';
|
||||
|
||||
export const Channels = () => {
|
||||
const [indexOpen, setIndexOpen] = useState(0);
|
||||
|
@ -20,7 +21,7 @@ export const Channels = () => {
|
|||
});
|
||||
|
||||
if (loading || !data || !data.getChannels) {
|
||||
return <Card>Loading....</Card>;
|
||||
return <LoadingCard title={'Channels'} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -34,6 +34,7 @@ import { ReactComponent as GitBranchIcon } from '../../icons/git-branch.svg';
|
|||
import { ReactComponent as RadioIcon } from '../../icons/radio.svg';
|
||||
import { ReactComponent as CopyIcon } from '../../icons/copy.svg';
|
||||
import { ReactComponent as ShieldIcon } from '../../icons/shield.svg';
|
||||
import { ReactComponent as CrosshairIcon } from '../../icons/crosshair.svg';
|
||||
|
||||
interface IconProps {
|
||||
color?: string;
|
||||
|
@ -100,3 +101,4 @@ export const GitBranch = styleIcon(GitBranchIcon);
|
|||
export const Radio = styleIcon(RadioIcon);
|
||||
export const Copy = styleIcon(CopyIcon);
|
||||
export const Shield = styleIcon(ShieldIcon);
|
||||
export const Crosshair = styleIcon(CrosshairIcon);
|
||||
|
|
|
@ -75,12 +75,6 @@ export const SmallLink = styled.a`
|
|||
}
|
||||
`;
|
||||
|
||||
export const TitleRow = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const SubTitle = styled.h4`
|
||||
margin: 5px 0;
|
||||
font-weight: 500;
|
||||
|
|
|
@ -11,6 +11,7 @@ import { XSvg, Layers, GitBranch } from '../generic/Icons';
|
|||
import { unSelectedNavButton } from '../../styles/Themes';
|
||||
import { DecodeCard } from './decode/Decode';
|
||||
import { OpenChannelCard } from './openChannel/OpenChannel';
|
||||
import { AdminSwitch } from '../adminSwitch/AdminSwitch';
|
||||
|
||||
const sectionColor = '#69c0ff';
|
||||
|
||||
|
@ -69,10 +70,14 @@ export const QuickActions = () => {
|
|||
default:
|
||||
return (
|
||||
<QuickRow>
|
||||
<QuickCard onClick={() => setOpenCard('open_channel')}>
|
||||
<GitBranch size={'24px'} color={sectionColor} />
|
||||
<QuickTitle>Open</QuickTitle>
|
||||
</QuickCard>
|
||||
<AdminSwitch>
|
||||
<QuickCard
|
||||
onClick={() => setOpenCard('open_channel')}
|
||||
>
|
||||
<GitBranch size={'24px'} color={sectionColor} />
|
||||
<QuickTitle>Open</QuickTitle>
|
||||
</QuickCard>
|
||||
</AdminSwitch>
|
||||
<QuickCard onClick={() => setOpenCard('decode')}>
|
||||
<Layers size={'24px'} color={sectionColor} />
|
||||
<QuickTitle>Decode</QuickTitle>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { toast } from 'react-toastify';
|
|||
import { getErrorContent } from '../../utils/error';
|
||||
import { PaymentsCard } from './PaymentsCards';
|
||||
import styled from 'styled-components';
|
||||
import { LoadingCard } from '../loading/LoadingCard';
|
||||
|
||||
export const AddMargin = styled.div`
|
||||
margin-left: 10px;
|
||||
|
@ -33,7 +34,7 @@ export const ResumeList = () => {
|
|||
}, [data, loading]);
|
||||
|
||||
if (loading || !data || !data.getResume) {
|
||||
return <Card>Loading....</Card>;
|
||||
return <LoadingCard title={'Resume'} />;
|
||||
}
|
||||
|
||||
const renderInvoices = () => {
|
||||
|
|
109
src/components/secureButton/LoginModal.tsx
Normal file
109
src/components/secureButton/LoginModal.tsx
Normal file
|
@ -0,0 +1,109 @@
|
|||
import React, { useState } from 'react';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import { toast } from 'react-toastify';
|
||||
import {
|
||||
SingleLine,
|
||||
Sub4Title,
|
||||
Input,
|
||||
NoWrapTitle,
|
||||
ColorButton,
|
||||
} from '../../components/generic/Styled';
|
||||
import { LoginButton } from '../../components/auth/Password';
|
||||
import { Circle, ChevronRight } from '../generic/Icons';
|
||||
import styled from 'styled-components';
|
||||
import { useAccount } from '../../context/AccountContext';
|
||||
import { getAuthString } from '../../utils/auth';
|
||||
|
||||
const RadioText = styled.div`
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
const ButtonRow = styled.div`
|
||||
width: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
interface LoginProps {
|
||||
macaroon: string;
|
||||
color: string;
|
||||
callback: any;
|
||||
variables: {};
|
||||
setModalOpen: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export const LoginModal = ({
|
||||
macaroon,
|
||||
color,
|
||||
setModalOpen,
|
||||
callback,
|
||||
variables,
|
||||
}: LoginProps) => {
|
||||
const [pass, setPass] = useState<string>('');
|
||||
const [storeSession, setStoreSession] = useState<boolean>(false);
|
||||
const { host, cert } = useAccount();
|
||||
|
||||
const handleClick = () => {
|
||||
try {
|
||||
const bytes = CryptoJS.AES.decrypt(macaroon, pass);
|
||||
const decrypted = bytes.toString(CryptoJS.enc.Utf8);
|
||||
|
||||
if (storeSession) {
|
||||
sessionStorage.setItem('session', decrypted);
|
||||
}
|
||||
const auth = getAuthString(host, decrypted, cert);
|
||||
callback({ variables: { ...variables, auth } });
|
||||
setModalOpen(false);
|
||||
} catch (error) {
|
||||
toast.error('Wrong Password');
|
||||
}
|
||||
};
|
||||
|
||||
const renderButton = (
|
||||
onClick: () => void,
|
||||
text: string,
|
||||
selected: boolean,
|
||||
) => (
|
||||
<ColorButton color={color} onClick={onClick}>
|
||||
<Circle size={'10px'} fillcolor={selected ? 'white' : ''} />
|
||||
<RadioText>{text}</RadioText>
|
||||
</ColorButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
LOGIN MODAL
|
||||
<SingleLine>
|
||||
<Sub4Title>Password:</Sub4Title>
|
||||
<Input onChange={e => setPass(e.target.value)} />
|
||||
</SingleLine>
|
||||
<SingleLine>
|
||||
<NoWrapTitle>Don't ask me again this session:</NoWrapTitle>
|
||||
<ButtonRow>
|
||||
{renderButton(
|
||||
() => setStoreSession(true),
|
||||
'Yes',
|
||||
storeSession,
|
||||
)}
|
||||
{renderButton(
|
||||
() => setStoreSession(false),
|
||||
'No',
|
||||
!storeSession,
|
||||
)}
|
||||
</ButtonRow>
|
||||
</SingleLine>
|
||||
{pass !== '' && (
|
||||
<LoginButton
|
||||
disabled={pass === ''}
|
||||
enabled={pass !== ''}
|
||||
onClick={handleClick}
|
||||
color={color}
|
||||
>
|
||||
Unlock
|
||||
<ChevronRight />
|
||||
</LoginButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
66
src/components/secureButton/SecureButton.tsx
Normal file
66
src/components/secureButton/SecureButton.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
import React, { useState } from 'react';
|
||||
import Modal from '../modal/ReactModal';
|
||||
import { LoginModal } from './LoginModal';
|
||||
import { ColorButton } from '../generic/Styled';
|
||||
import { useAccount } from '../../context/AccountContext';
|
||||
import { getAuthString } from '../../utils/auth';
|
||||
|
||||
interface SecureButtonProps {
|
||||
callback: any;
|
||||
color: string;
|
||||
disabled: boolean;
|
||||
enabled: boolean;
|
||||
children: any;
|
||||
variables: {};
|
||||
}
|
||||
|
||||
export const SecureButton = ({
|
||||
callback,
|
||||
color,
|
||||
enabled,
|
||||
disabled,
|
||||
children,
|
||||
variables,
|
||||
}: SecureButtonProps) => {
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
|
||||
const { host, cert } = useAccount();
|
||||
|
||||
const currentAuth = localStorage.getItem('account') || 'auth1';
|
||||
const adminMacaroon = localStorage.getItem(`${currentAuth}-admin`) || '';
|
||||
const sessionAdmin = sessionStorage.getItem('session') || '';
|
||||
|
||||
if (!adminMacaroon && !sessionAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const auth = getAuthString(host, sessionAdmin, cert);
|
||||
|
||||
const handleClick = () => setModalOpen(true);
|
||||
|
||||
const onClick = sessionAdmin
|
||||
? () => callback({ variables: { ...variables, auth } })
|
||||
: handleClick;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ColorButton
|
||||
color={color}
|
||||
disabled={disabled}
|
||||
enabled={enabled}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</ColorButton>
|
||||
<Modal isOpen={modalOpen} setIsOpen={setModalOpen}>
|
||||
<LoginModal
|
||||
color={color}
|
||||
macaroon={adminMacaroon}
|
||||
setModalOpen={setModalOpen}
|
||||
callback={callback}
|
||||
variables={variables}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -93,8 +93,6 @@ const AccountProvider = ({ children }: any) => {
|
|||
const readMacaroon = read ? read : sessionAdmin;
|
||||
const loggedIn = host !== '' && readMacaroon !== '';
|
||||
|
||||
console.log('REFRESHING', readMacaroon);
|
||||
|
||||
updateAccount((prevState: any) => {
|
||||
const newState = { ...prevState };
|
||||
return merge(newState, {
|
||||
|
|
|
@ -216,3 +216,16 @@ export const RECOVER_FUNDS = gql`
|
|||
recoverFunds(auth: $auth, backup: $backup)
|
||||
}
|
||||
`;
|
||||
|
||||
export const CHANNEL_FEES = gql`
|
||||
query GetChannelFees($auth: String!) {
|
||||
getChannelFees(auth: $auth) {
|
||||
alias
|
||||
color
|
||||
baseFee
|
||||
feeRate
|
||||
transactionId
|
||||
transactionVout
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
1
src/icons/crosshair.svg
Normal file
1
src/icons/crosshair.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-crosshair"><circle cx="12" cy="12" r="10"></circle><line x1="22" y1="12" x2="18" y2="12"></line><line x1="6" y1="12" x2="2" y2="12"></line><line x1="12" y1="6" x2="12" y2="2"></line><line x1="12" y1="22" x2="12" y2="18"></line></svg>
|
After Width: | Height: | Size: 437 B |
|
@ -7,6 +7,7 @@ import { ChannelView } from '../../views/channels/ChannelView';
|
|||
import { SettingsView } from '../../views/settings/Settings';
|
||||
import { ResumeList } from '../../components/resume/ResumeList';
|
||||
import { BackupsView } from '../../views/backups/Backups';
|
||||
import { FeesView } from '../../views/fees/Fees';
|
||||
|
||||
const ContentStyle = styled.div`
|
||||
/* display: flex;
|
||||
|
@ -27,6 +28,7 @@ export const Content = () => {
|
|||
<Route path="/backups" render={() => <BackupsView />} />
|
||||
<Route path="/resume" render={() => <ResumeList />} />
|
||||
<Route path="/settings" render={() => <SettingsView />} />
|
||||
<Route path="/fees" render={() => <FeesView />} />
|
||||
<Route path="*" render={() => <NotFound />} />
|
||||
</Switch>
|
||||
</ContentStyle>
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
Server,
|
||||
Settings,
|
||||
Shield,
|
||||
Crosshair,
|
||||
} from '../../components/generic/Icons';
|
||||
import { useSettings } from '../../context/SettingsContext';
|
||||
|
||||
|
@ -84,6 +85,7 @@ const CHANNEL_LINK = '/channels';
|
|||
const RESUME_LINK = '/resume';
|
||||
const BACKUPS_LINK = '/backups';
|
||||
const SETTINGS_LINK = '/settings';
|
||||
const FEES_LINK = '/fees';
|
||||
|
||||
export const Navigation = () => {
|
||||
const { theme } = useSettings();
|
||||
|
@ -113,6 +115,15 @@ export const Navigation = () => {
|
|||
<NavSeparation />
|
||||
Channels
|
||||
</NavButton>
|
||||
<NavButton
|
||||
selectedColor={navButtonColor[theme]}
|
||||
selected={pathname === FEES_LINK}
|
||||
to={FEES_LINK}
|
||||
>
|
||||
<Crosshair />
|
||||
<NavSeparation />
|
||||
Fees
|
||||
</NavButton>
|
||||
<NavButton
|
||||
selectedColor={navButtonColor[theme]}
|
||||
selected={pathname === RESUME_LINK}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { DownloadBackups } from './DownloadBackups';
|
||||
import { VerifyBackups } from './VerifyBackups';
|
||||
import { RecoverFunds } from './RecoverFunds';
|
||||
import { AdminSwitch } from '../../components/adminSwitch/AdminSwitch';
|
||||
|
||||
const backupColor = '#ffffff';
|
||||
|
||||
|
@ -37,7 +38,9 @@ export const BackupsView = () => {
|
|||
<Separation />
|
||||
<DownloadBackups color={backupColor} />
|
||||
<VerifyBackups color={backupColor} />
|
||||
<RecoverFunds color={backupColor} />
|
||||
<AdminSwitch>
|
||||
<RecoverFunds color={backupColor} />
|
||||
</AdminSwitch>
|
||||
</Card>
|
||||
</CardWithTitle>
|
||||
);
|
||||
|
|
|
@ -14,11 +14,7 @@ import {
|
|||
} from '../../components/generic/Styled';
|
||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
||||
import { XSvg, ChevronRight } from '../../components/generic/Icons';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const RightButton = styled(ColorButton)`
|
||||
margin: 0 0 0 auto;
|
||||
`;
|
||||
import { SecureButton } from '../../components/secureButton/SecureButton';
|
||||
|
||||
export const RecoverFunds = ({ color }: { color: string }) => {
|
||||
const [backupString, setBackupString] = useState<string>('');
|
||||
|
@ -43,12 +39,12 @@ export const RecoverFunds = ({ color }: { color: string }) => {
|
|||
<DarkSubTitle>Backup String: </DarkSubTitle>
|
||||
<Input onChange={e => setBackupString(e.target.value)} />
|
||||
</SingleLine>
|
||||
<RightButton
|
||||
disabled={backupString === ''}
|
||||
<SecureButton
|
||||
callback={recoverFunds}
|
||||
variables={{ backup: backupString }}
|
||||
color={color}
|
||||
onClick={() =>
|
||||
recoverFunds({ variables: { auth, backup: backupString } })
|
||||
}
|
||||
enabled={true}
|
||||
disabled={backupString === ''}
|
||||
>
|
||||
{loading ? (
|
||||
<ScaleLoader height={8} width={2} color={color} />
|
||||
|
@ -58,7 +54,7 @@ export const RecoverFunds = ({ color }: { color: string }) => {
|
|||
<ChevronRight />
|
||||
</>
|
||||
)}
|
||||
</RightButton>
|
||||
</SecureButton>
|
||||
</SubCard>
|
||||
);
|
||||
|
||||
|
|
83
src/views/fees/FeeCard.tsx
Normal file
83
src/views/fees/FeeCard.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
SubCard,
|
||||
Separation,
|
||||
SingleLine,
|
||||
DarkSubTitle,
|
||||
} from '../../components/generic/Styled';
|
||||
import { renderLine } from '../../components/generic/Helpers';
|
||||
import { MainInfo, NodeTitle, ColLine } from './Fees.style';
|
||||
|
||||
interface FeeCardProps {
|
||||
channelInfo: any;
|
||||
index: number;
|
||||
setIndexOpen: (index: number) => void;
|
||||
indexOpen: number;
|
||||
}
|
||||
|
||||
export const FeeCard = ({
|
||||
channelInfo,
|
||||
index,
|
||||
setIndexOpen,
|
||||
indexOpen,
|
||||
}: FeeCardProps) => {
|
||||
const {
|
||||
alias,
|
||||
color,
|
||||
baseFee,
|
||||
feeRate,
|
||||
transactionId,
|
||||
transactionVout,
|
||||
} = channelInfo;
|
||||
|
||||
const handleClick = () => {
|
||||
if (indexOpen === index) {
|
||||
setIndexOpen(0);
|
||||
} else {
|
||||
setIndexOpen(index);
|
||||
}
|
||||
};
|
||||
|
||||
const renderDetails = () => {
|
||||
return (
|
||||
<>
|
||||
<Separation />
|
||||
{renderLine('Transaction Id:', transactionId)}
|
||||
{renderLine('Transaction Vout:', transactionVout)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SubCard color={color} key={index}>
|
||||
<MainInfo onClick={() => handleClick()}>
|
||||
<SingleLine>
|
||||
<NodeTitle>{alias ? alias : 'Unknown'}</NodeTitle>
|
||||
<ColLine>
|
||||
<SingleLine>
|
||||
<DarkSubTitle>{`Base Fee:`}</DarkSubTitle>
|
||||
<SingleLine>
|
||||
{baseFee}
|
||||
<DarkSubTitle>
|
||||
{baseFee === 1 ? 'sat' : 'sats'}
|
||||
</DarkSubTitle>
|
||||
</SingleLine>
|
||||
</SingleLine>
|
||||
<SingleLine>
|
||||
<DarkSubTitle>{`Fee Rate:`}</DarkSubTitle>
|
||||
<SingleLine>
|
||||
{feeRate}
|
||||
<DarkSubTitle>
|
||||
{feeRate === 1
|
||||
? 'sat/million'
|
||||
: 'sats/million'}
|
||||
</DarkSubTitle>
|
||||
</SingleLine>
|
||||
</SingleLine>
|
||||
</ColLine>
|
||||
</SingleLine>
|
||||
</MainInfo>
|
||||
{index === indexOpen && renderDetails()}
|
||||
</SubCard>
|
||||
);
|
||||
};
|
50
src/views/fees/Fees.style.tsx
Normal file
50
src/views/fees/Fees.style.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
import styled from 'styled-components';
|
||||
|
||||
export const ColLine = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* justify-content: space-between; */
|
||||
/* flex-grow: 2; */
|
||||
flex-basis: 30%;
|
||||
`;
|
||||
|
||||
export const NodeTitle = styled.div`
|
||||
/* padding: 2px; */
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
export const StatusLine = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
right: -8px;
|
||||
top: -8px;
|
||||
display: flex;
|
||||
/* flex-direction: column; */
|
||||
justify-content: flex-end;
|
||||
/* align-items: flex-start; */
|
||||
/* z-index: 2; */
|
||||
margin: 0 0 -8px 0;
|
||||
/* height: 36px; */
|
||||
/* margin-left: 5px; */
|
||||
/* margin: -8px -7px 0 0; */
|
||||
`;
|
||||
|
||||
export const StatusDot = styled.div`
|
||||
margin: 0 2px;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 100%;
|
||||
background-color: ${({ color }: { color: string }) => color};
|
||||
`;
|
||||
|
||||
export const DetailLine = styled.div`
|
||||
font-size: 14px;
|
||||
word-wrap: break-word;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
export const MainInfo = styled.div`
|
||||
cursor: pointer;
|
||||
`;
|
43
src/views/fees/Fees.tsx
Normal file
43
src/views/fees/Fees.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useQuery } from '@apollo/react-hooks';
|
||||
import { CHANNEL_FEES } from '../../graphql/query';
|
||||
import { Card, CardWithTitle, SubTitle } from '../../components/generic/Styled';
|
||||
import { useAccount } from '../../context/AccountContext';
|
||||
import { getAuthString } from '../../utils/auth';
|
||||
import { toast } from 'react-toastify';
|
||||
import { getErrorContent } from '../../utils/error';
|
||||
import { LoadingCard } from '../../components/loading/LoadingCard';
|
||||
import { FeeCard } from './FeeCard';
|
||||
|
||||
export const FeesView = () => {
|
||||
const [indexOpen, setIndexOpen] = useState(0);
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
|
||||
const { loading, data } = useQuery(CHANNEL_FEES, {
|
||||
variables: { auth },
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
});
|
||||
|
||||
if (loading || !data || !data.getChannelFees) {
|
||||
return <LoadingCard title={'Fees'} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<CardWithTitle>
|
||||
<SubTitle>Fees</SubTitle>
|
||||
<Card>
|
||||
{data.getChannelFees.map((channel: any, index: number) => (
|
||||
<FeeCard
|
||||
channelInfo={channel}
|
||||
index={index + 1}
|
||||
setIndexOpen={setIndexOpen}
|
||||
indexOpen={indexOpen}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
</CardWithTitle>
|
||||
);
|
||||
};
|
Loading…
Add table
Reference in a new issue