Skip to content

Commit

Permalink
feat(suite): Passphrase modal
Browse files Browse the repository at this point in the history
  • Loading branch information
jvaclavik committed Apr 26, 2024
1 parent 3590071 commit dafcfd0
Show file tree
Hide file tree
Showing 29 changed files with 1,250 additions and 598 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { FormattedMessage } from 'react-intl';
import styled from 'styled-components';
import { Image } from '../Image/Image';
import { DeviceModelInternal } from '@trezor/connect';
import { Card } from '../Card/Card';
import { Icon } from '../assets/Icon/Icon';
import { spacingsPx } from '@trezor/theme';

const Row = styled.div`
display: flex;
gap: ${spacingsPx.xs};
align-items: center;
justify-content: space-between;
`;

interface EnterOnTrezorButtonProps {
submit: (value: string, passphraseOnDevice?: boolean) => void;
isVisible: boolean;
value: string;
deviceModel?: DeviceModelInternal;
}

export const EnterOnTrezorButton = ({
submit,
isVisible,
value,
deviceModel,
}: EnterOnTrezorButtonProps) => {
if (!isVisible) return null;

return (
<Card
paddingType="small"
onClick={() => submit(value, true)}
data-test="@passphrase/enter-on-device-button"
>
<Row>
{deviceModel && <Image alt="Trezor" image={`TREZOR_${deviceModel}`} height={34} />}
<FormattedMessage
id="TR_ENTER_PASSPHRASE_ON_DEVICE"
defaultMessage="Enter passphrase on Trezor"
/>
<Icon icon="ARROW_RIGHT" />
</Row>
</Card>
);
};
325 changes: 164 additions & 161 deletions packages/components/src/components/Passphrase/PassphraseTypeCard.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,13 @@ export default meta;
export const Button: StoryObj<ButtonProps> = {
args: {
children: 'Button label',
margin: { top: undefined, right: undefined, bottom: undefined, left: undefined },
},
argTypes: {
margin: {
table: {
category: 'Frame props',
},
},
},
};
43 changes: 27 additions & 16 deletions packages/components/src/components/buttons/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ import {
} from '../buttonStyleUtils';
import { focusStyleTransition, getFocusShadowStyle } from '../../../utils/utils';
import { useElevation } from '../../ElevationContext/ElevationContext';
import { makePropsTransient } from '../../../utils/transientProps';
import { FrameProps, TransientFrameProps, withFrameProps } from '../../common/frameProps';

interface ButtonContainerProps {
type ButtonContainerProps = TransientFrameProps & {
$variant: ButtonVariant;
$size: ButtonSize;
$iconAlignment?: IconAlignment;
$hasIcon?: boolean;
$isFullWidth?: boolean;
$elevation: Elevation;
}
};

export const ButtonContainer = styled.button<ButtonContainerProps>`
display: flex;
Expand All @@ -49,6 +51,8 @@ export const ButtonContainer = styled.button<ButtonContainerProps>`
pointer-events: none;
cursor: default;
}
${withFrameProps}
`;

interface ContentProps {
Expand Down Expand Up @@ -80,20 +84,21 @@ type SelectedHTMLButtonProps = Pick<
'onClick' | 'onMouseOver' | 'onMouseLeave' | 'type' | 'tabIndex'
>;

export interface ButtonProps extends SelectedHTMLButtonProps {
variant?: ButtonVariant;
size?: ButtonSize;
isDisabled?: boolean;
isLoading?: boolean;
isFullWidth?: boolean;
icon?: IconType;
iconSize?: number;
iconAlignment?: IconAlignment;
children: React.ReactNode;
title?: string;
className?: string;
'data-test'?: string;
}
export type ButtonProps = SelectedHTMLButtonProps &
FrameProps & {
variant?: ButtonVariant;
size?: ButtonSize;
isDisabled?: boolean;
isLoading?: boolean;
isFullWidth?: boolean;
icon?: IconType;
iconSize?: number;
iconAlignment?: IconAlignment;
children: React.ReactNode;
title?: string;
className?: string;
'data-test'?: string;
};

export const Button = ({
variant = 'primary',
Expand All @@ -106,8 +111,13 @@ export const Button = ({
iconAlignment = 'left',
type = 'button',
children,
margin,
...rest
}: ButtonProps) => {
const frameProps = {
margin,
};

const theme = useTheme();
const { elevation } = useElevation();

Expand All @@ -132,6 +142,7 @@ export const Button = ({
$hasIcon={!!icon || isLoading}
$elevation={elevation}
{...rest}
{...makePropsTransient(frameProps)}
>
{!isLoading && icon && IconComponent}
{isLoading && Loader}
Expand Down
38 changes: 28 additions & 10 deletions packages/components/src/components/typography/Heading/Heading.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
import styled from 'styled-components';

import { typography } from '@trezor/theme';
import { TypographyStyle, typography } from '@trezor/theme';
import { FrameProps, TransientFrameProps, withFrameProps } from '../../common/frameProps';
import { makePropsTransient } from '../../../utils/transientProps';

const H1 = styled.h1`
${typography.titleLarge};
`;
type HeadingProps = TransientFrameProps & {
children: string;
$typographyStyle: TypographyStyle;
};

const H2 = styled.h2`
${typography.titleMedium};
`;
const Heading = styled.h1<HeadingProps>`
${({ $typographyStyle }) => typography[$typographyStyle]};
const H3 = styled.h3`
${typography.titleSmall};
${withFrameProps}
`;

