Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines all selectors that are used in customers address add/edit form.
*/
export default {
addressEmailInput: '#customer_address_customer_email',
addressFirstnameInput: '#customer_address_first_name',
addressLastnameInput: '#customer_address_last_name',
addressCompanyInput: '#customer_address_company',
addressCountrySelect: '#customer_address_id_country',
addressStateSelect: '#customer_address_id_state',
addressStateBlock: '.js-address-state-select',
addressDniInput: '#customer_address_dni',
addressDniInputLabel: 'label[for="customer_address_dni"]',
addressPostcodeInput: '#customer_address_postcode',
addressPostcodeInputLabel: 'label[for="customer_address_postcode"]',
};

View File

@@ -0,0 +1,33 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import AutocompleteWithEmail from '@components/form/autocomplete-with-email';
import CountryPostcodeRequiredToggler from '@components/country-postcode-required-toggler';
import addressFormMap from './address-form-map';
const {$} = window;
$(() => {
new AutocompleteWithEmail(addressFormMap.addressEmailInput, {
firstName: addressFormMap.addressFirstnameInput,
lastName: addressFormMap.addressLastnameInput,
company: addressFormMap.addressCompanyInput,
});
new window.prestashop.component.CountryStateSelectionToggler(
addressFormMap.addressCountrySelect,
addressFormMap.addressStateSelect,
addressFormMap.addressStateBlock,
);
new window.prestashop.component.CountryDniRequiredToggler(
addressFormMap.addressCountrySelect,
addressFormMap.addressDniInput,
addressFormMap.addressDniInputLabel,
);
new CountryPostcodeRequiredToggler(
addressFormMap.addressCountrySelect,
addressFormMap.addressPostcodeInput,
addressFormMap.addressPostcodeInputLabel,
);
});

View File

@@ -0,0 +1,23 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const addressGrid = new window.prestashop.component.Grid('address');
addressGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
addressGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
addressGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
addressGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
addressGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
addressGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
addressGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
addressGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
window.prestashop.component.initComponents(
[
'ChoiceTable',
],
);
});

View File

@@ -0,0 +1,87 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import AliasFormMap from '@pages/alias/form/alias-form.map';
const {$} = window;
/**
* This component is used in alias form page to manage the behavior of the aliases collection.
*/
export default class AliasesCollectionManager {
$collection: JQuery;
idxAlias: number;
constructor() {
// Get dom element of the collection
this.$collection = $(AliasFormMap.aliasesCollection);
this.idxAlias = this.$collection.children(AliasFormMap.aliasItem).length - 1;
// Initialize listeners
this.initListeners();
// If we have no alias, we add one
if (this.$collection.children(AliasFormMap.aliasItem).length === 0) {
this.onAddAlias(null, false);
}
}
// Initialize listeners to manage the collection properly.
private initListeners(): void {
this.$collection.parent().on('click', AliasFormMap.addAliasButton, (e: Event) => this.onAddAlias(e));
this.$collection.on('click', AliasFormMap.deleteAliasButton, (e: Event) => this.onDeleteAlias(e));
this.$collection.on('keydown', AliasFormMap.aliasItemInput, (e: Event) => this.onKeyDownAlias(e as KeyboardEvent));
}
// On click in add alias button
private onAddAlias(e: Event|null = null, needFocus: boolean = true): void {
if (e) {
e.preventDefault();
}
// +1 idx
this.idxAlias += 1;
// Retrieve the prototype and format it
let prototype = this.$collection.data('prototype');
prototype = prototype.replace(/__name__/g, this.idxAlias);
// Then, add it at the bottom of the collection
this.$collection.append(prototype);
// We set active to true on the added alias
this.$collection.children().last().find('[name$="[active]"][value=1]').prop('checked', true);
// We set focus on last added input if needed
if (needFocus) {
this.$collection.children().last().find(AliasFormMap.aliasItemInput).focus();
}
// Check if we need to display delete buttons or not
this.refreshDeleteAliasButtons();
}
// On click on delete alias button
private onDeleteAlias(e: Event): void {
e.preventDefault();
// Remove the alias element related to this button
const $item = $(e.target as HTMLElement);
$item.parents(AliasFormMap.aliasItem).remove();
// Check if we need to display delete buttons or not
this.refreshDeleteAliasButtons();
}
// On key down in alias item input => if it's a comma (and the value is already set), add a new alias and focus on new input
private onKeyDownAlias(e: KeyboardEvent): void {
if (e.key === ',') {
e.preventDefault();
if (this.$collection.children().last().find('input').val() !== '') {
this.onAddAlias(e);
}
}
}
// Check if we need to display delete buttons or not (if there is only one alias, we hide the delete buttons)
private refreshDeleteAliasButtons(): void {
if (this.$collection.children().length === 1) {
this.$collection.children().find(AliasFormMap.deleteAliasButton).addClass('d-none');
} else {
this.$collection.children().find(AliasFormMap.deleteAliasButton).removeClass('d-none');
}
}
};

View File

@@ -0,0 +1,12 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
aliasesCollection: '.js-aliases-collection',
addAliasButton: '.js-btn-add-alias',
deleteAliasButton: '.js-btn-delete-alias',
aliasItem: '.alias-item',
aliasItemInput: '.form-control[name$="[alias]"]',
};

View File

@@ -0,0 +1,14 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import AliasesCollectionManager from '@pages/alias/components/aliases-collection-manager';
import FormSubmitButton from '@components/form-submit-button';
const {$} = window;
$(() => {
new FormSubmitButton();
new AliasesCollectionManager();
});

View File

@@ -0,0 +1,15 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
generateSecretLink: 'a.generate-client-secret',
generateSecretModalId: 'generate-secret-modal',
copySecret: '.copy-secret-to-clipboard',
copySecretIcon: '.copy-secret-to-clipboard i',
clientIdSource: '.js-client-id-source',
clientIdDestination: '.js-client-id-destination',
toggleAllScopes: '.js-toggle-all-scopes',
scopesSwitches: '[id^="api_client_scopes_"] .ps-switch',
};

View File

@@ -0,0 +1,96 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ApiClientMap from '@pages/api-client/api-client-map';
import ConfirmModal from '@components/modal/confirm-modal';
import textToLinkRewriteCopier from '@components/text-to-link-rewrite-copier';
const {$} = window;
$(() => {
// Auto-generate client_id from client_name (only for new clients)
textToLinkRewriteCopier({
sourceElementSelector: ApiClientMap.clientIdSource,
destinationElementSelector: ApiClientMap.clientIdDestination,
});
// Toggle all scopes enable/disable
document.querySelectorAll<HTMLButtonElement>(ApiClientMap.toggleAllScopes).forEach((button) => {
button.addEventListener('click', () => {
const {action} = button.dataset;
const targetValue = action === 'enable' ? '1' : '0';
const switches = document.querySelectorAll<HTMLElement>(ApiClientMap.scopesSwitches);
switches.forEach((switchElement) => {
const radioToSelect = switchElement.querySelector<HTMLInputElement>(`input[type="radio"][value="${targetValue}"]`);
if (radioToSelect && !radioToSelect.checked) {
radioToSelect.click();
}
});
});
});
// Display a confirmation modal when regeneration link is clicked before submitting the regeneration
document.querySelector<HTMLLinkElement>(ApiClientMap.generateSecretLink)?.addEventListener('click', (event) => {
event.preventDefault();
const generateLink = event.target as HTMLLinkElement;
const generateConfirmModal = new ConfirmModal(
{
id: ApiClientMap.generateSecretModalId,
confirmTitle: generateLink.dataset.confirmTitle,
confirmMessage: generateLink.dataset.confirmMessage,
confirmButtonLabel: generateLink.dataset.confirmButtonLabel,
closeButtonLabel: generateLink.dataset.closeButtonLabel,
confirmButtonClass: 'btn-warning',
closable: true,
},
() => {
submitGeneration(generateLink);
},
);
generateConfirmModal.show();
});
function submitGeneration(generateLink: HTMLLinkElement): void {
const form = document.createElement('form');
form.setAttribute('method', 'POST');
form.setAttribute('action', generateLink.href);
form.setAttribute('style', 'display: none;');
document.body.appendChild(form);
form.submit();
}
function copyClientSecret(copyLink: HTMLLinkElement): void {
// Fallback to navigator.clipboard.writeText because it only works with https
const input = document.createElement('input');
input.value = copyLink.dataset.secret ?? '';
document.body.prepend(input);
input.select();
input.setSelectionRange(0, 99999);
try {
document.execCommand('copy');
} finally {
input.remove();
}
}
// Copy secret to clipboard
document.querySelector<HTMLLinkElement>(ApiClientMap.copySecret)?.addEventListener('click', (event: Event) => {
event.preventDefault();
const copyLink = event.target as HTMLLinkElement;
copyClientSecret(copyLink);
});
document.querySelector<HTMLElement>(ApiClientMap.copySecretIcon)?.addEventListener('click', (event: Event) => {
event.preventDefault();
const copyLinkIcon = event.target as HTMLElement;
const copyLink = copyLinkIcon.parentElement as HTMLLinkElement;
copyClientSecret(copyLink);
});
});

View File

@@ -0,0 +1,16 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const grid = new window.prestashop.component.Grid('api_client');
grid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.AsyncToggleColumnExtension());
});

View File

@@ -0,0 +1,23 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const attachmentGrid = new window.prestashop.component.Grid('attachment');
attachmentGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
attachmentGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
attachmentGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
attachmentGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
attachmentGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
attachmentGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
attachmentGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
attachmentGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
window.prestashop.component.initComponents(
[
'TranslatableInput',
],
);
});

View File

@@ -0,0 +1,11 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines all selectors that are used in customers address add/edit form.
*/
export default {
attributeGroupShopAssociationInput: '#attribute_group_shop_association',
};

View File

@@ -0,0 +1,19 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import AttributeGroupFormMap from '@pages/attribute-group/form/attribute-group-form-map';
const {$} = window;
$(() => {
window.prestashop.component.initComponents(
[
'TranslatableInput',
'TranslatableField',
],
);
new window.prestashop.component.ChoiceTree(AttributeGroupFormMap.attributeGroupShopAssociationInput).enableAutoCheckChildren();
});

View File

@@ -0,0 +1,27 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ShowcaseCard from '@components/showcase-card/showcase-card';
import ShowcaseCardCloseExtension from '@components/showcase-card/extension/showcase-card-close-extension';
const {$} = window;
$(() => {
const grid = new window.prestashop.component.Grid('attribute_group');
grid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.PositionExtension(grid));
grid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
const showcaseCard = new ShowcaseCard('attributesShowcaseCard');
showcaseCard.addExtension(new ShowcaseCardCloseExtension());
});

View File

@@ -0,0 +1,14 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines all selectors that are used in attribute address add/edit form.
*/
export default {
attributeShopAssociationInput: '#attribute_shop_association',
attributeGroupSelect: '#attribute_attribute_group',
attributeColorFormRow: '.js-attribute-type-color-form-row',
attributeTextureFormRow: '.js-attribute-type-texture-form-row',
};

View File

@@ -0,0 +1,43 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import AttributeFormMap from '@pages/attribute/form/attribute-form-map';
import FormSubmitButton from '@components/form-submit-button';
const {$} = window;
$(() => {
window.prestashop.component.initComponents(
[
'TranslatableInput',
'TranslatableField',
],
);
new window.prestashop.component.ChoiceTree(AttributeFormMap.attributeShopAssociationInput).enableAutoCheckChildren();
new FormSubmitButton();
});
document.addEventListener('DOMContentLoaded', () => {
const attributeGroupSelect = document.querySelector(AttributeFormMap.attributeGroupSelect) as HTMLSelectElement | null;
const attributeColorRow = document.querySelector(AttributeFormMap.attributeColorFormRow) as HTMLElement | null;
const attributeTextureRow = document.querySelector(AttributeFormMap.attributeTextureFormRow) as HTMLElement | null;
if (!attributeGroupSelect || !attributeColorRow || !attributeTextureRow) return;
const toggleDisplay = () => {
const selectedOption = attributeGroupSelect?.selectedOptions[0];
const isColorGroup = selectedOption?.dataset.iscolorgroup;
const displayValue = isColorGroup ? 'flex' : 'none';
attributeColorRow.style.display = displayValue;
attributeTextureRow.style.display = displayValue;
};
toggleDisplay();
attributeGroupSelect?.addEventListener('change', toggleDisplay);
});

View File

@@ -0,0 +1,19 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const grid = new window.prestashop.component.Grid('attribute');
grid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.PositionExtension(grid));
grid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
});

View File

@@ -0,0 +1,14 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const backupGrid = new window.prestashop.component.Grid('backup');
backupGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
backupGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
backupGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
backupGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
backupGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
});

View File

@@ -0,0 +1,9 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
openRangeSelectionModal: 'openRangeSelectionModal',
shippingMethodChange: 'carrierShippingMethodChange',
rangesUpdated: 'carrierRangesUpdated',
};

View File

