PluginProbe
seQura / 3.1.0
seQura v3.1.0
4.3.4 4.3.3 4.3.2 4.3.1 trunk 2.0.0 2.0.10 2.0.11 2.0.12 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0.0 3.0.2 3.0.5 3.0.6 3.0.7 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 4.0.0 All 30 releases
sequra / assets / js / src / core / WidgetSettingsForm.js

WidgetSettingsForm.js in seQura 3.1.0, at assets/js/src/core/WidgetSettingsForm.js

769 lines 33.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { Repeater } from "./Repeater";
2
3 if (!window.SequraFE) {
4 window.SequraFE = {};
5 }
6
7 (function () {
8 /**
9 * @typedef WidgetLabels
10 * @property {string|null} message
11 * @property {string|null} messageBelowLimit
12 */
13
14 /**
15 * @typedef WidgetLocation
16 * @property {string|null} selForTarget
17 * @property {string|null} product
18 * @property {string|null} country
19 * @property {string|null} campaign
20 */
21
22 /**
23 * @typedef MiniWidget
24 * @property {string|null} selForPrice
25 * @property {string|null} selForLocation
26 * @property {string} message
27 * @property {string|null} messageBelowLimit
28 * @property {string|null} product
29 * @property {string|null} country
30 * @property {string|null} campaign
31 */
32
33 /**
34 * @typedef CountryPaymentMethod
35 * @property {string|null} countryCode
36 * @property {string|null} product
37 * @property {string|null} campaign
38 * @property {string|null} title
39 */
40
41 /**
42 * @typedef WidgetSettings
43 * @property {boolean} useWidgets
44 * @property {string|null} assetsKey
45 * @property {boolean} displayWidgetOnProductPage
46 * @property {boolean} showInstallmentAmountInProductListing
47 * @property {boolean} showInstallmentAmountInCartPage
48 * @property {WidgetLabels|null} widgetLabels
49 * @property {string|null} widgetStyles
50 * @property {string|null} selForPrice
51 * @property {string|null} selForAltPrice
52 * @property {string|null} selForAltPriceTrigger
53 * @property {string|null} selForDefaultLocation
54 * @property {WidgetLocation[]} customLocations
55 *
56 * @property {string|null} selForCartPrice
57 * @property {string|null} selForCartLocation
58 *
59 * @property {string|null} selForListingPrice
60 * @property {string|null} selForListingLocation
61 */
62
63 /**
64 * Handles widgets settings form logic.
65 *
66 * @param {{
67 * widgetSettings: WidgetSettings,
68 * connectionSettings: ConnectionSettings,
69 * countrySettings: CountrySettings[],
70 * paymentMethods: PaymentMethod[],
71 * allPaymentMethods: CountryPaymentMethod[],
72 * }} data
73 * @param {{
74 * saveWidgetSettingsUrl: string,
75 * getPaymentMethodsUrl: string,
76 * getAllPaymentMethodsUrl: string,
77 * page: string,
78 * appState: string,
79 * }} configuration
80 * @constructor
81 */
82 function WidgetSettingsForm(data, configuration) {
83 /** @type AjaxServiceType */
84 const api = SequraFE.ajaxService;
85
86 let allPaymentMethods = data.allPaymentMethods;
87
88 const {
89 elementGenerator: generator,
90 validationService: validator,
91 utilities
92 } = SequraFE;
93
94 /** @type WidgetSettings */
95 let activeSettings;
96 /** @type WidgetSettings */
97 let changedSettings;
98 /** @type string[] */
99 let paymentMethodIds;
100 /** @type boolean */
101 let isAssetKeyValid = false;
102
103 /** @type WidgetSettings */
104 const defaultFormData = {
105 useWidgets: false,
106 assetsKey: '',
107 displayWidgetOnProductPage: false,
108 widgetLabels: {
109 message: SequraFE.miniWidgetLabels.messages['ES'],
110 messageBelowLimit: SequraFE.miniWidgetLabels.messagesBelowLimit['ES']
111 },
112 widgetStyles: '{"alignment":"center","amount-font-bold":"true","amount-font-color":"#1C1C1C","amount-font-size":"15","background-color":"white","border-color":"#B1AEBA","border-radius":"","class":"","font-color":"#1C1C1C","link-font-color":"#1C1C1C","link-underline":"true","no-costs-claim":"","size":"M","starting-text":"only","type":"banner"}',
113 showInstallmentAmountInProductListing: false,
114 showInstallmentAmountInCartPage: false,
115 selForPrice: '.summary .price>.amount,.summary .price ins .amount',
116 selForAltPrice: '.woocommerce-variation-price .price>.amount,.woocommerce-variation-price .price ins .amount',
117 selForAltPriceTrigger: '.variations',
118 selForDefaultLocation: '.summary>.price',
119 customLocations: [],
120 selForCartPrice: '.order-total .amount',
121 selForCartLocation: '.order-total',
122 selForListingPrice: '.product .price>.amount:first-child,.product .price ins .amount',
123 selForListingLocation: '.product .price',
124 };
125
126 /**
127 * Handles form rendering.
128 */
129 this.render = () => {
130 if (!activeSettings) {
131 activeSettings = utilities.cloneObject(defaultFormData);
132 for (let key in activeSettings) {
133 activeSettings[key] = data?.widgetSettings?.[key] ?? defaultFormData[key];
134 }
135 }
136
137 paymentMethodIds = data.paymentMethods?.map((paymentMethod) => paymentMethod.product);
138 isAssetKeyValid = activeSettings.assetsKey && activeSettings.assetsKey.length !== 0;
139 changedSettings = utilities.cloneObject(activeSettings)
140 initForm();
141
142 disableFooter(true);
143 utilities.hideLoader();
144 }
145
146 /**
147 * Initializes the form structure.
148 */
149 const initForm = () => {
150 const pageContent = document.querySelector('.sq-content');
151 pageContent?.append(
152 generator.createElement('div', 'sq-content-inner', '', null, [
153 generator.createElement('div', 'sqp-flash-message-wrapper'),
154 generator.createPageHeading({
155 title: `widgets.title.${configuration.appState}`,
156 text: 'widgets.description'
157 }),
158 generator.createRadioGroupField({
159 value: changedSettings.useWidgets,
160 label: 'widgets.usePromotionalComponents.label',
161 options: [
162 { label: 'widgets.usePromotionalComponents.options.yes', value: true },
163 { label: 'widgets.usePromotionalComponents.options.no', value: false }
164 ],
165 onChange: (value) => handleChange('useWidgets', value)
166 })
167 ])
168 );
169
170 renderAssetsKeyField();
171 renderAdditionalSettings();
172 renderControls();
173 // maybeShowProductRelatedFields();
174 maybeShowRelatedFields('.sq-product-related-field', changedSettings.displayWidgetOnProductPage);
175 maybeShowRelatedFields('.sq-cart-related-field', changedSettings.showInstallmentAmountInCartPage);
176 maybeShowRelatedFields('.sq-listing-related-field', changedSettings.showInstallmentAmountInProductListing);
177 }
178
179 /**
180 * Renders the assets key field.
181 */
182 const renderAssetsKeyField = () => {
183 const pageInnerContent = document.querySelector('.sq-content-inner');
184 if (changedSettings.useWidgets) {
185 pageInnerContent?.append(
186 generator.createTextField({
187 name: 'assets-key-input',
188 value: changedSettings.assetsKey,
189 className: 'sq-text-input',
190 label: 'widgets.assetKey.label',
191 description: 'widgets.assetKey.description',
192 onChange: (value) => handleChange('assetsKey', value)
193 })
194 );
195
196 if (changedSettings.assetsKey?.length !== 0) {
197 validator.validateField(
198 document.querySelector('[name="assets-key-input"]'),
199 !isAssetKeyValid,
200 'validation.invalidField'
201 );
202 }
203 }
204 }
205
206 /**
207 * Renders additional widget settings.
208 */
209 const renderAdditionalSettings = () => {
210 if (!changedSettings.useWidgets || !isAssetKeyValid) {
211 return;
212 }
213
214 const pageInnerContent = document.querySelector('.sq-content-inner');
215
216 pageInnerContent?.append(
217 generator.createTextArea(
218 {
219 className: 'sq-text-input sq-text-area',
220 name: 'widget-styles',
221 label: 'widgets.configurator.label',
222 description: 'widgets.configurator.description.start',
223 value: changedSettings.widgetStyles,
224 onChange: (value) => handleChange('widgetStyles', value),
225 rows: 10
226 }
227 ),
228 generator.createToggleField({
229 value: changedSettings.displayWidgetOnProductPage,
230 label: 'widgets.displayOnProductPage.label',
231 description: 'widgets.displayOnProductPage.description',
232 onChange: (value) => handleChange('displayWidgetOnProductPage', value)
233 }),
234 // Product widget related fields
235 generator.createTextField({
236 value: changedSettings.selForPrice,
237 name: 'selForPrice',
238 className: 'sq-text-input sq-product-related-field',
239 label: 'widgets.selForPrice.label',
240 description: 'widgets.selForPrice.description',
241 onChange: (value) => handleChange('selForPrice', value)
242 }),
243 generator.createTextField({
244 value: changedSettings.selForAltPrice,
245 name: 'selForAltPrice',
246 className: 'sq-text-input sq-product-related-field',
247 label: 'widgets.selForAltPrice.label',
248 description: 'widgets.selForAltPrice.description',
249 onChange: (value) => handleChange('selForAltPrice', value)
250 }),
251 generator.createTextField({
252 value: changedSettings.selForAltPriceTrigger,
253 name: 'selForAltPriceTrigger',
254 className: 'sq-text-input sq-product-related-field',
255 label: 'widgets.selForAltPriceTrigger.label',
256 description: 'widgets.selForAltPriceTrigger.description',
257 onChange: (value) => handleChange('selForAltPriceTrigger', value)
258 }),
259 generator.createTextField({
260 value: changedSettings.selForDefaultLocation,
261 name: 'selForDefaultLocation',
262 className: 'sq-text-input sq-product-related-field',
263 label: 'widgets.defaultLocationSel.label',
264 description: 'widgets.defaultLocationSel.description',
265 onChange: (value) => handleChange('selForDefaultLocation', value)
266 }),
267 generator.createElement('div', 'sq-field-wrapper sq-locations-container sq-product-related-field'),
268 // End of product widget related fields
269 generator.createToggleField({
270 value: changedSettings.showInstallmentAmountInCartPage,
271 label: 'widgets.showInCartPage.label',
272 description: 'widgets.showInCartPage.description',
273 onChange: (value) => handleChange('showInstallmentAmountInCartPage', value)
274 }),
275
276 generator.createTextField({
277 value: changedSettings.selForCartPrice,
278 name: 'selForCartPrice',
279 className: 'sq-text-input sq-cart-related-field',
280 label: 'widgets.selForCartPrice.label',
281 description: 'widgets.selForCartPrice.description',
282 onChange: (value) => handleChange('selForCartPrice', value)
283 }),
284 generator.createTextField({
285 value: changedSettings.selForCartLocation,
286 name: 'selForCartLocation',
287 className: 'sq-text-input sq-cart-related-field',
288 label: 'widgets.cartDefaultLocationSel.label',
289 description: 'widgets.cartDefaultLocationSel.description',
290 onChange: (value) => handleChange('selForCartLocation', value)
291 }),
292
293 // End of cart widget related fields
294 generator.createToggleField({
295 value: changedSettings.showInstallmentAmountInProductListing,
296 label: 'widgets.showInProductListing.label',
297 description: 'widgets.showInProductListing.description',
298 onChange: (value) => handleChange('showInstallmentAmountInProductListing', value)
299 }),
300
301 generator.createTextField({
302 value: changedSettings.selForListingPrice,
303 name: 'selForListingPrice',
304 className: 'sq-text-input sq-listing-related-field',
305 label: 'widgets.selForListingPrice.label',
306 description: 'widgets.selForListingPrice.description',
307 onChange: (value) => handleChange('selForListingPrice', value)
308 }),
309 generator.createTextField({
310 value: changedSettings.selForListingLocation,
311 name: 'selForListingLocation',
312 className: 'sq-text-input sq-listing-related-field',
313 label: 'widgets.selForListingLocation.label',
314 description: 'widgets.selForListingLocation.description',
315 onChange: (value) => handleChange('selForListingLocation', value)
316 })
317 )
318
319 document.querySelector('.sqp-textarea-field .sqp-field-subtitle').append(
320 generator.createButtonLink({
321 className: 'sq-link-button',
322 text: 'widgets.configurator.description.link',
323 href: 'https://live.sequracdn.com/assets/static/simulator.html',
324 openInNewTab: true
325 }),
326 generator.createElement('span', '', 'widgets.configurator.description.end'),
327 )
328
329 // renderLabelsConfiguration();
330 renderLocations();
331 }
332
333 const maybeShowRelatedFields = (relatedFieldClass, show) => {
334 const selector = `.sq-field-wrapper:has(${relatedFieldClass}),.sq-field-wrapper${relatedFieldClass}`;
335 const hiddenClass = 'sqs--hidden';
336 document.querySelectorAll(selector).forEach((el) => {
337 if (show) {
338 el.classList.remove(hiddenClass)
339 } else {
340 el.classList.add(hiddenClass)
341 }
342 });
343 }
344
345 const renderLocations = () => {
346 new Repeater({
347 containerSelector: '.sq-locations-container',
348 data: changedSettings.customLocations,
349 getHeaders: () => [
350 {
351 title: SequraFE.translationService.translate('widgets.locations.headerTitle'),
352 description: SequraFE.translationService.translate('widgets.locations.headerDescription')
353 },
354 ],
355 getRowContent: (data) => {
356 let displayWidget = true;
357 if (data && 'undefined' !== typeof data.displayWidget) {
358 displayWidget = data.displayWidget;
359 }
360
361 return `
362 <div class="sq-table__row-field-wrapper sq-table__row-field-wrapper--grow sq-table__row-field-wrapper--space-between">
363 <h3 class="sqp-field-title">${SequraFE.translationService.translate('widgets.displayOnProductPage.label')}
364 <label class="sq-toggle"><input class="sqp-toggle-input" type="checkbox" ${displayWidget ? 'checked' : ''}><span class="sqp-toggle-round"></span></label>
365 </h3>
366 <span class="sqp-field-subtitle">${SequraFE.translationService.translate('widgets.displayOnProductPage.description')}</span>
367 </div>
368
369 <div class="sq-table__row-field-wrapper sq-table__row-field-wrapper--grow">
370 <label class="sq-table__row-field-label">${SequraFE.translationService.translate('widgets.locations.selector')}</label>
371 <span class="sqp-field-subtitle">${SequraFE.translationService.translate('widgets.locations.leaveEmptyToUseDefault')}</span>
372 <input class="sq-table__row-field" type="text" value="${data && 'undefined' !== typeof data.selForTarget ? data.selForTarget : ''}">
373 </div>
374 <div class="sq-table__row-field-wrapper sq-table__row-field-wrapper--grow">
375 <label class="sq-table__row-field-label">${SequraFE.translationService.translate('widgets.configurator.label')}</label>
376 <span class="sqp-field-subtitle">${SequraFE.translationService.translate('widgets.configurator.description.start')}<a class="sq-link-button" href="https://live.sequracdn.com/assets/static/simulator.html" target="_blank"><span>${SequraFE.translationService.translate('widgets.configurator.description.link')}</span></a><span>${SequraFE.translationService.translate('widgets.configurator.description.end')} ${SequraFE.translationService.translate('widgets.locations.leaveEmptyToUseDefault')}</span></span>
377 <textarea class="sqp-field-component sq-text-input sq-text-area" rows="5">${data && 'undefined' !== typeof data.widgetStyles ? data.widgetStyles : ''}</textarea>
378 </div>
379 `
380 },
381 getRowHeader: (data) => {
382 let selectedFound = false;
383 return `
384 <div class="sq-table__row-field-wrapper">
385 <label class="sq-table__row-field-label">${SequraFE.translationService.translate('widgets.locations.paymentMethod')}</label>
386 <select class="sq-table__row-field">${allPaymentMethods ? allPaymentMethods.map((pm, idx) => {
387 if (!pm.supportsWidgets) {
388 return '';
389 }
390
391 let selected = '';
392 if(!selectedFound && data && data.product === pm.product && data.country === pm.countryCode && data.campaign === pm.campaign) {
393 selected = ' selected';
394 selectedFound = true;
395 }
396 const dataCampaign = pm.campaign ? ` data-campaign="${pm.campaign}"` : '';
397 return `<option key="${idx}" data-country-code="${pm.countryCode}" data-product="${pm.product}"${dataCampaign + selected}>${pm.title}</option>`;
398 }).join('') : ''
399 }
400 </select>
401 </div>
402 `
403 },
404 handleChange: table => {
405 const customLocations = [];
406 table.querySelectorAll('.sq-table__row').forEach(row => {
407 const select = row.querySelector('select');
408 const selForTarget = row.querySelector('input[type="text"]').value;
409 const widgetStyles = row.querySelector('textarea').value;
410 const displayWidget = row.querySelector('input[type="checkbox"]').checked;
411 const dataset = select.selectedIndex === -1 ? null : select.options[select.selectedIndex].dataset;
412
413 const product = dataset && 'undefined' !== typeof dataset.product ? dataset.product : null;
414 const country = dataset && 'undefined' !== typeof dataset.countryCode ? dataset.countryCode : null;
415 const campaign = dataset && 'undefined' !== typeof dataset.campaign ? dataset.campaign : null;
416 customLocations.push({ selForTarget, product, country, campaign, widgetStyles, displayWidget });
417 });
418 handleChange('customLocations', customLocations)
419 },
420 addRowText: 'widgets.locations.addRow',
421 removeRowText: 'widgets.locations.removeRow',
422 });
423 }
424
425 const renderLabelsConfiguration = () => {
426 if (!changedSettings.showInstallmentAmountInProductListing) {
427 return;
428 }
429
430 const pageInnerContent = document.querySelector('.sq-content-inner');
431
432 if (!changedSettings.widgetLabels.message) {
433 changedSettings.widgetLabels.message = miniWidgetLabels.messages.hasOwnProperty(SequraFE.adminLanguage) ?
434 miniWidgetLabels.messages[SequraFE.adminLanguage] : miniWidgetLabels.messages['ES'];
435 }
436
437 if (!changedSettings.widgetLabels.messageBelowLimit) {
438 changedSettings.widgetLabels.messageBelowLimit = miniWidgetLabels.messagesBelowLimit.hasOwnProperty(SequraFE.adminLanguage) ?
439 miniWidgetLabels.messagesBelowLimit[SequraFE.adminLanguage] : miniWidgetLabels.messagesBelowLimit['ES'];
440 }
441
442 pageInnerContent?.append(
443 generator.createTextField({
444 name: 'labels-message',
445 value: changedSettings.widgetLabels.message,
446 className: 'sq-text-input',
447 label: 'widgets.teaserMessage.label',
448 description: 'widgets.teaserMessage.description',
449 onChange: (value) => handleLabelChange('message', value)
450 }),
451 generator.createTextField({
452 name: 'labels-message-below-limit',
453 value: changedSettings.widgetLabels.messageBelowLimit,
454 className: 'sq-text-input',
455 label: 'widgets.messageBelowLimit.label',
456 description: 'widgets.messageBelowLimit.description',
457 onChange: (value) => handleLabelChange('messageBelowLimit', value)
458 })
459 );
460 }
461
462 /**
463 * Renders form controls.
464 */
465 const renderControls = () => {
466 const pageContent = document.querySelector('.sq-content');
467 const pageInnerContent = document.querySelector('.sq-content-inner');
468
469 if (configuration.appState === SequraFE.appStates.ONBOARDING) {
470 pageInnerContent?.append(
471 generator.createButtonField({
472 className: 'sq-controls sqm--block',
473 buttonType: 'primary',
474 buttonLabel: 'general.continue',
475 onClick: handleSave
476 })
477 )
478
479 return;
480 }
481
482 pageContent?.append(
483 generator.createPageFooter({
484 onSave: handleSave,
485 onCancel: () => {
486 utilities.showLoader();
487 const pageContent = document.querySelector('.sq-content');
488 while (pageContent?.firstChild) {
489 pageContent?.removeChild(pageContent?.firstChild);
490 }
491
492 this.render();
493 }
494 })
495 );
496 }
497
498 const isCssSelectorValid = selector => {
499 try {
500 document.querySelector(selector);
501 return true;
502 } catch {
503 return false;
504 }
505 }
506
507 const isCustomLocationValid = value => {
508 try {
509 value.forEach(location => {
510 if ('' !== location.selForTarget && !isCssSelectorValid(location.selForTarget)) {
511 throw new Error('Invalid selector');
512 }
513 if ('' !== location.widgetStyles && !isJSONValid(location.widgetStyles)) {
514 throw new Error('Invalid selector');
515 }
516 if (!allPaymentMethods.some(pm => pm.supportsWidgets && pm.product === location.product && pm.countryCode === location.country && pm.campaign === location.campaign)) {
517 throw new Error('Invalid payment method');
518 }
519 // Check if exists other location with the same product and country
520 if (value.filter(l => l.product === location.product && l.country === location.country && l.campaign === location.campaign).length > 1) {
521 throw new Error('Duplicated entry found');
522 }
523 });
524 return true;
525 } catch {
526 return false;
527 }
528 }
529
530 /**
531 * Handles the form input changes.
532 *
533 * @param name
534 * @param value
535 */
536 const handleChange = (name, value) => {
537 changedSettings[name] = value;
538 disableFooter(false);
539
540 if (name === 'useWidgets' || name === 'showInstallmentAmountInProductListing') {
541 refreshForm();
542 }
543
544 if (name === 'widgetStyles') {
545 const isValid = validator.validateJson(
546 document.querySelector('[name="widget-styles"]'),
547 value,
548 'validation.invalidJson'
549 );
550 disableFooter(!isValid);
551 }
552
553 if (name === 'assetsKey') {
554 utilities.showLoader();
555 isAssetsKeyValid()
556 .then((isValid) => {
557 isAssetKeyValid = isValid;
558 refreshForm();
559 validator.validateField(
560 document.querySelector('[name="assets-key-input"]'),
561 !isValid,
562 'validation.invalidField'
563 );
564 })
565 .finally(utilities.hideLoader);
566 }
567
568 if (name === 'displayWidgetOnProductPage') {
569 maybeShowRelatedFields('.sq-product-related-field', value);
570 }
571 if (name === 'showInstallmentAmountInCartPage') {
572 maybeShowRelatedFields('.sq-cart-related-field', value);
573 }
574 if (name === 'showInstallmentAmountInProductListing') {
575 maybeShowRelatedFields('.sq-listing-related-field', value);
576 }
577
578 if (['selForPrice', 'selForDefaultLocation', 'selForAltPrice', 'selForAltPriceTrigger', 'selForCartPrice', 'selForCartLocation', 'selForListingPrice', 'selForListingLocation'].includes(name)) {
579 const required = ['selForPrice', 'selForDefaultLocation', 'selForCartPrice', 'selForCartLocation', 'selForListingPrice', 'selForListingLocation'];
580 const isValid = validator.validateCssSelector(
581 document.querySelector(`[name="${name}"]`),
582 required.includes(name),
583 'validation.invalidField'
584 );
585 disableFooter(!isValid);
586 }
587
588 if (name === 'customLocations') {
589 const isValid = isCustomLocationValid(value);
590 validator.validateField(
591 document.querySelector(`.sq-product-related-field .sq-table`),
592 !isValid,
593 'validation.invalidField'
594 );
595 disableFooter(!isValid);
596 }
597 }
598
599 const handleLabelChange = (name, value) => {
600 changedSettings['widgetLabels'][name] = value;
601 disableFooter(false);
602 }
603
604 /**
605 * Re-renders the form.
606 */
607 const refreshForm = () => {
608 document.querySelector('.sq-content-inner')?.remove();
609 configuration.appState !== SequraFE.appStates.ONBOARDING && document.querySelector('.sq-page-footer').remove();
610 initForm();
611 }
612
613 /**
614 * Handles the saving of the form.
615 */
616 const handleSave = () => {
617 if (changedSettings.useWidgets && changedSettings.assetsKey?.length === 0) {
618 validator.validateRequiredField(
619 document.querySelector('[name="assets-key-input"]'),
620 'validation.requiredField'
621 )
622
623 return;
624 }
625
626 if (changedSettings.useWidgets && !isAssetKeyValid) {
627 return;
628 }
629
630 if (changedSettings.useWidgets) {
631 let valid = isJSONValid(changedSettings.widgetStyles);
632
633 validator.validateField(
634 document.querySelector(`[name="widget-styles"]`),
635 !valid,
636 'validation.invalidJSON'
637 );
638
639 if (changedSettings.displayWidgetOnProductPage) {
640 for (const name of ['selForPrice', 'selForDefaultLocation']) {
641 valid = validator.validateCssSelector(
642 document.querySelector(`[name="${name}"]`),
643 true,
644 'validation.invalidField'
645 ) && valid;
646 }
647 for (const name of ['selForAltPrice', 'selForAltPriceTrigger']) {
648 valid = validator.validateCssSelector(
649 document.querySelector(`[name="${name}"]`),
650 false,
651 'validation.invalidField'
652 ) && valid;
653 }
654
655 const isValid = isCustomLocationValid(changedSettings.customLocations);
656 valid = isValid && valid;
657 validator.validateField(
658 document.querySelector(`.sq-product-related-field .sq-table`),
659 !isValid,
660 'validation.invalidField'
661 );
662 }
663
664 if (changedSettings.showInstallmentAmountInCartPage) {
665 for (const name of ['selForCartPrice', 'selForCartLocation']) {
666 valid = validator.validateCssSelector(
667 document.querySelector(`[name="${name}"]`),
668 true,
669 'validation.invalidField'
670 ) && valid;
671 }
672 }
673
674 if (changedSettings.showInstallmentAmountInProductListing) {
675 // valid = validator.validateRequiredField(
676 // document.querySelector('[name="labels-message"]'),
677 // 'validation.requiredField'
678 // ) && valid;
679
680 // valid = validator.validateRequiredField(
681 // document.querySelector('[name="labels-message-below-limit"]'),
682 // 'validation.requiredField'
683 // ) && valid;
684
685 for (const name of ['selForListingPrice', 'selForListingLocation']) {
686 valid = validator.validateCssSelector(
687 document.querySelector(`[name="${name}"]`),
688 true,
689 'validation.invalidField'
690 ) && valid;
691 }
692 }
693
694 if (!valid) {
695 return;
696 }
697 }
698
699 utilities.showLoader();
700 api.post(configuration.saveWidgetSettingsUrl, changedSettings, SequraFE.customHeader)
701 .then(() => {
702 if (configuration.appState === SequraFE.appStates.ONBOARDING) {
703 const index = SequraFE.pages.onboarding.indexOf(SequraFE.appPages.ONBOARDING.WIDGETS)
704 SequraFE.pages.onboarding.length > index + 1 ?
705 window.location.hash = configuration.appState + '-' + SequraFE.pages.onboarding[index + 1] :
706 window.location.hash = SequraFE.appStates.PAYMENT + '-' + SequraFE.appPages.PAYMENT.METHODS;
707 }
708
709 activeSettings = utilities.cloneObject(changedSettings);
710 SequraFE.state.setData('widgetSettings', activeSettings);
711
712 disableFooter(true);
713 })
714 .finally(utilities.hideLoader);
715 }
716
717 /**
718 * Disables footer form controls.
719 *
720 * @param disable
721 */
722 const disableFooter = (disable) => {
723 if (configuration.appState !== SequraFE.appStates.ONBOARDING) {
724 utilities.disableFooter(disable);
725 }
726 }
727
728 /**
729 * Validates JSON string.
730 *
731 * @param jsonString
732 *
733 * @returns {boolean}
734 */
735 const isJSONValid = (jsonString) => {
736 try {
737 JSON.parse(jsonString);
738
739 return true;
740 } catch (e) {
741 return false
742 }
743 }
744
745 /**
746 * Returns a Promise<boolean> for assets key validation.
747 *
748 * @returns {Promise<boolean>}
749 */
750 const isAssetsKeyValid = () => {
751 const mode = data.connectionSettings.environment;
752 const merchantId = data.countrySettings[0].merchantId;
753 const assetsKey = changedSettings.assetsKey;
754 const methods = paymentMethodIds.filter((id) => id !== 'i1').join('_');
755
756 const validationUrl =
757 `https://${mode}.sequracdn.com/scripts/${merchantId}/${assetsKey}/${methods}_cost.json`;
758
759 let customHeader = {
760 'Content-Type': 'text/plain'
761 };
762
763 return api.get(validationUrl, null, customHeader, null, SequraFE.customHeader).then(() => true).catch(() => false)
764 }
765 }
766
767 SequraFE.WidgetSettingsForm = WidgetSettingsForm;
768 })();
769