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 ( + + Carregando... + + ); +}; + +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} +} +`;