@@ -0,0 +1,250 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {EventEmitter} from 'events';
import CarrierFormMap from '@pages/carrier/form/carrier-form-map';
import CarrierFormEventMap from '@pages/carrier/form/carrier-form-event-map';
import ConfirmModal from '@js/components/modal/confirm-modal';
import {Range} from '@pages/carrier/form/types';
const {$} = window;
/**
* This component is used in carrier form page to manage the behavior of the form:
* - Selections of zones, ranges and ranges prices
* - Update form when the carrier shipping method change
*/
export default class CarrierFormManager {
eventEmitter: EventEmitter;
currentShippingSymbol: string;
$zonesInput: JQuery;
$rangesInput: JQuery;
$shippingMethodInput: JQuery;
$freeShippingInput: JQuery;
/**
* @param {EventEmitter} eventEmitter
*/
constructor(eventEmitter: EventEmitter) {
this.eventEmitter = eventEmitter;
this.currentShippingSymbol = '';
// Initialize dom elements
this.$zonesInput = $(CarrierFormMap.zonesInput);
this.$rangesInput = $(CarrierFormMap.rangesInput);
this.$shippingMethodInput = $(CarrierFormMap.shippingMethodInput);
this.$freeShippingInput = $(CarrierFormMap.freeShippingInput);
// Initialize form
this.initForm();
// Initialize listeners
this.initListeners();
}
private initForm() {
// First toggle shipping related controls
this.refreshFreeShipping();
// Then, we need to refresh the shipping method symbol
this.refreshCurrentShippingSymbol();
this.onChangeZones();
}
private initListeners() {
this.$zonesInput.on('change', () => this.onChangeZones());
this.$freeShippingInput.on('change', () => {
this.refreshFreeShipping();
this.onChangeZones();
});
this.$shippingMethodInput.on('change', () => this.refreshCurrentShippingSymbol());
$(CarrierFormMap.zonesContainer).on('click', CarrierFormMap.deleteZoneButton, (e:Event) => this.onDeleteZone(e));
this.eventEmitter.on(CarrierFormEventMap.rangesUpdated, (ranges: Range[]) => this.onChangeRanges(ranges));
}
private refreshFreeShipping(): void {
const isFreeShipping = $(`${CarrierFormMap.freeShippingInput}:checked`).val() === '1';
CarrierFormMap.shippingControls.forEach((inputId: string) => {
const $inputGroup = $(inputId).closest('.form-group');
$inputGroup.toggleClass('d-none', isFreeShipping);
$(inputId).prop('required', !isFreeShipping);
});
}
private refreshCurrentShippingSymbol() {
// First, we need to get the units of the selected shipping method
const shippingMethodUnits = $(CarrierFormMap.shippingMethodRow).data('units');
const shippingMethodValue = <number> this.$shippingMethodInput.filter(':checked').first().val() || -1;
this.currentShippingSymbol = shippingMethodUnits[shippingMethodValue] || '?';
// Then, we need to emit an event to update this symbol to other components
this.eventEmitter.emit(CarrierFormEventMap.shippingMethodChange, this.currentShippingSymbol);
// Finally, we need to update the ranges names with the new symbol
$(CarrierFormMap.rangeRow).each((_, rangeRow: HTMLElement) => {
const $rangeRow = $(rangeRow);
const $rangeName = $rangeRow.find(CarrierFormMap.rangeNamePreview);
const $rangeNameHidden = $rangeRow.find(CarrierFormMap.rangeNameInput);
const from = $rangeRow.find(CarrierFormMap.rangeFromInput).val();
const to = $rangeRow.find(CarrierFormMap.rangeToInput).val();
const rangeName = `${from}${this.currentShippingSymbol} - ${to}${this.currentShippingSymbol}`;
$rangeName.text(rangeName);
$rangeNameHidden.val(rangeName);
});
}
private onChangeZones() {
// First, we retrieve the zones actually displayed and selected
const $zonesContainer = $(CarrierFormMap.zonesContainer);
const $zonesRows = $(CarrierFormMap.zoneRow);
const zones = <string[]> this.$zonesInput.val() ?? [];
// First, we need to delete the zones that are not selected and already displayed
// (and we keep the zones that are already displayed)
const zonesAlreadyDisplayed = <string[]>[];
$zonesRows.each((_, zoneRow: HTMLElement) => {
const $zoneRow = $(zoneRow);
const zoneId = $zoneRow.find(CarrierFormMap.zoneIdInput).val()?.toString();
if (zoneId !== undefined) {
if (!zones.includes(zoneId)) {
$zoneRow.remove();
} else {
zonesAlreadyDisplayed.push(zoneId);
}
}
});
// Then, we need to add the zones that are selected but not displayed
const zonePrototype = $zonesContainer.data('prototype');
zones.forEach((zoneId: string) => {
if (!zonesAlreadyDisplayed.includes(zoneId)) {
// We create new zone row by duplicating the prototype and replacing the zone index
const prototype = zonePrototype.replace(/__zone__/g, $(CarrierFormMap.zoneRow).length);
// We need to update the zone id and the zone name
const $prototype = $(prototype);
$prototype.attr('data-zone-id', zoneId);
$prototype.find(CarrierFormMap.zoneIdInput).val(zoneId);
$prototype.find(CarrierFormMap.zoneNamePreview).text(this.$zonesInput.find(CarrierFormMap.zoneIdOption(zoneId)).text());
// We append the new zone row into the zones container
$zonesContainer.append($prototype);
// Next, we need to prepare the ranges for this zone
const $rangeContainer = $prototype.find(CarrierFormMap.rangesContainer);
const $rangeContainerBody = $prototype.find(CarrierFormMap.rangesContainerBody);
const rangePrototype = $rangeContainer.data('prototype');
// @ts-ignore
const ranges = <Range[]>JSON.parse(this.$rangesInput.val() || '[]');
// For each range selected, we need to create a new range row with the range prototype
ranges.forEach((range: Range, index) => {
// Then, we append the new range row into the range container
const $rPrototype = this.prepareRangePrototype(rangePrototype, index, range);
$rangeContainerBody.append($rPrototype);
});
}
});
}
private onDeleteZone(e: Event) {
e.preventDefault();
// We need to get the zone id to delete
const $currentTarget = $(e.currentTarget as HTMLElement);
const $currentZoneRow = $currentTarget.parents(CarrierFormMap.zoneRow);
const idZoneToDelete = $currentZoneRow.children(CarrierFormMap.zoneIdInput).val();
// We need to display a confirmation modal before deleting the zone
const modal = new ConfirmModal(
{
id: 'modal-confirm-submit-feature-flag',
confirmButtonClass: 'btn-danger',
confirmTitle: $currentTarget.data('modal-title'),
confirmMessage: '',
confirmButtonLabel: $currentTarget.data('modal-confirm'),
closeButtonLabel: $currentTarget.data('modal-cancel'),
},
() => {
// If, the user confirms the deletion, we need to remove the zone
// First, we need to remove this zone from the zones
let zones = <string[]> this.$zonesInput.val() || [];
zones = zones.filter((zoneId: string) => zoneId !== idZoneToDelete);
// And update the zones selected values and trigger the zones selector change event
this.$zonesInput.val(zones);
this.$zonesInput.change();
},
);
modal.show();
}
private onChangeRanges(ranges: Range[]) {
// We retrieve all ranges containers in the page
const $rangesContainerBodies = $(CarrierFormMap.rangesContainerBody);
// For each range container, we need to update the ranges
$rangesContainerBodies.each((_, zoneRangesContainer: HTMLElement) => {
// First, we need to save all values for this range.
const $zoneRangesContainerBody = $(zoneRangesContainer);
const pricesRanges = $(zoneRangesContainer).find(CarrierFormMap.rangeRow).map((__, rangeRow: HTMLElement) => {
const $rangeRow = $(rangeRow);
const from = parseFloat($rangeRow.find(CarrierFormMap.rangeFromInput).val()?.toString() || '0');
const to = parseFloat($rangeRow.find(CarrierFormMap.rangeToInput).val()?.toString() || '0');
const price = $rangeRow.find(CarrierFormMap.rangePriceInput).val() || '';
return {from, to, price};
});
// Then, we reset the ranges container
$zoneRangesContainerBody.html('');
// and, we need to add all the ranges selected
const rangePrototype = $zoneRangesContainerBody.closest(CarrierFormMap.rangesContainer).data('prototype');
ranges.forEach((range: Range, index) => {
// First, we need to prepare the range prototype
const $rPrototype = this.prepareRangePrototype(rangePrototype, index, range);
// Then, we need to search the previous price if exist (oldFrom = newFrom OR oldTo = newTo)
let price = '';
for (let i = 0; i < pricesRanges.length; i += 1) {
if (pricesRanges[i].from === range.from || pricesRanges[i].to === range.to) {
price = pricesRanges[i].price.toString();
break;
}
}
// We set the previous value for this range if it exists
// @ts-ignore
$rPrototype.find(CarrierFormMap.rangePriceInput)
.attr('data-from', range.from || '0')
.attr('data-to', range.to || '0')
.val(price);
// Then, we append the new range row into the range container
$zoneRangesContainerBody.append($rPrototype);
});
});
}
private prepareRangePrototype(rangePrototype: string, index: number, range: Range): JQuery {
// We prepare the range prototype by replacing the range index, and setting the range values
const $rPrototype = $(rangePrototype.replace(/__range__/g, index.toString()));
$rPrototype.find(CarrierFormMap.rangeFromInput).val(range.from || '0');
$rPrototype.find(CarrierFormMap.rangeToInput).val(range.to || '0');
$rPrototype.find(CarrierFormMap.rangeNamePreview)
.text(`${range.from}${this.currentShippingSymbol} - ${range.to}${this.currentShippingSymbol}`);
// We return the prototype well formed
return $rPrototype;
}
}

View File

@@ -0,0 +1,38 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
form: 'form[name="carrier"]',
navigationBar: '#form-nav',
freeShippingInput: 'input[name="carrier[shipping_settings][is_free]"]',
zonesInput: '#carrier_shipping_settings_zones',
zoneIdOption: (zoneId: number|string): string => `option[value="${zoneId}"]`,
rangesInput: '#carrier_shipping_settings_ranges_data',
rangesSelectionAppId: '#carrier_shipping_settings_ranges-app',
addRangeButton: '.js-add-carrier-ranges-btn',
shippingMethodRow: '#carrier_shipping_settings_shipping_method',
shippingMethodInput: 'input[name="carrier[shipping_settings][shipping_method]"]',
deleteZoneButton: '.js-carrier-delete-zone',
zonesContainer: '#carrier_shipping_settings_ranges_costs',
rangesContainer: '.js-carrier-range-container',
rangesContainerBody: '.js-carrier-range-container-body',
zoneRow: '.js-carrier-zone-row',
zoneIdInput: 'input[name$="[zoneId]"]',
rangeNamePreview: '.js-carrier-range-name .text-preview-value',
rangeNameInput: '.js-carrier-range-name input[type="hidden"]',
rangeRow: '.js-carrier-range-row',
zoneNamePreview: '.card-title .text-preview-value',
rangeFromInput: 'input[name$="[from]"]',
rangeToInput: 'input[name$="[to]"]',
rangePriceInput: 'input[name$="[price]"]',
shippingControls: [
'#carrier_shipping_settings_id_tax_rule_group',
'#carrier_shipping_settings_has_additional_handling_fee',
'#carrier_shipping_settings_shipping_method',
'#carrier_shipping_settings_range_behavior',
'#carrier_shipping_settings_ranges',
'#carrier_shipping_settings_ranges_costs',
],
};

View File

@@ -0,0 +1,58 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import CarrierFormMap from '@pages/carrier/form/carrier-form-map';
import {createApp} from 'vue';
import {createI18n} from 'vue-i18n';
import CarrierRangesModal from '@pages/carrier/form/components/CarrierRangesModal.vue';
import EventEmitter from '@components/event-emitter';
import ReplaceFormatter from '@PSVue/plugins/vue-i18n/replace-formatter';
import CarrierFormEventMap from '@pages/carrier/form/carrier-form-event-map';
export default class CarrierRanges {
private readonly eventEmitter: typeof EventEmitter;
constructor(eventEmitter: typeof EventEmitter) {
this.eventEmitter = eventEmitter;
this.initRangesSelectionModal();
}
initRangesSelectionModal(): void {
// Create the modal container
const $showModal = $(CarrierFormMap.addRangeButton);
const $modalContainer = $(`<div id="${CarrierFormMap.rangesSelectionAppId.slice(1)}"></div>`);
$showModal.after($modalContainer);
// Retreive translations from the button
const i18n = createI18n({
locale: 'en',
formatter: new ReplaceFormatter(),
messages: {en: $showModal.data('translations')},
});
// Initialize the Vue app with the CarrierRangesModal component
const vueApp = createApp(CarrierRangesModal, {
i18n,
eventEmitter: this.eventEmitter,
}).use(i18n);
// Mount the Vue app to the modal container
vueApp.mount(CarrierFormMap.rangesSelectionAppId);
// Open the modal with data when the button "Add range" is clicked
$showModal.click((e: JQuery.ClickEvent) => {
e.preventDefault();
e.stopImmediatePropagation();
const data = $(CarrierFormMap.rangesInput).val() || '[]';
this.eventEmitter.emit(CarrierFormEventMap.openRangeSelectionModal, JSON.parse(data.toString()));
});
// Listen the modal to apply the ranges selected to the data
this.eventEmitter.on(CarrierFormEventMap.rangesUpdated, (ranges: Array<object>) => {
const $data = $(CarrierFormMap.rangesInput);
$data.val(JSON.stringify(ranges));
});
}
}

View File

@@ -0,0 +1,324 @@
<!--*
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*-->
<template>
<div data-role="carrier-ranges-edit-modal">
<modal
v-if="isModalShown"
:modal-title="$t('modal.title')"
:confirm-label="$t('modal.apply')"
:cancel-label="$t('modal.cancel')"
:confirmation="true"
:close-on-click-outside="false"
@close="cancelChanges"
@confirm="applyChanges"
@mouseleave="mouseLeave"
>
<template #header>
<h5 class="modal-title">
{{ $t('modal.title') }}
</h5>
</template>
<template #body>
<div
class="alert alert-danger"
v-if="overlappingAlert"
role="alert"
>
{{ $t('modal.overlappingAlert') }}
</div>
<div
class="alert alert-danger"
v-if="negativeRangeAlert"
role="alert"
>
{{ $t('modal.negativeRangeAlert') }}
</div>
<div class="table-container">
<table class="table table-carrier-ranges-modal">
<thead>
<tr>
<th />
<th>{{ $t('modal.col.from') }}</th>
<th>{{ $t('modal.col.to') }} </th>
<th>{{ $t('modal.col.action') }}</th>
</tr>
</thead>
<tbody :key="refreshKey">
<template
:key="i"
v-for="r, i in ranges"
>
<tr :data-row="i">
<td>
<button
type="button"
class="btn-add"
@click.prevent="addRange(i)"
>
<i class="material-icons">add</i>
</button>
</td>
<td>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">{{ this.symbol }}</span>
</div>
<input
type="number"
class="form-control form-from"
inputmode="decimal"
v-model.number="r.from"
>
</div>
</td>
<td>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">{{ this.symbol }}</span>
</div>
<input
type="number"
class="form-control form-to"
inputmode="decimal"
v-model.number="r.to"
>
</div>
</td>
<td align="center">
<button
type="button"
@click.prevent="deleteRange(i)"
class="btn-delete"
>
<i class="material-icons">delete</i>
</button>
</td>
</tr>
</template>
</tbody>
</table>
<button
@click.prevent="addRange()"
type="button"
class="btn btn-sm btn-outline-secondary mt-2"
>
<i class="material-icons">add_box</i>
{{ $t('modal.addRange') }}
</button>
</div>
</template>
</modal>
</div>
</template>
<script lang="ts">
import Modal from '@PSVue/components/Modal.vue';
import {defineComponent} from 'vue';
import CarrierFormEventMap from '@pages/carrier/form/carrier-form-event-map';
import {Range} from '@pages/carrier/form/types';
interface CarrierRangesModalStates {
isModalShown: boolean, // define if the modal is shown
ranges: Range[], // define the ranges currently displayed
savedRanges: Range[], // define the ranges saved before the changes
refreshKey: number, // force the refresh of the table by incrementing this key
errors: boolean, // define if there are errors in the ranges
overlappingAlert: boolean, // define if there are overlapping ranges (and display an alert)
symbol: string, // define the current symbol used in function of the shipping method
}
export default defineComponent({
name: 'CarrierRangesModal',
components: {Modal},
data(): CarrierRangesModalStates {
return {
isModalShown: false,
ranges: [],
savedRanges: [],
refreshKey: 0,
errors: false,
overlappingAlert: false,
negativeRangeAlert: false,
symbol: '',
};
},
props: {
eventEmitter: {
type: Object,
required: true,
},
},
mounted() {
// If we need to open this modal
this.eventEmitter.on(CarrierFormEventMap.openRangeSelectionModal, (ranges: Range[]) => {
this.ranges = ranges ?? [];
this.openModal();
});
// If we need to change the shipping method symbol
this.eventEmitter.on(CarrierFormEventMap.shippingMethodChange, (symbol: string) => { this.symbol = symbol; });
},
methods: {
openModal() {
// We add a class to the body to prevent scrolling
document.querySelector('body')?.classList.add('overflow-hidden');
this.isModalShown = true;
// We save the ranges to be able to cancel the changes
this.savedRanges.splice(0, this.savedRanges.length);
this.ranges.forEach((range) => this.savedRanges.push({from: range.from, to: range.to}));
// We add an empty range if there is none
if (this.ranges.length === 0) {
this.ranges.push({from: null, to: null});
}
// We reset the errors
this.errors = false;
this.overlappingAlert = false;
this.negativeRangeAlert = false;
},
closeModal() {
// We remove the class to allow scrolling
document.querySelector('body')?.classList.remove('overflow-hidden');
this.isModalShown = false;
this.refreshKey = 0;
},
cancelChanges() {
// We cancel the changes and close the modal
this.ranges.splice(0, this.ranges.length);
this.savedRanges.forEach((range) => this.ranges.push({from: range.from, to: range.to}));
// We remove empty ranges
this.ranges = this.ranges.filter((range) => range.from !== null || range.to !== null);
// Then, we close the modal
this.closeModal();
},
applyChanges() {
// We remove empty ranges
this.ranges = this.ranges.filter((range) => range.from !== null || range.to !== null);
// We validate the changes
this.validateChanges();
if (!this.errors) {
// We emit the new ranges
this.eventEmitter.emit(CarrierFormEventMap.rangesUpdated, this.ranges);
// We close the modal
this.closeModal();
}
},
validateChanges() {
const table = <HTMLElement>document.querySelector('.table-carrier-ranges-modal');
// Reset errors
this.errors = false;
this.overlappingAlert = false;
this.negativeRangeAlert = false;
// We remove the error class from all inputs already in error
table.querySelectorAll('input.is-invalid').forEach((input) => {
input.classList.remove('is-invalid');
});
// We sort the ranges by min values
this.ranges.sort((a, b) => (a.from || 0) - (b.from || 0));
// We check ranges
let saveMax: null | number = null;
this.ranges.forEach((range, index) => {
// Check if all fields are filled and are not negative
if (range.from === null || typeof range.from === 'string' || range.from < 0) {
table.querySelectorAll(`tr[data-row="${index}"] input.form-from`)
.forEach((input) => {
input.classList.add('is-invalid');
});
this.errors = true;
this.negativeRangeAlert = true;
}
if (range.to === null || typeof range.to === 'string' || range.to < 0) {
table.querySelectorAll(`tr[data-row="${index}"] input.form-to`)
.forEach((input) => {
input.classList.add('is-invalid');
});
this.errors = true;
this.negativeRangeAlert = true;
}
// Check overlapping
if (saveMax !== null && range.from !== null && range.from < saveMax) {
table.querySelectorAll(`tr[data-row="${index - 1}"] input.form-to`)
.forEach((input) => {
input.classList.add('is-invalid');
});
table.querySelectorAll(`tr[data-row="${index}"] input.form-from`)
.forEach((input) => {
input.classList.add('is-invalid');
});
this.errors = true;
this.overlappingAlert = true;
}
// Check from < to for each range
if (range.to !== null && range.from !== null && range.to <= range.from) {
table.querySelectorAll(`tr[data-row="${index}"] input.form-to`)
.forEach((input) => {
input.classList.add('is-invalid');
});
this.errors = true;
}
saveMax = range.to;
});
},
addRange(index: undefined | number) {
// Add new range at the index specified, at the bottom if not specified
// (with "from" already set to the previous "to")
if (index === undefined) {
this.ranges.push({from: this.ranges[this.ranges.length - 1]?.to, to: null});
} else {
this.ranges.splice(index + 1, 0, {from: this.ranges[index]?.to, to: null});
}
},
deleteRange(rangeIndex: number) {
// We remove the selected range
this.ranges.splice(rangeIndex, 1);
// We add an empty range if there is none
if (this.ranges.length === 0) {
this.ranges.push({from: null, to: null});
}
},
},
});
</script>
<style lang="scss" type="text/scss" scoped>
@import '~@scss/config/_settings.scss';
.modal {
.modal-footer {
justify-content: space-between;
}
.table {
margin-bottom: 0;
border-bottom: 0;
tr td {
border: 0;
}
}
.btn-delete,
.btn-add {
border: none;
background: none;
i {
font-size: 1.2em;
}
}
.table-container {
max-height: 60vh;
overflow-y: auto;
}
}
</style>

View File

