Skip to content

Commit b336a4a

Browse files
committed
feat: Show warning modal when Merge local data option is unchecked
1 parent d9cf9f7 commit b336a4a

File tree

6 files changed

+142
-11
lines changed

6 files changed

+142
-11
lines changed

packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export class SignInWithRecoveryCodes implements UseCaseInterface<void> {
103103
payload: {
104104
payload: {
105105
ephemeral: false,
106-
mergeLocal: false,
106+
mergeLocal: dto.mergeLocal ?? true,
107107
awaitSync: true,
108108
checkIntegrity: false,
109109
},

packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodesDTO.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export interface SignInWithRecoveryCodesDTO {
33
username: string
44
password: string
55
hvmToken?: string
6+
mergeLocal?: boolean
67
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import AlertDialog from '@/Components/AlertDialog/AlertDialog'
2+
import Button from '@/Components/Button/Button'
3+
import Icon from '@/Components/Icon/Icon'
4+
import { FunctionComponent } from 'react'
5+
6+
type Props = {
7+
onClose: () => void
8+
onConfirm: () => void
9+
}
10+
11+
const ConfirmNoMergeDialog: FunctionComponent<Props> = ({ onClose, onConfirm }) => {
12+
return (
13+
<AlertDialog closeDialog={onClose}>
14+
<div className="flex items-center justify-between text-lg font-bold">
15+
Delete local data?
16+
<button className="rounded p-1 font-bold hover:bg-contrast" onClick={onClose}>
17+
<Icon type="close" />
18+
</button>
19+
</div>
20+
<div className="sk-panel-row">
21+
<div>
22+
<p className="text-base text-foreground lg:text-sm">
23+
You have chosen not to merge your local data. If you proceed, your local notes and tags will be permanently
24+
deleted and replaced with data from your account. This action cannot be undone.
25+
</p>
26+
<p className="mt-2 text-base font-semibold text-danger lg:text-sm">
27+
Are you sure you want to continue without merging?
28+
</p>
29+
</div>
30+
</div>
31+
<div className="mt-4 flex justify-end gap-2">
32+
<Button onClick={onClose}>Cancel</Button>
33+
<Button primary colorStyle="danger" onClick={onConfirm}>
34+
Delete Local Data and Continue
35+
</Button>
36+
</div>
37+
</AlertDialog>
38+
)
39+
}
40+
41+
export default ConfirmNoMergeDialog

packages/web/src/javascripts/Components/AccountMenu/ConfirmPassword.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import IconButton from '@/Components/Button/IconButton'
1818
import { useApplication } from '../ApplicationProvider'
1919
import { useCaptcha } from '@/Hooks/useCaptcha'
2020
import { isErrorResponse } from '@standardnotes/snjs'
21+
import MergeLocalDataCheckbox from './MergeLocalDataCheckbox'
22+
import ConfirmNoMergeDialog from './ConfirmNoMergeDialog'
2123

2224
type Props = {
2325
setMenuPane: (pane: AccountMenuPane) => void
@@ -37,6 +39,7 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
3739

3840
const [hvmToken, setHVMToken] = useState('')
3941
const [captchaURL, setCaptchaURL] = useState('')
42+
const [showNoMergeConfirmation, setShowNoMergeConfirmation] = useState(false)
4043

4144
const register = useCallback(() => {
4245
setIsRegistering(true)
@@ -124,9 +127,14 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
124127
return
125128
}
126129

130+
if (notesAndTagsCount > 0 && !shouldMergeLocal) {
131+
setShowNoMergeConfirmation(true)
132+
return
133+
}
134+
127135
checkIfCaptchaRequiredAndRegister()
128136
},
129-
[checkIfCaptchaRequiredAndRegister, confirmPassword, password],
137+
[checkIfCaptchaRequiredAndRegister, confirmPassword, password, notesAndTagsCount, shouldMergeLocal],
130138
)
131139

132140
const handleKeyDown: KeyboardEventHandler = useCallback(
@@ -145,6 +153,15 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
145153
setMenuPane(AccountMenuPane.Register)
146154
}, [setMenuPane])
147155

156+
const closeNoMergeConfirmation = useCallback(() => {
157+
setShowNoMergeConfirmation(false)
158+
}, [])
159+
160+
const confirmRegisterWithoutMerge = useCallback(() => {
161+
setShowNoMergeConfirmation(false)
162+
checkIfCaptchaRequiredAndRegister()
163+
}, [checkIfCaptchaRequiredAndRegister])
164+
148165
const confirmPasswordForm = (
149166
<>
150167
<div className="mb-3 px-3 text-sm">
@@ -182,12 +199,11 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
182199
disabled={isRegistering}
183200
/>
184201
{notesAndTagsCount > 0 ? (
185-
<Checkbox
186-
name="should-merge-local"
187-
label={`Merge local data (${notesAndTagsCount} notes and tags)`}
202+
<MergeLocalDataCheckbox
188203
checked={shouldMergeLocal}
189204
onChange={handleShouldMergeChange}
190205
disabled={isRegistering}
206+
notesAndTagsCount={notesAndTagsCount}
191207
/>
192208
) : null}
193209
</form>
@@ -208,6 +224,9 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
208224
<div className="text-base font-bold">{captchaURL ? 'Human verification' : 'Confirm password'}</div>
209225
</div>
210226
{captchaURL ? <div className="p-[10px]">{captchaIframe}</div> : confirmPasswordForm}
227+
{showNoMergeConfirmation && (
228+
<ConfirmNoMergeDialog onClose={closeNoMergeConfirmation} onConfirm={confirmRegisterWithoutMerge} />
229+
)}
211230
</>
212231
)
213232
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Icon from '@/Components/Icon/Icon'
2+
import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip'
3+
import { ChangeEventHandler, FunctionComponent } from 'react'
4+
5+
type Props = {
6+
checked: boolean
7+
onChange: ChangeEventHandler<HTMLInputElement>
8+
disabled?: boolean
9+
notesAndTagsCount: number
10+
}
11+
12+
const MergeLocalDataCheckbox: FunctionComponent<Props> = ({ checked, onChange, disabled, notesAndTagsCount }) => {
13+
return (
14+
<label htmlFor="should-merge-local" className="fit-content mb-2 flex items-center text-sm">
15+
<input
16+
className="mr-2 accent-danger"
17+
type="checkbox"
18+
name="should-merge-local"
19+
id="should-merge-local"
20+
checked={checked}
21+
onChange={onChange}
22+
disabled={disabled}
23+
/>
24+
<span className="text-danger">Merge local data ({notesAndTagsCount} notes and tags)</span>
25+
<StyledTooltip
26+
label="If unchecked, your local notes and tags will be permanently deleted and replaced with data from your account."
27+
showOnMobile
28+
className="!z-modal !max-w-[30ch] whitespace-normal"
29+
>
30+
<button type="button" className="ml-1 rounded-full p-0.5 hover:bg-contrast">
31+
<Icon type="info" className="text-danger" size="small" />
32+
</button>
33+
</StyledTooltip>
34+
</label>
35+
)
36+
}
37+
38+
export default MergeLocalDataCheckbox

packages/web/src/javascripts/Components/AccountMenu/SignIn.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import HorizontalSeparator from '../Shared/HorizontalSeparator'
1313
import { getErrorFromErrorResponse, isErrorResponse, getCaptchaHeader } from '@standardnotes/snjs'
1414
import { useApplication } from '../ApplicationProvider'
1515
import { useCaptcha } from '@/Hooks/useCaptcha'
16+
import MergeLocalDataCheckbox from './MergeLocalDataCheckbox'
17+
import ConfirmNoMergeDialog from './ConfirmNoMergeDialog'
1618

1719
type Props = {
1820
setMenuPane: (pane: AccountMenuPane) => void
@@ -34,6 +36,7 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
3436
const [isPrivateUsername, setIsPrivateUsername] = useState(false)
3537

3638
const [isRecoverySignIn, setIsRecoverySignIn] = useState(false)
39+
const [showNoMergeConfirmation, setShowNoMergeConfirmation] = useState(false)
3740

3841
const [captchaURL, setCaptchaURL] = useState('')
3942
const [showCaptcha, setShowCaptcha] = useState(false)
@@ -139,6 +142,7 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
139142
username: email,
140143
password: password,
141144
hvmToken,
145+
mergeLocal: shouldMergeLocal,
142146
})
143147
.then((result) => {
144148
if (result.isFailed()) {
@@ -166,7 +170,15 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
166170
.finally(() => {
167171
setIsSigningIn(false)
168172
})
169-
}, [application.accountMenuController, application.signInWithRecoveryCodes, email, hvmToken, password, recoveryCodes])
173+
}, [
174+
application.accountMenuController,
175+
application.signInWithRecoveryCodes,
176+
email,
177+
hvmToken,
178+
password,
179+
recoveryCodes,
180+
shouldMergeLocal,
181+
])
170182

