Skip to content

Commit

Permalink
✨(lld): rework coin control UI
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasWerey committed Oct 8, 2024
1 parent b0d535b commit c9f388c
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { useCallback } from "react";
import { Flex, Text } from "@ledgerhq/react-ui";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { setHasProtectedOrdinalsAssets } from "~/renderer/actions/settings";
import Switch from "~/renderer/components/Switch";
import { hasProtectedOrdinalsAssetsSelector } from "~/renderer/reducers/settings";

export const ExcludeAssetsSection: React.FC = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const hasProtectedOrdinals = useSelector(hasProtectedOrdinalsAssetsSelector);

const onSwitchChange = useCallback(() => {
dispatch(setHasProtectedOrdinalsAssets(!hasProtectedOrdinals));
}, [dispatch, hasProtectedOrdinals]);

return (
<Flex alignItems="center">
<Text variant="bodyLineHeight" color="neutral.c70" flex={1} fontSize={14}>
{t("ordinals.inscriptions.discoveryDrawer.protectDescription")}
</Text>
<Flex>
<Switch isChecked={hasProtectedOrdinals} onChange={onSwitchChange} />
</Flex>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import { Flex, Icons, Button, Link } from "@ledgerhq/react-ui";

type Props = {
onClickLink: () => void;
onClose: () => void;
};

export const Footer: React.FC<Props> = ({ onClickLink, onClose }) => (
<Flex justifyContent="space-between" alignItems="center" flex={1} p={10}>
<Flex alignItems="center" columnGap={1}>
<Link
textProps={{
fontSize: 4,
color: "palette.text.shade100",
}}
onClick={onClickLink}
>
{"Learn more about this setting"}
</Link>
<Icons.ExternalLink size="S" />
</Flex>
<Button borderRadius={4} variant="color" onClick={onClose}>
{"CONFIRM"}
</Button>
</Flex>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";
import {
BitcoinAccount,
BitcoinOutput,
Transaction,
TransactionStatus,
UtxoStrategy,
} from "@ledgerhq/live-common/families/bitcoin/types";
import { AccountBridge } from "@ledgerhq/types-live";
import { Unit } from "@ledgerhq/types-cryptoassets";
import { getUTXOStatus } from "@ledgerhq/live-common/families/bitcoin/logic";
import { Text } from "@ledgerhq/react-ui";

type Props = {
utxo: BitcoinOutput;
utxoStrategy: UtxoStrategy;
status: TransactionStatus;
account: BitcoinAccount;
totalExcludedUTXOS: number;
bridge: AccountBridge<Transaction>;
unit: Unit;
updateTransaction: (updater: (t: Transaction) => Transaction) => void;
};

export const Row: React.FC<Props> = ({
utxo,
utxoStrategy,
status,
account,
totalExcludedUTXOS,
bridge,
unit,
updateTransaction,
}) => {
const { excluded, reason } = getUTXOStatus(utxo, utxoStrategy);
const utxoStatus = excluded ? reason || "" : "";

const input = (status.txInputs || []).find(
input => input.previousOutputIndex === utxo.outputIndex && input.previousTxHash === utxo.hash,
);
const unconfirmed = utxoStatus === "pickPendingUtxo";
const last = !excluded && totalExcludedUTXOS + 1 === account.bitcoinResources?.utxos.length;
const disabled = unconfirmed || last;

const handleClick = () => {
if (disabled) return;
const updatedStrategy = {
...utxoStrategy,
excludeUTXOs: excluded
? utxoStrategy.excludeUTXOs.filter(
e => e.hash !== utxo.hash || e.outputIndex !== utxo.outputIndex,
)
: [...utxoStrategy.excludeUTXOs, { hash: utxo.hash, outputIndex: utxo.outputIndex }],
};

updateTransaction(t => bridge.updateTransaction(t, { utxoStrategy: updatedStrategy }));
};

return <Text>{"eaz"}</Text>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Text, Flex } from "@ledgerhq/react-ui";
import React from "react";

export const UtxoPickerHeaderSection: React.FC = () => {
return (
<Flex flexDirection="column">
<Text variant="body" fontSize={14} ff="Inter|SemiBold">
UTXOs
</Text>
<Text color="neutral.c70" fontSize={14} variant="bodyLineHeight">
Select the UTXO you want to use when you do transactions. Unchecked UTXO wont be used.
</Text>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { bitcoinPickingStrategy, Transaction } from "@ledgerhq/live-common/families/bitcoin/types";
import { Account, AccountBridge, AccountRaw, TransactionStatusCommon } from "@ledgerhq/types-live";
import useBitcoinPickingStrategy, {
Option,
} from "~/renderer/families/bitcoin/useBitcoinPickingStrategy";
import { SelectInput, Text, Flex } from "@ledgerhq/react-ui";

type Props = {
transaction: Transaction;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
bridge: AccountBridge<any, Account, TransactionStatusCommon, AccountRaw>;
onChange: (updateFn: (t: Transaction, p: Partial<Transaction>) => void) => void;
};

export const UtxoStrategyPicker: React.FC<Props> = ({ transaction, bridge, onChange }) => {
const { t } = useTranslation();
const { item, options } = useBitcoinPickingStrategy(transaction.utxoStrategy.strategy);

const handleChange = (selectedItem: Option) => {
onChange(
bridge.updateTransaction(transaction, {
utxoStrategy: {
...transaction.utxoStrategy,
strategy: selectedItem
? bitcoinPickingStrategy[selectedItem.value as keyof typeof bitcoinPickingStrategy]
: 0,
},
}),
);
};

return (
<Flex flexDirection="column" rowGap={4}>
<Text variant="h2Inter" flex={1} fontSize={14}>
{t("bitcoin.strategy")}
</Text>
<SelectInput
isSearchable={false}
placeholder={t("bitcoin.pickingStrategy")}
options={options}
value={item}
onChange={item => handleChange(item as Option)}
/>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { UtxoPickerHeaderSection } from "./UtxoPickerHeaderSection";
import { UtxoStrategyPicker } from "./UtxoStrategyPicker";
import { Row } from "./Row";
import { Footer } from "./Footer";
import { ExcludeAssetsSection } from "./ExcludeAssetsSection";

export { UtxoPickerHeaderSection, UtxoStrategyPicker, Row, Footer, ExcludeAssetsSection };
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from "react";
import { useCoinControlModal } from "./useCoinControlModal";
import Modal, { ModalBody } from "~/renderer/components/Modal";
import {
BitcoinAccount,
Transaction,
TransactionStatus,
} from "@ledgerhq/live-common/families/bitcoin/types";
import TrackPage from "~/renderer/analytics/TrackPage";
import { Trans } from "react-i18next";
import {
UtxoStrategyPicker,
UtxoPickerHeaderSection,
Row,
ExcludeAssetsSection,
Footer,
} from "./components";
import { Box, Divider, Flex } from "@ledgerhq/react-ui";
import { useTheme } from "styled-components";

type ViewProps = ReturnType<typeof useCoinControlModal>;

const View: React.FC<ViewProps> = ({
isOpened,
account,
unit,
bitcoinResources,
totalExcludedUTXOS,
bridge,
error,
returning,
transaction,
utxoStrategy,
status,
onClose,
onChange,
updateTransaction,
onClickLink,
}) => {
const { colors } = useTheme();

return (
<Modal
width={500}
isOpened={isOpened}
centered
onClose={onClose}
bodyStyle={{
backgroundColor: colors.background.main,
}}
>
<TrackPage category="Modal" name="BitcoinCoinControl" />
<ModalBody
title={<Trans i18nKey="bitcoin.modalTitle" />}
onClose={onClose}
render={() => (
<Box>
<UtxoStrategyPicker transaction={transaction} bridge={bridge} onChange={onChange} />
<Divider my={24} />
<Flex flexDirection="column" rowGap={24}>
<UtxoPickerHeaderSection />
<Flex flexDirection="column" rowGap={2}>
{bitcoinResources.utxos.map(utxo => (
<Row
key={utxo.hash}
utxo={utxo}
utxoStrategy={utxoStrategy}
totalExcludedUTXOS={totalExcludedUTXOS}
updateTransaction={updateTransaction}
bridge={bridge}
status={status}
account={account}
unit={unit}
/>
))}
</Flex>
</Flex>
<Divider my={24} />
<ExcludeAssetsSection />
</Box>
)}
renderFooter={() => <Footer onClickLink={onClickLink} onClose={onClose} />}
/>
</Modal>
);
};

type Props = {
isOpened?: boolean;
account: BitcoinAccount;
transaction: Transaction;
status: TransactionStatus;
onClose: () => void;
onChange: (a: (t: Transaction, p: Partial<Transaction>) => void) => void;
updateTransaction: (updater: (t: Transaction) => Transaction) => void;
};

export const CoinControlModal: React.FC<Props> = props => {
return <View {...useCoinControlModal({ ...props })} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
BitcoinAccount,
Transaction,
TransactionStatus,
} from "@ledgerhq/live-common/families/bitcoin/types";
import { useCallback } from "react";
import { urls } from "~/config/urls";
import { useAccountUnit } from "~/renderer/hooks/useAccountUnit";
import { openURL } from "~/renderer/linking";
import { getUTXOStatus } from "@ledgerhq/live-common/families/bitcoin/logic";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";

type Props = {
isOpened?: boolean;
account: BitcoinAccount;
transaction: Transaction;
status: TransactionStatus;
onClose: () => void;
onChange: (a: (t: Transaction, p: Partial<Transaction>) => void) => void;
updateTransaction: (updater: (t: Transaction) => Transaction) => void;
};

export const useCoinControlModal = ({
isOpened,
account,
transaction,
status,
onClose,
onChange,
updateTransaction,
}: Props) => {
const onClickLink = useCallback(() => openURL(urls.coinControl), []);
const unit = useAccountUnit(account);
const { bitcoinResources } = account;
const { utxoStrategy } = transaction;
const totalExcludedUTXOS = bitcoinResources?.utxos
.map(u => getUTXOStatus(u, utxoStrategy))
.filter(({ excluded }) => excluded).length;

const bridge = getAccountBridge(account);
const errorKeys = Object.keys(status.errors);
const error = errorKeys.length ? status.errors[errorKeys[0]] : null;
const returning = (status.txOutputs || []).find(output => !!output.isChange);

return {
account,
isOpened,
unit,
bitcoinResources,
totalExcludedUTXOS,
bridge,
error,
returning,
utxoStrategy,
transaction,
status,
onClose,
onChange,
updateTransaction,
onClickLink,
};
};
Loading

0 comments on commit c9f388c

Please sign in to comment.