diff --git a/components/checkout/CheckoutSidebarView/CheckoutSidebarView.tsx b/components/checkout/CheckoutSidebarView/CheckoutSidebarView.tsx index fb562e7af..3be28ddf7 100644 --- a/components/checkout/CheckoutSidebarView/CheckoutSidebarView.tsx +++ b/components/checkout/CheckoutSidebarView/CheckoutSidebarView.tsx @@ -1,4 +1,3 @@ -import cn from 'classnames' import Link from 'next/link' import { FC } from 'react' import CartItem from '@components/cart/CartItem' @@ -7,24 +6,34 @@ import { useUI } from '@components/ui/context' import useCart from '@framework/cart/use-cart' import usePrice from '@framework/product/use-price' import ShippingWidget from '../ShippingWidget' +import useCheckout from '@framework/checkout/use-checkout' import PaymentWidget from '../PaymentWidget' import SidebarLayout from '@components/common/SidebarLayout' import s from './CheckoutSidebarView.module.css' const CheckoutSidebarView: FC = () => { - const { setSidebarView } = useUI() - const { data } = useCart() + const { setSidebarView, closeSidebar } = useUI() + const { data: cartData } = useCart() + const { data: checkoutData, submit: onCheckout } = useCheckout(); + + async function handleSubmit(event: React.ChangeEvent) { + event.preventDefault(); + + await onCheckout(); + + closeSidebar(); + } const { price: subTotal } = usePrice( - data && { - amount: Number(data.subtotalPrice), - currencyCode: data.currency.code, + cartData && { + amount: Number(cartData.subtotalPrice), + currencyCode: cartData.currency.code, } ) const { price: total } = usePrice( - data && { - amount: Number(data.totalPrice), - currencyCode: data.currency.code, + cartData && { + amount: Number(cartData.totalPrice), + currencyCode: cartData.currency.code, } ) @@ -38,22 +47,22 @@ const CheckoutSidebarView: FC = () => { Checkout - setSidebarView('PAYMENT_VIEW')} /> - setSidebarView('SHIPPING_VIEW')} /> + setSidebarView('PAYMENT_VIEW')} /> + setSidebarView('SHIPPING_VIEW')} />
    - {data!.lineItems.map((item: any) => ( + {cartData!.lineItems.map((item: any) => ( ))}
-
+
  • Subtotal @@ -74,14 +83,11 @@ const CheckoutSidebarView: FC = () => {
{/* Once data is correcly filled */} - {/* */} -
- + ) } diff --git a/components/checkout/PaymentMethodView/PaymentMethodView.tsx b/components/checkout/PaymentMethodView/PaymentMethodView.tsx index a5f6f4b51..dfa452e58 100644 --- a/components/checkout/PaymentMethodView/PaymentMethodView.tsx +++ b/components/checkout/PaymentMethodView/PaymentMethodView.tsx @@ -1,83 +1,123 @@ import { FC } from 'react' import cn from 'classnames' + +import useAddCard from '@framework/customer/card/use-add-item' import { Button, Text } from '@components/ui' import { useUI } from '@components/ui/context' -import s from './PaymentMethodView.module.css' import SidebarLayout from '@components/common/SidebarLayout' +import s from './PaymentMethodView.module.css' + +interface Form extends HTMLFormElement { + cardHolder: HTMLInputElement + cardNumber: HTMLInputElement + cardExpireDate: HTMLInputElement + cardCvc: HTMLInputElement + firstName: HTMLInputElement + lastName: HTMLInputElement + company: HTMLInputElement + streetNumber: HTMLInputElement + zipCode: HTMLInputElement + city: HTMLInputElement + country: HTMLSelectElement +} + const PaymentMethodView: FC = () => { const { setSidebarView } = useUI() + const addCard = useAddCard() + + async function handleSubmit(event: React.ChangeEvent
) { + event.preventDefault(); + + await addCard({ + cardHolder: event.target.cardHolder.value, + cardNumber: event.target.cardNumber.value, + cardExpireDate: event.target.cardExpireDate.value, + cardCvc: event.target.cardCvc.value, + firstName: event.target.firstName.value, + lastName: event.target.lastName.value, + company: event.target.company.value, + streetNumber: event.target.streetNumber.value, + zipCode: event.target.zipCode.value, + city: event.target.city.value, + country: event.target.country.value + }); + + setSidebarView('CHECKOUT_VIEW') + } return ( - setSidebarView('CHECKOUT_VIEW')}> -
- Payment Method -
-
- - -
-
-
- - + + setSidebarView('CHECKOUT_VIEW')}> +
+ Payment Method +
+
+ +
-
- - +
+
+ + +
+
+ + +
+
+ + +
-
- - +
+
+
+ + +
+
+ + +
-
-
-
-
- - +
+ +
-
- - +
+ +
-
-
- - -
-
- - -
-
- - -
-
-
- - +
+ +
-
- - +
+
+ + +
+
+ + +
+
+
+ +
-
-
- -
-
-
- -
- +
+ +
+ + ) } diff --git a/components/checkout/PaymentWidget/PaymentWidget.tsx b/components/checkout/PaymentWidget/PaymentWidget.tsx index e1892934e..09afe4158 100644 --- a/components/checkout/PaymentWidget/PaymentWidget.tsx +++ b/components/checkout/PaymentWidget/PaymentWidget.tsx @@ -1,14 +1,15 @@ import { FC } from 'react' import s from './PaymentWidget.module.css' -import { ChevronRight, CreditCard } from '@components/icons' +import { ChevronRight, CreditCard, Check } from '@components/icons' interface ComponentProps { - onClick?: () => any + onClick?: () => any; + isValid?: boolean; } -const PaymentWidget: FC = ({ onClick }) => { - /* Shipping Address - Only available with checkout set to true - +const PaymentWidget: FC = ({ onClick, isValid }) => { + /* Shipping Address + Only available with checkout set to true - This means that the provider does offer checkout functionality. */ return (
@@ -20,7 +21,7 @@ const PaymentWidget: FC = ({ onClick }) => { {/* VISA #### #### #### 2345 */}
- + {isValid ? : }
) diff --git a/components/checkout/ShippingView/ShippingView.tsx b/components/checkout/ShippingView/ShippingView.tsx index 1d03a2aac..696172b61 100644 --- a/components/checkout/ShippingView/ShippingView.tsx +++ b/components/checkout/ShippingView/ShippingView.tsx @@ -1,77 +1,115 @@ import { FC } from 'react' import cn from 'classnames' -import s from './ShippingView.module.css' + import Button from '@components/ui/Button' import { useUI } from '@components/ui/context' import SidebarLayout from '@components/common/SidebarLayout' +import useAddAddress from '@framework/customer/address/use-add-item' + +import s from './ShippingView.module.css' + +interface Form extends HTMLFormElement { + cardHolder: HTMLInputElement + cardNumber: HTMLInputElement + cardExpireDate: HTMLInputElement + cardCvc: HTMLInputElement + firstName: HTMLInputElement + lastName: HTMLInputElement + company: HTMLInputElement + streetNumber: HTMLInputElement + zipCode: HTMLInputElement + city: HTMLInputElement + country: HTMLSelectElement +} const PaymentMethodView: FC = () => { const { setSidebarView } = useUI() + const addAddress = useAddAddress() + + async function handleSubmit(event: React.ChangeEvent
) { + event.preventDefault(); + + await addAddress({ + type: event.target.type.value, + firstName: event.target.firstName.value, + lastName: event.target.lastName.value, + company: event.target.company.value, + streetNumber: event.target.streetNumber.value, + apartments: event.target.streetNumber.value, + zipCode: event.target.zipCode.value, + city: event.target.city.value, + country: event.target.country.value + }); + + setSidebarView('CHECKOUT_VIEW') + } return ( - setSidebarView('CHECKOUT_VIEW')}> -
-

- Shipping -

-
-
- - Same as billing address -
-
- - - Use a different shipping address - -
-
-
-
- - + + setSidebarView('CHECKOUT_VIEW')}> +
+

+ Shipping +

+
+
+ + Same as billing address
-
- - +
+ + + Use a different shipping address +
-
-
- - -
-
- - -
-
- - -
-
-
- - +
+
+
+ + +
+
+ + +
-
- - +
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ +
-
-
- -
-
-
- -
- +
+ +
+ + ) } diff --git a/components/checkout/ShippingWidget/ShippingWidget.tsx b/components/checkout/ShippingWidget/ShippingWidget.tsx index b072178b0..b7684a1b1 100644 --- a/components/checkout/ShippingWidget/ShippingWidget.tsx +++ b/components/checkout/ShippingWidget/ShippingWidget.tsx @@ -1,15 +1,16 @@ import { FC } from 'react' import s from './ShippingWidget.module.css' -import { ChevronRight, MapPin } from '@components/icons' +import { ChevronRight, MapPin, Check } from '@components/icons' import cn from 'classnames' interface ComponentProps { onClick?: () => any + isValid?: boolean; } -const ShippingWidget: FC = ({ onClick }) => { - /* Shipping Address - Only available with checkout set to true - +const ShippingWidget: FC = ({ onClick, isValid }) => { + /* Shipping Address + Only available with checkout set to true - This means that the provider does offer checkout functionality. */ return (
@@ -24,7 +25,7 @@ const ShippingWidget: FC = ({ onClick }) => { */}
- + {isValid ? : }
) diff --git a/framework/commerce/api/endpoints/checkout.ts b/framework/commerce/api/endpoints/checkout.ts index b39239a6a..09ebff621 100644 --- a/framework/commerce/api/endpoints/checkout.ts +++ b/framework/commerce/api/endpoints/checkout.ts @@ -1,25 +1,39 @@ import type { CheckoutSchema } from '../../types/checkout' +import type { GetAPISchema } from '..' + import { CommerceAPIError } from '../utils/errors' import isAllowedOperation from '../utils/is-allowed-operation' -import type { GetAPISchema } from '..' const checkoutEndpoint: GetAPISchema< any, CheckoutSchema >['endpoint']['handler'] = async (ctx) => { - const { req, res, handlers } = ctx + const { req, res, handlers, config } = ctx if ( !isAllowedOperation(req, res, { - GET: handlers['checkout'], + GET: handlers['getCheckout'], + POST: handlers['submitCheckout'], }) ) { return } + const { cookies } = req + const cartId = cookies[config.cartCookie] + try { - const body = null - return await handlers['checkout']({ ...ctx, body }) + // Create checkout + if (req.method === 'GET') { + const body = { ...req.body, cartId } + return await handlers['getCheckout']({ ...ctx, body }) + } + + // Create checkout + if (req.method === 'POST') { + const body = { ...req.body, cartId } + return await handlers['submitCheckout']({ ...ctx, body }) + } } catch (error) { console.error(error) diff --git a/framework/commerce/api/endpoints/customer/address.ts b/framework/commerce/api/endpoints/customer/address.ts new file mode 100644 index 000000000..d5ede697a --- /dev/null +++ b/framework/commerce/api/endpoints/customer/address.ts @@ -0,0 +1,65 @@ +import type { CustomerAddressSchema } from '../../../types/customer/address' +import type { GetAPISchema } from '../..' + +import { CommerceAPIError } from '../../utils/errors' +import isAllowedOperation from '../../utils/is-allowed-operation' + +const customerShippingEndpoint: GetAPISchema< + any, + CustomerAddressSchema +>['endpoint']['handler'] = async (ctx) => { + const { req, res, handlers, config } = ctx + + if ( + !isAllowedOperation(req, res, { + GET: handlers['getAddresses'], + POST: handlers['addItem'], + PUT: handlers['updateItem'], + DELETE: handlers['removeItem'], + }) + ) { + return + } + + const { cookies } = req + + // Cart id might be usefull for anonymous shopping + const cartId = cookies[config.cartCookie] + + try { + // Return customer addresses + if (req.method === 'GET') { + const body = { cartId } + return await handlers['getAddresses']({ ...ctx, body }) + } + + // Create or add an item to customer addresses list + if (req.method === 'POST') { + const body = { ...req.body, cartId } + return await handlers['addItem']({ ...ctx, body }) + } + + // Update item in customer addresses list + if (req.method === 'PUT') { + const body = { ...req.body, cartId } + return await handlers['updateItem']({ ...ctx, body }) + } + + // Remove an item from customer addresses list + if (req.method === 'DELETE') { + const body = { ...req.body, cartId } + return await handlers['removeItem']({ ...ctx, body }) + } + } catch (error) { + console.error(error) + + const message = + error instanceof CommerceAPIError + ? 'An unexpected error ocurred with the Commerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +export default customerShippingEndpoint diff --git a/framework/commerce/api/endpoints/customer/card.ts b/framework/commerce/api/endpoints/customer/card.ts new file mode 100644 index 000000000..ad268cbb9 --- /dev/null +++ b/framework/commerce/api/endpoints/customer/card.ts @@ -0,0 +1,65 @@ +import type { CustomerCardSchema } from '../../../types/customer/card' +import type { GetAPISchema } from '../..' + +import { CommerceAPIError } from '../../utils/errors' +import isAllowedOperation from '../../utils/is-allowed-operation' + +const customerCardEndpoint: GetAPISchema< + any, + CustomerCardSchema +>['endpoint']['handler'] = async (ctx) => { + const { req, res, handlers, config } = ctx + + if ( + !isAllowedOperation(req, res, { + GET: handlers['getCards'], + POST: handlers['addItem'], + PUT: handlers['updateItem'], + DELETE: handlers['removeItem'], + }) + ) { + return + } + + const { cookies } = req + + // Cart id might be usefull for anonymous shopping + const cartId = cookies[config.cartCookie] + + try { + // Create or add a card + if (req.method === 'GET') { + const body = { ...req.body } + return await handlers['getCards']({ ...ctx, body }) + } + + // Create or add an item to customer cards + if (req.method === 'POST') { + const body = { ...req.body, cartId } + return await handlers['addItem']({ ...ctx, body }) + } + + // Update item in customer cards + if (req.method === 'PUT') { + const body = { ...req.body, cartId } + return await handlers['updateItem']({ ...ctx, body }) + } + + // Remove an item from customer cards + if (req.method === 'DELETE') { + const body = { ...req.body, cartId } + return await handlers['removeItem']({ ...ctx, body }) + } + } catch (error) { + console.error(error) + + const message = + error instanceof CommerceAPIError + ? 'An unexpected error ocurred with the Commerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +export default customerCardEndpoint diff --git a/framework/commerce/api/endpoints/customer.ts b/framework/commerce/api/endpoints/customer/index.ts similarity index 75% rename from framework/commerce/api/endpoints/customer.ts rename to framework/commerce/api/endpoints/customer/index.ts index 6372c494f..eb2a048b7 100644 --- a/framework/commerce/api/endpoints/customer.ts +++ b/framework/commerce/api/endpoints/customer/index.ts @@ -1,7 +1,8 @@ -import type { CustomerSchema } from '../../types/customer' -import { CommerceAPIError } from '../utils/errors' -import isAllowedOperation from '../utils/is-allowed-operation' -import type { GetAPISchema } from '..' +import type { CustomerSchema } from '../../../types/customer' +import type { GetAPISchema } from '../..' + +import { CommerceAPIError } from '../../utils/errors' +import isAllowedOperation from '../../utils/is-allowed-operation' const customerEndpoint: GetAPISchema< any, diff --git a/framework/commerce/api/index.ts b/framework/commerce/api/index.ts index 32fe8cf80..716c11ed5 100644 --- a/framework/commerce/api/index.ts +++ b/framework/commerce/api/index.ts @@ -9,6 +9,8 @@ import type { SignupSchema } from '../types/signup' import type { ProductsSchema } from '../types/product' import type { WishlistSchema } from '../types/wishlist' import type { CheckoutSchema } from '../types/checkout' +import type { CustomerCardSchema } from '../types/customer/card' +import type { CustomerAddressSchema } from '../types/customer/address' import { defaultOperations, OPERATIONS, @@ -25,6 +27,8 @@ export type APISchemas = | ProductsSchema | WishlistSchema | CheckoutSchema + | CustomerCardSchema + | CustomerAddressSchema export type GetAPISchema< C extends CommerceAPI, diff --git a/framework/commerce/checkout/use-checkout.ts b/framework/commerce/checkout/use-checkout.ts new file mode 100644 index 000000000..0fe74cb28 --- /dev/null +++ b/framework/commerce/checkout/use-checkout.ts @@ -0,0 +1,34 @@ +import type { SWRHook, HookFetcherFn } from '../utils/types' +import type { GetCheckoutHook } from '../types/checkout' + +import Cookies from 'js-cookie' + +import { useHook, useSWRHook } from '../utils/use-hook' +import { Provider, useCommerce } from '..' + +export type UseCheckout< + H extends SWRHook> = SWRHook +> = ReturnType + +export const fetcher: HookFetcherFn = async ({ + options, + input: { cartId }, + fetch, +}) => { + return cartId ? await fetch(options) : null +} + +const fn = (provider: Provider) => provider.checkout?.useCheckout! + +const useCheckout: UseCheckout = (input) => { + const hook = useHook(fn) + const { cartCookie } = useCommerce() + const fetcherFn = hook.fetcher ?? fetcher + const wrapper: typeof fetcher = (context) => { + context.input.cartId = Cookies.get(cartCookie) + return fetcherFn(context) + } + return useSWRHook({ ...hook, fetcher: wrapper })(input) +} + +export default useCheckout diff --git a/framework/commerce/checkout/use-submit-checkout.tsx b/framework/commerce/checkout/use-submit-checkout.tsx new file mode 100644 index 000000000..da609b906 --- /dev/null +++ b/framework/commerce/checkout/use-submit-checkout.tsx @@ -0,0 +1,21 @@ +import type { HookFetcherFn, MutationHook } from '../utils/types' +import type { SubmitCheckoutHook } from '../types/checkout' +import type { Provider } from '..' + +import { useHook, useMutationHook } from '../utils/use-hook' +import { mutationFetcher } from '../utils/default-fetcher' + +export type UseSubmitCheckout< + H extends MutationHook> = MutationHook +> = ReturnType + +export const fetcher: HookFetcherFn = mutationFetcher + +const fn = (provider: Provider) => provider.checkout?.useSubmitCheckout! + +const useSubmitCheckout: UseSubmitCheckout = (...args) => { + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(...args) +} + +export default useSubmitCheckout diff --git a/framework/commerce/customer/address/use-add-item.tsx b/framework/commerce/customer/address/use-add-item.tsx new file mode 100644 index 000000000..94c45142e --- /dev/null +++ b/framework/commerce/customer/address/use-add-item.tsx @@ -0,0 +1,21 @@ +import type { HookFetcherFn, MutationHook } from '../../utils/types' +import type { AddItemHook } from '../../types/customer/address' +import type { Provider } from '../..' + +import { useHook, useMutationHook } from '../../utils/use-hook' +import { mutationFetcher } from '../../utils/default-fetcher' + +export type UseAddItem< + H extends MutationHook> = MutationHook +> = ReturnType + +export const fetcher: HookFetcherFn = mutationFetcher + +const fn = (provider: Provider) => provider.customer?.address?.useAddItem! + +const useAddItem: UseAddItem = (...args) => { + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(...args) +} + +export default useAddItem diff --git a/framework/commerce/customer/address/use-addresses.tsx b/framework/commerce/customer/address/use-addresses.tsx new file mode 100644 index 000000000..f9b0dca36 --- /dev/null +++ b/framework/commerce/customer/address/use-addresses.tsx @@ -0,0 +1,34 @@ +import type { SWRHook, HookFetcherFn } from '../../utils/types' +import type { GetAddressesHook } from '../../types/customer/address' + +import Cookies from 'js-cookie' + +import { useHook, useSWRHook } from '../../utils/use-hook' +import { Provider, useCommerce } from '../..' + +export type UseAddresses< + H extends SWRHook> = SWRHook +> = ReturnType + +export const fetcher: HookFetcherFn = async ({ + options, + input: { cartId }, + fetch, +}) => { + return cartId ? await fetch(options) : null +} + +const fn = (provider: Provider) => provider.customer?.address.useAddresses! + +const useAddresses: UseAddresses = (input) => { + const hook = useHook(fn) + const { cartCookie } = useCommerce() + const fetcherFn = hook.fetcher ?? fetcher + const wrapper: typeof fetcher = (context) => { + context.input.cartId = Cookies.get(cartCookie) + return fetcherFn(context) + } + return useSWRHook({ ...hook, fetcher: wrapper })(input) +} + +export default useAddresses diff --git a/framework/commerce/customer/address/use-remove-item.tsx b/framework/commerce/customer/address/use-remove-item.tsx new file mode 100644 index 000000000..820a65dad --- /dev/null +++ b/framework/commerce/customer/address/use-remove-item.tsx @@ -0,0 +1,21 @@ +import type { HookFetcherFn, MutationHook } from '../../utils/types' +import type { RemoveItemHook } from '../../types/customer/address' +import type { Provider } from '../..' + +import { useHook, useMutationHook } from '../../utils/use-hook' +import { mutationFetcher } from '../../utils/default-fetcher' + +export type UseRemoveItem< + H extends MutationHook> = MutationHook +> = ReturnType + +export const fetcher: HookFetcherFn = mutationFetcher + +const fn = (provider: Provider) => provider.customer?.address?.useRemoveItem! + +const useRemoveItem: UseRemoveItem = (input) => { + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(input) +} + +export default useRemoveItem diff --git a/framework/commerce/customer/address/use-update-item.tsx b/framework/commerce/customer/address/use-update-item.tsx new file mode 100644 index 000000000..e33a61a37 --- /dev/null +++ b/framework/commerce/customer/address/use-update-item.tsx @@ -0,0 +1,21 @@ +import type { HookFetcherFn, MutationHook } from '../../utils/types' +import type { UpdateItemHook } from '../../types/customer/address' +import type { Provider } from '../..' + +import { useHook, useMutationHook } from '../../utils/use-hook' +import { mutationFetcher } from '../../utils/default-fetcher' + +export type UseUpdateItem< + H extends MutationHook> = MutationHook +> = ReturnType + +export const fetcher: HookFetcherFn = mutationFetcher + +const fn = (provider: Provider) => provider?.customer?.address?.useUpdateItem! + +const useUpdateItem: UseUpdateItem = (input) => { + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(input) +} + +export default useUpdateItem diff --git a/framework/commerce/customer/card/use-add-item.tsx b/framework/commerce/customer/card/use-add-item.tsx new file mode 100644 index 000000000..7b4ffdb17 --- /dev/null +++ b/framework/commerce/customer/card/use-add-item.tsx @@ -0,0 +1,21 @@ +import type { HookFetcherFn, MutationHook } from '../../utils/types' +import type { AddItemHook } from '../../types/customer/card' +import type { Provider } from '../..' + +import { useHook, useMutationHook } from '../../utils/use-hook' +import { mutationFetcher } from '../../utils/default-fetcher' + +export type UseAddItem< + H extends MutationHook> = MutationHook +> = ReturnType + +export const fetcher: HookFetcherFn = mutationFetcher + +const fn = (provider: Provider) => provider.customer?.card?.useAddItem! + +const useAddItem: UseAddItem = (...args) => { + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(...args) +} + +export default useAddItem diff --git a/framework/commerce/customer/card/use-cards.tsx b/framework/commerce/customer/card/use-cards.tsx new file mode 100644 index 000000000..5a37cf2c6 --- /dev/null +++ b/framework/commerce/customer/card/use-cards.tsx @@ -0,0 +1,34 @@ +import type { SWRHook, HookFetcherFn } from '../../utils/types' +import type { GetCardsHook } from '../../types/customer/card' + +import Cookies from 'js-cookie' + +import { useHook, useSWRHook } from '../../utils/use-hook' +import { Provider, useCommerce } from '../..' + +export type UseCards< + H extends SWRHook> = SWRHook +> = ReturnType + +export const fetcher: HookFetcherFn = async ({ + options, + input: { cartId }, + fetch, +}) => { + return cartId ? await fetch(options) : null +} + +const fn = (provider: Provider) => provider.customer?.card.useCards! + +const useCards: UseCards = (input) => { + const hook = useHook(fn) + const { cartCookie } = useCommerce() + const fetcherFn = hook.fetcher ?? fetcher + const wrapper: typeof fetcher = (context) => { + context.input.cartId = Cookies.get(cartCookie) + return fetcherFn(context) + } + return useSWRHook({ ...hook, fetcher: wrapper })(input) +} + +export default useCards diff --git a/framework/commerce/customer/card/use-remove-item.tsx b/framework/commerce/customer/card/use-remove-item.tsx new file mode 100644 index 000000000..1d85fa636 --- /dev/null +++ b/framework/commerce/customer/card/use-remove-item.tsx @@ -0,0 +1,21 @@ +import type { HookFetcherFn, MutationHook } from '../../utils/types' +import type { RemoveItemHook } from '../../types/customer/card' +import type { Provider } from '../..' + +import { useHook, useMutationHook } from '../../utils/use-hook' +import { mutationFetcher } from '../../utils/default-fetcher' + +export type UseRemoveItem< + H extends MutationHook> = MutationHook +> = ReturnType + +export const fetcher: HookFetcherFn = mutationFetcher + +const fn = (provider: Provider) => provider.customer?.card?.useRemoveItem! + +const useRemoveItem: UseRemoveItem = (input) => { + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(input) +} + +export default useRemoveItem diff --git a/framework/commerce/customer/card/use-update-item.tsx b/framework/commerce/customer/card/use-update-item.tsx new file mode 100644 index 000000000..cd8837d7f --- /dev/null +++ b/framework/commerce/customer/card/use-update-item.tsx @@ -0,0 +1,21 @@ +import type { HookFetcherFn, MutationHook } from '../../utils/types' +import type { UpdateItemHook } from '../../types/customer/card' +import type { Provider } from '../..' + +import { useHook, useMutationHook } from '../../utils/use-hook' +import { mutationFetcher } from '../../utils/default-fetcher' + +export type UseUpdateItem< + H extends MutationHook> = MutationHook +> = ReturnType + +export const fetcher: HookFetcherFn = mutationFetcher + +const fn = (provider: Provider) => provider?.customer?.card?.useUpdateItem! + +const useUpdateItem: UseUpdateItem = (input) => { + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(input) +} + +export default useUpdateItem diff --git a/framework/commerce/index.tsx b/framework/commerce/index.tsx index dd740809f..46a4a2081 100644 --- a/framework/commerce/index.tsx +++ b/framework/commerce/index.tsx @@ -15,6 +15,7 @@ import type { Signup, Login, Logout, + Checkout, } from '@commerce/types' import type { Fetcher, SWRHook, MutationHook } from './utils/types' @@ -29,6 +30,10 @@ export type Provider = CommerceConfig & { useUpdateItem?: MutationHook useRemoveItem?: MutationHook } + checkout?: { + useCheckout?: SWRHook + useSubmitCheckout?: MutationHook + } wishlist?: { useWishlist?: SWRHook useAddItem?: MutationHook @@ -36,6 +41,18 @@ export type Provider = CommerceConfig & { } customer?: { useCustomer?: SWRHook + card: { + useCards?: SWRHook + useAddItem?: MutationHook + useUpdateItem?: MutationHook + useRemoveItem?: MutationHook + } + address: { + useAddresses?: SWRHook + useAddItem?: MutationHook + useUpdateItem?: MutationHook + useRemoveItem?: MutationHook + } } products?: { useSearch?: SWRHook diff --git a/framework/commerce/types/checkout.ts b/framework/commerce/types/checkout.ts index 9e3c7ecfa..27f553207 100644 --- a/framework/commerce/types/checkout.ts +++ b/framework/commerce/types/checkout.ts @@ -1,10 +1,57 @@ -export type CheckoutSchema = { +import type { UseSubmitCheckout } from '../checkout/use-submit-checkout' +import type { Address } from './customer/address' +import type { Card } from './customer/card' + +// Index +export type Checkout = unknown; + +export type CheckoutTypes = { + card?: Card + address?: Address + checkout?: Checkout + hasPayment?: boolean + hasShipping?: boolean +} + +export type SubmitCheckoutHook = { + data: T + input?: T + fetcherInput: T + body: { item: T } + actionInput: T +} + +export type GetCheckoutHook = { + data: T['checkout'] | null + input: {} + fetcherInput: { cartId?: string } + swrState: { isEmpty: boolean } + mutations: { submit: UseSubmitCheckout } +} + +export type CheckoutHooks = { + submitCheckout: SubmitCheckoutHook + getCheckout: GetCheckoutHook +} + +export type GetCheckoutHandler = + GetCheckoutHook & { + body: { cartId: string } + } + +export type SubmitCheckoutHandler = + SubmitCheckoutHook & { + body: { cartId: string } + } + +export type CheckoutHandlers = { + getCheckout: GetCheckoutHandler + submitCheckout: SubmitCheckoutHandler +} + +export type CheckoutSchema = { endpoint: { options: {} - handlers: { - checkout: { - data: null - } - } + handlers: CheckoutHandlers } } diff --git a/framework/commerce/types/customer/address.ts b/framework/commerce/types/customer/address.ts new file mode 100644 index 000000000..5b6ca4b49 --- /dev/null +++ b/framework/commerce/types/customer/address.ts @@ -0,0 +1,93 @@ +export interface Address { + id: string; + mask: string; +} + +export interface AddressFields { + type: string; + firstName: string; + lastName: string; + company: string; + streetNumber: string; + apartments: string; + zipCode: string; + city: string; + country: string; +} + +export type CustomerAddressTypes = { + address?: Address; + fields: AddressFields; +} + +export type GetAddressesHook = { + data: T['address'] | null + input: {} + fetcherInput: { cartId?: string } + swrState: { isEmpty: boolean } +} + +export type AddItemHook = { + data: T['address'] + input?: T['fields'] + fetcherInput: T['fields'] + body: { item: T['fields'] } + actionInput: T['fields'] +} + +export type UpdateItemHook = { + data: T['address'] | null + input: { item?: T['fields']; wait?: number } + fetcherInput: { itemId: string; item: T['fields'] } + body: { itemId: string; item: T['fields'] } + actionInput: T['fields'] & { id: string } +} + +export type RemoveItemHook = { + data: T['address'] | null + input: { item?: T['fields'] } + fetcherInput: { itemId: string } + body: { itemId: string } + actionInput: { id: string } +} + +export type CustomerAddressHooks = { + getAddresses: GetAddressesHook + addItem: AddItemHook + updateItem: UpdateItemHook + removeItem: RemoveItemHook +} + +export type AddresssHandler = GetAddressesHook & { + body: { cartId?: string } +} + +export type AddItemHandler = AddItemHook & { + body: { cartId: string } +} + +export type UpdateItemHandler = + UpdateItemHook & { + data: T['address'] + body: { cartId: string } + } + +export type RemoveItemHandler = + RemoveItemHook & { + body: { cartId: string } + } + + +export type CustomerAddressHandlers = { + getAddresses: GetAddressesHook + addItem: AddItemHandler + updateItem: UpdateItemHandler + removeItem: RemoveItemHandler +} + +export type CustomerAddressSchema = { + endpoint: { + options: {} + handlers: CustomerAddressHandlers + } +} diff --git a/framework/commerce/types/customer/card.ts b/framework/commerce/types/customer/card.ts new file mode 100644 index 000000000..a8731411f --- /dev/null +++ b/framework/commerce/types/customer/card.ts @@ -0,0 +1,96 @@ +export interface Card { + id: string; + mask: string; + provider: string; +} + +export interface CardFields { + cardHolder: string; + cardNumber: string; + cardExpireDate: string; + cardCvc: string; + firstName: string; + lastName: string; + company: string; + streetNumber: string; + zipCode: string; + city: string; + country: string; +} + +export type CustomerCardTypes = { + card?: Card; + fields: CardFields; +} + +export type GetCardsHook = { + data: T['card'] | null + input: {} + fetcherInput: { cartId?: string } + swrState: { isEmpty: boolean } +} + +export type AddItemHook = { + data: T['card'] + input?: T['fields'] + fetcherInput: T['fields'] + body: { item: T['fields'] } + actionInput: T['fields'] +} + +export type UpdateItemHook = { + data: T['card'] | null + input: { item?: T['fields']; wait?: number } + fetcherInput: { itemId: string; item: T['fields'] } + body: { itemId: string; item: T['fields'] } + actionInput: T['fields'] & { id: string } +} + +export type RemoveItemHook = { + data: T['card'] | null + input: { item?: T['fields'] } + fetcherInput: { itemId: string } + body: { itemId: string } + actionInput: { id: string } +} + +export type CustomerCardHooks = { + getCards: GetCardsHook + addItem: AddItemHook + updateItem: UpdateItemHook + removeItem: RemoveItemHook +} + +export type CardsHandler = GetCardsHook & { + body: { cartId?: string } +} + +export type AddItemHandler = AddItemHook & { + body: { cartId: string } +} + +export type UpdateItemHandler = + UpdateItemHook & { + data: T['card'] + body: { cartId: string } + } + +export type RemoveItemHandler = + RemoveItemHook & { + body: { cartId: string } + } + + +export type CustomerCardHandlers = { + getCards: GetCardsHook + addItem: AddItemHandler + updateItem: UpdateItemHandler + removeItem: RemoveItemHandler +} + +export type CustomerCardSchema = { + endpoint: { + options: {} + handlers: CustomerCardHandlers + } +} diff --git a/framework/commerce/types/customer.ts b/framework/commerce/types/customer/index.ts similarity index 81% rename from framework/commerce/types/customer.ts rename to framework/commerce/types/customer/index.ts index ba90acdf4..7a99d8c5e 100644 --- a/framework/commerce/types/customer.ts +++ b/framework/commerce/types/customer/index.ts @@ -1,5 +1,8 @@ +export * as Card from "./card" +export * as Address from "./address" + // TODO: define this type -export type Customer = any +export type Customer = unknown export type CustomerTypes = { customer: Customer diff --git a/framework/commerce/utils/types.ts b/framework/commerce/utils/types.ts index 751cea4a5..2bca30852 100644 --- a/framework/commerce/utils/types.ts +++ b/framework/commerce/utils/types.ts @@ -87,6 +87,8 @@ export type HookSchemaBase = { export type SWRHookSchemaBase = HookSchemaBase & { // Custom state added to the response object of SWR swrState?: {} + // Instances of MutationSchemaBase that the hook returns for better DX + mutations?: Record['useHook']>> } export type MutationSchemaBase = HookSchemaBase & { @@ -102,7 +104,7 @@ export type SWRHook = { context: SWRHookContext ): HookFunction< H['input'] & { swrOptions?: SwrOptions }, - ResponseState & H['swrState'] + ResponseState & H['swrState'] & H['mutations'] > fetchOptions: HookFetcherOptions fetcher?: HookFetcherFn diff --git a/pages/api/customer/address.ts b/pages/api/customer/address.ts new file mode 100644 index 000000000..5815ea462 --- /dev/null +++ b/pages/api/customer/address.ts @@ -0,0 +1,4 @@ +import customerAddressApi from '@framework/api/endpoints/customer/address' +import commerce from '@lib/api/commerce' + +export default customerAddressApi(commerce) diff --git a/pages/api/customer/card.ts b/pages/api/customer/card.ts new file mode 100644 index 000000000..6f88b8c74 --- /dev/null +++ b/pages/api/customer/card.ts @@ -0,0 +1,4 @@ +import customerCardApi from '@framework/api/endpoints/customer/card' +import commerce from '@lib/api/commerce' + +export default customerCardApi(commerce) diff --git a/pages/api/customer.ts b/pages/api/customer/index.ts similarity index 100% rename from pages/api/customer.ts rename to pages/api/customer/index.ts