mirror of
https://github.com/apotdevin/thunderhub.git
synced 2025-02-22 22:25:21 +01:00
feat: onChain send and receive
This commit is contained in:
parent
9705073959
commit
acc3d9ef8d
5 changed files with 354 additions and 11 deletions
|
@ -34,7 +34,7 @@ export const CreateInvoiceCard = ({ color }: { color: string }) => {
|
||||||
<Input
|
<Input
|
||||||
color={color}
|
color={color}
|
||||||
type={'number'}
|
type={'number'}
|
||||||
onChange={e => setAmount(10000)}
|
onChange={e => setAmount(parseInt(e.target.value))}
|
||||||
/>
|
/>
|
||||||
<ColorButton
|
<ColorButton
|
||||||
color={color}
|
color={color}
|
||||||
|
|
104
src/components/quickActions/receiveOnChain/ReceiveOnChain.tsx
Normal file
104
src/components/quickActions/receiveOnChain/ReceiveOnChain.tsx
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
ColorButton,
|
||||||
|
NoWrapTitle,
|
||||||
|
DarkSubTitle,
|
||||||
|
Separation,
|
||||||
|
} from '../../generic/Styled';
|
||||||
|
import { useMutation } from '@apollo/react-hooks';
|
||||||
|
import { CREATE_ADDRESS } from '../../../graphql/mutation';
|
||||||
|
import { Edit, Circle } from '../../generic/Icons';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { getErrorContent } from '../../../utils/error';
|
||||||
|
import { AccountContext } from '../../../context/AccountContext';
|
||||||
|
import { getAuthString } from '../../../utils/auth';
|
||||||
|
|
||||||
|
const SingleLine = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const RadioText = styled.div`
|
||||||
|
margin-left: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ButtonRow = styled.div`
|
||||||
|
width: auto;
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TitleWithSpacing = styled(NoWrapTitle)`
|
||||||
|
margin-right: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ReceiveOnChainCard = ({ color }: { color: string }) => {
|
||||||
|
const [nested, setNested] = useState(false);
|
||||||
|
const [received, setReceived] = useState(false);
|
||||||
|
|
||||||
|
const { host, read, cert } = useContext(AccountContext);
|
||||||
|
const auth = getAuthString(host, read, cert);
|
||||||
|
|
||||||
|
const [createAddress, { data }] = useMutation(CREATE_ADDRESS, {
|
||||||
|
onError: error => toast.error(getErrorContent(error)),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
data && data.createAddress && setReceived(true);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<SingleLine>
|
||||||
|
<ButtonRow>
|
||||||
|
<TitleWithSpacing>Type of Address:</TitleWithSpacing>
|
||||||
|
<ColorButton
|
||||||
|
color={color}
|
||||||
|
onClick={() => {
|
||||||
|
setNested(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Circle
|
||||||
|
size={'10px'}
|
||||||
|
fillcolor={nested ? '' : 'white'}
|
||||||
|
/>
|
||||||
|
<RadioText>P2WPKH</RadioText>
|
||||||
|
</ColorButton>
|
||||||
|
<ColorButton
|
||||||
|
color={color}
|
||||||
|
onClick={() => {
|
||||||
|
setNested(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Circle
|
||||||
|
size={'10px'}
|
||||||
|
fillcolor={nested ? 'white' : ''}
|
||||||
|
/>
|
||||||
|
<RadioText>NP2WPKH</RadioText>
|
||||||
|
</ColorButton>
|
||||||
|
</ButtonRow>
|
||||||
|
<ColorButton
|
||||||
|
color={color}
|
||||||
|
disabled={received}
|
||||||
|
onClick={() => {
|
||||||
|
createAddress({ variables: { auth, nested } });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Edit />
|
||||||
|
Create Address
|
||||||
|
</ColorButton>
|
||||||
|
</SingleLine>
|
||||||
|
{data && data.createAddress && (
|
||||||
|
<>
|
||||||
|
<Separation />
|
||||||
|
<SingleLine>
|
||||||
|
<DarkSubTitle bottom={'0px'}>New Address:</DarkSubTitle>
|
||||||
|
{data.createAddress}
|
||||||
|
</SingleLine>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
226
src/components/quickActions/sendOnChain/SendOnChain.tsx
Normal file
226
src/components/quickActions/sendOnChain/SendOnChain.tsx
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Input,
|
||||||
|
ColorButton,
|
||||||
|
NoWrapTitle,
|
||||||
|
DarkSubTitle,
|
||||||
|
} from '../../generic/Styled';
|
||||||
|
import { useMutation } from '@apollo/react-hooks';
|
||||||
|
import { PAY_ADDRESS } from '../../../graphql/mutation';
|
||||||
|
import { Send, Circle } from '../../generic/Icons';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { getErrorContent } from '../../../utils/error';
|
||||||
|
import { AccountContext } from '../../../context/AccountContext';
|
||||||
|
import { getAuthString } from '../../../utils/auth';
|
||||||
|
import { SettingsContext } from '../../../context/SettingsContext';
|
||||||
|
import { getValue } from '../../../helpers/Helpers';
|
||||||
|
|
||||||
|
const SingleLine = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const RadioText = styled.div`
|
||||||
|
margin-left: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ButtonRow = styled.div`
|
||||||
|
width: auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
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);
|
||||||
|
const [type, setType] = useState('none');
|
||||||
|
const [amount, setAmount] = useState(0);
|
||||||
|
const [sendAll, setSendAll] = useState(false);
|
||||||
|
const [isSent, setIsSent] = useState(false);
|
||||||
|
|
||||||
|
const { price, symbol, currency } = useContext(SettingsContext);
|
||||||
|
|
||||||
|
const { host, read, cert } = useContext(AccountContext);
|
||||||
|
const auth = getAuthString(host, read, cert);
|
||||||
|
|
||||||
|
const [payAddress, { data }] = useMutation(PAY_ADDRESS, {
|
||||||
|
onError: error => toast.error(getErrorContent(error)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const priceProps = { price, symbol, currency };
|
||||||
|
const getFormat = (amount: number) =>
|
||||||
|
getValue({
|
||||||
|
amount,
|
||||||
|
...priceProps,
|
||||||
|
});
|
||||||
|
|
||||||
|
const feeFormat = (amount: number) => {
|
||||||
|
if (type === 'fee') {
|
||||||
|
return getFormat(amount);
|
||||||
|
} else {
|
||||||
|
return `${amount} blocks`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeAmount =
|
||||||
|
type === 'fee'
|
||||||
|
? { fee: amount }
|
||||||
|
: type === 'target'
|
||||||
|
? { target: amount }
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const tokenAmount = sendAll ? { sendAll } : { tokens };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data && data.sendToAddress && data.sendToAddress.id) {
|
||||||
|
setIsSent(true);
|
||||||
|
toast.success('On Chain Payment Sent!');
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
if (isSent && data && data.sendToAddress && data.sendToAddress.id) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<SingleLine>
|
||||||
|
<DarkSubTitle>Payment Id:</DarkSubTitle>
|
||||||
|
{data.sendToAddress.id}
|
||||||
|
</SingleLine>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<SingleLine>
|
||||||
|
<NoWrapTitle>Send to Address:</NoWrapTitle>
|
||||||
|
<Input
|
||||||
|
color={color}
|
||||||
|
onChange={e => setAddress(e.target.value)}
|
||||||
|
/>
|
||||||
|
</SingleLine>
|
||||||
|
<SingleLine>
|
||||||
|
<NoWrapTitle>Send All:</NoWrapTitle>
|
||||||
|
<ButtonRow>
|
||||||
|
<ColorButton
|
||||||
|
color={color}
|
||||||
|
onClick={() => {
|
||||||
|
setSendAll(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Circle
|
||||||
|
size={'10px'}
|
||||||
|
fillcolor={sendAll ? 'white' : ''}
|
||||||
|
/>
|
||||||
|
<RadioText>Yes</RadioText>
|
||||||
|
</ColorButton>
|
||||||
|
<ColorButton
|
||||||
|
color={color}
|
||||||
|
onClick={() => {
|
||||||
|
setSendAll(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Circle
|
||||||
|
size={'10px'}
|
||||||
|
fillcolor={!sendAll ? 'white' : ''}
|
||||||
|
/>
|
||||||
|
<RadioText>No</RadioText>
|
||||||
|
</ColorButton>
|
||||||
|
</ButtonRow>
|
||||||
|
</SingleLine>
|
||||||
|
{!sendAll && (
|
||||||
|
<SingleLine>
|
||||||
|
<NoWrapTitle>Amount:</NoWrapTitle>
|
||||||
|
<ButtonRow>
|
||||||
|
<DarkSubTitle>{`(${getFormat(tokens)})`}</DarkSubTitle>
|
||||||
|
<SmallInput
|
||||||
|
color={color}
|
||||||
|
type={'number'}
|
||||||
|
onChange={e => setTokens(parseInt(e.target.value))}
|
||||||
|
/>
|
||||||
|
</ButtonRow>
|
||||||
|
</SingleLine>
|
||||||
|
)}
|
||||||
|
<SingleLine>
|
||||||
|
<NoWrapTitle>Fee:</NoWrapTitle>
|
||||||
|
<ButtonRow>
|
||||||
|
<ColorButton
|
||||||
|
color={color}
|
||||||
|
onClick={() => {
|
||||||
|
setType('none');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Circle
|
||||||
|
size={'10px'}
|
||||||
|
fillcolor={type === 'none' ? 'white' : ''}
|
||||||
|
/>
|
||||||
|
<RadioText>Auto</RadioText>
|
||||||
|
</ColorButton>
|
||||||
|
<ColorButton
|
||||||
|
color={color}
|
||||||
|
onClick={() => {
|
||||||
|
setType('fee');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Circle
|
||||||
|
size={'10px'}
|
||||||
|
fillcolor={type === 'fee' ? 'white' : ''}
|
||||||
|
/>
|
||||||
|
<RadioText>{`Fee (Sats/Byte)`}</RadioText>
|
||||||
|
</ColorButton>
|
||||||
|
<ColorButton
|
||||||
|
color={color}
|
||||||
|
onClick={() => {
|
||||||
|
setType('target');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Circle
|
||||||
|
size={'10px'}
|
||||||
|
fillcolor={type === 'target' ? 'white' : ''}
|
||||||
|
/>
|
||||||
|
<RadioText>{`Target Confirmations`}</RadioText>
|
||||||
|
</ColorButton>
|
||||||
|
</ButtonRow>
|
||||||
|
{type !== 'none' && (
|
||||||
|
<ButtonRow>
|
||||||
|
<DarkSubTitle bottom={'0px'}>
|
||||||
|
{`(${feeFormat(amount)})`}
|
||||||
|
</DarkSubTitle>
|
||||||
|
<SmallInput
|
||||||
|
color={color}
|
||||||
|
type={'number'}
|
||||||
|
onChange={e => setAmount(parseInt(e.target.value))}
|
||||||
|
/>
|
||||||
|
</ButtonRow>
|
||||||
|
)}
|
||||||
|
</SingleLine>
|
||||||
|
<RightButton
|
||||||
|
color={color}
|
||||||
|
onClick={() => {
|
||||||
|
payAddress({
|
||||||
|
variables: {
|
||||||
|
auth,
|
||||||
|
address,
|
||||||
|
...typeAmount,
|
||||||
|
...tokenAmount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Send />
|
||||||
|
Send To Address
|
||||||
|
</RightButton>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
|
@ -47,9 +47,9 @@ export const PAY_ADDRESS = gql`
|
||||||
mutation PayAddress(
|
mutation PayAddress(
|
||||||
$auth: String!
|
$auth: String!
|
||||||
$address: String!
|
$address: String!
|
||||||
$tokens: Number!
|
$tokens: Int
|
||||||
$fee: Number
|
$fee: Int
|
||||||
$target: Number
|
$target: Int
|
||||||
$sendAll: Boolean
|
$sendAll: Boolean
|
||||||
) {
|
) {
|
||||||
sendToAddress(
|
sendToAddress(
|
||||||
|
@ -59,7 +59,13 @@ export const PAY_ADDRESS = gql`
|
||||||
fee: $fee
|
fee: $fee
|
||||||
target: $target
|
target: $target
|
||||||
sendAll: $sendAll
|
sendAll: $sendAll
|
||||||
)
|
) {
|
||||||
|
confirmationCount
|
||||||
|
id
|
||||||
|
isConfirmed
|
||||||
|
isOutgoing
|
||||||
|
tokens
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,19 @@ export const getErrorContent = (error: ApolloError): ReactNode => {
|
||||||
const errors = error.graphQLErrors.map(x => x.message);
|
const errors = error.graphQLErrors.map(x => x.message);
|
||||||
|
|
||||||
const renderMessage = errors.map((error, i) => {
|
const renderMessage = errors.map((error, i) => {
|
||||||
const errorMsg = JSON.parse(error);
|
if (error === 'rateLimitReached') {
|
||||||
return (
|
return 'Rate Limit Reached.';
|
||||||
<div
|
}
|
||||||
key={i}
|
try {
|
||||||
>{`${errorMsg.details} [${errorMsg.msg}/${errorMsg.code}]`}</div>
|
const errorMsg = JSON.parse(error);
|
||||||
);
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
>{`${errorMsg.details} [${errorMsg.msg}/${errorMsg.code}]`}</div>
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('JSON parsing error:', e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return <div>{renderMessage}</div>;
|
return <div>{renderMessage}</div>;
|
||||||
|
|
Loading…
Add table
Reference in a new issue