refactor: mobile modals (#2173)
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import Button from '@/Components/Button/Button'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import IconButton from '@/Components/Button/IconButton'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
@@ -7,10 +6,6 @@ import CopyButton from './CopyButton'
|
||||
import Bullet from './Bullet'
|
||||
import { downloadSecretKey } from './download-secret-key'
|
||||
import { TwoFactorActivation } from './TwoFactorActivation'
|
||||
import ModalDialog from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
|
||||
type Props = {
|
||||
@@ -18,72 +13,59 @@ type Props = {
|
||||
}
|
||||
|
||||
const SaveSecretKey: FunctionComponent<Props> = ({ activation: act }) => {
|
||||
const download = (
|
||||
<IconButton
|
||||
focusable={false}
|
||||
title="Download"
|
||||
icon="download"
|
||||
className="p-0"
|
||||
onClick={() => {
|
||||
downloadSecretKey(act.secretKey)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel
|
||||
closeDialog={() => {
|
||||
act.cancelActivation()
|
||||
}}
|
||||
>
|
||||
Step 2 of 3 - Save secret key
|
||||
</ModalDialogLabel>
|
||||
<ModalDialogDescription className="h-33 flex flex-row items-center">
|
||||
<div className="flex flex-grow flex-col">
|
||||
<div className="flex flex-row flex-wrap items-center gap-1">
|
||||
<Bullet />
|
||||
<div className="text-sm">
|
||||
<b>Save your secret key</b>{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://standardnotes.com/help/21/where-should-i-store-my-two-factor-authentication-secret-key"
|
||||
>
|
||||
somewhere safe
|
||||
</a>
|
||||
:
|
||||
</div>
|
||||
<DecoratedInput
|
||||
disabled={true}
|
||||
right={[<CopyButton copyValue={act.secretKey} />, download]}
|
||||
value={act.secretKey}
|
||||
className={{ container: 'ml-2' }}
|
||||
/>
|
||||
<div className="h-33 flex flex-row items-center px-4 py-4">
|
||||
<div className="flex flex-grow flex-col">
|
||||
<div className="flex flex-row flex-wrap items-center gap-1">
|
||||
<Bullet />
|
||||
<div className="text-sm">
|
||||
<b>Save your secret key</b>{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://standardnotes.com/help/21/where-should-i-store-my-two-factor-authentication-secret-key"
|
||||
>
|
||||
somewhere safe
|
||||
</a>
|
||||
:
|
||||
</div>
|
||||
<div className="h-2" />
|
||||
<div className="flex flex-row items-center">
|
||||
<Bullet />
|
||||
<div className="min-w-1" />
|
||||
<div className="text-sm">
|
||||
You can use this key to generate codes if you lose access to your authenticator app.
|
||||
<br />
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="underline hover:no-underline"
|
||||
href="https://standardnotes.com/help/22/what-happens-if-i-lose-my-2fa-device-and-my-secret-key"
|
||||
>
|
||||
Learn more
|
||||
<Icon className="ml-1 inline" type="open-in" size="small" />
|
||||
</a>
|
||||
</div>
|
||||
<DecoratedInput
|
||||
disabled={true}
|
||||
right={[
|
||||
<CopyButton copyValue={act.secretKey} />,
|
||||
<IconButton
|
||||
focusable={false}
|
||||
title="Download"
|
||||
icon="download"
|
||||
className="p-0"
|
||||
onClick={() => {
|
||||
downloadSecretKey(act.secretKey)
|
||||
}}
|
||||
/>,
|
||||
]}
|
||||
value={act.secretKey}
|
||||
className={{ container: 'ml-2' }}
|
||||
/>
|
||||
</div>
|
||||
<div className="h-2" />
|
||||
<div className="flex flex-row items-center">
|
||||
<Bullet />
|
||||
<div className="min-w-1" />
|
||||
<div className="text-sm">
|
||||
You can use this key to generate codes if you lose access to your authenticator app.
|
||||
<br />
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="underline hover:no-underline"
|
||||
href="https://standardnotes.com/help/22/what-happens-if-i-lose-my-2fa-device-and-my-secret-key"
|
||||
>
|
||||
Learn more
|
||||
<Icon className="ml-1 inline" type="open-in" size="small" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
<Button className="min-w-20" label="Back" onClick={() => act.openScanQRCode()} />
|
||||
<Button className="min-w-20" primary label="Next" onClick={() => act.openVerification()} />
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,58 +2,53 @@ import { FunctionComponent } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import QRCode from 'qrcode.react'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { TwoFactorActivation } from './TwoFactorActivation'
|
||||
import AuthAppInfoTooltip from './AuthAppInfoPopup'
|
||||
import ModalDialog from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||
import CopyButton from './CopyButton'
|
||||
import Bullet from './Bullet'
|
||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||
|
||||
type Props = {
|
||||
activation: TwoFactorActivation
|
||||
}
|
||||
|
||||
const ScanQRCode: FunctionComponent<Props> = ({ activation: act }) => {
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
|
||||
return (
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={act.cancelActivation}>Step 1 of 3 - Scan QR code</ModalDialogLabel>
|
||||
<ModalDialogDescription className="h-33 flex flex-col items-center gap-5 md:flex-row">
|
||||
<div className="w-25 h-25 flex items-center justify-center bg-info">
|
||||
<QRCode className="border-2 border-solid border-neutral-contrast" value={act.qrCode} size={100} />
|
||||
</div>
|
||||
<div className="flex flex-grow flex-col gap-2">
|
||||
<div className="flex flex-row items-center">
|
||||
<Bullet />
|
||||
<div className="min-w-1" />
|
||||
<div className="text-sm">
|
||||
Open your <b>authenticator app</b>.
|
||||
</div>
|
||||
<div className="min-w-2" />
|
||||
<AuthAppInfoTooltip />
|
||||
<div className="h-33 flex flex-col items-center gap-5 px-4 py-4 md:flex-row">
|
||||
<div className="flex items-center justify-center bg-info">
|
||||
<QRCode
|
||||
className="border-2 border-solid border-neutral-contrast"
|
||||
value={act.qrCode}
|
||||
size={isMobileScreen ? 200 : 150}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-grow flex-col gap-2">
|
||||
<div className="flex flex-row items-center">
|
||||
<Bullet />
|
||||
<div className="min-w-1" />
|
||||
<div className="text-sm">
|
||||
Open your <b>authenticator app</b>.
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<Bullet className="mt-2 self-start" />
|
||||
<div className="min-w-1" />
|
||||
<div className="flex-grow text-sm">
|
||||
<b>Scan this QR code</b> or <b>add this secret key</b>:
|
||||
</div>
|
||||
</div>
|
||||
<DecoratedInput
|
||||
className={{ container: 'w-92 ml-4' }}
|
||||
disabled={true}
|
||||
value={act.secretKey}
|
||||
right={[<CopyButton copyValue={act.secretKey} />]}
|
||||
/>
|
||||
<div className="min-w-2" />
|
||||
<AuthAppInfoTooltip />
|
||||
</div>
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
<Button className="min-w-20" label="Cancel" onClick={() => act.cancelActivation()} />
|
||||
<Button className="min-w-20" primary label="Next" onClick={() => act.openSaveSecretKey()} />
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
<div className="flex flex-row items-center">
|
||||
<Bullet className="mt-2 self-start" />
|
||||
<div className="min-w-1" />
|
||||
<div className="flex-grow text-sm">
|
||||
<b>Scan this QR code</b> or <b>add this secret key</b>:
|
||||
</div>
|
||||
</div>
|
||||
<DecoratedInput
|
||||
className={{ container: 'w-92 ml-4' }}
|
||||
disabled={true}
|
||||
value={act.secretKey}
|
||||
right={[<CopyButton copyValue={act.secretKey} />]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/Pre
|
||||
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import RecoveryCodeBanner from '@/Components/RecoveryCodeBanner/RecoveryCodeBanner'
|
||||
import Modal, { ModalAction } from '@/Components/Shared/Modal'
|
||||
import ModalOverlay from '@/Components/Shared/ModalOverlay'
|
||||
|
||||
type Props = {
|
||||
auth: TwoFactorAuth
|
||||
@@ -17,6 +19,74 @@ type Props = {
|
||||
}
|
||||
|
||||
const TwoFactorAuthView: FunctionComponent<Props> = ({ auth, application }) => {
|
||||
const shouldShowActivationModal = auth.status !== 'fetching' && is2FAActivation(auth.status)
|
||||
|
||||
const activationModalTitle = shouldShowActivationModal
|
||||
? auth.status.activationStep === 'scan-qr-code'
|
||||
? 'Step 1 of 3 - Scan QR code'
|
||||
: auth.status.activationStep === 'save-secret-key'
|
||||
? 'Step 2 of 3 - Save secret key'
|
||||
: auth.status.activationStep === 'verification'
|
||||
? 'Step 3 of 3 - Verification'
|
||||
: auth.status.activationStep === 'success'
|
||||
? 'Successfully Enabled'
|
||||
: ''
|
||||
: ''
|
||||
|
||||
const closeActivationModal = () => {
|
||||
if (auth.status === 'fetching') {
|
||||
return
|
||||
}
|
||||
if (!is2FAActivation(auth.status)) {
|
||||
return
|
||||
}
|
||||
if (auth.status.activationStep === 'success') {
|
||||
auth.status.finishActivation()
|
||||
}
|
||||
auth.status.cancelActivation()
|
||||
}
|
||||
|
||||
const activationModalActions: ModalAction[] = shouldShowActivationModal
|
||||
? [
|
||||
{
|
||||
label: 'Cancel',
|
||||
onClick: auth.status.cancelActivation,
|
||||
type: 'cancel',
|
||||
mobileSlot: 'left',
|
||||
hidden: auth.status.activationStep !== 'scan-qr-code',
|
||||
},
|
||||
{
|
||||
label: 'Back',
|
||||
onClick:
|
||||
auth.status.activationStep === 'save-secret-key'
|
||||
? auth.status.openScanQRCode
|
||||
: auth.status.openSaveSecretKey,
|
||||
type: 'cancel',
|
||||
mobileSlot: 'left',
|
||||
hidden: auth.status.activationStep !== 'save-secret-key' && auth.status.activationStep !== 'verification',
|
||||
},
|
||||
{
|
||||
label: 'Next',
|
||||
onClick:
|
||||
auth.status.activationStep === 'scan-qr-code'
|
||||
? auth.status.openSaveSecretKey
|
||||
: auth.status.activationStep === 'save-secret-key'
|
||||
? auth.status.openVerification
|
||||
: auth.status.enable2FA,
|
||||
type: 'primary',
|
||||
mobileSlot: 'right',
|
||||
hidden: auth.status.activationStep === 'success',
|
||||
},
|
||||
{
|
||||
label: 'Finish',
|
||||
onClick: auth.status.finishActivation,
|
||||
type: 'primary',
|
||||
mobileSlot: 'right',
|
||||
hidden: auth.status.activationStep !== 'success',
|
||||
},
|
||||
]
|
||||
: []
|
||||
|
||||
return (
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
@@ -45,9 +115,11 @@ const TwoFactorAuthView: FunctionComponent<Props> = ({ auth, application }) => {
|
||||
</PreferencesSegment>
|
||||
)}
|
||||
</PreferencesGroup>
|
||||
{auth.status !== 'fetching' && is2FAActivation(auth.status) && (
|
||||
<TwoFactorActivationView activation={auth.status} />
|
||||
)}
|
||||
<ModalOverlay isOpen={shouldShowActivationModal} onDismiss={closeActivationModal}>
|
||||
<Modal title={activationModalTitle} close={closeActivationModal} actions={activationModalActions}>
|
||||
{shouldShowActivationModal && <TwoFactorActivationView activation={auth.status} />}
|
||||
</Modal>
|
||||
</ModalOverlay>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import Button from '@/Components/Button/Button'
|
||||
import ModalDialog from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||
import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
@@ -12,18 +7,12 @@ type Props = {
|
||||
activation: TwoFactorActivation
|
||||
}
|
||||
|
||||
const TwoFactorSuccess: FunctionComponent<Props> = ({ activation: act }) => (
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={act.finishActivation}>Successfully Enabled</ModalDialogLabel>
|
||||
<ModalDialogDescription className="flex flex-row items-center">
|
||||
<div className="flex flex-row items-center justify-center pt-2">
|
||||
<Subtitle>Two-factor authentication has been successfully enabled for your account.</Subtitle>
|
||||
</div>
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
<Button className="min-w-20" primary label="Finish" onClick={act.finishActivation} />
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
const TwoFactorSuccess: FunctionComponent<Props> = () => (
|
||||
<div className="flex flex-row items-center px-4 py-4">
|
||||
<div className="flex flex-row items-center justify-center pt-2">
|
||||
<Subtitle>Two-factor authentication has been successfully enabled for your account.</Subtitle>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default observer(TwoFactorSuccess)
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import Button from '@/Components/Button/Button'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import Bullet from './Bullet'
|
||||
import { TwoFactorActivation } from './TwoFactorActivation'
|
||||
import ModalDialog from '@/Components/Shared/ModalDialog'
|
||||
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||
|
||||
type Props = {
|
||||
activation: TwoFactorActivation
|
||||
@@ -17,47 +12,40 @@ const Verification: FunctionComponent<Props> = ({ activation: act }) => {
|
||||
const secretKeyClass = act.verificationStatus === 'invalid-secret' ? 'border-danger' : ''
|
||||
const authTokenClass = act.verificationStatus === 'invalid-auth-code' ? 'border-danger' : ''
|
||||
return (
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={act.cancelActivation}>Step 3 of 3 - Verification</ModalDialogLabel>
|
||||
<ModalDialogDescription className="h-33 flex flex-row items-center">
|
||||
<div className="flex flex-grow flex-col gap-4">
|
||||
<div className="flex flex-row flex-wrap items-center gap-1">
|
||||
<div className="text-sm">
|
||||
<Bullet className="align-middle" />
|
||||
<span className="align-middle">
|
||||
Enter your <b>secret key</b>:
|
||||
</span>
|
||||
</div>
|
||||
<DecoratedInput
|
||||
className={{ container: `ml-2 w-full md:w-96 ${secretKeyClass}` }}
|
||||
onChange={act.setInputSecretKey}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap items-center gap-1">
|
||||
<div className="text-sm">
|
||||
<Bullet className="align-middle" />
|
||||
<span className="align-middle">
|
||||
Verify the <b>authentication code</b> generated by your authenticator app:
|
||||
</span>
|
||||
</div>
|
||||
<DecoratedInput
|
||||
className={{ container: `ml-2 w-full md:w-30 ${authTokenClass}` }}
|
||||
onChange={act.setInputOtpToken}
|
||||
/>
|
||||
<div className="h-33 flex flex-row items-center px-4 py-4">
|
||||
<div className="flex flex-grow flex-col gap-4">
|
||||
<div className="flex flex-row flex-wrap items-center gap-1">
|
||||
<div className="text-sm">
|
||||
<Bullet className="align-middle" />
|
||||
<span className="align-middle">
|
||||
Enter your <b>secret key</b>:
|
||||
</span>
|
||||
</div>
|
||||
<DecoratedInput
|
||||
className={{ container: `ml-2 w-full md:w-96 ${secretKeyClass}` }}
|
||||
onChange={act.setInputSecretKey}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap items-center gap-1">
|
||||
<div className="text-sm">
|
||||
<Bullet className="align-middle" />
|
||||
<span className="align-middle">
|
||||
Verify the <b>authentication code</b> generated by your authenticator app:
|
||||
</span>
|
||||
</div>
|
||||
<DecoratedInput
|
||||
className={{ container: `ml-2 w-full md:w-30 ${authTokenClass}` }}
|
||||
onChange={act.setInputOtpToken}
|
||||
/>
|
||||
</div>
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
{act.verificationStatus === 'invalid-auth-code' && (
|
||||
<div className="flex-grow text-sm text-danger">Incorrect authentication code, please try again.</div>
|
||||
)}
|
||||
{act.verificationStatus === 'invalid-secret' && (
|
||||
<div className="flex-grow text-sm text-danger">Incorrect secret key, please try again.</div>
|
||||
)}
|
||||
<Button className="min-w-20" label="Back" onClick={act.openSaveSecretKey} />
|
||||
<Button className="min-w-20" primary label="Next" onClick={act.enable2FA} />
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user