wip: adding recovery phrase prompt.

pull/1846/head
Warrick Corfe-Tan 4 years ago
parent bfed4a88dd
commit b0a8c6c3eb

@ -426,5 +426,12 @@
"dialogClearAllDataDeletionFailedMultiple": "Data not deleted by those Service Nodes: $snodes$",
"dialogClearAllDataDeletionQuestion": "Would you like to clear only this device, or delete your entire account?",
"deviceOnly": "Device Only",
"entireAccount": "Entire Account"
"entireAccount": "Entire Account",
"recoveryPhraseSecureMessage": "Secure your account by saving your recovery phrase",
"recoveryPhraseSecureTitle": "You're almost finished!",
"recoveryPhraseSecureButtonText": "Secure your account",
"recoveryPhraseRevealMessage": "Reveal your recovery phrase then store it safely to secure it",
"recoveryPhraseRevealButtonText": "Reveal Recovery Phrase",
"recoveryPhraseInfoMessage": "Meet your recovery phrase Your recovery phrase is the master key to your session ID - you can use it to restore your Session ID if you lose access to your device, Store your recovery phrase in a safe place, and don't give it to anyone.",
"recoveryPhraseCompleteTitle": "Account secured!"
}

@ -11,6 +11,7 @@ import { SessionWrapperModal } from '../session/SessionWrapperModal';
const deleteDbLocally = async () => {
window?.log?.info('configuration message sent successfully. Deleting everything');
window.persistStore?.purge();
await window.Signal.Logs.deleteAll();
await window.Signal.Data.removeAll();
await window.Signal.Data.close();

@ -1,8 +1,13 @@
import React from 'react';
import React, { useState } from 'react';
import classNames from 'classnames';
import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
import { DefaultTheme, useTheme } from 'styled-components';
import { SessionButton } from './SessionButton';
import styled, { DefaultTheme, useTheme } from 'styled-components';
import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton';
import { Constants } from '../../session';
import { UserUtils } from '../../session/utils';
import { useDispatch, useSelector } from 'react-redux';
import { disableRecoveryPhrasePrompt } from '../../state/ducks/userConfig';
import { getShowRecoveryPhrasePrompt } from '../../state/selectors/userConfig';
const Tab = ({
isSelected,
@ -17,8 +22,8 @@ const Tab = ({
}) => {
const handleClick = onSelect
? () => {
onSelect(type);
}
onSelect(type);
}
: undefined;
return (
@ -40,22 +45,232 @@ type Props = {
export const LeftPaneSectionHeader = (props: Props) => {
const { label, buttonIcon, buttonClicked } = props;
const theme = useTheme();
const showRecoveryPhrasePrompt = useSelector(getShowRecoveryPhrasePrompt);
return (
<div className="module-left-pane__header">
{label && <Tab label={label} type={0} isSelected={true} key={label} />}
{buttonIcon && (
<SessionButton onClick={buttonClicked} key="compose" theme={theme}>
<SessionIcon
iconType={buttonIcon}
iconSize={SessionIconSize.Small}
iconColor="white"
theme={theme}
/>
</SessionButton>
)}
</div>
<StyledLeftPaneHeaderContainer>
<div className="module-left-pane__header">
{label && <Tab label={label} type={0} isSelected={true} key={label} />}
{buttonIcon && (
<SessionButton onClick={buttonClicked} key="compose" theme={theme}>
<SessionIcon
iconType={buttonIcon}
iconSize={SessionIconSize.Small}
iconColor="white"
theme={theme}
/>
</SessionButton>
)}
</div>
{showRecoveryPhrasePrompt && <LeftPaneBanner />}
</StyledLeftPaneHeaderContainer>
);
};
export const LeftPaneBanner = (Props: any) => {
const [completion, setCompletion] = useState(80);
const [bodyText, setBodyText] = useState(window.i18n('recoveryPhraseSecureMessage'));
const [buttonText, setButtonText] = useState(window.i18n('recoveryPhraseSecureButtonText'));
const [recoveryPhraseHidden, setRecoveryPhraseHidden] = useState(true);
const [isCompleted, setIsCompleted] = useState(false);
const [bannerTitle, setBannerTitle] = useState(window.i18n("recoveryPhraseSecureTitle"));
const recoveryPhrase = UserUtils.getCurrentRecoveryPhrase();
const secondsBeforeRemoval = 2 * 1000;
const completionText = `${completion}%`;
const handleShowRecoveryClick = () => {
setRecoveryPhraseHidden(false);
setBodyText(window.i18n('recoveryPhraseInfoMessage'));
setButtonText(window.i18n('copy'));
}
const handleSecureClick = () => {
if (completion === 80) {
setCompletion(90);
setBodyText(window.i18n('recoveryPhraseRevealMessage'));
setButtonText(window.i18n('recoveryPhraseRevealButtonText'));
}
}
const BannerInner = (props: any) => {
const dispatch = useDispatch();
const handleCopyPhraseClick = async () => {
await navigator.clipboard.writeText(recoveryPhrase);
setCompletion(100)
setBannerTitle(window.i18n('recoveryPhraseCompleteTitle'));
setBodyText('');
setRecoveryPhraseHidden(true);
setIsCompleted(true);
// remove banner after a small delay
setTimeout(() => {
dispatch(disableRecoveryPhrasePrompt());
}, secondsBeforeRemoval);
}
let onClick =
completion === 80 ? handleSecureClick :
completion === 90 ?
recoveryPhraseHidden ?
handleShowRecoveryClick :
handleCopyPhraseClick
: null;
// TODO: This can be refactored down. all returns have p tag + button, final has conditional phrase element.
return (
<StyledBannerInner>
<p>
{bodyText}
</p>
{!recoveryPhraseHidden &&
<StyledRecoveryPhrase
theme={theme}
className="left-pane-banner___phrase" onClick={handleShowRecoveryClick}>
{recoveryPhrase}
</StyledRecoveryPhrase>
}
{!isCompleted &&
<SessionButton
buttonType={SessionButtonType.Default}
text={buttonText}
onClick={onClick}
/>
}
</StyledBannerInner>
)
}
const useColumn = completion === 90 && handleShowRecoveryClick;
const theme = useTheme();
return (
<StyledLeftPaneBanner
border={useTheme().colors.sessionBorder}
isCompleted={isCompleted}>
<StyledProgressBarContainer>
<StyledProgressBarInner
color={Constants.UI.COLORS.GREEN}
width={completion + '%'}
/>
</StyledProgressBarContainer>
<StyledBannerTitle
theme={theme}>
{bannerTitle} <span>{completionText}</span>
</StyledBannerTitle>
<StyledBannerContainer flexDirection={useColumn ? 'column' : 'row'} >
<BannerInner />
</StyledBannerContainer>
</StyledLeftPaneBanner>
)
}
interface StyledProgressBarContainerProps {
theme: DefaultTheme;
}
const StyledProgressBarContainer = styled.div`
width: 100%;
height: 5px;
flex-direction: row;
background: ${(p: StyledProgressBarContainerProps) => p.theme.colors.sessionBorderColor};
`;
interface StyledProgressBarProps {
width: string;
color?: string;
}
const StyledProgressBarInner = styled.div`
background: ${(p: StyledProgressBarProps) => p.color};
width: ${(p: StyledProgressBarProps) => p.width};
transition: width 0.5s ease-in;
height: 100%;
`;
interface StyledBannerTitle {
theme: DefaultTheme;
}
export const StyledBannerTitle = styled.div`
line-height: 1.3;
font-size: ${(p: StyledBannerTitle) => p.theme.common.fonts.md};
font-weight: bold;
margin:
${Constants.UI.SPACING.marginSm}
${Constants.UI.SPACING.marginSm}
0
${Constants.UI.SPACING.marginSm};
span {
color: ${(p: StyledBannerTitle) => p.theme.colors.textAccent};
}
`;
interface StyledLeftPaneBannerProps {
isCompleted?: boolean;
border: string;
theme: DefaultTheme;
}
export const StyledLeftPaneBanner = styled.div`
background: ${(p: StyledLeftPaneBannerProps) => p.theme.colors.recoveryPhraseBannerBackground};
display: flex;
flex-direction: column;
border-bottom: ${(p: StyledLeftPaneBannerProps) => p.border};
opacity: 1;
transition: opacity 2s;
${(p: StyledLeftPaneBannerProps) => p.isCompleted === true ?
`
opacity: 0;
`
:
null
}
`;
const StyledBannerInner = styled.div`
p {
margin: 0;
}
.left-pane-banner___phrase {
margin-top: ${Constants.UI.SPACING.marginMd};
}
.session-button {
margin-top: ${Constants.UI.SPACING.marginSm};
}
`;
interface StyledRecoveryPhraseProps {
theme: DefaultTheme;
}
const StyledRecoveryPhrase = styled.p`
margin: ${Constants.UI.SPACING.marginXs};
border-radius: 5px;
padding: 5px;
border: ${(props: StyledRecoveryPhraseProps) => props.theme.colors.sessionBorderHighContrast};
`;
// 90%
// tap and hold the redacted words to reveal your recovery phrase then store it safely to secure
// meet your recovery phrase
// your recovery phrase is the master key to your session ID - you can use it to restore your Session ID if you lose access to your device, Store your recovery phrase in a safe place, and don't give it to anyone.
interface StyledBannerContainerProps {
flexDirection?: string;
}
export const StyledBannerContainer = styled.div`
display: flex;
flex-direction: ${(p: StyledBannerContainerProps) => p.flexDirection};
justify-content: space-between;
padding: ${Constants.UI.SPACING.marginSm}
`;
export const StyledLeftPaneHeaderContainer = styled.div`
display: flex;
flex-direction: column;
`;

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import classNames from 'classnames';
import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton';
@ -13,6 +13,12 @@ import { getFocusedSettingsSection } from '../../state/selectors/section';
import { getTheme } from '../../state/selectors/theme';
import { recoveryPhraseModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog';
type Props = {
settingsCategory: SessionSettingCategory;
showSettingsSection: (category: SessionSettingCategory) => void;
theme: DefaultTheme;
};
const getCategories = () => {
return [
{

@ -52,6 +52,7 @@ export class SessionInboxView extends React.Component<any, State> {
}
const persistor = persistStore(this.store);
window.persistStore = persistor;
return (
<Provider store={this.store}>

@ -3,12 +3,11 @@ import classNames from 'classnames';
import moment from 'moment';
import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { Constants } from '../../../session';
import { ToastUtils } from '../../../session/utils';
import autoBind from 'auto-bind';
import MicRecorder from 'mic-recorder-to-mp3';
import styled, { useTheme } from 'styled-components';
import styled from 'styled-components';
interface Props {
onExitVoiceNoteView: any;
@ -164,7 +163,7 @@ class SessionRecordingInner extends React.Component<Props, State> {
)}
{hasRecording && (
<SessionIconButton
iconType={SessionIconType.Delete2}
iconType={SessionIconType.Delete}
iconSize={SessionIconSize.Medium}
onClick={this.onDeleteVoiceMessage}
/>
@ -366,7 +365,6 @@ class SessionRecordingInner extends React.Component<Props, State> {
const audioURL = window.URL.createObjectURL(this.audioBlobMp3);
this.audioElement = new Audio(audioURL);
// ww adding record duration
this.setState({
recordDuration: this.audioElement.duration,
});

@ -13,8 +13,6 @@ export enum SessionIconType {
CircleElipses = 'circleElipses',
Contacts = 'contacts',
Delete = 'delete',
Delete2 = 'delete2',
Delete3 = 'delete3',
Ellipses = 'ellipses',
Emoji = 'emoji',
Error = 'error',
@ -130,23 +128,11 @@ export const icons = {
ratio: 1,
},
[SessionIconType.Delete]: {
path:
'M1.63 97.99l36.55-36.55L1.63 24.89c-2.17-2.17-2.17-5.73 0-7.9L16.99 1.63c2.17-2.17 5.73-2.17 7.9 0l36.55 36.55L97.99 1.63c2.17-2.17 5.73-2.17 7.9 0l15.36 15.36c2.17 2.17 2.17 5.73 0 7.9L84.7 61.44l36.55 36.55c2.17 2.17 2.17 5.73 0 7.9l-15.36 15.36c-2.17 2.17-5.73 2.17-7.9 0L61.44 84.7l-36.55 36.55c-2.17 2.17-5.73 2.17-7.9 0L1.63 105.89c-2.17-2.17-2.17-5.73 0-7.9z',
viewBox: '0 0 122.88 122.88',
ratio: 1,
},
[SessionIconType.Delete2]: {
path:
'M11.17 37.16h83.48a8.4 8.4 0 012 .16 5.93 5.93 0 012.88 1.56 5.43 5.43 0 011.64 3.34 7.65 7.65 0 01-.06 1.44L94 117.31V117.72a7.06 7.06 0 01-.2.9v.06a5.89 5.89 0 01-5.47 4.07H17.32a6.17 6.17 0 01-1.25-.19 6.17 6.17 0 01-1.16-.48 6.18 6.18 0 01-3.08-4.88l-7-73.49a7.69 7.69 0 01-.06-1.66 5.37 5.37 0 011.63-3.29 6 6 0 013-1.58 8.94 8.94 0 011.79-.13zM5.65 8.8h31.47V6a2.44 2.44 0 010-.27 6 6 0 011.76-4A6 6 0 0143.09 0h19.67a6 6 0 015.7 6v2.8h32.39a4.7 4.7 0 014.31 4.43v10.36a2.59 2.59 0 01-2.59 2.59H2.59A2.59 2.59 0 010 23.62V13.53a1.56 1.56 0 010-.31 4.72 4.72 0 013.88-4.34 10.4 10.4 0 011.77-.08zm42.1 52.7a4.77 4.77 0 019.49 0v37a4.77 4.77 0 01-9.49 0v-37zm23.73-.2a4.58 4.58 0 015-4.06 4.47 4.47 0 014.51 4.46l-2 37a4.57 4.57 0 01-5 4.06 4.47 4.47 0 01-4.51-4.46l2-37zM25 61.7a4.46 4.46 0 014.5-4.46 4.58 4.58 0 015 4.06l2 37a4.47 4.47 0 01-4.51 4.46 4.57 4.57 0 01-5-4.06l-2-37z',
viewBox: '0 0 105.16 122.88',
ratio: 1,
},
[SessionIconType.Delete3]: {
path:
'M4.873 9.058h33.35V6.187c0-.095.002-.186.014-.279.075-1.592.762-3.037 1.816-4.086l-.007-.007C41.15.711 42.683.025 44.371.009l.023.002V0H64.325c.106 0 .207.009.309.022 1.583.084 3.019.76 4.064 1.81 1.102 1.104 1.786 2.635 1.803 4.315l-.003.021h.014V9.057h32.926c.138 0 .268.014.401.034 1.182.106 2.254.625 3.034 1.41l.004.007.005-.007c.851.857 1.386 2.048 1.401 3.368l-.002.032h.014v10.861c0 1.472-1.195 2.665-2.667 2.665H2.667C1.195 27.426 0 26.233 0 24.762V13.919c0-.106.004-.211.018-.315v-.021c.089-1.207.624-2.304 1.422-3.098l-.007-.002c.862-.861 2.057-1.396 3.377-1.414l.032.002v-.013h.031zM77.79 49.097h-5.945v56.093h5.945V49.097zm-19.33 0h-5.948v56.093h5.948V49.097zm-19.33 0h-5.946v56.093h5.946V49.097zM10.837 31.569h87.385l.279.018.127.007.134.011h.009l.163.023c1.363.163 2.638.789 3.572 1.708 1.04 1.025 1.705 2.415 1.705 3.964 0 .098-.009.193-.019.286l-.002.068-.014.154-7.393 79.335-.007.043h.007l-.016.139-.051.283-.002.005-.002.018c-.055.331-.12.646-.209.928l-.007.022-.002.005-.009.018-.023.062-.004.021c-.118.354-.264.698-.432 1.009-1.009 1.88-2.879 3.187-5.204 3.187H18.13l-.247-.014v.003l-.011-.003-.032-.004c-.46-.023-.889-.091-1.288-.202-.415-.116-.818-.286-1.197-.495l-.009-.002-.002.002c-1.785-.977-2.975-2.882-3.17-5.022L4.88 37.79l-.011-.125-.011-.247-.004-.116h-.005c0-1.553.664-2.946 1.707-3.971.976-.955 2.32-1.599 3.756-1.726l.122-.004v-.007l.3-.013.104.002v-.014h-.001zm87.386 5.334H10.837v-.007l-.116.004c-.163.022-.322.106-.438.222-.063.063-.104.132-.104.179h-.007l.007.118 7.282 79.244h-.002l.002.012c.032.376.202.691.447.825l-.002.004.084.032.063.012H90.825c.207 0 .399-.157.518-.377l.084-.197.054-.216.014-.138h.005l7.384-79.21-.003-.11c0-.045-.041-.111-.103-.172-.12-.118-.286-.202-.451-.227l-.104.002zm.111-.002h-.016.016zm.549.512v-.004.004zm5.297.377l-.002.018.002-.018zM40.887 14.389H5.332v7.706h97.63v-7.706H67.844c-1.472 0-2.664-1.192-2.664-2.664V6.168h.007c-.007-.22-.106-.433-.259-.585-.137-.141-.324-.229-.521-.252H44.394v-.006c-.213.007-.422.104-.576.259l-.004-.004-.007.004c-.131.134-.231.313-.259.501l.007.102v5.537c-.001 1.472-1.196 2.665-2.668 2.665z',
viewBox: '0 0 108.294 122.88',
ratio: 1,
},
[SessionIconType.DoubleCheckCircleFilled]: {
path:
'M7.91731278,0.313257194 C6.15053376,1.58392424 5,3.65760134 5,6 C5,6.343797 5.0247846,6.68180525 5.07266453,7.01233547 L5,7.085 L3.205,5.295 L2.5,6 L5,8.5 L5.33970233,8.16029767 C5.80439817,9.59399486 6.71914823,10.8250231 7.91731278,11.6867428 C7.31518343,11.8898758 6.67037399,12 6,12 C2.688,12 0,9.312 0,6 C0,2.688 2.688,0 6,0 C6.67037399,0 7.31518343,0.110124239 7.91731278,0.313257194 Z M12,0 C15.312,0 18,2.688 18,6 C18,9.312 15.312,12 12,12 C8.688,12 6,9.312 6,6 C6,2.688 8.688,0 12,0 Z M11,8.5 L15.5,4 L14.795,3.29 L11,7.085 L9.205,5.295 L8.5,6 L11,8.5 Z',

@ -3,7 +3,6 @@ import React from 'react';
// import 'reset-css/reset.css';
import { DefaultTheme, ThemeProvider } from 'styled-components';
import { pushToastWarning } from '../../session/utils/Toast';
const white = '#ffffff';
const black = '#000000';
@ -11,8 +10,10 @@ const warning = '#e7b100';
const destructive = '#ff453a';
const accentLightTheme = '#00e97b';
const accentDarkTheme = '#00f782';
const borderLightTheme = '#f1f1f1';
const borderDarkTheme = '#ffffff0F';
const borderLightThemeColor = '#f1f1f1';
const borderDarkThemeColor = '#ffffff0F';
const borderHighContrastLightTheme = '#afafaf';
const borderHighContrastDarkTheme = '#484848';
const borderAvatarColor = '#00000059';
const common = {
@ -55,6 +56,7 @@ export const lightTheme: DefaultTheme = {
textColorSubtleNoOpacity: '#52514f',
textColorOpposite: white,
textHighlight: `${black}33`,
textAccent: '#00c769',
// inbox
inboxBackground: white,
// buttons
@ -75,9 +77,12 @@ export const lightTheme: DefaultTheme = {
conversationItemHasUnread: '#fcfcfc',
conversationItemSelected: '#f0f0f0',
clickableHovered: '#dfdfdf',
sessionBorder: `1px solid ${borderLightTheme}`,
sessionBorder: `1px solid ${borderLightThemeColor}`,
sessionBorderColor: borderLightThemeColor,
sessionBorderHighContrast: `1px solid ${borderHighContrastLightTheme}`,
sessionUnreadBorder: `4px solid ${accentLightTheme}`,
leftpaneOverlayBackground: white,
recoveryPhraseBannerBackground: white,
// scrollbars
scrollBarTrack: '#fcfcfc',
scrollBarThumb: '#474646',
@ -111,6 +116,7 @@ export const darkTheme = {
textColorSubtleNoOpacity: '#7f7d7d',
textColorOpposite: black,
textHighlight: `${accentDarkTheme}99`,
textAccent: accentDarkTheme,
// inbox
inboxBackground: 'linear-gradient(180deg, #171717 0%, #121212 100%)',
// buttons
@ -131,9 +137,12 @@ export const darkTheme = {
conversationItemHasUnread: '#2c2c2c',
conversationItemSelected: '#404040',
clickableHovered: '#414347',
sessionBorder: `1px solid ${borderDarkTheme}`,
sessionBorder: `1px solid ${borderDarkThemeColor}`,
sessionBorderColor: borderDarkThemeColor,
sessionBorderHighContrast: `1px solid ${borderHighContrastDarkTheme}`,
sessionUnreadBorder: `4px solid ${accentDarkTheme}`,
leftpaneOverlayBackground: 'linear-gradient(180deg, #171717 0%, #121212 100%)',
recoveryPhraseBannerBackground: '#1f1f1f',
// scrollbars
scrollBarTrack: '#1b1b1b',
scrollBarThumb: '#474646',

@ -6,10 +6,12 @@ import { createSlice } from '@reduxjs/toolkit';
export interface UserConfigState {
audioAutoplay: boolean;
showRecoveryPhrasePrompt: boolean;
}
export const initialUserConfigState = {
audioAutoplay: false,
showRecoveryPhrasePrompt: true,
};
const userConfigSlice = createSlice({
@ -19,9 +21,14 @@ const userConfigSlice = createSlice({
toggleAudioAutoplay: state => {
state.audioAutoplay = !state.audioAutoplay;
},
disableRecoveryPhrasePrompt: state => {
console.log('setting recovery phrase state');
state.showRecoveryPhrasePrompt = false
},
},
});
const { actions, reducer } = userConfigSlice;
export const { toggleAudioAutoplay } = actions;
export const { toggleAudioAutoplay, disableRecoveryPhrasePrompt } = actions;
export const userConfigReducer = reducer;

@ -8,3 +8,9 @@ export const getAudioAutoplay = createSelector(
getUserConfig,
(state: UserConfigState): boolean => state.audioAutoplay
);
export const getShowRecoveryPhrasePrompt = createSelector(
getUserConfig,
(state: UserConfigState): boolean => state.showRecoveryPhrasePrompt
);

4
ts/styled.d.ts vendored

@ -39,6 +39,7 @@ declare module 'styled-components' {
textColorSubtleNoOpacity: string;
textColorOpposite: string;
textHighlight: string;
textAccent: string;
// inbox
inboxBackground: string;
// buttons
@ -60,8 +61,11 @@ declare module 'styled-components' {
conversationItemSelected: string;
clickableHovered: string;
sessionBorder: string;
sessionBorderColor: string;
sessionBorderHighContrast: string;
sessionUnreadBorder: string;
leftpaneOverlayBackground: string;
recoveryPhraseBannerBackground: string;
// scrollbars
scrollBarTrack: string;
scrollBarThumb: string;

1
ts/window.d.ts vendored

@ -52,6 +52,7 @@ declare global {
};
lokiSnodeAPI: LokiSnodeAPI;
onLogin: any;
persistStore?: Persistor;
resetDatabase: any;
restart: any;
getSeedNodeList: () => Array<any> | undefined;

Loading…
Cancel
Save