@@ -0,0 +1,38 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ChoiceTable from '@js/components/choice-table';
import NavbarHandler from '@js/components/navbar-handler';
import CarrierFormManager from '@pages/carrier/form/carrier-form-manager';
import CarrierRanges from '@pages/carrier/form/carrier-range-modal';
import CarrierFormMap from '@pages/carrier/form/carrier-form-map';
import NavbarFormErrorHandler from '@js/components/navbar-form-error-handler';
$(() => {
// Initialize components
window.prestashop.component.initComponents([
'TranslatableInput',
'EventEmitter',
'MultipleZoneChoice',
'ChoiceTable',
]);
// Initialize the ranges selection modal
new CarrierRanges(window.prestashop.instance.eventEmitter);
new ChoiceTable();
// Initialize the carrier form manager
new CarrierFormManager(window.prestashop.instance.eventEmitter);
const carrierForm = document.querySelector(CarrierFormMap.form);
if (carrierForm instanceof HTMLElement) {
new NavbarFormErrorHandler({
form: carrierForm,
navbarHandler: new NavbarHandler($(CarrierFormMap.navigationBar)),
});
}
});

View File

@@ -0,0 +1,9 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export interface Range {
from: number|null,
to: number|null,
}

View File

@@ -0,0 +1,28 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ShowcaseCard from '@components/showcase-card/showcase-card';
import ShowcaseCardCloseExtension from '@components/showcase-card/extension/showcase-card-close-extension';
const {$} = window;
$(() => {
const carrierGrid = new window.prestashop.component.Grid('carrier');
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.PositionExtension(carrierGrid));
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
carrierGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
const showcaseCard = new ShowcaseCard('carriersShowcaseCard');
showcaseCard.addExtension(new ShowcaseCardCloseExtension());
});

View File

@@ -0,0 +1,7 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
switchCustomer: 'switchCartRuleCustomer',
};

View File

@@ -0,0 +1,24 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const discountContainer = '.discount-container';
export default {
codeGeneratorBtn: '#cart_rule_information .js-generator-btn',
codeInput: '#cart_rule_information_code',
currencySelect: '#cart_rule_actions_discount_reduction_currency',
customerItem: '#cart_rule_conditions_customer_list .entity-item',
customerSearchContainer: '#cart_rule_conditions_customer',
discountApplicationSelect: '#cart_rule_actions_discount_discount_application',
discountContainer,
giftProductSearchContainer: '#cart_rule_actions_gift_product',
applyToDiscountedProductsContainer: '.apply-to-discounted-products',
highlightSwitchContainer: '.js-highlight-switch-container',
includeTaxInput: '#cart_rule_actions_discount_reduction_include_tax',
reductionTypeSelect: '#cart_rule_actions_discount_reduction_type',
// eslint-disable-next-line max-len
reductionValueSymbol: `${discountContainer} .price-reduction-value .input-group .input-group-append .input-group-text, .price-reduction-value .input-group .input-group-prepend .input-group-text`,
specificProductSearchComponent: '#cart_rule_actions_discount_specific_product',
specificProductSearchContainer: '.specific-product-search-container',
};

View File

@@ -0,0 +1,99 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import CartRuleMap from '@pages/cart-rule/cart-rule-map';
import ProductSearchInput from '@components/form/product-search-input';
export default class DiscountApplicationManager {
private readonly reductionTypeSelector: string;
private readonly applicationSelect: string;
constructor() {
this.reductionTypeSelector = CartRuleMap.reductionTypeSelect;
this.applicationSelect = CartRuleMap.discountApplicationSelect;
this.init();
}
private init(): void {
new ProductSearchInput(CartRuleMap.specificProductSearchComponent);
this.updateChoices(this.getReductionTypeSelect().value);
this.toggleExcludeDiscountedProducts(this.getReductionTypeSelect().value);
this.toggleSpecificProductsSearch(this.getApplicationSelect().value);
this.listenReductionTypeChanges();
this.listenDiscountApplicationChanges();
}
private listenReductionTypeChanges(): void {
const reductionTypeSelect = this.getReductionTypeSelect();
reductionTypeSelect.addEventListener('change', (e: Event) => {
const currentTarget = <HTMLSelectElement> e.currentTarget;
this.updateChoices(currentTarget.value);
this.toggleExcludeDiscountedProducts(currentTarget.value);
});
}
private listenDiscountApplicationChanges(): void {
const applicationSelect = this.getApplicationSelect();
applicationSelect.addEventListener('change', (e: Event) => {
const currentTarget = <HTMLSelectElement> e.currentTarget;
this.toggleSpecificProductsSearch(currentTarget.value);
});
}
private updateChoices(reductionType: string): void {
const discountApplicationSelect = <HTMLSelectElement> document.querySelector(CartRuleMap.discountApplicationSelect);
const selectedValue = discountApplicationSelect.value;
$(discountApplicationSelect).empty();
let choices: Record<string, string> = JSON.parse(<string> discountApplicationSelect.dataset.amountChoices);
if (reductionType === 'percentage') {
choices = JSON.parse(<string> discountApplicationSelect.dataset.percentageChoices);
}
Object.entries(choices).forEach(([label, value]) => {
const newOption = document.createElement('option');
newOption.label = label;
newOption.value = value;
newOption.selected = selectedValue === value;
discountApplicationSelect.add(newOption);
});
}
private toggleExcludeDiscountedProducts(reductionType: string): void {
const excludeDiscountedProductsEl = <HTMLDivElement> document.querySelector(CartRuleMap.applyToDiscountedProductsContainer);
if (reductionType === 'percentage') {
$(excludeDiscountedProductsEl).fadeIn();
} else {
$(excludeDiscountedProductsEl).fadeOut();
}
}
private toggleSpecificProductsSearch(applicationChoice: string): void {
const specificProductSearchContainer = <HTMLDivElement> document.querySelector(CartRuleMap.specificProductSearchContainer);
if (applicationChoice === 'specific_product') {
$(specificProductSearchContainer).fadeIn();
} else {
$(specificProductSearchContainer).fadeOut();
}
}
private getReductionTypeSelect(): HTMLSelectElement {
return <HTMLSelectElement> document.querySelector(this.reductionTypeSelector);
}
private getApplicationSelect(): HTMLSelectElement {
return <HTMLSelectElement> document.querySelector(this.applicationSelect);
}
}

View File

@@ -0,0 +1,34 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import CartRuleMap from '@pages/cart-rule/cart-rule-map';
import PriceReductionManager from '@components/form/price-reduction-manager';
import DiscountApplicationManager from '@pages/cart-rule/form/discount-application-manager';
export default class DiscountManager {
constructor() {
this.init();
}
private init(): void {
new DiscountApplicationManager();
new PriceReductionManager(
CartRuleMap.reductionTypeSelect,
CartRuleMap.includeTaxInput,
CartRuleMap.currencySelect,
CartRuleMap.reductionValueSymbol,
);
this.toggleCurrency();
document.querySelector(CartRuleMap.reductionTypeSelect)?.addEventListener('change', this.toggleCurrency);
}
private toggleCurrency(): void {
if ($(CartRuleMap.reductionTypeSelect).val() === 'percentage') {
$(CartRuleMap.currencySelect).fadeOut();
} else {
$(CartRuleMap.currencySelect).fadeIn();
}
}
}

View File

@@ -0,0 +1,45 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import CartRuleMap from '@pages/cart-rule/cart-rule-map';
import FormFieldToggler from '@components/form/form-field-toggler';
import CartRuleEventMap from '@pages/cart-rule/cart-rule-event-map';
import CustomerSearchInput from '@components/form/customer-search-input';
import DiscountManager from '@pages/cart-rule/form/discount-manager';
import ProductSearchInput from '@components/form/product-search-input';
$(() => {
window.prestashop.component.initComponents([
'TranslatableField',
'TranslatableInput',
'EventEmitter',
]);
// It is important that discountManager is initialized before DisablingSwitch
// or else it won't find reduction type value when it is disabled therefore not toggling some inputs correctly on init
new DiscountManager();
window.prestashop.component.initComponents([
'DisablingSwitch',
'GeneratableInput',
]);
window.prestashop.instance.generatableInput.attachOn(CartRuleMap.codeGeneratorBtn);
new FormFieldToggler({
disablingInputSelector: CartRuleMap.codeInput,
targetSelector: CartRuleMap.highlightSwitchContainer,
matchingValue: '',
});
new CustomerSearchInput(
CartRuleMap.customerSearchContainer,
CartRuleMap.customerItem,
// use all shops constraint
() => null,
CartRuleEventMap.switchCustomer,
);
new ProductSearchInput(CartRuleMap.giftProductSearchContainer);
});

View File

@@ -0,0 +1,18 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const cartRuleGrid = new window.prestashop.component.Grid('cart_rule');
cartRuleGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
cartRuleGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
cartRuleGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
cartRuleGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
cartRuleGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
cartRuleGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
cartRuleGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
cartRuleGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
cartRuleGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
});

View File

@@ -0,0 +1,17 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const grid = new window.prestashop.component.Grid('cart');
grid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
});

View File

@@ -0,0 +1,21 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines all selectors that are used in catalog price rule add/edit form.
*/
export default {
// mapping for price-field-availability-handler
initialPrice: '#catalog_price_rule_leave_initial_price',
price: '#catalog_price_rule_price',
currencyId: '#catalog_price_rule_id_currency',
reductionTypeSelect: '#catalog_price_rule_reduction_type',
reductionTypeAmountSymbol: '.price-reduction-value .input-group .input-group-append .input-group-text, '
+ '.price-reduction-value .input-group .input-group-prepend .input-group-text',
// mapping for include-tax-field-visibility-handler
reductionType: '.js-reduction-type-source',
includeTax: '.js-include-tax-row',
};

View File

@@ -0,0 +1,37 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const {$} = window;
/**
* Shows/hides 'include_tax' field depending from 'reduction_type' field value
*
* @deprecated use @components/form/include-tax-field-toggle.ts
*/
export default class IncludeTaxFieldVisibilityHandler {
$sourceSelector: JQuery;
$targetSelector: JQuery;
constructor(sourceSelector: string, targetSelector: string) {
this.$sourceSelector = $(sourceSelector);
this.$targetSelector = $(targetSelector);
this.handle();
this.$sourceSelector.on('change', () => this.handle());
}
/**
* When source value is 'percentage', target field is shown, else hidden
*
* @private
*/
private handle(): void {
if (this.$sourceSelector.val() === 'percentage') {
this.$targetSelector.fadeOut();
} else {
this.$targetSelector.fadeIn();
}
}
}

View File

@@ -0,0 +1,24 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import PriceReductionManager from '@components/form/price-reduction-manager';
import PriceFieldAvailabilityHandler from './price-field-availability-handler';
import CatalogPriceRuleFormMap from './catalog-price-rule-form-map';
const {$} = window;
$(() => {
new PriceFieldAvailabilityHandler(
CatalogPriceRuleFormMap.initialPrice,
CatalogPriceRuleFormMap.price,
);
new PriceReductionManager(
CatalogPriceRuleFormMap.reductionTypeSelect,
CatalogPriceRuleFormMap.includeTax,
CatalogPriceRuleFormMap.currencyId,
CatalogPriceRuleFormMap.reductionTypeAmountSymbol,
);
});

View File

@@ -0,0 +1,33 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const {$} = window;
/**
* Enables/disables 'price' field depending from 'leave_initial_price' field checkbox value
*/
export default class PriceFieldAvailabilityHandler {
$sourceSelector: JQuery;
$targetSelector: JQuery;
constructor(checkboxSelector: string, targetSelector: string) {
this.$sourceSelector = $(checkboxSelector);
this.$targetSelector = $(targetSelector);
this.handle();
this.$sourceSelector.on('change', () => this.handle());
}
/**
* When checkbox value is 1, target field is disabled, else enabled
*
* @private
*/
private handle(): void {
const checkboxVal = this.$sourceSelector.is(':checked');
this.$targetSelector.prop('disabled', checkboxVal);
}
}

View File

@@ -0,0 +1,17 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const priceRuleGrid = new window.prestashop.component.Grid('catalog_price_rule');
priceRuleGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
priceRuleGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
priceRuleGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
priceRuleGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
priceRuleGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
priceRuleGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
priceRuleGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
priceRuleGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
});

View File

@@ -0,0 +1,12 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import SpecificPriceFormHandler from './specific-price-form-handler';
const {$} = window;
$(() => {
new SpecificPriceFormHandler();
});

View File

@@ -0,0 +1,17 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
priceList: '#js-specific-price-list',
cancel: '#specific_price_form .js-cancel',
priceForm: '#specific_price_form',
save: '#specific_price_form .js-save',
openCreate: '#js-open-create-specific-price-form',
leavBPrice: (selectorPrefix: string): string => `${selectorPrefix}leave_bprice`,
reductionType: (selectorPrefix: string): string => `${selectorPrefix}sp_reduction_type`,
modalCancel: '#form_modal_cancel',
modalClose: '#form_modal_cancel',
modalSave: '#form_modal_save',
};

View File

