Merge pull request #508 from ente-io/recover-modal-redesign

Recovery key modal redesign
This commit is contained in:
Abhinav Kumar 2022-05-13 17:56:20 +05:30 committed by GitHub
commit 9658fb041f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 201 additions and 183 deletions

View file

@ -1,86 +0,0 @@
import styled from 'styled-components';
import { FreeFlowText, IconButton } from './Container';
import CopyIcon from './icons/CopyIcon';
import React, { useState } from 'react';
import { Tooltip, OverlayTrigger } from 'react-bootstrap';
import TickIcon from '@mui/icons-material/Done';
import EnteSpinner from './EnteSpinner';
const Wrapper = styled.div`
position: relative;
margin: 20px 0;
`;
const CopyButtonWrapper = styled(IconButton)`
position: absolute;
top: 0px;
right: 0px;
background: none !important;
margin: 10px;
`;
export const CodeWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
background: #1a1919;
padding: 37px 40px 20px 20px;
color: white;
width: 100%;
`;
type Iprops = React.PropsWithChildren<{
code: string;
wordBreak?: 'normal' | 'break-all' | 'keep-all' | 'break-word';
}>;
export const CodeBlock = (props: Iprops) => {
const [copied, setCopied] = useState<boolean>(false);
const copyToClipboardHelper = (text: string) => () => {
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1000);
};
const RenderCopiedMessage = (props) => {
const { style, ...rest } = props;
return (
<Tooltip
{...rest}
style={{ ...style, zIndex: 2001 }}
id="button-tooltip">
copied
</Tooltip>
);
};
return (
<Wrapper>
<CodeWrapper>
{props.code ? (
<FreeFlowText style={{ wordBreak: props.wordBreak }}>
{props.code}
</FreeFlowText>
) : (
<EnteSpinner />
)}
</CodeWrapper>
{props.code && (
<OverlayTrigger
show={copied}
placement="bottom"
trigger={'click'}
overlay={RenderCopiedMessage}
delay={{ show: 200, hide: 800 }}>
<CopyButtonWrapper
onClick={copyToClipboardHelper(props.code)}
style={{
background: 'none',
...(copied ? { color: '#51cd7c' } : {}),
}}>
{copied ? <TickIcon /> : <CopyIcon />}
</CopyButtonWrapper>
</OverlayTrigger>
)}
</Wrapper>
);
};

View file

@ -0,0 +1,20 @@
import React from 'react';
import constants from 'utils/strings/constants';
import { CopyButtonWrapper } from './styledComponents';
import DoneIcon from '@mui/icons-material/Done';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import { Tooltip } from '@mui/material';
export default function CopyButton({ code, copied, copyToClipboardHelper }) {
return (
<Tooltip arrow open={copied} title={constants.COPIED}>
<CopyButtonWrapper onClick={copyToClipboardHelper(code)}>
{copied ? (
<DoneIcon fontSize="small" />
) : (
<ContentCopyIcon fontSize="small" />
)}
</CopyButtonWrapper>
</Tooltip>
);
}

View file

@ -0,0 +1,43 @@
import { FreeFlowText } from '../Container';
import React, { useState } from 'react';
import EnteSpinner from '../EnteSpinner';
import { Wrapper, CodeWrapper } from './styledComponents';
import CopyButton from './CopyButton';
type Iprops = React.PropsWithChildren<{
code: string;
wordBreak?: 'normal' | 'break-all' | 'keep-all' | 'break-word';
}>;
// comment
export const CodeBlock = (props: Iprops) => {
const [copied, setCopied] = useState<boolean>(false);
const copyToClipboardHelper = (text: string) => () => {
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1000);
};
if (!props.code) {
return (
<Wrapper>
<EnteSpinner />
</Wrapper>
);
}
return (
<Wrapper>
<CodeWrapper>
<FreeFlowText style={{ wordBreak: props.wordBreak }}>
{props.code}
</FreeFlowText>
</CodeWrapper>
<CopyButton
code={props.code}
copied={copied}
copyToClipboardHelper={copyToClipboardHelper}
/>
</Wrapper>
);
};

View file

@ -0,0 +1,21 @@
import { IconButton } from '@mui/material';
import { CenteredFlex } from 'components/Container';
import styled from 'styled-components';
export const Wrapper = styled(CenteredFlex)`
position: relative;
background: ${({ theme }) => theme.palette.accent.dark};
border-radius: ${({ theme }) => theme.shape.borderRadius}px;
min-height: 80px;
`;
export const CopyButtonWrapper = styled(IconButton)`
position: absolute;
top: 0px;
right: 0px;
margin: ${({ theme }) => theme.spacing(1)};
`;
export const CodeWrapper = styled.div`
padding: 36px 36px 16px 16px;
border-radius: ${({ theme }) => theme.shape.borderRadius}px;
`;

View file

@ -175,12 +175,11 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
disablePadding: true,
'aria-labelledby': 'collection-options',
}}>
<Paper sx={{ borderRadius: '10px' }}>
<Paper>
<MenuList
sx={{
padding: 0,
border: 'none',
borderRadius: '8px',
}}>
<MenuItem>
<ListItem onClick={showRenameCollectionModal}>

View file

@ -1,20 +1,6 @@
import React from 'react';
import { Spinner } from 'react-bootstrap';
import CircularProgress from '@mui/material/CircularProgress';
export default function EnteSpinner(props) {
const { style, ...others } = props ?? {};
return (
<Spinner
animation="border"
style={{
width: '36px',
height: '36px',
borderWidth: '0.20em',
color: '#51cd7c',
...(style && style),
}}
{...others}
role="status"
/>
);
return <CircularProgress color="accent" size={32} {...props} />;
}

View file

@ -0,0 +1,74 @@
import React, { useEffect, useState } from 'react';
import { downloadAsFile } from 'utils/file';
import { getRecoveryKey } from 'utils/crypto';
import constants from 'utils/strings/constants';
import MessageDialog from '../MessageDialog';
import { CodeBlock } from '../CodeBlock';
import { ButtonProps, Typography } from '@mui/material';
import * as bip39 from 'bip39';
import { DashedBorderWrapper } from './styledComponents';
// mobile client library only supports english.
bip39.setDefaultWordlist('english');
interface Props {
show: boolean;
onHide: () => void;
somethingWentWrong: any;
}
function RecoveryKey({ somethingWentWrong, ...props }: Props) {
const [recoveryKey, setRecoveryKey] = useState(null);
useEffect(() => {
if (!props.show) {
return;
}
const main = async () => {
try {
const recoveryKey = await getRecoveryKey();
setRecoveryKey(bip39.entropyToMnemonic(recoveryKey));
} catch (e) {
somethingWentWrong();
props.onHide();
}
};
main();
}, [props.show]);
function onSaveClick() {
downloadAsFile(constants.RECOVERY_KEY_FILENAME, recoveryKey);
props.onHide();
}
const recoveryKeyDialogAttributes = {
title: constants.RECOVERY_KEY,
close: {
text: constants.SAVE_LATER,
variant: 'secondary' as ButtonProps['color'],
},
staticBackdrop: true,
proceed: {
text: constants.SAVE,
action: onSaveClick,
disabled: !recoveryKey,
variant: 'accent' as ButtonProps['color'],
},
};
return (
<MessageDialog
show={props.show}
onHide={props.onHide}
size="sm"
attributes={recoveryKeyDialogAttributes}>
<Typography mb={3}>{constants.RECOVERY_KEY_DESCRIPTION}</Typography>
<DashedBorderWrapper>
<CodeBlock code={recoveryKey} />
<Typography m={2}>
{constants.KEY_NOT_STORED_DISCLAIMER}
</Typography>
</DashedBorderWrapper>
</MessageDialog>
);
}
export default RecoveryKey;

View file

@ -0,0 +1,6 @@
import { Box, styled } from '@mui/material';
export const DashedBorderWrapper = styled(Box)(({ theme }) => ({
border: `1px dashed ${theme.palette.grey.A400}`,
borderRadius: theme.spacing(1),
}));

View file

@ -1,66 +0,0 @@
import React, { useEffect, useState } from 'react';
import { downloadAsFile } from 'utils/file';
import { getRecoveryKey } from 'utils/crypto';
import constants from 'utils/strings/constants';
import MessageDialog from './MessageDialog';
import { CodeBlock } from './CodeBlock';
const bip39 = require('bip39');
// mobile client library only supports english.
bip39.setDefaultWordlist('english');
interface Props {
show: boolean;
onHide: () => void;
somethingWentWrong: any;
}
function RecoveryKeyModal({ somethingWentWrong, ...props }: Props) {
const [recoveryKey, setRecoveryKey] = useState(null);
useEffect(() => {
if (!props.show) {
return;
}
const main = async () => {
try {
const recoveryKey = await getRecoveryKey();
setRecoveryKey(bip39.entropyToMnemonic(recoveryKey));
} catch (e) {
somethingWentWrong();
props.onHide();
}
};
main();
}, [props.show]);
function onSaveClick() {
downloadAsFile(constants.RECOVERY_KEY_FILENAME, recoveryKey);
onClose();
}
function onClose() {
props.onHide();
}
return (
<MessageDialog
show={props.show}
onHide={onClose}
size="lg"
attributes={{
title: constants.RECOVERY_KEY,
close: {
text: constants.SAVE_LATER,
variant: 'danger',
},
staticBackdrop: true,
proceed: {
text: constants.SAVE,
action: onSaveClick,
disabled: !recoveryKey,
variant: 'success',
},
}}>
<p>{constants.RECOVERY_KEY_DESCRIPTION}</p>
<CodeBlock code={recoveryKey} />
<p>{constants.KEY_NOT_STORED_DISCLAIMER}</p>
</MessageDialog>
);
}
export default RecoveryKeyModal;

View file

@ -21,7 +21,6 @@ export default function SubscriptionDetails({ userDetails }: Iprops) {
flexDirection={'column'}
height={160}
bgcolor="accent.main"
borderRadius={'8px'}
position={'relative'}>
{userDetails ? (
<>
@ -61,7 +60,6 @@ export default function SubscriptionDetails({ userDetails }: Iprops) {
position={'relative'}
zIndex="100"
height="64px"
borderRadius={'0 0 8px 8px'}
bgcolor="accent.dark"
padding="16px">
<LinearProgress

View file

@ -2,7 +2,7 @@ import React, { useContext, useState } from 'react';
import SidebarButton from './Button';
import constants from 'utils/strings/constants';
import FixLargeThumbnails from 'components/FixLargeThumbnail';
import RecoveryKeyModal from 'components/RecoveryKeyModal';
import RecoveryKey from 'components/RecoveryKey';
import TwoFactorModal from 'components/TwoFactorModal';
import { PAGES } from 'constants/pages';
import { useRouter } from 'next/router';
@ -65,7 +65,7 @@ export default function UtilitySection({ closeSidebar }) {
{constants.COMPRESS_THUMBNAILS}
</SidebarButton>
<RecoveryKeyModal
<RecoveryKey
show={recoverModalView}
onHide={closeRecoveryKeyModal}
somethingWentWrong={somethingWentWrong}

View file

@ -11,7 +11,7 @@ import {
} from 'utils/crypto';
import SetPasswordForm from 'components/SetPasswordForm';
import { justSignedUp, setJustSignedUp } from 'utils/storage';
import RecoveryKeyModal from 'components/RecoveryKeyModal';
import RecoveryKey from 'components/RecoveryKey';
import { PAGES } from 'constants/pages';
import Container from 'components/Container';
import EnteSpinner from 'components/EnteSpinner';
@ -84,7 +84,7 @@ export default function Generate() {
</EnteSpinner>
</Container>
) : recoverModalView ? (
<RecoveryKeyModal
<RecoveryKey
show={recoverModalView}
onHide={() => {
setRecoveryModalView(false);

View file

@ -17,12 +17,11 @@
}
html, body {
font-family: Arial, Helvetica, sans-serif;
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
font-family:Inter, Arial, sans-serif !important;
font-family:Inter, Arial, sans-serif;
}
#__next {

View file

@ -61,6 +61,20 @@ const darkThemeOptions = createTheme({
},
},
},
MuiDialog: {
styleOverrides: {
paper: {
'& .MuiDialogActions-root': {
padding: '32px 24px',
},
},
root: {
'& .MuiDialogActions-root button': {
marginLeft: '16px',
},
},
},
},
},
palette: {
@ -77,8 +91,9 @@ const darkThemeOptions = createTheme({
secondary: '#808080',
},
accent: {
main: '#43BA6C',
dark: '#369556',
main: '#1dba54',
dark: '#248546',
light: '#2cd366',
},
danger: {
@ -88,6 +103,7 @@ const darkThemeOptions = createTheme({
grey: {
A100: '#ccc',
A200: 'rgba(256, 256, 256, 0.24)',
A400: '#434343',
},
divider: 'rgba(255, 255, 255, 0.24)',
},

7
src/types/theme/styled.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
// styled.d.ts
import 'styled-components';
import { Theme } from '@mui/material';
declare module 'styled-components' {
export interface DefaultTheme extends Theme {}
}

View file

@ -161,10 +161,10 @@ const englishConstants = {
CHANGE_PASSWORD: 'Change password',
GO_BACK: 'go back',
RECOVERY_KEY: 'Recovery key',
SAVE_LATER: 'save later',
SAVE: 'save',
SAVE_LATER: 'Do this later',
SAVE: 'Save Key',
RECOVERY_KEY_DESCRIPTION:
'if you forget your password, the only way you can recover your data is with this key',
'If you forget your password, the only way you can recover your data is with this key.',
RECOVER_KEY_GENERATION_FAILED:
'recovery code could not be generated, please try again',
KEY_NOT_STORED_DISCLAIMER:
@ -720,6 +720,7 @@ const englishConstants = {
ALL_ALBUMS: 'All Albums',
PHOTOS: 'Photos',
ENDS: 'Ends',
COPIED: 'copied',
};
export default englishConstants;