feat: Show warning modal when Merge local data option is unchecked (#2953) [skip e2e]
* feat: Show warning modal when Merge local data option is unchecked * keep checkbox available when using recovery sign in
This commit is contained in:
committed by
GitHub
parent
11e36e1d4c
commit
531cc70667
@@ -103,7 +103,7 @@ export class SignInWithRecoveryCodes implements UseCaseInterface<void> {
|
|||||||
payload: {
|
payload: {
|
||||||
payload: {
|
payload: {
|
||||||
ephemeral: false,
|
ephemeral: false,
|
||||||
mergeLocal: false,
|
mergeLocal: dto.mergeLocal ?? true,
|
||||||
awaitSync: true,
|
awaitSync: true,
|
||||||
checkIntegrity: false,
|
checkIntegrity: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ export interface SignInWithRecoveryCodesDTO {
|
|||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
hvmToken?: string
|
hvmToken?: string
|
||||||
|
mergeLocal?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import AlertDialog from '@/Components/AlertDialog/AlertDialog'
|
||||||
|
import Button from '@/Components/Button/Button'
|
||||||
|
import Icon from '@/Components/Icon/Icon'
|
||||||
|
import { FunctionComponent } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void
|
||||||
|
onConfirm: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmNoMergeDialog: FunctionComponent<Props> = ({ onClose, onConfirm }) => {
|
||||||
|
return (
|
||||||
|
<AlertDialog closeDialog={onClose}>
|
||||||
|
<div className="flex items-center justify-between text-lg font-bold">
|
||||||
|
Delete local data?
|
||||||
|
<button className="rounded p-1 font-bold hover:bg-contrast" onClick={onClose}>
|
||||||
|
<Icon type="close" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="sk-panel-row">
|
||||||
|
<div>
|
||||||
|
<p className="text-base text-foreground lg:text-sm">
|
||||||
|
You have chosen not to merge your local data. If you proceed, your local notes and tags will be permanently
|
||||||
|
deleted and replaced with data from your account. This action cannot be undone.
|
||||||
|
</p>
|
||||||
|
<p className="mt-2 text-base font-semibold text-danger lg:text-sm">
|
||||||
|
Are you sure you want to continue without merging?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex justify-end gap-2">
|
||||||
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
|
<Button primary colorStyle="danger" onClick={onConfirm}>
|
||||||
|
Delete Local Data and Continue
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConfirmNoMergeDialog
|
||||||
@@ -18,6 +18,8 @@ import IconButton from '@/Components/Button/IconButton'
|
|||||||
import { useApplication } from '../ApplicationProvider'
|
import { useApplication } from '../ApplicationProvider'
|
||||||
import { useCaptcha } from '@/Hooks/useCaptcha'
|
import { useCaptcha } from '@/Hooks/useCaptcha'
|
||||||
import { isErrorResponse } from '@standardnotes/snjs'
|
import { isErrorResponse } from '@standardnotes/snjs'
|
||||||
|
import MergeLocalDataCheckbox from './MergeLocalDataCheckbox'
|
||||||
|
import ConfirmNoMergeDialog from './ConfirmNoMergeDialog'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setMenuPane: (pane: AccountMenuPane) => void
|
setMenuPane: (pane: AccountMenuPane) => void
|
||||||
@@ -37,6 +39,7 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
|
|||||||
|
|
||||||
const [hvmToken, setHVMToken] = useState('')
|
const [hvmToken, setHVMToken] = useState('')
|
||||||
const [captchaURL, setCaptchaURL] = useState('')
|
const [captchaURL, setCaptchaURL] = useState('')
|
||||||
|
const [showNoMergeConfirmation, setShowNoMergeConfirmation] = useState(false)
|
||||||
|
|
||||||
const register = useCallback(() => {
|
const register = useCallback(() => {
|
||||||
setIsRegistering(true)
|
setIsRegistering(true)
|
||||||
@@ -124,9 +127,14 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (notesAndTagsCount > 0 && !shouldMergeLocal) {
|
||||||
|
setShowNoMergeConfirmation(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
checkIfCaptchaRequiredAndRegister()
|
checkIfCaptchaRequiredAndRegister()
|
||||||
},
|
},
|
||||||
[checkIfCaptchaRequiredAndRegister, confirmPassword, password],
|
[checkIfCaptchaRequiredAndRegister, confirmPassword, password, notesAndTagsCount, shouldMergeLocal],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleKeyDown: KeyboardEventHandler = useCallback(
|
const handleKeyDown: KeyboardEventHandler = useCallback(
|
||||||
@@ -145,6 +153,15 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
|
|||||||
setMenuPane(AccountMenuPane.Register)
|
setMenuPane(AccountMenuPane.Register)
|
||||||
}, [setMenuPane])
|
}, [setMenuPane])
|
||||||
|
|
||||||
|
const closeNoMergeConfirmation = useCallback(() => {
|
||||||
|
setShowNoMergeConfirmation(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const confirmRegisterWithoutMerge = useCallback(() => {
|
||||||
|
setShowNoMergeConfirmation(false)
|
||||||
|
checkIfCaptchaRequiredAndRegister()
|
||||||
|
}, [checkIfCaptchaRequiredAndRegister])
|
||||||
|
|
||||||
const confirmPasswordForm = (
|
const confirmPasswordForm = (
|
||||||
<>
|
<>
|
||||||
<div className="mb-3 px-3 text-sm">
|
<div className="mb-3 px-3 text-sm">
|
||||||
@@ -182,12 +199,11 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
|
|||||||
disabled={isRegistering}
|
disabled={isRegistering}
|
||||||
/>
|
/>
|
||||||
{notesAndTagsCount > 0 ? (
|
{notesAndTagsCount > 0 ? (
|
||||||
<Checkbox
|
<MergeLocalDataCheckbox
|
||||||
name="should-merge-local"
|
|
||||||
label={`Merge local data (${notesAndTagsCount} notes and tags)`}
|
|
||||||
checked={shouldMergeLocal}
|
checked={shouldMergeLocal}
|
||||||
onChange={handleShouldMergeChange}
|
onChange={handleShouldMergeChange}
|
||||||
disabled={isRegistering}
|
disabled={isRegistering}
|
||||||
|
notesAndTagsCount={notesAndTagsCount}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</form>
|
</form>
|
||||||
@@ -208,6 +224,9 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
|
|||||||
<div className="text-base font-bold">{captchaURL ? 'Human verification' : 'Confirm password'}</div>
|
<div className="text-base font-bold">{captchaURL ? 'Human verification' : 'Confirm password'}</div>
|
||||||
</div>
|
</div>
|
||||||
{captchaURL ? <div className="p-[10px]">{captchaIframe}</div> : confirmPasswordForm}
|
{captchaURL ? <div className="p-[10px]">{captchaIframe}</div> : confirmPasswordForm}
|
||||||
|
{showNoMergeConfirmation && (
|
||||||
|
<ConfirmNoMergeDialog onClose={closeNoMergeConfirmation} onConfirm={confirmRegisterWithoutMerge} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import Icon from '@/Components/Icon/Icon'
|
||||||
|
import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip'
|
||||||
|
import { ChangeEventHandler, FunctionComponent } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
checked: boolean
|
||||||
|
onChange: ChangeEventHandler<HTMLInputElement>
|
||||||
|
disabled?: boolean
|
||||||
|
notesAndTagsCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const MergeLocalDataCheckbox: FunctionComponent<Props> = ({ checked, onChange, disabled, notesAndTagsCount }) => {
|
||||||
|
return (
|
||||||
|
<label htmlFor="should-merge-local" className="fit-content mb-2 flex items-center text-sm">
|
||||||
|
<input
|
||||||
|
className="mr-2 accent-danger"
|
||||||
|
type="checkbox"
|
||||||
|
name="should-merge-local"
|
||||||
|
id="should-merge-local"
|
||||||
|
checked={checked}
|
||||||
|
onChange={onChange}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<span className="text-danger">Merge local data ({notesAndTagsCount} notes and tags)</span>
|
||||||
|
<StyledTooltip
|
||||||
|
label="If unchecked, your local notes and tags will be permanently deleted and replaced with data from your account."
|
||||||
|
showOnMobile
|
||||||
|
className="!z-modal !max-w-[30ch] whitespace-normal"
|
||||||
|
>
|
||||||
|
<button type="button" className="ml-1 rounded-full p-0.5 hover:bg-contrast">
|
||||||
|
<Icon type="info" className="text-danger" size="small" />
|
||||||
|
</button>
|
||||||
|
</StyledTooltip>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MergeLocalDataCheckbox
|
||||||
@@ -13,6 +13,8 @@ import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
|||||||
import { getErrorFromErrorResponse, isErrorResponse, getCaptchaHeader } from '@standardnotes/snjs'
|
import { getErrorFromErrorResponse, isErrorResponse, getCaptchaHeader } from '@standardnotes/snjs'
|
||||||
import { useApplication } from '../ApplicationProvider'
|
import { useApplication } from '../ApplicationProvider'
|
||||||
import { useCaptcha } from '@/Hooks/useCaptcha'
|
import { useCaptcha } from '@/Hooks/useCaptcha'
|
||||||
|
import MergeLocalDataCheckbox from './MergeLocalDataCheckbox'
|
||||||
|
import ConfirmNoMergeDialog from './ConfirmNoMergeDialog'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setMenuPane: (pane: AccountMenuPane) => void
|
setMenuPane: (pane: AccountMenuPane) => void
|
||||||
@@ -34,6 +36,7 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
|
|||||||
const [isPrivateUsername, setIsPrivateUsername] = useState(false)
|
const [isPrivateUsername, setIsPrivateUsername] = useState(false)
|
||||||
|
|
||||||
const [isRecoverySignIn, setIsRecoverySignIn] = useState(false)
|
const [isRecoverySignIn, setIsRecoverySignIn] = useState(false)
|
||||||
|
const [showNoMergeConfirmation, setShowNoMergeConfirmation] = useState(false)
|
||||||
|
|
||||||
const [captchaURL, setCaptchaURL] = useState('')
|
const [captchaURL, setCaptchaURL] = useState('')
|
||||||
const [showCaptcha, setShowCaptcha] = useState(false)
|
const [showCaptcha, setShowCaptcha] = useState(false)
|
||||||
@@ -139,6 +142,7 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
|
|||||||
username: email,
|
username: email,
|
||||||
password: password,
|
password: password,
|
||||||
hvmToken,
|
hvmToken,
|
||||||
|
mergeLocal: shouldMergeLocal,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.isFailed()) {
|
if (result.isFailed()) {
|
||||||
@@ -166,7 +170,15 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsSigningIn(false)
|
setIsSigningIn(false)
|
||||||
})
|
})
|
||||||
}, [application.accountMenuController, application.signInWithRecoveryCodes, email, hvmToken, password, recoveryCodes])
|
}, [
|
||||||
|
application.accountMenuController,
|
||||||
|
application.signInWithRecoveryCodes,
|
||||||
|
email,
|
||||||
|
hvmToken,
|
||||||
|
password,
|
||||||
|
recoveryCodes,
|
||||||
|
shouldMergeLocal,
|
||||||
|
])
|
||||||
|
|
||||||
const onPrivateUsernameChange = useCallback(
|
const onPrivateUsernameChange = useCallback(
|
||||||
(newisPrivateUsername: boolean, privateUsernameIdentifier?: string) => {
|
(newisPrivateUsername: boolean, privateUsernameIdentifier?: string) => {
|
||||||
@@ -189,13 +201,18 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (notesAndTagsCount > 0 && !shouldMergeLocal) {
|
||||||
|
setShowNoMergeConfirmation(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (isRecoverySignIn) {
|
if (isRecoverySignIn) {
|
||||||
recoverySignIn()
|
recoverySignIn()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
signIn()
|
signIn()
|
||||||
}, [email, isRecoverySignIn, password, recoverySignIn, signIn])
|
}, [email, isRecoverySignIn, password, recoverySignIn, signIn, notesAndTagsCount, shouldMergeLocal])
|
||||||
|
|
||||||
const handleSignInFormSubmit = useCallback(
|
const handleSignInFormSubmit = useCallback(
|
||||||
(e: React.SyntheticEvent) => {
|
(e: React.SyntheticEvent) => {
|
||||||
@@ -272,12 +289,11 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
|
|||||||
onChange={handleEphemeralChange}
|
onChange={handleEphemeralChange}
|
||||||
/>
|
/>
|
||||||
{notesAndTagsCount > 0 ? (
|
{notesAndTagsCount > 0 ? (
|
||||||
<Checkbox
|
<MergeLocalDataCheckbox
|
||||||
name="should-merge-local"
|
|
||||||
label={`Merge local data (${notesAndTagsCount} notes and tags)`}
|
|
||||||
checked={shouldMergeLocal}
|
checked={shouldMergeLocal}
|
||||||
disabled={isSigningIn}
|
|
||||||
onChange={handleShouldMergeChange}
|
onChange={handleShouldMergeChange}
|
||||||
|
disabled={isSigningIn}
|
||||||
|
notesAndTagsCount={notesAndTagsCount}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -291,6 +307,19 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const closeNoMergeConfirmation = useCallback(() => {
|
||||||
|
setShowNoMergeConfirmation(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const confirmSignInWithoutMerge = useCallback(() => {
|
||||||
|
setShowNoMergeConfirmation(false)
|
||||||
|
if (isRecoverySignIn) {
|
||||||
|
recoverySignIn()
|
||||||
|
} else {
|
||||||
|
signIn()
|
||||||
|
}
|
||||||
|
}, [signIn, isRecoverySignIn, recoverySignIn])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-3 mt-1 flex items-center px-3">
|
<div className="mb-3 mt-1 flex items-center px-3">
|
||||||
@@ -305,6 +334,9 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
|
|||||||
<div className="text-base font-bold">{showCaptcha ? 'Human verification' : 'Sign in'}</div>
|
<div className="text-base font-bold">{showCaptcha ? 'Human verification' : 'Sign in'}</div>
|
||||||
</div>
|
</div>
|
||||||
{showCaptcha ? <div className="p-[10px]">{captchaIframe}</div> : signInForm}
|
{showCaptcha ? <div className="p-[10px]">{captchaIframe}</div> : signInForm}
|
||||||
|
{showNoMergeConfirmation && (
|
||||||
|
<ConfirmNoMergeDialog onClose={closeNoMergeConfirmation} onConfirm={confirmSignInWithoutMerge} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user