@@ -0,0 +1,568 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import SpecificMap from './selectors-map';
const {$} = window;
class SpecificPriceFormHandler {
prefixCreateForm: string;
prefixEditForm: string;
editModalIsOpen: boolean;
$createPriceFormDefaultValues: Record<string, any>;
constructor() {
this.prefixCreateForm = 'form_step2_specific_price_';
this.prefixEditForm = 'form_modal_';
this.editModalIsOpen = false;
this.$createPriceFormDefaultValues = {};
this.storePriceFormDefaultValues();
this.loadAndDisplayExistingSpecificPricesList();
this.configureAddPriceFormBehavior();
this.configureEditPriceModalBehavior();
this.configureDeletePriceButtonsBehavior();
this.configureMultipleModalsBehavior();
}
/**
* @private
*/
private loadAndDisplayExistingSpecificPricesList(): void {
const listContainer = $(SpecificMap.priceList);
const url = listContainer
.data('listingUrl')
.replace(/list\/\d+/, `list/${this.getProductId()}`);
$.ajax({
type: 'GET',
url,
}).done((specificPrices) => {
const tbody = listContainer.find('tbody');
tbody.find('tr').remove();
if (specificPrices.length > 0) {
listContainer.removeClass('hide');
} else {
listContainer.addClass('hide');
}
const specificPricesList = this.renderSpecificPricesListingAsHtml(
specificPrices,
);
tbody.append(specificPricesList);
});
}
/**
* @param array specificPrices
*
* @returns string
*
* @private
*/
private renderSpecificPricesListingAsHtml(
specificPrices: Record<string, any>,
): string {
let specificPricesList = '';
const $specificPricesListElement = $('#js-specific-price-list');
const self = this;
$.each(specificPrices, (index, specificPrice) => {
const deleteAttr = $specificPricesListElement.attr('data-action-delete');
let row;
if (deleteAttr) {
const deleteUrl = deleteAttr.replace(
/delete\/\d+/,
`delete/${specificPrice.id_specific_price}`,
);
row = self.renderSpecificPriceRow(specificPrice, deleteUrl);
}
specificPricesList += row;
});
return specificPricesList;
}
/**
* @param Object specificPrice
* @param string deleteUrl
*
* @returns string
*
* @private
*/
private renderSpecificPriceRow(
specificPrice: Record<string, any>,
deleteUrl: string,
): string {
const specificPriceId = specificPrice.id_specific_price;
/* eslint-disable max-len */
const canDelete = specificPrice.can_delete
? `<a href="${deleteUrl}" class="js-delete delete btn tooltip-link delete pl-0 pr-0"><i class="material-icons">delete</i></a>`
: '';
const canEdit = specificPrice.can_edit
? `<a href="#" data-specific-price-id="${specificPriceId}" class="js-edit edit btn tooltip-link delete pl-0 pr-0"><i class="material-icons">edit</i></a>`
: '';
const row = `<tr> \
<td>${specificPrice.id_specific_price}</td> \
<td>${specificPrice.rule_name}</td> \
<td>${specificPrice.attributes_name}</td> \
<td>${specificPrice.currency}</td> \
<td>${specificPrice.country}</td> \
<td>${specificPrice.group}</td> \
<td>${specificPrice.customer}</td> \
<td>${specificPrice.fixed_price}</td> \
<td>${specificPrice.impact}</td> \
<td>${specificPrice.period}</td> \
<td>${specificPrice.from_quantity}</td> \
<td>${canDelete}</td> \
<td>${canEdit}</td></tr>`;
/* eslint-enable max-len */
return row;
}
/**
* @private
*/
private configureAddPriceFormBehavior() {
const usePrefixForCreate = true;
const selectorPrefix = this.getPrefixSelector(usePrefixForCreate);
$(SpecificMap.cancel).on('click', () => {
this.resetCreatePriceFormDefaultValues();
$(SpecificMap.priceForm).collapse('hide');
});
$(SpecificMap.save).on('click', () => this.submitCreatePriceForm());
// eslint-disable-next-line
$(SpecificMap.openCreate).on('click', () => this.loadAndFillOptionsForSelectCombinationInput(usePrefixForCreate),
);
$(SpecificMap.leavBPrice(selectorPrefix)).on('click', () => this.enableSpecificPriceFieldIfEligible(usePrefixForCreate),
);
// eslint-disable-next-line
$(SpecificMap.reductionType(selectorPrefix)).on('change', () => this.enableSpecificPriceTaxFieldIfEligible(usePrefixForCreate),
);
}
/**
* @private
*/
private configureEditPriceFormInsideModalBehavior() {
const usePrefixForCreate = false;
const selectorPrefix = this.getPrefixSelector(usePrefixForCreate);
$(SpecificMap.modalCancel).on('click', () => this.closeEditPriceModalAndRemoveForm(),
);
$(SpecificMap.modalClose).on('click', () => this.closeEditPriceModalAndRemoveForm(),
);
$(SpecificMap.modalSave).on('click', () => this.submitEditPriceForm());
this.loadAndFillOptionsForSelectCombinationInput(usePrefixForCreate);
$(SpecificMap.leavBPrice(selectorPrefix)).on('click', () => this.enableSpecificPriceFieldIfEligible(usePrefixForCreate),
);
$(SpecificMap.reductionType).on('change', () => this.enableSpecificPriceTaxFieldIfEligible(usePrefixForCreate),
);
this.reinitializeDatePickers();
this.initializeLeaveBPriceField(usePrefixForCreate);
this.enableSpecificPriceTaxFieldIfEligible(usePrefixForCreate);
}
/**
* @private
*/
private reinitializeDatePickers() {
$('.datepicker input').datetimepicker({
format: 'YYYY-MM-DD HH:mm:ss',
sideBySide: true,
icons: {
time: 'time',
date: 'date',
up: 'up',
down: 'down',
},
});
}
/**
* @param boolean usePrefixForCreate
*
* @private
*/
private initializeLeaveBPriceField(usePrefixForCreate: boolean): void {
const selectorPrefix = this.getPrefixSelector(usePrefixForCreate);
if ($(`${selectorPrefix}sp_price`).val() !== '') {
$(`${selectorPrefix}sp_price`).prop('disabled', false);
$(`${selectorPrefix}leave_bprice`).prop('checked', false);
}
}
/**
* @private
*/
private configureEditPriceModalBehavior(): void {
$(document).on('click', '#js-specific-price-list .js-edit', (event) => {
event.preventDefault();
const specificPriceId = $(event.currentTarget).data('specificPriceId');
this.openEditPriceModalAndLoadForm(specificPriceId);
});
}
/**
* @private
*/
private configureDeletePriceButtonsBehavior(): void {
$(document).on('click', '#js-specific-price-list .js-delete', (event) => {
event.preventDefault();
this.deleteSpecificPrice(event.currentTarget);
});
}
private configureMultipleModalsBehavior(): void {
$('.modal').on('hidden.bs.modal', () => {
if (this.editModalIsOpen) {
$('body').addClass('modal-open');
}
});
}
/**
* @private
*/
private submitCreatePriceForm(): void {
const url = $('#specific_price_form').attr('data-action');
const data = $(
'#specific_price_form input, #specific_price_form select, #form_id_product',
).serialize();
$('#specific_price_form .js-save').attr('disabled', 'disabled');
$.ajax({
type: 'POST',
url,
data,
})
.done(() => {
window.showSuccessMessage(
window.translate_javascripts['Form update success'],
);
this.resetCreatePriceFormDefaultValues();
$('#specific_price_form').collapse('hide');
this.loadAndDisplayExistingSpecificPricesList();
$('#specific_price_form .js-save').removeAttr('disabled');
})
.fail((errors) => {
window.showErrorMessage(errors.responseJSON);
$('#specific_price_form .js-save').removeAttr('disabled');
});
}
/**
* @private
*/
private submitEditPriceForm(): void {
const baseUrl = <string>(
$('#edit-specific-price-modal-form').attr('data-action')
);
const specificPriceId = $('#edit-specific-price-modal-form').data(
'specificPriceId',
);
const url = baseUrl.replace(/update\/\d+/, `update/${specificPriceId}`);
/* eslint-disable-next-line max-len */
const data = $(
'#edit-specific-price-modal-form input, #edit-specific-price-modal-form select, #form_id_product',
).serialize();
$('#edit-specific-price-modal-form .js-save').attr('disabled', 'disabled');
$.ajax({
type: 'POST',
url,
data,
})
.done(() => {
window.showSuccessMessage(
window.translate_javascripts['Form update success'],
);
this.closeEditPriceModalAndRemoveForm();
this.loadAndDisplayExistingSpecificPricesList();
$('#edit-specific-price-modal-form .js-save').removeAttr('disabled');
})
.fail((errors) => {
window.showErrorMessage(errors.responseJSON);
$('#edit-specific-price-modal-form .js-save').removeAttr('disabled');
});
}
/**
* @param string clickedLink selector
*
* @private
*/
private deleteSpecificPrice(clickedLink: HTMLElement): void {
window.modalConfirmation
.create(
window.translate_javascripts[
'Are you sure you want to delete this item?'
],
null,
{
onContinue: () => {
const url = $(clickedLink).attr('href');
$(clickedLink).attr('disabled', 'disabled');
$.ajax({
type: 'GET',
url,
})
.done((response) => {
this.loadAndDisplayExistingSpecificPricesList();
window.showSuccessMessage(response);
$(clickedLink).removeAttr('disabled');
})
.fail((errors) => {
window.showErrorMessage(errors.responseJSON);
$(clickedLink).removeAttr('disabled');
});
},
},
)
.show();
}
/**
* Store 'add specific price' form values
* for future usage
*
* @private
*/
private storePriceFormDefaultValues(): void {
const storage = this.$createPriceFormDefaultValues;
$('#specific_price_form')
.find('select,input')
.each((index, value) => {
storage[<string>$(value).attr('id')] = $(value).val();
});
$('#specific_price_form')
.find('input:checkbox')
.each((index, value) => {
storage[<string>$(value).attr('id')] = $(value).prop('checked');
});
this.$createPriceFormDefaultValues = storage;
}
/**
* @param boolean usePrefixForCreate
*
* @private
*/
private loadAndFillOptionsForSelectCombinationInput(
usePrefixForCreate: boolean,
): void {
const selectorPrefix = this.getPrefixSelector(usePrefixForCreate);
const inputField = $(`${selectorPrefix}sp_id_product_attribute`);
const action = <string>inputField.attr('data-action');
const url = action.replace(
/product-combinations\/\d+/,
`product-combinations/${this.getProductId()}`,
);
$.ajax({
type: 'GET',
url,
}).done((combinations) => {
/** remove all options except first one */
inputField.find('option:gt(0)').remove();
$.each(combinations, (index, combination) => {
inputField.append(
`<option value="${combination.id}">${combination.name}</option>`,
);
});
if (inputField.data('selectedAttribute') !== '0') {
inputField.val(inputField.data('selectedAttribute')).trigger('change');
}
});
}
/**
* @param boolean usePrefixForCreate
*
* @private
*/
private enableSpecificPriceTaxFieldIfEligible(
usePrefixForCreate: boolean,
): void {
const selectorPrefix = this.getPrefixSelector(usePrefixForCreate);
if ($(`${selectorPrefix}sp_reduction_type`).val() === 'percentage') {
$(`${selectorPrefix}sp_reduction_tax`).hide();
} else {
$(`${selectorPrefix}sp_reduction_tax`).show();
}
}
/**
* Reset 'add specific price' form values
* using previously stored default values
*
* @private
*/
private resetCreatePriceFormDefaultValues(): void {
const previouslyStoredValues = this.$createPriceFormDefaultValues;
$('#specific_price_form')
.find('input')
.each((index, value) => {
$(value).val(previouslyStoredValues[<string>$(value).attr('id')]);
});
$('#specific_price_form')
.find('select')
.each((index, value) => {
$(value)
.val(previouslyStoredValues[<string>$(value).attr('id')])
.change();
});
$('#specific_price_form')
.find('input:checkbox')
.each((index, value) => {
$(value).prop('checked', true);
});
}
/**
* @param boolean usePrefixForCreate
*
* @private
*/
private enableSpecificPriceFieldIfEligible(
usePrefixForCreate: boolean,
): void {
const selectorPrefix = this.getPrefixSelector(usePrefixForCreate);
$(`${selectorPrefix}sp_price`)
.prop('disabled', $(`${selectorPrefix}leave_bprice`).is(':checked'))
.val('');
}
/**
* Open 'edit specific price' form into a modal
*
* @param integer specificPriceId
*
* @private
*/
private openEditPriceModalAndLoadForm(specificPriceId: string): void {
const url = $('#js-specific-price-list')
.data('actionEdit')
.replace(/form\/\d+/, `form/${specificPriceId}`);
$('#edit-specific-price-modal').modal('show');
this.editModalIsOpen = true;
$.ajax({
type: 'GET',
url,
})
.done((response) => {
this.insertEditSpecificPriceFormIntoModal(response);
$('#edit-specific-price-modal-form').data(
'specificPriceId',
specificPriceId,
);
this.configureEditPriceFormInsideModalBehavior();
})
.fail((errors) => {
window.showErrorMessage(errors.responseJSON);
});
}
/**
* @private
*/
private closeEditPriceModalAndRemoveForm(): void {
$('#edit-specific-price-modal').modal('hide');
this.editModalIsOpen = false;
const formLocationHolder = $('#edit-specific-price-modal-form');
formLocationHolder.empty();
}
/**
* @param string form: HTML 'edit specific price' form
*
* @private
*/
insertEditSpecificPriceFormIntoModal(form: HTMLElement): void {
const formLocationHolder = $('#edit-specific-price-modal-form');
formLocationHolder.empty();
formLocationHolder.append(form);
}
/**
* Get product ID for current Catalog Product page
*
* @returns integer
*
* @private
*/
private getProductId(): string | number | string[] | undefined {
return $('#form_id_product').val();
}
/**
* @param boolean usePrefixForCreate
*
* @returns string
*
* @private
*/
private getPrefixSelector(usePrefixForCreate: boolean): string {
if (usePrefixForCreate) {
return `#${this.prefixCreateForm}`;
}
return `#${this.prefixEditForm}`;
}
}
export default SpecificPriceFormHandler;

View File

@@ -0,0 +1,12 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
redirectOption: {
typeInput: '#category_redirect_option_type',
targetInput: '#category_redirect_option_target',
groupSelector: '.form-group',
},
};

View File

@@ -0,0 +1,13 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {EventEmitter} from '@components/event-emitter';
import RedirectOptionManager from '@pages/category/edit/manager/redirect-option-manager';
const {$} = window;
$(() => {
new RedirectOptionManager(EventEmitter);
});

View File

@@ -0,0 +1,78 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import EntitySearchInput from '@components/entity-search-input';
import {EventEmitter} from 'events';
import CategoryMap from '@pages/category/category-map';
const {$} = window;
/**
* This component is used in category page to selected where the redirection points to when the
* category is disabled. It is composed on two inputs:
* - a selection of the redirection type
* - a rich component to select a category
*/
export default class RedirectOptionManager {
eventEmitter: EventEmitter;
$redirectTypeInput: JQuery;
$redirectTargetInput: JQuery;
$redirectTargetRow: JQuery;
entitySearchInput!: EntitySearchInput;
/**
* @param {EventEmitter} eventEmitter
*/
constructor(eventEmitter: EventEmitter) {
this.eventEmitter = eventEmitter;
this.$redirectTypeInput = $(CategoryMap.redirectOption.typeInput);
this.$redirectTargetInput = $(CategoryMap.redirectOption.targetInput);
// Target only inputs present in the redirect target row
this.$redirectTargetRow = this.$redirectTargetInput.closest(CategoryMap.redirectOption.groupSelector);
if (this.$redirectTargetInput.length) {
this.entitySearchInput = new EntitySearchInput(this.$redirectTargetInput, {});
this.watchRedirectType();
}
}
/**
* Watch the selected redirection type and adapt the inputs accordingly.
*
* @private
*/
private watchRedirectType(): void {
this.$redirectTypeInput.on('change', () => {
const redirectType = this.$redirectTypeInput.val();
switch (redirectType) {
case '301':
case '302':
this.entitySearchInput.setValues([]);
this.showTarget();
break;
case '404':
case '410':
default:
this.entitySearchInput.setValues([]);
this.hideTarget();
break;
}
});
}
private showTarget(): void {
this.$redirectTargetRow.removeClass('d-none');
}
private hideTarget(): void {
this.$redirectTargetRow.addClass('d-none');
}
}

View File

@@ -0,0 +1,91 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import CategoryPositionExtension from '@components/grid/extension/column/catalog/category-position-extension';
/* eslint-disable */
import DeleteCategoryRowActionExtension from '@components/grid/extension/action/row/category/delete-category-row-action-extension';
import DeleteCategoriesBulkActionExtension from '@components/grid/extension/action/bulk/category/delete-categories-bulk-action-extension';
/* eslint-enable */
import textToLinkRewriteCopier from '@components/text-to-link-rewrite-copier';
import FormSubmitButton from '@components/form-submit-button';
import ShowcaseCard from '@components/showcase-card/showcase-card';
import ShowcaseCardCloseExtension from '@components/showcase-card/extension/showcase-card-close-extension';
import Serp from '@app/utils/serp/index';
const {$} = window;
$(() => {
const categoriesGrid = new window.prestashop.component.Grid('category');
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
categoriesGrid.addExtension(new CategoryPositionExtension(categoriesGrid));
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.AsyncToggleColumnExtension());
categoriesGrid.addExtension(new DeleteCategoryRowActionExtension());
categoriesGrid.addExtension(new DeleteCategoriesBulkActionExtension());
categoriesGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
const showcaseCard = new ShowcaseCard('categoriesShowcaseCard');
showcaseCard.addExtension(new ShowcaseCardCloseExtension());
window.prestashop.component.initComponents(
[
'TranslatableField',
'TinyMCEEditor',
'TranslatableInput',
'TextWithRecommendedLengthCounter',
'ChoiceTable',
],
);
const translatorInput = window.prestashop.instance.translatableInput;
textToLinkRewriteCopier({
sourceElementSelector: 'input[name^="category[name]"]',
/* eslint-disable-next-line max-len */
destinationElementSelector: `${translatorInput.localeInputSelector}:not(.d-none) input[name^="category[link_rewrite]"]`,
});
textToLinkRewriteCopier({
sourceElementSelector: 'input[name^="root_category[name]"]',
/* eslint-disable-next-line max-len */
destinationElementSelector: `${translatorInput.localeInputSelector}:not(.d-none) input[name^="root_category[link_rewrite]"]`,
});
new Serp(
{
container: '#serp-app',
defaultTitle: 'input[name*="category[name]"]',
watchedTitle: 'input[name*="category[meta_title]"]',
defaultDescription: '#category_description .translation-field.active textarea',
watchedDescription: 'textarea[name*="category[meta_description]"]',
watchedMetaUrl: 'input[name*="category[link_rewrite]"]',
multiLanguageInput: `${translatorInput.localeInputSelector}:not(.d-none)`,
multiLanguageItem: translatorInput.localeItemSelector,
},
$('#serp-app').data('category-url'),
);
new FormSubmitButton();
new window.prestashop.component.TaggableField({
tokenFieldSelector: 'input.js-taggable-field',
options: {
createTokensOnBlur: true,
},
});
new window.prestashop.component.ChoiceTree('#category_id_parent');
new window.prestashop.component.ChoiceTree('#category_shop_association').enableAutoCheckChildren();
new window.prestashop.component.ChoiceTree('#root_category_id_parent');
new window.prestashop.component.ChoiceTree('#root_category_shop_association').enableAutoCheckChildren();
});

View File

@@ -0,0 +1,55 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import textToLinkRewriteCopier from '@components/text-to-link-rewrite-copier';
import Serp from '@app/utils/serp/index';
const {$} = window;
$(() => {
new window.prestashop.component.ChoiceTree('#cms_page_page_category_id');
window.prestashop.component.initComponents(
[
'TranslatableInput',
'TranslatableField',
'TinyMCEEditor',
'TextWithRecommendedLengthCounter',
],
);
const translatorInput = window.prestashop.instance.translatableInput;
new Serp(
{
container: '#serp-app',
defaultTitle: 'input[name^="cms_page[title]"]',
watchedTitle: 'input[name^="cms_page[meta_title]"]',
defaultDescription: 'input[name^="cms_page[description]"]',
watchedDescription: 'input[name^="cms_page[meta_description]"]',
watchedMetaUrl: 'input[name^="cms_page[friendly_url]"]',
multiLanguageInput: `${translatorInput.localeInputSelector}:not(.d-none)`,
multiLanguageItem: translatorInput.localeItemSelector,
},
$('#serp-app').data('cms-url'),
);
new window.prestashop.component.TaggableField({
tokenFieldSelector: 'input.js-taggable-field',
options: {
createTokensOnBlur: true,
},
});
new window.prestashop.component.PreviewOpener('.js-preview-url');
textToLinkRewriteCopier({
sourceElementSelector: 'input.js-copier-source-title',
/* eslint-disable-next-line max-len */
destinationElementSelector: `${translatorInput.localeInputSelector}:not(.d-none) input.js-copier-destination-friendly-url`,
});
new window.prestashop.component.ChoiceTree('#cms_page_shop_association').enableAutoCheckChildren();
});

View File

@@ -0,0 +1,61 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import textToLinkRewriteCopier from '@components/text-to-link-rewrite-copier';
import ShowcaseCard from '@components/showcase-card/showcase-card';
import ShowcaseCardCloseExtension from '@components/showcase-card/extension/showcase-card-close-extension';
const {$} = window;
$(() => {
const cmsCategory = new window.prestashop.component.Grid('cms_page_category');
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.PositionExtension(cmsCategory));
cmsCategory.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
window.prestashop.component.initComponents(
[
'TranslatableInput',
],
);
const translatorInput = window.prestashop.instance.translatableInput;
textToLinkRewriteCopier({
sourceElementSelector: 'input[name^="cms_page_category[name]"]',
/* eslint-disable-next-line max-len */
destinationElementSelector: `${translatorInput.localeInputSelector}:not(.d-none) input[name^="cms_page_category[friendly_url]"]`,
});
new window.prestashop.component.ChoiceTree('#cms_page_category_parent_category');
const shopChoiceTree = new window.prestashop.component.ChoiceTree('#cms_page_category_shop_association');
shopChoiceTree.enableAutoCheckChildren();
const cmsGrid = new window.prestashop.component.Grid('cms_page');
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.PositionExtension(cmsGrid));
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
cmsGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
const helperBlock = new ShowcaseCard('cms-pages-showcase-card');
helperBlock.addExtension(new ShowcaseCardCloseExtension());
});

