0.4.2 • Published 1 year ago

@medusa-vue/components v0.4.2

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

Medusa Vue

Vue 3 composables and components for seamless and streamlined interaction with a Medusa.

If you're building a custom vue based storefront that consumes a medusa backend and find yourself wishing you had something nice at hands like medusa-react to streamline your data management - this might be your library!

Installation

The library uses @tanstack/vue-query under the hood.

For the core composables run:

npm install @medusa-vue/core
# or
yarn add @medusa-vue/core

For the components (WIP :construction_worker:):

npm install @medusa-vue/components
# or
yarn add @medusa-vue/components

Quick Start

In order to use the composables exposed by this library you need to register it as a plugin in your main file before mounting your app. The plugin takes a config object that needs at least a baseUrl where it can reach your server. Optionally, it allows you to pass additional props to configure both the underlying medusa-js and the vue-query client. Here's the complete interface. Refer to these and these docs, respectively to get an idea on how the parts work together.

interface MedusaVueClientProps {
  baseUrl: string;
  maxRetries?: number;
  /**
   * Authentication token
   */
  apiKey?: string;
  /**
   * PublishableApiKey identifier that defines the scope of resources
   * available within the request
   */
  publishableApiKey?: string;

  queryClientProviderProps?: VueQueryPluginOptions;
}

Plug it in:

// main.ts
import { createApp } from 'vue';
import App from './App.vue';

import { createMedusaVueClient } from '@medusa-vue/core';

const client = createMedusaVueClient({
  baseUrl: '<YOUR_SERVER_BASE_URL>',
});

const app = createApp(App);

app.use(client).mount('#app');

The hooks exposed by medusa-vue fall into two main categories: queries and mutations.

Queries

Queries simply wrap around vue-query's useQuery hook to fetch some data from your medusa server

// ./my-product-list.vue
<script setup lang="ts">
import { watch } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
import { useProducts } from '@medusa-vue/core';

const { data, error, isLoading } = useProducts();
</script>

<template>
  <ul v-for="products in data?.products">
    <li>...</li>
  </ul>
</template>

Note: If you've worked with @medusajs/medusa-react you might be used to being able to destructure the recordset returned by the server, i.e. const { products } = useProducts(). This is however not possible with vue due to the way it's reactive system works.

Mutations

Mutations wrap around vue-query's useMutation to mutate data and perform server-side effects on your medusa server. If you are not entirely familiar with this idea of "mutations", creating a cart would be a mutation because it creates a cart in your server (and database). Mutations also have to be invoked imperatively, meaning that calling for the mutation to take action, you will have to call a mutate() function returned from mutation hooks.

<script setup lang="ts">
import { useCreateCart } from '@medusa-vue/core';

const createCart = useCreateCart();
const handleClick = () => {
  createCart.mutate({}); // create an empty cart
};
</script>

<template>
  <Button isLoading="{createCart.isLoading}" onClick="{handleClick}">
    Create cart
  </Button>
</template>

The mutation hooks will return exactly what vue-query's useMutation returns. In addition, the options you pass in to the hooks will be passed along to useMutation.

Components

NOTE: This is still work in progress and new components will gradually be added!:construction_worker:

If you prefer declarative templates, @medusa-vue/components provided (almost) renderless components to use directly in your template and provide data through slot-props. This allows for extremely streamlinend and declarative templating:

<script setup lang="ts">
import { UseProducts } from '@medusa-vue/components';
</script>

<template>
  <use-products v-slot="{ data, isLoading }">
    <loading-spinner v-if="isLoading" />

    <product-list :products="data.products" />
  </use-products>
</template>

The component also allows to pass down the laoding indicating component via a slot:

<script setup lang="ts">
import { UseProducts } from '@medusa-vue/components';
</script>

<template>
  <use-products>
    <template #fallback>
      <div>Loading....</div>
    </template>

    <template v-slot="{ data, isLoading }">
      <product-list :products="data.products" />
    </template>
  </use-products>
</template>

Utilities

A set of utility functions are also exposed from the library to make your life easier when dealing with displaying money amounts

formatVariantPrice()

  • formatVariantPrice(params: FormatVariantPriceParams): string
type FormatVariantPriceParams = {
  variant: ProductVariantInfo;
  region: RegionInfo;
  includeTaxes?: boolean;
  minimumFractionDigits?: number;
  maximumFractionDigits?: number;
  locale?: string;
};

type ProductVariantInfo = Pick<ProductVariant, 'prices'>;

type RegionInfo = {
  currency_code: string;
  tax_code: string;
  tax_rate: number;
};

Given a variant and region, will return a string representing the localized amount (i.e: $19.50)

The behavior of minimumFractionDigits and maximumFractionDigits is the same as the one explained by MDN here. In fact, in order to convert the decimal amount, we use the browser's Intl.NumberFormat method.

computeVariantPrice()

  • computeVariantPrice(params: ComputeVariantPriceParams): number
type ComputeVariantPriceParams = {
  variant: ProductVariantInfo;
  region: RegionInfo;
  includeTaxes?: boolean;
};

Determines a variant's price based on the region provided. Returns a decimal number representing the amount.

formatAmount()

  • formatAmount(params: FormatAmountParams): string
type FormatAmountParams = {
  amount: number;
  region: RegionInfo;
  includeTaxes?: boolean;
  minimumFractionDigits?: number;
  maximumFractionDigits?: number;
  locale?: string;
};

Returns a localized string based on the input params representing the amount (i.e: "$10.99").

computeAmount()

  • computeAmount(params: ComputeAmountParams): number
type ComputeAmountParams = {
  amount: number;
  region: RegionInfo;
  includeTaxes?: boolean;
};

Takes an integer amount, a region, and includeTaxes boolean. Returns a decimal amount including (or excluding) taxes.

Credits

Based on and inspired by medusa-react. Keep up the good work! :beers:

0.4.2

1 year ago

0.4.1

1 year ago

0.4.0

1 year ago

0.3.0

1 year ago

0.2.0

1 year ago