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
|
||||
color={color}
|
||||
type={'number'}
|
||||
onChange={e => setAmount(10000)}
|
||||
onChange={e => setAmount(parseInt(e.target.value))}
|
||||
/>
|
||||
<ColorButton
|
||||
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(
|
||||
$auth: String!
|
||||
$address: String!
|
||||
$tokens: Number!
|
||||
$fee: Number
|
||||
$target: Number
|
||||
$tokens: Int
|
||||
$fee: Int
|
||||
$target: Int
|
||||
$sendAll: Boolean
|
||||
) {
|
||||
sendToAddress(
|
||||
|
@ -59,7 +59,13 @@ export const PAY_ADDRESS = gql`
|
|||
fee: $fee
|
||||
target: $target
|
||||
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 renderMessage = errors.map((error, i) => {
|
||||
if (error === 'rateLimitReached') {
|
||||
return 'Rate Limit Reached.';
|
||||
}
|
||||
try {
|
||||
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>;
|
||||
|
|
Loading…
Add table
Reference in a new issue