Feat: Medusa react price list (#1258)

* export everything from price lists in core

* medusa-js price list

* feat: add product list for price lists

* feat: add product list for price lists

* add price list to admin module

* add price list hooks initial

* refactor: product list controller

* fix: add integration test for price list products

* update types

* add tests for price lists

* update medusa react tests

* update update request for price lists

* Apply suggestions from code review

Co-authored-by: Sebastian Rindom <skrindom@gmail.com>

* rename methods

* pr feedback

* list products from price list

* fix errors after merge

* update medusa react with medusa-js method name changes

* redo changes

* update hook names

* fix: routes in msw handler

Co-authored-by: Sebastian Rindom <skrindom@gmail.com>
Co-authored-by: Zakaria El Asri <zakaria.elas@gmail.com>
This commit is contained in:
Philip Korsholm
2022-04-03 20:48:49 +02:00
committed by GitHub
parent fb33dbaca3
commit b164977a19
24 changed files with 838 additions and 276 deletions

View File

@@ -21,17 +21,21 @@ Thank you for considering contributing to Medusa! This document will outline how
### Branches ### Branches
All changes should be part of a branch and submitted as a pull request - your branches should be prefixed with one of: All changes should be part of a branch and submitted as a pull request - your branches should be prefixed with one of:
- `fix/` for bug fixes - `fix/` for bug fixes
- `feat/` for features - `feat/` for features
- `docs/` for documentation changes - `docs/` for documentation changes
### Commits ### Commits
Strive towards keeping your commits small and isolated - this helps the reviewer understand what is going on and makes it easier to process your requests. Strive towards keeping your commits small and isolated - this helps the reviewer understand what is going on and makes it easier to process your requests.
### Pull Requests ### Pull Requests
Once your changes are ready you must submit your branch as a pull request. Your pull request should be opened against the `develop` branch in the main Medusa repo. Once your changes are ready you must submit your branch as a pull request. Your pull request should be opened against the `develop` branch in the main Medusa repo.
In your PR's description you should follow the structure: In your PR's description you should follow the structure:
- **What** - what changes are in this PR - **What** - what changes are in this PR
- **Why** - why are these changes relevant - **Why** - why are these changes relevant
- **How** - how have the changes been implemented - **How** - how have the changes been implemented

View File

@@ -67,6 +67,7 @@ After these four steps and only a couple of minutes, you now have a complete com
Write-ups for all features will be made available in [Github discussions](https://github.com/medusajs/medusa/discussions) prior to starting the implementation process. Write-ups for all features will be made available in [Github discussions](https://github.com/medusajs/medusa/discussions) prior to starting the implementation process.
### Q1 ### Q1
- [x] Admin revamp - [x] Admin revamp
- [x] Tax API - [x] Tax API
- [x] Strategy pattern - [x] Strategy pattern
@@ -74,6 +75,7 @@ Write-ups for all features will be made available in [Github discussions](https:
- [ ] Bulk import / export - [ ] Bulk import / export
### Q2 ### Q2
- [ ] Extended Product API (custom fields, price lists, publishing control, and more) - [ ] Extended Product API (custom fields, price lists, publishing control, and more)
- [ ] Extended Order API (managing placed orders, improved inventory control, and more) - [ ] Extended Order API (managing placed orders, improved inventory control, and more)
- [ ] Sales Channel API - [ ] Sales Channel API

File diff suppressed because it is too large Load Diff

View File

@@ -120,8 +120,8 @@ yarn global add create-strapi-app@3.6.8
create-strapi-app strapi-medusa --template https://github.com/Deathwish98/strapi-medusa-template.git create-strapi-app strapi-medusa --template https://github.com/Deathwish98/strapi-medusa-template.git
``` ```
> Note: The plugin expects node version to be '>= 10.16.0 and <=14.x.x', otherwise it will throw an error.
> Note: The plugin expects node version to be '>= 10.16.0 and <=14.x.x', otherwise it will throw an error.
After running the command, you have a full Strapi project configured to synchronize with Medusa. Upon the initial start of the Strapi server, all the required models will be created. They will correlate with models from Medusa to allow for two-way synchronization. After running the command, you have a full Strapi project configured to synchronize with Medusa. Upon the initial start of the Strapi server, all the required models will be created. They will correlate with models from Medusa to allow for two-way synchronization.

View File

@@ -8,7 +8,6 @@
</video> </video>
</div> </div>
### Introduction ### Introduction
Handling payments is at the core of every commerce system; it allows us to run our businesses. Consequently, a vast landscape of payment providers has developed, each with varying cost models, implementational specifications, and analytical capabilities. Handling payments is at the core of every commerce system; it allows us to run our businesses. Consequently, a vast landscape of payment providers has developed, each with varying cost models, implementational specifications, and analytical capabilities.

View File

@@ -24,7 +24,7 @@ export default () => {
router.get("/admin/hello", (req, res) => { router.get("/admin/hello", (req, res) => {
res.json({ res.json({
message: "Welcome to Your Store!" message: "Welcome to Your Store!",
}) })
}) })
@@ -64,7 +64,7 @@ Finally, for each route you add, create an `OPTIONS` request:
router.options("/admin/hello", cors(corsOptions)) router.options("/admin/hello", cors(corsOptions))
router.get("/admin/hello", (req, res) => { router.get("/admin/hello", (req, res) => {
//... //...
}); })
``` ```
## Multiple Endpoints ## Multiple Endpoints
@@ -76,13 +76,13 @@ You can add more than one endpoints in `src/api/index.js`:
```js ```js
router.get("/admin/hello", (req, res) => { router.get("/admin/hello", (req, res) => {
res.json({ res.json({
message: "Welcome to Your Store!" message: "Welcome to Your Store!",
}) })
}) })
router.get("/admin/bye", (req, res) => { router.get("/admin/bye", (req, res) => {
res.json({ res.json({
message: "Come back again!" message: "Come back again!",
}) })
}) })
``` ```
@@ -97,7 +97,7 @@ To do that with the previous example, first, create the file `src/api/hello.js`
export default (router) => { export default (router) => {
router.get("/admin/hello", (req, res) => { router.get("/admin/hello", (req, res) => {
res.json({ res.json({
message: "Welcome to Your Store!" message: "Welcome to Your Store!",
}) })
}) })
} }
@@ -111,7 +111,7 @@ Next, create the file `src/api/bye.js` with the following content:
export default (router) => { export default (router) => {
router.get("/admin/bye", (req, res) => { router.get("/admin/bye", (req, res) => {
res.json({ res.json({
message: "Come back again!" message: "Come back again!",
}) })
}) })
} }
@@ -148,14 +148,12 @@ You can retrieve any registered service in your endpoint using `req.scope.resolv
Heres an example of an endpoint that retrieves the count of products in your store: Heres an example of an endpoint that retrieves the count of products in your store:
```js ```js
router.get('/admin/products/count', (req, res) => { router.get("/admin/products/count", (req, res) => {
const productService = req.scope.resolve('productService') const productService = req.scope.resolve("productService")
productService productService.count().then((count) => {
.count()
.then((count) => {
res.json({ res.json({
count count,
}) })
}) })
}) })
@@ -170,13 +168,13 @@ Protected routes are routes that should be accessible by logged-in users only.
To make a route protected, first, import the `authenticate` middleware: To make a route protected, first, import the `authenticate` middleware:
```js ```js
import authenticate from '@medusajs/medusa/dist/api/middlewares/authenticate' import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate"
``` ```
Then, add the middleware to your route: Then, add the middleware to your route:
```js ```js
router.get('/store/products/count', authenticate(), (req, res) => { router.get("/store/products/count", authenticate(), (req, res) => {
//... //...
}) })
``` ```

View File

@@ -24,7 +24,7 @@ export default () => {
router.get("/store/hello", (req, res) => { router.get("/store/hello", (req, res) => {
res.json({ res.json({
message: "Welcome to My Store!" message: "Welcome to My Store!",
}) })
}) })
@@ -47,13 +47,13 @@ You can add more than one endpoints in `src/api/index.js`:
```js ```js
router.get("/store/hello", (req, res) => { router.get("/store/hello", (req, res) => {
res.json({ res.json({
message: "Welcome to My Store!" message: "Welcome to My Store!",
}) })
}) })
router.get("/store/bye", (req, res) => { router.get("/store/bye", (req, res) => {
res.json({ res.json({
message: "Come back again!" message: "Come back again!",
}) })
}) })
``` ```
@@ -68,7 +68,7 @@ To do that with the previous example, first, create the file `src/api/hello.js`
export default (router) => { export default (router) => {
router.get("/store/hello", (req, res) => { router.get("/store/hello", (req, res) => {
res.json({ res.json({
message: "Welcome to My Store!" message: "Welcome to My Store!",
}) })
}) })
} }
@@ -82,7 +82,7 @@ Next, create the file `src/api/bye.js` with the following content:
export default (router) => { export default (router) => {
router.get("/store/bye", (req, res) => { router.get("/store/bye", (req, res) => {
res.json({ res.json({
message: "Come back again!" message: "Come back again!",
}) })
}) })
} }
@@ -119,14 +119,12 @@ You can retrieve any registered service in your endpoint using `req.scope.resolv
Heres an example of an endpoint that retrieves the count of products in your store: Heres an example of an endpoint that retrieves the count of products in your store:
```js ```js
router.get('/store/products/count', (req, res) => { router.get("/store/products/count", (req, res) => {
const productService = req.scope.resolve('productService') const productService = req.scope.resolve("productService")
productService productService.count().then((count) => {
.count()
.then((count) => {
res.json({ res.json({
count count,
}) })
}) })
}) })
@@ -141,13 +139,13 @@ Protected routes are routes that should be accessible by logged-in customers onl
To make a route protected, first, import the `authenticate` middleware: To make a route protected, first, import the `authenticate` middleware:
```js ```js
import authenticate from '@medusajs/medusa/dist/api/middlewares/authenticate' import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate"
``` ```
Then, add the middleware to your route: Then, add the middleware to your route:
```jsx ```jsx
router.get('/store/products/count', authenticate(), (req, res) => { router.get("/store/products/count", authenticate(), (req, res) => {
//... //...
}) })
``` ```

View File

@@ -1,7 +1,5 @@
--- ---
title: Create a Service title: Create a Service
--- ---
# Create a Service # Create a Service
@@ -31,7 +29,7 @@ To create a service, you should create a JavaScript file in `src/services` to ho
For example, if you want to create a service `helloService`, create the file `hello.js` in `src/services` with the following content: For example, if you want to create a service `helloService`, create the file `hello.js` in `src/services` with the following content:
```js ```js
import { BaseService } from "medusa-interfaces"; import { BaseService } from "medusa-interfaces"
class HelloService extends BaseService { class HelloService extends BaseService {
getMessage() { getMessage() {
@@ -39,7 +37,7 @@ class HelloService extends BaseService {
} }
} }
export default HelloService; export default HelloService
``` ```
## Service Constructor ## Service Constructor
@@ -85,10 +83,10 @@ constructor({ helloService }) {
To use your custom service in an endpoint, you can use `req.scope.resolve` passing it the services registration name: To use your custom service in an endpoint, you can use `req.scope.resolve` passing it the services registration name:
```js ```js
const helloService = req.scope.resolve('helloService'); const helloService = req.scope.resolve("helloService")
res.json({ res.json({
message: helloService.getMessage() message: helloService.getMessage(),
}) })
``` ```

View File

@@ -1,7 +1,5 @@
--- ---
title: Introduction title: Introduction
--- ---
## Architecture overview ## Architecture overview

View File

@@ -24,6 +24,7 @@ import AdminRegionsResource from "./regions"
import AdminNotificationsResource from "./notifications" import AdminNotificationsResource from "./notifications"
import AdminUploadsResource from "./uploads" import AdminUploadsResource from "./uploads"
import AdminProductTagsResource from "./product-tags" import AdminProductTagsResource from "./product-tags"
import AdminPriceListResource from "./price-lists"
class Admin extends BaseResource { class Admin extends BaseResource {
public auth = new AdminAuthResource(this.client) public auth = new AdminAuthResource(this.client)
@@ -35,6 +36,7 @@ class Admin extends BaseResource {
public giftCards = new AdminGiftCardsResource(this.client) public giftCards = new AdminGiftCardsResource(this.client)
public invites = new AdminInvitesResource(this.client) public invites = new AdminInvitesResource(this.client)
public notes = new AdminNotesResource(this.client) public notes = new AdminNotesResource(this.client)
public priceLists = new AdminPriceListResource(this.client)
public products = new AdminProductsResource(this.client) public products = new AdminProductsResource(this.client)
public productTags = new AdminProductTagsResource(this.client) public productTags = new AdminProductTagsResource(this.client)
public productTypes = new AdminProductTypesResource(this.client) public productTypes = new AdminProductTypesResource(this.client)

View File

@@ -0,0 +1,99 @@
import {
AdminPostPriceListPricesPricesReq,
AdminPostPriceListsPriceListPriceListReq,
AdminPostPriceListsPriceListReq,
AdminPriceListDeleteRes,
AdminPriceListRes,
AdminGetPriceListPaginationParams,
AdminPriceListsListRes,
AdminDeletePriceListPricesPricesReq,
AdminPriceListDeleteBatchRes,
AdminGetPriceListsPriceListProductsParams,
} from "@medusajs/medusa"
import qs from "qs"
import { ResponsePromise } from "../../typings"
import BaseResource from "../base"
class AdminPriceListResource extends BaseResource {
create(
payload: AdminPostPriceListsPriceListReq,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminPriceListRes> {
const path = `/admin/price-lists`
return this.client.request("POST", path, payload, {}, customHeaders)
}
update(
id: string,
payload: AdminPostPriceListsPriceListPriceListReq,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminPriceListRes> {
const path = `/admin/price-lists/${id}`
return this.client.request("POST", path, payload, {}, customHeaders)
}
delete(
id: string,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminPriceListDeleteRes> {
const path = `/admin/price-lists/${id}`
return this.client.request("DELETE", path, {}, {}, customHeaders)
}
retrieve(
id: string,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminPriceListRes> {
const path = `/admin/price-lists/${id}`
return this.client.request("GET", path, {}, {}, customHeaders)
}
list(
query?: AdminGetPriceListPaginationParams,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminPriceListsListRes> {
let path = `/admin/price-lists/`
if (query) {
const queryString = qs.stringify(query)
path = `/admin/price-lists?${queryString}`
}
return this.client.request("GET", path, {}, {}, customHeaders)
}
listProducts(
id: string,
query?: AdminGetPriceListsPriceListProductsParams,
customHeaders: Record<string, any> = {}
): ResponsePromise<any> {
let path = `/admin/price-lists/${id}/products`
if (query) {
const queryString = qs.stringify(query)
path = `/admin/price-lists/${id}/products?${queryString}`
}
return this.client.request("GET", path, {}, {}, customHeaders)
}
addPrices(
id: string,
payload: AdminPostPriceListPricesPricesReq,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminPriceListRes> {
const path = `/admin/price-lists/${id}/prices/batch`
return this.client.request("POST", path, payload, {}, customHeaders)
}
deletePrices(
id: string,
payload: AdminDeletePriceListPricesPricesReq,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminPriceListDeleteBatchRes> {
const path = `/admin/price-lists/${id}/prices/batch`
return this.client.request("DELETE", path, payload, {}, customHeaders)
}
}
export default AdminPriceListResource

View File

@@ -22,7 +22,10 @@ class CustomerResource extends BaseResource {
* @param customHeaders * @param customHeaders
* @return { ResponsePromise<StoreCustomersRes>} * @return { ResponsePromise<StoreCustomersRes>}
*/ */
create(payload: StorePostCustomersReq, customHeaders: Record<string, any> = {}): ResponsePromise<StoreCustomersRes> { create(
payload: StorePostCustomersReq,
customHeaders: Record<string, any> = {}
): ResponsePromise<StoreCustomersRes> {
const path = `/store/customers` const path = `/store/customers`
return this.client.request("POST", path, payload, {}, customHeaders) return this.client.request("POST", path, payload, {}, customHeaders)
} }
@@ -32,7 +35,9 @@ class CustomerResource extends BaseResource {
* @param customHeaders * @param customHeaders
* @return {ResponsePromise<StoreCustomersRes>} * @return {ResponsePromise<StoreCustomersRes>}
*/ */
retrieve(customHeaders: Record<string, any> = {}): ResponsePromise<StoreCustomersRes> { retrieve(
customHeaders: Record<string, any> = {}
): ResponsePromise<StoreCustomersRes> {
const path = `/store/customers/me` const path = `/store/customers/me`
return this.client.request("GET", path, {}, {}, customHeaders) return this.client.request("GET", path, {}, {}, customHeaders)
} }
@@ -45,7 +50,8 @@ class CustomerResource extends BaseResource {
*/ */
update( update(
payload: StorePostCustomersCustomerReq, payload: StorePostCustomersCustomerReq,
customHeaders: Record<string, any> = {}): ResponsePromise<StoreCustomersRes> { customHeaders: Record<string, any> = {}
): ResponsePromise<StoreCustomersRes> {
const path = `/store/customers/me` const path = `/store/customers/me`
return this.client.request("POST", path, payload, {}, customHeaders) return this.client.request("POST", path, payload, {}, customHeaders)
} }
@@ -58,7 +64,8 @@ class CustomerResource extends BaseResource {
*/ */
listOrders( listOrders(
params?: StoreGetCustomersCustomerOrdersParams, params?: StoreGetCustomersCustomerOrdersParams,
customHeaders: Record<string, any> = {}): ResponsePromise<StoreCustomersListOrdersRes> { customHeaders: Record<string, any> = {}
): ResponsePromise<StoreCustomersListOrdersRes> {
let path = `/store/customers/me/orders` let path = `/store/customers/me/orders`
if (params) { if (params) {
const query = qs.stringify(params) const query = qs.stringify(params)
@@ -77,7 +84,8 @@ class CustomerResource extends BaseResource {
*/ */
resetPassword( resetPassword(
payload: StorePostCustomersCustomerPasswordTokenReq, payload: StorePostCustomersCustomerPasswordTokenReq,
customHeaders: Record<string, any> = {}): ResponsePromise<StoreCustomersRes> { customHeaders: Record<string, any> = {}
): ResponsePromise<StoreCustomersRes> {
const path = `/store/customers/password-reset` const path = `/store/customers/password-reset`
return this.client.request("POST", path, payload, {}, customHeaders) return this.client.request("POST", path, payload, {}, customHeaders)
} }

View File

@@ -1076,6 +1076,18 @@
"deleted_at": null, "deleted_at": null,
"metadata": null "metadata": null
}, },
"price_list": {
"id": "pl_ZXDBABWJVW6115VEMZXDBAB",
"name": "test price list",
"description": "test price list",
"type": "products",
"status": "active",
"starts_at": null,
"ends_at": null,
"created_at": "2021-03-16T21:24:35.828Z",
"updated_at": "2021-03-16T21:24:35.828Z",
"deleted_at": null
},
"note": { "note": {
"id": "note_01F0ZXDBAB9KVZWJVW6115VEM7", "id": "note_01F0ZXDBAB9KVZWJVW6115VEM7",
"value": "customer contacted", "value": "customer contacted",

View File

@@ -170,6 +170,89 @@ export const adminHandlers = [
) )
}), }),
rest.post("/admin/price-lists/", (req, res, ctx) => {
const body = req.body as Record<string, any>
return res(
ctx.status(200),
ctx.json({
price_list: {
...fixtures.get("price_list"),
...body,
},
})
)
}),
rest.get("/admin/price-lists/", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
price_lists: fixtures.list("price_list"),
count: 2,
offset: 0,
limit: 10,
})
)
}),
rest.get("/admin/price-lists/:id", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
price_list: fixtures.get("price_list"),
})
)
}),
rest.post("/admin/price-lists/:id", (req, res, ctx) => {
const body = req.body as Record<string, any>
return res(
ctx.status(200),
ctx.json({
price_list: {
...fixtures.get("price_list"),
...body,
id: req.params.id,
},
})
)
}),
rest.delete("/admin/price-lists/:id", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
id: req.params.id,
object: "price_list",
deleted: true,
})
)
}),
rest.delete("/admin/price-lists/:id/prices/batch", (req, res, ctx) => {
const body = req.body as Record<string, any>
return res(
ctx.status(200),
ctx.json({
ids: body.price_ids,
object: "money-amount",
deleted: true,
})
)
}),
rest.post("/admin/price-lists/:id/prices/batch", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
price_list: {
...fixtures.get("price_list"),
id: req.params.id,
},
})
)
}),
rest.post("/admin/return-reasons/", (req, res, ctx) => { rest.post("/admin/return-reasons/", (req, res, ctx) => {
const body = req.body as Record<string, any> const body = req.body as Record<string, any>
return res( return res(

View File

@@ -10,6 +10,7 @@ export * from "./orders"
export * from "./products" export * from "./products"
export * from "./product-tags" export * from "./product-tags"
export * from "./product-types" export * from "./product-types"
export * from "./price-lists"
export * from "./return-reasons" export * from "./return-reasons"
export * from "./regions" export * from "./regions"
export * from "./shipping-options" export * from "./shipping-options"

View File

@@ -0,0 +1,2 @@
export * from "./queries"
export * from "./mutations"

View File

@@ -0,0 +1,108 @@
import {
AdminPriceListRes,
AdminPostPriceListsPriceListPriceListReq,
AdminPostPriceListsPriceListReq,
AdminPostPriceListPricesPricesReq,
AdminDeletePriceListPricesPricesReq,
AdminPriceListDeleteRes,
AdminPriceListDeleteBatchRes,
} from "@medusajs/medusa"
import { Response } from "@medusajs/medusa-js"
import { useMutation, UseMutationOptions, useQueryClient } from "react-query"
import { useMedusa } from "../../../contexts/medusa"
import { buildOptions } from "../../utils/buildOptions"
import { adminPriceListKeys } from "./queries"
export const useAdminCreatePriceList = (
options?: UseMutationOptions<
Response<AdminPriceListRes>,
Error,
AdminPostPriceListsPriceListReq
>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
(payload: AdminPostPriceListsPriceListReq) =>
client.admin.priceLists.create(payload),
buildOptions(queryClient, adminPriceListKeys.lists(), options)
)
}
export const useAdminUpdatePriceList = (
id: string,
options?: UseMutationOptions<
Response<AdminPriceListRes>,
Error,
AdminPostPriceListsPriceListPriceListReq
>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
(payload: AdminPostPriceListsPriceListPriceListReq) =>
client.admin.priceLists.update(id, payload),
buildOptions(
queryClient,
[adminPriceListKeys.detail(id), adminPriceListKeys.lists()],
options
)
)
}
export const useAdminDeletePriceList = (
id: string,
options?: UseMutationOptions<Response<AdminPriceListDeleteRes>, Error, void>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
() => client.admin.priceLists.delete(id),
buildOptions(
queryClient,
[adminPriceListKeys.detail(id), adminPriceListKeys.lists()],
options
)
)
}
export const useAdminCreatePriceListPrices = (
id: string,
options?: UseMutationOptions<
Response<AdminPriceListRes>,
Error,
AdminPostPriceListPricesPricesReq
>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
(payload: AdminPostPriceListPricesPricesReq) =>
client.admin.priceLists.addPrices(id, payload),
buildOptions(queryClient, adminPriceListKeys.lists(), options)
)
}
export const useAdminDeletePriceListPrices = (
id: string,
options?: UseMutationOptions<
Response<AdminPriceListDeleteBatchRes>,
Error,
AdminDeletePriceListPricesPricesReq
>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
(payload: AdminDeletePriceListPricesPricesReq) =>
client.admin.priceLists.deletePrices(id, payload),
buildOptions(
queryClient,
[adminPriceListKeys.detail(id), adminPriceListKeys.lists()],
options
)
)
}

View File

@@ -0,0 +1,70 @@
import {
AdminGetPriceListsPriceListProductsParams,
AdminGetPriceListPaginationParams,
AdminPriceListsListRes,
AdminPriceListRes,
AdminProductsListRes,
} from "@medusajs/medusa"
import { Response } from "@medusajs/medusa-js"
import { useQuery } from "react-query"
import { useMedusa } from "../../../contexts"
import { UseQueryOptionsWrapper } from "../../../types"
import { queryKeysFactory } from "../../utils/index"
const ADMIN_PRICE_LISTS_QUERY_KEY = `admin_price_lists` as const
export const adminPriceListKeys = queryKeysFactory(ADMIN_PRICE_LISTS_QUERY_KEY)
type PriceListQueryKeys = typeof adminPriceListKeys
export const useAdminPriceLists = (
query?: AdminGetPriceListPaginationParams,
options?: UseQueryOptionsWrapper<
Response<AdminPriceListsListRes>,
Error,
ReturnType<PriceListQueryKeys["list"]>
>
) => {
const { client } = useMedusa()
const { data, ...rest } = useQuery(
adminPriceListKeys.list(query),
() => client.admin.priceLists.list(query),
options
)
return { ...data, ...rest } as const
}
export const useAdminPriceListProducts = (
id: string,
query?: AdminGetPriceListsPriceListProductsParams,
options?: UseQueryOptionsWrapper<
Response<AdminProductsListRes>,
Error,
ReturnType<PriceListQueryKeys["detail"]>
>
) => {
const { client } = useMedusa()
const { data, ...rest } = useQuery(
adminPriceListKeys.detail(id),
() => client.admin.priceLists.listProducts(id, query),
options
)
return { ...data, ...rest } as const
}
export const useAdminPriceList = (
id: string,
options?: UseQueryOptionsWrapper<
Response<AdminPriceListRes>,
Error,
ReturnType<PriceListQueryKeys["detail"]>
>
) => {
const { client } = useMedusa()
const { data, ...rest } = useQuery(
adminPriceListKeys.detail(id),
() => client.admin.priceLists.retrieve(id),
options
)
return { ...data, ...rest } as const
}

View File

@@ -0,0 +1,139 @@
import {
useAdminCreatePriceList,
useAdminCreatePriceListPrices,
useAdminDeletePriceList,
useAdminDeletePriceListPrices,
useAdminUpdatePriceList,
} from "../../../../src"
import { renderHook } from "@testing-library/react-hooks"
import { fixtures } from "../../../../mocks/data"
import { createWrapper } from "../../../utils"
import { PriceListType } from "@medusajs/medusa/dist/types/price-list"
describe("useAdminCreatePriceList hook", () => {
test("creates a price list and returns it", async () => {
const priceList = {
name: "talked to customer",
type: PriceListType.SALE,
description: "test",
prices: [],
}
const { result, waitFor } = renderHook(() => useAdminCreatePriceList(), {
wrapper: createWrapper(),
})
result.current.mutate(priceList)
await waitFor(() => result.current.isSuccess)
expect(result.current.data.response.status).toEqual(200)
expect(result.current.data.price_list).toEqual(
expect.objectContaining({
...fixtures.get("price_list"),
...priceList,
})
)
})
})
describe("useAdminUpdatePriceList hook", () => {
test("updates a price list and returns it", async () => {
const priceList = {
name: "talked to customer",
type: PriceListType.SALE,
prices: [],
customer_groups: [],
}
const { result, waitFor } = renderHook(
() => useAdminUpdatePriceList(fixtures.get("price_list").id),
{
wrapper: createWrapper(),
}
)
result.current.mutate(priceList)
await waitFor(() => result.current.isSuccess)
expect(result.current.data.response.status).toEqual(200)
expect(result.current.data.price_list).toEqual(
expect.objectContaining({
...fixtures.get("price_list"),
...priceList,
})
)
})
})
describe("useAdminDeletePriceList hook", () => {
test("deletes a price list", async () => {
const { result, waitFor } = renderHook(
() => useAdminDeletePriceList(fixtures.get("price_list").id),
{
wrapper: createWrapper(),
}
)
result.current.mutate()
await waitFor(() => result.current.isSuccess)
expect(result.current.data.response.status).toEqual(200)
expect(result.current.data).toEqual(
expect.objectContaining({
id: fixtures.get("price_list").id,
deleted: true,
})
)
})
})
describe("useAdminDeletePriceListBatch hook", () => {
test("deletes a money amounts from price list", async () => {
const { result, waitFor } = renderHook(
() => useAdminDeletePriceListPrices(fixtures.get("price_list").id),
{
wrapper: createWrapper(),
}
)
result.current.mutate({ price_ids: [fixtures.get("money_amount").id] })
await waitFor(() => result.current.isSuccess)
expect(result.current.data.response.status).toEqual(200)
expect(result.current.data).toEqual(
expect.objectContaining({
ids: [fixtures.get("money_amount").id],
deleted: true,
})
)
})
})
describe("useAdminDeletePriceList hook", () => {
test("Adds prices to price list", async () => {
const { result, waitFor } = renderHook(
() => useAdminCreatePriceListPrices(fixtures.get("price_list").id),
{
wrapper: createWrapper(),
}
)
result.current.mutate({ prices: [] })
await waitFor(() => result.current.isSuccess)
expect(result.current.data.response.status).toEqual(200)
expect(result.current.data).toEqual(
expect.objectContaining({
price_list: {
...fixtures.get("price_list"),
},
})
)
})
})

View File

@@ -0,0 +1,35 @@
import { useAdminPriceList, useAdminPriceLists } from "../../../../src"
import { renderHook } from "@testing-library/react-hooks"
import { fixtures } from "../../../../mocks/data"
import { createWrapper } from "../../../utils"
describe("useAdminPriceLists hook", () => {
test("returns a list of price lists", async () => {
const priceLists = fixtures.list("price_list")
const { result, waitFor } = renderHook(() => useAdminPriceLists(), {
wrapper: createWrapper(),
})
await waitFor(() => result.current.isSuccess)
expect(result.current.response.status).toEqual(200)
expect(result.current.price_lists).toEqual(priceLists)
})
})
describe("useAdminPriceList hook", () => {
test("returns a price list", async () => {
const priceList = fixtures.get("price_list")
const { result, waitFor } = renderHook(
() => useAdminPriceList(priceList.id),
{
wrapper: createWrapper(),
}
)
await waitFor(() => result.current.isSuccess)
expect(result.current.response.status).toEqual(200)
expect(result.current.price_list).toEqual(priceList)
})
})

View File

@@ -41,6 +41,7 @@ export * from "./routes/admin/shipping-options"
export * from "./routes/admin/regions" export * from "./routes/admin/regions"
export * from "./routes/admin/product-tags" export * from "./routes/admin/product-tags"
export * from "./routes/admin/product-types" export * from "./routes/admin/product-types"
export * from "./routes/admin/price-lists"
// Store // Store
export * from "./routes/store/auth" export * from "./routes/store/auth"

View File

@@ -58,6 +58,12 @@ export type AdminPriceListRes = {
price_list: PriceList price_list: PriceList
} }
export type AdminPriceListDeleteBatchRes = {
ids: string[]
deleted: boolean
object: string
}
export type AdminPriceListDeleteRes = DeleteResponse export type AdminPriceListDeleteRes = DeleteResponse
export type AdminPriceListsListRes = PaginatedResponse & { export type AdminPriceListsListRes = PaginatedResponse & {
@@ -70,4 +76,5 @@ export * from "./delete-price-list"
export * from "./get-price-list" export * from "./get-price-list"
export * from "./list-price-lists" export * from "./list-price-lists"
export * from "./update-price-list" export * from "./update-price-list"
export * from "./delete-prices-batch"
export * from "./list-price-list-products" export * from "./list-price-list-products"

View File

@@ -9,7 +9,6 @@ import {
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { omit } from "lodash" import { omit } from "lodash"
import { Product } from "../../../../models/product" import { Product } from "../../../../models/product"
import { DateComparisonOperator } from "../../../../types/common" import { DateComparisonOperator } from "../../../../types/common"
import { import {

View File

@@ -5,7 +5,6 @@ import { MedusaError } from "medusa-core-utils"
import { Product } from "../../models/product" import { Product } from "../../models/product"
import { ProductService } from "../../services" import { ProductService } from "../../services"
import { getListConfig } from "../../utils/get-query-config" import { getListConfig } from "../../utils/get-query-config"
import { FindConfig } from "../../types/common"
import { FilterableProductProps } from "../../types/product" import { FilterableProductProps } from "../../types/product"
type ListContext = { type ListContext = {