refactor: migrate account-menu to react - implement functionality
- implement "merge local data" - show "error reporting dialog" - fix TS errors - cleanup TODOs
This commit is contained in:
@@ -8,6 +8,7 @@ import { isDesktopApplication, isSameDay, preventRefreshing } from '@/utils';
|
|||||||
import { storage, StorageKey } from '@Services/localStorage';
|
import { storage, StorageKey } from '@Services/localStorage';
|
||||||
import { disableErrorReporting, enableErrorReporting, errorReportingId } from '@Services/errorReporting';
|
import { disableErrorReporting, enableErrorReporting, errorReportingId } from '@Services/errorReporting';
|
||||||
import {
|
import {
|
||||||
|
STRING_ACCOUNT_MENU_UNCHECK_MERGE,
|
||||||
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE,
|
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE,
|
||||||
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
|
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
|
||||||
STRING_E2E_ENABLED,
|
STRING_E2E_ENABLED,
|
||||||
@@ -27,8 +28,9 @@ import { BackupFile, ContentType } from '@node_modules/@standardnotes/snjs';
|
|||||||
import { PasswordWizardType } from '@/types';
|
import { PasswordWizardType } from '@/types';
|
||||||
import { JSXInternal } from '@node_modules/preact/src/jsx';
|
import { JSXInternal } from '@node_modules/preact/src/jsx';
|
||||||
import TargetedEvent = JSXInternal.TargetedEvent;
|
import TargetedEvent = JSXInternal.TargetedEvent;
|
||||||
import { alertDialog } from '@Services/alertService';
|
import TargetedKeyboardEvent = JSXInternal.TargetedKeyboardEvent;
|
||||||
import TargetedMouseEvent = JSXInternal.TargetedMouseEvent;
|
import TargetedMouseEvent = JSXInternal.TargetedMouseEvent;
|
||||||
|
import { alertDialog, confirmDialog } from '@Services/alertService';
|
||||||
import { RefObject } from 'react';
|
import { RefObject } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -67,7 +69,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
const emailInputRef = useRef<HTMLInputElement>();
|
const emailInputRef = useRef<HTMLInputElement>();
|
||||||
const passwordInputRef = useRef<HTMLInputElement>();
|
const passwordInputRef = useRef<HTMLInputElement>();
|
||||||
|
|
||||||
// TODO: Vardan `showLogin` and `showRegister` were in `formData` in Angular code, check whether I need to write similarly
|
|
||||||
const [showLogin, setShowLogin] = useState(false);
|
const [showLogin, setShowLogin] = useState(false);
|
||||||
const [showRegister, setShowRegister] = useState(false);
|
const [showRegister, setShowRegister] = useState(false);
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||||
@@ -94,31 +95,24 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
const [selectedAutoLockInterval, setSelectedAutoLockInterval] = useState<unknown>(null);
|
const [selectedAutoLockInterval, setSelectedAutoLockInterval] = useState<unknown>(null);
|
||||||
const [isImportDataLoading, setIsImportDataLoading] = useState(false);
|
const [isImportDataLoading, setIsImportDataLoading] = useState(false);
|
||||||
const [isErrorReportingEnabled, setIsErrorReportingEnabled] = useState(false);
|
const [isErrorReportingEnabled, setIsErrorReportingEnabled] = useState(false);
|
||||||
const [appVersion, setAppVersion] = useState(''); // TODO: Vardan: figure out how to get `appVersion` similar to original code
|
const [appVersion, setAppVersion] = useState('');
|
||||||
const [hasPasscode, setHasPasscode] = useState(application.hasPasscode());
|
const [hasPasscode, setHasPasscode] = useState(application.hasPasscode());
|
||||||
const [isBackupEncrypted, setIsBackupEncrypted] = useState(isEncryptionEnabled);
|
const [isBackupEncrypted, setIsBackupEncrypted] = useState(isEncryptionEnabled);
|
||||||
const [isSyncInProgress, setIsSyncInProgress] = useState(false);
|
const [isSyncInProgress, setIsSyncInProgress] = useState(false);
|
||||||
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(getProtectionsDisabledUntil());
|
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(getProtectionsDisabledUntil());
|
||||||
|
|
||||||
const user = application.getUser();
|
|
||||||
|
|
||||||
const reloadAutoLockInterval = useCallback(async () => {
|
const reloadAutoLockInterval = useCallback(async () => {
|
||||||
const interval = await application.getAutolockService().getAutoLockInterval();
|
const interval = await application.getAutolockService().getAutoLockInterval();
|
||||||
setSelectedAutoLockInterval(interval);
|
setSelectedAutoLockInterval(interval);
|
||||||
}, [application]);
|
}, [application]);
|
||||||
|
|
||||||
|
const user = application.getUser();
|
||||||
const errorReportingIdValue = errorReportingId();
|
const errorReportingIdValue = errorReportingId();
|
||||||
const canAddPasscode = !application.isEphemeralSession();
|
const canAddPasscode = !application.isEphemeralSession();
|
||||||
const keyStorageInfo = StringUtils.keyStorageInfo(application);
|
const keyStorageInfo = StringUtils.keyStorageInfo(application);
|
||||||
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions();
|
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions();
|
||||||
const showBetaWarning = appState.showBetaWarning;
|
const showBetaWarning = appState.showBetaWarning;
|
||||||
|
|
||||||
/*
|
|
||||||
const displayRegistrationForm = () => {
|
|
||||||
console.log('display registration form!');
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
const focusWithTimeout = (inputElementRef: RefObject<HTMLInputElement>) => {
|
const focusWithTimeout = (inputElementRef: RefObject<HTMLInputElement>) => {
|
||||||
// In case the ref element is not yet available at this moment,
|
// In case the ref element is not yet available at this moment,
|
||||||
// we call `focus()` after timeout.
|
// we call `focus()` after timeout.
|
||||||
@@ -142,26 +136,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
passwordInputRef.current.blur();
|
passwordInputRef.current.blur();
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO: move to top
|
|
||||||
type FormData = {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
passwordConfirmation: string;
|
|
||||||
showLogin: boolean;
|
|
||||||
showRegister: boolean;
|
|
||||||
showPasscodeForm: boolean;
|
|
||||||
isStrictSignin?: boolean;
|
|
||||||
isEphemeral: boolean;
|
|
||||||
shouldMergeLocal?: boolean;
|
|
||||||
url: string;
|
|
||||||
isAuthenticating: boolean;
|
|
||||||
status: string;
|
|
||||||
passcode: string;
|
|
||||||
passcodeConfirmation: string;
|
|
||||||
};*/
|
|
||||||
|
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
setStatus(STRING_GENERATING_LOGIN_KEYS);
|
setStatus(STRING_GENERATING_LOGIN_KEYS);
|
||||||
setIsAuthenticating(true);
|
setIsAuthenticating(true);
|
||||||
@@ -220,8 +194,11 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAuthFormSubmit = (event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>) => {
|
const handleAuthFormSubmit = (event:
|
||||||
// TODO: If I don't need `submit` form at all, get rid of `onSubmit` and thus there will be no need to `preventDefault`
|
TargetedEvent<HTMLFormElement> |
|
||||||
|
TargetedMouseEvent<HTMLButtonElement> |
|
||||||
|
TargetedKeyboardEvent<HTMLButtonElement>
|
||||||
|
) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
@@ -239,15 +216,13 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
|
|
||||||
const handleHostInputChange = (event: TargetedEvent<HTMLInputElement>) => {
|
const handleHostInputChange = (event: TargetedEvent<HTMLInputElement>) => {
|
||||||
const { value } = event.target as HTMLInputElement;
|
const { value } = event.target as HTMLInputElement;
|
||||||
setServer(value);
|
setUrl(value);
|
||||||
application.setHost(value);
|
application.setHost(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// const handleKeyPressKeyDown = (event: KeyboardEvent) => {
|
|
||||||
const handleKeyPressKeyDown = (event: KeyboardEvent) => {
|
const handleKeyPressKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
// TODO: fix TS error for `event`
|
handleAuthFormSubmit(event as TargetedKeyboardEvent<HTMLButtonElement>);
|
||||||
handleAuthFormSubmit(event);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -266,8 +241,18 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
setPasswordConfirmation(value);
|
setPasswordConfirmation(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMergeLocalData = () => {
|
const handleMergeLocalData = async (event: TargetedEvent<HTMLInputElement>) => {
|
||||||
console.log('handleMergeLocalData');
|
const { checked } = event.target as HTMLInputElement;
|
||||||
|
|
||||||
|
if (!checked) {
|
||||||
|
setShouldMergeLocal(checked);
|
||||||
|
|
||||||
|
const confirmResult = await confirmDialog({
|
||||||
|
text: STRING_ACCOUNT_MENU_UNCHECK_MERGE,
|
||||||
|
confirmButtonStyle: 'danger'
|
||||||
|
});
|
||||||
|
setShouldMergeLocal(!confirmResult);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openPasswordWizard = () => {
|
const openPasswordWizard = () => {
|
||||||
@@ -320,7 +305,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submitPasscodeForm = async (event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>) => {
|
const submitPasscodeForm = async (event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>) => {
|
||||||
// TODO: If I don't need `submit` form at all, get rid of `onSubmit` and thus there will be no need to `preventDefault`
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (passcode !== passcodeConfirmation) {
|
if (passcode !== passcodeConfirmation) {
|
||||||
@@ -353,7 +337,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
refreshEncryptionStatus();
|
refreshEncryptionStatus();
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Vardan: check whether this (and `handleConfirmPasscodeChange`) method is required in the end
|
|
||||||
const handlePasscodeChange = (event: TargetedEvent<HTMLInputElement>) => {
|
const handlePasscodeChange = (event: TargetedEvent<HTMLInputElement>) => {
|
||||||
const { value } = event.target as HTMLInputElement;
|
const { value } = event.target as HTMLInputElement;
|
||||||
setPasscode(value);
|
setPasscode(value);
|
||||||
@@ -387,14 +370,10 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
appState.accountMenuReact.setSigningOut(true);
|
appState.accountMenuReact.setSigningOut(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Vardan: the name `changePasscodePressed` comes from original code; it is very similar to my `handlePasscodeChange`.
|
|
||||||
// Check if `handlePasscodeChange` is not required, remove it and rename `changePasscodePressed` to `handlePasscodeChange`
|
|
||||||
const changePasscodePressed = () => {
|
const changePasscodePressed = () => {
|
||||||
handleAddPassCode();
|
handleAddPassCode();
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Vardan: the name `removePasscodePressed` comes from original code;
|
|
||||||
// Check if I rename`changePasscodePressed` to `handlePasscodeChange`, also rename `removePasscodePressed` to `handleRemovePasscode`
|
|
||||||
const removePasscodePressed = async () => {
|
const removePasscodePressed = async () => {
|
||||||
await preventRefreshing(
|
await preventRefreshing(
|
||||||
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
|
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
|
||||||
@@ -494,17 +473,27 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openErrorReportingDialog = () => {
|
const openErrorReportingDialog = () => {
|
||||||
console.log('openErrorReportingDialog');
|
alertDialog({
|
||||||
|
title: 'Data sent during automatic error reporting',
|
||||||
|
text: `
|
||||||
|
We use <a target="_blank" rel="noreferrer" href="https://www.bugsnag.com/">Bugsnag</a>
|
||||||
|
to automatically report errors that occur while the app is running. See
|
||||||
|
<a target="_blank" rel="noreferrer" href="https://docs.bugsnag.com/platforms/javascript/#sending-diagnostic-data">
|
||||||
|
this article, paragraph 'Browser' under 'Sending diagnostic data',
|
||||||
|
</a>
|
||||||
|
to see what data is included in error reports.
|
||||||
|
<br><br>
|
||||||
|
Error reports never include IP addresses and are fully
|
||||||
|
anonymized. We use error reports to be alerted when something in our
|
||||||
|
code is causing unexpected errors and crashes in your application
|
||||||
|
experience.
|
||||||
|
`
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: check whether this works fine (e.g. remove all tags and notes and then add one and check whether UI behaves appropriately)
|
|
||||||
const notesAndTagsCount = application.getItems([ContentType.Note, ContentType.Tag]).length;
|
const notesAndTagsCount = application.getItems([ContentType.Note, ContentType.Tag]).length;
|
||||||
const hasProtections = application.hasProtectionSources();
|
const hasProtections = application.hasProtectionSources();
|
||||||
|
|
||||||
|
|
||||||
// TODO: Vardan: this is as per `this.autorun` from `$onInit`, check whether it works
|
|
||||||
// I'm mostly concerned about having dependency, since I think it is running only once in original code
|
|
||||||
// (I suppose it runs here only once, too. But need to recheck)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSyncError(appState.sync.errorMessage);
|
setSyncError(appState.sync.errorMessage);
|
||||||
setIsSyncInProgress(appState.sync.inProgress);
|
setIsSyncInProgress(appState.sync.inProgress);
|
||||||
@@ -515,11 +504,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: in original `AccountMenu`, the `appVersion` is available in constructor (the `window.electronAppVersion` is `undefined`).
|
|
||||||
// But I can't find where `appVersion` is passed to AccountMenu's constructor... The only place I found is `app.ts`, where
|
|
||||||
// it sets constant `appVersion` from `bridge.appVersion` - maybe constructor takes that value from there?
|
|
||||||
// Ask someone to explain that part.
|
|
||||||
// Here I just take the version from `application.bridge.appVersion`, as it is done in `app.ts`.
|
|
||||||
setAppVersion(`v${((window as any).electronAppVersion || application.bridge.appVersion)}`);
|
setAppVersion(`v${((window as any).electronAppVersion || application.bridge.appVersion)}`);
|
||||||
}, [appVersion, application.bridge.appVersion]);
|
}, [appVersion, application.bridge.appVersion]);
|
||||||
|
|
||||||
@@ -534,7 +518,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const host = application.getHost();
|
const host = application.getHost();
|
||||||
setServer(host);
|
setServer(host);
|
||||||
setUrl(host); // TODO: Vardan: maybe `url` is not needed at all, recheck
|
setUrl(host);
|
||||||
}, [application]);
|
}, [application]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -590,7 +574,6 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
</div>
|
</div>
|
||||||
<form className='sk-panel-form' onSubmit={handleAuthFormSubmit} noValidate>
|
<form className='sk-panel-form' onSubmit={handleAuthFormSubmit} noValidate>
|
||||||
<div className='sk-panel-section'>
|
<div className='sk-panel-section'>
|
||||||
{/* TODO: Vardan: there are `should-focus` and `sn-autofocus`, implement them */}
|
|
||||||
<input className='sk-input contrast'
|
<input className='sk-input contrast'
|
||||||
name='email'
|
name='email'
|
||||||
type='email'
|
type='email'
|
||||||
@@ -707,12 +690,12 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
{notesAndTagsCount > 0 && (
|
{notesAndTagsCount > 0 && (
|
||||||
<label className='sk-panel-row.justify-left'>
|
<label className='sk-panel-row justify-left'>
|
||||||
<div className='sk-horizontal-group tight'>
|
<div className='sk-horizontal-group tight'>
|
||||||
<input
|
<input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
onChange={handleMergeLocalData}
|
|
||||||
checked={shouldMergeLocal}
|
checked={shouldMergeLocal}
|
||||||
|
onChange={handleMergeLocalData}
|
||||||
/>
|
/>
|
||||||
<p className='sk-p'>Merge local data ({notesAndTagsCount}) notes and tags</p>
|
<p className='sk-p'>Merge local data ({notesAndTagsCount}) notes and tags</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user