refactor: mobile modals (#2173)
This commit is contained in:
@@ -1,13 +1,9 @@
|
||||
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 Button from '@/Components/Button/Button'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import { FunctionComponent, useCallback, useMemo, useState } from 'react'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { useBeforeUnload } from '@/Hooks/useBeforeUnload'
|
||||
import ChangeEmailForm from './ChangeEmailForm'
|
||||
import ChangeEmailSuccess from './ChangeEmailSuccess'
|
||||
import Modal, { ModalAction } from '@/Components/Shared/Modal'
|
||||
|
||||
enum SubmitButtonTitles {
|
||||
Default = 'Continue',
|
||||
@@ -37,7 +33,7 @@ const ChangeEmail: FunctionComponent<Props> = ({ onCloseDialog, application }) =
|
||||
|
||||
const applicationAlertService = application.alertService
|
||||
|
||||
const validateCurrentPassword = async () => {
|
||||
const validateCurrentPassword = useCallback(async () => {
|
||||
if (!currentPassword || currentPassword.length === 0) {
|
||||
applicationAlertService.alert('Please enter your current password.').catch(console.error)
|
||||
|
||||
@@ -54,14 +50,14 @@ const ChangeEmail: FunctionComponent<Props> = ({ onCloseDialog, application }) =
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
}, [application, applicationAlertService, currentPassword])
|
||||
|
||||
const resetProgressState = () => {
|
||||
setSubmitButtonTitle(SubmitButtonTitles.Default)
|
||||
setIsContinuing(false)
|
||||
}
|
||||
|
||||
const processEmailChange = async () => {
|
||||
const processEmailChange = useCallback(async () => {
|
||||
await application.downloadBackup()
|
||||
|
||||
setLockContinue(true)
|
||||
@@ -73,17 +69,17 @@ const ChangeEmail: FunctionComponent<Props> = ({ onCloseDialog, application }) =
|
||||
setLockContinue(false)
|
||||
|
||||
return success
|
||||
}
|
||||
}, [application, currentPassword, newEmail])
|
||||
|
||||
const dismiss = () => {
|
||||
const dismiss = useCallback(() => {
|
||||
if (lockContinue) {
|
||||
applicationAlertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
|
||||
} else {
|
||||
onCloseDialog()
|
||||
}
|
||||
}
|
||||
}, [applicationAlertService, lockContinue, onCloseDialog])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const handleSubmit = useCallback(async () => {
|
||||
if (lockContinue || isContinuing) {
|
||||
return
|
||||
}
|
||||
@@ -115,31 +111,43 @@ const ChangeEmail: FunctionComponent<Props> = ({ onCloseDialog, application }) =
|
||||
setIsContinuing(false)
|
||||
setSubmitButtonTitle(SubmitButtonTitles.Finish)
|
||||
setCurrentStep(Steps.FinishStep)
|
||||
}
|
||||
}, [currentStep, dismiss, isContinuing, lockContinue, processEmailChange, validateCurrentPassword])
|
||||
|
||||
const handleDialogClose = () => {
|
||||
const handleDialogClose = useCallback(() => {
|
||||
if (lockContinue) {
|
||||
applicationAlertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
|
||||
} else {
|
||||
onCloseDialog()
|
||||
}
|
||||
}
|
||||
}, [applicationAlertService, lockContinue, onCloseDialog])
|
||||
|
||||
const modalActions = useMemo(
|
||||
(): ModalAction[] => [
|
||||
{
|
||||
label: 'Cancel',
|
||||
onClick: handleDialogClose,
|
||||
type: 'cancel',
|
||||
mobileSlot: 'left',
|
||||
},
|
||||
{
|
||||
label: submitButtonTitle,
|
||||
onClick: handleSubmit,
|
||||
type: 'primary',
|
||||
mobileSlot: 'right',
|
||||
},
|
||||
],
|
||||
[handleDialogClose, handleSubmit, submitButtonTitle],
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={handleDialogClose}>Change Email</ModalDialogLabel>
|
||||
<ModalDialogDescription className="flex flex-row items-center px-4.5">
|
||||
{currentStep === Steps.InitialStep && (
|
||||
<ChangeEmailForm setNewEmail={setNewEmail} setCurrentPassword={setCurrentPassword} />
|
||||
)}
|
||||
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons className="px-4.5">
|
||||
<Button className="min-w-20" primary label={submitButtonTitle} onClick={handleSubmit} />
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
<Modal title="Change Email" close={handleDialogClose} actions={modalActions}>
|
||||
<div className="px-4.5 py-4">
|
||||
{currentStep === Steps.InitialStep && (
|
||||
<ChangeEmailForm setNewEmail={setNewEmail} setCurrentPassword={setCurrentPassword} />
|
||||
)}
|
||||
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import PasswordWizard from '@/Components/PasswordWizard/PasswordWizard'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
import ModalOverlay from '@/Components/Shared/ModalOverlay'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -33,6 +34,8 @@ const Credentials: FunctionComponent<Props> = ({ application }: Props) => {
|
||||
setShouldShowPasswordWizard(false)
|
||||
}, [])
|
||||
|
||||
const closeChangeEmailDialog = () => setIsChangeEmailDialogOpen(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
@@ -55,14 +58,14 @@ const Credentials: FunctionComponent<Props> = ({ application }: Props) => {
|
||||
Current password was set on <span className="font-bold">{passwordCreatedOn}</span>
|
||||
</Text>
|
||||
<Button className="mt-3 min-w-20" label="Change password" onClick={presentPasswordWizard} />
|
||||
{isChangeEmailDialogOpen && (
|
||||
<ChangeEmail onCloseDialog={() => setIsChangeEmailDialogOpen(false)} application={application} />
|
||||
)}
|
||||
<ModalOverlay isOpen={isChangeEmailDialogOpen} onDismiss={closeChangeEmailDialog}>
|
||||
<ChangeEmail onCloseDialog={closeChangeEmailDialog} application={application} />
|
||||
</ModalOverlay>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
{shouldShowPasswordWizard ? (
|
||||
<ModalOverlay isOpen={shouldShowPasswordWizard} onDismiss={dismissPasswordWizard}>
|
||||
<PasswordWizard application={application} dismissModal={dismissPasswordWizard} />
|
||||
) : null}
|
||||
</ModalOverlay>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import { FunctionComponent, useCallback, useMemo, useState } from 'react'
|
||||
|
||||
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 Button from '@/Components/Button/Button'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { isEmailValid } from '@/Utils'
|
||||
import { SubscriptionController } from '@/Controllers/Subscription/SubscriptionController'
|
||||
|
||||
import InviteForm from './InviteForm'
|
||||
import InviteSuccess from './InviteSuccess'
|
||||
import Modal, { ModalAction } from '@/Components/Shared/Modal'
|
||||
|
||||
enum SubmitButtonTitles {
|
||||
Default = 'Send Invite',
|
||||
Default = 'Invite',
|
||||
Sending = 'Sending...',
|
||||
Finish = 'Finish',
|
||||
}
|
||||
@@ -36,7 +32,7 @@ const Invite: FunctionComponent<Props> = ({ onCloseDialog, application, subscrip
|
||||
const [lockContinue, setLockContinue] = useState(false)
|
||||
const [currentStep, setCurrentStep] = useState(Steps.InitialStep)
|
||||
|
||||
const validateInviteeEmail = async () => {
|
||||
const validateInviteeEmail = useCallback(async () => {
|
||||
if (!isEmailValid(inviteeEmail)) {
|
||||
application.alertService
|
||||
.alert('The email you entered has an invalid format. Please review your input and try again.')
|
||||
@@ -46,22 +42,22 @@ const Invite: FunctionComponent<Props> = ({ onCloseDialog, application, subscrip
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}, [application.alertService, inviteeEmail])
|
||||
|
||||
const handleDialogClose = () => {
|
||||
const handleDialogClose = useCallback(() => {
|
||||
if (lockContinue) {
|
||||
application.alertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
|
||||
} else {
|
||||
onCloseDialog()
|
||||
}
|
||||
}
|
||||
}, [application.alertService, lockContinue, onCloseDialog])
|
||||
|
||||
const resetProgressState = () => {
|
||||
setSubmitButtonTitle(SubmitButtonTitles.Default)
|
||||
setIsContinuing(false)
|
||||
}
|
||||
|
||||
const processInvite = async () => {
|
||||
const processInvite = useCallback(async () => {
|
||||
setLockContinue(true)
|
||||
|
||||
const success = await subscriptionState.sendSubscriptionInvitation(inviteeEmail)
|
||||
@@ -69,9 +65,9 @@ const Invite: FunctionComponent<Props> = ({ onCloseDialog, application, subscrip
|
||||
setLockContinue(false)
|
||||
|
||||
return success
|
||||
}
|
||||
}, [inviteeEmail, subscriptionState])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const handleSubmit = useCallback(async () => {
|
||||
if (lockContinue || isContinuing) {
|
||||
return
|
||||
}
|
||||
@@ -107,21 +103,43 @@ const Invite: FunctionComponent<Props> = ({ onCloseDialog, application, subscrip
|
||||
setIsContinuing(false)
|
||||
setSubmitButtonTitle(SubmitButtonTitles.Finish)
|
||||
setCurrentStep(Steps.FinishStep)
|
||||
}
|
||||
}, [
|
||||
application.alertService,
|
||||
currentStep,
|
||||
handleDialogClose,
|
||||
isContinuing,
|
||||
lockContinue,
|
||||
processInvite,
|
||||
validateInviteeEmail,
|
||||
])
|
||||
|
||||
const modalActions = useMemo(
|
||||
(): ModalAction[] => [
|
||||
{
|
||||
label: submitButtonTitle,
|
||||
onClick: handleSubmit,
|
||||
type: 'primary',
|
||||
mobileSlot: 'right',
|
||||
disabled: lockContinue,
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
onClick: handleDialogClose,
|
||||
type: 'cancel',
|
||||
mobileSlot: 'left',
|
||||
hidden: currentStep === Steps.FinishStep,
|
||||
},
|
||||
],
|
||||
[currentStep, handleDialogClose, handleSubmit, lockContinue, submitButtonTitle],
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={handleDialogClose}>Share your Subscription</ModalDialogLabel>
|
||||
<ModalDialogDescription className="flex flex-row items-center px-4.5">
|
||||
{currentStep === Steps.InitialStep && <InviteForm setInviteeEmail={setInviteeEmail} />}
|
||||
{currentStep === Steps.FinishStep && <InviteSuccess />}
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons className="px-4.5">
|
||||
<Button className="min-w-20" primary label={submitButtonTitle} onClick={handleSubmit} />
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
<Modal title="Share your Subscription" close={handleDialogClose} actions={modalActions}>
|
||||
<div className="px-4.5 py-4">
|
||||
{currentStep === Steps.InitialStep && <InviteForm setInviteeEmail={setInviteeEmail} />}
|
||||
{currentStep === Steps.FinishStep && <InviteSuccess />}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import InvitationsList from './InvitationsList'
|
||||
import Invite from './Invite/Invite'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import SharingStatusText from './SharingStatusText'
|
||||
import ModalOverlay from '@/Components/Shared/ModalOverlay'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -28,6 +29,8 @@ const SubscriptionSharing: FunctionComponent<Props> = ({ application, viewContro
|
||||
const isSubscriptionSharingFeatureAvailable =
|
||||
application.features.getFeatureStatus(FeatureIdentifier.SubscriptionSharing) === FeatureStatus.Entitled
|
||||
|
||||
const closeInviteDialog = () => setIsInviteDialogOpen(false)
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
@@ -42,13 +45,13 @@ const SubscriptionSharing: FunctionComponent<Props> = ({ application, viewContro
|
||||
{!subscriptionState.allInvitationsUsed && (
|
||||
<Button className="min-w-20" label="Invite" onClick={() => setIsInviteDialogOpen(true)} />
|
||||
)}
|
||||
{isInviteDialogOpen && (
|
||||
<ModalOverlay isOpen={isInviteDialogOpen} onDismiss={closeInviteDialog}>
|
||||
<Invite
|
||||
onCloseDialog={() => setIsInviteDialogOpen(false)}
|
||||
onCloseDialog={closeInviteDialog}
|
||||
application={application}
|
||||
subscriptionState={subscriptionState}
|
||||
/>
|
||||
)}
|
||||
</ModalOverlay>
|
||||
</div>
|
||||
) : (
|
||||
<NoProSubscription application={application} />
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import Button from '@/Components/Button/Button'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import IconPicker from '@/Components/Icon/IconPicker'
|
||||
import Popover from '@/Components/Popover/Popover'
|
||||
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 Modal, { ModalAction } from '@/Components/Shared/Modal'
|
||||
import Spinner from '@/Components/Spinner/Spinner'
|
||||
import { Platform, SmartViewDefaultIconName, VectorIconNameOrEmoji } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { EditSmartViewModalController } from './EditSmartViewModalController'
|
||||
|
||||
type Props = {
|
||||
@@ -63,15 +59,40 @@ const EditSmartViewModal = ({ controller, platform }: Props) => {
|
||||
}
|
||||
}, [isPredicateJsonValid])
|
||||
|
||||
const modalActions = useMemo(
|
||||
(): ModalAction[] => [
|
||||
{
|
||||
label: 'Delete',
|
||||
onClick: deleteView,
|
||||
disabled: isSaving,
|
||||
type: 'destructive',
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
onClick: closeDialog,
|
||||
disabled: isSaving,
|
||||
type: 'cancel',
|
||||
mobileSlot: 'left',
|
||||
},
|
||||
{
|
||||
label: isSaving ? <Spinner className="h-4.5 w-4.5" /> : 'Save',
|
||||
onClick: saveSmartView,
|
||||
disabled: isSaving,
|
||||
type: 'primary',
|
||||
mobileSlot: 'right',
|
||||
},
|
||||
],
|
||||
[closeDialog, deleteView, isSaving, saveSmartView],
|
||||
)
|
||||
|
||||
if (!view) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={closeDialog}>Edit Smart View "{view.title}"</ModalDialogLabel>
|
||||
<ModalDialogDescription>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Modal title={`Edit Smart View "${view.title}"`} close={closeDialog} actions={modalActions}>
|
||||
<div className="px-4 py-4">
|
||||
<div className="flex h-full flex-col gap-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="text-sm font-semibold">Title:</div>
|
||||
<input
|
||||
@@ -115,9 +136,9 @@ const EditSmartViewModal = ({ controller, platform }: Props) => {
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<div className="flex flex-grow flex-col gap-2.5">
|
||||
<div className="text-sm font-semibold">Predicate:</div>
|
||||
<div className="flex flex-col overflow-hidden rounded-md border border-border">
|
||||
<div className="flex flex-grow flex-col overflow-hidden rounded-md border border-border">
|
||||
<textarea
|
||||
className="h-full min-h-[10rem] w-full flex-grow resize-none bg-default py-1.5 px-2.5 font-mono text-sm"
|
||||
value={predicateJson}
|
||||
@@ -136,19 +157,8 @@ const EditSmartViewModal = ({ controller, platform }: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
<Button className="mr-auto" disabled={isSaving} onClick={deleteView} colorStyle="danger">
|
||||
Delete
|
||||
</Button>
|
||||
<Button disabled={isSaving} onClick={saveSmartView} primary colorStyle="info">
|
||||
{isSaving ? <Spinner className="h-4.5 w-4.5" /> : 'Save'}
|
||||
</Button>
|
||||
<Button disabled={isSaving} onClick={closeDialog}>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ const SmartViewItem = ({ view, onEdit, onDelete }: Props) => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 py-1.5">
|
||||
<Icon type={view.iconString} size="custom" className="h-5.5 w-5.5" />
|
||||
<span className="mr-auto text-sm">{view.title}</span>
|
||||
<Icon type={view.iconString} size="custom" className="h-5.5 w-5.5 flex-shrink-0" />
|
||||
<span className="mr-auto overflow-hidden text-ellipsis text-sm">{view.title}</span>
|
||||
<Button small onClick={onEdit}>
|
||||
Edit
|
||||
</Button>
|
||||
|
||||
@@ -15,6 +15,7 @@ import NoSubscriptionBanner from '@/Components/NoSubscriptionBanner/NoSubscripti
|
||||
import { EditSmartViewModalController } from './EditSmartViewModalController'
|
||||
import { STRING_DELETE_TAG } from '@/Constants/Strings'
|
||||
import { confirmDialog } from '@standardnotes/ui-services'
|
||||
import ModalOverlay from '@/Components/Shared/ModalOverlay'
|
||||
|
||||
type NewType = {
|
||||
application: WebApplication
|
||||
@@ -88,12 +89,15 @@ const SmartViews = ({ application, featuresController }: Props) => {
|
||||
)}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
{!!editSmartViewModalController.view && (
|
||||
<ModalOverlay isOpen={!!editSmartViewModalController.view} onDismiss={editSmartViewModalController.closeDialog}>
|
||||
<EditSmartViewModal controller={editSmartViewModalController} platform={application.platform} />
|
||||
)}
|
||||
{addSmartViewModalController.isAddingSmartView && (
|
||||
</ModalOverlay>
|
||||
<ModalOverlay
|
||||
isOpen={addSmartViewModalController.isAddingSmartView}
|
||||
onDismiss={addSmartViewModalController.closeModal}
|
||||
>
|
||||
<AddSmartViewModal controller={addSmartViewModalController} platform={application.platform} />
|
||||
)}
|
||||
</ModalOverlay>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import PreferencesMenuItem from './PreferencesComponents/MenuItem'
|
||||
import { PreferencesMenu } from './PreferencesMenu'
|
||||
import { PreferenceId } from '@standardnotes/ui-services'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import { classNames } from '@standardnotes/snjs'
|
||||
import { isIOS } from '@/Utils'
|
||||
|
||||
type Props = {
|
||||
menu: PreferencesMenu
|
||||
@@ -53,7 +55,12 @@ const PreferencesMenuView: FunctionComponent<Props> = ({ menu }) => {
|
||||
}, [application])
|
||||
|
||||
return (
|
||||
<div className="border-t border-border bg-default px-5 pt-2 md:border-0 md:bg-contrast md:px-0 md:py-0">
|
||||
<div
|
||||
className={classNames(
|
||||
'border-t border-border bg-default px-5 pt-2 md:border-0 md:bg-contrast md:px-0 md:py-0',
|
||||
isIOS() ? 'pb-safe-bottom' : 'pb-2 md:pb-0',
|
||||
)}
|
||||
>
|
||||
<div className="hidden min-w-55 flex-col overflow-y-auto px-3 py-6 md:flex">
|
||||
{menuItems.map((pref) => (
|
||||
<PreferencesMenuItem
|
||||
@@ -81,6 +88,11 @@ const PreferencesMenuView: FunctionComponent<Props> = ({ menu }) => {
|
||||
onChange={(paneId) => {
|
||||
selectPane(paneId as PreferenceId)
|
||||
}}
|
||||
classNameOverride={{
|
||||
wrapper: 'relative',
|
||||
popover: 'bottom-full w-full max-h-max',
|
||||
}}
|
||||
portal={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,12 +4,13 @@ import { observer } from 'mobx-react-lite'
|
||||
import { PreferencesMenu } from './PreferencesMenu'
|
||||
import PreferencesCanvas from './PreferencesCanvas'
|
||||
import { PreferencesProps } from './PreferencesProps'
|
||||
import { isIOS } from '@/Utils'
|
||||
import { useDisableBodyScrollOnMobile } from '@/Hooks/useDisableBodyScrollOnMobile'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
|
||||
import { ESCAPE_COMMAND } from '@standardnotes/ui-services'
|
||||
import Modal from '../Shared/Modal'
|
||||
import { AlertDialogLabel } from '@reach/alert-dialog'
|
||||
import { classNames } from '@standardnotes/snjs'
|
||||
import { isIOS } from '@/Utils'
|
||||
|
||||
const PreferencesView: FunctionComponent<PreferencesProps> = ({
|
||||
application,
|
||||
@@ -18,8 +19,6 @@ const PreferencesView: FunctionComponent<PreferencesProps> = ({
|
||||
userProvider,
|
||||
mfaProvider,
|
||||
}) => {
|
||||
const isDesktopScreen = useMediaQuery(MediaQueryBreakpoints.md)
|
||||
|
||||
const menu = useMemo(
|
||||
() => new PreferencesMenu(application, viewControllerManager.enableUnfinishedFeatures),
|
||||
[viewControllerManager.enableUnfinishedFeatures, application],
|
||||
@@ -56,26 +55,32 @@ const PreferencesView: FunctionComponent<PreferencesProps> = ({
|
||||
}, [addAndroidBackHandler, closePreferences])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute top-0 left-0 z-preferences flex h-full w-full flex-col bg-default pt-safe-top',
|
||||
isIOS() ? 'pb-safe-bottom' : 'pb-2 md:pb-0',
|
||||
)}
|
||||
style={{
|
||||
top: !isDesktopScreen ? `${document.documentElement.scrollTop}px` : '',
|
||||
<Modal
|
||||
close={closePreferences}
|
||||
title="Preferences"
|
||||
className={{
|
||||
content: 'md:h-full md:!max-h-full md:!w-full',
|
||||
description: 'flex flex-col',
|
||||
}}
|
||||
customHeader={
|
||||
<AlertDialogLabel
|
||||
className={classNames(
|
||||
'flex w-full flex-row items-center justify-between border-b border-solid border-border bg-default px-3 pb-2 md:p-3',
|
||||
isIOS() ? 'pt-safe-top' : 'pt-2',
|
||||
)}
|
||||
>
|
||||
<div className="hidden h-8 w-8 md:block" />
|
||||
<h1 className="text-base font-bold md:text-lg">Your preferences for Standard Notes</h1>
|
||||
<RoundIconButton
|
||||
onClick={() => {
|
||||
closePreferences()
|
||||
}}
|
||||
icon="close"
|
||||
label="Close preferences"
|
||||
/>
|
||||
</AlertDialogLabel>
|
||||
}
|
||||
>
|
||||
<div className="flex w-full flex-row items-center justify-between border-b border-solid border-border bg-default px-3 py-2 md:p-3">
|
||||
<div className="hidden h-8 w-8 md:block" />
|
||||
<h1 className="text-base font-bold md:text-lg">Your preferences for Standard Notes</h1>
|
||||
<RoundIconButton
|
||||
onClick={() => {
|
||||
closePreferences()
|
||||
}}
|
||||
icon="close"
|
||||
label="Close preferences"
|
||||
/>
|
||||
</div>
|
||||
<PreferencesCanvas
|
||||
menu={menu}
|
||||
application={application}
|
||||
@@ -84,7 +89,7 @@ const PreferencesView: FunctionComponent<PreferencesProps> = ({
|
||||
userProvider={userProvider}
|
||||
mfaProvider={mfaProvider}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import PreferencesView from './PreferencesView'
|
||||
import { PreferencesViewWrapperProps } from './PreferencesViewWrapperProps'
|
||||
import { useCommandService } from '../CommandProvider'
|
||||
import { OPEN_PREFERENCES_COMMAND } from '@standardnotes/ui-services'
|
||||
import ModalOverlay from '../Shared/ModalOverlay'
|
||||
|
||||
const PreferencesViewWrapper: FunctionComponent<PreferencesViewWrapperProps> = ({
|
||||
viewControllerManager,
|
||||
@@ -18,18 +19,16 @@ const PreferencesViewWrapper: FunctionComponent<PreferencesViewWrapperProps> = (
|
||||
})
|
||||
}, [commandService, viewControllerManager])
|
||||
|
||||
if (!viewControllerManager.preferencesController?.isOpen) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<PreferencesView
|
||||
closePreferences={() => viewControllerManager.preferencesController.closePreferences()}
|
||||
application={application}
|
||||
viewControllerManager={viewControllerManager}
|
||||
mfaProvider={application}
|
||||
userProvider={application}
|
||||
/>
|
||||
<ModalOverlay isOpen={viewControllerManager.preferencesController?.isOpen} className="p-0">
|
||||
<PreferencesView
|
||||
closePreferences={() => viewControllerManager.preferencesController.closePreferences()}
|
||||
application={application}
|
||||
viewControllerManager={viewControllerManager}
|
||||
mfaProvider={application}
|
||||
userProvider={application}
|
||||
/>
|
||||
</ModalOverlay>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user