171183
const onPrivateUsernameChange = useCallback(
172184
(newisPrivateUsername: boolean, privateUsernameIdentifier?: string) => {
@@ -189,13 +201,18 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
189201
return
190202
}
191203

204+
if (notesAndTagsCount > 0 && !shouldMergeLocal) {
205+
setShowNoMergeConfirmation(true)
206+
return
207+
}
208+
192209
if (isRecoverySignIn) {
193210
recoverySignIn()
194211
return
195212
}
196213

197214
signIn()
198-
}, [email, isRecoverySignIn, password, recoverySignIn, signIn])
215+
}, [email, isRecoverySignIn, password, recoverySignIn, signIn, notesAndTagsCount, shouldMergeLocal])
199216

200217
const handleSignInFormSubmit = useCallback(
201218
(e: React.SyntheticEvent) => {
@@ -272,12 +289,11 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
272289
onChange={handleEphemeralChange}
273290
/>
274291
{notesAndTagsCount > 0 ? (
275-
<Checkbox
276-
name="should-merge-local"
277-
label={`Merge local data (${notesAndTagsCount} notes and tags)`}
292+
<MergeLocalDataCheckbox
278293
checked={shouldMergeLocal}
279-
disabled={isSigningIn}
280294
onChange={handleShouldMergeChange}
295+
disabled={isSigningIn || isRecoverySignIn}
296+
notesAndTagsCount={notesAndTagsCount}
281297
/>
282298
) : null}
283299
</div>
@@ -291,6 +307,19 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
291307
</>
292308
)
293309

310+
const closeNoMergeConfirmation = useCallback(() => {
311+
setShowNoMergeConfirmation(false)
312+
}, [])
313+
314+
const confirmSignInWithoutMerge = useCallback(() => {
315+
setShowNoMergeConfirmation(false)
316+
if (isRecoverySignIn) {
317+
recoverySignIn()
318+
} else {
319+
signIn()
320+
}
321+
}, [signIn, isRecoverySignIn, recoverySignIn])
322+
294323
return (
295324
<>
296325
<div className="mb-3 mt-1 flex items-center px-3">
@@ -305,6 +334,9 @@ const SignInPane: FunctionComponent<Props> = ({ setMenuPane }) => {
305334
<div className="text-base font-bold">{showCaptcha ? 'Human verification' : 'Sign in'}</div>
306335
</div>
307336
{showCaptcha ? <div className="p-[10px]">{captchaIframe}</div> : signInForm}
337+
{showNoMergeConfirmation && (
338+
<ConfirmNoMergeDialog onClose={closeNoMergeConfirmation} onConfirm={confirmSignInWithoutMerge} />
339+
)}
308340
</>
309341
)
310342
}

0 commit comments

Comments
 (0)