View File

@@ -0,0 +1,31 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Responsible for actions in Contacts listing page.
*/
export default class ContactsPage {
constructor() {
const contactGrid = new window.prestashop.component.Grid('contact');
contactGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
contactGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
contactGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
contactGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
contactGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
contactGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitGridActionExtension());
contactGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
contactGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
contactGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
window.prestashop.component.initComponents(
[
'TranslatableInput',
],
);
new window.prestashop.component.ChoiceTree('#contact_shop_association').enableAutoCheckChildren();
}
}

View File

@@ -0,0 +1,12 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ContactsPage from './ContactsPage';
const {$} = window;
$(() => {
new ContactsPage();
});

View File

@@ -0,0 +1,20 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import FormFieldToggler, {ToggleType} from '@components/form/form-field-toggler';
import CountryMap from '@pages/country/country-map';
export default class ZipCodeManager {
constructor() {
this.initZipCodeToggler();
}
private initZipCodeToggler(): void {
new FormFieldToggler({
disablingInputSelector: CountryMap.isZipCodeNeededSwitch,
targetSelector: CountryMap.zipCodeFormatInput,
toggleType: ToggleType.availability,
});
}
}

View File

@@ -0,0 +1,9 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
isZipCodeNeededSwitch: 'input[name="country[need_zip_code]"]',
zipCodeFormatInput: '#country_zip_code_format',
};

View File

@@ -0,0 +1,20 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ZipCodeManager from '@pages/country/components/zip-code-manager';
import FormSubmitButton from '@components/form-submit-button';
const {$} = window;
$(() => {
window.prestashop.component.initComponents(
[
'TranslatableInput',
],
);
new FormSubmitButton();
new ZipCodeManager();
});

View File

@@ -0,0 +1,19 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const countryGrid = new window.prestashop.component.Grid('country');
countryGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
countryGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
countryGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
countryGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
countryGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
countryGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
countryGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
countryGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
countryGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
countryGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
});

View File

@@ -0,0 +1,20 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const creditSlipGrid = new window.prestashop.component.Grid('credit_slip');
creditSlipGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
creditSlipGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
creditSlipGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
creditSlipGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
creditSlipGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
window.prestashop.component.initComponents(
[
'TranslatableInput',
],
);
});

View File

@@ -0,0 +1,76 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const {$} = window;
/**
* This class triggers events required for turning on or off exchange rates scheduler an displaying the right text
* below the switch.
*/
export default class ExchangeRatesUpdateScheduler {
constructor() {
this.initEvents();
}
private initEvents() {
$(document).on(
'change',
'.js-live-exchange-rate',
(event: JQueryEventObject) => this.initLiveExchangeRate(event),
);
}
/**
* @param {Object} event
*
* @private
*/
private initLiveExchangeRate(event: JQueryEventObject) {
const $liveExchangeRatesSwitch = $(event.currentTarget);
const $form = $liveExchangeRatesSwitch.closest('form');
const formItems = $form.serialize();
$.ajax({
type: 'POST',
url: $liveExchangeRatesSwitch.attr('data-url'),
data: formItems,
})
.then((response) => {
if (!response.status) {
window.showErrorMessage(response.message);
this.changeTextByCurrentSwitchValue(
<string>$liveExchangeRatesSwitch.val(),
);
return;
}
window.showSuccessMessage(response.message);
this.changeTextByCurrentSwitchValue(
<string>$liveExchangeRatesSwitch.val(),
);
})
.fail((response: AjaxResponse) => {
if (typeof response.responseJSON !== 'undefined') {
window.showErrorMessage(response.responseJSON.message);
this.changeTextByCurrentSwitchValue(
<string>$liveExchangeRatesSwitch.val(),
);
}
});
}
changeTextByCurrentSwitchValue(switchValue: string): void {
const valueParsed = parseInt(switchValue, 10);
$('.js-exchange-rate-text-when-disabled').toggleClass(
'd-none',
valueParsed !== 0,
);
$('.js-exchange-rate-text-when-enabled').toggleClass(
'd-none',
valueParsed !== 1,
);
}
}

View File

@@ -0,0 +1,111 @@
<!--*
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*-->
<template>
<div class="row">
<div class="col-4">
<h4>{{ $t('step.symbol') }}</h4>
<input
data-role="custom-symbol"
type="text"
v-model="customSymbol"
>
</div>
<div class="col-8 border-left">
<h4>{{ $t('step.format') }}</h4>
<div class="row">
<div
class="ps-radio col-6"
v-for="(pattern, transformation) in availableFormats"
:key="transformation"
:id="transformation"
>
<input
type="radio"
:checked="transformation === customTransformation"
:value="transformation"
>
<label @click.prevent.stop="customTransformation = transformation">
{{ displayPattern(pattern) }}
</label>
</div>
</div>
</div>
</div>
</template>
<script>
import {NumberFormatter} from '@app/cldr';
import {defineComponent} from 'vue';
export default defineComponent({
name: 'CurrencyFormatForm',
data: () => ({
value: {
symbol: '',
transformation: '',
},
}),
props: {
language: {
type: Object,
required: true,
default: () => {},
},
},
computed: {
availableFormats() {
return this.language.transformations;
},
customSymbol: {
get() {
return this.value.symbol;
},
set(symbol) {
this.value.symbol = symbol;
this.$emit('formatChange', this.value);
},
},
customTransformation: {
get() {
return this.value.transformation;
},
set(transformation) {
this.value.transformation = transformation;
this.$emit('formatChange', this.value);
},
},
},
methods: {
displayPattern(pattern) {
const patterns = pattern.split(';');
const priceSpecification = {...this.language.priceSpecification};
priceSpecification.positivePattern = patterns[0];
priceSpecification.negativePattern = patterns.length > 1 ? patterns[1] : `-${pattern}`;
priceSpecification.currencySymbol = this.customSymbol;
const currencyFormatter = NumberFormatter.build(priceSpecification);
return currencyFormatter.format(14251999.42);
},
},
mounted() {
this.customSymbol = this.language.priceSpecification.currencySymbol;
const currencyPattern = this.language.priceSpecification.positivePattern;
// Detect which transformation matches the language pattern
/* eslint-disable-next-line no-restricted-syntax,guard-for-in */
for (const transformation in this.language.transformations) {
const transformationPatterns = this.language.transformations[
transformation
].split(';');
if (transformationPatterns[0] === currencyPattern) {
this.customTransformation = transformation;
break;
}
}
},
});
</script>

View File

@@ -0,0 +1,98 @@
<!--*
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*-->
<template>
<div
:id="id"
class="card-block row"
>
<div class="col-sm">
<language-list
v-if="languagesCount"
:languages="languages"
@selectLanguage="selectLanguage"
@resetLanguage="resetLanguage"
/>
<currency-modal
:language="selectedLanguage"
@close="closeModal"
@applyCustomization="applyCustomization"
/>
</div>
</div>
</template>
<script>
import {showGrowl} from '@app/utils/growl';
import {EventEmitter} from '@components/event-emitter';
import CurrencyFormEventMap from '@pages/currency/form/currency-form-event-map';
import LanguageList from './LanguageList';
import CurrencyModal from './CurrencyModal';
export default {
name: 'CurrencyFormatter',
data: () => ({selectedLanguage: null}),
props: {
id: {
type: String,
required: true,
},
languages: {
type: Array,
required: true,
},
currencyData: {
type: Object,
required: true,
},
},
components: {LanguageList, CurrencyModal},
computed: {
languagesCount() {
return this.languages.length;
},
},
methods: {
closeModal() {
this.selectedLanguage = null;
},
selectLanguage(language) {
this.selectedLanguage = language;
},
resetLanguage(language) {
const patterns = language.currencyPattern.split(';');
language.priceSpecification.positivePattern = patterns[0];
language.priceSpecification.negativePattern = patterns.length > 1 ? patterns[1] : `-${patterns[0]}`;
language.priceSpecification.currencySymbol = language.currencySymbol;
this.currencyData.transformations[language.id] = '';
this.currencyData.symbols[language.id] = language.currencySymbol;
showGrowl('success', this.$t('list.reset.success'));
EventEmitter.emit(CurrencyFormEventMap.refreshCurrencyApp, this.currencyData);
},
applyCustomization(customData) {
const selectedPattern = this.selectedLanguage.transformations[
customData.transformation
];
const patterns = selectedPattern.split(';');
this.selectedLanguage.priceSpecification.currencySymbol = customData.symbol;
this.selectedLanguage.priceSpecification.positivePattern = patterns[0];
// eslint-disable-next-line
this.selectedLanguage.priceSpecification.negativePattern =
patterns.length > 1 ? patterns[1] : `-${patterns[0]}`;
this.currencyData.transformations[this.selectedLanguage.id] = customData.transformation;
this.currencyData.symbols[this.selectedLanguage.id] = customData.symbol;
EventEmitter.emit(CurrencyFormEventMap.refreshCurrencyApp, this.currencyData);
this.closeModal();
},
},
};
</script>

View File

@@ -0,0 +1,64 @@
<!--*
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*-->
<template>
<div data-role="currency-format-edit-modal">
<modal
confirmation
:modal-title="modalTitle"
@close="$emit('close')"
@confirm="$emit('applyCustomization', customData)"
v-if="language !== null"
class=""
>
<template #body>
<currency-format-form
:language="language"
@formatChange="customData = $event"
/>
</template>
</modal>
</div>
</template>
<script>
import Modal from '@PSVue/components/Modal';
import {defineComponent} from 'vue';
import CurrencyFormatForm from './CurrencyFormatForm';
export default defineComponent({
name: 'CurrencyModal',
data: () => ({
customData: null,
}),
components: {
CurrencyFormatForm,
Modal,
},
props: {
language: {
type: Object,
required: false,
default: null,
},
},
computed: {
modalTitle() {
return this.$t('modal.title') + (this.language !== null ? ` + ${this.language.name}` : '');
},
},
});
</script>
<style lang="scss" scoped>
@import '~@scss/config/_settings.scss';
.modal-header .close {
font-size: var(--#{$cdk}size-20);
opacity: 1;
}
.modal-content {
border-radius: 0
}
</style>

View File

@@ -0,0 +1,91 @@
<!--*
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*-->
<template>
<table class="grid-table js-grid-table table">
<thead class="thead-default">
<tr class="column-headers">
<th scope="col">
{{ $t('list.language') }}
</th>
<th scope="col">
{{ $t('list.example') }}
</th>
<th scope="col">
<div class="text-right">
{{ $t('list.edit') }}
</div>
</th>
<th scope="col">
<div class="grid-actions-header-text">
{{ $t('list.reset') }}
</div>
</th>
</tr>
</thead>
<tbody>
<tr
v-for="language in languages"
:key="language.id"
>
<td>
{{ language.name }}
</td>
<td>
{{ displayFormat(language) }}
</td>
<td>
<div class="btn-group-action text-right">
<div class="btn-group">
<button
type="button"
class="btn"
@click.prevent.stop="$emit('selectLanguage', language)"
>
<i class="material-icons">edit</i>
</button>
</div>
</div>
</td>
<td>
<div class="btn-group-action text-right">
<div class="btn-group">
<button
type="button"
class="btn"
@click.prevent.stop="$emit('resetLanguage', language)"
>
<i class="material-icons">refresh</i>
</button>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<script>
import {NumberFormatter} from '@app/cldr';
export default {
name: 'LanguageList',
props: {
languages: {
type: Array,
required: true,
},
},
methods: {
displayFormat(language) {
const currencyFormatter = NumberFormatter.build(language.priceSpecification);
return this.$t('list.example.format', {
price: currencyFormatter.format(14251999.42),
discount: currencyFormatter.format(-566.268),
});
},
},
};
</script>

View File

@@ -0,0 +1,11 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines all selectors that are used in currency add/edit form events.
*/
export default {
refreshCurrencyApp: 'refreshCurrencyApp',
};

View File

@@ -0,0 +1,24 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines all selectors that are used in currency add/edit form.
*/
export default {
currencyForm: '#currency_form',
currencyFormFooter: '#currency_form .card .card-footer',
currencySelector: '#currency_selected_iso_code',
isUnofficialCheckbox: '#currency_unofficial',
namesInput: (langId: string): string => `#currency_names_${langId}`,
symbolsInput: (langId: string): string => `#currency_symbols_${langId}`,
transformationsInput: (langId: string): string => `#currency_transformations_${langId}`,
isoCodeInput: '#currency_iso_code',
exchangeRateInput: '#currency_exchange_rate',
resetDefaultSettingsInput: '#currency_reset_default_settings',
loadingDataModal: '#currency_loading_data_modal',
precisionInput: '#currency_precision',
shopAssociationTree: '#currency_shop_association',
currencyFormatter: '#currency_formatter',
};

View File

@@ -0,0 +1,307 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {createApp} from 'vue';
import {createI18n} from 'vue-i18n';
import {showGrowl} from '@app/utils/growl';
import ConfirmModal from '@components/modal';
import ReplaceFormatter from '@PSVue/plugins/vue-i18n/replace-formatter';
import {EventEmitter} from '@components/event-emitter';
import CurrencyFormatter from './components/CurrencyFormatter.vue';
import CurrencyFormEventMap from './currency-form-event-map';
export default class CurrencyForm {
map: Record<string, any>;
$currencyForm: JQuery;
$currencyFormFooter: JQuery;
apiReferenceUrl: string;
originalLanguages: any;
translations: Record<string, any>;
$currencySelector: JQuery;
$isUnofficialCheckbox: JQuery;
$isoCodeInput: JQuery;
$exchangeRateInput: JQuery;
$precisionInput: JQuery;
$resetDefaultSettingsButton: JQuery;
currencyFormatterId: string;
$loadingDataModal: JQuery;
hideModal: boolean;
state: Record<string, any>;
currencyFormatter: any;
/**
* @param {object} currencyFormMap - Page map
*/
constructor(currencyFormMap: Record<string, any>) {
this.map = currencyFormMap;
this.$currencyForm = $(this.map.currencyForm);
this.$currencyFormFooter = $(this.map.currencyFormFooter);
this.apiReferenceUrl = this.$currencyForm.data('reference-url');
this.originalLanguages = this.$currencyForm.data('languages');
this.translations = this.$currencyForm.data('translations');
this.$currencySelector = $(this.map.currencySelector);
this.$isUnofficialCheckbox = $(this.map.isUnofficialCheckbox);
this.$isoCodeInput = $(this.map.isoCodeInput);
this.$exchangeRateInput = $(this.map.exchangeRateInput);
this.$precisionInput = $(this.map.precisionInput);
this.$resetDefaultSettingsButton = $(this.map.resetDefaultSettingsInput);
this.$loadingDataModal = $(this.map.loadingDataModal);
this.currencyFormatterId = this.map.currencyFormatter.replace('#', '');
this.hideModal = true;
this.currencyFormatter = null;
this.$loadingDataModal.on('shown.bs.modal', () => {
if (this.hideModal) {
this.$loadingDataModal.modal('hide');
}
});
this.state = {};
}
init(): void {
this.initListeners();
this.initFields();
this.initState();
this.initCurrencyFormatter();
EventEmitter.on(CurrencyFormEventMap.refreshCurrencyApp, (currencyData) => {
this.state.currencyData = currencyData;
this.fillCurrencyCustomData(currencyData);
this.initCurrencyFormatter();
});
}
initState(): void {
this.state = {
currencyData: this.getCurrencyDataFromForm(),
languages: [...this.originalLanguages],
};
}
initCurrencyFormatter(): void {
// Customizer only present when languages data are present (for installed currencies only)
if (!this.originalLanguages.length) {
return;
}
$(`<div id="${this.currencyFormatterId}"></div>`).insertBefore(
this.$currencyFormFooter,
);
const i18n = createI18n({
locale: 'en',
formatter: new ReplaceFormatter(),
messages: {en: this.translations},
});
this.currencyFormatter = createApp(CurrencyFormatter, {
data: () => this.state,
languages: this.state.languages,
currencyData: this.state.currencyData,
id: this.currencyFormatterId,
}).use(i18n).mount(this.map.currencyFormatter);
}
initListeners(): void {
this.$currencySelector.on('change', this.onCurrencySelectorChange.bind(this));
this.$isUnofficialCheckbox.on(
'change',
this.onIsUnofficialCheckboxChange.bind(this),
);
this.$resetDefaultSettingsButton.on(
'click',
this.onResetDefaultSettingsClick.bind(this),
);
}
initFields(): void {
if (!this.isUnofficialCurrency()) {
this.$isUnofficialCheckbox.prop('checked', false);
this.$isoCodeInput.prop('readonly', true);
} else {
this.$currencySelector.val('');
this.$isoCodeInput.prop('readonly', false);
}
}
onCurrencySelectorChange(): void {
const selectedISOCode = this.$currencySelector.val();
if (selectedISOCode !== '') {
this.$isUnofficialCheckbox.prop('checked', false);
this.$isoCodeInput.prop('readonly', true);
this.resetCurrencyData(<string>selectedISOCode);
} else {
this.$isUnofficialCheckbox.prop('checked', true);
this.$isoCodeInput.prop('readonly', false);
}
}
isUnofficialCurrency(): boolean {
if (this.$isUnofficialCheckbox.prop('type') === 'hidden') {
return this.$isUnofficialCheckbox.attr('value') === '1';
}
return this.$isUnofficialCheckbox.prop('checked');
}
onIsUnofficialCheckboxChange(): void {
if (this.isUnofficialCurrency()) {
this.$currencySelector.val('');
this.$isoCodeInput.prop('readonly', false);
} else {
this.$isoCodeInput.prop('readonly', true);
}
}
async onResetDefaultSettingsClick(): Promise<void> {
await this.resetCurrencyData(<string> this.$isoCodeInput.val());
}
showResetDefaultSettingsConfirmModal(): void {
const confirmTitle = this.translations['modal.restore.title'];
const confirmMessage = this.translations['modal.restore.body'];
const confirmButtonLabel = this.translations['modal.restore.apply'];
const closeButtonLabel = this.translations['modal.restore.cancel'];
const modal = new ConfirmModal(
{
id: 'currency_restore_default_settings',
confirmTitle,
confirmMessage,
confirmButtonLabel,
closeButtonLabel,
},
() => this.onResetDefaultSettingsClick(),
);
modal.show();
}
async resetCurrencyData(selectedISOCode: string): Promise<void> {
this.$loadingDataModal.modal('show');
this.$resetDefaultSettingsButton.addClass('spinner');
this.state.currencyData = await this.fetchCurrency(selectedISOCode);
this.fillCurrencyData(this.state.currencyData);
// Reset languages
this.originalLanguages.forEach((language: Record<string, any>) => {
// Use language data (which contain the reference) to reset
// price specification data (which contain the custom values)
const patterns = language.currencyPattern.split(';');
/* eslint-disable */
language.priceSpecification.positivePattern = patterns[0];
language.priceSpecification.negativePattern =
patterns.length > 1 ? patterns[1] : `-${patterns[0]}`;
language.priceSpecification.currencySymbol = language.currencySymbol;
/* eslint-enable */
});
this.state.languages = [...this.originalLanguages];
EventEmitter.emit(CurrencyFormEventMap.refreshCurrencyApp, this.state.currencyData);
this.hideModal = true;
this.$loadingDataModal.modal('hide');
this.$resetDefaultSettingsButton.removeClass('spinner');
}
async fetchCurrency(currencyIsoCode: string): Promise<Record<string, any>> {
let currencyData: Record<string, any> = {};
if (currencyIsoCode) {
try {
const response = await fetch(`${this.apiReferenceUrl.replace('{/id}', `/${currencyIsoCode}`)}`);
currencyData = await response.json();
if (currencyData && currencyData.transformations === undefined) {
currencyData.transformations = {};
Object.keys(currencyData.symbols).forEach((langId) => {
currencyData.transformations[langId] = '';
});
}
} catch (errorResponse: any) {
if (errorResponse.body && errorResponse.body.error) {
showGrowl('error', errorResponse.body.error, 3000);
} else {
showGrowl(
'error',
`Can not find CLDR data for currency ${currencyIsoCode}`,
3000,
);
}
}
}
return currencyData;
}
fillCurrencyData(
currencyData: Record<string, any>,
): void | Record<string, any> {
if (!currencyData) {
return;
}
Object.keys(currencyData.symbols).forEach((langId) => {
const langNameSelector = this.map.namesInput(langId);
$(langNameSelector).val(currencyData.names[langId]);
});
this.fillCurrencyCustomData(currencyData);
this.$isoCodeInput.val(currencyData.isoCode);
this.$exchangeRateInput.val(currencyData.exchangeRate);
this.$precisionInput.val(currencyData.precision);
}
fillCurrencyCustomData(currencyData: Record<string, any>): void {
Object.keys(currencyData.symbols).forEach((langId) => {
const langSymbolSelector = this.map.symbolsInput(langId);
$(langSymbolSelector).val(currencyData.symbols[langId]);
});
Object.keys(currencyData.transformations).forEach((langId) => {
const langTransformationSelector = this.map.transformationsInput(langId);
$(langTransformationSelector).val(currencyData.transformations[langId]);
});
}
getCurrencyDataFromForm(): Record<string, any> {
const currencyData: Record<string, any> = {
names: {},
symbols: {},
transformations: {},
isoCode: this.$isoCodeInput.val(),
exchangeRate: this.$exchangeRateInput.val(),
precision: this.$precisionInput.val(),
};
this.originalLanguages.forEach((lang: Record<string, any>) => {
currencyData.names[<string>lang.id] = $(
this.map.namesInput(lang.id),
).val();
currencyData.symbols[lang.id] = $(this.map.symbolsInput(lang.id)).val();
currencyData.transformations[lang.id] = $(
this.map.transformationsInput(lang.id),
).val();
});
return currencyData;
}
}

View File

@@ -0,0 +1,19 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import currencyFormMap from './currency-form-map';
import CurrencyForm from './currency-form';
const {$} = window;
$(() => {
window.prestashop.component.initComponents(['TranslatableInput']);
const choiceTree = new window.prestashop.component.ChoiceTree(
currencyFormMap.shopAssociationTree,
);
choiceTree.enableAutoCheckChildren();
const currencyForm = new CurrencyForm(currencyFormMap);
currencyForm.init();
});

View File

@@ -0,0 +1,25 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ExchangeRatesUpdateScheduler from '@pages/currency/ExchangeRatesUpdateScheduler';
const {$} = window;
$(() => {
const currency = new window.prestashop.component.Grid('currency');
currency.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
currency.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
currency.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
currency.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
currency.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
currency.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
currency.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
currency.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
currency.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
currency.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
new ExchangeRatesUpdateScheduler();
});

View File

@@ -0,0 +1,19 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const customerGroups = new window.prestashop.component.Grid('customer_groups');
customerGroups.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
customerGroups.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
customerGroups.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
customerGroups.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
customerGroups.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
customerGroups.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
customerGroups.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
customerGroups.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
customerGroups.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
customerGroups.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
});

