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:
Antonella Sgarlatta
2025-11-04 09:51:09 -03:00
committed by GitHub
parent 11e36e1d4c
commit 531cc70667
6 changed files with 142 additions and 11 deletions

View File

@@ -103,7 +103,7 @@ export class SignInWithRecoveryCodes implements UseCaseInterface<void> {
payload: {
payload: {
ephemeral: false,
mergeLocal: false,
mergeLocal: dto.mergeLocal ?? true,
awaitSync: true,
checkIntegrity: false,
},

View File

@@ -3,4 +3,5 @@ export interface SignInWithRecoveryCodesDTO {
username: string
password: string
hvmToken?: string
mergeLocal?: boolean
}

View File

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

View File

@@ -18,6 +18,8 @@ import IconButton from '@/Components/Button/IconButton'
import { useApplication } from '../ApplicationProvider'
import { useCaptcha } from '@/Hooks/useCaptcha'
import { isErrorResponse } from '@standardnotes/snjs'
import MergeLocalDataCheckbox from './MergeLocalDataCheckbox'
import ConfirmNoMergeDialog from './ConfirmNoMergeDialog'
type Props = {
setMenuPane: (pane: AccountMenuPane) => void
@@ -37,6 +39,7 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
const [hvmToken, setHVMToken] = useState('')
const [captchaURL, setCaptchaURL] = useState('')
const [showNoMergeConfirmation, setShowNoMergeConfirmation] = useState(false)
const register = useCallback(() => {
setIsRegistering(true)
@@ -124,9 +127,14 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
return
}
if (notesAndTagsCount > 0 && !shouldMergeLocal) {
setShowNoMergeConfirmation(true)
return
}
checkIfCaptchaRequiredAndRegister()
},
[checkIfCaptchaRequiredAndRegister, confirmPassword, password],
[checkIfCaptchaRequiredAndRegister, confirmPassword, password, notesAndTagsCount, shouldMergeLocal],
)
const handleKeyDown: KeyboardEventHandler = useCallback(
@@ -145,6 +153,15 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
setMenuPane(AccountMenuPane.Register)
}, [setMenuPane])
const closeNoMergeConfirmation = useCallback(() => {
setShowNoMergeConfirmation(false)
}, [])
const confirmRegisterWithoutMerge = useCallback(() => {
setShowNoMergeConfirmation(false)
checkIfCaptchaRequiredAndRegister()
}, [checkIfCaptchaRequiredAndRegister])
const confirmPasswordForm = (
<>
<div className="mb-3 px-3 text-sm">
@@ -182,12 +199,11 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
disabled={isRegistering}
/>
{notesAndTagsCount > 0 ? (
<Checkbox
name="should-merge-local"
label={`Merge local data (${notesAndTagsCount} notes and tags)`}
<MergeLocalDataCheckbox
checked={shouldMergeLocal}
onChange={handleShouldMergeChange}
disabled={isRegistering}
notesAndTagsCount={notesAndTagsCount}
/>
) : null}
</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>
{captchaURL ? <div className="p-[10px]">{captchaIframe}</div> : confirmPasswordForm}
{showNoMergeConfirmation && (
<ConfirmNoMergeDialog onClose={closeNoMergeConfirmation} onConfirm={confirmRegisterWithoutMerge} />
)}
</>
)
}

View File

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

View File

@@ -13,6 +13,8 @@ import HorizontalSeparator from '../Shared/HorizontalSeparator'
import { getErrorFromErrorResponse, isErrorResponse, getCaptchaHeader } from '@standardnotes/snjs'
import { useApplication } from '../ApplicationProvider'
import { useCaptcha } from '@/Hooks/useCaptcha'
import MergeLocalDataCheckbox from './MergeLocalDataCheckbox'
import ConfirmNoMergeDialog from './ConfirmNoMergeDialog'
type Props = {
setMenuPane: (pane: AccountMenuPane) => void
@@ -34,6 +36,7 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
const [isPrivateUsername, setIsPrivateUsername] = useState(false)
const [isRecoverySignIn, setIsRecoverySignIn] = useState(false)
const [showNoMergeConfirmation, setShowNoMergeConfirmation] = useState(false)
const [captchaURL, setCaptchaURL] = useState('')
const [showCaptcha, setShowCaptcha] = useState(false)
@@ -139,6 +142,7 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
username: email,
password: password,
hvmToken,
mergeLocal: shouldMergeLocal,
})
.then((result) => {
if (result.isFailed()) {
@@ -166,7 +170,15 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
.finally(() => {
setIsSigningIn(false)
})
}, [application.accountMenuController, application.signInWithRecoveryCodes, email, hvmToken, password, recoveryCodes])
}, [
application.accountMenuController,
application.signInWithRecoveryCodes,
email,
hvmToken,
password,
recoveryCodes,
shouldMergeLocal,
])
const onPrivateUsernameChange = useCallback(
(newisPrivateUsername: boolean, privateUsernameIdentifier?: string) => {
@@ -189,13 +201,18 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
return
}
if (notesAndTagsCount > 0 && !shouldMergeLocal) {
setShowNoMergeConfirmation(true)
return
}
if (isRecoverySignIn) {
recoverySignIn()
return
}
signIn()
}, [email, isRecoverySignIn, password, recoverySignIn, signIn])
}, [email, isRecoverySignIn, password, recoverySignIn, signIn, notesAndTagsCount, shouldMergeLocal])
const handleSignInFormSubmit = useCallback(
(e: React.SyntheticEvent) => {
@@ -272,12 +289,11 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
onChange={handleEphemeralChange}
/>
{notesAndTagsCount > 0 ? (
<Checkbox
name="should-merge-local"
label={`Merge local data (${notesAndTagsCount} notes and tags)`}
<MergeLocalDataCheckbox
checked={shouldMergeLocal}
disabled={isSigningIn}
onChange={handleShouldMergeChange}
disabled={isSigningIn}
notesAndTagsCount={notesAndTagsCount}
/>
) : null}
</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 (
<>
<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>
{showCaptcha ? <div className="p-[10px]">{captchaIframe}</div> : signInForm}
{showNoMergeConfirmation && (
<ConfirmNoMergeDialog onClose={closeNoMergeConfirmation} onConfirm={confirmSignInWithoutMerge} />
)}
</>
)
}