export { H1, H2, H3 };
type HProps = FrameProps & {
children: React.ReactNode;
};

export const H1 = ({ margin, ...props }: HProps) => (
<Heading as="h1" {...makePropsTransient({ margin })} $typographyStyle="titleLarge" {...props} />
);
export const H2 = ({ margin, ...props }: HProps) => (
<Heading
as="h2"
{...makePropsTransient({ margin })}
$typographyStyle="titleMedium"
{...props}
/>
);
export const H3 = ({ margin, ...props }: HProps) => (
<Heading as="h3" {...makePropsTransient({ margin })} $typographyStyle="titleSmall" {...props} />
);
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { Sidebar } from './Sidebar/Sidebar';
import { CoinjoinBars } from './CoinjoinBars/CoinjoinBars';
import { MobileAccountsMenu } from 'src/components/wallet/WalletLayout/AccountsMenu/MobileAccountsMenu';
import { selectSelectedAccount } from 'src/reducers/wallet/selectedAccountReducer';
import { useAppShortcuts } from './utils';

export const SCROLL_WRAPPER_ID = 'layout-scroll';

export const Wrapper = styled.div`
display: flex;
flex: 1;
Expand Down Expand Up @@ -100,6 +100,8 @@ export const SuiteLayout = ({ children }: SuiteLayoutProps) => {

const isAccountPage = !!selectedAccount;

useAppShortcuts();

return (
<ElevationContext baseElevation={-1}>
<Wrapper ref={wrapperRef} data-test="@suite-layout">
Expand Down
32 changes: 31 additions & 1 deletion packages/suite/src/components/suite/layouts/SuiteLayout/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,39 @@
import { createDeviceInstance, selectDevice } from '@suite-common/wallet-core';
import { useEvent } from 'react-use';
import { goto } from 'src/actions/suite/routerActions';
import { useCustomBackends } from 'src/hooks/settings/backends';
import { useSelector } from 'src/hooks/suite';
import { useDispatch, useSelector } from 'src/hooks/suite';

export const useEnabledBackends = () => {
const enabledNetworks = useSelector(state => state.wallet.settings.enabledNetworks);
const customBackends = useCustomBackends();

return customBackends.filter(backend => enabledNetworks.includes(backend.coin));
};

export const useAppShortcuts = () => {
const device = useSelector(selectDevice);
const dispatch = useDispatch();

useEvent('keydown', e => {
const modKey = e.metaKey; // CMD or Ctrl key

// press CMD + P to show PassphraseModal
if (modKey && e.key === 'p' && device) {
dispatch(createDeviceInstance({ device }));
e.preventDefault(); // prevent default behaviour
}

// press CMD + D to show SwitchDevice
if (modKey && e.key === 'd' && device) {
dispatch(
goto('suite-switch-device', {
params: {
cancelable: true,
},
}),
);
e.preventDefault(); // prevent default behaviour
}
});
};
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import styled from 'styled-components';
import { Spinner } from '@trezor/components';
import { Translation, Modal } from 'src/components/suite';
import { H3, Spinner, Text } from '@trezor/components';
import { Translation } from 'src/components/suite';
import { CardWithDevice } from 'src/views/suite/SwitchDevice/CardWithDevice';
import { SwitchDeviceRenderer } from 'src/views/suite/SwitchDevice/SwitchDeviceRenderer';
import { useSelector } from 'src/hooks/suite';
import { selectDevice } from '@suite-common/wallet-core';

const Expand = styled.div`
display: flex;
flex-direction: column;
width: 100%;
justify-content: center;
align-items: center;
margin: 40px 0;
`;

const StyledModal = styled(Modal)`
width: 360px;
`;
export const DiscoveryLoader = () => {
const device = useSelector(selectDevice);
if (!device) return null;

export const DiscoveryLoader = () => (
<StyledModal
heading={<Translation id="TR_COIN_DISCOVERY_IN_PROGRESS" />}
description={<Translation id="TR_TO_FIND_YOUR_ACCOUNTS_AND" />}
data-test="@discovery/loader"
>
<Expand>
<Spinner size={80} isGrey={false} />
</Expand>
</StyledModal>
);
return (
<SwitchDeviceRenderer isCancelable={false} data-test="@discovery/loader">
<CardWithDevice device={device}>
<Expand>
<Spinner size={80} isGrey={false} />
<H3>
<Translation id="TR_COIN_DISCOVERY_IN_PROGRESS" />
</H3>
<Text color="textSubdued">
<Translation id="TR_TO_FIND_YOUR_ACCOUNTS_AND" />
</Text>
</Expand>
</CardWithDevice>
</SwitchDeviceRenderer>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import styled from 'styled-components';
import { Spinner } from '@trezor/components';
import { Translation, Modal } from 'src/components/suite';

const Expand = styled.div`
display: flex;
width: 100%;
justify-content: center;
margin: 40px 0;
`;

const StyledModal = styled(Modal)`
width: 360px;
`;

export const DiscoveryLoaderLegacy = () => (
<StyledModal
heading={<Translation id="TR_COIN_DISCOVERY_IN_PROGRESS" />}
description={<Translation id="TR_TO_FIND_YOUR_ACCOUNTS_AND" />}
data-test="@discovery/loader"
>
<Expand>
<Spinner size={80} isGrey={false} />
</Expand>
</StyledModal>
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ import { usePreferredModal } from 'src/hooks/suite/usePreferredModal';
import { ReduxModal } from '../ReduxModal/ReduxModal';
import { ForegroundAppModal } from './ForegroundAppModal';
import { DiscoveryLoader } from './DiscoveryLoader';
import { useSelector } from 'src/hooks/suite';
import { DiscoveryLoaderLegacy } from './DiscoveryLoaderLegacy';

/** Displays whichever redux modal or foreground app should be displayed */
export const ModalSwitcher = () => {
const isViewOnlyModeVisible = useSelector(
state => state.suite.settings.debug.isViewOnlyModeVisible,
);
const modal = usePreferredModal();

// return <DiscoveryLoader />; // @TODO remove
switch (modal.type) {
case 'foreground-app':
return <ForegroundAppModal {...modal.payload} />;
case 'redux-modal':
return <ReduxModal {...modal.payload} />;
case 'discovery-loading':
return <DiscoveryLoader />;
return isViewOnlyModeVisible ? <DiscoveryLoader /> : <DiscoveryLoaderLegacy />;
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
PinModal,
PinInvalidModal,
PassphraseModal,
PassphraseModalLegacy,
PassphraseSourceModal,
PassphraseOnDeviceModal,
ConfirmActionModal,
Expand All @@ -29,6 +30,9 @@ export const DeviceContextModal = ({
}: ReduxModalProps<typeof MODAL.CONTEXT_DEVICE>) => {
const device = useSelector(selectDevice);
const intl = useIntl();
const isViewOnlyModeVisible = useSelector(
state => state.suite.settings.debug.isViewOnlyModeVisible,
);

if (!device) return null;

Expand All @@ -44,7 +48,11 @@ export const DeviceContextModal = ({

// Passphrase on host
case UI.REQUEST_PASSPHRASE:
return <PassphraseModal device={device} />;
return isViewOnlyModeVisible ? (
<PassphraseModal device={device} />
) : (
<PassphraseModalLegacy device={device} />
);

case 'WordRequestType_Plain':
return <WordModal renderer={renderer} />;
Expand Down
Loading

0 comments on commit dafcfd0

Please sign in to comment.