Baroshem / nuxt-medusa

🛍️ Medusa module for Nuxt
https://nuxt-medusa.vercel.app
MIT License
130 stars 10 forks source link

Add `useCart` composable to manage carts #15

Closed carpad88 closed 1 year ago

carpad88 commented 1 year ago

Is your feature request related to a problem? Please describe.

This request suggests adding a new composable function called useCart.

Implementing store management for carts would be a valuable addition to the nuxt-medusa module. Using a store, developers can ensure that data is consistent across the entire application, avoiding data duplication or inconsistency. Additionally, using a store can simplify the process of passing data between components and pages, making it easier to build complex applications.

Describe the solution you'd like

With store management for carts, developers could easily manage the state of a user's cart across the entire application, allowing them to easily track cart contents, update quantities, and manage discounts or promotions. I would like to use the composable like this:

<script setup>
const { addItem } = useCart()
</script>

<template>
  <div>
    <div v-for="product in products" :key="product.id">
      <h3>{{ product.name }}</h3>
      <p>{{ product.price }}</p>
      <button @click="addItem(product.id)">Add to Cart</button>
    </div>
  </div>
</template>

Describe alternatives you've considered

The next code block defines the useCart composable with some useful methods to manage a cart on the Medusa server:

export const useCart = (options?: any) => {
  const client = useMedusaClient()

  const createCart = async (data: {}) => {
    return client.carts.create(data)
  }

  const updateCart = async (cartId: string, data: {}) => {
    return client.carts.update(cartId, data)
  }

  const completeCart = async (cartId: string, options: {}) => {
    return client.carts.complete(cartId, options)
  }

  const createPaymentSession = async (cartId: string, options: {}) => {
    return client.carts.createPaymentSessions(cartId, options)
  }

  const updatePaymentSession = async (cartId: string, provider_id: string, data: {}) => {
    return client.carts.updatePaymentSession(cartId, provider_id, { data })
  }

  const refreshPaymentSession = async (cartId: string, provider_id: string) => {
    return client.carts.refreshPaymentSession(cartId, provider_id)
  }

  const setPaymentSession = async (cartId: string, data: {}) => {
    return client.carts.setPaymentSession(cartId, data)
  }

  const addShippingMethodToCart = async (cartId: string, data: {}) => {
    return client.carts.addShippingMethod(cartId, data)
  }

  const deletePaymentSession = async (cartId: string, provider_id: string) => {
    return client.carts.deletePaymentSession(cartId, provider_id)
  }

  return {
      createCart,
      updateCart,
      completeCart,
      createPaymentSession,
      updatePaymentSession,
      refreshPaymentSession,
      setPaymentSession,
      addShippingMethodToCart,
      deletePaymentSession
    }
}

Regarding the store, I think there are two options, the first is to use the new useState composable that comes with Nuxt 3, and the second would be to add Pinia as store management, but that would imply adding a dependency to the module.

Baroshem commented 1 year ago

Hey, thanks for rising this issue!

This sounds really intersting but at this point I thought more about handling the cart cookies (you know, the cookie that is assigned to the certain session so that the user will get the cart items when visiting the website easily) instead of wrapping the built in functionalities of the medusa js client.

What do you think about this approach? Something like https://github.com/nuxt-modules/supabase/blob/main/src/runtime/plugins/supabase.client.ts ?

carpad88 commented 1 year ago

The Supabase reference makes sense, but using that approach would require setting up other composables, like useMedusaUser and useMedusaSession.

As for the cart, if I understood well, the idea would be to put the cart_id the Medusa server returns in a cookie. In this case, maybe the useCart composable should consider an option to let the user where to save the cart_id, for instance, in local storage or a cookie.

As far as I've used Medusa, the flow has been this:

Following this flow, we could save the cart_id in a cookie rather than localstorage, but I think both options would be great.

We need to keep in mind that the cart_id is returned in the response, while the session id is already returned as a cookie by the Medusa server and saved automatically in the browser.

Baroshem commented 1 year ago

Yes, this was my idea. Basically handle the functionality you described in the these four points in one composable/server utility so that it will be easier for the users to achieve the same result.

As you have used similar pattern already for Medusa, would you be interested in developing such composable? :)

I can help you along the way.

I would just recommend to keep the naming convention useMedusa<Something> for naming composables and take inspiration from the supabase module.

In the upcoming days I think that I will be developing the composable and server utility responsible for user management :)

carpad88 commented 1 year ago

Sure, I would like to develop the composable. It should consider cases with an anonymous user and a logged user.

Since the logged user will require the useMedusaUser composable, I will start with the case where the user is not logged in. Once the user management is ready, I could implement the case for authenticated users.

Baroshem commented 1 year ago

That's awesome! Please also keep in mind the usage on the server side :)

Baroshem commented 1 year ago

@carpad88

I did some research and playing with user related composables and server utilities and I decided to just add the documentation about using it with the already implemented medusa client instead of developing these functioanlities from scratch.

Basically to fetch user data on the client you just need to call method from medusa client, while on the server you only need to forward the cookies like following:

import { serverMedusaClient } from '#medusa/server'

export default eventHandler(async (event) => {
  const client = serverMedusaClient(event)

  const { customer } = await client.auth.getSession({
    Cookie: event.node.req.headers.cookie,
  });

  return customer
})

For your case (cart functionality) maybe it will be worth to just explain in the documentation how to use it instead of developing the composables themsevles (this is a guess, I am leaving the decision to you - just giving you a headsup from what I ended up with with auth).

Let me know what do you think about it :)

carpad88 commented 1 year ago

@Baroshem

I think it would make more sense to keep it consistent and implement the cart functionality the same way as the auth, so I'll also explain in the documentation how to make it without adding composables to the module.

Following the same line, maybe it would be worth adding an example in the docs of how users can add composables as a wrapper for the Medusa client, so it is up to users which composables they need.

Baroshem commented 1 year ago

Hey @carpad88

Yes that was my idea! If you are ready, then I think you can proceed with the docs.

For the second case, I am not sure if that is actually needed. Lets give users flexibility on how they want to write their composables :)

carpad88 commented 1 year ago

Hey @Baroshem

I'm already working on the solution. I have a proposal, so I'll update the docs and send a PR so you can review it.

carpad88 commented 1 year ago

I'm closing this issue as the documentation already has an example of how to implement it.

Resolve in #18