From 7460e1acbc751bdc7fa23082dd572a90ca0c5b12 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 26 Apr 2024 16:45:58 +0200 Subject: [PATCH 1/4] Add ibc in slice --- .../components/ibc/ibc-in/chain-dropdown.tsx | 17 ++++------- .../components/ibc/ibc-in/chain-picker.tsx | 13 -------- .../ibc/ibc-in/cosmos-wallet-connector.tsx | 19 ++++++++++++ .../components/ibc/ibc-in/interchain-ui.tsx | 7 +++-- .../src/components/ibc/ibc-loader.ts | 4 +-- .../components/ibc/ibc-out/chain-selector.tsx | 4 +-- .../components/ibc/ibc-out/ibc-out-form.tsx | 8 +++-- apps/minifront/src/state/ibc-in.ts | 20 +++++++++++++ .../state/{ibc.test.ts => ibc-out.test.ts} | 16 +++++----- .../src/state/{ibc.ts => ibc-out.ts} | 30 +++++++++---------- apps/minifront/src/state/index.ts | 9 ++++-- 11 files changed, 88 insertions(+), 59 deletions(-) delete mode 100644 apps/minifront/src/components/ibc/ibc-in/chain-picker.tsx create mode 100644 apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx create mode 100644 apps/minifront/src/state/ibc-in.ts rename apps/minifront/src/state/{ibc.test.ts => ibc-out.test.ts} (89%) rename apps/minifront/src/state/{ibc.ts => ibc-out.ts} (90%) diff --git a/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx b/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx index 62bff84454..aef27405d2 100644 --- a/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx +++ b/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx @@ -3,11 +3,8 @@ import { useMemo } from 'react'; import { Avatar, Box, Combobox, Skeleton, Stack, Text } from '@interchain-ui/react'; import { matchSorter } from 'match-sorter'; import { useManager } from '@cosmos-kit/react'; - -interface Option { - label: string; - value: string; -} +import { useStore } from '../../../state'; +import { ibcInSelector } from '../../../state/ibc-in'; export interface ChainInfo { chainName: string; @@ -16,10 +13,6 @@ export interface ChainInfo { icon?: string; } -export interface ChooseChainProps { - onChange: (selectedItem: Option) => void; -} - const ChainOption = ({ chainInfo: { label, icon } }: { chainInfo: ChainInfo }) => { return ( @@ -54,8 +47,9 @@ const useChainInfos = (): ChainInfo[] => { // Note the console will display aria-label warnings (despite them being present). // The cosmology team has been notified of the issue. -export const ChainDropdown = ({ onChange }: ChooseChainProps) => { +export const ChainDropdown = () => { const chainInfos = useChainInfos(); + const { setChain } = useStore(ibcInSelector); const [selectedKey, setSelectedKey] = React.useState(); const [filterValue, setFilterValue] = React.useState(''); @@ -79,6 +73,7 @@ export const ChainDropdown = ({ onChange }: ChooseChainProps) => { onInputChange={value => { setFilterValue(value); if (!value) { + setChain(undefined); setSelectedKey(undefined); } }} @@ -90,7 +85,7 @@ export const ChainDropdown = ({ onChange }: ChooseChainProps) => { const found = chainInfos.find(options => options.value === item) ?? null; if (found) { - onChange(found); + setChain(found); setFilterValue(found.label); } } diff --git a/apps/minifront/src/components/ibc/ibc-in/chain-picker.tsx b/apps/minifront/src/components/ibc/ibc-in/chain-picker.tsx deleted file mode 100644 index 5740f06e8f..0000000000 --- a/apps/minifront/src/components/ibc/ibc-in/chain-picker.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useEffect, useState } from 'react'; -import { ChainName } from 'cosmos-kit'; -import { ChainDropdown } from './chain-dropdown'; - -export const ChainPicker = () => { - // Temp until slices are setup - const [chainName, setChainName] = useState(); - useEffect(() => { - console.log(`You chose: ${chainName}`); - }, [chainName]); - - return setChainName(e.value)} />; -}; diff --git a/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx b/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx new file mode 100644 index 0000000000..04fcda9e91 --- /dev/null +++ b/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx @@ -0,0 +1,19 @@ +import { useStore } from '../../../state'; +import { ibcInSelector } from '../../../state/ibc-in'; + +export const CosmosWalletConnector = () => { + const { chain } = useStore(ibcInSelector); + + // const { + // connect, + // openView, + // status, + // username, + // address, + // message, + // wallet, + // chain: chainInfo, + // } = useChain(providedChainName || defaultChainName); + + return
{chain?.chainName}
; +}; diff --git a/apps/minifront/src/components/ibc/ibc-in/interchain-ui.tsx b/apps/minifront/src/components/ibc/ibc-in/interchain-ui.tsx index 65e3f8a607..3099525b2c 100644 --- a/apps/minifront/src/components/ibc/ibc-in/interchain-ui.tsx +++ b/apps/minifront/src/components/ibc/ibc-in/interchain-ui.tsx @@ -1,6 +1,7 @@ import { IbcChainProvider } from './chain-provider'; import { useRegistry } from '../../../fetchers/registry'; -import { ChainPicker } from './chain-picker'; +import { CosmosWalletConnector } from './cosmos-wallet-connector'; +import { ChainDropdown } from './chain-dropdown'; export const InterchainUi = () => { const { data, isLoading, error } = useRegistry(); @@ -13,9 +14,9 @@ export const InterchainUi = () => { {/* negative margin offsets div inserted by provider */}
- +
- {/* WalletSection to go here */} +
); }; diff --git a/apps/minifront/src/components/ibc/ibc-loader.ts b/apps/minifront/src/components/ibc/ibc-loader.ts index c98409ff0a..5bfb620e49 100644 --- a/apps/minifront/src/components/ibc/ibc-loader.ts +++ b/apps/minifront/src/components/ibc/ibc-loader.ts @@ -32,8 +32,8 @@ export const IbcLoader: LoaderFunction = async (): Promise => // set initial account if accounts exist and asset if account has asset list useStore.setState(state => { - state.ibc.selection = initialSelection; - state.ibc.chain = initialChain; + state.ibcOut.selection = initialSelection; + state.ibcOut.chain = initialChain; }); } diff --git a/apps/minifront/src/components/ibc/ibc-out/chain-selector.tsx b/apps/minifront/src/components/ibc/ibc-out/chain-selector.tsx index ba7f461a33..ef137ab3e7 100644 --- a/apps/minifront/src/components/ibc/ibc-out/chain-selector.tsx +++ b/apps/minifront/src/components/ibc/ibc-out/chain-selector.tsx @@ -8,13 +8,13 @@ import { import { cn } from '@penumbra-zone/ui/lib/utils'; import { useState } from 'react'; import { useStore } from '../../../state'; -import { ibcSelector } from '../../../state/ibc'; +import { ibcOutSelector } from '../../../state/ibc-out'; import { useLoaderData } from 'react-router-dom'; import { IbcLoaderResponse } from '../ibc-loader'; import { Chain } from '@penumbra-labs/registry'; export const ChainSelector = () => { - const { chain, setChain } = useStore(ibcSelector); + const { chain, setChain } = useStore(ibcOutSelector); const { chains: ibcConnections } = useLoaderData() as IbcLoaderResponse; const [openSelect, setOpenSelect] = useState(false); diff --git a/apps/minifront/src/components/ibc/ibc-out/ibc-out-form.tsx b/apps/minifront/src/components/ibc/ibc-out/ibc-out-form.tsx index c519218736..96dc542b5d 100644 --- a/apps/minifront/src/components/ibc/ibc-out/ibc-out-form.tsx +++ b/apps/minifront/src/components/ibc/ibc-out/ibc-out-form.tsx @@ -3,7 +3,11 @@ import { Input } from '@penumbra-zone/ui/components/ui/input'; import { ChainSelector } from './chain-selector'; import { useLoaderData } from 'react-router-dom'; import { useStore } from '../../../state'; -import { filterBalancesPerChain, ibcSelector, ibcValidationErrors } from '../../../state/ibc'; +import { + filterBalancesPerChain, + ibcOutSelector, + ibcValidationErrors, +} from '../../../state/ibc-out'; import InputToken from '../../shared/input-token'; import { InputBlock } from '../../shared/input-block'; import { IbcLoaderResponse } from '../ibc-loader'; @@ -20,7 +24,7 @@ export const IbcOutForm = () => { selection, setSelection, chain, - } = useStore(ibcSelector); + } = useStore(ibcOutSelector); const filteredBalances = filterBalancesPerChain(balances, chain, stakingTokenMetadata, assets); const validationErrors = useStore(ibcValidationErrors); diff --git a/apps/minifront/src/state/ibc-in.ts b/apps/minifront/src/state/ibc-in.ts new file mode 100644 index 0000000000..28632e8c2e --- /dev/null +++ b/apps/minifront/src/state/ibc-in.ts @@ -0,0 +1,20 @@ +import { AllSlices, SliceCreator } from '.'; +import { ChainInfo } from '../components/ibc/ibc-in/chain-dropdown'; + +export interface IbcInSlice { + chain?: ChainInfo; + setChain: (chain?: ChainInfo) => void; +} + +export const createIbcInSlice = (): SliceCreator => set => { + return { + chain: undefined, + setChain: chain => { + set(state => { + state.ibcIn.chain = chain; + }); + }, + }; +}; + +export const ibcInSelector = (state: AllSlices) => state.ibcIn; diff --git a/apps/minifront/src/state/ibc.test.ts b/apps/minifront/src/state/ibc-out.test.ts similarity index 89% rename from apps/minifront/src/state/ibc.test.ts rename to apps/minifront/src/state/ibc-out.test.ts index 78050c89ab..f9bfdf31eb 100644 --- a/apps/minifront/src/state/ibc.test.ts +++ b/apps/minifront/src/state/ibc-out.test.ts @@ -12,7 +12,7 @@ import { produce } from 'immer'; import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; import { addressFromBech32m } from '@penumbra-zone/bech32m/penumbra'; import { Chain } from '@penumbra-labs/registry'; -import { currentTimePlusTwoDaysRounded } from './ibc'; +import { currentTimePlusTwoDaysRounded } from './ibc-out'; describe.skip('IBC Slice', () => { const selectionExample = new BalancesResponse({ @@ -47,15 +47,15 @@ describe.skip('IBC Slice', () => { }); test('the default is empty, false or undefined', () => { - expect(useStore.getState().ibc.amount).toBe(''); - expect(useStore.getState().ibc.selection).toBeUndefined(); - expect(useStore.getState().ibc.chain).toBeUndefined(); + expect(useStore.getState().ibcOut.amount).toBe(''); + expect(useStore.getState().ibcOut.selection).toBeUndefined(); + expect(useStore.getState().ibcOut.chain).toBeUndefined(); }); describe('setAmount', () => { test('amount can be set', () => { - useStore.getState().ibc.setAmount('2'); - expect(useStore.getState().ibc.amount).toBe('2'); + useStore.getState().ibcOut.setAmount('2'); + expect(useStore.getState().ibcOut.amount).toBe('2'); }); test('validate high enough amount validates', () => { @@ -94,8 +94,8 @@ describe.skip('IBC Slice', () => { addressPrefix: 'osmo', } satisfies Chain; - useStore.getState().ibc.setChain(chain); - expect(useStore.getState().ibc.chain).toBe(chain); + useStore.getState().ibcOut.setChain(chain); + expect(useStore.getState().ibcOut.chain).toBe(chain); }); }); diff --git a/apps/minifront/src/state/ibc.ts b/apps/minifront/src/state/ibc-out.ts similarity index 90% rename from apps/minifront/src/state/ibc.ts rename to apps/minifront/src/state/ibc-out.ts index fa003f351f..950a6421c2 100644 --- a/apps/minifront/src/state/ibc.ts +++ b/apps/minifront/src/state/ibc-out.ts @@ -22,7 +22,7 @@ import { errorToast } from '@penumbra-zone/ui/lib/toast/presets'; import { Chain } from '@penumbra-labs/registry'; import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -export interface IbcSendSlice { +export interface IbcOutSlice { selection: BalancesResponse | undefined; setSelection: (selection: BalancesResponse) => void; amount: string; @@ -35,7 +35,7 @@ export interface IbcSendSlice { txInProgress: boolean; } -export const createIbcSendSlice = (): SliceCreator => (set, get) => { +export const createIbcOutSlice = (): SliceCreator => (set, get) => { return { amount: '', selection: undefined, @@ -44,22 +44,22 @@ export const createIbcSendSlice = (): SliceCreator => (set, get) = txInProgress: false, setSelection: selection => { set(state => { - state.ibc.selection = selection; + state.ibcOut.selection = selection; }); }, setAmount: amount => { set(state => { - state.ibc.amount = amount; + state.ibcOut.amount = amount; }); }, setChain: chain => { set(state => { - state.ibc.chain = chain; + state.ibcOut.chain = chain; }); }, setDestinationChainAddress: addr => { set(state => { - state.ibc.destinationChainAddress = addr; + state.ibcOut.destinationChainAddress = addr; }); }, sendIbcWithdraw: async () => { @@ -68,18 +68,18 @@ export const createIbcSendSlice = (): SliceCreator => (set, get) = }); try { - const req = await getPlanRequest(get().ibc); + const req = await getPlanRequest(get().ibcOut); await planBuildBroadcast('ics20Withdrawal', req); // Reset form set(state => { - state.ibc.amount = ''; + state.ibcOut.amount = ''; }); } catch (e) { errorToast(e, 'Ics20 withdrawal error').render(); } finally { set(state => { - state.ibc.txInProgress = false; + state.ibcOut.txInProgress = false; }); } }, @@ -154,7 +154,7 @@ const getPlanRequest = async ({ selection, chain, destinationChainAddress, -}: IbcSendSlice): Promise => { +}: IbcOutSlice): Promise => { if (!destinationChainAddress) throw new Error('no destination chain address set'); if (!chain) throw new Error('Chain not set'); if (!selection) throw new Error('No asset selected'); @@ -184,16 +184,16 @@ const getPlanRequest = async ({ }); }; -export const ibcSelector = (state: AllSlices) => state.ibc; +export const ibcOutSelector = (state: AllSlices) => state.ibcOut; export const ibcValidationErrors = (state: AllSlices) => { return { - recipientErr: !state.ibc.destinationChainAddress + recipientErr: !state.ibcOut.destinationChainAddress ? false - : !validateUnknownAddress(state.ibc.chain, state.ibc.destinationChainAddress), - amountErr: !state.ibc.selection + : !validateUnknownAddress(state.ibcOut.chain, state.ibcOut.destinationChainAddress), + amountErr: !state.ibcOut.selection ? false - : amountMoreThanBalance(state.ibc.selection, state.ibc.amount), + : amountMoreThanBalance(state.ibcOut.selection, state.ibcOut.amount), }; }; diff --git a/apps/minifront/src/state/index.ts b/apps/minifront/src/state/index.ts index 695b9fe60e..a975e71358 100644 --- a/apps/minifront/src/state/index.ts +++ b/apps/minifront/src/state/index.ts @@ -2,11 +2,12 @@ import { create, StateCreator } from 'zustand'; import { enableMapSet } from 'immer'; import { immer } from 'zustand/middleware/immer'; import { createSwapSlice, SwapSlice } from './swap'; -import { createIbcSendSlice, IbcSendSlice } from './ibc'; +import { createIbcOutSlice, IbcOutSlice } from './ibc-out'; import { createSendSlice, SendSlice } from './send'; import { createStakingSlice, StakingSlice } from './staking'; import { createUnclaimedSwapsSlice, UnclaimedSwapsSlice } from './unclaimed-swaps'; import { createTransactionsSlice, TransactionsSlice } from './transactions'; +import { createIbcInSlice, IbcInSlice } from './ibc-in'; /** * Required to enable use of `Map`s in Zustand state when using Immer @@ -16,7 +17,8 @@ import { createTransactionsSlice, TransactionsSlice } from './transactions'; enableMapSet(); export interface AllSlices { - ibc: IbcSendSlice; + ibcIn: IbcInSlice; + ibcOut: IbcOutSlice; send: SendSlice; staking: StakingSlice; swap: SwapSlice; @@ -33,7 +35,8 @@ export type SliceCreator = StateCreator< export const initializeStore = () => { return immer((setState, getState: () => AllSlices, store) => ({ - ibc: createIbcSendSlice()(setState, getState, store), + ibcIn: createIbcInSlice()(setState, getState, store), + ibcOut: createIbcOutSlice()(setState, getState, store), send: createSendSlice()(setState, getState, store), staking: createStakingSlice()(setState, getState, store), swap: createSwapSlice()(setState, getState, store), From 6d7b30ee8adc4660f42f1fde5807712b3f5c933c Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 26 Apr 2024 17:59:23 +0200 Subject: [PATCH 2/4] Connect wallet --- apps/minifront/package.json | 1 + .../components/ibc/ibc-in/address-card.tsx | 104 +++++++++ .../components/ibc/ibc-in/chain-dropdown.tsx | 18 +- .../ibc/ibc-in/cosmos-wallet-connector.tsx | 120 ++++++++-- .../src/components/ibc/ibc-in/user-info.tsx | 31 +++ .../ibc/ibc-in/wallet-connect-button.tsx | 221 ++++++++++++++++++ apps/minifront/src/state/ibc-in.ts | 10 +- pnpm-lock.yaml | 3 + 8 files changed, 485 insertions(+), 23 deletions(-) create mode 100644 apps/minifront/src/components/ibc/ibc-in/address-card.tsx create mode 100644 apps/minifront/src/components/ibc/ibc-in/user-info.tsx create mode 100644 apps/minifront/src/components/ibc/ibc-in/wallet-connect-button.tsx diff --git a/apps/minifront/package.json b/apps/minifront/package.json index 65a4042e53..efce41bd18 100644 --- a/apps/minifront/package.json +++ b/apps/minifront/package.json @@ -42,6 +42,7 @@ "framer-motion": "^11.0.22", "immer": "^10.0.4", "lodash": "^4.17.21", + "lucide-react": "^0.363.0", "match-sorter": "^6.3.4", "osmo-query": "^16.11.0", "react": "^18.2.0", diff --git a/apps/minifront/src/components/ibc/ibc-in/address-card.tsx b/apps/minifront/src/components/ibc/ibc-in/address-card.tsx new file mode 100644 index 0000000000..41eb350971 --- /dev/null +++ b/apps/minifront/src/components/ibc/ibc-in/address-card.tsx @@ -0,0 +1,104 @@ +import { Box, ClipboardCopyText, Stack } from '@interchain-ui/react'; +import { WalletStatus } from 'cosmos-kit'; +import { ReactNode, useEffect, useState } from 'react'; + +const SIZES = { + lg: { + height: 32, + walletImageSize: 144, + }, + md: { + height: 24, + walletImageSize: 96, + }, + sm: { + height: 20, + walletImageSize: 64, + }, +}; + +interface CopyAddressType { + address?: string; + walletIcon?: string; + isLoading?: boolean; + maxDisplayLength?: number; + size?: keyof typeof SIZES; +} + +export const ConnectedShowAddress = ({ + address = '', + walletIcon, + size = 'md', + maxDisplayLength, +}: CopyAddressType) => { + const [displayAddress, setDisplayAddress] = useState(''); + const defaultMaxLength = { + lg: 14, + md: 16, + sm: 18, + }; + + useEffect(() => { + if (!address) setDisplayAddress('address not identified yet'); + if (address && maxDisplayLength) + setDisplayAddress(stringTruncateFromCenter(address, maxDisplayLength)); + if (address && !maxDisplayLength) + setDisplayAddress(stringTruncateFromCenter(address, defaultMaxLength[size])); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [address]); + + const walletImgDimension = `${SIZES[size].walletImageSize}px`; + + const chainIcon = + address && walletIcon ? ( + + {displayAddress} + + ) : null; + + return ( + + {chainIcon} + + + ); +}; + +export const CopyAddressBtn = ({ + walletStatus, + connected, +}: { + walletStatus: WalletStatus; + connected: ReactNode; +}) => { + switch (walletStatus) { + case WalletStatus.Connected: + return <>{connected}; + default: + return <>; + } +}; + +function stringTruncateFromCenter(str: string, maxLength: number) { + const midChar = '…'; // character to insert into the center of the result + + if (str.length <= maxLength) return str; + + // length of beginning part + const left = Math.ceil(maxLength / 2); + + // start index of ending part + const right = str.length - Math.floor(maxLength / 2) + 1; + + return str.substring(0, left) + midChar + str.substring(right); +} diff --git a/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx b/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx index aef27405d2..e5fbcb73a0 100644 --- a/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx +++ b/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { Avatar, Box, Combobox, Skeleton, Stack, Text } from '@interchain-ui/react'; import { matchSorter } from 'match-sorter'; import { useManager } from '@cosmos-kit/react'; @@ -49,11 +49,21 @@ const useChainInfos = (): ChainInfo[] => { // The cosmology team has been notified of the issue. export const ChainDropdown = () => { const chainInfos = useChainInfos(); - const { setChain } = useStore(ibcInSelector); + const { setSelectedChain } = useStore(ibcInSelector); const [selectedKey, setSelectedKey] = React.useState(); const [filterValue, setFilterValue] = React.useState(''); + // Initialize selection to first chain + useEffect(() => { + if (chainInfos.length > 0) { + setSelectedKey(chainInfos[0]!.value); + setFilterValue(chainInfos[0]!.label); + } + + // if (!chainName) setSelectedKey(undefined); + }, [chainInfos]); + const filteredItems = React.useMemo(() => { return matchSorter(chainInfos, filterValue, { keys: ['label', 'value'], @@ -73,7 +83,7 @@ export const ChainDropdown = () => { onInputChange={value => { setFilterValue(value); if (!value) { - setChain(undefined); + setSelectedChain(undefined); setSelectedKey(undefined); } }} @@ -85,7 +95,7 @@ export const ChainDropdown = () => { const found = chainInfos.find(options => options.value === item) ?? null; if (found) { - setChain(found); + setSelectedChain(found); setFilterValue(found.label); } } diff --git a/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx b/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx index 04fcda9e91..89b4f302c8 100644 --- a/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx +++ b/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx @@ -1,19 +1,111 @@ import { useStore } from '../../../state'; import { ibcInSelector } from '../../../state/ibc-in'; +import { useChain, useManager } from '@cosmos-kit/react'; +import { Box, Stack } from '@interchain-ui/react'; +import { + Connected, + Connecting, + Disconnected, + Error, + NotExist, + Rejected, + WalletConnectComponent, +} from './wallet-connect-button'; +import { MouseEventHandler } from 'react'; +import { UserInfo } from './user-info'; + +const ConnectWalletButtonAlt = () => { + const { connect, openView, status } = useChainConnector(); + + const onClickConnect: MouseEventHandler = e => { + e.preventDefault(); + void connect(); + }; + + const onClickOpenView: MouseEventHandler = e => { + e.preventDefault(); + openView(); + }; + + return ( + } + connecting={} + connected={} + rejected={} + error={} + notExist={} + /> + ); +}; + +const useChainConnector = () => { + const { selectedChain } = useStore(ibcInSelector); + const { chainRecords } = useManager(); + const defaultChain = chainRecords[0]!.name; + return useChain(selectedChain?.chainName ?? defaultChain); +}; export const CosmosWalletConnector = () => { - const { chain } = useStore(ibcInSelector); - - // const { - // connect, - // openView, - // status, - // username, - // address, - // message, - // wallet, - // chain: chainInfo, - // } = useChain(providedChainName || defaultChainName); - - return
{chain?.chainName}
; + const { selectedChain } = useStore(ibcInSelector); + const { username, address, status } = useChainConnector(); + + return ( + + + {/*{isMultiChain ? (*/} + {/* {chooseChain}*/} + {/*) : (*/} + {/* */} + {/* */} + {/* */} + {/*)}*/} + + + + {address && } + + + + + + {/*{connectWalletWarn}*/} + + + + + ); }; diff --git a/apps/minifront/src/components/ibc/ibc-in/user-info.tsx b/apps/minifront/src/components/ibc/ibc-in/user-info.tsx new file mode 100644 index 0000000000..40e014ca01 --- /dev/null +++ b/apps/minifront/src/components/ibc/ibc-in/user-info.tsx @@ -0,0 +1,31 @@ +import { Stack } from '@interchain-ui/react'; +import { Identicon } from '@penumbra-zone/ui/components/ui/identicon'; +import { ConnectedShowAddress, CopyAddressBtn } from './address-card'; +import { WalletStatus } from 'cosmos-kit'; + +interface UserInfoProps { + address: string; + username?: string; + status: WalletStatus; +} + +export const UserInfo = ({ address, username, status }: UserInfoProps) => { + return ( + +
+ + } + /> +
+ {username} +
+ ); +}; diff --git a/apps/minifront/src/components/ibc/ibc-in/wallet-connect-button.tsx b/apps/minifront/src/components/ibc/ibc-in/wallet-connect-button.tsx new file mode 100644 index 0000000000..dbb725b6b2 --- /dev/null +++ b/apps/minifront/src/components/ibc/ibc-in/wallet-connect-button.tsx @@ -0,0 +1,221 @@ +import { Box, Button, Stack, Text, useColorModeValue } from '@interchain-ui/react'; +import { WalletStatus } from 'cosmos-kit'; +import { WalletIcon } from '@penumbra-zone/ui/components/ui/icons/wallet'; +import { TriangleAlert } from 'lucide-react'; +import { MouseEventHandler, ReactNode } from 'react'; + +interface IConnectWalletButton { + buttonText?: string; + isLoading?: boolean; + isDisabled?: boolean; + onClickConnectBtn?: MouseEventHandler; +} + +export const ConnectWalletButton = ({ + buttonText, + isLoading, + isDisabled, + onClickConnectBtn, +}: IConnectWalletButton) => { + return ( + + ); +}; + +export const Disconnected = ({ + buttonText, + onClick, +}: { + buttonText: string; + onClick: MouseEventHandler; +}) => { + return ; +}; + +export const Connected = ({ + buttonText, + onClick, +}: { + buttonText: string; + onClick: MouseEventHandler; +}) => { + return ; +}; + +export const Connecting = () => { + return ; +}; + +export const Rejected = ({ + buttonText, + wordOfWarning, + onClick, +}: { + buttonText: string; + wordOfWarning?: string; + onClick: MouseEventHandler; +}) => { + const bg = useColorModeValue('$orange200', '$orange300'); + + return ( + + + + {wordOfWarning && ( + + + + + + + + Warning:  + + + {wordOfWarning} + + + )} + + ); +}; + +export const Error = ({ + buttonText, + wordOfWarning, + onClick, +}: { + buttonText: string; + wordOfWarning?: string; + onClick: MouseEventHandler; +}) => { + const bg = useColorModeValue('$orange200', '$orange300'); + + return ( + + + + {wordOfWarning && ( + + + + + + + + Warning:  + + + {wordOfWarning} + + + )} + + ); +}; + +export const NotExist = ({ + buttonText, + onClick, +}: { + buttonText: string; + onClick: MouseEventHandler; +}) => { + return ( + + ); +}; + +export const WalletConnectComponent = ({ + walletStatus, + disconnect, + connecting, + connected, + rejected, + error, + notExist, +}: { + walletStatus: WalletStatus; + disconnect: ReactNode; + connecting: ReactNode; + connected: ReactNode; + rejected: ReactNode; + error: ReactNode; + notExist: ReactNode; +}) => { + switch (walletStatus) { + case WalletStatus.Disconnected: + return <>{disconnect}; + case WalletStatus.Connecting: + return <>{connecting}; + case WalletStatus.Connected: + return <>{connected}; + case WalletStatus.Rejected: + return <>{rejected}; + case WalletStatus.Error: + return <>{error}; + case WalletStatus.NotExist: + return <>{notExist}; + default: + return <>{disconnect}; + } +}; diff --git a/apps/minifront/src/state/ibc-in.ts b/apps/minifront/src/state/ibc-in.ts index 28632e8c2e..91f3113f50 100644 --- a/apps/minifront/src/state/ibc-in.ts +++ b/apps/minifront/src/state/ibc-in.ts @@ -2,16 +2,16 @@ import { AllSlices, SliceCreator } from '.'; import { ChainInfo } from '../components/ibc/ibc-in/chain-dropdown'; export interface IbcInSlice { - chain?: ChainInfo; - setChain: (chain?: ChainInfo) => void; + selectedChain?: ChainInfo; + setSelectedChain: (chain?: ChainInfo) => void; } export const createIbcInSlice = (): SliceCreator => set => { return { - chain: undefined, - setChain: chain => { + selectedChain: undefined, + setSelectedChain: chain => { set(state => { - state.ibcIn.chain = chain; + state.ibcIn.selectedChain = chain; }); }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf3ed4ab55..79797528cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -436,6 +436,9 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 + lucide-react: + specifier: ^0.363.0 + version: 0.363.0(react@18.2.0) match-sorter: specifier: ^6.3.4 version: 6.3.4 From 3f614146e93631499671fd1d306e3f4b8e8e7b52 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 29 Apr 2024 22:16:23 +0200 Subject: [PATCH 3/4] Update dropdown --- apps/minifront/package.json | 13 +- .../components/ibc/ibc-in/chain-dropdown.tsx | 163 +++---- .../ibc/ibc-in/cosmos-wallet-connector.tsx | 2 + .../src/components/ibc/ibc-in/ibc-in-form.tsx | 2 +- .../components/ibc/ibc-in/interchain-ui.tsx | 4 +- .../src/components/ibc/ibc-loader.ts | 2 +- packages/ui/components/ui/avatar.tsx | 46 ++ packages/ui/components/ui/command.tsx | 144 ++++++ packages/ui/components/ui/popover.tsx | 4 +- packages/ui/package.json | 2 + pnpm-lock.yaml | 429 ++++++++++++++---- 11 files changed, 615 insertions(+), 196 deletions(-) create mode 100644 packages/ui/components/ui/avatar.tsx create mode 100644 packages/ui/components/ui/command.tsx diff --git a/apps/minifront/package.json b/apps/minifront/package.json index efce41bd18..dfd9db74da 100644 --- a/apps/minifront/package.json +++ b/apps/minifront/package.json @@ -20,8 +20,8 @@ "@bufbuild/protobuf": "^1.9.0", "@cosmjs/proto-signing": "^0.32.3", "@cosmjs/stargate": "^0.32.3", - "@cosmos-kit/react": "^2.11.0", - "@interchain-ui/react": "^1.23.3", + "@cosmos-kit/react": "^2.11.2", + "@interchain-ui/react": "^1.23.10", "@penumbra-labs/registry": "^5.1.0", "@penumbra-zone/bech32m": "workspace:*", "@penumbra-zone/client": "workspace:*", @@ -36,15 +36,14 @@ "@tanstack/react-query": "^5.28.9", "bech32": "^2.0.0", "bignumber.js": "^9.1.2", - "chain-registry": "^1.41.9", - "cosmos-kit": "^2.10.0", + "chain-registry": "^1.45.5", + "cosmos-kit": "^2.10.2", "date-fns": "^3.6.0", "framer-motion": "^11.0.22", "immer": "^10.0.4", "lodash": "^4.17.21", "lucide-react": "^0.363.0", - "match-sorter": "^6.3.4", - "osmo-query": "^16.11.0", + "osmo-query": "^16.12.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-helmet": "^6.1.0", @@ -56,7 +55,7 @@ "zustand": "^4.5.2" }, "devDependencies": { - "@chain-registry/types": "^0.25.7", + "@chain-registry/types": "^0.28.4", "@penumbra-zone/polyfills": "workspace:*", "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.2", diff --git a/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx b/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx index e5fbcb73a0..9d6ca60a7b 100644 --- a/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx +++ b/apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx @@ -1,10 +1,20 @@ import * as React from 'react'; -import { useEffect, useMemo } from 'react'; -import { Avatar, Box, Combobox, Skeleton, Stack, Text } from '@interchain-ui/react'; -import { matchSorter } from 'match-sorter'; +import { useMemo } from 'react'; import { useManager } from '@cosmos-kit/react'; -import { useStore } from '../../../state'; +import { Popover, PopoverContent, PopoverTrigger } from '@penumbra-zone/ui/components/ui/popover'; +import { ChevronsUpDown } from 'lucide-react'; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from '@penumbra-zone/ui/components/ui/command'; +import { Button } from '@penumbra-zone/ui/components/ui/button'; import { ibcInSelector } from '../../../state/ibc-in'; +import { useStore } from '../../../state'; +import { Avatar, AvatarImage } from '@penumbra-zone/ui/components/ui/avatar'; +import { Identicon } from '@penumbra-zone/ui/components/ui/identicon'; export interface ChainInfo { chainName: string; @@ -13,22 +23,6 @@ export interface ChainInfo { icon?: string; } -const ChainOption = ({ chainInfo: { label, icon } }: { chainInfo: ChainInfo }) => { - return ( - - name[0]!} - size='xs' - src={icon} - fallbackMode='bg' - /> - {label} - - ); -}; - const useChainInfos = (): ChainInfo[] => { const { chainRecords, getChainLogo } = useManager(); return useMemo( @@ -51,88 +45,59 @@ export const ChainDropdown = () => { const chainInfos = useChainInfos(); const { setSelectedChain } = useStore(ibcInSelector); - const [selectedKey, setSelectedKey] = React.useState(); - const [filterValue, setFilterValue] = React.useState(''); + const [open, setOpen] = React.useState(false); + const [value, setValue] = React.useState(''); - // Initialize selection to first chain - useEffect(() => { - if (chainInfos.length > 0) { - setSelectedKey(chainInfos[0]!.value); - setFilterValue(chainInfos[0]!.label); - } - - // if (!chainName) setSelectedKey(undefined); - }, [chainInfos]); - - const filteredItems = React.useMemo(() => { - return matchSorter(chainInfos, filterValue, { - keys: ['label', 'value'], - }); - }, [chainInfos, filterValue]); - - const avatarUrl = filteredItems.find(i => i.value === selectedKey)?.icon ?? undefined; + const selected = chainInfos.find(c => c.value === value); return ( - -
Select chain
- { - setFilterValue(value); - if (!value) { - setSelectedChain(undefined); - setSelectedKey(undefined); - } - }} - selectedKey={selectedKey} - onSelectionChange={item => { - if (item) { - setSelectedKey(item as string); - - const found = chainInfos.find(options => options.value === item) ?? null; - - if (found) { - setSelectedChain(found); - setFilterValue(found.label); - } - } - }} - inputAddonStart={ - selectedKey && avatarUrl ? ( - name[0]!} - size='xs' - src={avatarUrl} - fallbackMode='bg' - className='px-2' - /> + + + + + + + + No framework found. + + {chainInfos.map(chain => ( + { + setValue(currentValue === value ? '' : currentValue); + setOpen(false); + + const match = chainInfos.find(options => options.value === currentValue); + if (match) { + setSelectedChain(match); + } + }} + className='flex gap-2' + > + + + + + {chain.label} + + ))} + + + + ); }; diff --git a/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx b/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx index 89b4f302c8..d7a56e7295 100644 --- a/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx +++ b/apps/minifront/src/components/ibc/ibc-in/cosmos-wallet-connector.tsx @@ -51,6 +51,8 @@ export const CosmosWalletConnector = () => { const { selectedChain } = useStore(ibcInSelector); const { username, address, status } = useChainConnector(); + console.log(selectedChain); + return ( { }} > - ); }; - -export const Disconnected = ({ - buttonText, - onClick, -}: { - buttonText: string; - onClick: MouseEventHandler; -}) => { - return ; -}; - -export const Connected = ({ - buttonText, - onClick, -}: { - buttonText: string; - onClick: MouseEventHandler; -}) => { - return ; -}; - -export const Connecting = () => { - return ; -}; - -export const Rejected = ({ - buttonText, - wordOfWarning, - onClick, -}: { - buttonText: string; - wordOfWarning?: string; - onClick: MouseEventHandler; -}) => { - const bg = useColorModeValue('$orange200', '$orange300'); - - return ( - - - - {wordOfWarning && ( - - - - - - - - Warning:  - - - {wordOfWarning} - - - )} - - ); -}; - -export const Error = ({ - buttonText, - wordOfWarning, - onClick, -}: { - buttonText: string; - wordOfWarning?: string; - onClick: MouseEventHandler; -}) => { - const bg = useColorModeValue('$orange200', '$orange300'); - - return ( - - - - {wordOfWarning && ( - - - - - - - - Warning:  - - - {wordOfWarning} - - - )} - - ); -}; - -export const NotExist = ({ - buttonText, - onClick, -}: { - buttonText: string; - onClick: MouseEventHandler; -}) => { - return ( - - ); -}; - -export const WalletConnectComponent = ({ - walletStatus, - disconnect, - connecting, - connected, - rejected, - error, - notExist, -}: { - walletStatus: WalletStatus; - disconnect: ReactNode; - connecting: ReactNode; - connected: ReactNode; - rejected: ReactNode; - error: ReactNode; - notExist: ReactNode; -}) => { - switch (walletStatus) { - case WalletStatus.Disconnected: - return <>{disconnect}; - case WalletStatus.Connecting: - return <>{connecting}; - case WalletStatus.Connected: - return <>{connected}; - case WalletStatus.Rejected: - return <>{rejected}; - case WalletStatus.Error: - return <>{error}; - case WalletStatus.NotExist: - return <>{notExist}; - default: - return <>{disconnect}; - } -}; diff --git a/apps/minifront/src/components/ibc/layout.tsx b/apps/minifront/src/components/ibc/layout.tsx index a97afc40c8..1e7bbdcd50 100644 --- a/apps/minifront/src/components/ibc/layout.tsx +++ b/apps/minifront/src/components/ibc/layout.tsx @@ -8,12 +8,12 @@ export const IbcLayout = () => { <>
- + @@ -22,7 +22,7 @@ export const IbcLayout = () => { direction='left' // Negative calculated margin giving lint issue /* eslint-disable-next-line tailwindcss/enforces-negative-arbitrary-values */ - className='invisible absolute inset-y-0 left-0 my-auto -ml-[calc(30vw-3px)] size-[30vw] text-stone-700 md:visible' + className='invisible absolute -bottom-44 left-0 z-0 my-auto -ml-[calc(30vw-3px)] size-[30vw] text-stone-700 md:visible' /> diff --git a/packages/ui/components/ui/command.tsx b/packages/ui/components/ui/command.tsx index d8b5c4451a..d5bb1c872a 100644 --- a/packages/ui/components/ui/command.tsx +++ b/packages/ui/components/ui/command.tsx @@ -27,7 +27,7 @@ type CommandDialogProps = DialogProps; const CommandDialog = ({ children, ...props }: CommandDialogProps) => { return ( - + {children} @@ -40,6 +40,7 @@ const CommandInput = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( + // eslint-disable-next-line react/no-unknown-property