mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 12:24:18 +00:00
initial provider (#2)
This commit is contained in:
committed by
GitHub
parent
5b4d1b57d4
commit
3342d9d1bb
@@ -14,6 +14,7 @@ const PROVIDERS = [
|
|||||||
'shopify',
|
'shopify',
|
||||||
'swell',
|
'swell',
|
||||||
'vendure',
|
'vendure',
|
||||||
|
'medusa',
|
||||||
]
|
]
|
||||||
|
|
||||||
function getProviderName() {
|
function getProviderName() {
|
||||||
@@ -25,6 +26,8 @@ function getProviderName() {
|
|||||||
? 'shopify'
|
? 'shopify'
|
||||||
: process.env.NEXT_PUBLIC_SWELL_STORE_ID
|
: process.env.NEXT_PUBLIC_SWELL_STORE_ID
|
||||||
? 'swell'
|
? 'swell'
|
||||||
|
: process.env.NEXT_PUBLIC_MEDUSA_STORE_URL
|
||||||
|
? 'medusa'
|
||||||
: 'local')
|
: 'local')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
63
framework/medusa/README.md
Normal file
63
framework/medusa/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://www.medusa-commerce.com">
|
||||||
|
<img alt="Medusa" src="https://user-images.githubusercontent.com/7554214/129161578-19b83dc8-fac5-4520-bd48-53cba676edd2.png" width="100" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<h1 align="center">
|
||||||
|
Medusa Provider
|
||||||
|
</h1>
|
||||||
|
<p align="center">
|
||||||
|
Medusa is an open-source headless commerce engine that enables developers to create amazing digital commerce experiences.
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/medusajs/medusa/blob/master/LICENSE">
|
||||||
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Medusa is released under the MIT license." />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
|
||||||
|
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.gg/xpCwq3Kfn8">
|
||||||
|
<img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/intent/follow?screen_name=medusajs">
|
||||||
|
<img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
You can view a working demo of the Medusa provider for Next.js Commerce at https://medusa.vercel.store/
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
You need a [Medusa](https://medusa-commerce.com/) instance, either in the cloud or self-hosted.
|
||||||
|
|
||||||
|
Clone this repo and install dependencies using `yarn` or `npm install`
|
||||||
|
|
||||||
|
Copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mv framework/medusa/.env.template .env.local
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, set the environment following variables in your `.env.local`.
|
||||||
|
|
||||||
|
- `NEXT_PUBLIC_MEDUSA_STORE_URL` must point to the URL where your Medusa instance is running.
|
||||||
|
|
||||||
|
- `NEXT_PUBLIC_MEDUSA_IMAGE_HOST` must point to your image hosting service.
|
||||||
|
|
||||||
|
```
|
||||||
|
COMMERCE_PROVIDER=medusa
|
||||||
|
NEXT_PUBLIC_MEDUSA_STORE_URL=https://medusa-demo.store
|
||||||
|
NEXT_PUBLIC_MEDUSA_IMG_HOST=medusa-public-images.s3.eu-west-1.amazonaws.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The entire customer flow is carried out using the [Storefront API](https://docs.medusa-commerce.com/api/store). This means that there is no existing, pre-built checkout flow. The checkout flow must be built using the `Storefront API`, for an example of how to do this feel free to have a look at our [Next.js](https://github.com/medusajs/gatsby-starter-medusa) starter project.
|
||||||
|
|
||||||
|
- `Medusa` does not currently support any wishlist features.
|
||||||
|
|
||||||
|
- `Medusa` does not nativly support searches. This can be implemented using plugins such as `MeiliSearch`, see [#381](https://github.com/medusajs/medusa/pull/381).
|
||||||
|
|
||||||
|
- `Medusa` does not come with any page/blog building feature. This can be implemented using `Medusa` in conjunction with a CMS such as `Contentful`. For inspiration on how to do this check out our [Contentful starter](https://github.com/medusajs/medusa-starter-contentful)
|
1
framework/medusa/api/endpoints/cart/index.ts
Normal file
1
framework/medusa/api/endpoints/cart/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/medusa/api/endpoints/catalog/index.ts
Normal file
1
framework/medusa/api/endpoints/catalog/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/medusa/api/endpoints/catalog/products.ts
Normal file
1
framework/medusa/api/endpoints/catalog/products.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/medusa/api/endpoints/checkout/index.ts
Normal file
1
framework/medusa/api/endpoints/checkout/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/medusa/api/endpoints/customer/index.ts
Normal file
1
framework/medusa/api/endpoints/customer/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/medusa/api/endpoints/login/index.ts
Normal file
1
framework/medusa/api/endpoints/login/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/medusa/api/endpoints/logout/index.ts
Normal file
1
framework/medusa/api/endpoints/logout/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/medusa/api/endpoints/signup/index.ts
Normal file
1
framework/medusa/api/endpoints/signup/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
1
framework/medusa/api/endpoints/wishlist/index.tsx
Normal file
1
framework/medusa/api/endpoints/wishlist/index.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function noopApi(...args: any[]): void {}
|
30
framework/medusa/api/index.ts
Normal file
30
framework/medusa/api/index.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { CommerceAPIConfig } from '@commerce/api'
|
||||||
|
import { CommerceAPI, getCommerceApi as commerceApi } from '@commerce/api'
|
||||||
|
import fetchApi from './utils/fetch-medusa-api'
|
||||||
|
import { MEDUSA_CART_ID_COOKIE } from '../const'
|
||||||
|
|
||||||
|
import * as operations from './operations'
|
||||||
|
|
||||||
|
export interface MedusaConfig extends CommerceAPIConfig {
|
||||||
|
fetch: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: MedusaConfig = {
|
||||||
|
commerceUrl: '',
|
||||||
|
apiToken: '',
|
||||||
|
cartCookie: MEDUSA_CART_ID_COOKIE,
|
||||||
|
customerCookie: '',
|
||||||
|
cartCookieMaxAge: 60 * 60 * 24 * 30,
|
||||||
|
fetch: fetchApi,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const provider = { config, operations }
|
||||||
|
|
||||||
|
export type Provider = typeof provider
|
||||||
|
export type MedusaAPI<P extends Provider = Provider> = CommerceAPI<P | any>
|
||||||
|
|
||||||
|
export function getCommerceApi<P extends Provider>(
|
||||||
|
customProvider: P = provider as any
|
||||||
|
): MedusaAPI<P> {
|
||||||
|
return commerceApi(customProvider as any)
|
||||||
|
}
|
19
framework/medusa/api/operations/get-all-pages.ts
Normal file
19
framework/medusa/api/operations/get-all-pages.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export type Page = { url: string }
|
||||||
|
export type GetAllPagesResult = { pages: Page[] }
|
||||||
|
import type { MedusaConfig } from '..'
|
||||||
|
|
||||||
|
export default function getAllPagesOperation() {
|
||||||
|
function getAllPages({
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
}: {
|
||||||
|
url?: string
|
||||||
|
config?: Partial<MedusaConfig>
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetAllPagesResult> {
|
||||||
|
return Promise.resolve({
|
||||||
|
pages: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return getAllPages
|
||||||
|
}
|
35
framework/medusa/api/operations/get-all-product-paths.ts
Normal file
35
framework/medusa/api/operations/get-all-product-paths.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { OperationContext } from '@commerce/api/operations'
|
||||||
|
import { Product } from '@commerce/types/product'
|
||||||
|
import { Product as MedusaProduct } from '@medusajs/medusa-js/lib/types'
|
||||||
|
import { MedusaConfig } from '..'
|
||||||
|
|
||||||
|
export type GetAllProductPathsResult = {
|
||||||
|
products: Array<{ path: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function getAllProductPathsOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<any>) {
|
||||||
|
async function getAllProductsPaths({
|
||||||
|
config: cfg,
|
||||||
|
}: {
|
||||||
|
config?: Partial<MedusaConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<{ products: Product[] | any[] }> {
|
||||||
|
const config = commerce.getConfig(cfg)
|
||||||
|
|
||||||
|
const results = await config.fetch('products', 'list', {})
|
||||||
|
|
||||||
|
const productHandles = results.data?.products
|
||||||
|
? results.data.products.map(({ handle }: MedusaProduct) => ({
|
||||||
|
path: `/${handle}`,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
|
||||||
|
return {
|
||||||
|
products: productHandles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAllProductsPaths
|
||||||
|
}
|
41
framework/medusa/api/operations/get-all-products.ts
Normal file
41
framework/medusa/api/operations/get-all-products.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Product } from '@commerce/types/product'
|
||||||
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
|
import type { MedusaConfig } from '../'
|
||||||
|
import { normalizeProduct } from '@framework/utils/normalizers/normalize-products'
|
||||||
|
import { Product as MedusaProduct } from '@medusajs/medusa-js/lib/types'
|
||||||
|
|
||||||
|
export type ProductVariables = { first?: number }
|
||||||
|
|
||||||
|
export default function getAllProductsOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<any>) {
|
||||||
|
async function getAllProducts({
|
||||||
|
config: cfg,
|
||||||
|
variables,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: ProductVariables
|
||||||
|
config?: Partial<MedusaConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<{ products: Product[] | any[] }> {
|
||||||
|
const config = commerce.getConfig(cfg)
|
||||||
|
const query = variables?.first && { limit: variables.first }
|
||||||
|
|
||||||
|
const results = await config.fetch(
|
||||||
|
'products',
|
||||||
|
'list',
|
||||||
|
query ? { query: query } : {}
|
||||||
|
)
|
||||||
|
|
||||||
|
const products: Product[] = results.data?.products
|
||||||
|
? results.data.products.map((product: MedusaProduct) =>
|
||||||
|
normalizeProduct(product)
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
|
||||||
|
return {
|
||||||
|
products,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getAllProducts
|
||||||
|
}
|
6
framework/medusa/api/operations/get-customer-wishlist.ts
Normal file
6
framework/medusa/api/operations/get-customer-wishlist.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default function getCustomerWishlistOperation() {
|
||||||
|
function getCustomerWishlist(): any {
|
||||||
|
return { wishlist: {} }
|
||||||
|
}
|
||||||
|
return getCustomerWishlist
|
||||||
|
}
|
13
framework/medusa/api/operations/get-page.ts
Normal file
13
framework/medusa/api/operations/get-page.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export type Page = any
|
||||||
|
export type GetPageResult = { page?: Page }
|
||||||
|
|
||||||
|
export type PageVariables = {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function getPageOperation() {
|
||||||
|
function getPage(): Promise<GetPageResult> {
|
||||||
|
return Promise.resolve({})
|
||||||
|
}
|
||||||
|
return getPage
|
||||||
|
}
|
46
framework/medusa/api/operations/get-product.ts
Normal file
46
framework/medusa/api/operations/get-product.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Product } from '@commerce/types/product'
|
||||||
|
import { GetProductOperation } from '@commerce/types/product'
|
||||||
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
|
import { Product as MedusaProduct } from '@medusajs/medusa-js/lib/types'
|
||||||
|
import { normalizeProduct } from '@framework/utils/normalizers/normalize-products'
|
||||||
|
import { MedusaConfig } from '..'
|
||||||
|
|
||||||
|
export default function getProductOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<any>) {
|
||||||
|
async function getProduct<T extends GetProductOperation>({
|
||||||
|
variables,
|
||||||
|
config: cfg,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: T['variables']
|
||||||
|
config?: Partial<MedusaConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<Product | {} | any> {
|
||||||
|
const config = commerce.getConfig(cfg)
|
||||||
|
|
||||||
|
const response = await config.fetch('products', 'list', {})
|
||||||
|
|
||||||
|
if (response.data?.products) {
|
||||||
|
const products: MedusaProduct[] = response.data.products
|
||||||
|
const product = products
|
||||||
|
? products.find(({ handle }) => handle === variables!.slug)
|
||||||
|
: null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commerce only provides us with the slug/path for finding
|
||||||
|
* the specified product. We do not have a endpoint for retrieving
|
||||||
|
* products using handles. Perhaps we should ask Vercel if we can
|
||||||
|
* change this so the variables also expose the product_id, which
|
||||||
|
* we can use for retrieving products.
|
||||||
|
*/
|
||||||
|
if (product) {
|
||||||
|
return {
|
||||||
|
product: normalizeProduct(product),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getProduct
|
||||||
|
}
|
33
framework/medusa/api/operations/get-site-info.ts
Normal file
33
framework/medusa/api/operations/get-site-info.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { OperationContext } from '@commerce/api/operations'
|
||||||
|
import { Category } from '@commerce/types/site'
|
||||||
|
import { MedusaConfig } from '..'
|
||||||
|
|
||||||
|
export type GetSiteInfoResult<
|
||||||
|
T extends { categories: any[]; brands: any[] } = {
|
||||||
|
categories: Category[]
|
||||||
|
brands: any[]
|
||||||
|
}
|
||||||
|
> = T
|
||||||
|
|
||||||
|
export default function getSiteInfoOperation({}: OperationContext<any>) {
|
||||||
|
function getSiteInfo({
|
||||||
|
query,
|
||||||
|
variables,
|
||||||
|
config: cfg,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: any
|
||||||
|
config?: Partial<MedusaConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<GetSiteInfoResult> {
|
||||||
|
/** We should add collections to our Storefront API,
|
||||||
|
* so we can populate the site with collections here
|
||||||
|
*/
|
||||||
|
return Promise.resolve({
|
||||||
|
categories: [],
|
||||||
|
brands: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSiteInfo
|
||||||
|
}
|
6
framework/medusa/api/operations/index.ts
Normal file
6
framework/medusa/api/operations/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export { default as getPage } from './get-page'
|
||||||
|
export { default as getSiteInfo } from './get-site-info'
|
||||||
|
export { default as getAllPages } from './get-all-pages'
|
||||||
|
export { default as getProduct } from './get-product'
|
||||||
|
export { default as getAllProducts } from './get-all-products'
|
||||||
|
export { default as getAllProductPaths } from './get-all-product-paths'
|
8
framework/medusa/api/utils/fetch-medusa-api.ts
Normal file
8
framework/medusa/api/utils/fetch-medusa-api.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { callMedusa } from '@framework/utils/call-medusa'
|
||||||
|
|
||||||
|
const fetchApi = async (query: string, method: string, variables: any) => {
|
||||||
|
const response = await callMedusa(method, query, variables)
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
export default fetchApi
|
3
framework/medusa/api/utils/fetch.ts
Normal file
3
framework/medusa/api/utils/fetch.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import zeitFetch from '@vercel/fetch'
|
||||||
|
|
||||||
|
export default zeitFetch()
|
3
framework/medusa/auth/index.ts
Normal file
3
framework/medusa/auth/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as useLogin } from './use-login'
|
||||||
|
export { default as useLogout } from './use-logout'
|
||||||
|
export { default as useSignup } from './use-signup'
|
49
framework/medusa/auth/use-login.tsx
Normal file
49
framework/medusa/auth/use-login.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
||||||
|
import { CommerceError, ValidationError } from '@commerce/utils/errors'
|
||||||
|
import { useCustomer } from '../customer'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export default useLogin as UseLogin<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'auth',
|
||||||
|
method: 'authenticate',
|
||||||
|
},
|
||||||
|
async fetcher({ input: { email, password }, options, fetch }) {
|
||||||
|
if (!(email && password)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An email and password are required to login',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch({
|
||||||
|
...options,
|
||||||
|
variables: { email: email, password: password },
|
||||||
|
}).catch((_e) => {
|
||||||
|
throw new CommerceError({
|
||||||
|
errors: [
|
||||||
|
new ValidationError({
|
||||||
|
message:
|
||||||
|
'A user with that email and password combination does not exist',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() => {
|
||||||
|
const { revalidate } = useCustomer()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function login(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
await revalidate()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, revalidate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
29
framework/medusa/auth/use-logout.tsx
Normal file
29
framework/medusa/auth/use-logout.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
|
export default useLogout as UseLogout<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'auth',
|
||||||
|
method: 'logout',
|
||||||
|
},
|
||||||
|
async fetcher({ options, fetch }) {
|
||||||
|
await fetch({ ...options })
|
||||||
|
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() => {
|
||||||
|
return useCallback(
|
||||||
|
async function logout(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
51
framework/medusa/auth/use-signup.tsx
Normal file
51
framework/medusa/auth/use-signup.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
|
||||||
|
export default useSignup as UseSignup<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'customers',
|
||||||
|
method: 'create',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { firstName, lastName, email, password },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}) {
|
||||||
|
if (!(firstName && lastName && email && password)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message:
|
||||||
|
'A first name, last name, email and password are required to signup',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return await fetch({
|
||||||
|
...options,
|
||||||
|
variables: {
|
||||||
|
payload: {
|
||||||
|
first_name: firstName,
|
||||||
|
last_name: lastName,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() => {
|
||||||
|
const { revalidate } = useCustomer()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function signup(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
await revalidate()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, revalidate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
4
framework/medusa/cart/index.ts
Normal file
4
framework/medusa/cart/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { default as useCart } from './use-cart'
|
||||||
|
export { default as useAddItem } from './use-add-item'
|
||||||
|
export { default as useRemoveItem } from './use-remove-item'
|
||||||
|
export { default as useUpdateItem } from './use-update-item'
|
63
framework/medusa/cart/use-add-item.tsx
Normal file
63
framework/medusa/cart/use-add-item.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { MEDUSA_CART_ID_COOKIE } from '@framework/const'
|
||||||
|
import { MedusaAddItemProps } from '@framework/types'
|
||||||
|
import type { AddItemHook } from '../types/cart'
|
||||||
|
import { normalizeCart } from '@framework/utils/normalizers/normalize-cart'
|
||||||
|
import { useCart } from 'framework/local/cart'
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<AddItemHook> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'carts',
|
||||||
|
method: 'addItem',
|
||||||
|
},
|
||||||
|
async fetcher({ input: item, options, fetch }) {
|
||||||
|
if (item.quantity && !Number.isInteger(item.quantity)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'The item quantity has to be a valid integer greater than 0',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const variables: {
|
||||||
|
cart_id: string
|
||||||
|
payload: MedusaAddItemProps
|
||||||
|
} = {
|
||||||
|
cart_id: Cookies.get(MEDUSA_CART_ID_COOKIE)!,
|
||||||
|
payload: {
|
||||||
|
variant_id: item.variantId,
|
||||||
|
quantity: item.quantity ?? 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await fetch({
|
||||||
|
...options,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
|
|
||||||
|
return normalizeCart(data.cart)
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(e)
|
||||||
|
throw new CommerceError({ message: e.message })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() => {
|
||||||
|
const { mutate } = useCart()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function addItem(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
await mutate(data, false)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
76
framework/medusa/cart/use-cart.tsx
Normal file
76
framework/medusa/cart/use-cart.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useCart, { UseCart } from '@commerce/cart/use-cart'
|
||||||
|
import { normalizeCart } from '@framework/utils/normalizers/normalize-cart'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
import { MEDUSA_CART_ID_COOKIE } from '@framework/const'
|
||||||
|
|
||||||
|
export default useCart as UseCart<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
async fetcher({ fetch }) {
|
||||||
|
const cart_id = Cookies.get(MEDUSA_CART_ID_COOKIE)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If cart already exits, then try to fetch it
|
||||||
|
*/
|
||||||
|
if (cart_id) {
|
||||||
|
try {
|
||||||
|
const existingCartResponse = await fetch({
|
||||||
|
query: 'carts',
|
||||||
|
method: 'retrieve',
|
||||||
|
variables: { cart_id: cart_id },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (existingCartResponse?.cart) {
|
||||||
|
return normalizeCart(existingCartResponse.cart)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
/**
|
||||||
|
* noop: If the cart_id does not exits, then we
|
||||||
|
* continue and create a new cart and set a new
|
||||||
|
* CART_COOKIE
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCartResponse = await fetch({
|
||||||
|
query: 'carts',
|
||||||
|
method: 'create',
|
||||||
|
variables: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (newCartResponse?.cart) {
|
||||||
|
Cookies.set(MEDUSA_CART_ID_COOKIE, newCartResponse.cart.id, {
|
||||||
|
expires: 30,
|
||||||
|
})
|
||||||
|
return normalizeCart(newCartResponse.cart)
|
||||||
|
}
|
||||||
|
throw new CommerceError({ message: 'Medusa cart error' })
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ useData }) =>
|
||||||
|
(input) => {
|
||||||
|
const response = useData({
|
||||||
|
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||||
|
})
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
Object.create(response, {
|
||||||
|
isEmpty: {
|
||||||
|
get() {
|
||||||
|
return (response.data?.lineItems.length ?? 0) <= 0
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[response]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
55
framework/medusa/cart/use-remove-item.tsx
Normal file
55
framework/medusa/cart/use-remove-item.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import {
|
||||||
|
HookFetcherContext,
|
||||||
|
MutationHook,
|
||||||
|
MutationHookContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
|
||||||
|
import { MEDUSA_CART_ID_COOKIE } from '@framework/const'
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
import { normalizeCart } from '@framework/utils/normalizers/normalize-cart'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import { RemoveItemHook } from '@commerce/types/cart'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import useCart from './use-cart'
|
||||||
|
|
||||||
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'carts',
|
||||||
|
method: 'deleteItem',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<RemoveItemHook>) {
|
||||||
|
const cart_id = Cookies.get(MEDUSA_CART_ID_COOKIE)
|
||||||
|
|
||||||
|
if (cart_id) {
|
||||||
|
const data = await fetch({
|
||||||
|
...options,
|
||||||
|
variables: { cart_id: cart_id, line_id: itemId },
|
||||||
|
})
|
||||||
|
|
||||||
|
return normalizeCart(data.cart)
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({ message: 'No cart was found' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }: MutationHookContext<RemoveItemHook>) =>
|
||||||
|
() => {
|
||||||
|
const { mutate } = useCart()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function removeItem(input) {
|
||||||
|
const data = await fetch({ input: { itemId: input.id } })
|
||||||
|
await mutate(data, false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
91
framework/medusa/cart/use-update-item.tsx
Normal file
91
framework/medusa/cart/use-update-item.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { MutationHook, MutationHookContext } from '@commerce/utils/types'
|
||||||
|
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
|
||||||
|
import { handler as removeItem } from './use-remove-item'
|
||||||
|
import { CommerceError, ValidationError } from '@commerce/utils/errors'
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
import { MEDUSA_CART_ID_COOKIE } from '@framework/const'
|
||||||
|
import { normalizeCart } from '@framework/utils/normalizers/normalize-cart'
|
||||||
|
import { LineItem, UpdateItemHook } from '@commerce/types/cart'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import { debounce } from 'lodash'
|
||||||
|
import useCart from '@commerce/cart/use-cart'
|
||||||
|
|
||||||
|
export type UpdateItemActionInput<T = any> = T extends LineItem
|
||||||
|
? Partial<UpdateItemHook['actionInput']>
|
||||||
|
: UpdateItemHook['actionInput']
|
||||||
|
|
||||||
|
export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'carts',
|
||||||
|
method: 'updateItem',
|
||||||
|
},
|
||||||
|
async fetcher({ input: { itemId, item }, options, fetch }) {
|
||||||
|
if (Number.isInteger(item.quantity)) {
|
||||||
|
if (item.quantity! < 1) {
|
||||||
|
return removeItem.fetcher!({
|
||||||
|
options: removeItem.fetchOptions,
|
||||||
|
input: { itemId },
|
||||||
|
fetch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (item.quantity) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'The item quantity has to be a valid integer',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cart_id = Cookies.get(MEDUSA_CART_ID_COOKIE)
|
||||||
|
|
||||||
|
const data = await fetch({
|
||||||
|
...options,
|
||||||
|
variables: {
|
||||||
|
cart_id: cart_id,
|
||||||
|
line_id: itemId,
|
||||||
|
payload: { quantity: item.quantity },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (data.cart) {
|
||||||
|
return normalizeCart(data.cart)
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({ message: 'No cart was found' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }: MutationHookContext<UpdateItemHook>) =>
|
||||||
|
<T extends LineItem | undefined = undefined>(
|
||||||
|
ctx: {
|
||||||
|
item?: T
|
||||||
|
wait?: number
|
||||||
|
} = {}
|
||||||
|
) => {
|
||||||
|
const { item } = ctx
|
||||||
|
const { mutate } = useCart()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
debounce(async (input: UpdateItemActionInput<T>) => {
|
||||||
|
const itemId = input.id ?? item?.id
|
||||||
|
const productId = input.productId ?? item?.productId
|
||||||
|
const variantId = input.productId ?? item?.variantId
|
||||||
|
|
||||||
|
if (!itemId || !productId || !variantId) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'Invalid input used for this operation',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({
|
||||||
|
input: {
|
||||||
|
itemId,
|
||||||
|
item: { productId, variantId, quantity: input.quantity },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await mutate(data, false)
|
||||||
|
return data
|
||||||
|
}, ctx.wait ?? 500),
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
9
framework/medusa/commerce.config.json
Normal file
9
framework/medusa/commerce.config.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"provider": "medusa",
|
||||||
|
"features": {
|
||||||
|
"wishlist": false,
|
||||||
|
"customerAuth": true,
|
||||||
|
"customCheckout": true,
|
||||||
|
"search": false
|
||||||
|
}
|
||||||
|
}
|
2
framework/medusa/const.ts
Normal file
2
framework/medusa/const.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const MEDUSA_PUBLIC_STORE_URL = process.env.NEXT_PUBLIC_MEDUSA_STORE_URL
|
||||||
|
export const MEDUSA_CART_ID_COOKIE = 'medusa_cart_id'
|
1
framework/medusa/customer/index.ts
Normal file
1
framework/medusa/customer/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as useCustomer } from './use-customer'
|
27
framework/medusa/customer/use-customer.tsx
Normal file
27
framework/medusa/customer/use-customer.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
|
||||||
|
import { normalizeCustomer } from '@framework/utils/normalizers/normalize-customer'
|
||||||
|
|
||||||
|
export default useCustomer as UseCustomer<typeof handler>
|
||||||
|
export const handler: SWRHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'auth',
|
||||||
|
method: 'getSession',
|
||||||
|
},
|
||||||
|
async fetcher({ options, fetch }) {
|
||||||
|
const data = await fetch({
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
return normalizeCustomer(data?.customer) || null
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ useData }) =>
|
||||||
|
(input) => {
|
||||||
|
return useData({
|
||||||
|
swrOptions: {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
...input?.swrOptions,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
44
framework/medusa/fetcher.ts
Normal file
44
framework/medusa/fetcher.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import { Fetcher } from '@commerce/utils/types'
|
||||||
|
import { callMedusa } from './utils/call-medusa'
|
||||||
|
|
||||||
|
enum Query {
|
||||||
|
Auth = <any>'auth',
|
||||||
|
Carts = <any>'carts',
|
||||||
|
Customers = <any>'customers',
|
||||||
|
Errors = <any>'errors',
|
||||||
|
Orders = <any>'orders',
|
||||||
|
Products = <any>'products',
|
||||||
|
ReturnReasons = <any>'returnReasons',
|
||||||
|
Returns = <any>'returns',
|
||||||
|
ShippingOptions = <any>'shippingOptions',
|
||||||
|
Swaps = <any>'swaps',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetcher: Fetcher = async ({ method, query, variables }) => {
|
||||||
|
if (!query) {
|
||||||
|
throw new CommerceError({ message: 'An argument for query is required' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.values(Query).includes(query!)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: `${query} is not a valid method argument. Available queries are ${Object.keys(
|
||||||
|
Query
|
||||||
|
)
|
||||||
|
.map((k) => Query[k as any])
|
||||||
|
.join(', ')}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!method) {
|
||||||
|
throw new CommerceError({ message: 'An argument for method is required' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await callMedusa(method, query, variables)
|
||||||
|
|
||||||
|
if (response.statusText === 'OK') {
|
||||||
|
const { data } = response
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
throw response
|
||||||
|
}
|
9
framework/medusa/index.tsx
Normal file
9
framework/medusa/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { getCommerceProvider, useCommerce as useCoreCommerce } from '@commerce'
|
||||||
|
import { medusaProvider, MedusaProvider } from './provider'
|
||||||
|
|
||||||
|
export { medusaProvider }
|
||||||
|
export type { MedusaProvider }
|
||||||
|
|
||||||
|
export const CommerceProvider = getCommerceProvider(medusaProvider)
|
||||||
|
|
||||||
|
export const useCommerce = () => useCoreCommerce<MedusaProvider>()
|
6
framework/medusa/medusa.ts
Normal file
6
framework/medusa/medusa.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import Medusa from '@medusajs/medusa-js'
|
||||||
|
import { MEDUSA_PUBLIC_STORE_URL } from './const'
|
||||||
|
|
||||||
|
const medusa: Medusa = new Medusa({ baseUrl: MEDUSA_PUBLIC_STORE_URL! })
|
||||||
|
|
||||||
|
export default medusa
|
8
framework/medusa/next.config.js
Normal file
8
framework/medusa/next.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const commerce = require('./commerce.config.json')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
commerce,
|
||||||
|
images: {
|
||||||
|
domains: [process.env.NEXT_PUBLIC_MEDUSA_IMG_HOST],
|
||||||
|
},
|
||||||
|
}
|
2
framework/medusa/product/index.ts
Normal file
2
framework/medusa/product/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as usePrice } from './use-price'
|
||||||
|
export { default as useSearch } from './use-search'
|
2
framework/medusa/product/use-price.tsx
Normal file
2
framework/medusa/product/use-price.tsx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from '@commerce/product/use-price'
|
||||||
|
export { default } from '@commerce/product/use-price'
|
42
framework/medusa/product/use-search.tsx
Normal file
42
framework/medusa/product/use-search.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useSearch, { UseSearch } from '@commerce/product/use-search'
|
||||||
|
import { Product } from '@commerce/types/product'
|
||||||
|
import { Product as MedusaProduct } from '@medusajs/medusa-js/lib/types'
|
||||||
|
import { normalizeProduct } from '@framework/utils/normalizers/normalize-products'
|
||||||
|
export default useSearch as UseSearch<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<any> = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: 'products',
|
||||||
|
method: 'list',
|
||||||
|
},
|
||||||
|
async fetcher({ input, options, fetch }) {
|
||||||
|
const { products } = await fetch({
|
||||||
|
...options,
|
||||||
|
variables: { query: null },
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
products: products
|
||||||
|
? products.map((product: MedusaProduct) => normalizeProduct(product))
|
||||||
|
: [],
|
||||||
|
found: products.length,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ useData }) =>
|
||||||
|
({ input = {} }) => {
|
||||||
|
return useData({
|
||||||
|
input: [
|
||||||
|
['search', input.search],
|
||||||
|
['categoryId', input.categoryId],
|
||||||
|
['brandId', input.brandId],
|
||||||
|
['sort', input.sort],
|
||||||
|
],
|
||||||
|
swrOptions: {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
...input.swrOptions,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
30
framework/medusa/provider.ts
Normal file
30
framework/medusa/provider.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Provider } from '@commerce'
|
||||||
|
|
||||||
|
import { fetcher } from './fetcher'
|
||||||
|
|
||||||
|
import { handler as useCart } from './cart/use-cart'
|
||||||
|
import { handler as useAddItem } from './cart/use-add-item'
|
||||||
|
import { handler as useUpdateItem } from './cart/use-update-item'
|
||||||
|
import { handler as useRemoveItem } from './cart/use-remove-item'
|
||||||
|
|
||||||
|
import { handler as useCustomer } from './customer/use-customer'
|
||||||
|
|
||||||
|
import { handler as useSearch } from './product/use-search'
|
||||||
|
|
||||||
|
import { handler as useLogin } from './auth/use-login'
|
||||||
|
import { handler as useLogout } from './auth/use-logout'
|
||||||
|
import { handler as useSignup } from './auth/use-signup'
|
||||||
|
|
||||||
|
import { MEDUSA_CART_ID_COOKIE } from './const'
|
||||||
|
|
||||||
|
export const medusaProvider: Provider = {
|
||||||
|
locale: 'en-us',
|
||||||
|
cartCookie: MEDUSA_CART_ID_COOKIE,
|
||||||
|
fetcher: fetcher,
|
||||||
|
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||||
|
customer: { useCustomer },
|
||||||
|
products: { useSearch },
|
||||||
|
auth: { useLogin, useLogout, useSignup },
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MedusaProvider = typeof medusaProvider
|
1
framework/medusa/types/cart.ts
Normal file
1
framework/medusa/types/cart.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/cart'
|
1
framework/medusa/types/checkout.ts
Normal file
1
framework/medusa/types/checkout.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/checkout'
|
1
framework/medusa/types/common.ts
Normal file
1
framework/medusa/types/common.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/common'
|
1
framework/medusa/types/customer.ts
Normal file
1
framework/medusa/types/customer.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/customer'
|
25
framework/medusa/types/index.ts
Normal file
25
framework/medusa/types/index.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import * as Cart from './cart'
|
||||||
|
import * as Checkout from './checkout'
|
||||||
|
import * as Common from './common'
|
||||||
|
import * as Customer from './customer'
|
||||||
|
import * as Login from './login'
|
||||||
|
import * as Logout from './logout'
|
||||||
|
import * as Page from './page'
|
||||||
|
import * as Product from './product'
|
||||||
|
import * as Signup from './signup'
|
||||||
|
import * as Site from './site'
|
||||||
|
import * as Wishlist from './wishlist'
|
||||||
|
|
||||||
|
export type {
|
||||||
|
Cart,
|
||||||
|
Checkout,
|
||||||
|
Common,
|
||||||
|
Customer,
|
||||||
|
Login,
|
||||||
|
Logout,
|
||||||
|
Page,
|
||||||
|
Product,
|
||||||
|
Signup,
|
||||||
|
Site,
|
||||||
|
Wishlist,
|
||||||
|
}
|
12
framework/medusa/types/login.ts
Normal file
12
framework/medusa/types/login.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import * as Core from '@commerce/types/login'
|
||||||
|
import type { LoginMutationVariables } from '../schema'
|
||||||
|
import { LoginBody, LoginTypes } from '@commerce/types/login'
|
||||||
|
|
||||||
|
export * from '@commerce/types/login'
|
||||||
|
|
||||||
|
export type LoginHook<T extends LoginTypes = LoginTypes> = {
|
||||||
|
data: null
|
||||||
|
actionInput: LoginBody
|
||||||
|
fetcherInput: LoginBody
|
||||||
|
body: T['body']
|
||||||
|
}
|
1
framework/medusa/types/logout.ts
Normal file
1
framework/medusa/types/logout.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/logout'
|
1
framework/medusa/types/page.ts
Normal file
1
framework/medusa/types/page.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/page'
|
1
framework/medusa/types/product.ts
Normal file
1
framework/medusa/types/product.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/product'
|
1
framework/medusa/types/signup.ts
Normal file
1
framework/medusa/types/signup.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/signup'
|
1
framework/medusa/types/site.ts
Normal file
1
framework/medusa/types/site.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/site'
|
1
framework/medusa/types/wishlist.ts
Normal file
1
framework/medusa/types/wishlist.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/wishlist'
|
420
framework/medusa/utils/call-medusa.ts
Normal file
420
framework/medusa/utils/call-medusa.ts
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import { MEDUSA_PUBLIC_STORE_URL } from '@framework/const'
|
||||||
|
import { Product } from '@medusajs/medusa-js/lib/types'
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
import medusa from '../medusa'
|
||||||
|
|
||||||
|
export const callMedusa = async (
|
||||||
|
method: string,
|
||||||
|
query: string,
|
||||||
|
variables: any
|
||||||
|
) => {
|
||||||
|
switch (query) {
|
||||||
|
case 'auth':
|
||||||
|
if (method === 'authenticate') {
|
||||||
|
const { email, password } = variables
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for email and password is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.auth.authenticate({
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
})
|
||||||
|
} else if (method === 'exists') {
|
||||||
|
const { email } = variables
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for email is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.auth.exists(email)
|
||||||
|
} else if (method === 'getSession') {
|
||||||
|
return await medusa.auth.getSession()
|
||||||
|
} else if ('logout') {
|
||||||
|
//NOT WORKING
|
||||||
|
return await fetch(`${MEDUSA_PUBLIC_STORE_URL}/store/auth`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'No valid method argument was provided',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case 'carts':
|
||||||
|
if (method === 'complete') {
|
||||||
|
const { cart_id } = variables
|
||||||
|
|
||||||
|
if (!cart_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.complete(cart_id)
|
||||||
|
} else if (method === 'create') {
|
||||||
|
const { payload } = variables
|
||||||
|
|
||||||
|
return await medusa.carts.create(payload)
|
||||||
|
} else if (method === 'createPaymentSessions') {
|
||||||
|
const { cart_id } = variables
|
||||||
|
|
||||||
|
if (!cart_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.createPaymentSessions(cart_id)
|
||||||
|
} else if (method === 'deletePaymentSessions') {
|
||||||
|
const { cart_id, provider_id } = variables
|
||||||
|
|
||||||
|
if (!(cart_id && provider_id)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id and provider_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.deletePaymentSession(cart_id, provider_id)
|
||||||
|
} else if (method === 'refreshPaymentSession') {
|
||||||
|
const { cart_id, provider_id } = variables
|
||||||
|
|
||||||
|
if (!(cart_id && provider_id)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id and provider_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.refreshPaymentSession(cart_id, provider_id)
|
||||||
|
} else if (method === 'updatePaymentSession') {
|
||||||
|
const { cart_id, provider_id, data } = variables
|
||||||
|
|
||||||
|
if (!(cart_id && provider_id)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id and provider_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.updatePaymentSession(cart_id, {
|
||||||
|
provider_id,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
} else if (method === 'setPaymentSession') {
|
||||||
|
const { cart_id, provider_id } = variables
|
||||||
|
|
||||||
|
if (!(cart_id && provider_id)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id and provider_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.setPaymentSession(cart_id, { provider_id })
|
||||||
|
} else if (method === 'deleteDiscount') {
|
||||||
|
const { cart_id, code } = variables
|
||||||
|
|
||||||
|
if (!(cart_id && code)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id and code is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.deleteDiscount(cart_id, code)
|
||||||
|
} else if (method === 'retrieve') {
|
||||||
|
const { cart_id } = variables
|
||||||
|
|
||||||
|
if (!cart_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id and code is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.retrieve(cart_id)
|
||||||
|
} else if (method === 'update') {
|
||||||
|
const { cart_id, payload } = variables
|
||||||
|
|
||||||
|
if (!(cart_id && payload)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id and payload is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return await medusa.carts.update(cart_id, payload)
|
||||||
|
} else if (method === 'addItem') {
|
||||||
|
const { cart_id, payload } = variables
|
||||||
|
const { variant_id, quantity } = payload
|
||||||
|
|
||||||
|
if (!cart_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!(variant_id && quantity)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for variant_id and quantity is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.lineItems.create(cart_id, {
|
||||||
|
variant_id: variant_id,
|
||||||
|
quantity: quantity,
|
||||||
|
})
|
||||||
|
} else if (method === 'deleteItem') {
|
||||||
|
const { cart_id, line_id } = variables
|
||||||
|
|
||||||
|
if (!(cart_id && line_id)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id and line_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.carts.lineItems.delete(cart_id, line_id)
|
||||||
|
} else if (method === 'updateItem') {
|
||||||
|
const { cart_id, line_id, payload } = variables
|
||||||
|
|
||||||
|
if (!(cart_id && line_id && payload)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id, line_id and payload is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return await medusa.carts.lineItems.update(cart_id, line_id, payload)
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'No valid method argument was provided',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case 'customers':
|
||||||
|
if (method === 'addAddresses') {
|
||||||
|
const { customer_id, payload } = variables
|
||||||
|
return await medusa.customers.addresses.addAddress(customer_id, payload)
|
||||||
|
} else if (method === 'updateAddresses') {
|
||||||
|
const { customer_id, address_id, payload } = variables
|
||||||
|
return await medusa.customers.addresses.updateAddress(
|
||||||
|
customer_id,
|
||||||
|
address_id,
|
||||||
|
payload
|
||||||
|
)
|
||||||
|
} else if (method === 'deleteAddress') {
|
||||||
|
const { customer_id, address_id } = variables
|
||||||
|
return await medusa.customers.addresses.deleteAddress(
|
||||||
|
customer_id,
|
||||||
|
address_id
|
||||||
|
)
|
||||||
|
} else if (method === 'listPaymentMethods') {
|
||||||
|
const { customer_id } = variables
|
||||||
|
return await medusa.customers.paymentMethods.list(customer_id)
|
||||||
|
} else if (method === 'create') {
|
||||||
|
const { payload } = variables
|
||||||
|
|
||||||
|
if (!payload) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for payload is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.customers.create(payload)
|
||||||
|
} else if (method === 'generatePasswordToken') {
|
||||||
|
const { payload } = variables
|
||||||
|
|
||||||
|
if (!payload) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for payload is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.customers.generatePasswordToken(payload)
|
||||||
|
} else if (method === 'listOrders') {
|
||||||
|
const { customer_id } = variables
|
||||||
|
|
||||||
|
if (!customer_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for customer_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return await medusa.customers.listOrders(customer_id)
|
||||||
|
} else if (method === 'resetPassword') {
|
||||||
|
const { payload } = variables
|
||||||
|
|
||||||
|
if (!payload) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for payload is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.customers.resetPassword(payload)
|
||||||
|
} else if (method === 'retrieve') {
|
||||||
|
const { customer_id } = variables
|
||||||
|
|
||||||
|
if (!customer_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for customer_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.customers.retrieve(customer_id)
|
||||||
|
} else if (method === 'update') {
|
||||||
|
const { customer_id, payload } = variables
|
||||||
|
|
||||||
|
if (!customer_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for customer_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.customers.update(customer_id, payload)
|
||||||
|
}
|
||||||
|
case 'orders':
|
||||||
|
if (method === 'lookupOrder') {
|
||||||
|
const { payload } = variables
|
||||||
|
|
||||||
|
if (!payload) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for payload is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.orders.lookupOrder(payload)
|
||||||
|
} else if (method === 'retrieve') {
|
||||||
|
const { order_id } = variables
|
||||||
|
|
||||||
|
if (!order_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for order_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.orders.retrieve(order_id)
|
||||||
|
} else if (method === 'retrieveByCartId') {
|
||||||
|
const { cart_id } = variables
|
||||||
|
|
||||||
|
if (!cart_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.orders.retrieveByCartId(cart_id)
|
||||||
|
}
|
||||||
|
case 'products':
|
||||||
|
if (method === 'variantsList') {
|
||||||
|
const { params } = variables
|
||||||
|
|
||||||
|
return await medusa.products.variants.list(params)
|
||||||
|
} else if (method === 'variantsRetrieve') {
|
||||||
|
const { variant_id } = variables
|
||||||
|
|
||||||
|
if (!variant_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for variant_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.products.variants.retrieve(variant_id)
|
||||||
|
} else if (method === 'list') {
|
||||||
|
const { query } = variables
|
||||||
|
|
||||||
|
return await medusa.products.list(
|
||||||
|
query && {
|
||||||
|
limit: query.limit || null,
|
||||||
|
offset: query.offset || null,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else if (method === 'retrieve') {
|
||||||
|
const { product_id } = variables
|
||||||
|
|
||||||
|
if (!product_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for product_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.products.retrieve(product_id)
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'No valid method argument was provided',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case 'returnReasons':
|
||||||
|
if (method === 'list') {
|
||||||
|
return await medusa.returnReasons.list()
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'No valid method argument was provided',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case 'returns':
|
||||||
|
if (method === 'create') {
|
||||||
|
const { payload } = variables
|
||||||
|
|
||||||
|
if (!payload) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for payload is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return await medusa.returns.create(payload)
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'No valid method argument was provided',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case 'shippingOptions':
|
||||||
|
if (method === 'list') {
|
||||||
|
const { cart_id } = variables
|
||||||
|
|
||||||
|
if (!cart_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.shippingOptions.list(cart_id)
|
||||||
|
} else if (method === 'create') {
|
||||||
|
const { cart_id } = variables
|
||||||
|
|
||||||
|
if (!cart_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.shippingOptions.listCartOptions(cart_id)
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'No valid method argument was provided',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case 'swaps':
|
||||||
|
if (method === 'create') {
|
||||||
|
const { cart_id } = variables
|
||||||
|
|
||||||
|
if (!cart_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.swaps.create({ cart_id })
|
||||||
|
} else if (method === 'retrieve') {
|
||||||
|
const { cart_id } = variables
|
||||||
|
|
||||||
|
if (!cart_id) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'An argument for cart_id is required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await medusa.swaps.retrieveByCartId(cart_id)
|
||||||
|
} else {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'No valid method argument was provided',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'No valid query argument was provided',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
77
framework/medusa/utils/normalizers/normalize-cart.ts
Normal file
77
framework/medusa/utils/normalizers/normalize-cart.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { Cart, LineItem, ProductVariant } from '../../types/cart'
|
||||||
|
import {
|
||||||
|
Cart as MedusaCart,
|
||||||
|
Discount as MedusaDiscount,
|
||||||
|
LineItem as MedusaLineItem,
|
||||||
|
ProductVariant as MedusaProductVariant,
|
||||||
|
} from '@medusajs/medusa-js/lib/types'
|
||||||
|
import { Discount } from '@commerce/types/common'
|
||||||
|
|
||||||
|
export function normalizeProductVariant(
|
||||||
|
{ id, title: name, sku }: MedusaProductVariant,
|
||||||
|
price: number,
|
||||||
|
thumbnail: string
|
||||||
|
): ProductVariant {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
price: price / 100,
|
||||||
|
sku: sku || id,
|
||||||
|
listPrice: price / 100,
|
||||||
|
requiresShipping: true,
|
||||||
|
image: { url: thumbnail, altText: name },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeLineItem({
|
||||||
|
id,
|
||||||
|
title: name,
|
||||||
|
variant,
|
||||||
|
quantity,
|
||||||
|
unit_price,
|
||||||
|
thumbnail,
|
||||||
|
}: MedusaLineItem): LineItem {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
path: variant?.product.handle || name.replace(' ', '-'),
|
||||||
|
variant: normalizeProductVariant(variant!, unit_price, thumbnail!),
|
||||||
|
variantId: variant!.id,
|
||||||
|
productId: variant!.product.id,
|
||||||
|
discounts: [],
|
||||||
|
quantity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeDiscount(discount: MedusaDiscount): Discount {
|
||||||
|
return {
|
||||||
|
value: discount.rule.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeCart({
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
created_at,
|
||||||
|
region,
|
||||||
|
items,
|
||||||
|
subtotal,
|
||||||
|
total,
|
||||||
|
tax_total,
|
||||||
|
customer_id,
|
||||||
|
discounts,
|
||||||
|
}: MedusaCart): Cart {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
customerId: customer_id,
|
||||||
|
discounts: discounts.map((discount) => normalizeDiscount(discount)),
|
||||||
|
createdAt: `${created_at}`,
|
||||||
|
currency: { code: region.currency_code },
|
||||||
|
lineItems: items.map((item) => normalizeLineItem(item)),
|
||||||
|
subtotalPrice: subtotal / 100,
|
||||||
|
totalPrice: total / 100,
|
||||||
|
taxesIncluded: tax_total > 0,
|
||||||
|
lineItemsSubtotalPrice: subtotal / 100,
|
||||||
|
}
|
||||||
|
}
|
9
framework/medusa/utils/normalizers/normalize-customer.ts
Normal file
9
framework/medusa/utils/normalizers/normalize-customer.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Customer } from '@commerce/types/customer'
|
||||||
|
import { Customer as MedusaCustomer } from '@medusajs/medusa-js/lib/types'
|
||||||
|
|
||||||
|
export function normalizeCustomer(customer: MedusaCustomer): Customer {
|
||||||
|
return {
|
||||||
|
firstName: customer.first_name,
|
||||||
|
lastName: customer.last_name,
|
||||||
|
}
|
||||||
|
}
|
98
framework/medusa/utils/normalizers/normalize-products.ts
Normal file
98
framework/medusa/utils/normalizers/normalize-products.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import {
|
||||||
|
Image as MedusaImage,
|
||||||
|
MoneyAmount as MedusaMoneyAmount,
|
||||||
|
Product as MedusaProduct,
|
||||||
|
ProductOption as MedusaProductOption,
|
||||||
|
ProductVariant as MedusaProductVariant,
|
||||||
|
} from '@medusajs/medusa-js/lib/types'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Product,
|
||||||
|
ProductImage,
|
||||||
|
ProductOption,
|
||||||
|
ProductPrice,
|
||||||
|
ProductVariant,
|
||||||
|
} from '../../types/product'
|
||||||
|
|
||||||
|
export function normalizeProductImages(images: MedusaImage[]): ProductImage[] {
|
||||||
|
if (!images || images.length < 1) {
|
||||||
|
return [{ url: '/' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(images)
|
||||||
|
return images.map(({ url }: MedusaImage) => ({
|
||||||
|
url,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeAvailability(variant: MedusaProductVariant): boolean {
|
||||||
|
if (
|
||||||
|
variant.manage_inventory &&
|
||||||
|
!variant.allow_backorder &&
|
||||||
|
variant.inventory_quantity < 1
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeProductVariants(
|
||||||
|
variants: MedusaProductVariant[]
|
||||||
|
): ProductVariant[] {
|
||||||
|
return variants.map((variant) => {
|
||||||
|
return {
|
||||||
|
id: variant.id,
|
||||||
|
options: [], //variants don't have options
|
||||||
|
availableForSale: normalizeAvailability(variant),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizePrice(price: MedusaMoneyAmount): ProductPrice {
|
||||||
|
return {
|
||||||
|
value: price.amount / 100,
|
||||||
|
currencyCode: price.currency_code.toUpperCase(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeOptions(
|
||||||
|
options: MedusaProductOption[]
|
||||||
|
): ProductOption[] {
|
||||||
|
return options.map((opt) => ({
|
||||||
|
id: opt.id,
|
||||||
|
displayName: opt.title,
|
||||||
|
values: opt.values.map((val) => {
|
||||||
|
return {
|
||||||
|
label: val.value,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeProduct({
|
||||||
|
id,
|
||||||
|
title: name,
|
||||||
|
description,
|
||||||
|
variants: medusaVariants,
|
||||||
|
options: medusaOptions,
|
||||||
|
images,
|
||||||
|
handle: slug,
|
||||||
|
thumbnail,
|
||||||
|
}: MedusaProduct): Product {
|
||||||
|
const tmpVariant = medusaVariants.reduce((prev, curr) =>
|
||||||
|
prev.prices.amount < curr.prices.amount ? prev : curr
|
||||||
|
)
|
||||||
|
|
||||||
|
const minPrice = normalizePrice(tmpVariant.prices[0]) //need to fix typing in medusa types
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description: description || '',
|
||||||
|
variants: normalizeProductVariants(medusaVariants),
|
||||||
|
images: thumbnail && !images.length ? [{ url: thumbnail }] : images,
|
||||||
|
options: normalizeOptions(medusaOptions),
|
||||||
|
price: minPrice,
|
||||||
|
path: `/${slug}`,
|
||||||
|
slug,
|
||||||
|
}
|
||||||
|
}
|
13
framework/medusa/wishlist/use-add-item.tsx
Normal file
13
framework/medusa/wishlist/use-add-item.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export function emptyHook() {
|
||||||
|
const useEmptyHook = async (options = {}) => {
|
||||||
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return useEmptyHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
17
framework/medusa/wishlist/use-remove-item.tsx
Normal file
17
framework/medusa/wishlist/use-remove-item.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
includeProducts?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emptyHook(options?: Options) {
|
||||||
|
const useEmptyHook = async ({ id }: { id: string | number }) => {
|
||||||
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return useEmptyHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
43
framework/medusa/wishlist/use-wishlist.tsx
Normal file
43
framework/medusa/wishlist/use-wishlist.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { HookFetcher } from '@commerce/utils/types'
|
||||||
|
import type { Product } from '@commerce/types/product'
|
||||||
|
|
||||||
|
const defaultOpts = {}
|
||||||
|
|
||||||
|
export type Wishlist = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
product_id: number
|
||||||
|
variant_id: number
|
||||||
|
id: number
|
||||||
|
product: Product
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseWishlistOptions {
|
||||||
|
includeProducts?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseWishlistInput extends UseWishlistOptions {
|
||||||
|
customerId?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = () => {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extendHook(
|
||||||
|
customFetcher: typeof fetcher,
|
||||||
|
// swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput>
|
||||||
|
swrOptions?: any
|
||||||
|
) {
|
||||||
|
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
|
||||||
|
return { data: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
useWishlist.extend = extendHook
|
||||||
|
|
||||||
|
return useWishlist
|
||||||
|
}
|
||||||
|
|
||||||
|
export default extendHook(fetcher)
|
@@ -10,6 +10,7 @@ const isShopify = provider === 'shopify'
|
|||||||
const isSaleor = provider === 'saleor'
|
const isSaleor = provider === 'saleor'
|
||||||
const isSwell = provider === 'swell'
|
const isSwell = provider === 'swell'
|
||||||
const isVendure = provider === 'vendure'
|
const isVendure = provider === 'vendure'
|
||||||
|
const isMedusa = provider === 'medusa'
|
||||||
|
|
||||||
module.exports = withCommerceConfig({
|
module.exports = withCommerceConfig({
|
||||||
commerce,
|
commerce,
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
"node": ">=14.x"
|
"node": ">=14.x"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@medusajs/medusa-js": "^1.0.0",
|
||||||
"@react-spring/web": "^9.2.1",
|
"@react-spring/web": "^9.2.1",
|
||||||
"@vercel/fetch": "^6.1.0",
|
"@vercel/fetch": "^6.1.0",
|
||||||
"autoprefixer": "^10.2.6",
|
"autoprefixer": "^10.2.6",
|
||||||
|
@@ -23,8 +23,8 @@
|
|||||||
"@components/*": ["components/*"],
|
"@components/*": ["components/*"],
|
||||||
"@commerce": ["framework/commerce"],
|
"@commerce": ["framework/commerce"],
|
||||||
"@commerce/*": ["framework/commerce/*"],
|
"@commerce/*": ["framework/commerce/*"],
|
||||||
"@framework": ["framework/local"],
|
"@framework": ["framework/medusa"],
|
||||||
"@framework/*": ["framework/local/*"]
|
"@framework/*": ["framework/medusa/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
||||||
|
19
yarn.lock
19
yarn.lock
@@ -929,6 +929,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
|
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
|
||||||
integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
|
integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
|
||||||
|
|
||||||
|
"@medusajs/medusa-js@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@medusajs/medusa-js/-/medusa-js-1.0.0.tgz#b8e2231f7d15eebfaf0d5299dc5716ac37171757"
|
||||||
|
integrity sha512-P8l/xI6Q07PHLO65ED1BuS+4BGa2dSLDAkpWGPf3Sc7giKYlgDXo1xXXT8+jnsJklRgWbaJIU9mbcdi79QbFQw==
|
||||||
|
dependencies:
|
||||||
|
axios "^0.21.0"
|
||||||
|
|
||||||
"@microsoft/fetch-event-source@2.0.1":
|
"@microsoft/fetch-event-source@2.0.1":
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d"
|
resolved "https://registry.yarnpkg.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d"
|
||||||
@@ -1620,6 +1627,13 @@ axe-core@^4.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.2.tgz#fcf8777b82c62cfc69c7e9f32c0d2226287680e7"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.2.tgz#fcf8777b82c62cfc69c7e9f32c0d2226287680e7"
|
||||||
integrity sha512-5LMaDRWm8ZFPAEdzTYmgjjEdj1YnQcpfrVajO/sn/LhbpGp0Y0H64c2hLZI1gRMxfA+w1S71Uc/nHaOXgcCvGg==
|
integrity sha512-5LMaDRWm8ZFPAEdzTYmgjjEdj1YnQcpfrVajO/sn/LhbpGp0Y0H64c2hLZI1gRMxfA+w1S71Uc/nHaOXgcCvGg==
|
||||||
|
|
||||||
|
axios@^0.21.0:
|
||||||
|
version "0.21.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
||||||
|
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.14.0"
|
||||||
|
|
||||||
axobject-query@^2.2.0:
|
axobject-query@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
|
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
|
||||||
@@ -3174,6 +3188,11 @@ flatten@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
||||||
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
||||||
|
|
||||||
|
follow-redirects@^1.14.0:
|
||||||
|
version "1.14.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
|
||||||
|
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
|
||||||
|
|
||||||
foreach@^2.0.5:
|
foreach@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
|
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
|
||||||
|
Reference in New Issue
Block a user