View File

@@ -0,0 +1,13 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines all selectors that are used in customer preferences form.
*/
export default {
checkboxPartnerOffersAlertOptin: '#configurationFormAlertMessageOptin',
switchPartnerOffers: '#form_enable_offers',
checkboxPartnerOffers: 'input[name="form[enable_offers]"]:checked',
};

View File

@@ -0,0 +1,24 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import CustomerPreferencesMap from '@pages/customer-preferences/customer-preferences-map';
$(() => {
window.prestashop.component.initComponents(
[
'MultistoreConfigField',
],
);
// Required fields : Display alert for optin checkbox
$(CustomerPreferencesMap.switchPartnerOffers).on('click', () => handleFormCheckboxPartnerOffers());
function handleFormCheckboxPartnerOffers(): void {
$(CustomerPreferencesMap.checkboxPartnerOffersAlertOptin).toggleClass(
'd-none',
($(CustomerPreferencesMap.checkboxPartnerOffers).val() === '1'),
);
}
});

View File

@@ -0,0 +1,10 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
forwardCustomerThreadModal: '#forwardThreadModal',
forwardSomeoneElseEmailInput: '#forward_customer_thread_someone_else_email',
forwardEmployeeInput: '#forward_customer_thread_employee_id',
};

View File

@@ -0,0 +1,22 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const grid = new window.prestashop.component.Grid('customer_thread');
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitGridActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ChoiceExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.PositionExtension(grid));
});

View File

@@ -0,0 +1,27 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import CustomerThreadViewPageMap from './customer-thread-view-page-map';
const {$} = window;
$(() => {
$(CustomerThreadViewPageMap.forwardEmployeeInput).on('change', (event) => {
const $someoneElseEmailInput = $(
CustomerThreadViewPageMap.forwardSomeoneElseEmailInput,
);
const $someElseEmailFormGroup = $someoneElseEmailInput.closest(
'.form-group',
);
const employeeId = $(event.currentTarget).val();
if (parseInt(<string>employeeId, 10) === 0) {
$someElseEmailFormGroup.removeClass('d-none');
} else {
$someElseEmailFormGroup.addClass('d-none');
}
});
});

View File

@@ -0,0 +1,139 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import customerFormMap from './customer-form-map';
import ChangePasswordHandler from '../../components/change-password-handler';
/**
* Class responsible for javascript actions in customer add/edit page.
*/
export default class CustomerForm {
constructor() {
// Watch password field change and strength indicator
const passwordHandler = new ChangePasswordHandler(
customerFormMap.passwordStrengthFeedbackContainer,
);
passwordHandler.watchPasswordStrength($(customerFormMap.passwordInput));
// Watch customer group checkbox change and if it was unchecked,
// update default group below if it's no longer in the list.
$(customerFormMap.customerGroupCheckboxes).on('change', (event) => {
this.checkOrUpdateDefaultGroup($(event.currentTarget).is(':checked'));
});
// Watch is_guest switch change and update other inputs accordingly
$(customerFormMap.isGuestRadios).on('change', (event) => {
if (Number($(event.currentTarget).val()) === 1) {
this.adaptFormForGuestCustomer();
} else {
this.adaptFormForRegisteredCustomer();
}
});
}
private checkOrUpdateDefaultGroup(wasChecked: boolean): void {
// Get currently selected group ID
const currentDefaultGroup = Number(<string> $(customerFormMap.defaultGroupSelectedOption).val());
// Get all checked groups in group access
const checkedGroups: number[] = [];
let firstGroupInList: number = 0;
$(customerFormMap.customerGroupCheckboxes).each((index, input) => {
const groupId = Number(<string> $(input).val());
// We will keep track of all checked groups
if ($(input).is(':checked')) {
checkedGroups.push(groupId);
}
// And store ID of the first group regardless of it's status
if (index === 0) {
firstGroupInList = groupId;
}
});
// If no groups are selected, use the first group in the list, no matter
// if it's selected or not.
if (!checkedGroups.length) {
$(customerFormMap.defaultGroupSelect).val(firstGroupInList).trigger('change');
return;
}
// If the last change was a newly added group and it's the only one in the list,
// we will set it as the default group.
if (wasChecked && checkedGroups.length === 1) {
$(customerFormMap.defaultGroupSelect).val(checkedGroups[0]).trigger('change');
return;
}
// If the default group is not in the list anymore, select the first checked group.
if (!checkedGroups.includes(currentDefaultGroup)) {
$(customerFormMap.defaultGroupSelect).val(checkedGroups[0]).trigger('change');
}
}
private adaptFormForGuestCustomer(): void {
// Disable password input and clean it
$(customerFormMap.passwordInput)
.prop('disabled', 'disabled')
.prop('required', false)
.val('')
.removeClass('border-danger')
.removeClass('border-success')
.popover('dispose');
// Hide password feedback
$(customerFormMap.passwordStrengthFeedbackContainer).toggleClass('d-none', true);
// Check groups and disable all checkboxes except guest group
$(customerFormMap.customerGroupCheckboxes).each((index, input) => {
if (Number($(input).val()) === window.data.guestGroupId) {
$(input).prop('checked', 'checked');
} else {
$(input).prop('checked', false);
}
$(input).prop('disabled', 'disabled');
});
// Disable select all selector
$('.js-choice-table-select-all').prop('disabled', 'disabled');
// Set guest default group and disable the field
$(customerFormMap.defaultGroupSelect).prop('disabled', 'disabled').val(window.data.guestGroupId).trigger('change');
// Disable "Enabled" input and set it to yes
$(customerFormMap.isEnabledRadios).prop('disabled', 'disabled');
$(customerFormMap.isEnabledRadiosOff).prop('checked', false);
$(customerFormMap.isEnabledRadiosOn).prop('checked', 'checked');
}
private adaptFormForRegisteredCustomer(): void {
// Enable password input
$(customerFormMap.passwordInput)
.prop('disabled', false)
.prop('required', 'required');
// Check default groups and enable all checkboxes
$(customerFormMap.customerGroupCheckboxes).each((index, input) => {
if (window.data.defaultGroups.includes(Number($(input).val()))) {
$(input).prop('checked', 'checked');
} else {
$(input).prop('checked', false);
}
$(input).prop('disabled', false);
});
// Enable select all selector
$('.js-choice-table-select-all').prop('disabled', false);
// Set customer group as default group and enable the field
$(customerFormMap.defaultGroupSelect).prop('disabled', false)
.val(window.data.customerGroupId).trigger('change');
// Enable "Enabled" input and set it to yes
$(customerFormMap.isEnabledRadios).prop('disabled', false);
$(customerFormMap.isEnabledRadiosOff).prop('checked', false);
$(customerFormMap.isEnabledRadiosOn).prop('checked', 'checked');
}
}

View File

@@ -0,0 +1,27 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines all selectors that are used in customer add/edit form.
*/
export default {
passwordInput: '#customer_password',
passwordStrengthFeedbackContainer: '.password-strength-feedback',
requiredFieldsFormAlertOptin: '#customerRequiredFieldsAlertMessageOptin',
requiredFieldsFormCheckboxOptin: '#customerRequiredFieldsContainer input[type="checkbox"][value="optin"]',
// Customer group inputs
customerGroupCheckboxes: 'input[type="checkbox"][name="customer[group_ids][]"]',
defaultGroupSelect: '#customer_default_group_id',
defaultGroupSelectedOption: '#customer_default_group_id option:selected',
// Is guest switch selector
isGuestRadios: 'input[name="customer[is_guest]"]',
// Is enabled switch and it's radios
isEnabledRadios: 'input[name="customer[is_enabled]"]',
isEnabledRadiosOn: 'input[name="customer[is_enabled]"][value="1"]',
isEnabledRadiosOff: 'input[name="customer[is_enabled]"][value="0"]',
};

View File

@@ -0,0 +1,16 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import CustomerForm from './CustomerForm';
$(() => {
new CustomerForm();
window.prestashop.component.initComponents(
[
'ChoiceTable',
],
);
});

View File

@@ -0,0 +1,120 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import FormSubmitButton from '@components/form-submit-button';
import LinkableItem from '@components/linkable-item';
import DeleteCustomersBulkActionExtension
from '@components/grid/extension/action/bulk/customer/delete-customers-bulk-action-extension';
import DeleteCustomerRowActionExtension
from '@components/grid/extension/action/row/customer/delete-customer-row-action-extension';
import ShowcaseCard from '@components/showcase-card/showcase-card';
import ShowcaseCardCloseExtension from '@components/showcase-card/extension/showcase-card-close-extension';
import CustomerFormMap from '@pages/customer/customer-form-map';
const {$} = window;
$(() => {
const customerGrid = new window.prestashop.component.Grid('customer');
customerGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
customerGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
customerGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
customerGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
customerGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
customerGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
customerGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitGridActionExtension());
customerGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
customerGrid.addExtension(new DeleteCustomersBulkActionExtension());
customerGrid.addExtension(new DeleteCustomerRowActionExtension());
customerGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
customerGrid.addExtension(new window.prestashop.component.GridExtensions.AsyncToggleColumnExtension());
const customerDiscountsGrid = new window.prestashop.component.Grid('customer_discount');
customerDiscountsGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
customerDiscountsGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
const customerAddressesGrid = new window.prestashop.component.Grid('customer_address');
customerAddressesGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
customerAddressesGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
customerAddressesGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
const customerOrdersGrid = new window.prestashop.component.Grid('customer_order');
customerOrdersGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
customerOrdersGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
customerOrdersGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
const customerCartsGrid = new window.prestashop.component.Grid('customer_cart');
customerCartsGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
customerCartsGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
customerCartsGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
const customerBoughtProductsGrid = new window.prestashop.component.Grid('customer_bought_product');
customerBoughtProductsGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
const customerViewedProductsGrid = new window.prestashop.component.Grid('customer_viewed_product');
customerViewedProductsGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
const showcaseCard = new ShowcaseCard('customersShowcaseCard');
showcaseCard.addExtension(new ShowcaseCardCloseExtension());
// in customer view page
// there are a lot of tables
// where you click any row
// and it redirects user to related page
new LinkableItem();
new FormSubmitButton();
// Scroll to the block
scrollToBlock();
// Required fields : Display alert for optin checkbox
$(CustomerFormMap.requiredFieldsFormCheckboxOptin).on('click', () => handleRequiredFieldsFormCheckboxOptin());
function scrollToBlock(): void {
const documentURL = new URL(document.URL);
const documentHash = documentURL.hash.slice(1);
if (documentHash === '') {
return;
}
const element = document.getElementById(documentHash);
if (!element) {
return;
}
// Fetch its position
let positionTop = 0;
if (element.offsetParent) {
let elementParent: HTMLElement|null = element;
do {
positionTop += elementParent.offsetTop;
elementParent = elementParent.offsetParent ? <HTMLElement> (elementParent.offsetParent) : null;
} while (elementParent !== null);
}
// Remove the header height
positionTop -= document.querySelector('#header_infos')?.getBoundingClientRect()?.height ?? 0;
// Remove the title bar height
positionTop -= document.querySelector('.header-toolbar')?.getBoundingClientRect()?.height ?? 0;
// Remove the height of the header of the card
positionTop -= document.querySelector('.card-header')?.getBoundingClientRect()?.height ?? 0;
// Remove the margin-bottom of the card
positionTop -= 10;
// Scroll to the block
window.scroll(0, positionTop);
}
function handleRequiredFieldsFormCheckboxOptin(): void {
$(CustomerFormMap.requiredFieldsFormAlertOptin).toggleClass(
'd-none',
!$(CustomerFormMap.requiredFieldsFormCheckboxOptin).is(':checked'),
);
}
});

