Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/hooks/address-items.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Blockchain, Session, UserAddress, useAuthContext, useUserContext } from '@dfx.swiss/react';
import { useMemo } from 'react';
import { addressLabel } from 'src/config/labels';
import { useSettingsContext } from 'src/contexts/settings.context';
import { useBlockchain } from './blockchain.hook';

export interface AddressItem {
address?: string;
addressLabel: string;
label: string;
chain?: Blockchain;
}

interface UseAddressItemsParams {
availableBlockchains?: Blockchain[];
}

interface UseAddressItemsResult {
addressItems: AddressItem[];
availableBlockchains: Blockchain[];
}

/**
* Hook to generate address items for blockchain/address selection.
* Supports linked addresses (userAddresses) for cross-chain operations.
*/
export function useAddressItems({ availableBlockchains }: UseAddressItemsParams = {}): UseAddressItemsResult {
const { session } = useAuthContext();
const { userAddresses } = useUserContext();
const { translate } = useSettingsContext();
const { toString } = useBlockchain();

// Combine current session with linked addresses, removing duplicates
const userSessions = useMemo(() => {
return [session, ...userAddresses].filter(
(a, i, arr) => a && arr.findIndex((b) => b?.address === a.address) === i,
) as (Session | UserAddress)[];
}, [session, userAddresses]);

// Create address items with their blockchains
const userAddressItems = useMemo(() => {
return userSessions.map((a) => ({
address: a.address,
addressLabel: addressLabel(a),
blockchains: a.blockchains,
}));
}, [userSessions]);

// Filter blockchains based on available blockchains and user's linked addresses
const validBlockchains = useMemo(() => {
const userBlockchains = userAddressItems.flatMap((a) => a.blockchains).filter((b, i, arr) => arr.indexOf(b) === i);

return availableBlockchains
? userBlockchains.filter((b) => availableBlockchains.includes(b))
: userBlockchains;
}, [userAddressItems, availableBlockchains]);

// Generate address items for dropdown
const addressItems: AddressItem[] = useMemo(() => {
if (userAddressItems.length === 0 || validBlockchains.length === 0) {
return [];
}

const items: AddressItem[] = validBlockchains.flatMap((blockchain) => {
const addressesForBlockchain = userAddressItems.filter((a) => a.blockchains.includes(blockchain));
return addressesForBlockchain.map((a) => ({
address: a.address,
addressLabel: a.addressLabel,
label: toString(blockchain),
chain: blockchain,
}));
});

// Add "Switch address" option
items.push({
addressLabel: translate('screens/buy', 'Switch address'),
label: translate('screens/buy', 'Login with a different address'),
});

return items;
}, [userAddressItems, validBlockchains, toString, translate]);

return { addressItems, availableBlockchains: validBlockchains };
}
36 changes: 9 additions & 27 deletions src/screens/buy.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ import { BuyCompletion } from '../components/payment/buy-completion';
import { PrivateAssetHint } from '../components/private-asset-hint';
import { QuoteErrorHint } from '../components/quote-error-hint';
import { SanctionHint } from '../components/sanction-hint';
import { addressLabel } from '../config/labels';
import { useAppHandlingContext } from '../contexts/app-handling.context';
import { useLayoutContext } from '../contexts/layout.context';
import { useSettingsContext } from '../contexts/settings.context';
import { useWalletContext } from '../contexts/wallet.context';
import { AddressItem, useAddressItems } from '../hooks/address-items.hook';
import { useAppParams } from '../hooks/app-params.hook';
import { useBlockchain } from '../hooks/blockchain.hook';
import useDebounce from '../hooks/debounce.hook';
Expand All @@ -68,19 +68,13 @@ enum Side {
GET = 'GET',
}

interface Address {
address: string;
label: string;
chain?: Blockchain;
}

interface FormData {
amount: string;
currency: Fiat;
paymentMethod: FiatPaymentMethod;
asset: Asset;
targetAmount: string;
address: Address;
address: AddressItem;
}

