Skip to content

Commit 33119ef

Browse files
authored
Adds check for unsaved changes (#599)
1 parent dd4a852 commit 33119ef

File tree

2 files changed

+50
-12
lines changed

2 files changed

+50
-12
lines changed

apps/ui/src/views/campaign/editor/EmailEditor.tsx

+23-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import './EmailEditor.css'
44
import Button, { LinkButton } from '../../../ui/Button'
55
import api from '../../../api'
66
import { Campaign, Resource, Template } from '../../../types'
7-
import { useNavigate } from 'react-router-dom'
7+
import { useBlocker, useNavigate } from 'react-router-dom'
88
import { localeState } from '../CampaignDetail'
99
import Modal from '../../../ui/Modal'
1010
import HtmlEditor from './HtmlEditor'
@@ -29,13 +29,27 @@ export default function EmailEditor() {
2929
const [template, setTemplate] = useState<Template | undefined>(templates[0])
3030
const [isSaving, setIsSaving] = useState(false)
3131
const [showConfig, setShowConfig] = useState(false)
32+
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
3233

3334
useEffect(() => {
3435
api.resources.all(project.id)
3536
.then(resources => setResources(resources))
3637
.catch(() => setResources([]))
3738
}, [])
3839

40+
const blocker = useBlocker(
41+
({ currentLocation, nextLocation }) => hasUnsavedChanges && currentLocation.pathname !== nextLocation.pathname,
42+
)
43+
44+
useEffect(() => {
45+
if (blocker.state !== 'blocked') return
46+
if (confirm(t('confirm_unsaved_changes'))) {
47+
blocker.proceed()
48+
} else {
49+
blocker.reset()
50+
}
51+
}, [blocker.state])
52+
3953
async function handleTemplateSave({ id, type, data }: Template) {
4054
setIsSaving(true)
4155
try {
@@ -46,10 +60,16 @@ export default function EmailEditor() {
4660
setCampaign(newCampaign)
4761
toast.success(t('template_saved'))
4862
} finally {
63+
setHasUnsavedChanges(false)
4964
setIsSaving(false)
5065
}
5166
}
5267

68+
const handleTemplateChange = (change: SetStateAction<Template | undefined>) => {
69+
setHasUnsavedChanges(true)
70+
setTemplate(change)
71+
}
72+
5373
const campaignChange = (change: SetStateAction<Campaign>) => {
5474
setCampaign(change)
5575
}
@@ -94,15 +114,15 @@ export default function EmailEditor() {
94114
<Suspense key={template.id} fallback={null}>
95115
<VisualEditor
96116
template={template}
97-
setTemplate={setTemplate}
117+
setTemplate={handleTemplateChange}
98118
resources={resources}
99119
/>
100120
</Suspense>
101121
)
102122
: <HtmlEditor
103123
template={template}
104124
key={template.id}
105-
setTemplate={setTemplate} />
125+
setTemplate={handleTemplateChange} />
106126
))
107127
}
108128
</section>

apps/ui/src/views/journey/JourneyEditor.tsx

+27-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { createElement, DragEventHandler, Fragment, memo, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
2-
import { useNavigate } from 'react-router-dom'
1+
import { createElement, DragEventHandler, Fragment, memo, ReactNode, SetStateAction, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
2+
import { useBlocker, useNavigate } from 'react-router-dom'
33
import ReactFlow, {
44
addEdge,
55
Background,
@@ -493,25 +493,42 @@ export default function JourneyEditor() {
493493
const journeyId = journey.id
494494

495495
const loadSteps = useCallback(async () => {
496-
497496
const steps = await api.journeys.steps.get(project.id, journeyId)
498497

499498
const { edges, nodes } = stepsToNodes(steps)
500499

501500
setNodes(nodes)
502501
setEdges(edges)
503-
504502
}, [project, journeyId])
505503

506504
useEffect(() => {
507505
void loadSteps()
508506
}, [loadSteps])
509507

510508
const [saving, setSaving] = useState(false)
509+
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
510+
511+
const blocker = useBlocker(
512+
({ currentLocation, nextLocation }) => hasUnsavedChanges && currentLocation.pathname !== nextLocation.pathname,
513+
)
514+
515+
useEffect(() => {
516+
if (blocker.state !== 'blocked') return
517+
if (confirm(t('confirm_unsaved_changes'))) {
518+
blocker.proceed()
519+
} else {
520+
blocker.reset()
521+
}
522+
}, [blocker.state])
523+
524+
const handleSetNodes = (nodes: SetStateAction<Array<Node<any, string | undefined>>>) => {
525+
setHasUnsavedChanges(true)
526+
setNodes(nodes)
527+
}
528+
511529
const saveSteps = useCallback(async () => {
512530

513531
setSaving(true)
514-
515532
try {
516533
const stepMap = await api.journeys.steps.set(project.id, journey.id, nodesToSteps(nodes, edges))
517534

@@ -524,6 +541,7 @@ export default function JourneyEditor() {
524541
} catch (error: any) {
525542
toast.error(`Unable to save: ${error}`)
526543
} finally {
544+
setHasUnsavedChanges(false)
527545
setSaving(false)
528546
}
529547
}, [project, journey, nodes, edges])
@@ -580,7 +598,7 @@ export default function JourneyEditor() {
580598
},
581599
}
582600

583-
setNodes(nds => nds.concat(newStep))
601+
handleSetNodes(nds => nds.concat(newStep))
584602

585603
}, [setNodes, flowInstance, project, journey])
586604

@@ -658,7 +676,7 @@ export default function JourneyEditor() {
658676
label={t('name')}
659677
name="name"
660678
value={editNode.data.name ?? ''}
661-
onChange={name => setNodes(nds => nds.map(n => n.id === editNode.id ? { ...n, data: { ...n.data, name } } : n))}
679+
onChange={name => handleSetNodes(nds => nds.map(n => n.id === editNode.id ? { ...n, data: { ...n.data, name } } : n))}
662680
/>
663681
{
664682
type.hasDataKey && (
@@ -667,14 +685,14 @@ export default function JourneyEditor() {
667685
subtitle={t('data_key_description')}
668686
name="data_key"
669687
value={editNode.data.data_key}
670-
onChange={data_key => setNodes(nds => nds.map(n => n.id === editNode.id ? { ...n, data: { ...n.data, data_key } } : n))}
688+
onChange={data_key => handleSetNodes(nds => nds.map(n => n.id === editNode.id ? { ...n, data: { ...n.data, data_key } } : n))}
671689
/>
672690
)
673691
}
674692
{
675693
type.Edit && createElement(type.Edit, {
676694
value: editNode.data.data ?? {},
677-
onChange: data => setNodes(nds => nds.map(n => n.id === editNode.id
695+
onChange: data => handleSetNodes(nds => nds.map(n => n.id === editNode.id
678696
? {
679697
...editNode,
680698
data: {

0 commit comments

Comments
 (0)