@capillarytech/creatives-library 8.0.200 → 8.0.201
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/constants/unified.js +0 -1
- package/package.json +1 -1
- package/utils/common.js +1 -7
- package/utils/createMobilePushPayload.js +29 -8
- package/utils/tests/createMobilePushPayload.test.js +53 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +3 -4
- package/v2Components/TemplatePreview/index.js +2 -2
- package/v2Containers/CreativesContainer/SlideBoxContent.js +2 -6
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +317 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +727 -0
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +13 -6
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +3 -15
- package/v2Containers/MobilePushNew/index.js +172 -70
- package/v2Containers/MobilePushNew/messages.js +21 -0
- package/v2Containers/MobilePushNew/sagas.js +8 -0
- package/v2Containers/MobilePushNew/tests/sagas.test.js +11 -45
- package/v2Containers/Templates/index.js +11 -15
|
@@ -20,6 +20,7 @@ import CapLabel from "@capillarytech/cap-ui-library/CapLabel";
|
|
|
20
20
|
import CapSelect from "@capillarytech/cap-ui-library/CapSelect";
|
|
21
21
|
import CapInput from "@capillarytech/cap-ui-library/CapInput";
|
|
22
22
|
import CapError from "@capillarytech/cap-ui-library/CapError";
|
|
23
|
+
import CapTooltip from "@capillarytech/cap-ui-library/CapTooltip";
|
|
23
24
|
import CapImageUpload from "../../../v2Components/CapImageUpload";
|
|
24
25
|
import CapVideoUpload from "../../../v2Components/CapVideoUpload";
|
|
25
26
|
import {
|
|
@@ -600,13 +601,19 @@ const MediaUploaders = ({
|
|
|
600
601
|
<FormattedMessage {...messages.optionalText} />
|
|
601
602
|
</CapLabel>
|
|
602
603
|
</CapRow>
|
|
603
|
-
<
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
className="action-on-click-checkbox"
|
|
604
|
+
<CapTooltip
|
|
605
|
+
title={<FormattedMessage {...messages.carouselActionDisabledMessage} />}
|
|
606
|
+
placement="topLeft"
|
|
607
607
|
>
|
|
608
|
-
<
|
|
609
|
-
|
|
608
|
+
<CapCheckbox
|
|
609
|
+
checked={button.actionOnClick}
|
|
610
|
+
onChange={(e) => handleCarouselActionOnClickChange(cardIndex, e.target.checked)}
|
|
611
|
+
className="action-on-click-checkbox"
|
|
612
|
+
disabled={true}
|
|
613
|
+
>
|
|
614
|
+
<FormattedMessage {...messages.actionOnClickBody} />
|
|
615
|
+
</CapCheckbox>
|
|
616
|
+
</CapTooltip>
|
|
610
617
|
<CapRow>
|
|
611
618
|
<CapLabel className="action-description">
|
|
612
619
|
<FormattedMessage {...messages.actionDescription} />
|
|
@@ -1132,7 +1132,7 @@ describe('MediaUploaders', () => {
|
|
|
1132
1132
|
});
|
|
1133
1133
|
|
|
1134
1134
|
describe('Carousel action link handlers', () => {
|
|
1135
|
-
it('should
|
|
1135
|
+
it('should render carousel action on click checkbox as disabled with tooltip', () => {
|
|
1136
1136
|
const onCarouselDataChange = jest.fn();
|
|
1137
1137
|
renderComponent({
|
|
1138
1138
|
mediaType: 'CAROUSEL',
|
|
@@ -1151,22 +1151,10 @@ describe('MediaUploaders', () => {
|
|
|
1151
1151
|
onCarouselDataChange,
|
|
1152
1152
|
});
|
|
1153
1153
|
|
|
1154
|
-
//
|
|
1154
|
+
// Verify checkbox is disabled
|
|
1155
1155
|
const checkbox = document.querySelector('.cap-checkbox-v2 input[type="checkbox"]');
|
|
1156
1156
|
if (checkbox) {
|
|
1157
|
-
|
|
1158
|
-
expect(onCarouselDataChange).toHaveBeenCalledWith(
|
|
1159
|
-
ANDROID,
|
|
1160
|
-
expect.arrayContaining([
|
|
1161
|
-
expect.objectContaining({
|
|
1162
|
-
buttons: expect.arrayContaining([
|
|
1163
|
-
expect.objectContaining({
|
|
1164
|
-
actionOnClick: true,
|
|
1165
|
-
})
|
|
1166
|
-
])
|
|
1167
|
-
})
|
|
1168
|
-
])
|
|
1169
|
-
);
|
|
1157
|
+
expect(checkbox).toBeDisabled();
|
|
1170
1158
|
} else {
|
|
1171
1159
|
// If checkbox not found, just verify component renders
|
|
1172
1160
|
expect(screen.getByTestId('cap-image-upload')).toBeInTheDocument();
|
|
@@ -21,6 +21,7 @@ import { intlShape } from "react-intl";
|
|
|
21
21
|
import "./index.scss";
|
|
22
22
|
import { GA } from "@capillarytech/cap-ui-utils";
|
|
23
23
|
import CapNotification from "@capillarytech/cap-ui-library/CapNotification";
|
|
24
|
+
import { CapModal } from "@capillarytech/cap-react-ui-library";
|
|
24
25
|
import { DAEMON } from "@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes";
|
|
25
26
|
import globalMessages from "../Cap/messages";
|
|
26
27
|
import * as actions from "./actions";
|
|
@@ -529,6 +530,9 @@ const MobilePushNew = ({
|
|
|
529
530
|
STANDARD_ERROR_MSG: {},
|
|
530
531
|
LIQUID_ERROR_MSG: {},
|
|
531
532
|
});
|
|
533
|
+
// Modal state for single platform confirmation
|
|
534
|
+
const [showModal, setShowModal] = useState(false);
|
|
535
|
+
const [modalContent, setModalContent] = useState({});
|
|
532
536
|
const [androidContent, setAndroidContent] = useState(INITIAL_CONTENT);
|
|
533
537
|
|
|
534
538
|
const [iosContent, setIosContent] = useState(INITIAL_CONTENT);
|
|
@@ -2130,39 +2134,93 @@ const MobilePushNew = ({
|
|
|
2130
2134
|
return panes;
|
|
2131
2135
|
}, [isAndroidSupported, isIosSupported, renderContentFields, formatMessage]);
|
|
2132
2136
|
|
|
2133
|
-
// Save button disabled logic:
|
|
2137
|
+
// Save button disabled logic: require at least one platform to have data
|
|
2138
|
+
const hasAndroidData = isAndroidSupported && androidContent?.title?.trim() && androidContent?.message?.trim();
|
|
2139
|
+
const hasIosData = isIosSupported && iosContent?.title?.trim() && iosContent?.message?.trim();
|
|
2140
|
+
const hasAnyPlatformData = hasAndroidData || hasIosData;
|
|
2141
|
+
|
|
2142
|
+
// Validation checks for save button
|
|
2143
|
+
const carouselErrors = Object.values(carouselLinkErrors).some((error) => error !== null && error !== "");
|
|
2144
|
+
const carouselValid = isCarouselDataValid();
|
|
2145
|
+
|
|
2134
2146
|
const isSaveDisabled = (
|
|
2135
|
-
|
|
2136
|
-
|| (
|
|
2137
|
-
||
|
|
2138
|
-
||
|
|
2139
|
-
|| !isCarouselDataValid()
|
|
2147
|
+
!hasAnyPlatformData // At least one supported platform must have data
|
|
2148
|
+
|| (isFullMode && (!templateName || !templateName.trim())) // Template name required in full mode
|
|
2149
|
+
|| carouselErrors
|
|
2150
|
+
|| !carouselValid
|
|
2140
2151
|
);
|
|
2141
2152
|
|
|
2142
|
-
//
|
|
2143
|
-
const
|
|
2144
|
-
if (
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2153
|
+
// Modal handler functions
|
|
2154
|
+
const setModalContentHandler = useCallback((type) => {
|
|
2155
|
+
if (type === 'ios') {
|
|
2156
|
+
const content = {
|
|
2157
|
+
title: formatMessage(messages.alertMessage),
|
|
2158
|
+
body: formatMessage(messages.iosTemplateNotConfigured),
|
|
2159
|
+
type: 'confirm',
|
|
2160
|
+
id: 'ios',
|
|
2161
|
+
};
|
|
2162
|
+
setModalContent(content);
|
|
2163
|
+
setShowModal(true);
|
|
2164
|
+
} else if (type === 'android') {
|
|
2165
|
+
const content = {
|
|
2166
|
+
title: formatMessage(messages.alertMessage),
|
|
2167
|
+
body: formatMessage(messages.androidTemplateNotConfigured),
|
|
2168
|
+
type: 'confirm',
|
|
2169
|
+
id: 'android',
|
|
2170
|
+
};
|
|
2171
|
+
setModalContent(content);
|
|
2172
|
+
setShowModal(true);
|
|
2150
2173
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2174
|
+
}, [formatMessage]);
|
|
2175
|
+
|
|
2176
|
+
const handleCancelModal = useCallback(() => {
|
|
2177
|
+
setShowModal(false);
|
|
2178
|
+
setModalContent({});
|
|
2179
|
+
}, []);
|
|
2180
|
+
|
|
2181
|
+
// Internal save function with optional modal validation skip
|
|
2182
|
+
const handleSaveInternal = useCallback((skipModalValidation = false) => {
|
|
2183
|
+
// Check for single platform data and show modal confirmation (unless skipped)
|
|
2184
|
+
if (!skipModalValidation) {
|
|
2185
|
+
const androidHasData = androidContent?.title?.trim() && androidContent?.message?.trim();
|
|
2186
|
+
const iosHasData = iosContent?.title?.trim() && iosContent?.message?.trim();
|
|
2187
|
+
|
|
2188
|
+
// If both platforms are supported but only one has data, show confirmation modal
|
|
2189
|
+
if (isAndroidSupported && isIosSupported) {
|
|
2190
|
+
if (androidHasData && !iosHasData) {
|
|
2191
|
+
setModalContentHandler('ios'); // Show iOS not configured modal
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
if (iosHasData && !androidHasData) {
|
|
2195
|
+
setModalContentHandler('android'); // Show Android not configured modal
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2157
2199
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2200
|
+
|
|
2201
|
+
// Only validate platforms that have data or are required
|
|
2202
|
+
// If user confirmed via modal, we allow single platform data
|
|
2203
|
+
if (!skipModalValidation) {
|
|
2204
|
+
// In normal flow, require data for all supported platforms
|
|
2205
|
+
if (isAndroidSupported && (!androidContent?.title?.trim() || !androidContent?.message?.trim())) {
|
|
2206
|
+
CapNotification.error({
|
|
2207
|
+
message: formatMessage(messages.androidValidationError),
|
|
2208
|
+
});
|
|
2209
|
+
if (onValidationFail) onValidationFail();
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
if (isIosSupported && (!iosContent?.title?.trim() || !iosContent?.message?.trim())) {
|
|
2213
|
+
CapNotification.error({
|
|
2214
|
+
message: formatMessage(messages.iosValidationError),
|
|
2215
|
+
});
|
|
2216
|
+
if (onValidationFail) onValidationFail();
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2163
2219
|
}
|
|
2164
|
-
//
|
|
2165
|
-
//
|
|
2220
|
+
// Note: In modal-confirmed flow (skipModalValidation = true), we skip platform validation
|
|
2221
|
+
// since user has already confirmed they want to save with incomplete platform data
|
|
2222
|
+
|
|
2223
|
+
// Template name validation - only required in full mode
|
|
2166
2224
|
const currentTemplateName = templateNameRef.current || templateName;
|
|
2167
2225
|
if (isFullMode && !currentTemplateName?.trim()) {
|
|
2168
2226
|
CapNotification.error({
|
|
@@ -2327,6 +2385,8 @@ const MobilePushNew = ({
|
|
|
2327
2385
|
}
|
|
2328
2386
|
|
|
2329
2387
|
// Create payload with enabled platform content and intl
|
|
2388
|
+
// In modal-confirmed flow, we may have intentionally missing platform data
|
|
2389
|
+
const allowSinglePlatform = skipModalValidation;
|
|
2330
2390
|
const payload = createMobilePushPayloadWithIntl({
|
|
2331
2391
|
templateName: finalTemplateName,
|
|
2332
2392
|
androidContent: isAndroidSupported ? processedAndroidContent : undefined,
|
|
@@ -2337,6 +2397,7 @@ const MobilePushNew = ({
|
|
|
2337
2397
|
sameContent,
|
|
2338
2398
|
options: {
|
|
2339
2399
|
mode: params?.mode,
|
|
2400
|
+
allowSinglePlatform,
|
|
2340
2401
|
},
|
|
2341
2402
|
intl,
|
|
2342
2403
|
});
|
|
@@ -2516,6 +2577,68 @@ const MobilePushNew = ({
|
|
|
2516
2577
|
isCarouselDataValid,
|
|
2517
2578
|
isFullMode,
|
|
2518
2579
|
templateId,
|
|
2580
|
+
activeTab,
|
|
2581
|
+
setModalContentHandler,
|
|
2582
|
+
]);
|
|
2583
|
+
|
|
2584
|
+
// Public save function (with modal validation)
|
|
2585
|
+
const handleSave = useCallback(() => {
|
|
2586
|
+
handleSaveInternal(false);
|
|
2587
|
+
}, [handleSaveInternal]);
|
|
2588
|
+
|
|
2589
|
+
// Save function that skips single platform modal validation
|
|
2590
|
+
const handleSaveWithoutModal = useCallback(() => {
|
|
2591
|
+
handleSaveInternal(true);
|
|
2592
|
+
}, [handleSaveInternal]);
|
|
2593
|
+
|
|
2594
|
+
const handleConfirmModal = useCallback(() => {
|
|
2595
|
+
setShowModal(false);
|
|
2596
|
+
setModalContent({});
|
|
2597
|
+
// Proceed with save after modal confirmation - skip single platform validation
|
|
2598
|
+
handleSaveWithoutModal();
|
|
2599
|
+
}, [handleSaveWithoutModal]);
|
|
2600
|
+
|
|
2601
|
+
const liquidMiddleWare = useCallback(() => {
|
|
2602
|
+
const onError = ({ standardErrors, liquidErrors }) => {
|
|
2603
|
+
setErrorMessage((prev) => ({
|
|
2604
|
+
STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...standardErrors },
|
|
2605
|
+
LIQUID_ERROR_MSG: { ...prev.LIQUID_ERROR_MSG, ...liquidErrors },
|
|
2606
|
+
}));
|
|
2607
|
+
};
|
|
2608
|
+
const onSuccess = () => handleSave();
|
|
2609
|
+
|
|
2610
|
+
validateMobilePushContent([androidContent, iosContent], {
|
|
2611
|
+
currentTab: activeTab === ANDROID ? 1 : 2,
|
|
2612
|
+
onError,
|
|
2613
|
+
onSuccess,
|
|
2614
|
+
getLiquidTags: globalActionsProps.getLiquidTags,
|
|
2615
|
+
formatMessage,
|
|
2616
|
+
messages: formBuilderMessages,
|
|
2617
|
+
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
2618
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
2619
|
+
isLiquidFlow: hasLiquidSupportFeature(),
|
|
2620
|
+
forwardedTags: {},
|
|
2621
|
+
skipTags: (tag) => {
|
|
2622
|
+
const skipRegexes = [
|
|
2623
|
+
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
2624
|
+
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
2625
|
+
/Link_to_[a-zA-z]/,
|
|
2626
|
+
/SURVEY.*\.TOKEN/,
|
|
2627
|
+
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
2628
|
+
];
|
|
2629
|
+
return skipRegexes.some((regex) => regex.test(tag));
|
|
2630
|
+
},
|
|
2631
|
+
singleTab: getSingleTab(accountData),
|
|
2632
|
+
});
|
|
2633
|
+
}, [
|
|
2634
|
+
androidContent,
|
|
2635
|
+
iosContent,
|
|
2636
|
+
activeTab,
|
|
2637
|
+
globalActionsProps,
|
|
2638
|
+
formatMessage,
|
|
2639
|
+
metaEntities,
|
|
2640
|
+
accountData,
|
|
2641
|
+
handleSave,
|
|
2519
2642
|
]);
|
|
2520
2643
|
|
|
2521
2644
|
// Helper to sync content between platforms
|
|
@@ -2547,7 +2670,9 @@ const MobilePushNew = ({
|
|
|
2547
2670
|
setTemplateName(value);
|
|
2548
2671
|
// Update ref to always have the latest value
|
|
2549
2672
|
templateNameRef.current = value;
|
|
2550
|
-
|
|
2673
|
+
// Only set error if user has interacted and field is empty
|
|
2674
|
+
// In full mode, template name is required only at save time, not during typing
|
|
2675
|
+
const isInvalid = isFullMode && value.trim() === "";
|
|
2551
2676
|
setTemplateNameError(isInvalid);
|
|
2552
2677
|
if (value && onEnterTemplateName) {
|
|
2553
2678
|
onEnterTemplateName();
|
|
@@ -2555,7 +2680,7 @@ const MobilePushNew = ({
|
|
|
2555
2680
|
onRemoveTemplateName();
|
|
2556
2681
|
}
|
|
2557
2682
|
},
|
|
2558
|
-
[onEnterTemplateName, onRemoveTemplateName]
|
|
2683
|
+
[onEnterTemplateName, onRemoveTemplateName, isFullMode]
|
|
2559
2684
|
);
|
|
2560
2685
|
|
|
2561
2686
|
// --- Only show template name input in full mode (not library/consumer mode) ---
|
|
@@ -2584,47 +2709,6 @@ const MobilePushNew = ({
|
|
|
2584
2709
|
[isFullMode, formatMessage, templateName, onTemplateNameChange, templateNameError, params?.id]
|
|
2585
2710
|
);
|
|
2586
2711
|
|
|
2587
|
-
const liquidMiddleWare = useCallback(() => {
|
|
2588
|
-
const onError = ({ standardErrors, liquidErrors }) => {
|
|
2589
|
-
setErrorMessage((prev) => ({
|
|
2590
|
-
STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...standardErrors },
|
|
2591
|
-
LIQUID_ERROR_MSG: { ...prev.LIQUID_ERROR_MSG, ...liquidErrors },
|
|
2592
|
-
}));
|
|
2593
|
-
};
|
|
2594
|
-
const onSuccess = () => handleSave();
|
|
2595
|
-
|
|
2596
|
-
validateMobilePushContent([androidContent, iosContent], {
|
|
2597
|
-
currentTab: activeTab === ANDROID ? 1 : 2,
|
|
2598
|
-
onError,
|
|
2599
|
-
onSuccess,
|
|
2600
|
-
getLiquidTags: globalActionsProps.getLiquidTags,
|
|
2601
|
-
formatMessage,
|
|
2602
|
-
messages: formBuilderMessages,
|
|
2603
|
-
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
2604
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
2605
|
-
isLiquidFlow: hasLiquidSupportFeature(),
|
|
2606
|
-
forwardedTags: {},
|
|
2607
|
-
skipTags: (tag) => {
|
|
2608
|
-
const skipRegexes = [
|
|
2609
|
-
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
2610
|
-
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
2611
|
-
/Link_to_[a-zA-z]/,
|
|
2612
|
-
/SURVEY.*\.TOKEN/,
|
|
2613
|
-
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
2614
|
-
];
|
|
2615
|
-
return skipRegexes.some((regex) => regex.test(tag));
|
|
2616
|
-
},
|
|
2617
|
-
singleTab: getSingleTab(accountData),
|
|
2618
|
-
});
|
|
2619
|
-
}, [
|
|
2620
|
-
androidContent,
|
|
2621
|
-
iosContent,
|
|
2622
|
-
activeTab,
|
|
2623
|
-
globalActionsProps,
|
|
2624
|
-
formatMessage,
|
|
2625
|
-
metaEntities,
|
|
2626
|
-
accountData,
|
|
2627
|
-
]);
|
|
2628
2712
|
|
|
2629
2713
|
const isLiquidFlow = hasLiquidSupportFeature();
|
|
2630
2714
|
|
|
@@ -2945,6 +3029,24 @@ const MobilePushNew = ({
|
|
|
2945
3029
|
/>
|
|
2946
3030
|
</CapColumn>
|
|
2947
3031
|
</CapRow>
|
|
3032
|
+
|
|
3033
|
+
{/* Modal for single platform confirmation */}
|
|
3034
|
+
<CapModal
|
|
3035
|
+
visible={showModal}
|
|
3036
|
+
title={modalContent.title}
|
|
3037
|
+
onOk={handleConfirmModal}
|
|
3038
|
+
onCancel={handleCancelModal}
|
|
3039
|
+
footer={[
|
|
3040
|
+
<CapButton key="cancel" onClick={handleCancelModal}>
|
|
3041
|
+
No
|
|
3042
|
+
</CapButton>,
|
|
3043
|
+
<CapButton key="confirm" type="primary" onClick={handleConfirmModal}>
|
|
3044
|
+
Yes
|
|
3045
|
+
</CapButton>,
|
|
3046
|
+
]}
|
|
3047
|
+
>
|
|
3048
|
+
{modalContent.body}
|
|
3049
|
+
</CapModal>
|
|
2948
3050
|
</CapSpin>
|
|
2949
3051
|
);
|
|
2950
3052
|
};
|
|
@@ -91,6 +91,10 @@ export default defineMessages({
|
|
|
91
91
|
id: `${scope}.actionOnClickBody`,
|
|
92
92
|
defaultMessage: 'Action on click of notification body',
|
|
93
93
|
},
|
|
94
|
+
carouselActionDisabledMessage: {
|
|
95
|
+
id: `${scope}.carouselActionDisabledMessage`,
|
|
96
|
+
defaultMessage: 'This section is being revamped. Till then it will remain disabled.',
|
|
97
|
+
},
|
|
94
98
|
actionDescription: {
|
|
95
99
|
id: `${scope}.actionDescription`,
|
|
96
100
|
defaultMessage: 'Define where the users will redirect when they click on the body of the push notification',
|
|
@@ -252,6 +256,10 @@ export default defineMessages({
|
|
|
252
256
|
id: `${scope}.contentValidationError`,
|
|
253
257
|
defaultMessage: '{platform} content must have title and message',
|
|
254
258
|
},
|
|
259
|
+
singlePlatformContentMissing: {
|
|
260
|
+
id: `${scope}.singlePlatformContentMissing`,
|
|
261
|
+
defaultMessage: 'At least one platform must have title and message',
|
|
262
|
+
},
|
|
255
263
|
// File validation error messages for useUpload.js
|
|
256
264
|
fileSizeError: {
|
|
257
265
|
id: `${scope}.fileSizeError`,
|
|
@@ -269,4 +277,17 @@ export default defineMessages({
|
|
|
269
277
|
id: `${scope}.gifFileTypeError`,
|
|
270
278
|
defaultMessage: 'Only GIF files are allowed',
|
|
271
279
|
},
|
|
280
|
+
// Modal confirmation messages for single platform data
|
|
281
|
+
alertMessage: {
|
|
282
|
+
id: `${scope}.alertMessage`,
|
|
283
|
+
defaultMessage: 'Alert',
|
|
284
|
+
},
|
|
285
|
+
androidTemplateNotConfigured: {
|
|
286
|
+
id: `${scope}.androidTemplateNotConfigured`,
|
|
287
|
+
defaultMessage: 'Android template is not configured. Continue save?',
|
|
288
|
+
},
|
|
289
|
+
iosTemplateNotConfigured: {
|
|
290
|
+
id: `${scope}.iosTemplateNotConfigured`,
|
|
291
|
+
defaultMessage: 'IOS template is not configured, Save without IOS template',
|
|
292
|
+
},
|
|
272
293
|
});
|
|
@@ -24,7 +24,11 @@ export function* createTemplate({template, callback}) {
|
|
|
24
24
|
errorMsg,
|
|
25
25
|
});
|
|
26
26
|
} catch (error) {
|
|
27
|
+
// Always dispatch failure action first, then call callback if provided
|
|
27
28
|
yield put({ type: types.CREATE_TEMPLATE_FAILURE, error, errorMsg });
|
|
29
|
+
if (callback) {
|
|
30
|
+
yield call(callback, new Error(errorMsg || error.message || error.toString()));
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
export function* uploadAsset(action) {
|
|
@@ -91,7 +95,11 @@ export function* editTemplate({ template, callback }) {
|
|
|
91
95
|
yield call(callback, editResponseData);
|
|
92
96
|
}
|
|
93
97
|
} catch (error) {
|
|
98
|
+
// Always dispatch failure action first, then call callback if provided
|
|
94
99
|
yield put({ type: types.EDIT_TEMPLATE_FAILURE, error, errorMsg });
|
|
100
|
+
if (callback) {
|
|
101
|
+
yield call(callback, new Error(errorMsg || error.message || error.toString()));
|
|
102
|
+
}
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
|
|
@@ -87,11 +87,7 @@ describe('MobilePushNew Sagas', () => {
|
|
|
87
87
|
[matchers.call.fn(Api.createMobilePushTemplateV2), mockResponse],
|
|
88
88
|
])
|
|
89
89
|
.call(Api.createMobilePushTemplateV2, template)
|
|
90
|
-
.
|
|
91
|
-
type: types.CREATE_TEMPLATE_FAILURE,
|
|
92
|
-
error: 'Bad Request',
|
|
93
|
-
errorMsg: 'Bad Request',
|
|
94
|
-
})
|
|
90
|
+
.call(callback, new Error('Bad Request'))
|
|
95
91
|
.run();
|
|
96
92
|
});
|
|
97
93
|
|
|
@@ -125,11 +121,8 @@ describe('MobilePushNew Sagas', () => {
|
|
|
125
121
|
[matchers.call.fn(Api.createMobilePushTemplateV2), throwError(error)],
|
|
126
122
|
])
|
|
127
123
|
.call(Api.createMobilePushTemplateV2, template)
|
|
128
|
-
.put({
|
|
129
|
-
|
|
130
|
-
error,
|
|
131
|
-
errorMsg: undefined,
|
|
132
|
-
})
|
|
124
|
+
.put({ type: types.CREATE_TEMPLATE_FAILURE, error, errorMsg: undefined })
|
|
125
|
+
.call(callback, new Error('Network error'))
|
|
133
126
|
.run();
|
|
134
127
|
});
|
|
135
128
|
|
|
@@ -372,11 +365,7 @@ describe('MobilePushNew Sagas', () => {
|
|
|
372
365
|
[matchers.call.fn(Api.createMobilePushTemplateV2), mockResponse],
|
|
373
366
|
])
|
|
374
367
|
.call(Api.createMobilePushTemplateV2, template)
|
|
375
|
-
.
|
|
376
|
-
type: types.EDIT_TEMPLATE_FAILURE,
|
|
377
|
-
error: 'Bad Request',
|
|
378
|
-
errorMsg: 'Bad Request',
|
|
379
|
-
})
|
|
368
|
+
.call(callback, new Error('Bad Request'))
|
|
380
369
|
.run();
|
|
381
370
|
});
|
|
382
371
|
|
|
@@ -392,11 +381,7 @@ describe('MobilePushNew Sagas', () => {
|
|
|
392
381
|
[matchers.call.fn(Api.createMobilePushTemplateV2), mockResponse],
|
|
393
382
|
])
|
|
394
383
|
.call(Api.createMobilePushTemplateV2, template)
|
|
395
|
-
.
|
|
396
|
-
type: types.EDIT_TEMPLATE_FAILURE,
|
|
397
|
-
error: 'Internal Server Error',
|
|
398
|
-
errorMsg: 'Internal Server Error',
|
|
399
|
-
})
|
|
384
|
+
.call(callback, new Error('Internal Server Error'))
|
|
400
385
|
.run();
|
|
401
386
|
});
|
|
402
387
|
|
|
@@ -408,11 +393,8 @@ describe('MobilePushNew Sagas', () => {
|
|
|
408
393
|
[matchers.call.fn(Api.createMobilePushTemplateV2), throwError(error)],
|
|
409
394
|
])
|
|
410
395
|
.call(Api.createMobilePushTemplateV2, template)
|
|
411
|
-
.put({
|
|
412
|
-
|
|
413
|
-
error,
|
|
414
|
-
errorMsg: undefined,
|
|
415
|
-
})
|
|
396
|
+
.put({ type: types.EDIT_TEMPLATE_FAILURE, error, errorMsg: undefined })
|
|
397
|
+
.call(callback, new Error('Network error'))
|
|
416
398
|
.run();
|
|
417
399
|
});
|
|
418
400
|
|
|
@@ -459,11 +441,7 @@ describe('MobilePushNew Sagas', () => {
|
|
|
459
441
|
[matchers.call.fn(Api.createMobilePushTemplateV2), mockResponse],
|
|
460
442
|
])
|
|
461
443
|
.call(Api.createMobilePushTemplateV2, template)
|
|
462
|
-
.
|
|
463
|
-
type: types.EDIT_TEMPLATE_FAILURE,
|
|
464
|
-
error: 'HTTP Error 400',
|
|
465
|
-
errorMsg: 'HTTP Error 400',
|
|
466
|
-
})
|
|
444
|
+
.call(callback, new Error('HTTP Error 400'))
|
|
467
445
|
.run();
|
|
468
446
|
});
|
|
469
447
|
|
|
@@ -479,11 +457,7 @@ describe('MobilePushNew Sagas', () => {
|
|
|
479
457
|
[matchers.call.fn(Api.createMobilePushTemplateV2), mockResponse],
|
|
480
458
|
])
|
|
481
459
|
.call(Api.createMobilePushTemplateV2, template)
|
|
482
|
-
.
|
|
483
|
-
type: types.EDIT_TEMPLATE_FAILURE,
|
|
484
|
-
error: 'HTTP Error 404',
|
|
485
|
-
errorMsg: 'HTTP Error 404',
|
|
486
|
-
})
|
|
460
|
+
.call(callback, new Error('HTTP Error 404'))
|
|
487
461
|
.run();
|
|
488
462
|
});
|
|
489
463
|
|
|
@@ -499,11 +473,7 @@ describe('MobilePushNew Sagas', () => {
|
|
|
499
473
|
[matchers.call.fn(Api.createMobilePushTemplateV2), mockResponse],
|
|
500
474
|
])
|
|
501
475
|
.call(Api.createMobilePushTemplateV2, template)
|
|
502
|
-
.
|
|
503
|
-
type: types.EDIT_TEMPLATE_FAILURE,
|
|
504
|
-
error: 'HTTP Error 422',
|
|
505
|
-
errorMsg: 'HTTP Error 422',
|
|
506
|
-
})
|
|
476
|
+
.call(callback, new Error('HTTP Error 422'))
|
|
507
477
|
.run();
|
|
508
478
|
});
|
|
509
479
|
|
|
@@ -519,11 +489,7 @@ describe('MobilePushNew Sagas', () => {
|
|
|
519
489
|
[matchers.call.fn(Api.createMobilePushTemplateV2), mockResponse],
|
|
520
490
|
])
|
|
521
491
|
.call(Api.createMobilePushTemplateV2, template)
|
|
522
|
-
.
|
|
523
|
-
type: types.EDIT_TEMPLATE_FAILURE,
|
|
524
|
-
error: 'HTTP Error 403',
|
|
525
|
-
errorMsg: 'HTTP Error 403',
|
|
526
|
-
})
|
|
492
|
+
.call(callback, new Error('HTTP Error 403'))
|
|
527
493
|
.run();
|
|
528
494
|
});
|
|
529
495
|
|
|
@@ -1357,19 +1357,16 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1357
1357
|
break;
|
|
1358
1358
|
}
|
|
1359
1359
|
case MOBILE_PUSH:
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
const { title, message, expandableDetails: { style = '', image, carouselData = [], ctas = [], media = [] } = {} } = mpushListingData || {};
|
|
1371
|
-
const {url = ''} = media?.[0] || {};
|
|
1372
|
-
templateData.content = (
|
|
1360
|
+
const mpushData = get(template, 'versions.base', template);
|
|
1361
|
+
const androidData = get(mpushData, 'ANDROID') || get(mpushData, 'androidContent');
|
|
1362
|
+
const iosData = get(mpushData, 'IOS') || get(mpushData, 'iosContent');
|
|
1363
|
+
let mpushListingData = androidData;
|
|
1364
|
+
if (isEmpty(androidData) || !androidData?.title) {
|
|
1365
|
+
mpushListingData = iosData;
|
|
1366
|
+
};
|
|
1367
|
+
const { title, message, expandableDetails: { style = '', image, carouselData = [], ctas = [], media = [] } = {} } = mpushListingData || {};
|
|
1368
|
+
const {url = ''} = media?.[0] || {};
|
|
1369
|
+
templateData.content = (
|
|
1373
1370
|
<div className='mobilepush-container'>
|
|
1374
1371
|
<div className="app-header">
|
|
1375
1372
|
<div className="app-header-left">
|
|
@@ -1441,8 +1438,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1441
1438
|
)}
|
|
1442
1439
|
</div>
|
|
1443
1440
|
);
|
|
1444
|
-
|
|
1445
|
-
}
|
|
1441
|
+
templateData.isNewMobilePush = true;
|
|
1446
1442
|
break;
|
|
1447
1443
|
case INAPP:
|
|
1448
1444
|
templateData.content = template;
|