feat: swapped out seed modal for settings category page

still work on category component but password protection works nicely
pull/3083/head
William Grant 11 months ago
parent 01bdc6e1cf
commit fd722d1f2f

@ -1,4 +1,3 @@
import { Dispatch, SetStateAction } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
@ -18,9 +17,9 @@ const StyledModalContainer = styled.div`
export type EnterPasswordModalProps = {
passwordHash: string;
passwordValid: boolean;
setPasswordValid: Dispatch<SetStateAction<boolean>>;
onClickOk: () => any;
onClickClose: () => any;
setPasswordValid: (value: boolean) => void;
onClickOk?: () => any;
onClickClose?: () => any;
title?: string;
};
@ -29,7 +28,9 @@ export const EnterPasswordModal = (props: EnterPasswordModalProps) => {
const dispatch = useDispatch();
const onClose = () => {
onClickClose();
if (onClickClose) {
onClickClose();
}
dispatch(updateEnterPasswordModal(null));
};
@ -52,7 +53,9 @@ export const EnterPasswordModal = (props: EnterPasswordModalProps) => {
window.removeEventListener('keyup', onEnter);
void onClickOk();
if (onClickOk) {
void onClickOk();
}
};
const onEnter = (event: any) => {

@ -2,7 +2,7 @@ import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { resetConversationExternal } from '../../state/ducks/conversations';
import { recoveryPhraseModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog';
import { updateDeleteAccountModal } from '../../state/ducks/modalDialog';
import {
SectionType,
setLeftOverlayMode,
@ -107,9 +107,6 @@ const LeftPaneSettingsCategoryRow = (props: {
dispatch(setLeftOverlayMode('message-requests'));
dispatch(resetConversationExternal());
break;
case 'recoveryPassword':
dispatch(recoveryPhraseModal({}));
break;
case 'clearData':
dispatch(updateDeleteAccountModal({}));
break;

@ -19,6 +19,7 @@ import { CategoryConversations } from './section/CategoryConversations';
import { SettingsCategoryHelp } from './section/CategoryHelp';
import { SettingsCategoryPermissions } from './section/CategoryPermissions';
import { SettingsCategoryPrivacy } from './section/CategoryPrivacy';
import { SettingsCategoryRecoveryPassword } from './section/CategoryRecoveryPassword';
export function displayPasswordModal(
passwordAction: PasswordAction,
@ -115,11 +116,12 @@ const SettingInCategory = (props: {
return <SettingsCategoryHelp />;
case 'permissions':
return <SettingsCategoryPermissions />;
case 'recoveryPassword':
return <SettingsCategoryRecoveryPassword />;
// these three down there have no options, they are just a button
// these are just buttons and don't have screens
case 'clearData':
case 'messageRequests':
case 'recoveryPassword':
default:
return null;
}

@ -43,9 +43,11 @@ export const SettingsHeader = (props: Props) => {
case 'privacy':
categoryTitle = window.i18n('privacySettingsTitle');
break;
case 'recoveryPassword':
categoryTitle = window.i18n('sessionRecoveryPassword');
break;
case 'clearData':
case 'messageRequests':
case 'recoveryPassword':
throw new Error(`no header for should be tried to be rendered for "${category}"`);
default:

@ -0,0 +1,94 @@
import { isEmpty } from 'lodash';
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import useMount from 'react-use/lib/useMount';
import { usePasswordModal } from '../../../hooks/usePasswordModal';
import { mnDecode } from '../../../session/crypto/mnemonic';
import { ToastUtils } from '../../../session/utils';
import { showSettingsSection } from '../../../state/ducks/section';
import { getTheme } from '../../../state/selectors/theme';
import { getThemeValue } from '../../../themes/globals';
import { getCurrentRecoveryPhrase } from '../../../util/storage';
import { SessionQRCode } from '../../SessionQRCode';
import { SessionSettingsItemWrapper } from '../SessionSettingListItem';
export const SettingsCategoryRecoveryPassword = () => {
const [loadingSeed, setLoadingSeed] = useState(true);
const [recoveryPhrase, setRecoveryPhrase] = useState('');
const [hexEncodedSeed, setHexEncodedSeed] = useState('');
const dispatch = useDispatch();
const { hasPassword, passwordValid } = usePasswordModal({
title: window.i18n('sessionRecoveryPassword'),
onClose: () => {
dispatch(showSettingsSection('privacy'));
},
});
const theme = useSelector(getTheme);
const copyRecoveryPhrase = (recoveryPhraseToCopy: string) => {
window.clipboard.writeText(recoveryPhraseToCopy);
ToastUtils.pushCopiedToClipBoard();
};
const fetchRecoverPhrase = () => {
const newRecoveryPhrase = getCurrentRecoveryPhrase();
setRecoveryPhrase(newRecoveryPhrase);
if (!isEmpty(newRecoveryPhrase)) {
setHexEncodedSeed(mnDecode(newRecoveryPhrase, 'english'));
}
setLoadingSeed(false);
};
useMount(() => {
if (!hasPassword || (hasPassword && passwordValid)) {
fetchRecoverPhrase();
}
});
if ((hasPassword && !passwordValid) || loadingSeed) {
return null;
}
return (
<>
<SessionSettingsItemWrapper
// TODO need to support escaping the HTML or passing through a ReactNode
title={window.i18n('sessionRecoveryPassword')}
description={window.i18n('recoveryPasswordDescription')}
inline={false}
>
<i
onClick={() => {
if (isEmpty(recoveryPhrase)) {
return;
}
copyRecoveryPhrase(recoveryPhrase);
}}
className="session-modal__text-highlight"
data-testid="recovery-phrase-seed-modal"
>
{recoveryPhrase}
</i>
<SessionQRCode
id={'session-recovery-passwod'}
value={hexEncodedSeed}
size={240}
backgroundColor={getThemeValue(
theme.includes('dark') ? '--text-primary-color' : '--background-primary-color'
)}
foregroundColor={getThemeValue(
theme.includes('dark') ? '--background-primary-color' : '--text-primary-color'
)}
logoImage={'./images/session/qr/shield.svg'}
logoWidth={56}
logoHeight={56}
logoIsSVG={true}
/>
{/* TODO Toggling between QR and recovery password */}
{/* TODO Permenantly hide button */}
</SessionSettingsItemWrapper>
</>
);
};

@ -1,19 +1,27 @@
import { isEmpty } from 'lodash';
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useMount } from 'react-use';
import useMount from 'react-use/lib/useMount';
import { Data } from '../data/data';
import { updateEnterPasswordModal } from '../state/ducks/modalDialog';
/**
* Password protection for a component if a password has been set
* @param title - Title of the password modal
* @param onSuccess - Callback when password is correct
* @param onClose - Callback when modal is cancelled or closed. Definitely use this if your component returns null until a password is entered
* @returns An object with two properties - hasPassword which is true if a password has been set, passwordValid which is true if the password entered is correct
*/
export function usePasswordModal({
title,
onSuccess,
onClose,
title,
}: {
onSuccess: () => void;
onClose: () => void;
title?: string;
onSuccess?: () => void;
onClose?: () => void;
}) {
const [hasPassword, setHasPassword] = useState(false);
const [passwordHash, setPasswordHash] = useState('');
const [passwordValid, setPasswordValid] = useState(false);
@ -25,6 +33,8 @@ export function usePasswordModal({
}
const hash = await Data.getPasswordHash();
setHasPassword(!!hash);
if (hash && !isEmpty(hash)) {
setPasswordHash(hash);
dispatch(
@ -33,12 +43,16 @@ export function usePasswordModal({
passwordValid,
setPasswordValid,
onClickOk: () => {
onSuccess();
if (onSuccess) {
onSuccess();
}
setPasswordHash('');
dispatch(updateEnterPasswordModal(null));
},
onClickClose: () => {
onClose();
if (onClose) {
onClose();
}
setPasswordHash('');
dispatch(updateEnterPasswordModal(null));
},
@ -51,4 +65,6 @@ export function usePasswordModal({
useMount(() => {
void validateAccess();
});
return { hasPassword, passwordValid };
}

Loading…
Cancel
Save