mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-08-28 11:46:02 +08:00
Adds check for unsaved changes (#599)
This commit is contained in:
parent
dd4a8528ca
commit
33119ef1d3
2 changed files with 50 additions and 12 deletions
|
@ -4,7 +4,7 @@ import './EmailEditor.css'
|
||||||
import Button, { LinkButton } from '../../../ui/Button'
|
import Button, { LinkButton } from '../../../ui/Button'
|
||||||
import api from '../../../api'
|
import api from '../../../api'
|
||||||
import { Campaign, Resource, Template } from '../../../types'
|
import { Campaign, Resource, Template } from '../../../types'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useBlocker, useNavigate } from 'react-router-dom'
|
||||||
import { localeState } from '../CampaignDetail'
|
import { localeState } from '../CampaignDetail'
|
||||||
import Modal from '../../../ui/Modal'
|
import Modal from '../../../ui/Modal'
|
||||||
import HtmlEditor from './HtmlEditor'
|
import HtmlEditor from './HtmlEditor'
|
||||||
|
@ -29,6 +29,7 @@ export default function EmailEditor() {
|
||||||
const [template, setTemplate] = useState<Template | undefined>(templates[0])
|
const [template, setTemplate] = useState<Template | undefined>(templates[0])
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
const [showConfig, setShowConfig] = useState(false)
|
const [showConfig, setShowConfig] = useState(false)
|
||||||
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.resources.all(project.id)
|
api.resources.all(project.id)
|
||||||
|
@ -36,6 +37,19 @@ export default function EmailEditor() {
|
||||||
.catch(() => setResources([]))
|
.catch(() => setResources([]))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const blocker = useBlocker(
|
||||||
|
({ currentLocation, nextLocation }) => hasUnsavedChanges && currentLocation.pathname !== nextLocation.pathname,
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (blocker.state !== 'blocked') return
|
||||||
|
if (confirm(t('confirm_unsaved_changes'))) {
|
||||||
|
blocker.proceed()
|
||||||
|
} else {
|
||||||
|
blocker.reset()
|
||||||
|
}
|
||||||
|
}, [blocker.state])
|
||||||
|
|
||||||
async function handleTemplateSave({ id, type, data }: Template) {
|
async function handleTemplateSave({ id, type, data }: Template) {
|
||||||
setIsSaving(true)
|
setIsSaving(true)
|
||||||
try {
|
try {
|
||||||
|
@ -46,10 +60,16 @@ export default function EmailEditor() {
|
||||||
setCampaign(newCampaign)
|
setCampaign(newCampaign)
|
||||||
toast.success(t('template_saved'))
|
toast.success(t('template_saved'))
|
||||||
} finally {
|
} finally {
|
||||||
|
setHasUnsavedChanges(false)
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTemplateChange = (change: SetStateAction<Template | undefined>) => {
|
||||||
|
setHasUnsavedChanges(true)
|
||||||
|
setTemplate(change)
|
||||||
|
}
|
||||||
|
|
||||||
const campaignChange = (change: SetStateAction<Campaign>) => {
|
const campaignChange = (change: SetStateAction<Campaign>) => {
|
||||||
setCampaign(change)
|
setCampaign(change)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +114,7 @@ export default function EmailEditor() {
|
||||||
<Suspense key={template.id} fallback={null}>
|
<Suspense key={template.id} fallback={null}>
|
||||||
<VisualEditor
|
<VisualEditor
|
||||||
template={template}
|
template={template}
|
||||||
setTemplate={setTemplate}
|
setTemplate={handleTemplateChange}
|
||||||
resources={resources}
|
resources={resources}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
@ -102,7 +122,7 @@ export default function EmailEditor() {
|
||||||
: <HtmlEditor
|
: <HtmlEditor
|
||||||
template={template}
|
template={template}
|
||||||
key={template.id}
|
key={template.id}
|
||||||
setTemplate={setTemplate} />
|
setTemplate={handleTemplateChange} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createElement, DragEventHandler, Fragment, memo, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
import { createElement, DragEventHandler, Fragment, memo, ReactNode, SetStateAction, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useBlocker, useNavigate } from 'react-router-dom'
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
addEdge,
|
||||||
Background,
|
Background,
|
||||||
|
@ -493,14 +493,12 @@ export default function JourneyEditor() {
|
||||||
const journeyId = journey.id
|
const journeyId = journey.id
|
||||||
|
|
||||||
const loadSteps = useCallback(async () => {
|
const loadSteps = useCallback(async () => {
|
||||||
|
|
||||||
const steps = await api.journeys.steps.get(project.id, journeyId)
|
const steps = await api.journeys.steps.get(project.id, journeyId)
|
||||||
|
|
||||||
const { edges, nodes } = stepsToNodes(steps)
|
const { edges, nodes } = stepsToNodes(steps)
|
||||||
|
|
||||||
setNodes(nodes)
|
setNodes(nodes)
|
||||||
setEdges(edges)
|
setEdges(edges)
|
||||||
|
|
||||||
}, [project, journeyId])
|
}, [project, journeyId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -508,10 +506,29 @@ export default function JourneyEditor() {
|
||||||
}, [loadSteps])
|
}, [loadSteps])
|
||||||
|
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
|
||||||
|
|
||||||
|
const blocker = useBlocker(
|
||||||
|
({ currentLocation, nextLocation }) => hasUnsavedChanges && currentLocation.pathname !== nextLocation.pathname,
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (blocker.state !== 'blocked') return
|
||||||
|
if (confirm(t('confirm_unsaved_changes'))) {
|
||||||
|
blocker.proceed()
|
||||||
|
} else {
|
||||||
|
blocker.reset()
|
||||||
|
}
|
||||||
|
}, [blocker.state])
|
||||||
|
|
||||||
|
const handleSetNodes = (nodes: SetStateAction<Array<Node<any, string | undefined>>>) => {
|
||||||
|
setHasUnsavedChanges(true)
|
||||||
|
setNodes(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
const saveSteps = useCallback(async () => {
|
const saveSteps = useCallback(async () => {
|
||||||
|
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stepMap = await api.journeys.steps.set(project.id, journey.id, nodesToSteps(nodes, edges))
|
const stepMap = await api.journeys.steps.set(project.id, journey.id, nodesToSteps(nodes, edges))
|
||||||
|
|
||||||
|
@ -524,6 +541,7 @@ export default function JourneyEditor() {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(`Unable to save: ${error}`)
|
toast.error(`Unable to save: ${error}`)
|
||||||
} finally {
|
} finally {
|
||||||
|
setHasUnsavedChanges(false)
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
}
|
}
|
||||||
}, [project, journey, nodes, edges])
|
}, [project, journey, nodes, edges])
|
||||||
|
@ -580,7 +598,7 @@ export default function JourneyEditor() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
setNodes(nds => nds.concat(newStep))
|
handleSetNodes(nds => nds.concat(newStep))
|
||||||
|
|
||||||
}, [setNodes, flowInstance, project, journey])
|
}, [setNodes, flowInstance, project, journey])
|
||||||
|
|
||||||
|
@ -658,7 +676,7 @@ export default function JourneyEditor() {
|
||||||
label={t('name')}
|
label={t('name')}
|
||||||
name="name"
|
name="name"
|
||||||
value={editNode.data.name ?? ''}
|
value={editNode.data.name ?? ''}
|
||||||
onChange={name => setNodes(nds => nds.map(n => n.id === editNode.id ? { ...n, data: { ...n.data, name } } : n))}
|
onChange={name => handleSetNodes(nds => nds.map(n => n.id === editNode.id ? { ...n, data: { ...n.data, name } } : n))}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
type.hasDataKey && (
|
type.hasDataKey && (
|
||||||
|
@ -667,14 +685,14 @@ export default function JourneyEditor() {
|
||||||
subtitle={t('data_key_description')}
|
subtitle={t('data_key_description')}
|
||||||
name="data_key"
|
name="data_key"
|
||||||
value={editNode.data.data_key}
|
value={editNode.data.data_key}
|
||||||
onChange={data_key => setNodes(nds => nds.map(n => n.id === editNode.id ? { ...n, data: { ...n.data, data_key } } : n))}
|
onChange={data_key => handleSetNodes(nds => nds.map(n => n.id === editNode.id ? { ...n, data: { ...n.data, data_key } } : n))}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
type.Edit && createElement(type.Edit, {
|
type.Edit && createElement(type.Edit, {
|
||||||
value: editNode.data.data ?? {},
|
value: editNode.data.data ?? {},
|
||||||
onChange: data => setNodes(nds => nds.map(n => n.id === editNode.id
|
onChange: data => handleSetNodes(nds => nds.map(n => n.id === editNode.id
|
||||||
? {
|
? {
|
||||||
...editNode,
|
...editNode,
|
||||||
data: {
|
data: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue