Skip to content

Commit

Permalink
Merge pull request PolkaGate#1386 from PolkaGate/profile
Browse files Browse the repository at this point in the history
Add account Profile
  • Loading branch information
Nick-1979 authored Jun 25, 2024
2 parents a06a27d + 50988fc commit cb9f200
Show file tree
Hide file tree
Showing 26 changed files with 945 additions and 111 deletions.
1 change: 1 addition & 0 deletions packages/extension-base/src/background/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface AccountJson extends KeyringPair$Meta {
balances?: string;
identities?: string;
isQR?: boolean;
profile?: string;
stakingAccount?: string;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/extension-polkagate/src/components/Identity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { getSubstrateAddress, isValidAddress } from '../util/utils';
import { ChainLogo, Identicon, Infotip, ShortAddress } from '.';

interface Props {
accountInfo?: DeriveAccountInfo;
accountInfo?: DeriveAccountInfo | null;
address?: string | AccountId;
api?: ApiPromise;
chain?: Chain;
Expand Down
10 changes: 5 additions & 5 deletions packages/extension-polkagate/src/components/MenuItem.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

/* eslint-disable react/jsx-max-props-per-line */

import { ArrowForwardIos as ArrowForwardIosIcon } from '@mui/icons-material';
import { Box, Grid, type SxProps, type Theme, Typography } from '@mui/material';
import React, { MouseEventHandler } from 'react';
import React, { type MouseEventHandler } from 'react';

import { noop } from '../util/utils';

Expand All @@ -17,14 +16,15 @@ interface Props {
text: string;
children?: React.ReactElement<Props>;
onClick?: MouseEventHandler<HTMLDivElement>;
showChevron?: boolean;
showSubMenu?: boolean;
py?: string;
fontSize?: string;
pl?: string;
withHoverEffect?: boolean;
}

export default function MenuItem({ children, disabled = false, fontSize, icon, iconComponent, onClick, pl = '0', py = '8px', showSubMenu = false, text, withHoverEffect }: Props): React.ReactElement<Props> {
export default function MenuItem({ children, disabled = false, fontSize, icon, iconComponent, onClick, pl = '0', py = '8px',showChevron, showSubMenu = false, text, withHoverEffect }: Props): React.ReactElement<Props> {
const hoverEffectStyles: SxProps<Theme> = {
'&:hover': { bgcolor: disabled ? 'none' : 'divider' },
borderRadius: '5px',
Expand Down Expand Up @@ -57,8 +57,8 @@ export default function MenuItem({ children, disabled = false, fontSize, icon, i
</Typography>
</Grid>
</Grid>
<Grid alignItems='center' container item sx={{ display: children ? 'inherit' : 'none' }} xs={1}>
<ArrowForwardIosIcon sx={{ color: 'secondary.light', fontSize: 18, m: 'auto', stroke: '#BA2882', strokeWidth: '2px', transform: showSubMenu ? 'rotate(-90deg)' : 'rotate(90deg)', transitionDuration: '0.3s', transitionProperty: 'transform' }} />
<Grid alignItems='center' container item sx={{ display: children || showChevron ? 'inherit' : 'none' }} xs={1}>
<ArrowForwardIosIcon sx={{ color: 'secondary.light', fontSize: 18, m: 'auto', stroke: '#BA2882', strokeWidth: '2px', transform: showChevron ? 'none' : (showSubMenu ? 'rotate(-90deg)' : 'rotate(90deg)'), transitionDuration: '0.3s', transitionProperty: 'transform' }} />
</Grid>
</Grid>
{
Expand Down
109 changes: 109 additions & 0 deletions packages/extension-polkagate/src/components/ProfileInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { Autocomplete, Grid, type SxProps, TextField, type Theme, Typography } from '@mui/material';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import Label from './Label';
import { useProfiles } from '../hooks';

interface Props {
disabled?: boolean;
helperText?: string;
label: string;
placeHolder?: string;
profileName: string | undefined;
setProfileName: React.Dispatch<React.SetStateAction<string | undefined>>;
style?: SxProps<Theme>;
}

export default function ProfileInput({ disabled = false, placeHolder = '', setProfileName, profileName, helperText = '', label, style }: Props): React.ReactElement<Props> {
const containerRef = useRef<HTMLDivElement>(null);

const profiles = useProfiles();

const [isPopperOpen, setTogglePopper] = useState<boolean>(false);
const [focus, setFocus] = useState<boolean>(false);
const [enteredProfile, setEnteredProfile] = useState<string | undefined>();
const [dropdownWidth, setDropdownWidth] = useState<string>('0');

const autocompleteOptions = useMemo(() => profiles?.userDefinedProfiles?.map((profile, index) => ({ index, profile })), [profiles?.userDefinedProfiles]);

useEffect(() => {
profileName && setEnteredProfile(profileName);
}, [profileName]);

useEffect(() => {
if (containerRef) {
setDropdownWidth(`${(containerRef.current?.offsetWidth || 0)}px`);
}
}, [containerRef?.current?.offsetWidth]);

const handleProfile = useCallback((value?: string): void => {
setTogglePopper(false);

if (!value) {
setProfileName(undefined);
setEnteredProfile(undefined);

return;
}

setEnteredProfile(value);
setProfileName(value)
}, [setProfileName]);

const openPopper = useCallback(() =>
profiles?.userDefinedProfiles && profiles.userDefinedProfiles?.length > 0 && !enteredProfile && !isPopperOpen && setTogglePopper(true)
, [profiles?.userDefinedProfiles?.length, enteredProfile, isPopperOpen]);

const closePopper = useCallback(() =>
setTogglePopper(false),
[]);

return (
<Grid alignItems='flex-end' container justifyContent='space-between' ref={containerRef} sx={{ position: 'relative', ...style }}>
<Label
helperText={helperText}
label={label}
style={{ position: 'relative', width: '100%' }}
>
<Autocomplete
componentsProps={{ paper: { sx: { '> ul': { m: 0, p: 0 }, border: '2px solid', borderColor: 'secondary.light', maxHeight: window.innerHeight / 2, ml: '-1px', my: '5px', p: 0, width: dropdownWidth } } }}
disableClearable
disabled={disabled || !autocompleteOptions}
freeSolo
getOptionLabel={(option) => option?.toString() as any}
inputValue={enteredProfile ?? ''}
onBlur={() => setFocus(false)}
onClose={closePopper}
onFocus={() => setFocus(true)}
onOpen={openPopper}
open={isPopperOpen && !enteredProfile}
options={autocompleteOptions as any}
renderInput={(params) => (
<TextField
{...params}
InputProps={{
...params.InputProps,
}}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => handleProfile(event.target.value)}
placeholder={placeHolder}
sx={{ '> div.MuiOutlinedInput-root': { '> fieldset': { border: 'none' }, '> input.MuiAutocomplete-input': { border: 'none', lineHeight: '31px', p: 0 }, border: 'none', height: '31px', p: 0, px: '5px' }, bgcolor: 'background.paper', border: `${focus ? '2px' : '1px'} solid`, borderColor: `${focus ? 'action.focus' : 'secondary.light'}`, borderRadius: '5px', height: '32px', lineHeight: '31px' }}
/>
)}
// @ts-ignore
renderOption={(_props, { index, profile }) => {
return (
<Grid alignItems='center' container item justifyContent='space-between' key={index} onClick={() => handleProfile(profile)} sx={{ '&:not(:last-child)': { borderBottom: '1px solid', borderBottomColor: 'secondary.light', mb: '5px' }, cursor: 'pointer', p: '5px' }}>
<Typography fontSize='12px' fontWeight={400} lineHeight='25px' overflow='hidden' textOverflow='ellipsis' whiteSpace='nowrap'>
{profile}
</Typography>
</Grid>);
}}
sx={{ border: 'none', height: '31px', p: 0 }}
/>
</Label>
</Grid>
);
}
3 changes: 1 addition & 2 deletions packages/extension-polkagate/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck
/* eslint-disable header/header */

export { default as AccountNamePasswordCreation } from './AccountNamePasswordCreation';
export { default as AccountHolderWithProxy } from './AccountHolderWithProxy';
Expand Down Expand Up @@ -95,5 +93,6 @@ export { default as FullScreenIcon } from './FullScreenIcon';
export { default as OptionalCopyButton } from './OptionalCopyButton';
export { default as Waiting } from './Waiting';
export { default as VaadinIcon } from './VaadinIcon';
export { default as ProfileInput } from './ProfileInput';

export * from './contexts';
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { IDENTITY_CHAINS, PROXY_CHAINS, SOCIAL_RECOVERY_CHAINS } from '../../../

interface AddressDetailsProps {
address: string | undefined;
accountInfo: DeriveAccountInfo | undefined
accountInfo: DeriveAccountInfo | undefined | null
}

export default function AccountIconsFs({ accountInfo, address }: AddressDetailsProps): React.ReactElement {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

/* eslint-disable react/jsx-max-props-per-line */

Expand All @@ -11,7 +10,7 @@ import { BN } from '@polkadot/util';

import { DisplayLogo, FormatBalance2, FormatPrice, Identicon, Identity, Infotip, Infotip2, OptionalCopyButton, ShortAddress2, VaadinIcon } from '../../../components';
import { useIdentity, useInfo, useTranslation } from '../../../hooks';
import { FetchedBalance } from '../../../hooks/useAssetsBalances';
import type { FetchedBalance } from '../../../hooks/useAssetsBalances';
import { showAccount, tieAccount } from '../../../messaging';
import { getValue } from '../../../popup/account/util';
import { BALANCES_VALIDITY_PERIOD } from '../../../util/constants';
Expand Down Expand Up @@ -129,9 +128,21 @@ interface AddressDetailsProps {
setAssetIdOnAssetHub: React.Dispatch<React.SetStateAction<number | undefined>>;
}

export default function AccountInformationForDetails({ accountAssets, address, label, price, pricesInCurrency, selectedAsset, setAssetIdOnAssetHub, setSelectedAsset }: AddressDetailsProps): React.ReactElement {
export const EyeIconFullScreen = ({ isHidden, onClick }: { isHidden: boolean | undefined, onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined }) => {
const { t } = useTranslation();
const theme = useTheme();

return (
<Infotip text={isHidden ? t('This account is hidden from websites') : t('This account is visible to websites')}>
<IconButton onClick={onClick} sx={{ height: '20px', ml: '7px', mt: '13px', p: 0, width: '28px' }}>
<VaadinIcon icon={isHidden ? 'vaadin:eye-slash' : 'vaadin:eye'} style={{ color: `${theme.palette.secondary.light}`, height: '20px' }} />
</IconButton>
</Infotip>
)
}

export default function AccountInformationForDetails({ accountAssets, address, label, price, pricesInCurrency, selectedAsset, setAssetIdOnAssetHub, setSelectedAsset }: AddressDetailsProps): React.ReactElement {
const theme = useTheme();
const { account, api, chain, formatted, genesisHash, token } = useInfo(address);

const accountInfo = useIdentity(genesisHash, formatted);
Expand Down Expand Up @@ -160,7 +171,7 @@ export default function AccountInformationForDetails({ accountAssets, address, l
if (!sortedAccountAssets) {
return sortedAccountAssets; // null or undefined!
} else {
return sortedAccountAssets.filter((_asset) => !getValue('total', _asset)?.isZero());
return sortedAccountAssets.filter((_asset) => !getValue('total', _asset as unknown as BalancesInfo)?.isZero());
}
}, [sortedAccountAssets]);

Expand Down Expand Up @@ -217,11 +228,10 @@ export default function AccountInformationForDetails({ accountAssets, address, l
// subIdOnly
/>
<Grid item width='40px'>
<Infotip text={account?.isHidden && t('This account is hidden from websites')}>
<IconButton onClick={toggleVisibility} sx={{ height: '20px', ml: '7px', mt: '13px', p: 0, width: '28px' }}>
<VaadinIcon icon={account?.isHidden ? 'vaadin:eye-slash' : 'vaadin:eye'} style={{ color: `${theme.palette.secondary.light}`, height: '20px' }} />
</IconButton>
</Infotip>
<EyeIconFullScreen
isHidden={account?.isHidden}
onClick={toggleVisibility}
/>
</Grid>
</Grid>
<Grid alignItems='center' container item>
Expand All @@ -234,7 +244,7 @@ export default function AccountInformationForDetails({ accountAssets, address, l
</Grid>
</Grid>
<SelectedAssetBox
balanceToShow={selectedAsset}
balanceToShow={selectedAsset as unknown as BalancesInfo}
genesisHash={genesisHash}
isBalanceOutdated={isBalanceOutdated}
isPriceOutdated={!!isPriceOutdated}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ import ReservedDisplayBalance from './components/ReservedDisplayBalance';
import LockedInReferenda from './unlock/Review';
import { AccountInformationForDetails, AccountSetting, AssetSelect, CommonTasks, DisplayBalance, ExternalLinks, LockedBalanceDisplay, TotalChart } from './components';

export const popupNumbers = {
LOCKED_IN_REFERENDA: 1,
FORGET_ACCOUNT: 2,
RENAME: 3,
EXPORT_ACCOUNT: 4,
DERIVE_ACCOUNT: 5,
RECEIVE: 6,
HISTORY: 7
export enum popupNumbers {
LOCKED_IN_REFERENDA,
FORGET_ACCOUNT,
RENAME,
EXPORT_ACCOUNT,
DERIVE_ACCOUNT,
RECEIVE,
HISTORY
};

export interface UnlockInformationType {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

/* eslint-disable react/jsx-max-props-per-line */

import type { AccountWithChildren } from '@polkadot/extension-base/background/types';

import { Grid } from '@mui/material';
import React, { useContext, useEffect, useState } from 'react';

import { AccountWithChildren } from '@polkadot/extension-base/background/types';

import { AccountContext, ActionContext } from '../../components';
import { useAccountsOrder, useFullscreen } from '../../hooks';
import { useAccountsOrder, useFullscreen, useProfileAccounts } from '../../hooks';
import { AddNewAccountButton } from '../../partials';
import { FullScreenHeader } from '../governance/FullScreenHeader';
import HeaderComponents from './components/HeaderComponents';
import DraggableAccountsList from './partials/DraggableAccountList';
import HomeMenu from './partials/HomeMenu';
import TotalBalancePieChart from './partials/TotalBalancePieChart';
import WatchList, { AssetsWithUiAndPrice } from './partials/WatchList';
import WatchList, { type AssetsWithUiAndPrice } from './partials/WatchList';
import ProfileTabs from './partials/ProfileTabs';

export interface AccountsOrder {
id: number,
Expand All @@ -26,13 +26,15 @@ export interface AccountsOrder {

export default function HomePageFullScreen(): React.ReactElement {
useFullscreen();
const initialAccountList = useAccountsOrder(true) as AccountsOrder[];
const initialAccountList = useAccountsOrder(true) as AccountsOrder[] | undefined;
const onAction = useContext(ActionContext);
const { accounts: accountsInExtension } = useContext(AccountContext);

const [hideNumbers, setHideNumbers] = useState<boolean>();
const [groupedAssets, setGroupedAssets] = useState<AssetsWithUiAndPrice[] | undefined>();

const profileAccounts = useProfileAccounts(initialAccountList) as AccountsOrder[] | undefined;

useEffect(() => {
if (accountsInExtension && accountsInExtension?.length === 0) {
onAction('/onboarding');
Expand All @@ -51,34 +53,39 @@ export default function HomePageFullScreen(): React.ReactElement {
noAccountDropDown
noChainSwitch
/>
<Grid container item justifyContent='space-around' sx={{ bgcolor: 'backgroundFL.secondary', height: 'calc(100vh - 70px)', maxWidth: '1282px', overflow: 'scroll', py: '40px' }}>
<Grid container direction='column' item rowGap='20px' width='760px'>
{initialAccountList &&
<DraggableAccountsList
hideNumbers={hideNumbers}
initialAccountList={initialAccountList}
/>
}
{initialAccountList && initialAccountList?.length <= 2 &&
<AddNewAccountButton />
}
</Grid>
<Grid container direction='column' item rowGap='20px' width='fit-content'>
<Grid container item width='fit-content'>
<TotalBalancePieChart
hideNumbers={hideNumbers}
setGroupedAssets={setGroupedAssets}
/>
<Grid container item sx={{ bgcolor: 'backgroundFL.secondary', maxWidth: '1282px' }}>
<Grid container item justifyContent='space-around' sx={{ bgcolor: 'backgroundFL.secondary', height: 'calc(100vh - 70px)', maxWidth: '1282px', overflow: 'scroll', pb: '40px' }}>
<ProfileTabs
orderedAccounts={initialAccountList}
/>
<Grid container direction='column' item rowGap='20px' width='760px'>
{profileAccounts &&
<DraggableAccountsList
hideNumbers={hideNumbers}
initialAccountList={profileAccounts}
/>
}
{initialAccountList && initialAccountList?.length <= 2 &&
<AddNewAccountButton />
}
</Grid>
{groupedAssets && groupedAssets?.length > 0 &&
<Grid container direction='column' item rowGap='20px' width='fit-content'>
<Grid container item width='fit-content'>
<WatchList
groupedAssets={groupedAssets}
<TotalBalancePieChart
hideNumbers={hideNumbers}
setGroupedAssets={setGroupedAssets}
/>
</Grid>
}
<Grid container item width='fit-content'>
<HomeMenu />
{groupedAssets && groupedAssets?.length > 0 &&
<Grid container item width='fit-content'>
<WatchList
groupedAssets={groupedAssets}
/>
</Grid>
}
<Grid container item width='fit-content'>
<HomeMenu />
</Grid>
</Grid>
</Grid>
</Grid>
Expand Down
Loading

0 comments on commit cb9f200

Please sign in to comment.