@pushwoosh/smart-components-kit 1.0.57 → 1.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ModalCloningJourney/ModalCloningJourney.d.ts +3 -0
- package/ModalCloningJourney/ModalCloningJourney.js +234 -0
- package/ModalCloningJourney/index.d.ts +1 -0
- package/ModalCloningJourney/index.js +1 -0
- package/ModalCloningJourney/types.d.ts +23 -0
- package/ModalCloningJourney/types.js +1 -0
- package/index.d.ts +1 -0
- package/index.js +2 -1
- package/package.json +2 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import { useParams } from 'react-router-dom';
|
|
4
|
+
import { FieldBox, Modal, ModalBody, ModalHeader, ModalFooter, NativeInput, Select, Button, Banner } from '@pushwoosh/dumb-components';
|
|
5
|
+
import { Horizontal, trackMixpanelEvent, useAsync, Vertical } from '@pushwoosh/kit-helpers';
|
|
6
|
+
import { H3, Paragraph } from '@pushwoosh/kit-typography';
|
|
7
|
+
import { useSPAContext } from '../context';
|
|
8
|
+
export function ModalCloningJourney({
|
|
9
|
+
journeyId,
|
|
10
|
+
journeyTitle,
|
|
11
|
+
onAfterClone,
|
|
12
|
+
onCancel,
|
|
13
|
+
isOpen
|
|
14
|
+
}) {
|
|
15
|
+
var _applicationsOptions$, _errors$title, _errors$destination, _errors$todo;
|
|
16
|
+
const params = useParams();
|
|
17
|
+
const {
|
|
18
|
+
applicationCode
|
|
19
|
+
} = params;
|
|
20
|
+
const [applicationsOptions, setApplicationsOptions] = useState([]);
|
|
21
|
+
const [isCloning, setIsCloning] = useState(false);
|
|
22
|
+
const {
|
|
23
|
+
rpcGateway,
|
|
24
|
+
rpcGatewayJourney
|
|
25
|
+
} = useSPAContext();
|
|
26
|
+
const applications = useAsync(async () => {
|
|
27
|
+
const appsRaw = await rpcGateway.applications.List({
|
|
28
|
+
perPage: 10000
|
|
29
|
+
});
|
|
30
|
+
return appsRaw.applications.map(({
|
|
31
|
+
iconUrl,
|
|
32
|
+
code,
|
|
33
|
+
title,
|
|
34
|
+
platforms
|
|
35
|
+
}) => ({
|
|
36
|
+
code,
|
|
37
|
+
title,
|
|
38
|
+
iconUrl,
|
|
39
|
+
platforms: Object.values(platforms).map(({
|
|
40
|
+
isConfigured,
|
|
41
|
+
isActive,
|
|
42
|
+
properties
|
|
43
|
+
}) => ({
|
|
44
|
+
isConfigured,
|
|
45
|
+
isActive,
|
|
46
|
+
properties
|
|
47
|
+
}))
|
|
48
|
+
}));
|
|
49
|
+
}, [rpcGateway]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (applications.value && applications.value.length > 0) {
|
|
52
|
+
const filteredApplications = applications.value.reduce((acc, item) => {
|
|
53
|
+
if (item.platforms.length > 0) {
|
|
54
|
+
acc.push({
|
|
55
|
+
label: item.title,
|
|
56
|
+
value: item.code
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return acc;
|
|
60
|
+
}, []);
|
|
61
|
+
setApplicationsOptions(filteredApplications);
|
|
62
|
+
}
|
|
63
|
+
}, [applications]);
|
|
64
|
+
const contentOptions = [{
|
|
65
|
+
label: 'Keep messages content',
|
|
66
|
+
value: 'keep'
|
|
67
|
+
}, {
|
|
68
|
+
label: 'Clear messages content',
|
|
69
|
+
value: 'clear'
|
|
70
|
+
}];
|
|
71
|
+
const {
|
|
72
|
+
watch,
|
|
73
|
+
setValue,
|
|
74
|
+
setError,
|
|
75
|
+
clearErrors,
|
|
76
|
+
handleSubmit,
|
|
77
|
+
formState: {
|
|
78
|
+
errors
|
|
79
|
+
},
|
|
80
|
+
reset
|
|
81
|
+
} = useForm({
|
|
82
|
+
defaultValues: {
|
|
83
|
+
title: `${journeyTitle} (copy)`,
|
|
84
|
+
destination: applicationsOptions.length > 0 ? [(_applicationsOptions$ = applicationsOptions.find(({
|
|
85
|
+
value
|
|
86
|
+
}) => value === applicationCode)) === null || _applicationsOptions$ === void 0 ? void 0 : _applicationsOptions$.value].filter(Boolean) : [],
|
|
87
|
+
todo: 'keep'
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const values = watch();
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
var _applicationsOptions$2;
|
|
93
|
+
reset({
|
|
94
|
+
title: `${journeyTitle} (copy)`,
|
|
95
|
+
destination: applicationsOptions.length > 0 ? [(_applicationsOptions$2 = applicationsOptions.find(({
|
|
96
|
+
value
|
|
97
|
+
}) => value === applicationCode)) === null || _applicationsOptions$2 === void 0 ? void 0 : _applicationsOptions$2.value].filter(Boolean) : [],
|
|
98
|
+
todo: 'keep'
|
|
99
|
+
});
|
|
100
|
+
}, [journeyTitle, applicationsOptions, applicationCode, reset]);
|
|
101
|
+
const onFormSubmit = handleSubmit(async data => {
|
|
102
|
+
let hasError = false;
|
|
103
|
+
if (!data.title) {
|
|
104
|
+
setError('title', {
|
|
105
|
+
message: 'Name is required'
|
|
106
|
+
});
|
|
107
|
+
hasError = true;
|
|
108
|
+
}
|
|
109
|
+
if (data.destination.length === 0) {
|
|
110
|
+
setError('destination', {
|
|
111
|
+
message: 'Project is required'
|
|
112
|
+
});
|
|
113
|
+
hasError = true;
|
|
114
|
+
}
|
|
115
|
+
if (!hasError) {
|
|
116
|
+
const {
|
|
117
|
+
todo,
|
|
118
|
+
destination,
|
|
119
|
+
title
|
|
120
|
+
} = data;
|
|
121
|
+
setIsCloning(true);
|
|
122
|
+
try {
|
|
123
|
+
await rpcGatewayJourney.journey.CloneJourneys({
|
|
124
|
+
journeyUuids: [journeyId],
|
|
125
|
+
destAppCodes: destination,
|
|
126
|
+
cloneWithContent: todo === 'keep',
|
|
127
|
+
sourceAppCode: applicationCode,
|
|
128
|
+
title
|
|
129
|
+
});
|
|
130
|
+
if (destination.length > 1 || destination.length === 1 && destination[0] !== applicationCode) {
|
|
131
|
+
trackMixpanelEvent({
|
|
132
|
+
name: 'CloneJourneyToAnotherApp'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
onAfterClone === null || onAfterClone === void 0 || onAfterClone(destination.includes(applicationCode));
|
|
136
|
+
} finally {
|
|
137
|
+
setIsCloning(false);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
const handleDestinationChange = newValue => {
|
|
142
|
+
if (newValue.length > 5) {
|
|
143
|
+
setError('destination', {
|
|
144
|
+
message: 'Maximum 5 projects'
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
setValue('destination', newValue.map(item => item.value));
|
|
148
|
+
if (newValue.length > 0) {
|
|
149
|
+
clearErrors('destination');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const isCloningToAnotherProject = applicationsOptions.length > 0 && (values.destination.length === 1 && values.destination[0] !== applicationCode || values.destination.length > 1);
|
|
154
|
+
const howItWorks = () => {
|
|
155
|
+
if (isCloningToAnotherProject) {
|
|
156
|
+
return values.todo === 'keep' ? 'How it works: Each project will get its own copy of presets. Editing presets applies changes across all campaigns in one project.' : 'How it works: Message content will be cleared in cloned campaigns. You need to select existing presets or create new content for your messages.';
|
|
157
|
+
} else {
|
|
158
|
+
return values.todo === 'keep' ? 'Message content will be copied to the cloned campaign. Editing presets applies changes across all campaigns in one project.' : 'Message content will be cleared in cloned campaign. You need to select an existing preset or create new content for your messages.';
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
return React.createElement(Modal, {
|
|
162
|
+
size: "medium",
|
|
163
|
+
isOpen: isOpen
|
|
164
|
+
}, React.createElement(ModalHeader, null, React.createElement(H3, null, "Clone campaign")), React.createElement(ModalBody, null, React.createElement(Vertical, {
|
|
165
|
+
"$gap": 24
|
|
166
|
+
}, React.createElement(Paragraph, null, "The campaign structure, settings, selected events, segments, and content will be copied. You can duplicate it within the current project or clone it to other projects."), React.createElement(FieldBox, {
|
|
167
|
+
error: (_errors$title = errors.title) === null || _errors$title === void 0 ? void 0 : _errors$title.message,
|
|
168
|
+
title: "Campaign name"
|
|
169
|
+
}, React.createElement(NativeInput, {
|
|
170
|
+
disabled: isCloning,
|
|
171
|
+
placeholder: "ex. Onboarding",
|
|
172
|
+
name: "title",
|
|
173
|
+
value: values.title,
|
|
174
|
+
onChange: e => {
|
|
175
|
+
setValue('title', e.target.value);
|
|
176
|
+
if (e.target.value) {
|
|
177
|
+
clearErrors('title');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
})), React.createElement(FieldBox, {
|
|
181
|
+
title: "Destination projects",
|
|
182
|
+
error: (_errors$destination = errors.destination) === null || _errors$destination === void 0 ? void 0 : _errors$destination.message,
|
|
183
|
+
description: "Maximum 5 projects"
|
|
184
|
+
}, React.createElement(Select, {
|
|
185
|
+
isDisabled: isCloning || applications.loading,
|
|
186
|
+
isLoading: applications.loading,
|
|
187
|
+
isMulti: true,
|
|
188
|
+
options: applicationsOptions,
|
|
189
|
+
name: "destination",
|
|
190
|
+
isClearable: true,
|
|
191
|
+
menuPortalTarget: document.querySelector('body'),
|
|
192
|
+
styles: {
|
|
193
|
+
menuPortal: base => ({
|
|
194
|
+
...base,
|
|
195
|
+
zIndex: 10001
|
|
196
|
+
})
|
|
197
|
+
},
|
|
198
|
+
onChange: handleDestinationChange,
|
|
199
|
+
value: values.destination ? applicationsOptions.filter(app => values.destination.includes(app.value)) : []
|
|
200
|
+
})), React.createElement(FieldBox, {
|
|
201
|
+
title: "What to do with the content in this campaign?",
|
|
202
|
+
error: (_errors$todo = errors.todo) === null || _errors$todo === void 0 ? void 0 : _errors$todo.message
|
|
203
|
+
}, React.createElement(Select, {
|
|
204
|
+
isDisabled: isCloning,
|
|
205
|
+
name: "todo",
|
|
206
|
+
options: contentOptions,
|
|
207
|
+
onChange: params => setValue('todo', (params === null || params === void 0 ? void 0 : params.value) ?? 'keep'),
|
|
208
|
+
value: contentOptions.find(item => item.value === values.todo),
|
|
209
|
+
menuPortalTarget: document.querySelector('body'),
|
|
210
|
+
styles: {
|
|
211
|
+
menuPortal: base => ({
|
|
212
|
+
...base,
|
|
213
|
+
zIndex: 10001
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}), React.createElement(Vertical, {
|
|
217
|
+
"$gap": 24
|
|
218
|
+
}, React.createElement(Paragraph, null, "How it works:", ' ', howItWorks()), isCloningToAnotherProject && values.todo === 'keep' && React.createElement(Banner, {
|
|
219
|
+
view: "ghost",
|
|
220
|
+
type: "warning",
|
|
221
|
+
title: "Preset changes don\u2019t sync across projects"
|
|
222
|
+
}))))), React.createElement(ModalFooter, null, React.createElement(Horizontal, {
|
|
223
|
+
"$justifyContent": "end",
|
|
224
|
+
"$gap": 8
|
|
225
|
+
}, React.createElement(Button, {
|
|
226
|
+
color: "secondary",
|
|
227
|
+
onClick: onCancel,
|
|
228
|
+
isDisabled: isCloning
|
|
229
|
+
}, "Cancel"), React.createElement(Button, {
|
|
230
|
+
color: "primary",
|
|
231
|
+
onClick: onFormSubmit,
|
|
232
|
+
isLoading: isCloning
|
|
233
|
+
}, "Clone"))));
|
|
234
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ModalCloningJourney } from './ModalCloningJourney';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ModalCloningJourney } from './ModalCloningJourney';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CloningJourneyValues {
|
|
2
|
+
title: string;
|
|
3
|
+
destination: string[];
|
|
4
|
+
todo: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ApplicationPlatforms {
|
|
7
|
+
isConfigured: boolean;
|
|
8
|
+
isActive: boolean;
|
|
9
|
+
properties: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface Application {
|
|
12
|
+
code: string;
|
|
13
|
+
title: string;
|
|
14
|
+
iconUrl: string;
|
|
15
|
+
platforms: ApplicationPlatforms[];
|
|
16
|
+
}
|
|
17
|
+
export interface ModalCloningJourneyProps {
|
|
18
|
+
journeyId: string;
|
|
19
|
+
journeyTitle: string;
|
|
20
|
+
isOpen: boolean;
|
|
21
|
+
onAfterClone?: (isClonedToCurrentApp: boolean) => void;
|
|
22
|
+
onCancel: () => void;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"@pushwoosh/kit-icons": "^2.1.6",
|
|
28
28
|
"@pushwoosh/kit-typography": "^1.7.3",
|
|
29
29
|
"idb-keyval": "^6.2.2",
|
|
30
|
+
"react-hook-form": "^7.68.0",
|
|
30
31
|
"react-router-dom": "^5.3.4"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
@@ -52,5 +53,5 @@
|
|
|
52
53
|
"react": "^16.14.0 || ^18.0.0",
|
|
53
54
|
"react-dom": "^16.14.0 || ^18.0.0"
|
|
54
55
|
},
|
|
55
|
-
"version": "1.0.
|
|
56
|
+
"version": "1.0.59"
|
|
56
57
|
}
|