@pushwoosh/smart-components-kit 1.0.56 → 1.0.58

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.
@@ -21,7 +21,7 @@ export function CampaignsConfigurationDrawer(props) {
21
21
  "$justifyContent": "space-between",
22
22
  "$alignItems": "center",
23
23
  "$margin": "0 0 4px"
24
- }, React.createElement(H1, null, "Send message"), React.createElement(CloseButtonWrap, {
24
+ }, React.createElement(H1, null, "Create campaign"), React.createElement(CloseButtonWrap, {
25
25
  onClick: onClose
26
26
  }, React.createElement(CloseIcon, {
27
27
  size: "medium"
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import { type ModalCloningJourneyProps } from './types';
3
+ export declare function ModalCloningJourney({ journeyId, journeyTitle, onAfterClone, onCancel, isOpen }: ModalCloningJourneyProps): React.JSX.Element;
@@ -0,0 +1,225 @@
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
+ handleSubmit,
76
+ formState: {
77
+ errors
78
+ },
79
+ reset
80
+ } = useForm({
81
+ defaultValues: {
82
+ title: `${journeyTitle} (copy)`,
83
+ destination: applicationsOptions.length > 0 ? [(_applicationsOptions$ = applicationsOptions.find(({
84
+ value
85
+ }) => value === applicationCode)) === null || _applicationsOptions$ === void 0 ? void 0 : _applicationsOptions$.value].filter(Boolean) : [],
86
+ todo: 'keep'
87
+ }
88
+ });
89
+ const values = watch();
90
+ useEffect(() => {
91
+ var _applicationsOptions$2;
92
+ reset({
93
+ title: `${journeyTitle} (copy)`,
94
+ destination: applicationsOptions.length > 0 ? [(_applicationsOptions$2 = applicationsOptions.find(({
95
+ value
96
+ }) => value === applicationCode)) === null || _applicationsOptions$2 === void 0 ? void 0 : _applicationsOptions$2.value].filter(Boolean) : [],
97
+ todo: 'keep'
98
+ });
99
+ }, [journeyTitle, applicationsOptions, applicationCode, reset]);
100
+ const onFormSubmit = handleSubmit(async data => {
101
+ let hasError = false;
102
+ if (!data.title) {
103
+ setError('title', {
104
+ message: 'Name is required'
105
+ });
106
+ hasError = true;
107
+ }
108
+ if (data.destination.length === 0) {
109
+ setError('destination', {
110
+ message: 'Project is required'
111
+ });
112
+ hasError = true;
113
+ }
114
+ if (!hasError) {
115
+ const {
116
+ todo,
117
+ destination,
118
+ title
119
+ } = data;
120
+ setIsCloning(true);
121
+ try {
122
+ await rpcGatewayJourney.journey.CloneJourneys({
123
+ journeyUuids: [journeyId],
124
+ destAppCodes: destination,
125
+ cloneWithContent: todo === 'keep',
126
+ sourceAppCode: applicationCode,
127
+ title
128
+ });
129
+ if (destination.length > 1 || destination.length === 1 && destination[0] !== applicationCode) {
130
+ trackMixpanelEvent({
131
+ name: 'CloneJourneyToAnotherApp'
132
+ });
133
+ }
134
+ onAfterClone === null || onAfterClone === void 0 || onAfterClone(destination.includes(applicationCode));
135
+ } finally {
136
+ setIsCloning(false);
137
+ }
138
+ }
139
+ });
140
+ const handleDestinationChange = newValue => {
141
+ if (newValue.length > 5) {
142
+ setError('destination', {
143
+ message: 'Maximum 5 projects'
144
+ });
145
+ } else {
146
+ setValue('destination', newValue.map(item => item.value));
147
+ }
148
+ };
149
+ const isCloningToAnotherProject = applicationsOptions.length > 0 && (values.destination.length === 1 && values.destination[0] !== applicationCode || values.destination.length > 1);
150
+ const howItWorks = () => {
151
+ if (isCloningToAnotherProject) {
152
+ 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.';
153
+ } else {
154
+ 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.';
155
+ }
156
+ };
157
+ return React.createElement(Modal, {
158
+ size: "medium",
159
+ isOpen: isOpen
160
+ }, React.createElement(ModalHeader, null, React.createElement(H3, null, "Clone campaign")), React.createElement(ModalBody, null, React.createElement(Vertical, {
161
+ "$gap": 24
162
+ }, 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, {
163
+ error: (_errors$title = errors.title) === null || _errors$title === void 0 ? void 0 : _errors$title.message,
164
+ title: "Campaign name"
165
+ }, React.createElement(NativeInput, {
166
+ disabled: isCloning,
167
+ placeholder: "ex. Onboarding",
168
+ name: "title",
169
+ value: values.title,
170
+ onChange: e => setValue('title', e.target.value)
171
+ })), React.createElement(FieldBox, {
172
+ title: "Destination projects",
173
+ error: (_errors$destination = errors.destination) === null || _errors$destination === void 0 ? void 0 : _errors$destination.message,
174
+ description: "Maximum 5 projects"
175
+ }, React.createElement(Select, {
176
+ isDisabled: isCloning || applications.loading,
177
+ isLoading: applications.loading,
178
+ isMulti: true,
179
+ options: applicationsOptions,
180
+ name: "destination",
181
+ isClearable: true,
182
+ menuPortalTarget: document.querySelector('body'),
183
+ styles: {
184
+ menuPortal: base => ({
185
+ ...base,
186
+ zIndex: 10001
187
+ })
188
+ },
189
+ onChange: handleDestinationChange,
190
+ value: values.destination ? applicationsOptions.filter(app => values.destination.includes(app.value)) : []
191
+ })), React.createElement(FieldBox, {
192
+ title: "What to do with the content in this campaign?",
193
+ error: (_errors$todo = errors.todo) === null || _errors$todo === void 0 ? void 0 : _errors$todo.message
194
+ }, React.createElement(Select, {
195
+ isDisabled: isCloning,
196
+ name: "todo",
197
+ options: contentOptions,
198
+ onChange: params => setValue('todo', (params === null || params === void 0 ? void 0 : params.value) ?? 'keep'),
199
+ value: contentOptions.find(item => item.value === values.todo),
200
+ menuPortalTarget: document.querySelector('body'),
201
+ styles: {
202
+ menuPortal: base => ({
203
+ ...base,
204
+ zIndex: 10001
205
+ })
206
+ }
207
+ }), React.createElement(Vertical, {
208
+ "$gap": 24
209
+ }, React.createElement(Paragraph, null, "How it works:", ' ', howItWorks()), isCloningToAnotherProject && values.todo === 'keep' && React.createElement(Banner, {
210
+ view: "ghost",
211
+ type: "warning",
212
+ title: "Preset changes don\u2019t sync across projects"
213
+ }))))), React.createElement(ModalFooter, null, React.createElement(Horizontal, {
214
+ "$justifyContent": "end",
215
+ "$gap": 8
216
+ }, React.createElement(Button, {
217
+ color: "secondary",
218
+ onClick: onCancel,
219
+ isDisabled: isCloning
220
+ }, "Cancel"), React.createElement(Button, {
221
+ color: "primary",
222
+ onClick: onFormSubmit,
223
+ isLoading: isCloning
224
+ }, "Clone"))));
225
+ }
@@ -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
@@ -11,3 +11,4 @@ export * from './RichMessageEditorSmart';
11
11
  export * from './Table';
12
12
  export * from './OpenActions';
13
13
  export * from './SystemParcel';
14
+ export * from './ModalCloningJourney';
package/index.js CHANGED
@@ -10,4 +10,5 @@ export * from './LanguagesSelector';
10
10
  export * from './RichMessageEditorSmart';
11
11
  export * from './Table';
12
12
  export * from './OpenActions';
13
- export * from './SystemParcel';
13
+ export * from './SystemParcel';
14
+ export * from './ModalCloningJourney';
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"
56
+ "version": "1.0.58"
56
57
  }