View File

@@ -0,0 +1,31 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const discountContainer = '.discount-container';
export default {
currencySelect: '#discount_value_reduction_currency',
currencySelectContainer: `${discountContainer} .price-reduction-currency-selector`,
discountContainer,
includeTaxInput: '#discount_value_reduction_include_tax',
reductionTypeSelect: '#discount_value_reduction_type',
reductionValueSymbol: `${discountContainer} .price-reduction-value .input-group .input-group-append .input-group-text,
${discountContainer} .price-reduction-value .input-group .input-group-prepend .input-group-text`,
freeGiftProductSearchContainer: '#discount_free_gift',
discountTypeRadios: '#discount_type_selector_discount_type_selector input[type="radio"]',
discountTypeSubmit: '#discountTypeSubmit',
specificProductsSearchContainer: '#discount_conditions_product_specific_products',
specificProductItem: '.specific-product-item',
specificProductId: '.specific-product-id',
specificProductType: '.specific-product-type',
specificCombinationId: '.specific-combination-choice',
carriersSelect: '#discount_conditions_delivery_carriers',
countriesSelect: '#discount_conditions_delivery_country',
categoryTree: '#discount_conditions_product_product_segment_category',
customerSearchContainer: '#discount_customer_eligibility_eligibility_single_customer',
productSegmentAttributes: '#discount_conditions_product_product_segment_attributes',
productSegmentFeatures: '#discount_conditions_product_product_segment_features',
quantityPerCustomerInput: '#discount_usability_quantity_per_customer',
customerEligibilityInput: '#discount_customer_eligibility_eligibility',
};

View File

@@ -0,0 +1,27 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import EntitySearchInput from '@components/entity-search-input';
import DiscountMap from '@pages/discount/discount-map';
const {$} = window;
export default class CreateFreeGiftDiscount {
$freeGiftSearchInput: JQuery;
entitySearchInput!: EntitySearchInput;
constructor() {
this.$freeGiftSearchInput = $(DiscountMap.freeGiftProductSearchContainer);
if (this.$freeGiftSearchInput.length) {
const autocompleteUrl = (document.querySelector(DiscountMap.freeGiftProductSearchContainer) as HTMLElement)
?.dataset.remoteUrl;
this.entitySearchInput = new EntitySearchInput(
this.$freeGiftSearchInput,
{remoteUrl: autocompleteUrl},
);
}
}
}

View File

@@ -0,0 +1,113 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import PriceReductionManager from '@components/form/price-reduction-manager';
import DiscountMap from '@pages/discount/discount-map';
import CreateFreeGiftDiscount from '@pages/discount/form/create-free-gift-discount';
import SpecificProducts from '@pages/discount/form/specific-products';
import initGroupedItemCollection from '@PSVue/components/grouped-item-collection';
import {getAllAttributeGroups, getAllFeatureGroups} from '@pages/discount/form/services';
import CustomerSearchInput from '@components/form/customer-search-input';
$(() => {
window.prestashop.component.initComponents(
[
'TranslatableInput',
'ToggleChildrenChoice',
'GeneratableInput',
'ChoiceTree',
'ChoiceTable',
'EventEmitter',
'DisablingSwitch',
],
);
new CreateFreeGiftDiscount();
new SpecificProducts();
// Initialize customer search for single customer eligibility
if ($(DiscountMap.customerSearchContainer).length > 0) {
new CustomerSearchInput(
DiscountMap.customerSearchContainer,
'.js-customer-item',
() => null,
);
}
const reductionTypeSelect = document.querySelector(DiscountMap.reductionTypeSelect);
if (reductionTypeSelect) {
reductionTypeSelect.addEventListener('change', toggleCurrency);
new PriceReductionManager(
DiscountMap.reductionTypeSelect,
DiscountMap.includeTaxInput,
DiscountMap.currencySelect,
DiscountMap.reductionValueSymbol,
DiscountMap.currencySelectContainer,
);
toggleCurrency();
}
const {eventEmitter} = window.prestashop.instance;
eventEmitter.on('ToggleChildrenChoice:toggled', (radio: HTMLInputElement) => {
// We need to trigger change those select2 elements because the component is not loaded when the page is displayed
// if we don't trigger change them, the placeholder cannot be loaded correctly.
if (radio.value === 'country') {
$(DiscountMap.countriesSelect).trigger('change');
}
if (radio.value === 'carriers') {
$(DiscountMap.carriersSelect).trigger('change');
}
if (radio.value === 'single_customer') {
$(DiscountMap.quantityPerCustomerInput).parents('.form-group').hide();
}
if (radio.value === 'customer_groups' || radio.value === 'all_customers') {
$(DiscountMap.quantityPerCustomerInput).parents('.form-group').show();
}
});
if ($(DiscountMap.customerEligibilityInput).find('input[type="radio"]:checked').attr('value') === 'single_customer') {
$(DiscountMap.quantityPerCustomerInput).parents('.form-group').hide();
} else {
$(DiscountMap.quantityPerCustomerInput).parents('.form-group').show();
}
$(DiscountMap.countriesSelect).select2({
templateResult: formatOption,
templateSelection: formatOption,
theme: 'bootstrap4',
});
$(DiscountMap.carriersSelect).select2({
templateResult: formatOption,
templateSelection: formatOption,
theme: 'bootstrap4',
});
function formatOption(option: any) {
if (!option.element || !option.element.dataset.logo) {
return option.text;
}
const imageUrl = option.element.dataset.logo;
return $(
`<span><img src="${imageUrl}"/> ${option.text} </span>`,
);
}
function toggleCurrency(): void {
if ($(DiscountMap.reductionTypeSelect).val() === 'percentage') {
$(DiscountMap.currencySelect).fadeOut();
} else {
$(DiscountMap.currencySelect).fadeIn();
}
}
new window.prestashop.component.ChoiceTree(DiscountMap.categoryTree);
initGroupedItemCollection(DiscountMap.productSegmentAttributes, getAllAttributeGroups);
initGroupedItemCollection(DiscountMap.productSegmentFeatures, getAllFeatureGroups);
});

View File

@@ -0,0 +1,106 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import Router from '@components/router';
import {Item, ItemGroup} from '@PSVue/components/grouped-item-collection/types';
const router = new Router();
const {$} = window;
interface AttributeGroup {
id: number;
name: string;
attributes: Attribute[];
}
interface Attribute {
id: number;
name: string;
color: string | null;
texture: string | null;
}
interface FeatureGroup {
id: number;
name: string;
feature_values: FeatureValue[];
}
interface FeatureValue {
id: number;
name: string;
}
/**
* Fetch all attribute groups and convert them into ItemGroup/Item expected by the
* GroupedItemCollection component.
*/
export const getAllAttributeGroups = async (): Promise<Array<ItemGroup>> => {
const attributeGroups: AttributeGroup[] = await $.get(router.generate('admin_all_attribute_groups'));
const itemGroups: ItemGroup[] = [];
// Transform attribute groups into ItemGroup
attributeGroups.forEach((attributeGroup: AttributeGroup) => {
const itemGroup: ItemGroup = {
id: attributeGroup.id,
name: attributeGroup.name,
items: [],
};
attributeGroup.attributes.forEach((attribute: Attribute) => {
const item: Item = {
id: attribute.id,
name: attribute.name,
selected: false,
groupId: itemGroup.id,
groupName: itemGroup.name,
color: attribute.color,
texture: attribute.texture,
};
itemGroup.items.push(item);
});
itemGroups.push(itemGroup);
});
return itemGroups;
};
/**
* Fetch all feature groups and convert them into ItemGroup/Item expected by the
* GroupedItemCollection component.
*/
export const getAllFeatureGroups = async (): Promise<Array<ItemGroup>> => {
const featureGroups: FeatureGroup[] = await $.get(router.generate('admin_all_feature_groups'));
const itemGroups: ItemGroup[] = [];
// Transform feature groups into ItemGroup
featureGroups.forEach((featureGroup: FeatureGroup) => {
const itemGroup: ItemGroup = {
id: featureGroup.id,
name: featureGroup.name,
items: [],
};
featureGroup.feature_values.forEach((featureValue: FeatureValue) => {
const item: Item = {
id: featureValue.id,
name: featureValue.name,
selected: false,
groupId: itemGroup.id,
groupName: itemGroup.name,
color: null,
texture: null,
};
itemGroup.items.push(item);
});
itemGroups.push(itemGroup);
});
return itemGroups;
};
export default {
getAllAttributeGroups,
getAllFeatureGroups,
};

View File

@@ -0,0 +1,112 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import EntitySearchInput from '@components/entity-search-input';
import DiscountMap from '@pages/discount/discount-map';
import Router from '@components/router';
interface CombinationChoice {
combinationId: number,
combinationName: string,
}
interface ProductCombinationsResult {
combinations: CombinationChoice[]
}
interface jQuerySelect2Choice {
id: number,
text: string,
}
interface jQuerySelect2Results {
results: jQuerySelect2Choice[]
}
const {$} = window;
export default class SpecificProducts {
private $specificProductsSearchInput: JQuery;
private entitySearchInput!: EntitySearchInput;
private router: Router;
constructor() {
this.router = new Router();
this.$specificProductsSearchInput = $(DiscountMap.specificProductsSearchContainer);
this.init();
}
init(): void {
if (this.$specificProductsSearchInput.length) {
const autocompleteUrl = (document.querySelector(DiscountMap.specificProductsSearchContainer) as HTMLElement)
?.dataset.remoteUrl;
this.entitySearchInput = new EntitySearchInput(
this.$specificProductsSearchInput,
{
remoteUrl: autocompleteUrl,
onSelectedContent: ($node: JQuery, item: any) => {
const $combinationIdSelect = $(DiscountMap.specificCombinationId, $node);
if (item.product_type === 'combinations') {
this.initCombinationSelector($combinationIdSelect, item.id);
}
},
filterSelected: false,
},
);
}
// Init specific products already present
$(DiscountMap.specificProductItem).each((index: number, element: Element) => {
const productId = Number(element.querySelector<HTMLInputElement>(DiscountMap.specificProductId)?.value);
const productType = String(element.querySelector<HTMLInputElement>(DiscountMap.specificProductType)?.value);
const $combinationIdSelect = $(DiscountMap.specificCombinationId, element);
if (productType === 'combinations') {
this.initCombinationSelector($combinationIdSelect, productId);
}
});
}
initCombinationSelector($combinationIdSelect: JQuery, productId: number): void {
const limit = $combinationIdSelect.data('minimum-results-for-search');
$combinationIdSelect.removeClass('d-none');
$combinationIdSelect.select2({
minimumResultsForSearch: limit,
ajax: {
url: () => this.router.generate('admin_products_search_product_combinations', {productId, limit}),
dataType: 'json',
type: 'GET',
delay: 250,
data(params: Record<string, string>): Record<string, string> {
return {
q: params.term,
};
},
processResults(data: ProductCombinationsResult): jQuerySelect2Results {
// prepend the "all combinations" choice to the top of the list
const allCombinationsChoice: CombinationChoice = {
combinationId: Number($combinationIdSelect.data('allCombinationsValue')),
combinationName: String($combinationIdSelect.data('allCombinationsLabel')),
};
const results = <jQuerySelect2Choice[]> [{
id: allCombinationsChoice.combinationId,
text: allCombinationsChoice.combinationName,
}];
results.push(...data.combinations.map((combination: CombinationChoice) => ({
id: combination.combinationId,
text: combination.combinationName,
})));
return {results};
},
},
});
}
}

View File

@@ -0,0 +1,35 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import DiscountMap from '@pages/discount/discount-map';
$(() => {
const discountGrid = new window.prestashop.component.Grid('discount');
discountGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
new window.prestashop.component.GridExtensions.FilterLinkGroup();
// Check the type selected and update the submit button state
const discountTypeRadios = document.querySelectorAll<HTMLInputElement>(DiscountMap.discountTypeRadios);
discountTypeRadios.forEach((discountTypeRadio: HTMLInputElement) => {
discountTypeRadio.addEventListener('change', () => {
const discountTypeSubmit = document.querySelector<HTMLInputElement>(DiscountMap.discountTypeSubmit);
if (discountTypeSubmit) {
discountTypeSubmit.disabled = discountTypeRadio.value === '';
discountTypeSubmit.classList.toggle('disabled', discountTypeRadio.value === '');
}
});
});
});

View File

@@ -0,0 +1,9 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
dkimEnableRadio: '.js-dkim-enable',
dkimConfigurationBlock: '.js-dkim-configuration',
};

View File

@@ -0,0 +1,22 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import EmailPageMap from '@pages/email/EmailPageMap';
const {$} = window;
/**
* Class DkimConfigurationToggler is responsible for showing/hiding DKIM configuration form
*/
class DkimConfigurationToggler {
constructor() {
$(EmailPageMap.dkimEnableRadio).on('change', (event) => {
const dkimEnable = Number($(event.currentTarget).val());
$(EmailPageMap.dkimConfigurationBlock).toggleClass('d-none', dkimEnable === 0);
});
}
}
export default DkimConfigurationToggler;

View File

@@ -0,0 +1,183 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const {$} = window;
/**
* Class is responsible for managing test email sending
*/
class EmailSendingTest {
$successAlert: JQuery;
$errorAlert: JQuery;
$loader: JQuery;
$sendEmailBtn: JQuery;
constructor() {
this.$successAlert = $('.js-test-email-success');
this.$errorAlert = $('.js-test-email-errors');
this.$loader = $('.js-test-email-loader');
this.$sendEmailBtn = $('.js-send-test-email-btn');
this.$sendEmailBtn.on('click', (event: JQueryEventObject) => {
this.handle(event);
});
}
/**
* Handle test email sending
*
* @param {Event} event
*
* @private
*/
private handle(event: JQueryEventObject): void {
// fill test email sending form with configured values
$('#test_email_sending_mail_method').val(
<string>$('input[name="form[mail_method]"]:checked').val(),
);
$('#test_email_sending_smtp_server').val(
<string>$('#form_smtp_config_server').val(),
);
$('#test_email_sending_smtp_username').val(
<string>$('#form_smtp_config_username').val(),
);
$('#test_email_sending_smtp_password').val(
<string>$('#form_smtp_config_password').val(),
);
$('#test_email_sending_smtp_port').val(
<string>$('#form_smtp_config_port').val(),
);
$('#test_email_sending_smtp_encryption').val(
<string>$('#form_smtp_config_encryption').val(),
);
$('#test_email_sending_dkim_enable').val(
<string>$('input[name="form[dkim_enable]"]:checked').val(),
);
$('#test_email_sending_dkim_key').val(
<string>$('#form_dkim_config_key').val(),
);
$('#test_email_sending_dkim_selector').val(
<string>$('#form_dkim_config_selector').val(),
);
$('#test_email_sending_dkim_domain').val(
<string>$('#form_dkim_config_domain').val(),
);
const $testEmailSendingForm = $(event.currentTarget).closest('form');
this.resetMessages();
this.hideSendEmailButton();
this.showLoader();
$.post({
url: <string>$testEmailSendingForm.attr('action'),
data: $testEmailSendingForm.serialize(),
}).then((response) => {
this.hideLoader();
this.showSendEmailButton();
if (response.errors.length !== 0) {
this.showErrors(response.errors);
return;
}
this.showSuccess();
});
}
/**
* Make sure that additional content (alerts, loader) is not visible
*
* @private
*/
private resetMessages(): void {
this.hideSuccess();
this.hideErrors();
}
/**
* Show success message
*
* @private
*/
private showSuccess(): void {
this.$successAlert.removeClass('d-none');
}
/**
* Hide success message
*
* @private
*/
private hideSuccess(): void {
this.$successAlert.addClass('d-none');
}
/**
* Show loader during AJAX call
*
* @private
*/
private showLoader(): void {
this.$loader.removeClass('d-none');
}
/**
* Hide loader
*
* @private
*/
private hideLoader(): void {
this.$loader.addClass('d-none');
}
/**
* Show errors
*
* @param {Array} errors
*
* @private
*/
private showErrors(errors: Array<string>): void {
errors.forEach((error) => {
this.$errorAlert.append(`<p>${error}</p>`);
});
this.$errorAlert.removeClass('d-none');
}
/**
* Hide errors
*
* @private
*/
private hideErrors(): void {
this.$errorAlert.addClass('d-none').empty();
}
/**
* Show send email button
*
* @private
*/
private showSendEmailButton(): void {
this.$sendEmailBtn.removeClass('d-none');
}
/**
* Hide send email button
*
* @private
*/
private hideSendEmailButton(): void {
this.$sendEmailBtn.addClass('d-none');
}
}
export default EmailSendingTest;

View File

@@ -0,0 +1,29 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import EmailSendingTest from '@pages/email/email-sending-test';
import SmtpConfigurationToggler from '@pages/email/smtp-configuration-toggler';
import DkimConfigurationToggler from '@pages/email/dkim-configuration-toggler';
const {$} = window;
$(() => {
const emailLogsGrid = new window.prestashop.component.Grid('email_logs');
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitGridActionExtension());
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
emailLogsGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
new EmailSendingTest();
new SmtpConfigurationToggler();
new DkimConfigurationToggler();
});

View File

