Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:secretkeylabs/xverse-web-extensi…
Browse files Browse the repository at this point in the history
…on into denys/eng-3474-make-ui-for-stx-rbf-in-the-extension
  • Loading branch information
dhriaznov committed Jan 11, 2024
2 parents 9eec4fa + 7898e48 commit 47c18d9
Show file tree
Hide file tree
Showing 63 changed files with 3,121 additions and 1,338 deletions.
443 changes: 306 additions & 137 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"@ledgerhq/hw-transport-webusb": "^6.27.13",
"@phosphor-icons/react": "^2.0.10",
"@react-spring/web": "^9.6.1",
"@secretkeylabs/xverse-core": "8.0.0",
"@scure/btc-signer": "^1.1.1",
"@secretkeylabs/xverse-core": "8.0.1",
"@stacks/connect": "7.4.1",
"@stacks/stacks-blockchain-api-types": "6.1.1",
"@stacks/transactions": "6.9.0",
Expand All @@ -26,6 +27,7 @@
"axios": "^1.1.3",
"bignumber.js": "^9.1.0",
"bip39": "^3.0.3",
"buffer": "6.0.3",
"c32check": "^2.0.0",
"classnames": "^2.3.2",
"crypto-browserify": "^3.12.0",
Expand Down Expand Up @@ -76,8 +78,7 @@
"style": "prettier --write \"src/**/*.{ts,tsx}\"",
"prepare": "husky install"
},
"resolutions": {
"styled-components": "^5",
"overrides": {
"buffer": "6.0.3"
},
"lint-staged": {
Expand Down
229 changes: 229 additions & 0 deletions src/app/components/confirmBtcTransaction/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import ledgerConnectDefaultIcon from '@assets/img/ledger/ledger_connect_default.svg';
import ledgerConnectBtcIcon from '@assets/img/ledger/ledger_import_connect_btc.svg';
import { delay } from '@common/utils/ledger';
import BottomModal from '@components/bottomModal';
import ActionButton from '@components/button';
import LedgerConnectionView from '@components/ledger/connectLedgerView';
import useWalletSelector from '@hooks/useWalletSelector';
import TransportFactory from '@ledgerhq/hw-transport-webusb';
import { btcTransaction, Transport } from '@secretkeylabs/xverse-core';
import Callout from '@ui-library/callout';
import { StickyHorizontalSplitButtonContainer, StyledP } from '@ui-library/common.styled';
import { isLedgerAccount } from '@utils/helper';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { MoonLoader } from 'react-spinners';
import styled from 'styled-components';
import SendLayout from '../../layouts/sendLayout';
import TransactionSummary from './transactionSummary';

const LoaderContainer = styled.div(() => ({
display: 'flex',
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}));

const ReviewTransactionText = styled(StyledP)`
text-align: left;
margin-bottom: ${(props) => props.theme.space.l};
`;

const BroadcastCallout = styled(Callout)`
margin-bottom: ${(props) => props.theme.space.m};
`;

const SuccessActionsContainer = styled.div((props) => ({
width: '100%',
display: 'flex',
flexDirection: 'column',
gap: props.theme.space.s,
paddingLeft: props.theme.space.m,
paddingRight: props.theme.space.m,
marginBottom: props.theme.space.xxl,
marginTop: props.theme.space.xxl,
}));

type Props = {
inputs: btcTransaction.EnhancedInput[];
outputs: btcTransaction.EnhancedOutput[];
feeOutput?: btcTransaction.TransactionFeeOutput;
isLoading: boolean;
isSubmitting: boolean;
isBroadcast?: boolean;
isError?: boolean;
showAccountHeader?: boolean;
hideBottomBar?: boolean;
cancelText: string;
confirmText: string;
onConfirm: (ledgerTransport?: Transport) => void;
onCancel: () => void;
onBackClick?: () => void;
confirmDisabled?: boolean;
getFeeForFeeRate?: (feeRate: number, useEffectiveFeeRate?: boolean) => Promise<number>;
onFeeRateSet?: (feeRate: number) => void;
};

function ConfirmBtcTransaction({
inputs,
outputs,
feeOutput,
isLoading,
isSubmitting,
isBroadcast,
isError = false,
cancelText,
confirmText,
onConfirm,
onCancel,
onBackClick,
showAccountHeader,
hideBottomBar,
confirmDisabled = false,
getFeeForFeeRate,
onFeeRateSet,
}: Props) {
const [isModalVisible, setIsModalVisible] = useState(false);
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const [isButtonDisabled, setIsButtonDisabled] = useState(false);
const [isConnectSuccess, setIsConnectSuccess] = useState(false);
const [isConnectFailed, setIsConnectFailed] = useState(false);
const [isTxRejected, setIsTxRejected] = useState(false);

const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' });
const { t: signatureRequestTranslate } = useTranslation('translation', {
keyPrefix: 'SIGNATURE_REQUEST',
});
const { selectedAccount } = useWalletSelector();

const hideBackButton = !onBackClick;

const onConfirmPress = async () => {
if (!isLedgerAccount(selectedAccount)) {
return onConfirm();
}

// show ledger connection screens
setIsModalVisible(true);
};

const handleConnectAndConfirm = async () => {
if (!selectedAccount) {
console.error('No account selected');

Check warning on line 112 in src/app/components/confirmBtcTransaction/index.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
return;
}
setIsButtonDisabled(true);

const transport = await TransportFactory.create();

if (!transport) {
setIsConnectSuccess(false);
setIsConnectFailed(true);
setIsButtonDisabled(false);
return;
}

setIsConnectSuccess(true);
await delay(1500);
setCurrentStepIndex(1);

try {
onConfirm(transport);
} catch (err) {
console.error(err);

Check warning on line 133 in src/app/components/confirmBtcTransaction/index.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
setIsTxRejected(true);
}
};

const handleRetry = async () => {
setIsTxRejected(false);
setIsConnectSuccess(false);
setCurrentStepIndex(0);
};

// TODO: this is a bit naive, but should be correct. We may want to look at the sig hash types of the inputs instead
const isPartialTransaction = !feeOutput;

return isLoading ? (
<LoaderContainer>
<MoonLoader color="white" size={50} />
</LoaderContainer>
) : (
<>
<SendLayout
selectedBottomTab="dashboard"
onClickBack={onBackClick}
hideBackButton={hideBackButton}
showAccountHeader={showAccountHeader}
hideBottomBar={hideBottomBar}
>
<ReviewTransactionText typography="headline_s">
{t('REVIEW_TRANSACTION')}
</ReviewTransactionText>
{!isBroadcast && <BroadcastCallout bodyText={t('PSBT_NO_BROADCAST_DISCLAIMER')} />}
<TransactionSummary
inputs={inputs}
outputs={outputs}
feeOutput={feeOutput}
isPartialTransaction={isPartialTransaction}
getFeeForFeeRate={getFeeForFeeRate}
onFeeRateSet={onFeeRateSet}
isSubmitting={isSubmitting}
/>
{!isLoading && (
<StickyHorizontalSplitButtonContainer>
<ActionButton onPress={onCancel} text={cancelText} transparent />
<ActionButton
onPress={onConfirmPress}
disabled={confirmDisabled}
processing={isSubmitting}
text={confirmText}
warning={isError}
/>
</StickyHorizontalSplitButtonContainer>
)}
</SendLayout>
<BottomModal header="" visible={isModalVisible} onClose={() => setIsModalVisible(false)}>
{currentStepIndex === 0 && (
<LedgerConnectionView
title={signatureRequestTranslate('LEDGER.CONNECT.TITLE')}
text={signatureRequestTranslate('LEDGER.CONNECT.SUBTITLE', { name: 'Bitcoin' })}
titleFailed={signatureRequestTranslate('LEDGER.CONNECT.ERROR_TITLE')}
textFailed={signatureRequestTranslate('LEDGER.CONNECT.ERROR_SUBTITLE')}
imageDefault={ledgerConnectBtcIcon}
isConnectSuccess={isConnectSuccess}
isConnectFailed={isConnectFailed}
/>
)}
{currentStepIndex === 1 && (
<LedgerConnectionView
title={signatureRequestTranslate('LEDGER.CONFIRM.TITLE')}
text={signatureRequestTranslate('LEDGER.CONFIRM.SUBTITLE')}
titleFailed={signatureRequestTranslate('LEDGER.CONFIRM.ERROR_TITLE')}
textFailed={signatureRequestTranslate('LEDGER.CONFIRM.ERROR_SUBTITLE')}
imageDefault={ledgerConnectDefaultIcon}
isConnectSuccess={false}
isConnectFailed={isTxRejected}
/>
)}
<SuccessActionsContainer>
<ActionButton
onPress={isTxRejected || isConnectFailed ? handleRetry : handleConnectAndConfirm}
text={signatureRequestTranslate(
isTxRejected || isConnectFailed ? 'LEDGER.RETRY_BUTTON' : 'LEDGER.CONNECT_BUTTON',
)}
disabled={isButtonDisabled}
processing={isButtonDisabled}
/>
<ActionButton
onPress={onCancel}
text={signatureRequestTranslate('LEDGER.CANCEL_BUTTON')}
transparent
/>
</SuccessActionsContainer>
</BottomModal>
</>
);
}

export default ConfirmBtcTransaction;
86 changes: 86 additions & 0 deletions src/app/components/confirmBtcTransaction/itemRow/amount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import TokenImage from '@components/tokenImage';
import useWalletSelector from '@hooks/useWalletSelector';
import { currencySymbolMap, getBtcFiatEquivalent, satsToBtc } from '@secretkeylabs/xverse-core';
import Avatar from '@ui-library/avatar';
import { StyledP } from '@ui-library/common.styled';
import BigNumber from 'bignumber.js';
import { useTranslation } from 'react-i18next';
import { NumericFormat } from 'react-number-format';
import styled from 'styled-components';

type Props = {
amount: number;
};

const RowCenter = styled.div<{ spaceBetween?: boolean }>((props) => ({
display: 'flex',
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: props.spaceBetween ? 'space-between' : 'initial',
}));

const NumberTypeContainer = styled.div`
text-align: right;
`;

const AvatarContainer = styled.div`
margin-right: ${(props) => props.theme.space.xs};
`;

export default function Amount({ amount }: Props) {
const { btcFiatRate, fiatCurrency } = useWalletSelector();
const { t } = useTranslation('translation');

const getFiatAmountString = (amountParam: number, btcFiatRateParam: string) => {
const fiatAmount = getBtcFiatEquivalent(
new BigNumber(amountParam),
BigNumber(btcFiatRateParam),
);
if (!fiatAmount) {
return '';
}

if (fiatAmount.isLessThan(0.01)) {
return `<${currencySymbolMap[fiatCurrency]}0.01 ${fiatCurrency}`;
}

return (
<NumericFormat
value={fiatAmount.toFixed(2).toString()}
displayType="text"
thousandSeparator
prefix={`${currencySymbolMap[fiatCurrency]} `}
suffix={` ${fiatCurrency}`}
renderText={(value: string) => `~ ${value}`}
/>
);
};

return (
<RowCenter>
<AvatarContainer>
<Avatar src={<TokenImage token="BTC" loading={false} size={32} />} />
</AvatarContainer>
<RowCenter spaceBetween>
<div>
<StyledP typography="body_medium_m" color="white_200">
{t('CONFIRM_TRANSACTION.AMOUNT')}
</StyledP>
</div>
<NumberTypeContainer>
<NumericFormat
value={satsToBtc(new BigNumber(amount)).toFixed()}
displayType="text"
thousandSeparator
suffix=" BTC"
renderText={(value: string) => <StyledP typography="body_medium_m">{value}</StyledP>}
/>
<StyledP typography="body_medium_s" color="white_400">
{getFiatAmountString(amount, btcFiatRate)}
</StyledP>
</NumberTypeContainer>
</RowCenter>
</RowCenter>
);
}
Loading

0 comments on commit 47c18d9

Please sign in to comment.