Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shipping costs #110

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
18 changes: 18 additions & 0 deletions src/components/LoadingImg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Image from 'next/image';


const LoadingImg = (props) => {

return (
<span className="align-middle" {...props} >
<Image
src='/cart-spinner.gif'
width="54px"
height="54px"
alt="Carregando..."
/>
</span>
);
};

export default LoadingImg;
65 changes: 43 additions & 22 deletions src/components/checkout/CheckoutForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -257,7 +272,13 @@ const CheckoutForm = ({countriesData}) => {
{/* Order*/}
<h2 className="text-xl font-medium mb-4">Your Order</h2>
<YourOrder cart={cart}/>

<ShippingCosts
cart={cart}
refetchCart={refetch}
shippingAddress={input?.shipping}
loadingCart={loadingCart}
validateFields={validateFields}
/>
{/*Payment*/}
<PaymentModes input={input} handleOnChange={handleOnChange}/>

Expand Down
198 changes: 198 additions & 0 deletions src/components/checkout/ShippingCosts.js
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={cx(
'choose-shipping-wrap flex-grow',
{ 'opacity-50': isLoading }
)}
>
{cart?.needsShippingAddress &&
<>
<h2 className="mb-2 text-xl text-bold">Shipping Costs</h2>
<hr className="my-4 " />
<div className="flex flex-wrap justify-between">
<div className="flex-grow">
{
<>
<button
disabled={isLoading}
type={"button"}
onClick={handleCalcShippingClick}
className={cx(
'bg-purple-600 text-white px-5 py-3 rounded-sm w-auto xl:w-full',
{ 'opacity-50': isLoading }
)}
>
Update Shipping Costs
</button>
{isLoading &&
<LoadingImg />
}
{requestError
? <p className="my-4 text-red-600">{requestError}</p>
: <p className="my-4 text-xs opacity-75">
{
[
cart?.customer?.shipping?.address1,
cart?.customer?.shipping?.city,
cart?.customer?.shipping?.state
].filter(val => val).join(' - ')
}
</p>
}
</>
}
{cart?.customer?.shipping?.country
&& cart?.customer?.shipping?.state
&& cart?.customer?.shipping?.postcode
&& cart?.shippingMethods?.length
&& <div className='shipping-methods-wrap'>
<div className='flex'>
<h2 className="my-2 self-center text-xl text-bold">
Choose Shipping Method
</h2>
</div>
<hr className="my-2" />
{cart?.shippingMethods?.map(method => (
<div key={method.id}>
<label>
<input
type="radio"
name="chosenShippingMethod"
className="my-2"
disabled={isLoading}
value={method.id}
onChange={handleShippingSelection}
checked={shippingMethod == method.id}
/> {method.label} - {formatCurrency(method.cost)}
</label>
</div>
))}
</div>
}
</div>
</div>
<table className="mt-4 checkout-cart table table-hover w-full mb-10">
<tbody>
<tr className="bg-gray-200">
<td className="w-24" />
<td className="woo-next-checkout-total font-normal ">Shipping</td>
<td className="woo-next-checkout-total font-bold ">{formatCurrency(cart.shippingTotal)}</td>
</tr>
<tr className="bg-gray-200">
<td className="" />
<td className="woo-next-checkout-total font-normal text-xl">Total</td>
<td className="woo-next-checkout-total font-bold text-xl">{cart.total}</td>
</tr>
</tbody>
</table>
</>
}
</div>
)

};

export default ShippingSelection;
38 changes: 37 additions & 1 deletion src/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;

Expand Down
Loading