From e69f2569fae5d70bbf1dea461a3da404b5171bec Mon Sep 17 00:00:00 2001 From: Cristi Sandru <149154151+csandru-plenty@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:53:07 +0200 Subject: [PATCH] feat: reject empty checkout (#770) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: reject empty checkout * feat: reject empty checkout * code refactor --------- Co-authored-by: Maximilian Röll <30629157+maxiroellplenty@users.noreply.github.com> --- apps/web/composables/useCart/types.ts | 1 + apps/web/composables/useCart/useCart.ts | 3 ++ .../composables/useCheckout/useCheckout.ts | 3 +- apps/web/lang/de.json | 1 + apps/web/lang/en.json | 1 + apps/web/middleware/reject-empty-checkout.ts | 8 +++++ apps/web/pages/cart.vue | 20 ++++++++----- apps/web/pages/checkout.vue | 23 +++++++------- apps/web/pages/readonly-checkout.vue | 30 +++++-------------- docs/changelog/changelog_en.md | 1 + 10 files changed, 50 insertions(+), 41 deletions(-) create mode 100644 apps/web/middleware/reject-empty-checkout.ts diff --git a/apps/web/composables/useCart/types.ts b/apps/web/composables/useCart/types.ts index af2aa78e4..f34d44462 100644 --- a/apps/web/composables/useCart/types.ts +++ b/apps/web/composables/useCart/types.ts @@ -33,6 +33,7 @@ export interface UseCart { setCart: SetCart; clearCartItems: ClearCartItems; lastUpdatedCartItem: Readonly>; + cartIsEmpty: ComputedRef; } export type UseCartReturn = () => UseCart; diff --git a/apps/web/composables/useCart/useCart.ts b/apps/web/composables/useCart/useCart.ts index bdc017dfa..6110958ef 100644 --- a/apps/web/composables/useCart/useCart.ts +++ b/apps/web/composables/useCart/useCart.ts @@ -243,6 +243,8 @@ export const useCart: UseCartReturn = () => { } }; + const cartIsEmpty = computed(() => !state.value.data?.items?.length); + return { setCart, clearCartItems, @@ -251,6 +253,7 @@ export const useCart: UseCartReturn = () => { addItemsToCart, deleteCartItem, getCart, + cartIsEmpty, ...toRefs(state.value), }; }; diff --git a/apps/web/composables/useCheckout/useCheckout.ts b/apps/web/composables/useCheckout/useCheckout.ts index 044cfe30d..54d480fc1 100644 --- a/apps/web/composables/useCheckout/useCheckout.ts +++ b/apps/web/composables/useCheckout/useCheckout.ts @@ -12,7 +12,7 @@ export const useCheckout = (cacheKey = '') => { init: false, })); - const { data: cart, getCart, clearCartItems, loading: cartLoading } = useCart(); + const { data: cart, cartIsEmpty, getCart, clearCartItems, loading: cartLoading } = useCart(); const { checkboxValue: termsAccepted, setShowErrors } = useAgreementCheckbox('checkoutGeneralTerms'); const { shippingAsBilling } = useShippingAsBilling(); const { addresses: shippingAddresses, get: getShipping } = useAddressStore(AddressType.Shipping); @@ -105,6 +105,7 @@ export const useCheckout = (cacheKey = '') => { return { ...toRefs(state.value), cart, + cartIsEmpty, getCart, clearCartItems, cartLoading, diff --git a/apps/web/lang/de.json b/apps/web/lang/de.json index aae9889aa..4d6315c45 100644 --- a/apps/web/lang/de.json +++ b/apps/web/lang/de.json @@ -418,6 +418,7 @@ "editAddress": "Adresse bearbeiten", "email": "E-Mail", "emptyCart": "Ihr Warenkorb ist leer", + "emptyCartNotification": "Sie haben keine Artikel in Ihrem Warenkorb", "emptyCartImgAlt": "Bild eines leeren Warenkorbs", "emptyStateAltText": "Bild, das beschreibt, dass keine Produkte gefunden wurden", "emptyStateText": "Wir haben keine Produkte mehr in dieser Kategorie.", diff --git a/apps/web/lang/en.json b/apps/web/lang/en.json index 3e9eb1500..154c15733 100644 --- a/apps/web/lang/en.json +++ b/apps/web/lang/en.json @@ -418,6 +418,7 @@ "editAddress": "Edit address", "email": "Email", "emptyCart": "Your cart is empty", + "emptyCartNotification": "You don't have any items in your cart", "emptyCartImgAlt": "Image of an empty cart", "emptyStateAltText": "Image that describes that no products were found", "emptyStateText": "We no longer have products in this category.", diff --git a/apps/web/middleware/reject-empty-checkout.ts b/apps/web/middleware/reject-empty-checkout.ts new file mode 100644 index 000000000..1e3fb6476 --- /dev/null +++ b/apps/web/middleware/reject-empty-checkout.ts @@ -0,0 +1,8 @@ +export default defineNuxtRouteMiddleware(async () => { + const { getCart, cartIsEmpty } = useCart(); + const localePath = useLocalePath(); + + await getCart(); + if (!cartIsEmpty.value) return; + return navigateTo(localePath(paths.cart)); +}); diff --git a/apps/web/pages/cart.vue b/apps/web/pages/cart.vue index 9d1d2b93a..d61b32d05 100644 --- a/apps/web/pages/cart.vue +++ b/apps/web/pages/cart.vue @@ -2,11 +2,11 @@ -
+
@@ -24,7 +24,7 @@ size="lg" class="w-full mb-4 md:mb-0" > - {{ $t('goToCheckout') }} + {{ t('goToCheckout') }} @@ -34,7 +34,7 @@
-

{{ $t('emptyCart') }}

+

{{ t('emptyCart') }}

@@ -46,10 +46,14 @@ import { cartGetters } from '@plentymarkets/shop-api'; definePageMeta({ pageType: 'static' }); const NuxtLink = resolveComponent('NuxtLink'); +const { send } = useNotification(); +const { t } = useI18n(); const viewport = useViewport(); const localePath = useLocalePath(); const { isAuthorized } = useCustomer(); -const { data: cart, loading } = useCart(); -const cartNotEmpty = computed(() => (cart.value?.items?.length ?? 0) > 0); +const { data: cart, cartIsEmpty, loading } = useCart(); const goToCheckout = () => (isAuthorized.value ? localePath(paths.checkout) : localePath(paths.guestLogin)); +onNuxtReady(() => { + if (cartIsEmpty.value) send({ type: 'neutral', message: t('emptyCartNotification') }); +}); diff --git a/apps/web/pages/checkout.vue b/apps/web/pages/checkout.vue index f2c2b67b2..ba10a4207 100644 --- a/apps/web/pages/checkout.vue +++ b/apps/web/pages/checkout.vue @@ -110,6 +110,7 @@ import { PayPalAddToCartCallback } from '~/components/PayPal/types'; definePageMeta({ layout: 'simplified-header-and-footer', pageType: 'static', + middleware: ['reject-empty-checkout'], }); const { send } = useNotification(); @@ -120,7 +121,7 @@ const { shippingPrivacyAgreement } = useAdditionalInformation(); const { checkboxValue: termsAccepted } = useAgreementCheckbox('checkoutGeneralTerms'); const { cart, - getCart, + cartIsEmpty, clearCartItems, cartLoading, anyAddressFormIsOpen, @@ -154,18 +155,15 @@ onNuxtReady(async () => { .catch((error) => useHandleError(error)); }); -await getCart().then( - async () => - await Promise.all([ - useCartShippingMethods().getShippingMethods(), - usePaymentMethods().fetchPaymentMethods(), - useAggregatedCountries().fetchAggregatedCountries(), - ]), -); +await Promise.all([ + useCartShippingMethods().getShippingMethods(), + usePaymentMethods().fetchPaymentMethods(), + useAggregatedCountries().fetchAggregatedCountries(), +]); const paypalCardDialog = ref(false); const disableShippingPayment = computed(() => loadShipping.value || loadPayment.value); - +const processingOrder = ref(false); const paypalPaymentId = computed(() => { if (!paymentMethods.value.list) return null; return paymentProviderGetters.getIdByPaymentKey(paymentMethods.value.list, PayPalPaymentKey); @@ -214,10 +212,15 @@ const handlePayPalExpress = (callback?: PayPalAddToCartCallback) => { const order = async () => { if (!readyToBuy()) return; + processingOrder.value = true; const paymentMethodsById = _.keyBy(paymentMethods.value.list, 'id'); paymentMethodsById[selectedPaymentId.value].key === 'plentyPayPal' ? (paypalCardDialog.value = true) : await handleRegularOrder(); }; + +watch(cartIsEmpty, async () => { + if (!processingOrder.value) await navigateTo(localePath(paths.cart)); +}); diff --git a/apps/web/pages/readonly-checkout.vue b/apps/web/pages/readonly-checkout.vue index 75dc32aa6..c36e8bd77 100644 --- a/apps/web/pages/readonly-checkout.vue +++ b/apps/web/pages/readonly-checkout.vue @@ -1,9 +1,9 @@