refactor: mobile modals (#2173)

This commit is contained in:
Aman Harwara
2023-01-24 19:26:20 +05:30
committed by GitHub
parent 6af95ddfeb
commit 42db3592b6
55 changed files with 1582 additions and 1033 deletions

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
</>
)
}

View File

@@ -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)

View File

@@ -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>
)
}