interface ValidatedData extends BuyPaymentInfo {
Expand Down Expand Up @@ -121,6 +115,11 @@ export default function BuyScreen(): JSX.Element {
const { rootRef } = useLayoutContext();
const { isInitialized } = useAppHandlingContext();

const filteredAssets = assets && filterAssets(Array.from(assets.values()).flat(), assetFilter);
const targetBlockchains = availableBlockchains?.filter((b) => filteredAssets?.some((a) => a.blockchain === b));

const { addressItems } = useAddressItems({ availableBlockchains: targetBlockchains });

const [availableAssets, setAvailableAssets] = useState<Asset[]>();
const [paymentInfo, setPaymentInfo] = useState<Buy>();
const [customAmountError, setCustomAmountError] = useState<string>();
Expand Down Expand Up @@ -148,23 +147,6 @@ export default function BuyScreen(): JSX.Element {
setValue(field, value, { shouldValidate: true });
}

const filteredAssets = assets && filterAssets(Array.from(assets.values()).flat(), assetFilter);
const blockchains = availableBlockchains?.filter((b) => filteredAssets?.some((a) => a.blockchain === b));

const addressItems: Address[] =
session?.address && blockchains?.length
? [
...blockchains.map((b) => ({
address: addressLabel(session),
label: toString(b),
chain: b,
})),
{
address: translate('screens/buy', 'Switch address'),
label: translate('screens/buy', 'Login with a different address'),
},
]
: [];
const availablePaymentMethods = [FiatPaymentMethod.BANK];

// no instant payments ATM
Expand Down Expand Up @@ -564,11 +546,11 @@ export default function BuyScreen(): JSX.Element {
</StyledHorizontalStack>

{!hideTargetSelection && (
<StyledDropdown<Address>
<StyledDropdown<AddressItem>
rootRef={rootRef}
name="address"
items={addressItems}
labelFunc={(item) => blankedAddress(item.address, { width })}
labelFunc={(item) => blankedAddress(item.addressLabel, { width })}
descriptionFunc={(item) => item.label}
full
forceEnable
Expand Down
43 changes: 13 additions & 30 deletions src/screens/sell.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import { BankAccountSelector } from 'src/components/order/bank-account-selector'
import { AddressSwitch } from 'src/components/payment/address-switch';
import { PaymentInformationContent } from 'src/components/payment/payment-info-sell';
import { PrivateAssetHint } from 'src/components/private-asset-hint';
import { addressLabel } from 'src/config/labels';
import { Urls } from 'src/config/urls';
import { useLayoutContext } from 'src/contexts/layout.context';
import { useWindowContext } from 'src/contexts/window.context';
Expand All @@ -54,6 +53,7 @@ import { CloseType, useAppHandlingContext } from '../contexts/app-handling.conte
import { AssetBalance } from '../contexts/balance.context';
import { useSettingsContext } from '../contexts/settings.context';
import { useWalletContext } from '../contexts/wallet.context';
import { AddressItem, useAddressItems } from '../hooks/address-items.hook';
import { useAppParams } from '../hooks/app-params.hook';
import { useBlockchain } from '../hooks/blockchain.hook';
import useDebounce from '../hooks/debounce.hook';
Expand All @@ -68,19 +68,13 @@ enum Side {
GET = 'GET',
}

interface Address {
address: string;
label: string;
chain?: Blockchain;
}

interface FormData {
bankAccount: BankAccount;
currency: Fiat;
asset: Asset;
amount: string;
targetAmount: string;
address: Address;
address: AddressItem;
}

interface CustomAmountError {
Expand Down Expand Up @@ -126,6 +120,11 @@ export default function SellScreen(): JSX.Element {
const { toString } = useBlockchain();
const { rootRef } = useLayoutContext();

const filteredAssets = assets && filterAssets(Array.from(assets.values()).flat(), assetFilter);
const sourceBlockchains = availableBlockchains?.filter((b) => filteredAssets?.some((a) => a.blockchain === b));

const { addressItems } = useAddressItems({ availableBlockchains: sourceBlockchains });

const [availableAssets, setAvailableAssets] = useState<Asset[]>();
const [customAmountError, setCustomAmountError] = useState<CustomAmountError>();
const [errorMessage, setErrorMessage] = useState<string>();
Expand Down Expand Up @@ -153,32 +152,16 @@ export default function SellScreen(): JSX.Element {
const availableBalance = selectedAsset && findBalance(selectedAsset);

useEffect(() => {
availableAssets && getBalances(availableAssets, selectedAddress?.address, selectedAddress?.chain).then(setBalances);
}, [getBalances, availableAssets]);
if (availableAssets && selectedAddress?.address) {
getBalances(availableAssets, selectedAddress.address, selectedAddress.chain).then(setBalances);
}
}, [getBalances, availableAssets, selectedAddress]);

// default params
function setVal(field: FieldPath<FormData>, value: FieldPathValue<FormData, FieldPath<FormData>>) {
setValue(field, value, { shouldValidate: true });
}

const filteredAssets = assets && filterAssets(Array.from(assets.values()).flat(), assetFilter);
const blockchains = availableBlockchains?.filter((b) => filteredAssets?.some((a) => a.blockchain === b));

const addressItems: Address[] =
session?.address && blockchains?.length
? [
...blockchains.map((b) => ({
address: addressLabel(session),
label: toString(b),
chain: b,
})),
{
address: translate('screens/buy', 'Switch address'),
label: translate('screens/buy', 'Login with a different address'),
},
]
: [];

useEffect(() => {
const activeBlockchain = walletBlockchain ?? blockchain;
const blockchains = activeBlockchain ? [activeBlockchain as Blockchain] : availableBlockchains ?? [];
Expand Down Expand Up @@ -595,11 +578,11 @@ export default function SellScreen(): JSX.Element {
</div>
</StyledHorizontalStack>
{!hideTargetSelection && (
<StyledDropdown<Address>
<StyledDropdown<AddressItem>
rootRef={rootRef}
name="address"
items={addressItems}
labelFunc={(item) => blankedAddress(item.address, { width })}
labelFunc={(item) => blankedAddress(item.addressLabel, { width })}
descriptionFunc={(item) => item.label}
full
forceEnable
Expand Down
Loading
Loading