Skip to content

Commit

Permalink
Fix: Multisig single chain view (#2649)
Browse files Browse the repository at this point in the history
  • Loading branch information
tuul-wq authored Nov 15, 2024
1 parent 6749c67 commit 2c3f5f7
Show file tree
Hide file tree
Showing 24 changed files with 641 additions and 387 deletions.
4 changes: 2 additions & 2 deletions src/renderer/entities/wallet/ui/Cards/WalletCardLg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type ReactNode } from 'react';
import { type Wallet } from '@/shared/core';
import { useI18n } from '@/shared/i18n';
import { cnTw } from '@/shared/lib/utils';
import { FootnoteText, StatusLabel } from '@/shared/ui';
import { BodyText, FootnoteText, StatusLabel } from '@/shared/ui';
import { walletUtils } from '../../lib/wallet-utils';
import { WalletIcon } from '../WalletIcon/WalletIcon';

Expand Down Expand Up @@ -33,7 +33,7 @@ export const WalletCardLg = ({ wallet, description, full, className }: Props) =>
)}
</div>
<div className="flex min-w-0 flex-col">
<FootnoteText className="truncate text-text-primary">{wallet.name}</FootnoteText>
<BodyText className="truncate text-text-primary">{wallet.name}</BodyText>
{typeof description === 'string' ? (
<FootnoteText className="text-text-tertiary">{description}</FootnoteText>
) : (
Expand Down
52 changes: 32 additions & 20 deletions src/renderer/entities/wallet/ui/Cards/WalletCardMd.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { type MouseEvent, type ReactNode } from 'react';
import { type MouseEvent, type PropsWithChildren, type ReactNode } from 'react';

import { type Wallet } from '@/shared/core';
import { cnTw } from '@/shared/lib/utils';
import { FootnoteText, IconButton } from '@/shared/ui';
import { cnTw, nonNullable, nullable } from '@/shared/lib/utils';
import { BodyText, FootnoteText } from '@/shared/ui';
import { walletUtils } from '../../lib/wallet-utils';
import { WalletIcon } from '../WalletIcon/WalletIcon';

Expand All @@ -11,12 +11,17 @@ type Props = {
description?: string | ReactNode;
prefix?: ReactNode;
hideIcon?: boolean;
className?: string;
onClick?: () => void;
onInfoClick?: () => void;
};

export const WalletCardMd = ({ wallet, description, prefix, hideIcon, className, onClick, onInfoClick }: Props) => {
export const WalletCardMd = ({
wallet,
description,
prefix,
hideIcon,
children,
onClick,
}: PropsWithChildren<Props>) => {
const isWalletConnect = walletUtils.isWalletConnectGroup(wallet);

const handleClick = (fn?: () => void) => {
Expand All @@ -33,19 +38,28 @@ export const WalletCardMd = ({ wallet, description, prefix, hideIcon, className,
className={cnTw(
'group relative flex w-full items-center rounded transition-colors',
'focus-within:bg-action-background-hover hover:bg-action-background-hover',
className,
)}
>
<button
className={cnTw('flex w-full items-center gap-x-2 rounded px-2 py-1.5', { 'pe-6': onInfoClick })}
className={cnTw('flex w-full items-center gap-x-2 rounded px-2 py-1.5', {
'pointer-events-none': nullable(onClick),
'pr-6': nonNullable(children),
})}
onClick={handleClick(onClick)}
>
{prefix}

{!hideIcon && <WalletIcon type={wallet.type} size={20} />}
{!hideIcon && <WalletIcon type={wallet.type} size={20} className="shrink-0" />}
<div className="flex min-w-0 flex-col">
<div className="flex items-center gap-x-2">
<FootnoteText className="truncate text-text-primary">{wallet.name}</FootnoteText>
<BodyText
className={cnTw(
'truncate text-text-secondary transition-colors',
'group-focus-within:text-text-primary group-hover:text-text-primary',
)}
>
{wallet.name}
</BodyText>
{isWalletConnect && (
<span
className={cnTw(
Expand All @@ -63,16 +77,14 @@ export const WalletCardMd = ({ wallet, description, prefix, hideIcon, className,
</div>
</button>

{onInfoClick && (
<IconButton
className={cnTw(
'absolute right-2 opacity-0 transition-opacity',
'focus:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100',
)}
name="details"
onClick={handleClick(onInfoClick)}
/>
)}
<div
className={cnTw(
'absolute right-2 top-1/2 flex -translate-y-1/2 opacity-0 transition-opacity',
'focus:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100',
)}
>
{children}
</div>
</div>
);
};
1 change: 1 addition & 0 deletions src/renderer/entities/wallet/ui/Cards/WalletCardSm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const WalletCardSm = ({ wallet, className, iconSize = 16, onClick, onInfo
{wallet.name}
</FootnoteText>
</button>
{/* TODO: do the same as in WalletCardMd */}
<IconButton className="absolute right-2" name="details" size={16} onClick={handleClick(onInfoClick)} />
</div>
);
Expand Down
66 changes: 43 additions & 23 deletions src/renderer/entities/wallet/ui/ContactItem/ContactItem.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,87 @@
import { type MouseEvent } from 'react';
import { type PropsWithChildren } from 'react';

import { type AccountId, type Address, type KeyType } from '@/shared/core';
import { cnTw, toAddress } from '@/shared/lib/utils';
import { BodyText, HelpText, Icon, IconButton, Identicon } from '@/shared/ui';
import { cnTw, nonNullable, toAddress } from '@/shared/lib/utils';
import { BodyText, HelpText, Icon, Identicon } from '@/shared/ui';
import { Hash } from '@/shared/ui-entities';
import { KeyIcon } from '../../lib/constants';

type Props = {
type Props = PropsWithChildren<{
name?: string;
address: Address | AccountId;
addressPrefix?: number;
keyType?: KeyType;
size?: number;
iconSize?: number;
className?: string;
hideAddress?: boolean;
onInfoClick?: () => void;
};
}>;

export const ContactItem = ({
name,
address,
addressPrefix,
size = 20,
iconSize = 20,
hideAddress = false,
keyType,
className,
onInfoClick,
children,
}: Props) => {
const formattedAddress = toAddress(address, { prefix: addressPrefix });

const handleClick = (event: MouseEvent<HTMLDivElement>) => {
event.stopPropagation();
};

return (
<div className={cnTw('group flex w-full items-center gap-x-2', className)}>
<div className="flex w-full items-center gap-x-2 overflow-hidden py-[3px]" onClick={handleClick}>
<div
className={cnTw(
'group relative flex w-full items-center rounded transition-colors',
'focus-within:bg-action-background-hover hover:bg-action-background-hover',
)}
>
<div className={cnTw('flex w-full items-center gap-x-2 overflow-hidden py-1.5 pl-2', children ? 'pr-9' : 'pr-3')}>
<div className="flex">
<Identicon address={formattedAddress} size={size} background={false} />
<Identicon address={formattedAddress} size={iconSize} background={false} />

{keyType && (
<Icon
className="z-10 -ml-2.5 rounded-full border bg-white text-text-secondary"
size={size}
size={iconSize}
name={KeyIcon[keyType]}
/>
)}
</div>

<div className="flex flex-col">
{name && (
<div className="flex flex-col overflow-hidden">
{name ? (
<BodyText
className={cnTw(
'truncate text-text-secondary transition-colors',
'group-hover:text-text-primary group-focus:text-text-primary',
'group-focus-within:text-text-primary group-hover:text-text-primary',
)}
>
{name}
</BodyText>
) : (
<BodyText
className={cnTw(
'text-text-secondary transition-colors',
'group-focus-within:text-text-primary group-hover:text-text-primary',
)}
>
<Hash value={formattedAddress} variant="truncate" />
</BodyText>
)}

{nonNullable(name) && !hideAddress && (
<HelpText className="truncate text-text-tertiary">{formattedAddress}</HelpText>
)}
{!hideAddress && <HelpText className="truncate text-text-tertiary">{formattedAddress}</HelpText>}
</div>
</div>

<IconButton name="details" className="mx-1.5" onClick={onInfoClick} />
<div
className={cnTw(
'absolute right-2 top-1/2 flex -translate-y-1/2 opacity-0 transition-opacity',
'focus:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100',
)}
>
{children}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ const PopoverGroup = ({ title, active = true, children }: PropsWithChildren<Grou
);
};

/**
* @deprecated Use `import { AccountExplorers, RootExplorers } from
* '@/shared/ui-entities'` instead.
*/
export const ExplorersPopover = Object.assign(ExplorersPopoverRoot, {
Group: PopoverGroup,
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const MultishardAccountsList = ({ chains, accounts, className }: Props) =
button={
<ContactItem
className="bg-white py-4 pr-2"
size={28}
iconSize={28}
name={baseAccount.name}
address={baseAccount.accountId}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const balanceSubUtils = {

function getSiblingAccounts(wallet: Wallet, wallets: Wallet[], chains: Record<ChainId, Chain>): Account[] {
if (walletUtils.isMultisig(wallet)) {
const signatoriesMap = dictionary(wallet.accounts[0].signatories, 'accountId');
const signatoriesMap = dictionary(wallet.accounts[0].signatories, 'accountId', true);
const signatories = walletUtils.getAccountsBy(wallets, (account) => signatoriesMap[account.accountId]);

return wallet.accounts.concat(uniqBy(signatories, 'accountId') as MultisigAccount[]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type Chain } from '@/shared/core';
import { useI18n } from '@/shared/i18n';
import { BodyText } from '@/shared/ui';
import { RankedAccount } from '@/shared/ui-entities';
import { type Vote as VoteType } from '@/domains/collectives';
import { type CoreMember, type Vote as VoteType } from '@/domains/collectives';
import { identityModel } from '../model/identity';
import { membersModel } from '../model/members';

Expand All @@ -31,7 +31,7 @@ export const Vote = ({ item, chain }: Props) => {
return (
<RankedAccount
rank={member?.rank || 0}
isActive={member?.isActive || false}
isActive={(member as CoreMember)?.isActive || false}
name={identity?.name}
accountId={item.accountId}
chain={chain}
Expand Down
91 changes: 35 additions & 56 deletions src/renderer/features/wallet-details/model/wallet-details-model.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { combine } from 'effector';
import { createGate } from 'effector-react';
import { isEmpty } from 'lodash';
import uniqBy from 'lodash/uniqBy';

import {
type AccountId,
type ChainId,
type Contact,
type ProxyAccount,
type ProxyGroup,
type Signatory,
type Wallet,
} from '@/shared/core';
import { dictionary, nullable } from '@/shared/lib/utils';
Expand Down Expand Up @@ -54,62 +53,44 @@ const $multisigAccount = $wallet.map((wallet) => {
return wallet.accounts.at(0) ?? null;
});

const $signatoryContacts = combine(
const $signatories = combine(
{
account: $multisigAccount,
wallets: walletModel.$wallets,
contacts: contactModel.$contacts,
},
({ account, wallets, contacts }): Signatory[] => {
if (nullable(account)) return [];

const contactsMap = dictionary(contacts, 'accountId');
const signatoriesMap = dictionary(account.signatories, 'accountId');
const allSignatories = walletUtils.getAccountsBy(wallets, ({ accountId }) => signatoriesMap[accountId]);
const signatoriesSet = new Set(allSignatories.map((signatory) => signatory.accountId));

return account.signatories
.filter((signatory) => !signatoriesSet.has(signatory.accountId))
.map((signatory) => ({ ...signatory, name: contactsMap[signatory.accountId]?.name }));
},
);

const $signatoryWallets = combine(
{
account: $multisigAccount,
wallets: walletModel.$wallets,
},
({ account, wallets }): [AccountId, Wallet][] => {
if (nullable(account)) return [];

const signatoriesMap = dictionary(account.signatories, 'accountId', () => true);

const walletsAndAccounts = walletUtils.getWalletsFilteredAccounts(wallets, {
accountFn: (a) => signatoriesMap[a.accountId],
});

if (!walletsAndAccounts) return [];

return walletsAndAccounts.map((wallet) => [wallet.accounts[0].accountId, wallet]);
},
);

const $signatoryAccounts = combine(
{
account: $multisigAccount,
wallets: walletModel.$wallets,
},
({ account, wallets }): Signatory[] => {
if (nullable(account)) return [];

const signatoriesMap = dictionary(account.signatories, 'accountId');
const allSignatories = walletUtils.getAccountsBy(wallets, ({ accountId }) => signatoriesMap[accountId]);
const uniqueSignatories = uniqBy(allSignatories, 'accountId');
const uniqueSignatoriesMap = dictionary(uniqueSignatories, 'accountId');

return account.signatories
.filter((signatory) => uniqueSignatoriesMap[signatory.accountId])
.map((signatory) => ({ ...signatory, name: uniqueSignatoriesMap[signatory.accountId]?.name }));
({ account, wallets, contacts }): { wallets: [Wallet, AccountId][]; contacts: Contact[]; people: AccountId[] } => {
if (!account) {
return { wallets: [], contacts: [], people: [] };
}

const signatoriesMap = dictionary(account.signatories, 'accountId', true);

const walletSignatories: [Wallet, AccountId][] = [];
for (const wallet of wallets) {
if (walletUtils.isWatchOnly(wallet)) continue;

for (const account of wallet.accounts) {
if (!signatoriesMap[account.accountId]) continue;

delete signatoriesMap[account.accountId];
walletSignatories.push([wallet, account.accountId]);
}
}

const contactSignatories: Contact[] = [];
for (const contact of contacts) {
if (!signatoriesMap[contact.accountId]) continue;

contactSignatories.push(contact);
delete signatoriesMap[contact.accountId];
}

return {
wallets: walletSignatories,
contacts: contactSignatories,
people: Object.keys(signatoriesMap) as AccountId[],
};
},
);

Expand Down Expand Up @@ -178,9 +159,7 @@ export const walletDetailsModel = {

$vaultAccounts,
$multiShardAccounts,
$signatoryContacts,
$signatoryWallets,
$signatoryAccounts,
$signatories,

$chainsProxies,
$walletProxyGroups,
Expand Down
Loading

0 comments on commit 2c3f5f7

Please sign in to comment.