diff --git a/framework/commerce/types/cart.ts b/framework/commerce/types/cart.ts index 15e24d32e..e0d56b3bb 100644 --- a/framework/commerce/types/cart.ts +++ b/framework/commerce/types/cart.ts @@ -1,5 +1,5 @@ -import { Shipping } from 'src/components/icons'; -import type { Discount, Measurement, Image } from './common' +import { ShippingMethod } from '@framework/schema'; +import type { Discount, Image, Measurement } from './common'; export type SelectedOption = { // The option's id. @@ -60,6 +60,31 @@ export type ProductVariant = { // Shopping cart, a.k.a Checkout export type Cart = { + id: string + // ID of the customer to which the cart belongs. + customerId?: string + // The email assigned to this cart + email?: string + // The date and time when the cart was created. + createdAt: string + // The currency used for this cart + currency: { code: string } + // Specifies if taxes are included in the line items. + taxesIncluded: boolean + lineItems: LineItem[] + // The sum of all the prices of all the items in the cart. + // Duties, taxes, shipping and discounts excluded. + lineItemsSubtotalPrice: number + // Price of the cart before duties, shipping and taxes. + subtotalPrice: number + // The sum of all the prices of all the items in the cart. + // Duties, taxes and discounts included. + totalPrice: number + // Discounts that have been applied on the cart. + discounts?: Discount[] +} + +export type CartCheckout = { id: string // ID of the customer to which the cart belongs. customerId?: string @@ -97,6 +122,10 @@ export type Cart = { // Discounts that have been applied on the cart. discounts?: Discount[] totalDiscount: number + shippingLine: { + priceWithTax: number + shippingMethod: ShippingMethod + } } /** diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 6a2778597..cc3c0bc9c 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3066,6 +3066,11 @@ export type CartFragment = { __typename?: 'Order' } & Pick< { __typename?: 'Discount' } & Pick > customer?: Maybe<{ __typename?: 'Customer' } & Pick> + shippingLines: Array< + Pick & { + shippingMethod: Pick; + } + > lines: Array< { __typename?: 'OrderLine' } & Pick< OrderLine, diff --git a/framework/vendure/utils/fragments/cart-fragment.ts b/framework/vendure/utils/fragments/cart-fragment.ts index f0a34b8e3..54ac08912 100644 --- a/framework/vendure/utils/fragments/cart-fragment.ts +++ b/framework/vendure/utils/fragments/cart-fragment.ts @@ -9,25 +9,8 @@ export const cartFragment = /* GraphQL */ ` total totalWithTax currencyCode - discounts { - type - description - amount - amountWithTax - } customer { id - firstName - lastName - emailAddress - } - shippingAddress { - streetLine1 - city - province - postalCode - countryCode - phoneNumber } lines { id diff --git a/framework/vendure/utils/normalize.ts b/framework/vendure/utils/normalize.ts index d81fbc2f1..e318d7535 100644 --- a/framework/vendure/utils/normalize.ts +++ b/framework/vendure/utils/normalize.ts @@ -1,6 +1,6 @@ -import { Cart } from '@commerce/types/cart' -import { ProductCard, Product } from '@commerce/types/product' -import { CartFragment, SearchResultFragment, Favorite } from '../schema' +import { Cart, CartCheckout } from '@commerce/types/cart' +import { Product, ProductCard } from '@commerce/types/product' +import { CartFragment, Favorite, SearchResultFragment, ShippingMethod } from '../schema' export function normalizeSearchResult(item: SearchResultFragment): ProductCard { return { @@ -36,6 +36,41 @@ export function normalizeFavoriteProductResult(item: Favorite) { export function normalizeCart(order: CartFragment): Cart { + return { + id: order.id.toString(), + createdAt: order.createdAt, + taxesIncluded: true, + lineItemsSubtotalPrice: order.subTotalWithTax / 100, + currency: { code: order.currencyCode }, + subtotalPrice: order.subTotalWithTax / 100, + totalPrice: order.totalWithTax / 100, + customerId: order.customer?.id, + lineItems: order.lines?.map((l) => ({ + id: l.id, + name: l.productVariant.name, + quantity: l.quantity, + slug: l.productVariant.product.slug, + variantId: l.productVariant.id, + productId: l.productVariant.productId, + images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }], + discounts: l.discounts.map((d) => ({ value: d.amount / 100 })), + path: '', + variant: { + id: l.productVariant.id, + name: l.productVariant.name, + sku: l.productVariant.sku, + price: l.discountedUnitPriceWithTax / 100, + listPrice: l.unitPriceWithTax / 100, + image: { + url: l.featuredAsset?.preview + '?preset=thumb' || '', + }, + requiresShipping: true, + }, + })), + } +} + +export function normalizeCartForCheckout(order: CartFragment): CartCheckout { return { id: order.id.toString(), createdAt: order.createdAt, @@ -59,6 +94,10 @@ export function normalizeCart(order: CartFragment): Cart { countryCode: order.shippingAddress?.countryCode || '', phoneNumber: order.shippingAddress?.phoneNumber || '', }, + shippingLine: { + priceWithTax: order.shippingLines[0].priceWithTax, + shippingMethod: order.shippingLines[0].shippingMethod as ShippingMethod + }, totalDiscount: order.discounts?.reduce((total, item) => total + item.amountWithTax, 0) / 100 || 0, discounts: order.discounts.map(item => { return { value: item.amountWithTax, description: item.description } diff --git a/framework/vendure/utils/queries/active-order-for-checkout-query.ts b/framework/vendure/utils/queries/active-order-for-checkout-query.ts new file mode 100644 index 000000000..7acedc492 --- /dev/null +++ b/framework/vendure/utils/queries/active-order-for-checkout-query.ts @@ -0,0 +1,116 @@ +export const getActiveOrderForCheckoutQuery = /* GraphQL */ ` +query getActiveOrderForCheckout { + activeOrder { + ...Cart + shippingAddress { + ...OrderAddress + __typename + } + __typename + } +} + +fragment Cart on Order { + id + code + state + active + customer { + id + firstName + lastName + emailAddress + } + lines { + id + featuredAsset { + ...Asset + __typename + } + unitPrice + unitPriceWithTax + quantity + linePriceWithTax + discountedLinePriceWithTax + unitPriceWithTax + discountedUnitPriceWithTax + productVariant { + id + name + price + priceWithTax + stockLevel + productId + product { + slug + } + __typename + } + discounts { + amount + amountWithTax + description + adjustmentSource + type + __typename + } + __typename + } + totalQuantity + subTotal + subTotalWithTax + total + totalWithTax + shipping + shippingWithTax + currencyCode + shippingLines { + priceWithTax + shippingMethod { + id + code + name + description + __typename + } + __typename + } + discounts { + amount + amountWithTax + description + adjustmentSource + type + __typename + } + __typename +} + +fragment Asset on Asset { + id + width + height + name + preview + focalPoint { + x + y + __typename + } + __typename +} + +fragment OrderAddress on OrderAddress { + fullName + company + streetLine1 + streetLine2 + city + province + postalCode + country + phoneNumber + __typename +} +` + diff --git a/src/components/hooks/cart/useRemoveProductInCart.tsx b/src/components/hooks/cart/useRemoveProductInCart.tsx index d66fd4306..27fcb8de2 100644 --- a/src/components/hooks/cart/useRemoveProductInCart.tsx +++ b/src/components/hooks/cart/useRemoveProductInCart.tsx @@ -1,11 +1,10 @@ +import { RemoveOrderLineMutation, RemoveOrderLineMutationVariables } from '@framework/schema' +import { removeOrderLineMutation } from '@framework/utils/mutations/remove-order-line-mutation' import { useState } from 'react' import { CommonError } from 'src/domains/interfaces/CommonError' -import rawFetcher from 'src/utils/rawFetcher' -import { AdjustOrderLineMutationVariables,AdjustOrderLineMutation, RemoveOrderLineMutation, RemoveOrderLineMutationVariables } from '@framework/schema' import { errorMapping } from 'src/utils/errrorMapping' +import rawFetcher from 'src/utils/rawFetcher' import { useGetActiveOrder } from '.' -import { adjustOrderLineMutation } from '@framework/utils/mutations/adjust-order-line-mutation' -import { removeOrderLineMutation } from '@framework/utils/mutations/remove-order-line-mutation' const useRemoveProductInCart = () => { const [loading, setLoading] = useState(false) diff --git a/src/components/hooks/order/useGetActiveOrderForCheckout.tsx b/src/components/hooks/order/useGetActiveOrderForCheckout.tsx new file mode 100644 index 000000000..fb69f82be --- /dev/null +++ b/src/components/hooks/order/useGetActiveOrderForCheckout.tsx @@ -0,0 +1,13 @@ +import { ActiveOrderQuery } from '@framework/schema' +import { normalizeCartForCheckout } from '@framework/utils/normalize' +import { getActiveOrderForCheckoutQuery } from '@framework/utils/queries/active-order-for-checkout-query' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + + +const useGetActiveOrderForCheckout = () => { + const { data, ...rest } = useSWR([getActiveOrderForCheckoutQuery], gglFetcher) + return { order: data?.activeOrder ? normalizeCartForCheckout(data!.activeOrder) : null, ...rest } +} + +export default useGetActiveOrderForCheckout diff --git a/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx b/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx index 7b4196279..59a0eee20 100644 --- a/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx +++ b/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx @@ -1,15 +1,15 @@ -import { Cart } from '@commerce/types/cart' +import { CartCheckout } from '@commerce/types/cart' +import Link from 'next/link' import React from 'react' import { ROUTE } from 'src/utils/constanst.utils' import { LANGUAGE } from 'src/utils/language.utils' import { ButtonCommon, CardItemCheckout, EmptyCommon } from '../../../common' import s from './CheckoutBill.module.scss' import FormPromotionCode from './FormPromotionCode/FormPromotionCode' -import Link from 'next/link' interface CheckoutBillProps { // data: CardItemCheckoutProps[] - data: Cart | null + data: CartCheckout | null } const CheckoutBill = ({ data }: CheckoutBillProps) => { diff --git a/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx b/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx index 68a8e71b5..47f5ed7a0 100644 --- a/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx +++ b/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx @@ -2,27 +2,30 @@ import React, { useEffect, useState } from 'react' import { Logo } from 'src/components/common' import CheckoutCollapse from 'src/components/common/CheckoutCollapse/CheckoutCollapse' import { useActiveCustomer } from 'src/components/hooks/auth' -import { useGetActiveOrder } from 'src/components/hooks/cart' +import useGetActiveOrderForCheckout from 'src/components/hooks/order/useGetActiveOrderForCheckout' import s from './CheckoutInfo.module.scss' import CustomerInfoForm from './components/CustomerInfoForm/CustomerInfoForm' import PaymentInfoForm from './components/PaymentInfoForm/PaymentInfoForm' import ShippingInfoForm from './components/ShippingInfoForm/ShippingInfoForm' +import ShippingMethod from './components/ShippingMethod/ShippingMethod' interface CheckoutInfoProps { onViewCart: () => void currency?: string } -enum CheckoutStep { +export enum CheckoutStep { CustomerInfo = 1, - ShippingInfo = 2, - PaymentInfo = 3, + ShippingAddressInfo = 2, + ShippingMethodInfo = 3, + PaymentInfo = 4, } const CheckoutInfo = ({ onViewCart, currency = "" }: CheckoutInfoProps) => { const [activeStep, setActiveStep] = useState(1) const [doneSteps, setDoneSteps] = useState([]) - const { order } = useGetActiveOrder() + const { order } = useGetActiveOrderForCheckout() const { customer } = useActiveCustomer() + console.log("active order checkout: ", order) useEffect(() => { if (customer) { @@ -82,7 +85,7 @@ const CheckoutInfo = ({ onViewCart, currency = "" }: CheckoutInfoProps) => { } else { return '' } - case CheckoutStep.ShippingInfo: + case CheckoutStep.ShippingAddressInfo: if (order?.shippingAddress) { const { streetLine1, city, province, postalCode, countryCode, phoneNumber } = order.shippingAddress return `${streetLine1}, ${city}, ${province}, ${postalCode}, ${countryCode}, ${phoneNumber}` @@ -100,9 +103,14 @@ const CheckoutInfo = ({ onViewCart, currency = "" }: CheckoutInfoProps) => { form: , }, { - id: CheckoutStep.ShippingInfo, - title: 'Shipping Information', - form: , + id: CheckoutStep.ShippingAddressInfo, + title: 'Shipping Address Information', + form: , + }, + { + id: CheckoutStep.ShippingMethodInfo, + title: 'Shipping Method Information', + form: , }, { id: CheckoutStep.PaymentInfo, diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx index fb62c0f24..63cde8385 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx @@ -7,15 +7,12 @@ import { LANGUAGE } from 'src/utils/language.utils' import { CustomInputCommon } from 'src/utils/type.utils' import * as Yup from 'yup' import ChekoutNotePolicy from '../ChekoutNotePolicy/ChekoutNotePolicy' -import ShippingMethod from '../ShippingMethod/ShippingMethod' import s from './ShippingInfoForm.module.scss' interface ShippingInfoFormProps { id: number activeStep: number onConfirm: (id: number) => void - currency: string - } @@ -49,7 +46,7 @@ const provinceOptions = [ }, ] -const ShippingInfoForm = ({ onConfirm, id, activeStep , currency}: ShippingInfoFormProps) => { +const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps) => { const addressRef = useRef(null) const { setOrderShippingAddress, loading } = useSetOrderShippingAddress() const { showMessageError } = useMessage() @@ -176,7 +173,6 @@ const ShippingInfoForm = ({ onConfirm, id, activeStep , currency}: ShippingInfoF onEnter={isValid ? submitForm : undefined} /> -
diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.tsx b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.tsx index c6b1ca845..911650646 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.tsx +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.tsx @@ -4,6 +4,7 @@ import React, { memo, useState } from 'react' import { useMessage } from 'src/components/contexts' import { useSetOrderShippingMethod } from 'src/components/hooks/order' import { Shipping } from 'src/components/icons' +import { CheckoutStep } from '../../CheckoutInfo' import s from './ShippingMethod.module.scss' import ShippingMethodItem from './ShippingMethodItem/ShippingMethodItem' @@ -27,13 +28,15 @@ const MOCKUP_DATA = [ ] interface Props { currency: string + onConfirm: (id: number) => void + } -const ShippingMethod = memo(({ currency }: Props) => { +const ShippingMethod = memo(({ currency, onConfirm }: Props) => { const { setOrderShippingMethod } = useSetOrderShippingMethod() const [selectedValue, setSelectedValue] = useState(MOCKUP_DATA[0]) const { showMessageError } = useMessage() - const [isShowOptions, setIsShowOptions] = useState(false) + const [isShowOptions, setIsShowOptions] = useState(true) const onChange = (id: string) => { const newValue = MOCKUP_DATA.find(item => item.id === id) @@ -47,7 +50,9 @@ const ShippingMethod = memo(({ currency }: Props) => { } const onSubmitCalBack = (isSuccess: boolean, msg?: string) => { - if (!isSuccess) { + if (isSuccess) { + onConfirm(CheckoutStep.ShippingMethodInfo) + } else { showMessageError(msg) } } diff --git a/src/components/modules/checkout/CheckoutPage/CheckoutPage.tsx b/src/components/modules/checkout/CheckoutPage/CheckoutPage.tsx index d467ed2c4..5c53d0898 100644 --- a/src/components/modules/checkout/CheckoutPage/CheckoutPage.tsx +++ b/src/components/modules/checkout/CheckoutPage/CheckoutPage.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames' import React, { useState } from 'react' import { MessageCommon } from 'src/components/common' import { useMessage } from 'src/components/contexts' -import { useGetActiveOrder } from 'src/components/hooks/cart' +import useGetActiveOrderForCheckout from 'src/components/hooks/order/useGetActiveOrderForCheckout' import IconHide from 'src/components/icons/IconHide' import { CHECKOUT_BILL_DATA } from 'src/utils/demo-data' import { CheckoutBill, CheckoutInfo } from '..' @@ -13,7 +13,7 @@ interface CheckoutPageProps { const CheckoutPage = ({ }: CheckoutPageProps) => { const { messages, removeMessage } = useMessage() const [isShow, setIsShow] = useState(false) - const { order } = useGetActiveOrder() + const { order } = useGetActiveOrderForCheckout() const onClose = () => { setIsShow(false)