From 1da683440d1c5252f58d2f1679fbeb428f6b9aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= <p.mueller@pcsg.de> Date: Wed, 3 Jul 2024 16:24:39 +0200 Subject: [PATCH] refactor(SimpleCheckoutWindow): style and UX --- bin/frontend/controls/SimpleCheckout.js | 160 +++++++++++------- .../controls/SimpleCheckoutWindow.css | 19 +++ bin/frontend/controls/SimpleCheckoutWindow.js | 97 +++++++++-- locale.xml | 11 ++ .../ERP/Order/SimpleCheckout/Checkout.html | 4 + src/QUI/ERP/Order/SimpleCheckout/Checkout.php | 6 +- 6 files changed, 221 insertions(+), 76 deletions(-) create mode 100644 bin/frontend/controls/SimpleCheckoutWindow.css diff --git a/bin/frontend/controls/SimpleCheckout.js b/bin/frontend/controls/SimpleCheckout.js index 18cd2d4..25202ec 100644 --- a/bin/frontend/controls/SimpleCheckout.js +++ b/bin/frontend/controls/SimpleCheckout.js @@ -1,3 +1,9 @@ +/** + * @event onOrderValid [this] - Fires as soon as all order requirements are met + * @event onOrderInvalid [this] - Fires if the order requirements are not met + * @event onOrderStart [this] - Fires as soon as the order starts to execute + * @event onOrderSuccessfull [this] - Fires if the order was successfully executed + */ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleCheckout', [ 'qui/QUI', @@ -6,13 +12,13 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko 'qui/utils/Form', 'Ajax' -], function(QUI, QUIControl, QUILoader, QUIFormUtils, QUIAjax) { +], function (QUI, QUIControl, QUILoader, QUIFormUtils, QUIAjax) { 'use strict'; return new Class({ Extends: QUIControl, - Type: 'package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleCheckout', + Type : 'package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleCheckout', Binds: [ 'update', @@ -21,18 +27,20 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko ], options: { - orderHash: false, - loadHashFromUrl: false + orderHash : false, + loadHashFromUrl : false, + showPayToOrderBtn: true, }, - initialize: function(options) { + initialize: function (options) { this.parent(options); - this.$Delivery = null; - this.$Billing = null; - this.$Shipping = null; - this.$Payment = null; - this.Loader = null; + this.$Delivery = null; + this.$Billing = null; + this.$Shipping = null; + this.$Payment = null; + this.Loader = null; + this.$PayToOrderBtn = null; this.addEvents({ onImport: this.$onImport, @@ -48,7 +56,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko } }, - $onImport: function() { + $onImport: function () { this.Loader = new QUILoader().inject(this.getElm()); this.$setAnchor(); @@ -64,8 +72,8 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko ], (LoginWindow) => { new LoginWindow({ redirect: false, - reload: false, - events: { + reload : false, + events : { onSuccess: () => { window.location.reload(); } @@ -79,8 +87,8 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko if (LoginNode) { require(['package/quiqqer/frontend-users/bin/frontend/controls/login/Login'], (Login) => { new Login({ - redirect: false, - reload: false, + redirect : false, + reload : false, onSuccess: () => { this.getElm().setStyle('minHeight', this.getElm().getSize().y); this.Loader.show(); @@ -119,7 +127,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko return this.$loadGUI(); }).then(() => { // Terms of Service - this.getElm().getElements('a[data-project]').addEvent('click', function(e) { + this.getElm().getElements('a[data-project]').addEvent('click', function (e) { let Target = e.target; if (Target.nodeName !== 'A') { @@ -132,12 +140,12 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko e.stop(); - require(['package/quiqqer/controls/bin/site/Window'], function(Win) { + require(['package/quiqqer/controls/bin/site/Window'], function (Win) { new Win({ showTitle: true, - project: Target.get('data-project'), - lang: Target.get('data-lang'), - id: Target.get('data-id') + project : Target.get('data-project'), + lang : Target.get('data-lang'), + id : Target.get('data-id') }).open(); }); }); @@ -154,7 +162,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }); }, - $onInject: function() { + $onInject: function () { this.$loadProducts().then(() => { return this.$loadCheckout(); }).catch((err) => { @@ -180,13 +188,13 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }); }, - $loadOrder: function() { + $loadOrder: function () { if (this.getAttribute('orderHash')) { return new Promise((resolve, reject) => { QUIAjax.post('package_quiqqer_order-simple-checkout_ajax_frontend_getOrder', resolve, { 'package': 'quiqqer/order-simple-checkout', orderHash: this.getAttribute('orderHash'), - onError: reject + onError : reject }); }); } @@ -194,7 +202,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko return Promise.resolve(); }, - $loadGUI: function() { + $loadGUI: function () { const hideLoader = () => { this.Loader.hide(); }; @@ -221,8 +229,8 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }).then((instances) => { this.$Delivery = instances[0]; this.$Shipping = instances[1]; - this.$Payment = instances[2]; - this.$Billing = instances[3]; + this.$Payment = instances[2]; + this.$Billing = instances[3]; if (!this.$Delivery && !this.$Shipping && !this.$Payment && !this.$Billing) { this.Loader.hide(); @@ -230,9 +238,13 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko } this.$Delivery.setAttribute('Checkout', this); - this.$Shipping.setAttribute('Checkout', this); + if (this.$Shipping) { + this.$Shipping.setAttribute('Checkout', this); + } this.$Payment.setAttribute('Checkout', this); - this.$Billing.setAttribute('Checkout', this); + if (this.$Billing) { + this.$Billing.setAttribute('Checkout', this); + } this.$Payment.addEvent('refreshBegin', showLoader); this.$Payment.addEvent('refreshEnd', hideLoader); @@ -266,15 +278,24 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko this.update().then(hideLoader); }); - this.$Billing.addEvent('change', () => { - this.Loader.show(); - this.update().then(hideLoader); - }); + if (this.$Billing) { + this.$Billing.addEvent('change', () => { + this.Loader.show(); + this.update().then(hideLoader); + }); + } - this.getElm().getElement('[name="pay"]').addEvent('click', (e) => { - e.stop(); - this.orderWithCosts(); - }); + this.$PayToOrderBtn = this.getElm().getElement('[name="pay"]'); + + if (this.getAttribute('showPayToOrderBtn')) { + this.$PayToOrderBtn.addEvent('click', (e) => { + e.stop(); + this.orderWithCosts(); + }); + } else { + this.$PayToOrderBtn.destroy(); + this.$PayToOrderBtn = null; + } this.$setSpacingOnMobile(); @@ -286,7 +307,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }); }, - $loadPayment: function() { + $loadPayment: function () { return new Promise((resolve, reject) => { QUIAjax.post( 'package_quiqqer_order-simple-checkout_ajax_frontend_getPaymentStep', @@ -313,8 +334,8 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko if (typeof Container.scrollIntoView === 'function') { Container.scrollIntoView({ behavior: 'smooth', - block: 'center', - inline: 'start' + block : 'center', + inline : 'start' }); } @@ -328,7 +349,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko { 'package': 'quiqqer/order-simple-checkout', orderHash: this.getAttribute('orderHash'), - onError: reject + onError : reject } ); }); @@ -336,7 +357,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }, - $loadProducts: function() { + $loadProducts: function () { if (this.getAttribute('products') && !this.getAttribute('orderHash')) { return new Promise((resolve, reject) => { QUIAjax.post( @@ -348,8 +369,8 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }, { 'package': 'quiqqer/order-simple-checkout', - products: JSON.encode(this.getAttribute('products')), - onError: reject + products : JSON.encode(this.getAttribute('products')), + onError : reject } ); }); @@ -358,7 +379,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko return Promise.resolve(); }, - $loadCheckout: function() { + $loadCheckout: function () { this.$setAnchor(); return new Promise((resolve) => { @@ -388,13 +409,13 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }); }, - setCurrency: function(currency) { + setCurrency: function (currency) { return new Promise((resolve, reject) => { QUIAjax.post('package_quiqqer_order-simple-checkout_ajax_frontend_setCurrency', resolve, { 'package': 'quiqqer/order-simple-checkout', - currency: currency, + currency : currency, orderHash: this.getAttribute('orderHash'), - onError: reject + onError : reject }); }).then(() => { return this.$refreshBasket(); @@ -408,10 +429,13 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }); }, - orderWithCosts: function() { + /** + * @return {Promise<void>} + */ + orderWithCosts: function () { this.Loader.show(); - this.update().then(() => { + return this.update().then(() => { this.Loader.show(); const Terms = this.getElm().getElement('[name="termsAndConditions"]'); @@ -431,6 +455,8 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko return; } + this.fireEvent('orderStart', [this]); + // execute order QUIAjax.post('package_quiqqer_order-simple-checkout_ajax_frontend_orderWithCosts', (result) => { const Container = this.getElm().getElement('.quiqqer-simple-checkout-container'); @@ -459,10 +485,12 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko if (typeof Container.scrollIntoView === 'function') { Container.scrollIntoView({ behavior: 'smooth', - block: 'center', - inline: 'start' + block : 'center', + inline : 'start' }); } + + this.fireEvent('orderSuccessful', [this]); } }); }); @@ -471,7 +499,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }, { 'package': 'quiqqer/order-simple-checkout', orderHash: this.getAttribute('orderHash'), - onError: (err) => { + onError : (err) => { if (typeof err.getMessage === 'function') { this.$showError(err.getMessage()); this.Loader.hide(); @@ -485,7 +513,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }); }, - $getControl: function(Node) { + $getControl: function (Node) { return new Promise((resolve) => { if (!Node || !Node.get('data-qui')) { return resolve(null); @@ -510,7 +538,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }); }, - $setAnchor: function() { + $setAnchor: function () { if (!this.getAttribute('loadHashFromUrl')) { return; } @@ -522,7 +550,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko window.location.hash = this.getAttribute('orderHash'); }, - $refreshBasket: function() { + $refreshBasket: function () { this.Loader.show(); return new Promise((resolve) => { @@ -540,7 +568,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko }); }, - $showError: function(message) { + $showError: function (message) { // @todo michael -> schönere error message QUI.getMessageHandler().then((MH) => { MH.addError(message); @@ -549,18 +577,24 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko console.error(message); }, - update: function() { - const PayButton = this.getElm().getElement('[name="pay"]'); - + update: function () { return new Promise((resolve) => { const orderData = QUIFormUtils.getFormData(this.getElm().getElement('form')); QUIAjax.post('package_quiqqer_order-simple-checkout_ajax_frontend_update', (isValid) => { - PayButton.disabled = !isValid; + if (isValid) { + this.fireEvent('orderValid', [this]); + } else { + this.fireEvent('orderInvalid', [this]); + } + + if (this.$PayToOrderBtn) { + this.$PayToOrderBtn.disabled = !isValid; + } const Delivery = this.getElm().getElement('.quiqqer-simple-checkout-data-delivery'); const Shipping = this.getElm().getElement('.quiqqer-simple-checkout-data-shipping'); - const Payment = this.getElm().getElement('.quiqqer-simple-checkout-data-payment'); + const Payment = this.getElm().getElement('.quiqqer-simple-checkout-data-payment'); if (!isValid) { QUIAjax.get('package_quiqqer_order-simple-checkout_ajax_frontend_validate', (missing) => { @@ -611,7 +645,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko 'package': 'quiqqer/order-simple-checkout', orderData: JSON.encode(orderData), orderHash: this.getAttribute('orderHash'), - onError: (err) => { + onError : (err) => { if (typeof err.getMessage === 'function') { this.$showError(err.getMessage()); this.Loader.hide(); @@ -631,7 +665,7 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko * Calculate needed margin on mobile. * Not pretty solution, needs to be reworked. */ - $setSpacingOnMobile: function() { + $setSpacingOnMobile: function () { if (QUI.getBodySize().x >= 768) { return; } diff --git a/bin/frontend/controls/SimpleCheckoutWindow.css b/bin/frontend/controls/SimpleCheckoutWindow.css new file mode 100644 index 0000000..8e178c6 --- /dev/null +++ b/bin/frontend/controls/SimpleCheckoutWindow.css @@ -0,0 +1,19 @@ +.SimpleCheckoutWindow .SimpleCheckoutWindow-btn-cancel { + color: grey; + cursor: pointer; + font-size: 12px; +} + +.SimpleCheckoutWindow .SimpleCheckoutWindow-btn-close { + float: right !important; +} + +.SimpleCheckoutWindow .buttons-multiple { + display: flex; + float: none !important; + justify-content: space-between; +} + +.SimpleCheckoutWindow .SimpleCheckoutWindow-btn-payToOrder { + order: 1; +} \ No newline at end of file diff --git a/bin/frontend/controls/SimpleCheckoutWindow.js b/bin/frontend/controls/SimpleCheckoutWindow.js index 4c9af4c..505b8c8 100644 --- a/bin/frontend/controls/SimpleCheckoutWindow.js +++ b/bin/frontend/controls/SimpleCheckoutWindow.js @@ -2,50 +2,116 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko 'qui/QUI', 'qui/controls/windows/Popup', - 'package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleCheckout' + 'qui/controls/buttons/Button', + 'Locale', + 'package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleCheckout', + 'css!package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleCheckoutWindow.css', -], function(QUI, QUIWindow, SimpleCheckout) { +], function (QUI, QUIWindow, QUIButton, QUILocale, SimpleCheckout) { 'use strict'; return new Class({ Extends: QUIWindow, - Type: 'package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleCheckoutWindow', + Type : 'package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleCheckoutWindow', Binds: [ '$onOpen' ], - initialize: function(options) { + options: { + 'class' : 'SimpleCheckoutWindow', + closeButton: false + }, + + initialize: function (options) { this.setAttributes({ maxHeight: 800, - maxWidth: 1200 + maxWidth : 1200 }); this.parent(options); - this.$Checkout = null; + this.$Checkout = null; + this.$PayToOrderBtn = null; + this.$isOrdering = false; this.addEvents({ onOpen: this.$onOpen }); }, - $onOpen: function() { + $onOpen: function () { if (this.$Checkout) { return; } + this.$Buttons.addClass('buttons-multiple'); + + this.$PayToOrderBtn = new QUIButton({ + 'class' : 'SimpleCheckoutWindow-btn-payToOrder', + textimage: 'fas fa-shopping-cart', + disabled : true, + text : QUILocale.get('quiqqer/order', 'ordering.btn.pay.to.order'), + title : QUILocale.get('quiqqer/order', 'ordering.btn.pay.to.order'), + events : { + onClick: () => { + this.$PayToOrderBtn.disable(); + + this.$Checkout.orderWithCosts().catch((e) => { + this.$PayToOrderBtn.enable(); + }); + } + } + }); + + const CancelBtn = new Element('span', { + 'class': 'SimpleCheckoutWindow-btn-cancel', + html : QUILocale.get('quiqqer/order-simple-checkout', 'SimpleCheckoutWindow.btn.cancel'), + events : { + click: () => { + this.close(); + } + } + }).inject(this.$Buttons); + + this.addButton(this.$PayToOrderBtn); + this.Loader.show(); this.getContent().set('html', ''); this.getContent().setStyle('padding', 0); this.$Checkout = new SimpleCheckout({ - products: this.getAttribute('products'), - events: { - onLoaded: () => { + products : this.getAttribute('products'), + showPayToOrderBtn: false, + events : { + onLoaded : () => { this.Loader.hide(); }, - onLoadedError: () => { + onOrderStart : () => { + CancelBtn.disabled = true; + this.$PayToOrderBtn.disable(); + }, + onOrderSuccessful: () => { + this.$Buttons.removeClass('buttons-multiple'); + CancelBtn.destroy(); + this.$PayToOrderBtn.destroy(); + + this.addButton(new QUIButton({ + 'class' : 'SimpleCheckoutWindow-btn-close', + textimage: 'fas fa-check', + text : QUILocale.get('quiqqer/order-simple-checkout', 'SimpleCheckoutWindow.btn.continue'), + title : QUILocale.get('quiqqer/order-simple-checkout', 'SimpleCheckoutWindow.btn.continue'), + events : { + onClick: () => { + this.fireEvent('closeOrderSuccessful', [this]); + this.close(); + } + } + })); + + this.getContent().scrollToTop = 0; + }, + onLoadedError : () => { require([ 'package/quiqqer/frontend-users/bin/frontend/controls/login/Window' ], (LoginWindow) => { @@ -61,7 +127,16 @@ define('package/quiqqer/order-simple-checkout/bin/frontend/controls/SimpleChecko } }).open(); }); + }, + onOrderValid : () => { + if (this.$isOrdering) { + return; + } + this.$PayToOrderBtn.enable(); + }, + onOrderInvalid : () => { + this.$PayToOrderBtn.disable(); } } }).inject(this.getContent()); diff --git a/locale.xml b/locale.xml index a0cb360..20ea6fd 100644 --- a/locale.xml +++ b/locale.xml @@ -69,4 +69,15 @@ </groups> + <groups name="quiqqer/order-simple-checkout" datatype="js"> + <locale name="SimpleCheckoutWindow.btn.continue"> + <de><![CDATA[Weiter]]></de> + <en><![CDATA[Continue]]></en> + </locale> + <locale name="SimpleCheckoutWindow.btn.cancel"> + <de><![CDATA[Abbrechen]]></de> + <en><![CDATA[Cancel]]></en> + </locale> + </groups> + </locales> \ No newline at end of file diff --git a/src/QUI/ERP/Order/SimpleCheckout/Checkout.html b/src/QUI/ERP/Order/SimpleCheckout/Checkout.html index b7122e9..b2b125a 100644 --- a/src/QUI/ERP/Order/SimpleCheckout/Checkout.html +++ b/src/QUI/ERP/Order/SimpleCheckout/Checkout.html @@ -61,12 +61,14 @@ {$Delivery->create()} </div> + {if $Shipping} <div class="quiqqer-simple-checkout-data-shipping simpleCheckout-details-section"> <h2 class="simpleCheckoutCard-subheader"> {locale group="quiqqer/shipping" var="ordering.step.title.Shipping"} </h2> {$Shipping->create()} </div> + {/if} <div class="quiqqer-simple-checkout-data-payment simpleCheckout-details-section"> <h2 class="simpleCheckoutCard-subheader"> @@ -75,12 +77,14 @@ {$Payment->create()} </div> + {if $BillingAddress} <div class="quiqqer-simple-checkout-data-billing simpleCheckout-details-section"> <h2 class="simpleCheckoutCard-subheader"> {locale group="quiqqer/order-simple-checkout" var="ordering.step.title.Billing"} </h2> {$BillingAddress->create()} </div> + {/if} <div class="quiqqer-simple-checkout-data-pay"> <div class="quiqqer-order-step-checkout-notice"> diff --git a/src/QUI/ERP/Order/SimpleCheckout/Checkout.php b/src/QUI/ERP/Order/SimpleCheckout/Checkout.php index ccd9ec7..43eab50 100644 --- a/src/QUI/ERP/Order/SimpleCheckout/Checkout.php +++ b/src/QUI/ERP/Order/SimpleCheckout/Checkout.php @@ -97,14 +97,16 @@ public function getBody(): string $BasketForHeader = new Basket($this); $BasketForHeader->setAttribute('basketForHeader', true); + $isShippingInstalled = QUI::getPackageManager()->isInstalled('quiqqer/shipping'); + $Engine->assign([ 'Order' => $this->getOrder(), 'Basket' => new Basket($this), 'BasketForHeader' => $BasketForHeader, 'User' => $this->getUser(), 'Delivery' => new CheckoutDelivery($this), - 'BillingAddress' => new CheckoutBillingAddress($this), - 'Shipping' => new CheckoutShipping($this), + 'BillingAddress' => $isShippingInstalled ? new CheckoutBillingAddress($this) : false, + 'Shipping' => $isShippingInstalled ? new CheckoutShipping($this) : false, 'Payment' => new CheckoutPayment($this), 'termsAndConditions' => $termsAndConditions ]); -- GitLab