diff --git a/src/components/LoadingImg.js b/src/components/LoadingImg.js
new file mode 100644
index 00000000..fa577563
--- /dev/null
+++ b/src/components/LoadingImg.js
@@ -0,0 +1,18 @@
+import Image from 'next/image';
+
+
+const LoadingImg = (props) => {
+
+ return (
+
+
+
+ );
+};
+
+export default LoadingImg;
diff --git a/src/components/checkout/CheckoutForm.js b/src/components/checkout/CheckoutForm.js
index c419b099..0ce5ef3c 100644
--- a/src/components/checkout/CheckoutForm.js
+++ b/src/components/checkout/CheckoutForm.js
@@ -18,6 +18,7 @@ import {
} from "../../utils/checkout";
import CheckboxField from "./form-elements/CheckboxField";
import CLEAR_CART_MUTATION from "../../mutations/clear-cart";
+import ShippingCosts from './ShippingCosts';
// Use this for testing purposes, so you dont have to fill the checkout form over an over again.
// const defaultCustomerInfo = {
@@ -79,7 +80,11 @@ const CheckoutForm = ({countriesData}) => {
const [createdOrderData, setCreatedOrderData] = useState({});
// Get Cart Data.
- const {data} = useQuery(GET_CART, {
+ const {
+ loading: loadingCart,
+ data,
+ refetch
+ } = useQuery(GET_CART, {
notifyOnNetworkStatusChange: true,
onCompleted: () => {
// Update cart in the localStorage.
@@ -108,40 +113,50 @@ const CheckoutForm = ({countriesData}) => {
const [ clearCartMutation ] = useMutation( CLEAR_CART_MUTATION );
- /*
- * Handle form submit.
- *
- * @param {Object} event Event Object.
+ /**
+ * Validate Billing and Shipping Details
*
- * @return {void}
+ * Note:
+ * 1. If billing is different than shipping address, only then validate billing.
+ * 2. We are passing theBillingStates?.length and theShippingStates?.length, so that
+ * the respective states should only be mandatory, if a country has states.
*/
- const handleFormSubmit = async (event) => {
- event.preventDefault();
+ const validateFields = () => {
+ let isValid= true;
- /**
- * Validate Billing and Shipping Details
- *
- * Note:
- * 1. If billing is different than shipping address, only then validate billing.
- * 2. We are passing theBillingStates?.length and theShippingStates?.length, so that
- * the respective states should only be mandatory, if a country has states.
- */
- const billingValidationResult = input?.billingDifferentThanShipping ? validateAndSanitizeCheckoutForm(input?.billing, theBillingStates?.length) : {errors: null, isValid: true};
+ const billingValidationResult = input?.billingDifferentThanShipping ? validateAndSanitizeCheckoutForm(input?.billing, theBillingStates?.length) : { errors: null, isValid: true };
const shippingValidationResult = validateAndSanitizeCheckoutForm(input?.shipping, theShippingStates?.length);
if (!shippingValidationResult.isValid || !billingValidationResult.isValid) {
setInput({
...input,
- billing: {...input.billing, errors: billingValidationResult.errors},
- shipping: {...input.shipping, errors: shippingValidationResult.errors}
+ billing: { ...input.billing, errors: billingValidationResult.errors },
+ shipping: { ...input.shipping, errors: shippingValidationResult.errors }
});
+ isValid = false;
+ }
+
+ return isValid;
+ };
+
+ /*
+ * Handle form submit.
+ *
+ * @param {Object} event Event Object.
+ *
+ * @return {void}
+ */
+ const handleFormSubmit = async (event) => {
+ event.preventDefault();
+
+ if( ! validateFields() ) {
return;
}
- if ( 'stripe-mode' === input.paymentMethod ) {
+ if ('stripe-mode' === input.paymentMethod) {
const createdOrderData = await handleStripeCheckout(input, cart?.products, setRequestError, clearCartMutation, setIsStripeOrderProcessing, setCreatedOrderData);
- return null;
+ return null;
}
const checkOutData = createCheckoutData(input);
@@ -257,7 +272,13 @@ const CheckoutForm = ({countriesData}) => {
{/* Order*/}
Your Order
-
+
{/*Payment*/}
diff --git a/src/components/checkout/ShippingCosts.js b/src/components/checkout/ShippingCosts.js
new file mode 100644
index 00000000..f53b61cb
--- /dev/null
+++ b/src/components/checkout/ShippingCosts.js
@@ -0,0 +1,198 @@
+import { useState } from 'react';
+import { v4 } from 'uuid';
+import { useMutation, useQuery } from '@apollo/client';
+import UPDATE_SHIPPING_ADDRESS from "../../mutations/update-shipping-address";
+import { UPDATE_SHIPPING_METHOD } from "../../mutations/update-shipping-method";
+import LoadingImg from "../LoadingImg";
+import { isEmpty } from 'lodash';
+import cx from 'classnames';
+import { formatCurrency } from "../../functions";
+
+const ShippingSelection = ({
+ cart,
+ refetchCart,
+ shippingAddress,
+ loadingCart,
+ validateFields,
+}) => {
+
+ const [
+ shippingMethod,
+ setShippingMethod
+ ] = useState(cart?.shippingMethod ?? '');
+
+ const [requestError, setRequestError] = useState(null);
+
+ const requestDefaultOptions = {
+ onCompleted: () => {
+ refetchCart('cart');
+ },
+ onError: (error) => {
+ if (error) {
+ const errorMessage = !isEmpty(error?.graphQLErrors?.[0])
+ ? error.graphQLErrors[0]?.message
+ : '';
+ console.warn(error);
+ if (setRequestError) {
+ setRequestError(errorMessage);
+ }
+ }
+ }
+ };
+
+ // Update Customer Shipping Address for cart shipping calculations.
+ const [updateShippingAddress, {
+ data: updatedShippingData,
+ loading: updatingShippingAddress,
+ error: updateShippingAddressError
+ }] = useMutation(UPDATE_SHIPPING_ADDRESS, requestDefaultOptions);
+
+ // Update Shipping Method.
+ const [ShippingSelectionMethod, {
+ data: chosenShippingData,
+ loading: choosingShippingMethod,
+ error: ShippingSelectionError
+ }] = useMutation(UPDATE_SHIPPING_METHOD, requestDefaultOptions);
+
+ const handleCalcShippingClick = async (event) => {
+
+ setRequestError("");
+ if (!validateFields()) {
+ setRequestError('Please fill out all required shipping fields to calculate shipping costs.');
+ return;
+ }
+
+ const {
+ errors,
+ createAccount,
+ orderNotes,
+ ...shipping
+ } = shippingAddress;
+
+ updateShippingAddress({
+ variables: {
+ input: {
+ clientMutationId: v4(),
+ shipping,
+ }
+ },
+ });
+ };
+
+ const handleShippingSelection = (event) => {
+ const chosenShippingMethod = event.target.value;
+
+ setShippingMethod(chosenShippingMethod);
+
+ if (chosenShippingMethod != shippingMethod) {
+ ShippingSelectionMethod({
+ variables: {
+ shippingMethod: {
+ clientMutationId: v4(),
+ shippingMethods: [chosenShippingMethod],
+ }
+ },
+ });
+
+ };
+ }
+
+ const isLoading = updatingShippingAddress
+ || choosingShippingMethod
+ || loadingCart;
+
+ return (
+
+ {cart?.needsShippingAddress &&
+ <>
+
Shipping Costs
+
+
+
+ {
+ <>
+
+ {isLoading &&
+
+ }
+ {requestError
+ ?
{requestError}
+ :
+ {
+ [
+ cart?.customer?.shipping?.address1,
+ cart?.customer?.shipping?.city,
+ cart?.customer?.shipping?.state
+ ].filter(val => val).join(' - ')
+ }
+
+ }
+ >
+ }
+ {cart?.customer?.shipping?.country
+ && cart?.customer?.shipping?.state
+ && cart?.customer?.shipping?.postcode
+ && cart?.shippingMethods?.length
+ &&
+
+
+ Choose Shipping Method
+
+
+
+ {cart?.shippingMethods?.map(method => (
+
+
+
+ ))}
+
+ }
+
+
+
+
+
+ |
+ Shipping |
+ {formatCurrency(cart.shippingTotal)} |
+
+
+ |
+ Total |
+ {cart.total} |
+
+
+
+ >
+ }
+
+ )
+
+};
+
+export default ShippingSelection;
diff --git a/src/functions.js b/src/functions.js
index e3b79f66..ea9c461f 100644
--- a/src/functions.js
+++ b/src/functions.js
@@ -14,6 +14,23 @@ export const getFloatVal = ( string ) => {
};
+/**
+ * Format float value as Currency string.
+ *
+ * @param {float} num The number to be formatted.
+ * @return {string}
+ */
+ export const formatCurrency = (num, currency = '$') => {
+ let floatValue = num;
+ if ('string' === typeof floatValue) {
+ floatValue = getFloatVal(floatValue);
+ }
+ if( ! floatValue ) {
+ floatValue = 0;
+ }
+ return (currency + floatValue.toFixed(2));
+};
+
/**
* Add first product.
*
@@ -237,8 +254,27 @@ export const getFormattedCart = ( data ) => {
formattedCart.products.push( product );
}
+ formattedCart.needsShippingAddress = data?.cart?.needsShippingAddress;
+ formattedCart.shippingMethod = data?.cart?.chosenShippingMethods[0] ?? '';
+ formattedCart.shippingMethods = data?.cart?.availableShippingMethods
+ ? data?.cart?.availableShippingMethods[0]?.rates
+ : [];
+ formattedCart.shippingTotal = formattedCart?.shippingMethods?.find(
+ ship => ship.id == formattedCart?.shippingMethod
+ )?.cost;
+
formattedCart.totalProductsCount = totalProductsCount;
- formattedCart.totalProductsPrice = data?.cart?.total ?? '';
+ formattedCart.totalProductsPrice = data?.cart?.subtotal ?? '';
+ formattedCart.total = data?.cart?.total ?? '';
+
+ if (data?.customer) {
+ let customer = {
+ ...data?.customer,
+ shipping: { ...data?.customer?.shipping },
+ billing: { ...data?.customer?.billing }
+ };
+ formattedCart.customer = customer;
+ }
return formattedCart;
diff --git a/src/mutations/update-shipping-address.js b/src/mutations/update-shipping-address.js
new file mode 100644
index 00000000..4eae9bcf
--- /dev/null
+++ b/src/mutations/update-shipping-address.js
@@ -0,0 +1,28 @@
+import { gql } from "@apollo/client";
+
+/**
+ * Update Customer Shipping address.
+ *
+ * This query is used for updating the customer shipping address.
+ */
+const UPDATE_SHIPPING_ADDRESS = gql`
+mutation UPDATE_SHIPPING_ADDRESS ($input: UpdateCustomerInput!) {
+ updateCustomer(input: $input) {
+ customer {
+ shipping {
+ address1
+ address2
+ city
+ state
+ postcode
+ phone
+ email
+ firstName
+ lastName
+ }
+ }
+ }
+}
+`;
+
+export default UPDATE_SHIPPING_ADDRESS;
diff --git a/src/mutations/update-shipping-method.js b/src/mutations/update-shipping-method.js
new file mode 100644
index 00000000..90a14ec6
--- /dev/null
+++ b/src/mutations/update-shipping-method.js
@@ -0,0 +1,34 @@
+import { gql } from "@apollo/client";
+
+/**
+ * Update Shipping method.
+ *
+ * This query is used for updating the selected shipping method option.
+ */
+export const UpdateShippingMethod = `
+ updateShippingMethod(input: $shippingMethod) {
+ cart {
+ availableShippingMethods {
+ packageDetails
+ supportsShippingCalculator
+ rates {
+ id
+ cost
+ label
+ }
+ }
+ chosenShippingMethods
+ shippingTotal
+ shippingTax
+ subtotal
+ subtotalTax
+ total
+ }
+ }
+`;
+
+export const UPDATE_SHIPPING_METHOD = gql`
+ mutation UPDATE_SHIPPING_METHOD ($shippingMethod: UpdateShippingMethodInput!) {
+ ${UpdateShippingMethod}
+ }
+`;
diff --git a/src/queries/get-cart.js b/src/queries/get-cart.js
index 632eae49..0d428bcd 100644
--- a/src/queries/get-cart.js
+++ b/src/queries/get-cart.js
@@ -1,4 +1,5 @@
import { gql } from "@apollo/client";
+import { GetCustomer } from "./get-customer";
const GET_CART = gql`
query GET_CART {
@@ -81,7 +82,19 @@ query GET_CART {
feeTotal
discountTax
discountTotal
+ availableShippingMethods {
+ packageDetails
+ rates {
+ id
+ label
+ cost
+ methodId
+ }
+ }
+ chosenShippingMethods
+ needsShippingAddress
}
+ ${GetCustomer}
}
`;
diff --git a/src/queries/get-customer.js b/src/queries/get-customer.js
new file mode 100644
index 00000000..31175030
--- /dev/null
+++ b/src/queries/get-customer.js
@@ -0,0 +1,26 @@
+import { gql } from "@apollo/client";
+
+export const GetCustomer = `
+customer {
+ email
+ shipping {
+ address1
+ address2
+ city
+ company
+ country
+ email
+ firstName
+ lastName
+ phone
+ postcode
+ state
+ }
+}
+`;
+
+export const GET_CUSTOMER = gql`
+query GET_CUSTOMER {
+ ${GetCustomer}
+}
+`;