From dcd11476826e8d5a99fbf74c96244570f619aca4 Mon Sep 17 00:00:00 2001 From: Michael Danielczok <michael@pcsg.de> Date: Sun, 2 Mar 2025 16:17:13 +0100 Subject: [PATCH 1/2] feat: show errors direclty under the input Improve error handling for coupon code input. Added a catch block to handle errors when adding a coupon code to the basket. The error message is now displayed directly under the input field, providing a better user experience. Related: quiqqer/coupons#20 --- ajax/frontend/redeem.php | 101 +++++++++------ bin/frontend/controls/CouponCodeInput.css | 19 ++- bin/frontend/controls/CouponCodeInput.html | 6 +- bin/frontend/controls/CouponCodeInput.js | 142 ++++++++++++++++++--- 4 files changed, 208 insertions(+), 60 deletions(-) diff --git a/ajax/frontend/redeem.php b/ajax/frontend/redeem.php index 0866ff8..9eef139 100644 --- a/ajax/frontend/redeem.php +++ b/ajax/frontend/redeem.php @@ -1,7 +1,7 @@ <?php /** - * This file contains package_quiqqer_coupons_ajax_delete + * This file contains package_quiqqer_coupons_ajax_frontend_redeem */ use QUI\ERP\Coupons\Handler; @@ -13,6 +13,15 @@ * @param int $id - CouponCode ID * @return bool - success */ + +/** + * Redeem a CouponCode + * @param string $code - coupon code + * @param string $orderHash - Order hash + * + * @throws QUI\Exception + * @throws QUI\ERP\Coupons\CouponCodeException + */ QUI::$Ajax->registerFunction( 'package_quiqqer_coupons_ajax_frontend_redeem', function ($code, $orderHash) { @@ -23,20 +32,25 @@ function ($code, $orderHash) { } catch (QUI\ERP\Coupons\CouponCodeException $Exception) { QUI\System\Log::writeDebugException($Exception); - QUI::getMessagesHandler()->addError($Exception->getMessage()); - - return false; +// QUI::getMessagesHandler()->addError($Exception->getMessage()); + throw $Exception; +// return false; } catch (Exception $Exception) { QUI\System\Log::writeException($Exception); - QUI::getMessagesHandler()->addError( - QUI::getLocale()->get( - 'quiqqer/coupons', - 'message.ajax.general_error' - ) - ); + throw new QUI\Exception([ + 'quiqqer/coupons', + 'message.ajax.general_error' + ]); - return false; +// QUI::getMessagesHandler()->addError( +// QUI::getLocale()->get( +// 'quiqqer/coupons', +// 'message.ajax.general_error' +// ) +// ); + +// return false; } $Order = QUI\ERP\Order\Handler::getInstance()->getOrderByHash($orderHash); @@ -49,40 +63,55 @@ function ($code, $orderHash) { foreach ($discounts as $Discount) { if (!DiscountEvents::isDiscountUsableWithQuantity($Discount, $productCount)) { - QUI::getMessagesHandler()->addError( - QUI::getLocale()->get( - 'quiqqer/coupons', - 'exception.CouponCode.discounts_invalid' - ) - ); - - return false; + throw new QUI\Exception([ + 'quiqqer/coupons', + 'exception.CouponCode.discounts_invalid' + ]); + +// QUI::getMessagesHandler()->addError( +// QUI::getLocale()->get( +// 'quiqqer/coupons', +// 'exception.CouponCode.discounts_invalid' +// ) +// ); +// +// return false; } if ($Discount->getAttribute('scope') === QUI\ERP\Discount\Handler::DISCOUNT_SCOPE_GRAND_TOTAL) { if (!DiscountEvents::isDiscountUsableWithPurchaseValue($Discount, $sum)) { - QUI::getMessagesHandler()->addError( - QUI::getLocale()->get( - 'quiqqer/coupons', - 'exception.CouponCode.discounts_invalid' - ) - ); - - return false; + throw new QUI\Exception([ + 'quiqqer/coupons', + 'exception.CouponCode.discounts_invalid' + ]); + +// QUI::getMessagesHandler()->addError( +// QUI::getLocale()->get( +// 'quiqqer/coupons', +// 'exception.CouponCode.discounts_invalid' +// ) +// ); +// +// return false; } continue; } if (!DiscountEvents::isDiscountUsableWithPurchaseValue($Discount, $subSum)) { - QUI::getMessagesHandler()->addError( - QUI::getLocale()->get( - 'quiqqer/coupons', - 'exception.CouponCode.discounts_invalid' - ) - ); - - return false; + throw new QUI\Exception([ + 'quiqqer/coupons', + 'exception.CouponCode.discounts_invalid' + ]); + +// QUI::getMessagesHandler()->addError( +// QUI::getLocale()->get( +// 'quiqqer/coupons', +// 'exception.CouponCode.discounts_invalid' +// ) +// ); +// +// return false; } } @@ -105,7 +134,7 @@ function ($code, $orderHash) { $CouponCode->addToOrder($Order); } - return true; +// return true; }, ['code', 'orderHash'] ); diff --git a/bin/frontend/controls/CouponCodeInput.css b/bin/frontend/controls/CouponCodeInput.css index cde8605..ebe6fcb 100644 --- a/bin/frontend/controls/CouponCodeInput.css +++ b/bin/frontend/controls/CouponCodeInput.css @@ -1,4 +1,6 @@ .quiqqer-coupons-couponcodeinput { + --_qui-error-text-color: var(--qui-error-text-color, red); + --_qui-error-input-border-color: var(--qui-error-input-border-color, red); container-type: inline-size; } @@ -14,13 +16,28 @@ } .quiqqer-coupons-couponcodeinput-input { - min-width: min(250px, 100cqi); + min-width: min(200px, 100cqi); } .quiqqer-coupons-remove { margin-left: 1rem; } +/* error handling */ +input.quiqqer-coupons-couponcodeinput-input[data-invalid] { + border-color: var(--_qui-error-input-border-color) +} + +.quiqqer-coupons-couponcodeinput__errorMsg:not([data-show]) { + display: none; +} + +:where(.quiqqer-coupons-couponcodeinput__errorMsg[data-show]) { + font-size: 0.875rem; + color: var(--_qui-error-text-color); + margin-top: 0.25rem; +} + /** * Simple Checkout */ diff --git a/bin/frontend/controls/CouponCodeInput.html b/bin/frontend/controls/CouponCodeInput.html index ccc1a19..65b4dfc 100644 --- a/bin/frontend/controls/CouponCodeInput.html +++ b/bin/frontend/controls/CouponCodeInput.html @@ -4,11 +4,13 @@ </label> <div class="quiqqer-coupons-couponcodeinput__inputGroup"> <input class="quiqqer-coupons-couponcodeinput-input" - type="text" name="code-input" id="code-input" data-name="code-input" + type="text" name="code-input" id="code-input" data-ref="code-input" placeholder="{{labelInputPlaceholder}}" + autocomplete="off" /> - <button class="quiqqer-coupons-couponcodeinput-btn btn btn-success" data-name="submit-coupon-code"> + <button class="quiqqer-coupons-couponcodeinput-btn btn btn-success" data-ref="submit-coupon-code"> {{submitBtnText}} </button> </div> + <div class="quiqqer-coupons-couponcodeinput__errorMsg" data-ref="errorMsg"></div> </div> \ No newline at end of file diff --git a/bin/frontend/controls/CouponCodeInput.js b/bin/frontend/controls/CouponCodeInput.js index 91b4811..174c5c5 100644 --- a/bin/frontend/controls/CouponCodeInput.js +++ b/bin/frontend/controls/CouponCodeInput.js @@ -31,16 +31,19 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [ Type: 'package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', Binds: [ - '$submit' + '$submit', + 'handleError' ], initialize: function(options) { this.parent(options); this.$Input = null; + this.$ErrorMsgContainer = null; this.Loader = new QUILoader(); this.$running = false; + this.addEvents({ onInject: this.$onInject }); @@ -65,17 +68,42 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [ submitBtnText: QUILocale.get(lg, lgPrefix + 'submitBtnText') })); + this.$ErrorMsgContainer = this.$Elm.querySelector('[data-ref="errorMsg"]'); + this.Loader.inject(this.$Elm); - this.$Input = this.$Elm.getElement('input[data-name="code-input"]'); + this.$Input = this.$Elm.getElement('[data-ref="code-input"]'); + + let timeoutId = null; + let previousValue = ''; + let currentValue = ''; + + const keyUpFunc = function(event ){ + currentValue = self.$Input.value.trim(); - this.$Input.addEvent('keyup', function(event) { if (event.code === 13) { + clearTimeout(timeoutId); + previousValue = currentValue; self.$submit(); + } else { + clearTimeout(timeoutId); + + timeoutId = setTimeout(function() { + if ( + (currentValue !== previousValue || currentValue === '') && + self.$ErrorMsgContainer.getAttribute('data-show') + ) { + self.hideErrorMessage(); + self.resetInvalidInput(); + } + previousValue = currentValue; + }, 400); } - }); + }; + + this.$Input.addEvent('keyup', keyUpFunc); - this.$Elm.getElement('[data-name="submit-coupon-code"]').addEvent('click', function(event) { + this.$Elm.getElement('[data-ref="submit-coupon-code"]').addEvent('click', function(event) { event.stop(); self.$submit(); }); @@ -153,12 +181,7 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [ this.$running = false; this.Loader.hide(); - }).catch((err) => { - console.error(err); - - this.$running = false; - this.Loader.hide(); - }); + }).catch(self.handleError); }, { 'package': 'quiqqer/order-simple-checkout', orderHash: orderHash @@ -184,7 +207,7 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [ 'package': 'quiqqer/order' }); }); - }); + }).catch(self.handleError); }, { 'package': 'quiqqer/order' }); @@ -196,19 +219,33 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [ OrderProcess.Loader.show(); + // OrderProcess.getOrder().then(function(orderHash) { + // return CouponCodes.addCouponCodeToBasket(code, orderHash); + // }).then(function(redeemed) { + // self.$running = false; + // + // if (redeemed === false) { + // OrderProcess.Loader.hide(); + // self.Loader.hide(); + // return; + // } + // + // self.$addCouponCodeToSession(code).then(function() { + // OrderProcess.reload(); + // }); + // }); + OrderProcess.getOrder().then(function(orderHash) { - return CouponCodes.addCouponCodeToBasket(code, orderHash); - }).then(function(redeemed) { - self.$running = false; + CouponCodes.addCouponCodeToBasket(code, orderHash).then(() => { + self.$running = false; - if (redeemed === false) { + self.$addCouponCodeToSession(code).then(function() { + OrderProcess.reload(); + }); + }).catch((err) => { OrderProcess.Loader.hide(); self.Loader.hide(); - return; - } - - self.$addCouponCodeToSession(code).then(function() { - OrderProcess.reload(); + self.handleError(err); }); }); }, @@ -285,6 +322,69 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [ }); }); }); + }, + + /** + * Show error message container + * @param msg + */ + showErrorMessage: function( msg) { + if (!msg) { + console.log("no error message provided"); + + return; + } + + if (!this.$ErrorMsgContainer) { + return; + } + + this.$ErrorMsgContainer.set('html', msg); + this.$ErrorMsgContainer.setAttribute('data-show', '1'); + }, + + /** + * Hide error message container + */ + hideErrorMessage: function() { + if (!this.$ErrorMsgContainer) { + return; + } + + this.$ErrorMsgContainer.removeAttribute('data-show'); + }, + + /** + * Set data-invalid attribute to the input + */ + highlightInvalidInput: function() { + this.$Input.setAttribute('data-invalid', '1'); + }, + + /** + * Remove data-invalid attribute from the input + */ + resetInvalidInput: function() { + this.$Input.removeAttribute('data-invalid'); + }, + + /** + * Handle error message (show error message and highlight invalid input) + * + * @param err + */ + handleError: function(err) { + console.error(err); + + if (err.options !== undefined && err.options.message) { + const msg = err.options.message; + this.showErrorMessage(msg); + this.highlightInvalidInput(); + } + + this.$running = false; + this.Loader.hide(); } + }); }); -- GitLab From 400e67fac48c16049f14023568e42994c17313f8 Mon Sep 17 00:00:00 2001 From: Michael Danielczok <michael@pcsg.de> Date: Tue, 4 Mar 2025 10:13:00 +0100 Subject: [PATCH 2/2] refactor: disabled quiqqer message on coupon code error Related: quiqqer/coupons#20 Acked-by: Henning Leutz <leutz@pcsg.de> --- ajax/frontend/redeem.php | 40 ------------------------ bin/frontend/classes/CouponCodes.js | 3 +- bin/frontend/controls/CouponCodeInput.js | 17 ---------- 3 files changed, 2 insertions(+), 58 deletions(-) diff --git a/ajax/frontend/redeem.php b/ajax/frontend/redeem.php index 9eef139..02f080a 100644 --- a/ajax/frontend/redeem.php +++ b/ajax/frontend/redeem.php @@ -32,9 +32,7 @@ function ($code, $orderHash) { } catch (QUI\ERP\Coupons\CouponCodeException $Exception) { QUI\System\Log::writeDebugException($Exception); -// QUI::getMessagesHandler()->addError($Exception->getMessage()); throw $Exception; -// return false; } catch (Exception $Exception) { QUI\System\Log::writeException($Exception); @@ -42,15 +40,6 @@ function ($code, $orderHash) { 'quiqqer/coupons', 'message.ajax.general_error' ]); - -// QUI::getMessagesHandler()->addError( -// QUI::getLocale()->get( -// 'quiqqer/coupons', -// 'message.ajax.general_error' -// ) -// ); - -// return false; } $Order = QUI\ERP\Order\Handler::getInstance()->getOrderByHash($orderHash); @@ -67,15 +56,6 @@ function ($code, $orderHash) { 'quiqqer/coupons', 'exception.CouponCode.discounts_invalid' ]); - -// QUI::getMessagesHandler()->addError( -// QUI::getLocale()->get( -// 'quiqqer/coupons', -// 'exception.CouponCode.discounts_invalid' -// ) -// ); -// -// return false; } if ($Discount->getAttribute('scope') === QUI\ERP\Discount\Handler::DISCOUNT_SCOPE_GRAND_TOTAL) { @@ -84,15 +64,6 @@ function ($code, $orderHash) { 'quiqqer/coupons', 'exception.CouponCode.discounts_invalid' ]); - -// QUI::getMessagesHandler()->addError( -// QUI::getLocale()->get( -// 'quiqqer/coupons', -// 'exception.CouponCode.discounts_invalid' -// ) -// ); -// -// return false; } continue; @@ -103,15 +74,6 @@ function ($code, $orderHash) { 'quiqqer/coupons', 'exception.CouponCode.discounts_invalid' ]); - -// QUI::getMessagesHandler()->addError( -// QUI::getLocale()->get( -// 'quiqqer/coupons', -// 'exception.CouponCode.discounts_invalid' -// ) -// ); -// -// return false; } } @@ -133,8 +95,6 @@ function ($code, $orderHash) { if ($Order instanceof QUI\ERP\Order\OrderInProcess) { $CouponCode->addToOrder($Order); } - -// return true; }, ['code', 'orderHash'] ); diff --git a/bin/frontend/classes/CouponCodes.js b/bin/frontend/classes/CouponCodes.js index cea109e..d0076ba 100644 --- a/bin/frontend/classes/CouponCodes.js +++ b/bin/frontend/classes/CouponCodes.js @@ -32,7 +32,8 @@ define('package/quiqqer/coupons/bin/frontend/classes/CouponCodes', [ 'package': pkg, code: code, orderHash: orderHash, - onError: reject + onError: reject, + showError: false // disable quiqqer message in frontend }); }); } diff --git a/bin/frontend/controls/CouponCodeInput.js b/bin/frontend/controls/CouponCodeInput.js index 174c5c5..df764ed 100644 --- a/bin/frontend/controls/CouponCodeInput.js +++ b/bin/frontend/controls/CouponCodeInput.js @@ -219,22 +219,6 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [ OrderProcess.Loader.show(); - // OrderProcess.getOrder().then(function(orderHash) { - // return CouponCodes.addCouponCodeToBasket(code, orderHash); - // }).then(function(redeemed) { - // self.$running = false; - // - // if (redeemed === false) { - // OrderProcess.Loader.hide(); - // self.Loader.hide(); - // return; - // } - // - // self.$addCouponCodeToSession(code).then(function() { - // OrderProcess.reload(); - // }); - // }); - OrderProcess.getOrder().then(function(orderHash) { CouponCodes.addCouponCodeToBasket(code, orderHash).then(() => { self.$running = false; @@ -244,7 +228,6 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [ }); }).catch((err) => { OrderProcess.Loader.hide(); - self.Loader.hide(); self.handleError(err); }); }); -- GitLab