@@ -0,0 +1,35 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const {$} = window;
/**
* Class SmtpConfigurationToggler is responsible for showing/hiding SMTP configuration form
*/
class SmtpConfigurationToggler {
constructor() {
$('.js-email-method').on('change', 'input[type="radio"]', (event) => {
const mailMethod = Number($(event.currentTarget).val());
$('.js-smtp-configuration').toggleClass(
'd-none',
this.getSmtpMailMethodOption() !== mailMethod,
);
});
}
/**
* Get SMTP mail option value
*
* @private
*
* @returns {Number}
*/
private getSmtpMailMethodOption(): number {
return $('.js-email-method').data('smtp-mail-method');
}
}
export default SmtpConfigurationToggler;

View File

@@ -0,0 +1,169 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ChoiceTree from '../../components/form/choice-tree';
import AddonsConnector from '../../components/addons-connector';
import ChangePasswordControl from '../../components/form/change-password-control';
import employeeFormMap from './employee-form-map';
import ChangePasswordHandler from '../../components/change-password-handler';
/**
* Class responsible for javascript actions in employee add/edit page.
*/
export default class EmployeeForm {
shopChoiceTreeSelector: string;
shopChoiceTree: ChoiceTree;
employeeProfileSelector: string;
tabsDropdownSelector: string;
constructor() {
this.shopChoiceTreeSelector = employeeFormMap.shopChoiceTree;
this.shopChoiceTree = new window.prestashop.component.ChoiceTree(this.shopChoiceTreeSelector);
this.employeeProfileSelector = employeeFormMap.profileSelect;
this.tabsDropdownSelector = employeeFormMap.defaultPageSelect;
this.shopChoiceTree.enableAutoCheckChildren();
new AddonsConnector(
employeeFormMap.addonsConnectForm,
employeeFormMap.addonsLoginButton,
);
new ChangePasswordControl(
employeeFormMap.changePasswordInputsBlock,
employeeFormMap.showChangePasswordBlockButton,
employeeFormMap.hideChangePasswordBlockButton,
employeeFormMap.oldPasswordInput,
employeeFormMap.newPasswordInput,
employeeFormMap.confirmNewPasswordInput,
employeeFormMap.generatedPasswordDisplayInput,
employeeFormMap.passwordStrengthFeedbackContainer,
employeeFormMap.generatedPasswordButton,
);
const passwordHandler = new ChangePasswordHandler(
employeeFormMap.passwordStrengthFeedbackContainer,
);
passwordHandler.watchPasswordStrength($(employeeFormMap.passwordInput));
this.initEvents();
this.toggleShopTree();
}
/**
* Initialize page's events.
*
* @private
*/
private initEvents(): void {
const $employeeProfilesDropdown = $(this.employeeProfileSelector);
const getTabsUrl = $employeeProfilesDropdown.data('get-tabs-url');
$(document).on('change', this.employeeProfileSelector, () => this.toggleShopTree(),
);
// Reload tabs dropdown when employee profile is changed.
$(document).on('change', this.employeeProfileSelector, (event) => {
const $tabsDropdown = $(this.tabsDropdownSelector);
$tabsDropdown.empty();
$tabsDropdown.prop('disabled', true);
$.get(
getTabsUrl,
{
profileId: $(event.currentTarget).val(),
},
(tabs) => {
this.reloadTabsDropdown(tabs);
},
'json',
);
});
}
/**
* Reload tabs dropdown with new content.
*
* @param {Object} accessibleTabs
*
* @private
*/
private reloadTabsDropdown(accessibleTabs: HTMLElement): void {
const $tabsDropdown = $(this.tabsDropdownSelector);
$tabsDropdown.empty();
Object.values(accessibleTabs).forEach((accessibleTab) => {
if (accessibleTab.children.length > 0 && accessibleTab.name) {
// If tab has children - create an option group and put children inside.
const $optgroup = this.createOptionGroup(accessibleTab.name);
Object.keys(accessibleTab.children).forEach((childKey) => {
if (accessibleTab.children[childKey].name) {
$optgroup.append(
this.createOption(
accessibleTab.children[childKey].name,
accessibleTab.children[childKey].id_tab,
),
);
}
});
$tabsDropdown.append($optgroup);
} else if (accessibleTab.name) {
// If tab doesn't have children - create an option.
$tabsDropdown.append(
this.createOption(accessibleTab.name, accessibleTab.id_tab),
);
}
});
$tabsDropdown.prop('disabled', false);
}
/**
* Hide shop choice tree if superadmin profile is selected, show it otherwise.
*
* @private
*/
private toggleShopTree(): void {
const $employeeProfileDropdown = $(this.employeeProfileSelector);
const superAdminProfileId = $employeeProfileDropdown.data('admin-profile');
$(this.shopChoiceTreeSelector)
.closest('.form-group')
.toggleClass(
'd-none',
$employeeProfileDropdown.val() === superAdminProfileId,
);
}
/**
* Creates an <optgroup> element
*
* @param {String} name
*
* @returns {jQuery}
*
* @private
*/
private createOptionGroup(name: string): JQuery {
return $(`<optgroup label="${name}">`);
}
/**
* Creates an <option> element.
*
* @param {String} name
* @param {String} value
*
* @returns {jQuery}
*
* @private
*/
private createOption(name: string, value: string): JQuery {
return $(`<option value="${value}">${name}</option>`);
}
}

View File

@@ -0,0 +1,27 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines all selectors that are used in employee add/edit form.
*/
export default {
shopChoiceTree: '#employee_shop_association',
profileSelect: '#employee_profile',
passwordInput: '#employee_password',
defaultPageSelect: '#employee_default_page',
addonsConnectForm: '#addons-connect-form',
addonsLoginButton: '#addons_login_btn',
// selectors related to "change password" form control
changePasswordInputsBlock: '.js-change-password-block',
showChangePasswordBlockButton: '.js-change-password',
hideChangePasswordBlockButton: '.js-change-password-cancel',
oldPasswordInput: '#employee_change_password_old_password',
newPasswordInput: '#employee_change_password_new_password_first',
confirmNewPasswordInput: '#employee_change_password_new_password_second',
generatedPasswordDisplayInput: '#employee_change_password_generated_password',
generatedPasswordButton: '#employee_change_password_generate_password_button',
passwordStrengthFeedbackContainer: '.password-strength-feedback',
};

View File

@@ -0,0 +1,10 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import EmployeeForm from './EmployeeForm';
$(() => {
new EmployeeForm();
});

View File

@@ -0,0 +1,27 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ShowcaseCard from '@components/showcase-card/showcase-card';
import ShowcaseCardCloseExtension from '@components/showcase-card/extension/showcase-card-close-extension';
const {$} = window;
$(() => {
const employeeGrid = new window.prestashop.component.Grid('employee');
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
employeeGrid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
const showcaseCard = new ShowcaseCard('employeesShowcaseCard');
showcaseCard.addExtension(new ShowcaseCardCloseExtension());
});

View File

@@ -0,0 +1,15 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
// jQuery is not available in this page
// so we use plain JS to to listen on button click
// which then goes back in browser history
(() => {
const backBtn = document.querySelector('.js-go-back-btn');
if (backBtn) {
backBtn.addEventListener('click', () => window.history.back());
}
})();

View File

@@ -0,0 +1,13 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
betaSubmitButton: '#feature_flag_beta_submit',
betaForm: 'form[name="feature_flag_beta"]',
betaFormInputFields: 'form[name="feature_flag_beta"] input',
stableForm: 'form[name="feature_flag_stable"]',
stableFormInputs: 'form[name="feature_flag_stable"] input',
stableSubmitButton: '#feature_flag_stable_submit',
};

View File

@@ -0,0 +1,65 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ConfirmModal from '@components/modal';
import FeatureFlagMap from '@pages/feature-flag/components-map';
const {$} = window;
$(() => {
const $submitButton = $(FeatureFlagMap.betaSubmitButton);
const $stableFormSubmitButton = $(FeatureFlagMap.stableSubmitButton);
const $form = $(FeatureFlagMap.betaForm);
const $betaFormInputs = $(FeatureFlagMap.betaFormInputFields);
const $stableForm = $(FeatureFlagMap.stableForm);
const $stableFormInputs = $(FeatureFlagMap.stableFormInputs);
const $stableFormInitialState = $stableForm.serialize();
const initialState = $form.serialize();
const initialFormData = $form.serializeArray();
$betaFormInputs.on('change', () => {
$submitButton.prop('disabled', initialState === $form.serialize());
});
$stableFormInputs.on('change', () => {
$stableFormSubmitButton.prop('disabled', $stableFormInitialState === $stableForm.serialize());
});
$submitButton.on('click', (event) => {
event.preventDefault();
const formData = $form.serializeArray();
if (initialState === $form.serialize()) {
return;
}
let oneFlagIsEnabled = false;
for (let i = 0; i < formData.length; i += 1) {
if ((formData[i].name !== 'form[_token]') && (formData[i].value !== '0') && (initialFormData[i].value === '0')) {
oneFlagIsEnabled = true;
break;
}
}
if (oneFlagIsEnabled) {
new ConfirmModal(
{
id: 'modal-confirm-submit-feature-flag',
confirmTitle: $submitButton.data('modal-title'),
confirmMessage: $submitButton.data('modal-message'),
confirmButtonLabel: $submitButton.data('modal-apply'),
closeButtonLabel: $submitButton.data('modal-cancel'),
},
() => {
$form.submit();
},
);
} else {
$form.submit();
}
});
});

View File

@@ -0,0 +1,12 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
window.prestashop.component.initComponents(
[
'TranslatableInput',
],
);
});

View File

@@ -0,0 +1,19 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
const grid = new window.prestashop.component.Grid('feature_value');
grid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.PositionExtension(grid));
});

View File

@@ -0,0 +1,13 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
window.prestashop.component.initComponents(
[
'TranslatableInput',
],
);
new window.prestashop.component.ChoiceTree('#feature_shop_association').enableAutoCheckChildren();
});

View File

@@ -0,0 +1,27 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ShowcaseCardCloseExtension from '@components/showcase-card/extension/showcase-card-close-extension';
import ShowcaseCard from '@components/showcase-card/showcase-card';
const {$} = window;
$(() => {
const grid = new window.prestashop.component.Grid('feature');
grid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.PositionExtension(grid));
const showcaseCard = new ShowcaseCard('featuresShowcaseCard');
showcaseCard.addExtension(new ShowcaseCardCloseExtension());
});

View File

@@ -0,0 +1,12 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
$(() => {
window.prestashop.component.initComponents(
[
'ChoiceTable',
],
);
});

View File

@@ -0,0 +1,83 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import DeleteImageTypeRowActionExtension
from '@components/grid/extension/action/row/image_type/delete-image-type-row-action-extension';
import ConfirmModal from '@components/modal/confirm-modal';
const {$} = window;
$(() => {
// Init image type grid
const grid = new window.prestashop.component.Grid('image_type');
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ChoiceExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
grid.addExtension(new DeleteImageTypeRowActionExtension());
// Regenerate thumbnails system
const $regenerateThumbnailsForm = $('form[name=regenerate_thumbnails]');
const $regenerateThumbnailsButton = $('#regenerate-thumbnails-button');
const $selectImage = $('#regenerate_thumbnails_image');
const $selectImageType = $('#regenerate_thumbnails_image-type');
const $parentImageFormat = $selectImageType.parents('.form-group');
const formatsByTypes = $selectImage.data('formats');
// First hide the image format select
$parentImageFormat.hide();
// On image type change, show the image format by the type selected
$selectImage.on('change', () => {
const selectedImage: string = ($selectImage.val() ?? 'all').toString();
// Reset format selector
$selectImageType.val(0);
$selectImageType.children('option').hide();
// If all is selected, hide the format selector
if (selectedImage === 'all') {
$parentImageFormat.hide();
} else {
// Else show the format selector...
$parentImageFormat.show();
// and the formats by the type selected
formatsByTypes[selectedImage].forEach((formatId: number) => {
$selectImageType.children(`option[value="${formatId}"]`).show();
});
// Don't forget to show the "all" option
$selectImageType.children('option[value="0"]').show();
}
});
// On submit regenerate thumbnails form, show a confirmation modal.
$regenerateThumbnailsButton.on('click', (event) => {
event.preventDefault();
// Display confirmation modal
const modal = new (ConfirmModal as any)(
{
id: 'regeneration-confirm-modal',
confirmTitle: $regenerateThumbnailsButton.data('confirm-title'),
confirmMessage: $regenerateThumbnailsButton.data('confirm-message'),
closeButtonLabel: $regenerateThumbnailsButton.data('confirm-cancel'),
confirmButtonLabel: $regenerateThumbnailsButton.data('confirm-apply'),
closable: true,
},
() => {
// If ok, submit the form
$regenerateThumbnailsForm.submit();
},
);
modal.show();
});
});

View File

@@ -0,0 +1,76 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const {$} = window;
export default class EntityFieldsValidator {
/**
* Validates entity fields
*
* @returns {boolean}
*/
validate(): boolean {
$('.js-validation-error').addClass('d-none');
return this.checkDuplicateSelectedValues() && this.checkRequiredFields();
}
/**
* Checks if there are no duplicate selected values.
*
* @returns {boolean}
* @private
*/
checkDuplicateSelectedValues(): boolean {
const uniqueFields: Array<string | number | string[] | undefined> = [];
let valid = true;
$('.js-entity-field select').each(function () {
const value = $(this).val();
if (value === 'no') {
return;
}
if ($.inArray(value, uniqueFields) !== -1) {
valid = false;
$('.js-duplicate-columns-warning').removeClass('d-none');
return;
}
uniqueFields.push(value);
});
return valid;
}
/**
* Checks if all required fields are selected.
*
* @returns {boolean}
* @private
*/
private checkRequiredFields(): boolean {
const requiredImportFields = $('.js-import-data-table').data(
'required-fields',
);
/* eslint-disable-next-line */
for (const key in requiredImportFields) {
if (
$(`option[value="${requiredImportFields[key]}"]:selected`).length === 0
) {
$('.js-missing-column-warning').removeClass('d-none');
$('.js-missing-column').text(
$(`option[value="${requiredImportFields[key]}"]:first`).text(),
);
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,102 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Class ImportBatchSizeCalculator calculates the import batch size.
* Import batch size is the maximum number of records that
* the import should handle in one batch.
*/
export default class ImportBatchSizeCalculator {
targetExecutionTime: number;
maxAcceleration: number;
minBatchSize: number;
maxBatchSize: number;
importStartTime: number;
actualExecutionTime: number;
constructor() {
// Target execution time in milliseconds.
this.targetExecutionTime = 5000;
// Maximum batch size increase multiplier.
this.maxAcceleration = 4;
// Minimum and maximum import batch sizes.
this.minBatchSize = 5;
this.maxBatchSize = 100;
this.importStartTime = 0;
this.actualExecutionTime = 0;
}
/**
* Marks the start of the import operation.
* Must be executed before starting the import,
* to be able to calculate the import batch size later on.
*/
markImportStart(): void {
this.importStartTime = new Date().getTime();
}
/**
* Marks the end of the import operation.
* Must be executed after the import operation finishes,
* to be able to calculate the import batch size later on.
*/
markImportEnd(): void {
this.actualExecutionTime = new Date().getTime() - this.importStartTime;
}
/**
* Calculates how much the import execution time can be increased to still be acceptable.
*
* @returns {number}
* @private
*/
private calculateAcceleration(): number {
return Math.min(
this.maxAcceleration,
this.targetExecutionTime / this.actualExecutionTime,
);
}
/**
* Calculates the recommended import batch size.
*
* @param {number} currentBatchSize current import batch size
* @param {number} maxBatchSize greater than zero, the batch size that shouldn't be exceeded
*
* @returns {number} recommended import batch size
*/
calculateBatchSize(currentBatchSize: number, maxBatchSize = 0): number {
if (!this.importStartTime) {
throw new Error('Import start is not marked.');
}
if (!this.actualExecutionTime) {
throw new Error('Import end is not marked.');
}
const candidates = [
this.maxBatchSize,
Math.max(
this.minBatchSize,
Math.floor(currentBatchSize * this.calculateAcceleration()),
),
];
if (maxBatchSize > 0) {
candidates.push(maxBatchSize);
}
return Math.min(...candidates);
}
}

View File

@@ -0,0 +1,58 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import ImportMatchConfiguration from './ImportMatchConfiguration';
import ImportDataTable from './ImportDataTable';
import EntityFieldsValidator from './EntityFieldsValidator';
import Importer from './Importer';
export default class ImportDataPage {
importer: Importer;
constructor() {
new ImportMatchConfiguration();
new ImportDataTable();
this.importer = new Importer();
$(document).on('click', '.js-process-import', (e: JQueryEventObject) => this.importHandler(e),
);
$(document).on('click', '.js-abort-import', () => this.importer.requestCancelImport(),
);
$(document).on('click', '.js-close-modal', () => this.importer.progressModal.hide(),
);
$(document).on('click', '.js-continue-import', () => this.importer.continueImport(),
);
}
/**
* Import process event handler
*/
importHandler(e: JQueryEventObject): void {
e.preventDefault();
const fieldsValidator = new EntityFieldsValidator();
if (!fieldsValidator.validate()) {
return;
}
const configuration: Record<string, any> = {};
// Collect the configuration from the form into an array.
$('.import-data-configuration-form')
.find(
'#skip, select[name^=type_value], #csv, #iso_lang, #entity,'
+ '#truncate, #match_ref, #regenerate, #forceIDs, #sendemail,'
+ '#separator, #multiple_value_separator',
)
.each((index, $input) => {
configuration[<string>$($input).attr('name')] = $($input).val();
});
this.importer.import(
$('.js-import-process-button').data('import_url'),
configuration,
);
}
}

Some files were not shown because too many files have changed in this diff Show More