Merge branch 'develop' into feature/subscription-info-in-preferences
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import { Sync, SubscriptionWrapper } from '@/preferences/panes/account';
|
||||
import { Sync, SubscriptionWrapper, Credentials } from '@/preferences/panes/account';
|
||||
import { PreferencesPane } from '@/preferences/components';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
|
||||
export const AccountPreferences = observer(({application}: {application: WebApplication}) => {
|
||||
type Props = {
|
||||
application: WebApplication;
|
||||
}
|
||||
export const AccountPreferences = observer(({application}: Props) => {
|
||||
return (
|
||||
<PreferencesPane>
|
||||
<Credentials application={application} />
|
||||
<Sync application={application} />
|
||||
<SubscriptionWrapper application={application} />
|
||||
</PreferencesPane>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/preferences/components';
|
||||
import { Button } from '@/components/Button';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { observer } from '@node_modules/mobx-react-lite';
|
||||
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
|
||||
import { dateToLocalizedString } from '@/utils';
|
||||
import { useState } from 'preact/hooks';
|
||||
import { ChangeEmail } from '@/preferences/panes/account/changeEmail';
|
||||
import { ChangePassword } from '@/preferences/panes/account/changePassword';
|
||||
|
||||
type Props = {
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
export const Credentials = observer(({ application }: Props) => {
|
||||
const [isChangePasswordDialogOpen, setIsChangePasswordDialogOpen] = useState(false);
|
||||
const [isChangeEmailDialogOpen, setIsChangeEmailDialogOpen] = useState(false);
|
||||
|
||||
const user = application.getUser();
|
||||
|
||||
const passwordCreatedAtTimestamp = application.getUserPasswordCreationDate() as Date;
|
||||
const passwordCreatedOn = dateToLocalizedString(passwordCreatedAtTimestamp);
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Credentials</Title>
|
||||
<div className={'text-input mt-2'}>
|
||||
Email
|
||||
</div>
|
||||
<Text>
|
||||
You're signed in as <span className='font-bold'>{user?.email}</span>
|
||||
</Text>
|
||||
<Button
|
||||
className='min-w-20 mt-3'
|
||||
type='normal'
|
||||
label='Change email'
|
||||
onClick={() => {
|
||||
setIsChangeEmailDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
<HorizontalSeparator classes='mt-5 mb-3' />
|
||||
<div className={'text-input mt-2'}>
|
||||
Password
|
||||
</div>
|
||||
<Text>
|
||||
Current password was set on <span className='font-bold'>{passwordCreatedOn}</span>
|
||||
</Text>
|
||||
<Button
|
||||
className='min-w-20 mt-3'
|
||||
type='normal'
|
||||
label='Change password'
|
||||
onClick={() => {
|
||||
setIsChangePasswordDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
{isChangeEmailDialogOpen && (
|
||||
<ChangeEmail
|
||||
onCloseDialog={() => setIsChangeEmailDialogOpen(false)}
|
||||
application={application}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
isChangePasswordDialogOpen && (
|
||||
<ChangePassword
|
||||
onCloseDialog={() => setIsChangePasswordDialogOpen(false)}
|
||||
application={application}
|
||||
/>
|
||||
)}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
);
|
||||
});
|
||||
@@ -11,7 +11,7 @@ type Props = {
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
const Sync = observer(({ application }: Props) => {
|
||||
export const Sync = observer(({ application }: Props) => {
|
||||
const formatLastSyncDate = (lastUpdatedDate: Date) => {
|
||||
return dateToLocalizedString(lastUpdatedDate);
|
||||
};
|
||||
@@ -56,5 +56,3 @@ const Sync = observer(({ application }: Props) => {
|
||||
</PreferencesGroup>
|
||||
);
|
||||
});
|
||||
|
||||
export default Sync;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { DecoratedInput } from '@/components/DecoratedInput';
|
||||
import { StateUpdater } from 'preact/hooks';
|
||||
import { FunctionalComponent } from 'preact';
|
||||
import { HtmlInputTypes } from '@/enums';
|
||||
|
||||
type Props = {
|
||||
setNewEmail: StateUpdater<string>
|
||||
setCurrentPassword: StateUpdater<string>
|
||||
}
|
||||
export const ChangeEmailForm: FunctionalComponent<Props> = ({
|
||||
setNewEmail,
|
||||
setCurrentPassword
|
||||
}) => {
|
||||
return (
|
||||
(
|
||||
<>
|
||||
<div className={'mt-2 mb-3'}>
|
||||
<DecoratedInput
|
||||
onChange={(newEmail) => {
|
||||
setNewEmail(newEmail);
|
||||
}}
|
||||
placeholder={'New Email'}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-2 mb-3'}>
|
||||
<DecoratedInput
|
||||
type={HtmlInputTypes.Password}
|
||||
onChange={(currentPassword) => {
|
||||
setCurrentPassword(currentPassword);
|
||||
}}
|
||||
placeholder={'Current Password'}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { FunctionalComponent } from 'preact';
|
||||
|
||||
export const ChangeEmailSuccess: FunctionalComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<div className={'sk-label sk-bold info'}>Your email has been successfully changed.</div>
|
||||
<p className={'sk-p'}>
|
||||
Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
import { useState } from '@node_modules/preact/hooks';
|
||||
import {
|
||||
ModalDialog,
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel
|
||||
} from '@/components/shared/ModalDialog';
|
||||
import { Button } from '@/components/Button';
|
||||
import { FunctionalComponent } from 'preact';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { useBeforeUnload } from '@/hooks/useBeforeUnload';
|
||||
import { ChangeEmailForm } from './ChangeEmailForm';
|
||||
import { ChangeEmailSuccess } from './ChangeEmailSuccess';
|
||||
import { isEmailValid } from '@/utils';
|
||||
|
||||
enum SubmitButtonTitles {
|
||||
Default = 'Continue',
|
||||
GeneratingKeys = 'Generating Keys...',
|
||||
Finish = 'Finish'
|
||||
}
|
||||
|
||||
enum Steps {
|
||||
InitialStep,
|
||||
FinishStep
|
||||
}
|
||||
|
||||
type Props = {
|
||||
onCloseDialog: () => void;
|
||||
application: WebApplication;
|
||||
}
|
||||
|
||||
export const ChangeEmail: FunctionalComponent<Props> = ({
|
||||
onCloseDialog,
|
||||
application
|
||||
}) => {
|
||||
const [currentPassword, setCurrentPassword] = useState('');
|
||||
const [newEmail, setNewEmail] = useState('');
|
||||
const [isContinuing, setIsContinuing] = useState(false);
|
||||
const [lockContinue, setLockContinue] = useState(false);
|
||||
const [submitButtonTitle, setSubmitButtonTitle] = useState(SubmitButtonTitles.Default);
|
||||
const [currentStep, setCurrentStep] = useState(Steps.InitialStep);
|
||||
|
||||
useBeforeUnload();
|
||||
|
||||
const applicationAlertService = application.alertService;
|
||||
|
||||
const validateCurrentPassword = async () => {
|
||||
if (!currentPassword || currentPassword.length === 0) {
|
||||
applicationAlertService.alert(
|
||||
'Please enter your current password.'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const success = await application.validateAccountPassword(currentPassword);
|
||||
if (!success) {
|
||||
applicationAlertService.alert(
|
||||
'The current password you entered is not correct. Please try again.'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
};
|
||||
|
||||
const validateNewEmail = async () => {
|
||||
if (!isEmailValid(newEmail)) {
|
||||
applicationAlertService.alert('The email you entered has an invalid format. Please review your input and try again.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const resetProgressState = () => {
|
||||
setSubmitButtonTitle(SubmitButtonTitles.Default);
|
||||
setIsContinuing(false);
|
||||
};
|
||||
|
||||
const processEmailChange = async () => {
|
||||
await application.downloadBackup();
|
||||
|
||||
setLockContinue(true);
|
||||
|
||||
const response = await application.changeEmail(
|
||||
newEmail,
|
||||
currentPassword,
|
||||
);
|
||||
|
||||
const success = !response.error;
|
||||
|
||||
setLockContinue(false);
|
||||
|
||||
return success;
|
||||
};
|
||||
|
||||
const dismiss = () => {
|
||||
if (lockContinue) {
|
||||
applicationAlertService.alert(
|
||||
'Cannot close window until pending tasks are complete.'
|
||||
);
|
||||
} else {
|
||||
onCloseDialog();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (lockContinue || isContinuing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentStep === Steps.FinishStep) {
|
||||
dismiss();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setIsContinuing(true);
|
||||
setSubmitButtonTitle(SubmitButtonTitles.GeneratingKeys);
|
||||
|
||||
const valid = await validateCurrentPassword() && await validateNewEmail();
|
||||
|
||||
if (!valid) {
|
||||
resetProgressState();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await processEmailChange();
|
||||
if (!success) {
|
||||
resetProgressState();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setIsContinuing(false);
|
||||
setSubmitButtonTitle(SubmitButtonTitles.Finish);
|
||||
setCurrentStep(Steps.FinishStep);
|
||||
};
|
||||
|
||||
const handleDialogClose = () => {
|
||||
if (lockContinue) {
|
||||
applicationAlertService.alert(
|
||||
'Cannot close window until pending tasks are complete.'
|
||||
);
|
||||
} else {
|
||||
onCloseDialog();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={handleDialogClose}>
|
||||
Change Email
|
||||
</ModalDialogLabel>
|
||||
<ModalDialogDescription>
|
||||
{currentStep === Steps.InitialStep && (
|
||||
<ChangeEmailForm
|
||||
setNewEmail={setNewEmail}
|
||||
setCurrentPassword={setCurrentPassword}
|
||||
/>
|
||||
)}
|
||||
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
{currentStep === Steps.InitialStep && (
|
||||
<Button
|
||||
className='min-w-20'
|
||||
type='normal'
|
||||
label='Cancel'
|
||||
onClick={handleDialogClose}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
className='min-w-20'
|
||||
type='primary'
|
||||
label={submitButtonTitle}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { DecoratedInput } from '@/components/DecoratedInput';
|
||||
import { StateUpdater } from 'preact/hooks';
|
||||
import { FunctionalComponent } from 'preact';
|
||||
import { HtmlInputTypes } from '@/enums';
|
||||
|
||||
type Props = {
|
||||
setCurrentPassword: StateUpdater<string>
|
||||
setNewPassword: StateUpdater<string>
|
||||
setNewPasswordConfirmation: StateUpdater<string>
|
||||
}
|
||||
export const ChangePasswordForm: FunctionalComponent<Props> = ({
|
||||
setCurrentPassword,
|
||||
setNewPassword,
|
||||
setNewPasswordConfirmation
|
||||
}) => {
|
||||
return (
|
||||
(
|
||||
<>
|
||||
<div className={'mt-2 mb-3'}>
|
||||
<DecoratedInput
|
||||
type={HtmlInputTypes.Password}
|
||||
onChange={(currentPassword) => {
|
||||
setCurrentPassword(currentPassword);
|
||||
}}
|
||||
placeholder={'Current Password'}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-2 mb-3'}>
|
||||
<DecoratedInput
|
||||
type={HtmlInputTypes.Password}
|
||||
placeholder={'New Password'}
|
||||
onChange={newPassword => setNewPassword(newPassword)}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-2 mb-3'}>
|
||||
<DecoratedInput
|
||||
type={HtmlInputTypes.Password}
|
||||
placeholder={'Confirm New Password'}
|
||||
onChange={newPasswordConfirmation => setNewPasswordConfirmation(newPasswordConfirmation)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { FunctionalComponent } from 'preact';
|
||||
|
||||
export const ChangePasswordSuccess: FunctionalComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<div className={'sk-label sk-bold info'}>Your password has been successfully changed.</div>
|
||||
<p className={'sk-p'}>
|
||||
Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,191 @@
|
||||
import { useState } from '@node_modules/preact/hooks';
|
||||
import {
|
||||
ModalDialog,
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel
|
||||
} from '@/components/shared/ModalDialog';
|
||||
import { Button } from '@/components/Button';
|
||||
import { FunctionalComponent } from 'preact';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { ChangePasswordSuccess } from '@/preferences/panes/account/changePassword/ChangePasswordSuccess';
|
||||
import { ChangePasswordForm } from '@/preferences/panes/account/changePassword/ChangePasswordForm';
|
||||
import { useBeforeUnload } from '@/hooks/useBeforeUnload';
|
||||
|
||||
enum SubmitButtonTitles {
|
||||
Default = 'Continue',
|
||||
GeneratingKeys = 'Generating Keys...',
|
||||
Finish = 'Finish'
|
||||
}
|
||||
|
||||
enum Steps {
|
||||
InitialStep,
|
||||
FinishStep
|
||||
}
|
||||
|
||||
type Props = {
|
||||
onCloseDialog: () => void;
|
||||
application: WebApplication;
|
||||
}
|
||||
|
||||
export const ChangePassword: FunctionalComponent<Props> = ({
|
||||
onCloseDialog,
|
||||
application
|
||||
}) => {
|
||||
const [currentPassword, setCurrentPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [newPasswordConfirmation, setNewPasswordConfirmation] = useState('');
|
||||
const [isContinuing, setIsContinuing] = useState(false);
|
||||
const [lockContinue, setLockContinue] = useState(false);
|
||||
const [submitButtonTitle, setSubmitButtonTitle] = useState(SubmitButtonTitles.Default);
|
||||
const [currentStep, setCurrentStep] = useState(Steps.InitialStep);
|
||||
|
||||
useBeforeUnload();
|
||||
|
||||
const applicationAlertService = application.alertService;
|
||||
|
||||
const validateCurrentPassword = async () => {
|
||||
if (!currentPassword || currentPassword.length === 0) {
|
||||
applicationAlertService.alert(
|
||||
'Please enter your current password.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!newPassword || newPassword.length === 0) {
|
||||
applicationAlertService.alert(
|
||||
'Please enter a new password.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (newPassword !== newPasswordConfirmation) {
|
||||
applicationAlertService.alert(
|
||||
'Your new password does not match its confirmation.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!application.getUser()?.email) {
|
||||
applicationAlertService.alert(
|
||||
'We don\'t have your email stored. Please log out then log back in to fix this issue.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Validate current password */
|
||||
const success = await application.validateAccountPassword(currentPassword);
|
||||
if (!success) {
|
||||
applicationAlertService.alert(
|
||||
'The current password you entered is not correct. Please try again.'
|
||||
);
|
||||
}
|
||||
return success;
|
||||
};
|
||||
|
||||
const resetProgressState = () => {
|
||||
setSubmitButtonTitle(SubmitButtonTitles.Default);
|
||||
setIsContinuing(false);
|
||||
};
|
||||
|
||||
const processPasswordChange = async () => {
|
||||
await application.downloadBackup();
|
||||
|
||||
setLockContinue(true);
|
||||
|
||||
const response = await application.changePassword(
|
||||
currentPassword,
|
||||
newPassword
|
||||
);
|
||||
|
||||
const success = !response.error;
|
||||
|
||||
setLockContinue(false);
|
||||
|
||||
return success;
|
||||
};
|
||||
|
||||
const dismiss = () => {
|
||||
if (lockContinue) {
|
||||
applicationAlertService.alert(
|
||||
'Cannot close window until pending tasks are complete.'
|
||||
);
|
||||
} else {
|
||||
onCloseDialog();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (lockContinue || isContinuing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentStep === Steps.FinishStep) {
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
setIsContinuing(true);
|
||||
setSubmitButtonTitle(SubmitButtonTitles.GeneratingKeys);
|
||||
|
||||
const valid = await validateCurrentPassword();
|
||||
|
||||
if (!valid) {
|
||||
resetProgressState();
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await processPasswordChange();
|
||||
if (!success) {
|
||||
resetProgressState();
|
||||
return;
|
||||
}
|
||||
setIsContinuing(false);
|
||||
setSubmitButtonTitle(SubmitButtonTitles.Finish);
|
||||
setCurrentStep(Steps.FinishStep);
|
||||
};
|
||||
|
||||
const handleDialogClose = () => {
|
||||
if (lockContinue) {
|
||||
applicationAlertService.alert(
|
||||
'Cannot close window until pending tasks are complete.'
|
||||
);
|
||||
} else {
|
||||
onCloseDialog();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={handleDialogClose}>
|
||||
Change Password
|
||||
</ModalDialogLabel>
|
||||
<ModalDialogDescription>
|
||||
{currentStep === Steps.InitialStep && (
|
||||
<ChangePasswordForm
|
||||
setCurrentPassword={setCurrentPassword}
|
||||
setNewPassword={setNewPassword}
|
||||
setNewPasswordConfirmation={setNewPasswordConfirmation}
|
||||
/>
|
||||
)}
|
||||
{currentStep === Steps.FinishStep && <ChangePasswordSuccess />}
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
{currentStep === Steps.InitialStep && (
|
||||
<Button
|
||||
className='min-w-20'
|
||||
type='normal'
|
||||
label='Cancel'
|
||||
onClick={handleDialogClose}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
className='min-w-20'
|
||||
type='primary'
|
||||
label={submitButtonTitle}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export { default as Sync } from './Sync';
|
||||
export { SubscriptionWrapper } from './subscription/SubscriptionWrapper';
|
||||
export { Sync } from './Sync';
|
||||
export { Credentials } from './Credentials';
|
||||
|
||||
@@ -6,11 +6,11 @@ import { FunctionComponent } from 'preact';
|
||||
import { downloadSecretKey } from './download-secret-key';
|
||||
import { TwoFactorActivation } from './TwoFactorActivation';
|
||||
import {
|
||||
TwoFactorDialog,
|
||||
TwoFactorDialogLabel,
|
||||
TwoFactorDialogDescription,
|
||||
TwoFactorDialogButtons,
|
||||
} from './TwoFactorDialog';
|
||||
ModalDialog,
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel
|
||||
} from '@/components/shared/ModalDialog';
|
||||
|
||||
export const SaveSecretKey: FunctionComponent<{
|
||||
activation: TwoFactorActivation;
|
||||
@@ -32,15 +32,15 @@ export const SaveSecretKey: FunctionComponent<{
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<TwoFactorDialog>
|
||||
<TwoFactorDialogLabel
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel
|
||||
closeDialog={() => {
|
||||
act.cancelActivation();
|
||||
}}
|
||||
>
|
||||
Step 2 of 3 - Save secret key
|
||||
</TwoFactorDialogLabel>
|
||||
<TwoFactorDialogDescription>
|
||||
</ModalDialogLabel>
|
||||
<ModalDialogDescription>
|
||||
<div className="flex-grow flex flex-col gap-2">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<div className="text-sm">
|
||||
@@ -70,8 +70,8 @@ export const SaveSecretKey: FunctionComponent<{
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</TwoFactorDialogDescription>
|
||||
<TwoFactorDialogButtons>
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
<Button
|
||||
className="min-w-20"
|
||||
type="normal"
|
||||
@@ -84,7 +84,7 @@ export const SaveSecretKey: FunctionComponent<{
|
||||
label="Next"
|
||||
onClick={() => act.openVerification()}
|
||||
/>
|
||||
</TwoFactorDialogButtons>
|
||||
</TwoFactorDialog>
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,17 +3,17 @@ import { observer } from 'mobx-react-lite';
|
||||
|
||||
import QRCode from 'qrcode.react';
|
||||
|
||||
import { DecoratedInput } from '../../../components/DecoratedInput';
|
||||
import { IconButton } from '../../../components/IconButton';
|
||||
import { DecoratedInput } from '@/components/DecoratedInput';
|
||||
import { IconButton } from '@/components/IconButton';
|
||||
import { Button } from '@/components/Button';
|
||||
import { TwoFactorActivation } from './TwoFactorActivation';
|
||||
import {
|
||||
TwoFactorDialog,
|
||||
TwoFactorDialogLabel,
|
||||
TwoFactorDialogDescription,
|
||||
TwoFactorDialogButtons,
|
||||
} from './TwoFactorDialog';
|
||||
import { AuthAppInfoTooltip } from './AuthAppInfoPopup';
|
||||
import {
|
||||
ModalDialog,
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel
|
||||
} from '@/components/shared/ModalDialog';
|
||||
|
||||
export const ScanQRCode: FunctionComponent<{
|
||||
activation: TwoFactorActivation;
|
||||
@@ -27,15 +27,15 @@ export const ScanQRCode: FunctionComponent<{
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<TwoFactorDialog>
|
||||
<TwoFactorDialogLabel
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel
|
||||
closeDialog={() => {
|
||||
act.cancelActivation();
|
||||
}}
|
||||
>
|
||||
Step 1 of 3 - Scan QR code
|
||||
</TwoFactorDialogLabel>
|
||||
<TwoFactorDialogDescription>
|
||||
</ModalDialogLabel>
|
||||
<ModalDialogDescription>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<div className="w-25 h-25 flex items-center justify-center bg-info">
|
||||
<QRCode value={act.qrCode} size={100} />
|
||||
@@ -61,8 +61,8 @@ export const ScanQRCode: FunctionComponent<{
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TwoFactorDialogDescription>
|
||||
<TwoFactorDialogButtons>
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
<Button
|
||||
className="min-w-20"
|
||||
type="normal"
|
||||
@@ -75,7 +75,7 @@ export const ScanQRCode: FunctionComponent<{
|
||||
label="Next"
|
||||
onClick={() => act.openSaveSecretKey()}
|
||||
/>
|
||||
</TwoFactorDialogButtons>
|
||||
</TwoFactorDialog>
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { ComponentChildren, FunctionComponent } from 'preact';
|
||||
import { IconButton } from '../../../components/IconButton';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogDescription,
|
||||
AlertDialogLabel,
|
||||
} from '@reach/alert-dialog';
|
||||
import { useRef } from 'preact/hooks';
|
||||
|
||||
/**
|
||||
* TwoFactorDialog is AlertDialog styled for 2FA
|
||||
* Can be generalized but more use cases are needed
|
||||
*/
|
||||
export const TwoFactorDialog: FunctionComponent<{
|
||||
children: ComponentChildren;
|
||||
}> = ({ children }) => {
|
||||
const ldRef = useRef<HTMLButtonElement>();
|
||||
|
||||
return (
|
||||
<AlertDialog leastDestructiveRef={ldRef}>
|
||||
{/* sn-component is focusable by default, but doesn't stretch to child width
|
||||
resulting in a badly focused dialog. Utility classes are not available
|
||||
at the sn-component level, only below it. tabIndex -1 disables focus
|
||||
and enables it on the child component */}
|
||||
<div tabIndex={-1} className="sn-component">
|
||||
<div
|
||||
tabIndex={0}
|
||||
className="w-160 bg-default rounded shadow-overlay focus:padded-ring-info"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export const TwoFactorDialogLabel: FunctionComponent<{
|
||||
closeDialog: () => void;
|
||||
}> = ({ children, closeDialog }) => (
|
||||
<AlertDialogLabel className="">
|
||||
<div className="px-4 pt-4 pb-3 flex flex-row">
|
||||
<div className="flex-grow color-black text-lg font-bold">{children}</div>
|
||||
<IconButton
|
||||
className="color-grey-1 h-5 w-5"
|
||||
icon="close"
|
||||
onClick={() => closeDialog()}
|
||||
/>
|
||||
</div>
|
||||
<hr className="h-1px bg-border no-border m-0" />
|
||||
</AlertDialogLabel>
|
||||
);
|
||||
|
||||
export const TwoFactorDialogDescription: FunctionComponent = ({ children }) => (
|
||||
<AlertDialogDescription className="px-4 py-4">
|
||||
{children}
|
||||
</AlertDialogDescription>
|
||||
);
|
||||
|
||||
export const TwoFactorDialogButtons: FunctionComponent = ({ children }) => (
|
||||
<>
|
||||
<hr className="h-1px bg-border no-border m-0" />
|
||||
<div className="px-4 py-4 flex flex-row justify-end gap-3">{children}</div>
|
||||
</>
|
||||
);
|
||||
@@ -4,11 +4,11 @@ import { observer } from 'mobx-react-lite';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { TwoFactorActivation } from './TwoFactorActivation';
|
||||
import {
|
||||
TwoFactorDialog,
|
||||
TwoFactorDialogLabel,
|
||||
TwoFactorDialogDescription,
|
||||
TwoFactorDialogButtons,
|
||||
} from './TwoFactorDialog';
|
||||
ModalDialog,
|
||||
ModalDialogButtons,
|
||||
ModalDialogDescription,
|
||||
ModalDialogLabel
|
||||
} from '@/components/shared/ModalDialog';
|
||||
|
||||
export const Verification: FunctionComponent<{
|
||||
activation: TwoFactorActivation;
|
||||
@@ -16,11 +16,11 @@ export const Verification: FunctionComponent<{
|
||||
const borderInv =
|
||||
act.verificationStatus === 'invalid' ? 'border-dark-red' : '';
|
||||
return (
|
||||
<TwoFactorDialog>
|
||||
<TwoFactorDialogLabel closeDialog={act.cancelActivation}>
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={act.cancelActivation}>
|
||||
Step 3 of 3 - Verification
|
||||
</TwoFactorDialogLabel>
|
||||
<TwoFactorDialogDescription>
|
||||
</ModalDialogLabel>
|
||||
<ModalDialogDescription>
|
||||
<div className="flex-grow flex flex-col gap-1">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<div className="text-sm">
|
||||
@@ -42,8 +42,8 @@ export const Verification: FunctionComponent<{
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TwoFactorDialogDescription>
|
||||
<TwoFactorDialogButtons>
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
{act.verificationStatus === 'invalid' && (
|
||||
<div className="text-sm color-danger">
|
||||
Incorrect credentials, please try again.
|
||||
@@ -61,7 +61,7 @@ export const Verification: FunctionComponent<{
|
||||
label="Next"
|
||||
onClick={act.enable2FA}
|
||||
/>
|
||||
</TwoFactorDialogButtons>
|
||||
</TwoFactorDialog>
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user