From 8f47e9c3fa4bb230df277cd31c6b00a2172ce280 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 1 Feb 2023 14:42:35 +0100 Subject: [PATCH 1/9] fix: Shipping profile CRUD --- .changeset/mean-ghosts-live.md | 5 + .../api/__tests__/admin/shipping-profile.js | 346 ++++++++++++++++++ integration-tests/api/factories/index.ts | 36 +- .../simple-shipping-option-factory.ts | 18 +- .../simple-shipping-profile-factory.ts | 35 ++ .../create-shipping-profile.ts | 21 +- .../update-shipping-profile.ts | 46 ++- packages/medusa/src/services/product.ts | 12 +- .../medusa/src/services/shipping-option.ts | 4 + .../medusa/src/services/shipping-profile.ts | 86 +++-- packages/medusa/src/types/shipping-profile.ts | 4 +- 11 files changed, 543 insertions(+), 70 deletions(-) create mode 100644 .changeset/mean-ghosts-live.md create mode 100644 integration-tests/api/__tests__/admin/shipping-profile.js create mode 100644 integration-tests/api/factories/simple-shipping-profile-factory.ts diff --git a/.changeset/mean-ghosts-live.md b/.changeset/mean-ghosts-live.md new file mode 100644 index 0000000000000..83f64d2dd742f --- /dev/null +++ b/.changeset/mean-ghosts-live.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): Fixes payloads associated with shipping profile requests, as well as fixes to the shippingProfileService. Also adds test suite for shipping profiles. diff --git a/integration-tests/api/__tests__/admin/shipping-profile.js b/integration-tests/api/__tests__/admin/shipping-profile.js new file mode 100644 index 0000000000000..2abfefe8fb8ff --- /dev/null +++ b/integration-tests/api/__tests__/admin/shipping-profile.js @@ -0,0 +1,346 @@ +const path = require("path") + +const setupServer = require("../../../helpers/setup-server") +const { useApi } = require("../../../helpers/use-api") +const { initDb, useDb } = require("../../../helpers/use-db") +const { + simpleProductFactory, + simpleShippingOptionFactory, + simpleShippingProfileFactory, +} = require("../../factories") +const adminSeeder = require("../../helpers/admin-seeder") + +const adminReqConfig = { + headers: { + Authorization: "Bearer test_token", + }, +} + +jest.setTimeout(30000) + +describe("/admin/shipping-profiles", () => { + let medusaProcess + let dbConnection + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd }) + medusaProcess = await setupServer({ cwd, verbose: false }) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + + medusaProcess.kill() + }) + + describe("GET /admin/shipping-profiles", () => { + beforeEach(async () => { + await adminSeeder(dbConnection) + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("lists shipping profiles", async () => { + const api = useApi() + + const { + data: { shipping_profiles }, + status, + } = await api.get("/admin/shipping-profiles", adminReqConfig) + + expect(status).toEqual(200) + + // Should contain default and gift_card profiles + expect(shipping_profiles.length).toEqual(2) + }) + + it("gets a shipping profile by id", async () => { + const api = useApi() + + const profile = await simpleShippingProfileFactory(dbConnection) + + const { + data: { shipping_profile }, + status, + } = await api.get( + `/admin/shipping-profiles/${profile.id}`, + adminReqConfig + ) + + expect(status).toEqual(200) + expect(shipping_profile).toEqual( + expect.objectContaining({ + ...profile, + updated_at: expect.any(String), + created_at: expect.any(String), + }) + ) + }) + }) + + describe("POST /admin/shipping-profiles", () => { + beforeEach(async () => { + await adminSeeder(dbConnection) + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("creates a custom shipping profile", async () => { + const api = useApi() + + const payload = { + name: "test-profile-2023", + type: "custom", + } + + const { + data: { shipping_profile }, + status, + } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) + + expect(status).toEqual(200) + expect(shipping_profile).toEqual( + expect.objectContaining({ + id: expect.any(String), + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + ...payload, + }) + ) + }) + + it("creates a default shipping profile", async () => { + const api = useApi() + + const payload = { + name: "test-profile-2023", + type: "default", + } + + const { + data: { shipping_profile }, + status, + } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) + + expect(status).toEqual(200) + expect(shipping_profile).toEqual( + expect.objectContaining({ + id: expect.any(String), + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + ...payload, + }) + ) + }) + + it("creates a gift_card shipping profile", async () => { + const api = useApi() + + const payload = { + name: "test-profile-2023", + type: "gift_card", + } + + const { + data: { shipping_profile }, + status, + } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) + + expect(status).toEqual(200) + expect(shipping_profile).toEqual( + expect.objectContaining({ + id: expect.any(String), + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + ...payload, + }) + ) + }) + + it("creates a shipping profile with metadata", async () => { + const api = useApi() + + const payload = { + name: "test-profile-2023", + type: "default", + metadata: { + custom_key: "custom_value", + }, + } + + const { + data: { shipping_profile }, + status, + } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) + + expect(status).toEqual(200) + expect(shipping_profile).toEqual( + expect.objectContaining({ + id: expect.any(String), + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + ...payload, + }) + ) + }) + + it("fails to create a shipping profile with invalid type", async () => { + const api = useApi() + expect.assertions(2) + + const payload = { + name: "test-profile-2023", + type: "invalid", + } + + await api + .post("/admin/shipping-profiles", payload, adminReqConfig) + .catch((err) => { + expect(err.response.status).toEqual(400) + expect(err.response.data.message).toEqual( + "type must be one of 'default', 'custom', 'gift_card'" + ) + }) + }) + + it("updates a shipping profile", async () => { + const api = useApi() + + const testProducts = await Promise.all( + [...Array(5).keys()].map(async () => { + return await simpleProductFactory(dbConnection) + }) + ) + + const testShippingOptions = await Promise.all( + [...Array(5).keys()].map(async () => { + return await simpleShippingOptionFactory(dbConnection) + }) + ) + + const payload = { + name: "test-profile-2023", + type: "custom", + metadata: { + my_key: "my_value", + }, + } + + const { + data: { shipping_profile: created }, + } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) + + const updatePayload = { + name: "test-profile-2023-updated", + products: testProducts.map((p) => p.id), + shipping_options: testShippingOptions.map((o) => o.id), + metadata: { + my_key: "", + my_new_key: "my_new_value", + }, + } + + const { + data: { shipping_profile }, + status, + } = await api.post( + `/admin/shipping-profiles/${created.id}`, + updatePayload, + adminReqConfig + ) + + expect(status).toEqual(200) + expect(shipping_profile).toEqual( + expect.objectContaining({ + name: "test-profile-2023-updated", + created_at: expect.any(String), + updated_at: expect.any(String), + metadata: { + my_new_key: "my_new_value", + }, + deleted_at: null, + type: "custom", + }) + ) + + const { + data: { products }, + } = await api.get(`/admin/products`, adminReqConfig) + + expect(products.length).toEqual(5) + expect(products).toEqual( + expect.arrayContaining( + testProducts.map((p) => { + return expect.objectContaining({ + id: p.id, + profile_id: shipping_profile.id, + }) + }) + ) + ) + + const { + data: { shipping_options }, + } = await api.get(`/admin/shipping-options`, adminReqConfig) + + const numberOfShippingOptionsWithProfile = shipping_options.filter( + (so) => so.profile_id === shipping_profile.id + ).length + + expect(numberOfShippingOptionsWithProfile).toEqual(5) + expect(shipping_options).toEqual( + expect.arrayContaining( + testShippingOptions.map((o) => { + return expect.objectContaining({ + id: o.id, + profile_id: shipping_profile.id, + }) + }) + ) + ) + }) + }) + + describe("DELETE /admin/shipping-profiles", () => { + beforeEach(async () => { + await adminSeeder(dbConnection) + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("deletes a shipping profile", async () => { + expect.assertions(2) + + const api = useApi() + + const profile = await simpleShippingProfileFactory(dbConnection) + + const { status } = await api.delete( + `/admin/shipping-profiles/${profile.id}`, + adminReqConfig + ) + + expect(status).toEqual(200) + await api + .get(`/admin/shipping-profiles/${profile.id}`, adminReqConfig) + .catch((err) => { + expect(err.response.status).toEqual(404) + }) + }) + }) +}) diff --git a/integration-tests/api/factories/index.ts b/integration-tests/api/factories/index.ts index b0e578e81e145..6647208a5b24d 100644 --- a/integration-tests/api/factories/index.ts +++ b/integration-tests/api/factories/index.ts @@ -1,25 +1,25 @@ -export * from "./simple-gift-card-factory" -export * from "./simple-payment-factory" export * from "./simple-batch-job-factory" -export * from "./simple-discount-factory" -export * from "./simple-order-factory" export * from "./simple-cart-factory" -export * from "./simple-region-factory" +export * from "./simple-custom-shipping-option-factory" +export * from "./simple-customer-factory" +export * from "./simple-discount-factory" +export * from "./simple-gift-card-factory" export * from "./simple-line-item-factory" +export * from "./simple-order-edit-factory" +export * from "./simple-order-factory" +export * from "./simple-order-item-change-factory" +export * from "./simple-payment-collection-factory" +export * from "./simple-payment-factory" +export * from "./simple-price-list-factory" +export * from "./simple-product-category-factory" export * from "./simple-product-factory" -export * from "./simple-product-variant-factory" export * from "./simple-product-tax-rate-factory" -export * from "./simple-shipping-tax-rate-factory" -export * from "./simple-tax-rate-factory" -export * from "./simple-shipping-option-factory" -export * from "./simple-shipping-method-factory" export * from "./simple-product-type-tax-rate-factory" -export * from "./simple-price-list-factory" -export * from "./simple-batch-job-factory" +export * from "./simple-product-variant-factory" +export * from "./simple-region-factory" export * from "./simple-sales-channel-factory" -export * from "./simple-custom-shipping-option-factory" -export * from "./simple-payment-collection-factory" -export * from "./simple-order-edit-factory" -export * from "./simple-order-item-change-factory" -export * from "./simple-customer-factory" -export * from "./simple-product-category-factory" +export * from "./simple-shipping-method-factory" +export * from "./simple-shipping-option-factory" +export * from "./simple-shipping-profile-factory" +export * from "./simple-shipping-tax-rate-factory" +export * from "./simple-tax-rate-factory" diff --git a/integration-tests/api/factories/simple-shipping-option-factory.ts b/integration-tests/api/factories/simple-shipping-option-factory.ts index d77901a271191..f3462eb0c552f 100644 --- a/integration-tests/api/factories/simple-shipping-option-factory.ts +++ b/integration-tests/api/factories/simple-shipping-option-factory.ts @@ -7,28 +7,29 @@ import { } from "@medusajs/medusa" import faker from "faker" import { Connection } from "typeorm" +import { simpleRegionFactory } from "./simple-region-factory" export type ShippingOptionFactoryData = { id?: string name?: string - region_id: string + region_id?: string is_return?: boolean is_giftcard?: boolean price?: number price_type?: ShippingOptionPriceType includes_tax?: boolean data?: object - requirements: ShippingOptionRequirementData[] + requirements?: ShippingOptionRequirementData[] } type ShippingOptionRequirementData = { - type: 'min_subtotal' | 'max_subtotal' + type: "min_subtotal" | "max_subtotal" amount: number } export const simpleShippingOptionFactory = async ( connection: Connection, - data: ShippingOptionFactoryData, + data: ShippingOptionFactoryData = {}, seed?: number ): Promise => { if (typeof seed !== "undefined") { @@ -44,11 +45,18 @@ export const simpleShippingOptionFactory = async ( type: ShippingProfileType.GIFT_CARD, }) + let region_id = data.region_id + + if (!region_id) { + const { id } = await simpleRegionFactory(connection) + region_id = id + } + const shippingOptionData = { id: data.id ?? `simple-so-${Math.random() * 1000}`, name: data.name || "Test Method", is_return: data.is_return ?? false, - region_id: data.region_id, + region_id: region_id, provider_id: "test-ful", profile_id: data.is_giftcard ? gcProfile.id : defaultProfile.id, price_type: data.price_type ?? ShippingOptionPriceType.FLAT_RATE, diff --git a/integration-tests/api/factories/simple-shipping-profile-factory.ts b/integration-tests/api/factories/simple-shipping-profile-factory.ts new file mode 100644 index 0000000000000..ffae978940356 --- /dev/null +++ b/integration-tests/api/factories/simple-shipping-profile-factory.ts @@ -0,0 +1,35 @@ +import { ShippingProfile, ShippingProfileType } from "@medusajs/medusa" +import faker from "faker" +import { Connection } from "typeorm" + +export type ShippingProfileFactoryData = { + id?: string + name?: string + type?: ShippingProfileType + metadata?: Record +} + +export const simpleShippingProfileFactory = async ( + connection: Connection, + data: ShippingOptionFactoryData = {}, + seed?: number +): Promise => { + if (typeof seed !== "undefined") { + faker.seed(seed) + } + + const manager = connection.manager + + const shippingProfileData = { + id: data.id ?? `simple-sp-${Math.random() * 1000}`, + name: data.name || `sp-${Math.random() * 1000}`, + type: data.type || ShippingProfileType.DEFAULT, + metadata: data.metadata, + products: [], + shipping_options: [], + } + + const created = manager.create(ShippingProfile, shippingProfileData) + + return await manager.save(created) +} diff --git a/packages/medusa/src/api/routes/admin/shipping-profiles/create-shipping-profile.ts b/packages/medusa/src/api/routes/admin/shipping-profiles/create-shipping-profile.ts index 4f4b5696dae01..aceb6cea87941 100644 --- a/packages/medusa/src/api/routes/admin/shipping-profiles/create-shipping-profile.ts +++ b/packages/medusa/src/api/routes/admin/shipping-profiles/create-shipping-profile.ts @@ -1,7 +1,8 @@ -import { IsString } from "class-validator" +import { IsEnum, IsObject, IsOptional, IsString } from "class-validator" +import { EntityManager } from "typeorm" +import { ShippingProfileType } from "../../../../models" import { ShippingProfileService } from "../../../../services" import { validator } from "../../../../utils/validator" -import { EntityManager } from "typeorm" /** * @oas [post] /shipping-profiles @@ -84,12 +85,26 @@ export default async (req, res) => { * type: object * required: * - name + * - type * properties: * name: - * description: "The name of the Shipping Profile" + * description: The name of the Shipping Profile * type: string + * type: + * description: The type of the Shipping Profile + * type: string + * enum: [default, gift_card, custom] */ export class AdminPostShippingProfilesReq { @IsString() name: string + + @IsEnum(ShippingProfileType, { + message: "type must be one of 'default', 'custom', 'gift_card'", + }) + type: ShippingProfileType + + @IsOptional() + @IsObject() + metadata?: Record } diff --git a/packages/medusa/src/api/routes/admin/shipping-profiles/update-shipping-profile.ts b/packages/medusa/src/api/routes/admin/shipping-profiles/update-shipping-profile.ts index 2f73275b77a97..46f4bae74df6e 100644 --- a/packages/medusa/src/api/routes/admin/shipping-profiles/update-shipping-profile.ts +++ b/packages/medusa/src/api/routes/admin/shipping-profiles/update-shipping-profile.ts @@ -1,8 +1,15 @@ -import { IsOptional, IsString } from "class-validator" +import { + IsArray, + IsEnum, + IsObject, + IsOptional, + IsString, +} from "class-validator" +import { EntityManager } from "typeorm" +import { ShippingProfileType } from "../../../../models" import { ShippingProfileService } from "../../../../services" import { validator } from "../../../../utils/validator" -import { EntityManager } from "typeorm" /** * @oas [post] /shipping-profiles/{id} @@ -93,11 +100,44 @@ export default async (req, res) => { * type: object * properties: * name: - * description: "The name of the Shipping Profile" + * description: The name of the Shipping Profile + * type: string + * metadata: + * description: An optional set of key-value pairs with additional information. + * type: object + * type: + * description: The type of the Shipping Profile * type: string + * enum: [default, gift_card, custom] + * products: + * description: An optional array of product ids to associate with the Shipping Profile + * type: array + * shipping_options: + * description: An optional array of shipping option ids to associate with the Shipping Profile + * type: array */ export class AdminPostShippingProfilesProfileReq { @IsString() @IsOptional() name?: string + + @IsOptional() + @IsObject() + metadata?: Record + + @IsOptional() + @IsEnum(ShippingProfileType, { + message: "type must be one of 'default', 'custom', 'gift_card'", + }) + type?: ShippingProfileType + + @IsOptional() + @IsArray() + @IsString({ each: true }) + products?: string[] + + @IsOptional() + @IsArray() + @IsString({ each: true }) + shipping_options?: string[] } diff --git a/packages/medusa/src/services/product.ts b/packages/medusa/src/services/product.ts index 71ba150f5f4d3..507a1e5627444 100644 --- a/packages/medusa/src/services/product.ts +++ b/packages/medusa/src/services/product.ts @@ -1,18 +1,18 @@ import { FlagRouter } from "../utils/flag-router" import { isDefined, MedusaError } from "medusa-core-utils" -import { EntityManager, In } from "typeorm" +import { EntityManager } from "typeorm" import { ProductVariantService, SearchService } from "." import { TransactionBaseService } from "../interfaces" import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels" import { Product, + ProductCategory, ProductOption, ProductTag, ProductType, ProductVariant, SalesChannel, - ProductCategory, } from "../models" import { ImageRepository } from "../repositories/image" import { @@ -446,7 +446,9 @@ class ProductService extends TransactionBaseService { if (categories?.length) { const categoryIds = categories.map((c) => c.id) - const categoryRecords = categoryIds.map((id) => ({ id } as ProductCategory)) + const categoryRecords = categoryIds.map( + (id) => ({ id } as ProductCategory) + ) product.categories = categoryRecords } @@ -561,7 +563,9 @@ class ProductService extends TransactionBaseService { if (categories?.length) { const categoryIds = categories.map((c) => c.id) - const categoryRecords = categoryIds.map((id) => ({ id } as ProductCategory)) + const categoryRecords = categoryIds.map( + (id) => ({ id } as ProductCategory) + ) product.categories = categoryRecords } diff --git a/packages/medusa/src/services/shipping-option.ts b/packages/medusa/src/services/shipping-option.ts index c674ac6dd9e12..76e2481c06c25 100644 --- a/packages/medusa/src/services/shipping-option.ts +++ b/packages/medusa/src/services/shipping-option.ts @@ -663,6 +663,10 @@ class ShippingOptionService extends TransactionBaseService { optionWithValidatedPrice.admin_only = update.admin_only } + if (isDefined(update.profile_id)) { + optionWithValidatedPrice.profile_id = update.profile_id + } + if ( this.featureFlagRouter_.isFeatureEnabled( TaxInclusivePricingFeatureFlag.key diff --git a/packages/medusa/src/services/shipping-profile.ts b/packages/medusa/src/services/shipping-profile.ts index 49a34192c90c7..e70f21f3e564a 100644 --- a/packages/medusa/src/services/shipping-profile.ts +++ b/packages/medusa/src/services/shipping-profile.ts @@ -284,7 +284,7 @@ class ShippingProfileService extends TransactionBaseService { this.shippingProfileRepository_ ) - const profile = await this.retrieve(profileId, { + let profile = await this.retrieve(profileId, { relations: [ "products", "products.profile", @@ -295,27 +295,22 @@ class ShippingProfileService extends TransactionBaseService { const { metadata, products, shipping_options, ...rest } = update - if (metadata) { - profile.metadata = setMetadata(profile, metadata) - } - if (products) { - const productServiceTx = this.productService_.withTransaction(manager) - for (const pId of products) { - await productServiceTx.update(pId, { - profile_id: profile.id, - }) - } + profile = await this.withTransaction(manager).addProducts( + profile.id, + products + ) } if (shipping_options) { - const shippingOptionServiceTx = - this.shippingOptionService_.withTransaction(manager) - for (const oId of shipping_options) { - await shippingOptionServiceTx.update(oId, { - profile_id: profile.id, - }) - } + profile = await this.withTransaction(manager).addShippingOptions( + profile.id, + shipping_options + ) + } + + if (metadata) { + profile.metadata = setMetadata(profile, metadata) } for (const [key, value] of Object.entries(rest)) { @@ -352,22 +347,31 @@ class ShippingProfileService extends TransactionBaseService { } /** - * Adds a product to a profile. The method is idempotent, so multiple calls - * with the same product variant will have the same result. - * @param profileId - the profile to add the product to. - * @param productId - the product to add. + * Adds an array of products to the profile. + * @param profileId - the profile to add the products to. + * @param productIds - the products to add. * @return the result of update */ - async addProduct( + async addProducts( profileId: string, - productId: string + productIds: string[] ): Promise { return await this.atomicPhase_(async (manager) => { - await this.productService_ - .withTransaction(manager) - .update(productId, { profile_id: profileId }) + const productServiceTx = this.productService_.withTransaction(manager) + for (const pId of productIds) { + await productServiceTx.update(pId, { + profile_id: profileId, + }) + } - return await this.retrieve(profileId) + return await this.retrieve(profileId, { + relations: [ + "products", + "products.profile", + "shipping_options", + "shipping_options.profile", + ], + }) }) } @@ -375,20 +379,30 @@ class ShippingProfileService extends TransactionBaseService { * Adds a shipping option to the profile. The shipping option can be used to * fulfill the products in the products field. * @param profileId - the profile to apply the shipping option to - * @param optionId - the option to add to the profile + * @param optionIds - the option to add to the profile * @return the result of the model update operation */ - async addShippingOption( + async addShippingOptions( profileId: string, - optionId: string + optionIds: string[] ): Promise { return await this.atomicPhase_(async (manager) => { - await this.shippingOptionService_ - .withTransaction(manager) - .update(optionId, { profile_id: profileId }) + const shippingOptionServiceTx = + this.shippingOptionService_.withTransaction(manager) + for (const oId of optionIds) { + await shippingOptionServiceTx.update(oId, { + profile_id: profileId, + }) + } - const updated = await this.retrieve(profileId) - return updated + return await this.retrieve(profileId, { + relations: [ + "products", + "products.profile", + "shipping_options", + "shipping_options.profile", + ], + }) }) } diff --git a/packages/medusa/src/types/shipping-profile.ts b/packages/medusa/src/types/shipping-profile.ts index 2a4db42f3761b..e813ff027f464 100644 --- a/packages/medusa/src/types/shipping-profile.ts +++ b/packages/medusa/src/types/shipping-profile.ts @@ -1,7 +1,9 @@ -import { Product, ShippingOption, ShippingProfileType } from "../models" +import { ShippingProfileType } from "../models" export type CreateShippingProfile = { name: string + type: ShippingProfileType + metadata?: Record } export type UpdateShippingProfile = { From 36c76b08c49bb2e9190490a540b16ba3d863aa22 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 1 Feb 2023 14:52:06 +0100 Subject: [PATCH 2/9] fix: OAS indentation --- .../admin/shipping-profiles/create-shipping-profile.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/medusa/src/api/routes/admin/shipping-profiles/create-shipping-profile.ts b/packages/medusa/src/api/routes/admin/shipping-profiles/create-shipping-profile.ts index aceb6cea87941..4b53042154df4 100644 --- a/packages/medusa/src/api/routes/admin/shipping-profiles/create-shipping-profile.ts +++ b/packages/medusa/src/api/routes/admin/shipping-profiles/create-shipping-profile.ts @@ -90,10 +90,10 @@ export default async (req, res) => { * name: * description: The name of the Shipping Profile * type: string - * type: - * description: The type of the Shipping Profile - * type: string - * enum: [default, gift_card, custom] + * type: + * description: The type of the Shipping Profile + * type: string + * enum: [default, gift_card, custom] */ export class AdminPostShippingProfilesReq { @IsString() From 3290a054ea0d3d6ccfb2bf3f846fc79fc7efd27f Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 1 Feb 2023 15:02:26 +0100 Subject: [PATCH 3/9] fix: use setMetadata method in create --- packages/medusa/src/services/shipping-profile.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/medusa/src/services/shipping-profile.ts b/packages/medusa/src/services/shipping-profile.ts index e70f21f3e564a..471ad1d90a396 100644 --- a/packages/medusa/src/services/shipping-profile.ts +++ b/packages/medusa/src/services/shipping-profile.ts @@ -260,7 +260,14 @@ class ShippingProfileService extends TransactionBaseService { ) } - const created = profileRepository.create(profile) + const { metadata, ...rest } = profile + + const created = profileRepository.create(rest) + + if (metadata) { + created.metadata = setMetadata(created, metadata) + } + const result = await profileRepository.save(created) return result }) From 3b37feab3fffae9b1f66d633cd8dc783c0af33cd Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 1 Feb 2023 15:27:03 +0100 Subject: [PATCH 4/9] fix unit tests --- .../__tests__/create-shipping-profile.js | 2 ++ .../src/services/__mocks__/shipping-profile.js | 18 +++++++++--------- .../src/services/__tests__/shipping-profile.js | 14 ++++++-------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/medusa/src/api/routes/admin/shipping-profiles/__tests__/create-shipping-profile.js b/packages/medusa/src/api/routes/admin/shipping-profiles/__tests__/create-shipping-profile.js index 9ce1b96d646a7..37c19ed827aed 100644 --- a/packages/medusa/src/api/routes/admin/shipping-profiles/__tests__/create-shipping-profile.js +++ b/packages/medusa/src/api/routes/admin/shipping-profiles/__tests__/create-shipping-profile.js @@ -10,6 +10,7 @@ describe("POST /admin/shipping-profiles", () => { subject = await request("POST", "/admin/shipping-profiles", { payload: { name: "Test Profile", + type: "default", }, adminSession: { jwt: { @@ -27,6 +28,7 @@ describe("POST /admin/shipping-profiles", () => { expect(ShippingProfileServiceMock.create).toHaveBeenCalledTimes(1) expect(ShippingProfileServiceMock.create).toHaveBeenCalledWith({ name: "Test Profile", + type: "default", }) }) }) diff --git a/packages/medusa/src/services/__mocks__/shipping-profile.js b/packages/medusa/src/services/__mocks__/shipping-profile.js index c29a205d5def5..c31b3bd646671 100644 --- a/packages/medusa/src/services/__mocks__/shipping-profile.js +++ b/packages/medusa/src/services/__mocks__/shipping-profile.js @@ -19,10 +19,10 @@ export const ShippingProfileServiceMock = { withTransaction: function () { return this }, - update: jest.fn().mockImplementation(data => { + update: jest.fn().mockImplementation((data) => { return Promise.resolve() }), - create: jest.fn().mockImplementation(data => { + create: jest.fn().mockImplementation((data) => { return Promise.resolve(data) }), createDefault: jest.fn().mockImplementation(() => { @@ -31,7 +31,7 @@ export const ShippingProfileServiceMock = { createGiftCardDefault: jest.fn().mockImplementation(() => { return Promise.resolve() }), - retrieve: jest.fn().mockImplementation(data => { + retrieve: jest.fn().mockImplementation((data) => { if (data === IdMap.getId("default")) { return Promise.resolve(profiles.default) } @@ -40,13 +40,13 @@ export const ShippingProfileServiceMock = { } return Promise.resolve(profiles.default) }), - retrieveGiftCardDefault: jest.fn().mockImplementation(data => { + retrieveGiftCardDefault: jest.fn().mockImplementation((data) => { return Promise.resolve({ id: IdMap.getId("giftCardProfile") }) }), - retrieveDefault: jest.fn().mockImplementation(data => { + retrieveDefault: jest.fn().mockImplementation((data) => { return Promise.resolve({ id: IdMap.getId("default_shipping_profile") }) }), - list: jest.fn().mockImplementation(selector => { + list: jest.fn().mockImplementation((selector) => { if (!selector) { return Promise.resolve([]) } @@ -135,10 +135,10 @@ export const ShippingProfileServiceMock = { ]) } }), - decorate: jest.fn().mockImplementation(d => Promise.resolve(d)), - addShippingOption: jest.fn().mockImplementation(() => Promise.resolve()), + decorate: jest.fn().mockImplementation((d) => Promise.resolve(d)), + addShippingOptions: jest.fn().mockImplementation(() => Promise.resolve()), removeShippingOption: jest.fn().mockImplementation(() => Promise.resolve()), - addProduct: jest.fn().mockImplementation(() => Promise.resolve()), + addProducts: jest.fn().mockImplementation(() => Promise.resolve()), removeProduct: jest.fn().mockImplementation(() => Promise.resolve()), fetchCartOptions: jest.fn().mockImplementation(() => { return Promise.resolve([{ id: IdMap.getId("cartShippingOption") }]) diff --git a/packages/medusa/src/services/__tests__/shipping-profile.js b/packages/medusa/src/services/__tests__/shipping-profile.js index c694721411701..7429d5e870f8a 100644 --- a/packages/medusa/src/services/__tests__/shipping-profile.js +++ b/packages/medusa/src/services/__tests__/shipping-profile.js @@ -142,10 +142,9 @@ describe("ShippingProfileService", () => { }) it("add product to profile successfully", async () => { - await profileService.addProduct( - IdMap.getId("validId"), - IdMap.getId("product2") - ) + await profileService.addProducts(IdMap.getId("validId"), [ + IdMap.getId("product2"), + ]) expect(productService.update).toBeCalledTimes(1) expect(productService.update).toBeCalledWith(IdMap.getId("product2"), { @@ -317,10 +316,9 @@ describe("ShippingProfileService", () => { }) it("add shipping option to profile successfully", async () => { - await profileService.addShippingOption( - IdMap.getId("validId"), - IdMap.getId("freeShipping") - ) + await profileService.addShippingOptions(IdMap.getId("validId"), [ + IdMap.getId("freeShipping"), + ]) expect(shippingOptionService.update).toBeCalledTimes(1) expect(shippingOptionService.update).toBeCalledWith( From 77e9e4c4ff6128b526a89a53aa240fb54d82695b Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 2 Feb 2023 09:41:40 +0100 Subject: [PATCH 5/9] make changes backward compatible --- .../services/__mocks__/shipping-profile.js | 4 +- .../services/__tests__/shipping-profile.js | 4 +- .../medusa/src/services/shipping-profile.ts | 39 ++++++++++++------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/packages/medusa/src/services/__mocks__/shipping-profile.js b/packages/medusa/src/services/__mocks__/shipping-profile.js index c31b3bd646671..36cc7827d5ffe 100644 --- a/packages/medusa/src/services/__mocks__/shipping-profile.js +++ b/packages/medusa/src/services/__mocks__/shipping-profile.js @@ -136,9 +136,9 @@ export const ShippingProfileServiceMock = { } }), decorate: jest.fn().mockImplementation((d) => Promise.resolve(d)), - addShippingOptions: jest.fn().mockImplementation(() => Promise.resolve()), + addShippingOption: jest.fn().mockImplementation(() => Promise.resolve()), removeShippingOption: jest.fn().mockImplementation(() => Promise.resolve()), - addProducts: jest.fn().mockImplementation(() => Promise.resolve()), + addProduct: jest.fn().mockImplementation(() => Promise.resolve()), removeProduct: jest.fn().mockImplementation(() => Promise.resolve()), fetchCartOptions: jest.fn().mockImplementation(() => { return Promise.resolve([{ id: IdMap.getId("cartShippingOption") }]) diff --git a/packages/medusa/src/services/__tests__/shipping-profile.js b/packages/medusa/src/services/__tests__/shipping-profile.js index 7429d5e870f8a..daee763df4def 100644 --- a/packages/medusa/src/services/__tests__/shipping-profile.js +++ b/packages/medusa/src/services/__tests__/shipping-profile.js @@ -142,7 +142,7 @@ describe("ShippingProfileService", () => { }) it("add product to profile successfully", async () => { - await profileService.addProducts(IdMap.getId("validId"), [ + await profileService.addProduct(IdMap.getId("validId"), [ IdMap.getId("product2"), ]) @@ -316,7 +316,7 @@ describe("ShippingProfileService", () => { }) it("add shipping option to profile successfully", async () => { - await profileService.addShippingOptions(IdMap.getId("validId"), [ + await profileService.addShippingOption(IdMap.getId("validId"), [ IdMap.getId("freeShipping"), ]) diff --git a/packages/medusa/src/services/shipping-profile.ts b/packages/medusa/src/services/shipping-profile.ts index 471ad1d90a396..f1ec483af0408 100644 --- a/packages/medusa/src/services/shipping-profile.ts +++ b/packages/medusa/src/services/shipping-profile.ts @@ -303,14 +303,14 @@ class ShippingProfileService extends TransactionBaseService { const { metadata, products, shipping_options, ...rest } = update if (products) { - profile = await this.withTransaction(manager).addProducts( + profile = await this.withTransaction(manager).addProduct( profile.id, products ) } if (shipping_options) { - profile = await this.withTransaction(manager).addShippingOptions( + profile = await this.withTransaction(manager).addShippingOption( profile.id, shipping_options ) @@ -354,19 +354,26 @@ class ShippingProfileService extends TransactionBaseService { } /** - * Adds an array of products to the profile. + * Adds a product of an array of products to the profile. * @param profileId - the profile to add the products to. - * @param productIds - the products to add. + * @param productId - the ID of the product or multiple products to add. * @return the result of update */ - async addProducts( + async addProduct( profileId: string, - productIds: string[] + productId: string | string[] ): Promise { return await this.atomicPhase_(async (manager) => { const productServiceTx = this.productService_.withTransaction(manager) - for (const pId of productIds) { - await productServiceTx.update(pId, { + + if (Array.isArray(productId)) { + for (const pId of productId) { + await productServiceTx.update(pId, { + profile_id: profileId, + }) + } + } else { + await productServiceTx.update(productId, { profile_id: profileId, }) } @@ -386,18 +393,24 @@ class ShippingProfileService extends TransactionBaseService { * Adds a shipping option to the profile. The shipping option can be used to * fulfill the products in the products field. * @param profileId - the profile to apply the shipping option to - * @param optionIds - the option to add to the profile + * @param optionId - the ID of the option or multiple options to add to the profile * @return the result of the model update operation */ - async addShippingOptions( + async addShippingOption( profileId: string, - optionIds: string[] + optionId: string | string[] ): Promise { return await this.atomicPhase_(async (manager) => { const shippingOptionServiceTx = this.shippingOptionService_.withTransaction(manager) - for (const oId of optionIds) { - await shippingOptionServiceTx.update(oId, { + if (Array.isArray(optionId)) { + for (const oId of optionId) { + await shippingOptionServiceTx.update(oId, { + profile_id: profileId, + }) + } + } else { + await shippingOptionServiceTx.update(optionId, { profile_id: profileId, }) } From 776c8d6331f1beee2cefd316b0bb15e589ead7bf Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 2 Feb 2023 11:36:06 +0100 Subject: [PATCH 6/9] add bulk methods to repos --- packages/medusa/src/repositories/product.ts | 13 ++++++ .../src/repositories/shipping-option.ts | 17 +++++++- .../medusa/src/services/shipping-profile.ts | 40 ++++++------------- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/packages/medusa/src/repositories/product.ts b/packages/medusa/src/repositories/product.ts index 98aab6edf18c8..b5854797d2f5a 100644 --- a/packages/medusa/src/repositories/product.ts +++ b/packages/medusa/src/repositories/product.ts @@ -330,6 +330,19 @@ export class ProductRepository extends Repository { return result[0] } + public async bulkSetShippingProfile( + productIds: string[], + shippingProfileId: string + ): Promise { + await this.createQueryBuilder() + .update(Product) + .set({ profile_id: shippingProfileId }) + .where({ id: In(productIds) }) + .execute() + + return this.findByIds(productIds) + } + public async bulkAddToCollection( productIds: string[], collectionId: string diff --git a/packages/medusa/src/repositories/shipping-option.ts b/packages/medusa/src/repositories/shipping-option.ts index eb9a7d9c5d166..b34f0505e2700 100644 --- a/packages/medusa/src/repositories/shipping-option.ts +++ b/packages/medusa/src/repositories/shipping-option.ts @@ -1,5 +1,18 @@ -import { EntityRepository, Repository } from "typeorm" +import { EntityRepository, In, Repository } from "typeorm" import { ShippingOption } from "../models/shipping-option" @EntityRepository(ShippingOption) -export class ShippingOptionRepository extends Repository {} +export class ShippingOptionRepository extends Repository { + public async bulkSetShippingProfile( + shippingOptionIds: string[], + shippingProfileId: string + ): Promise { + await this.createQueryBuilder() + .update(ShippingOption) + .set({ profile_id: shippingProfileId }) + .where({ id: In(shippingOptionIds) }) + .execute() + + return this.findByIds(shippingOptionIds) + } +} diff --git a/packages/medusa/src/services/shipping-profile.ts b/packages/medusa/src/services/shipping-profile.ts index f1ec483af0408..b6bd60ec490b6 100644 --- a/packages/medusa/src/services/shipping-profile.ts +++ b/packages/medusa/src/services/shipping-profile.ts @@ -303,17 +303,11 @@ class ShippingProfileService extends TransactionBaseService { const { metadata, products, shipping_options, ...rest } = update if (products) { - profile = await this.withTransaction(manager).addProduct( - profile.id, - products - ) + profile = await this.addProduct(profile.id, products) } if (shipping_options) { - profile = await this.withTransaction(manager).addShippingOption( - profile.id, - shipping_options - ) + profile = await this.addShippingOption(profile.id, shipping_options) } if (metadata) { @@ -364,19 +358,14 @@ class ShippingProfileService extends TransactionBaseService { productId: string | string[] ): Promise { return await this.atomicPhase_(async (manager) => { - const productServiceTx = this.productService_.withTransaction(manager) + const productRepo = manager.getCustomRepository(this.productRepository_) - if (Array.isArray(productId)) { - for (const pId of productId) { - await productServiceTx.update(pId, { - profile_id: profileId, - }) - } - } else { - await productServiceTx.update(productId, { - profile_id: profileId, - }) - } + const { id } = await this.retrieve(profileId) + + await productRepo.bulkSetShippingProfile( + Array.isArray(productId) ? productId : [productId], + id + ) return await this.retrieve(profileId, { relations: [ @@ -403,14 +392,9 @@ class ShippingProfileService extends TransactionBaseService { return await this.atomicPhase_(async (manager) => { const shippingOptionServiceTx = this.shippingOptionService_.withTransaction(manager) - if (Array.isArray(optionId)) { - for (const oId of optionId) { - await shippingOptionServiceTx.update(oId, { - profile_id: profileId, - }) - } - } else { - await shippingOptionServiceTx.update(optionId, { + + for (const oId of Array.isArray(optionId) ? optionId : [optionId]) { + await shippingOptionServiceTx.update(oId, { profile_id: profileId, }) } From b13d5c2edea01a71d511eebbeda8d6fbaf04b644 Mon Sep 17 00:00:00 2001 From: Kasper Date: Mon, 6 Feb 2023 10:08:16 +0100 Subject: [PATCH 7/9] merge develop into fix/create-shipping-profile --- packages/medusa/src/repositories/product.ts | 19 +++++++++ .../src/repositories/shipping-option.ts | 8 ++-- .../medusa/src/services/__mocks__/product.js | 3 +- .../src/services/__mocks__/shipping-option.js | 1 + .../services/__tests__/shipping-profile.js | 42 ++++++++++--------- packages/medusa/src/services/product.ts | 27 +++++++++++- .../medusa/src/services/shipping-option.ts | 21 +++++++++- .../medusa/src/services/shipping-profile.ts | 21 ++++------ 8 files changed, 103 insertions(+), 39 deletions(-) diff --git a/packages/medusa/src/repositories/product.ts b/packages/medusa/src/repositories/product.ts index b5854797d2f5a..a3f5f548368fd 100644 --- a/packages/medusa/src/repositories/product.ts +++ b/packages/medusa/src/repositories/product.ts @@ -499,6 +499,25 @@ export class ProductRepository extends Repository { ) } + /** + * Upserts shipping profile for products + * @param ids IDs of products to update + * @param shippingProfileId ID of shipping profile to assign to products + * @returns updated products + */ + public async upsertShippingProfile( + ids: string[], + shippingProfileId: string + ): Promise { + await this.createQueryBuilder() + .update(Product) + .set({ profile_id: shippingProfileId }) + .where({ id: In(ids) }) + .execute() + + return await this.findByIds(ids) + } + private _cleanOptions( options: FindWithoutRelationsOptions ): WithRequiredProperty { diff --git a/packages/medusa/src/repositories/shipping-option.ts b/packages/medusa/src/repositories/shipping-option.ts index b34f0505e2700..c943a7dffcc3a 100644 --- a/packages/medusa/src/repositories/shipping-option.ts +++ b/packages/medusa/src/repositories/shipping-option.ts @@ -3,16 +3,16 @@ import { ShippingOption } from "../models/shipping-option" @EntityRepository(ShippingOption) export class ShippingOptionRepository extends Repository { - public async bulkSetShippingProfile( - shippingOptionIds: string[], + public async upsertShippingProfile( + ids: string[], shippingProfileId: string ): Promise { await this.createQueryBuilder() .update(ShippingOption) .set({ profile_id: shippingProfileId }) - .where({ id: In(shippingOptionIds) }) + .where({ id: In(ids) }) .execute() - return this.findByIds(shippingOptionIds) + return this.findByIds(ids) } } diff --git a/packages/medusa/src/services/__mocks__/product.js b/packages/medusa/src/services/__mocks__/product.js index 59bae5d3fc564..5a4581f0ff2e0 100644 --- a/packages/medusa/src/services/__mocks__/product.js +++ b/packages/medusa/src/services/__mocks__/product.js @@ -63,7 +63,7 @@ export const products = { } export const ProductServiceMock = { - withTransaction: function() { + withTransaction: function () { return this }, create: jest.fn().mockImplementation((data) => { @@ -106,6 +106,7 @@ export const ProductServiceMock = { deleteOption: jest .fn() .mockReturnValue(Promise.resolve(products.productWithOptions)), + updateshippingProfiles: jest.fn().mockReturnValue(Promise.resolve()), retrieveVariants: jest.fn().mockImplementation((productId) => { if (productId === IdMap.getId("product1")) { return Promise.resolve([ diff --git a/packages/medusa/src/services/__mocks__/shipping-option.js b/packages/medusa/src/services/__mocks__/shipping-option.js index b866ec978231d..e5c1a0ed5c6e5 100644 --- a/packages/medusa/src/services/__mocks__/shipping-option.js +++ b/packages/medusa/src/services/__mocks__/shipping-option.js @@ -89,6 +89,7 @@ export const ShippingOptionServiceMock = { return Promise.resolve(undefined) }), update: jest.fn().mockReturnValue(Promise.resolve()), + updateShippingprofile: jest.fn().mockReturnValue(Promise.resolve()), listAndCount: jest.fn().mockImplementation((data) => { if (data.region_id === IdMap.getId("region-france")) { return Promise.resolve([[shippingOptions.franceShipping], 1]) diff --git a/packages/medusa/src/services/__tests__/shipping-profile.js b/packages/medusa/src/services/__tests__/shipping-profile.js index daee763df4def..4a3310b1c12e2 100644 --- a/packages/medusa/src/services/__tests__/shipping-profile.js +++ b/packages/medusa/src/services/__tests__/shipping-profile.js @@ -35,14 +35,14 @@ describe("ShippingProfileService", () => { }) const productService = { - update: jest.fn(), + updateShippingProfile: jest.fn(), withTransaction: function () { return this }, } const shippingOptionService = { - update: jest.fn(), + updateShippingProfile: jest.fn(), withTransaction: function () { return this }, @@ -75,10 +75,11 @@ describe("ShippingProfileService", () => { products: [IdMap.getId("product1")], }) - expect(productService.update).toBeCalledTimes(1) - expect(productService.update).toBeCalledWith(IdMap.getId("product1"), { - profile_id: id, - }) + expect(productService.updateShippingProfile).toBeCalledTimes(1) + expect(productService.updateShippingProfile).toBeCalledWith( + [IdMap.getId("product1")], + id + ) }) it("calls updateOne with shipping options", async () => { @@ -88,10 +89,10 @@ describe("ShippingProfileService", () => { shipping_options: [IdMap.getId("validId")], }) - expect(shippingOptionService.update).toBeCalledTimes(1) - expect(shippingOptionService.update).toBeCalledWith( - IdMap.getId("validId"), - { profile_id: id } + expect(shippingOptionService.updateShippingProfile).toBeCalledTimes(1) + expect(shippingOptionService.updateShippingProfile).toBeCalledWith( + [IdMap.getId("validId")], + id ) }) }) @@ -125,7 +126,7 @@ describe("ShippingProfileService", () => { const profRepo = MockRepository({ findOne: () => Promise.resolve({}) }) const productService = { - update: jest.fn(), + updateShippingProfile: jest.fn(), withTransaction: function () { return this }, @@ -146,10 +147,11 @@ describe("ShippingProfileService", () => { IdMap.getId("product2"), ]) - expect(productService.update).toBeCalledTimes(1) - expect(productService.update).toBeCalledWith(IdMap.getId("product2"), { - profile_id: IdMap.getId("validId"), - }) + expect(productService.updateShippingProfile).toBeCalledTimes(1) + expect(productService.updateShippingProfile).toBeCalledWith( + [IdMap.getId("product2")], + IdMap.getId("validId") + ) }) }) @@ -299,7 +301,7 @@ describe("ShippingProfileService", () => { const profRepo = MockRepository({ findOne: () => Promise.resolve({}) }) const shippingOptionService = { - update: jest.fn(), + updateShippingProfile: jest.fn(), withTransaction: function () { return this }, @@ -320,10 +322,10 @@ describe("ShippingProfileService", () => { IdMap.getId("freeShipping"), ]) - expect(shippingOptionService.update).toBeCalledTimes(1) - expect(shippingOptionService.update).toBeCalledWith( - IdMap.getId("freeShipping"), - { profile_id: IdMap.getId("validId") } + expect(shippingOptionService.updateShippingProfile).toBeCalledTimes(1) + expect(shippingOptionService.updateShippingProfile).toBeCalledWith( + [IdMap.getId("freeShipping")], + IdMap.getId("validId") ) }) }) diff --git a/packages/medusa/src/services/product.ts b/packages/medusa/src/services/product.ts index 507a1e5627444..081febbe336c8 100644 --- a/packages/medusa/src/services/product.ts +++ b/packages/medusa/src/services/product.ts @@ -33,7 +33,7 @@ import { ProductSelector, UpdateProductInput, } from "../types/product" -import { buildQuery, setMetadata } from "../utils" +import { buildQuery, isString, setMetadata } from "../utils" import EventBusService from "./event-bus" type InjectedDependencies = { @@ -925,6 +925,31 @@ class ProductService extends TransactionBaseService { }) } + /** + * + * @param productIds ID or IDs of the products to update + * @param profileId Shipping profile ID to update the shipping options with + * @returns updated shipping options + */ + async updateShippingProfile( + productIds: string | string[], + profileId: string + ): Promise { + return await this.atomicPhase_(async (manager) => { + const productRepo = manager.getCustomRepository(this.productRepository_) + + const ids = isString(productIds) ? [productIds] : productIds + + const products = await productRepo.upsertShippingProfile(ids, profileId) + + await this.eventBus_ + .withTransaction(manager) + .emit(ProductService.Events.UPDATED, products) + + return products + }) + } + /** * Creates a query object to be used for list queries. * @param selector - the selector to create the query from diff --git a/packages/medusa/src/services/shipping-option.ts b/packages/medusa/src/services/shipping-option.ts index 76e2481c06c25..8f544aeb15d92 100644 --- a/packages/medusa/src/services/shipping-option.ts +++ b/packages/medusa/src/services/shipping-option.ts @@ -21,7 +21,7 @@ import { UpdateShippingOptionInput, ValidatePriceTypeAndAmountInput, } from "../types/shipping-options" -import { buildQuery, setMetadata } from "../utils" +import { buildQuery, isString, setMetadata } from "../utils" import { FlagRouter } from "../utils/flag-router" import FulfillmentProviderService from "./fulfillment-provider" import RegionService from "./region" @@ -758,6 +758,25 @@ class ShippingOptionService extends TransactionBaseService { }) } + /** + * + * @param optionIds ID or IDs of the shipping options to update + * @param profileId Shipping profile ID to update the shipping options with + * @returns updated shipping options + */ + async updateShippingProfile( + optionIds: string | string[], + profileId: string + ): Promise { + return await this.atomicPhase_(async (manager) => { + const optionRepo = manager.getCustomRepository(this.optionRepository_) + + const ids = isString(optionIds) ? [optionIds] : optionIds + + return await optionRepo.upsertShippingProfile(ids, profileId) + }) + } + /** * Returns the amount to be paid for a shipping method. Will ask the * fulfillment provider to calculate the price if the shipping option has the diff --git a/packages/medusa/src/services/shipping-profile.ts b/packages/medusa/src/services/shipping-profile.ts index b6bd60ec490b6..d427f73e7738a 100644 --- a/packages/medusa/src/services/shipping-profile.ts +++ b/packages/medusa/src/services/shipping-profile.ts @@ -15,7 +15,7 @@ import { CreateShippingProfile, UpdateShippingProfile, } from "../types/shipping-profile" -import { buildQuery, setMetadata } from "../utils" +import { buildQuery, isString, setMetadata } from "../utils" import CustomShippingOptionService from "./custom-shipping-option" import ProductService from "./product" import ShippingOptionService from "./shipping-option" @@ -358,13 +358,11 @@ class ShippingProfileService extends TransactionBaseService { productId: string | string[] ): Promise { return await this.atomicPhase_(async (manager) => { - const productRepo = manager.getCustomRepository(this.productRepository_) + const productServiceTx = this.productService_.withTransaction(manager) - const { id } = await this.retrieve(profileId) - - await productRepo.bulkSetShippingProfile( - Array.isArray(productId) ? productId : [productId], - id + await productServiceTx.updateShippingProfile( + isString(productId) ? [productId] : productId, + profileId ) return await this.retrieve(profileId, { @@ -393,11 +391,10 @@ class ShippingProfileService extends TransactionBaseService { const shippingOptionServiceTx = this.shippingOptionService_.withTransaction(manager) - for (const oId of Array.isArray(optionId) ? optionId : [optionId]) { - await shippingOptionServiceTx.update(oId, { - profile_id: profileId, - }) - } + await shippingOptionServiceTx.updateShippingProfile( + isString(optionId) ? [optionId] : optionId, + profileId + ) return await this.retrieve(profileId, { relations: [ From 6d5e5a7cb37fa779c6662e6e2b6be5967044209d Mon Sep 17 00:00:00 2001 From: Kasper Date: Mon, 6 Feb 2023 11:00:58 +0100 Subject: [PATCH 8/9] address feedback --- .../admin-ui/build/assets/index-b2503cd8.css | 1 + .../admin-ui/build/assets/index-f3fa71e3.js | 262 ++++++++++++++++++ .../admin-ui/build/assets/login-c448d0a7.js | 1 + packages/admin-ui/build/build-manifest.json | 1 + packages/admin-ui/build/index.html | 15 + packages/admin-ui/build/vite.svg | 1 + packages/admin-ui/dist/index.d.ts | 6 + packages/admin-ui/dist/index.d.ts.map | 1 + packages/admin-ui/dist/index.js | 86 ++++++ packages/admin-ui/dist/types/build.d.ts | 14 + packages/admin-ui/dist/types/build.d.ts.map | 1 + packages/admin-ui/dist/types/build.js | 2 + packages/admin-ui/dist/types/config.d.ts | 5 + packages/admin-ui/dist/types/config.d.ts.map | 1 + packages/admin-ui/dist/types/config.js | 2 + packages/admin-ui/dist/types/index.d.ts | 3 + packages/admin-ui/dist/types/index.d.ts.map | 1 + packages/admin-ui/dist/types/index.js | 18 ++ packages/admin-ui/dist/types/misc.d.ts | 5 + packages/admin-ui/dist/types/misc.d.ts.map | 1 + packages/admin-ui/dist/types/misc.js | 2 + packages/admin-ui/dist/utils/format-base.d.ts | 3 + .../admin-ui/dist/utils/format-base.d.ts.map | 1 + packages/admin-ui/dist/utils/format-base.js | 7 + .../dist/utils/get-custom-vite-config.d.ts | 4 + .../utils/get-custom-vite-config.d.ts.map | 1 + .../dist/utils/get-custom-vite-config.js | 52 ++++ packages/admin-ui/dist/utils/index.d.ts | 3 + packages/admin-ui/dist/utils/index.d.ts.map | 1 + packages/admin-ui/dist/utils/index.js | 18 ++ packages/admin/api/index.d.ts | 3 + packages/admin/api/index.d.ts.map | 1 + packages/admin/api/index.js | 62 +++++ packages/admin/commands/build.d.ts | 8 + packages/admin/commands/build.d.ts.map | 1 + packages/admin/commands/build.js | 46 +++ packages/admin/commands/create-cli.d.ts | 3 + packages/admin/commands/create-cli.d.ts.map | 1 + packages/admin/commands/create-cli.js | 60 ++++ packages/admin/commands/run.d.ts | 2 + packages/admin/commands/run.d.ts.map | 1 + packages/admin/commands/run.js | 8 + packages/admin/loaders/index.d.ts | 2 + packages/admin/loaders/index.d.ts.map | 1 + packages/admin/loaders/index.js | 79 ++++++ packages/admin/preload/index.d.ts | 2 + packages/admin/preload/index.d.ts.map | 1 + packages/admin/preload/index.js | 116 ++++++++ packages/admin/types/index.d.ts | 47 ++++ packages/admin/types/index.d.ts.map | 1 + packages/admin/types/index.js | 2 + packages/admin/utils/index.d.ts | 3 + packages/admin/utils/index.d.ts.map | 1 + packages/admin/utils/index.js | 18 ++ packages/admin/utils/load-config.d.ts | 3 + packages/admin/utils/load-config.d.ts.map | 1 + packages/admin/utils/load-config.js | 33 +++ packages/admin/utils/reporter.d.ts | 13 + packages/admin/utils/reporter.d.ts.map | 1 + packages/admin/utils/reporter.js | 33 +++ packages/medusa/src/repositories/product.ts | 23 +- .../src/repositories/shipping-option.ts | 6 +- 62 files changed, 1080 insertions(+), 21 deletions(-) create mode 100644 packages/admin-ui/build/assets/index-b2503cd8.css create mode 100644 packages/admin-ui/build/assets/index-f3fa71e3.js create mode 100644 packages/admin-ui/build/assets/login-c448d0a7.js create mode 100644 packages/admin-ui/build/build-manifest.json create mode 100644 packages/admin-ui/build/index.html create mode 100644 packages/admin-ui/build/vite.svg create mode 100644 packages/admin-ui/dist/index.d.ts create mode 100644 packages/admin-ui/dist/index.d.ts.map create mode 100644 packages/admin-ui/dist/index.js create mode 100644 packages/admin-ui/dist/types/build.d.ts create mode 100644 packages/admin-ui/dist/types/build.d.ts.map create mode 100644 packages/admin-ui/dist/types/build.js create mode 100644 packages/admin-ui/dist/types/config.d.ts create mode 100644 packages/admin-ui/dist/types/config.d.ts.map create mode 100644 packages/admin-ui/dist/types/config.js create mode 100644 packages/admin-ui/dist/types/index.d.ts create mode 100644 packages/admin-ui/dist/types/index.d.ts.map create mode 100644 packages/admin-ui/dist/types/index.js create mode 100644 packages/admin-ui/dist/types/misc.d.ts create mode 100644 packages/admin-ui/dist/types/misc.d.ts.map create mode 100644 packages/admin-ui/dist/types/misc.js create mode 100644 packages/admin-ui/dist/utils/format-base.d.ts create mode 100644 packages/admin-ui/dist/utils/format-base.d.ts.map create mode 100644 packages/admin-ui/dist/utils/format-base.js create mode 100644 packages/admin-ui/dist/utils/get-custom-vite-config.d.ts create mode 100644 packages/admin-ui/dist/utils/get-custom-vite-config.d.ts.map create mode 100644 packages/admin-ui/dist/utils/get-custom-vite-config.js create mode 100644 packages/admin-ui/dist/utils/index.d.ts create mode 100644 packages/admin-ui/dist/utils/index.d.ts.map create mode 100644 packages/admin-ui/dist/utils/index.js create mode 100644 packages/admin/api/index.d.ts create mode 100644 packages/admin/api/index.d.ts.map create mode 100644 packages/admin/api/index.js create mode 100644 packages/admin/commands/build.d.ts create mode 100644 packages/admin/commands/build.d.ts.map create mode 100644 packages/admin/commands/build.js create mode 100644 packages/admin/commands/create-cli.d.ts create mode 100644 packages/admin/commands/create-cli.d.ts.map create mode 100644 packages/admin/commands/create-cli.js create mode 100644 packages/admin/commands/run.d.ts create mode 100644 packages/admin/commands/run.d.ts.map create mode 100644 packages/admin/commands/run.js create mode 100644 packages/admin/loaders/index.d.ts create mode 100644 packages/admin/loaders/index.d.ts.map create mode 100644 packages/admin/loaders/index.js create mode 100644 packages/admin/preload/index.d.ts create mode 100644 packages/admin/preload/index.d.ts.map create mode 100644 packages/admin/preload/index.js create mode 100644 packages/admin/types/index.d.ts create mode 100644 packages/admin/types/index.d.ts.map create mode 100644 packages/admin/types/index.js create mode 100644 packages/admin/utils/index.d.ts create mode 100644 packages/admin/utils/index.d.ts.map create mode 100644 packages/admin/utils/index.js create mode 100644 packages/admin/utils/load-config.d.ts create mode 100644 packages/admin/utils/load-config.d.ts.map create mode 100644 packages/admin/utils/load-config.js create mode 100644 packages/admin/utils/reporter.d.ts create mode 100644 packages/admin/utils/reporter.d.ts.map create mode 100644 packages/admin/utils/reporter.js diff --git a/packages/admin-ui/build/assets/index-b2503cd8.css b/packages/admin-ui/build/assets/index-b2503cd8.css new file mode 100644 index 0000000000000..1038f3966d5ad --- /dev/null +++ b/packages/admin-ui/build/assets/index-b2503cd8.css @@ -0,0 +1 @@ +*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.flex{display:flex}.h-screen{height:100vh}.min-h-screen{min-height:100vh}.w-full{width:100%}.w-screen{width:100vw}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity))}.bg-gradient-to-tr{background-image:linear-gradient(to top right,var(--tw-gradient-stops))}.from-\[\#7C53FF\]{--tw-gradient-from: #7C53FF;--tw-gradient-to: rgb(124 83 255 / 0);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-\[\#F796FF\]{--tw-gradient-to: #F796FF}.pb-12{padding-bottom:3rem}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity))} diff --git a/packages/admin-ui/build/assets/index-f3fa71e3.js b/packages/admin-ui/build/assets/index-f3fa71e3.js new file mode 100644 index 0000000000000..a7844c5524bc8 --- /dev/null +++ b/packages/admin-ui/build/assets/index-f3fa71e3.js @@ -0,0 +1,262 @@ +function EU(a,n){for(var u=0;uc[h]})}}}return Object.freeze(Object.defineProperty(a,Symbol.toStringTag,{value:"Module"}))}(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const h of document.querySelectorAll('link[rel="modulepreload"]'))c(h);new MutationObserver(h=>{for(const y of h)if(y.type==="childList")for(const p of y.addedNodes)p.tagName==="LINK"&&p.rel==="modulepreload"&&c(p)}).observe(document,{childList:!0,subtree:!0});function u(h){const y={};return h.integrity&&(y.integrity=h.integrity),h.referrerpolicy&&(y.referrerPolicy=h.referrerpolicy),h.crossorigin==="use-credentials"?y.credentials="include":h.crossorigin==="anonymous"?y.credentials="omit":y.credentials="same-origin",y}function c(h){if(h.ep)return;h.ep=!0;const y=u(h);fetch(h.href,y)}})();var Zp=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function yD(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}function RU(a){if(a.__esModule)return a;var n=a.default;if(typeof n=="function"){var u=function c(){if(this instanceof c){var h=[null];h.push.apply(h,arguments);var y=Function.bind.apply(n,h);return new y}return n.apply(this,arguments)};u.prototype=n.prototype}else u={};return Object.defineProperty(u,"__esModule",{value:!0}),Object.keys(a).forEach(function(c){var h=Object.getOwnPropertyDescriptor(a,c);Object.defineProperty(u,c,h.get?h:{enumerable:!0,get:function(){return a[c]}})}),u}var rC={},aC={},TU={get exports(){return aC},set exports(a){aC=a}},li={},pe={},CU={get exports(){return pe},set exports(a){pe=a}},Ag={},xU={get exports(){return Ag},set exports(a){Ag=a}};/** + * @license React + * react.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */(function(a,n){(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var u="18.2.0",c=Symbol.for("react.element"),h=Symbol.for("react.portal"),y=Symbol.for("react.fragment"),p=Symbol.for("react.strict_mode"),T=Symbol.for("react.profiler"),C=Symbol.for("react.provider"),x=Symbol.for("react.context"),L=Symbol.for("react.forward_ref"),D=Symbol.for("react.suspense"),A=Symbol.for("react.suspense_list"),$=Symbol.for("react.memo"),j=Symbol.for("react.lazy"),se=Symbol.for("react.offscreen"),Z=Symbol.iterator,X="@@iterator";function me(S){if(S===null||typeof S!="object")return null;var U=Z&&S[Z]||S[X];return typeof U=="function"?U:null}var V={current:null},z={transition:null},de={current:null,isBatchingLegacy:!1,didScheduleLegacyUpdate:!1},Se={current:null},Ee={},De=null;function qe(S){De=S}Ee.setExtraStackFrame=function(S){De=S},Ee.getCurrentStack=null,Ee.getStackAddendum=function(){var S="";De&&(S+=De);var U=Ee.getCurrentStack;return U&&(S+=U()||""),S};var Ie=!1,ze=!1,Lt=!1,Be=!1,nt=!1,Nt={ReactCurrentDispatcher:V,ReactCurrentBatchConfig:z,ReactCurrentOwner:Se};Nt.ReactDebugCurrentFrame=Ee,Nt.ReactCurrentActQueue=de;function rn(S){{for(var U=arguments.length,W=new Array(U>1?U-1:0),J=1;J1?U-1:0),J=1;J1){for(var vn=Array(Zt),bt=0;bt1){for(var En=Array(bt),zt=0;zt is not supported and will be removed in a future major release. Did you mean to render instead?")),U.Provider},set:function(ke){U.Provider=ke}},_currentValue:{get:function(){return U._currentValue},set:function(ke){U._currentValue=ke}},_currentValue2:{get:function(){return U._currentValue2},set:function(ke){U._currentValue2=ke}},_threadCount:{get:function(){return U._threadCount},set:function(ke){U._threadCount=ke}},Consumer:{get:function(){return W||(W=!0,Ue("Rendering is not supported and will be removed in a future major release. Did you mean to render instead?")),U.Consumer}},displayName:{get:function(){return U.displayName},set:function(ke){ve||(rn("Setting `displayName` on Context.Consumer has no effect. You should set it directly on the context with Context.displayName = '%s'.",ke),ve=!0)}}}),U.Consumer=Ze}return U._currentRenderer=null,U._currentRenderer2=null,U}var Ja=-1,Vn=0,qa=1,jr=2;function P(S){if(S._status===Ja){var U=S._result,W=U();if(W.then(function(Ze){if(S._status===Vn||S._status===Ja){var ke=S;ke._status=qa,ke._result=Ze}},function(Ze){if(S._status===Vn||S._status===Ja){var ke=S;ke._status=jr,ke._result=Ze}}),S._status===Ja){var J=S;J._status=Vn,J._result=W}}if(S._status===qa){var ve=S._result;return ve===void 0&&Ue(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent')) + +Did you accidentally put curly braces around the import?`,ve),"default"in ve||Ue(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent'))`,ve),ve.default}else throw S._result}function he(S){var U={_status:Ja,_result:S},W={$$typeof:j,_payload:U,_init:P};{var J,ve;Object.defineProperties(W,{defaultProps:{configurable:!0,get:function(){return J},set:function(Ze){Ue("React.lazy(...): It is not supported to assign `defaultProps` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),J=Ze,Object.defineProperty(W,"defaultProps",{enumerable:!0})}},propTypes:{configurable:!0,get:function(){return ve},set:function(Ze){Ue("React.lazy(...): It is not supported to assign `propTypes` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),ve=Ze,Object.defineProperty(W,"propTypes",{enumerable:!0})}}})}return W}function we(S){S!=null&&S.$$typeof===$?Ue("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof S!="function"?Ue("forwardRef requires a render function but was given %s.",S===null?"null":typeof S):S.length!==0&&S.length!==2&&Ue("forwardRef render functions accept exactly two parameters: props and ref. %s",S.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),S!=null&&(S.defaultProps!=null||S.propTypes!=null)&&Ue("forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component?");var U={$$typeof:L,render:S};{var W;Object.defineProperty(U,"displayName",{enumerable:!1,configurable:!0,get:function(){return W},set:function(J){W=J,!S.name&&!S.displayName&&(S.displayName=J)}})}return U}var Ye;Ye=Symbol.for("react.module.reference");function Et(S){return!!(typeof S=="string"||typeof S=="function"||S===y||S===T||nt||S===p||S===D||S===A||Be||S===se||Ie||ze||Lt||typeof S=="object"&&S!==null&&(S.$$typeof===j||S.$$typeof===$||S.$$typeof===C||S.$$typeof===x||S.$$typeof===L||S.$$typeof===Ye||S.getModuleId!==void 0))}function qt(S,U){Et(S)||Ue("memo: The first argument must be a component. Instead received: %s",S===null?"null":typeof S);var W={$$typeof:$,type:S,compare:U===void 0?null:U};{var J;Object.defineProperty(W,"displayName",{enumerable:!1,configurable:!0,get:function(){return J},set:function(ve){J=ve,!S.name&&!S.displayName&&(S.displayName=ve)}})}return W}function je(){var S=V.current;return S===null&&Ue(`Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: +1. You might have mismatching versions of React and the renderer (such as React DOM) +2. You might be breaking the Rules of Hooks +3. You might have more than one copy of React in the same app +See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.`),S}function ht(S){var U=je();if(S._context!==void 0){var W=S._context;W.Consumer===S?Ue("Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be removed in a future major release. Did you mean to call useContext(Context) instead?"):W.Provider===S&&Ue("Calling useContext(Context.Provider) is not supported. Did you mean to call useContext(Context) instead?")}return U.useContext(S)}function An(S){var U=je();return U.useState(S)}function on(S,U,W){var J=je();return J.useReducer(S,U,W)}function Rt(S){var U=je();return U.useRef(S)}function Nr(S,U){var W=je();return W.useEffect(S,U)}function ci(S,U){var W=je();return W.useInsertionEffect(S,U)}function Yu(S,U){var W=je();return W.useLayoutEffect(S,U)}function xa(S,U){var W=je();return W.useCallback(S,U)}function Ho(S,U){var W=je();return W.useMemo(S,U)}function kd(S,U,W){var J=je();return J.useImperativeHandle(S,U,W)}function Qu(S,U){{var W=je();return W.useDebugValue(S,U)}}function Fd(){var S=je();return S.useTransition()}function $i(S){var U=je();return U.useDeferredValue(S)}function Tt(){var S=je();return S.useId()}function vu(S,U,W){var J=je();return J.useSyncExternalStore(S,U,W)}var qi=0,zo,us,os,ls,ss,Bo,Io;function cs(){}cs.__reactDisabledLog=!0;function fs(){{if(qi===0){zo=console.log,us=console.info,os=console.warn,ls=console.error,ss=console.group,Bo=console.groupCollapsed,Io=console.groupEnd;var S={configurable:!0,enumerable:!0,value:cs,writable:!0};Object.defineProperties(console,{info:S,log:S,warn:S,error:S,group:S,groupCollapsed:S,groupEnd:S})}qi++}}function ds(){{if(qi--,qi===0){var S={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Hn({},S,{value:zo}),info:Hn({},S,{value:us}),warn:Hn({},S,{value:os}),error:Hn({},S,{value:ls}),group:Hn({},S,{value:ss}),groupCollapsed:Hn({},S,{value:Bo}),groupEnd:Hn({},S,{value:Io})})}qi<0&&Ue("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var mu=Nt.ReactCurrentDispatcher,Vr;function Hi(S,U,W){{if(Vr===void 0)try{throw Error()}catch(ve){var J=ve.stack.trim().match(/\n( *(at )?)/);Vr=J&&J[1]||""}return` +`+Vr+S}}var yu=!1,Ku;{var jo=typeof WeakMap=="function"?WeakMap:Map;Ku=new jo}function Vo(S,U){if(!S||yu)return"";{var W=Ku.get(S);if(W!==void 0)return W}var J;yu=!0;var ve=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var Ze;Ze=mu.current,mu.current=null,fs();try{if(U){var ke=function(){throw Error()};if(Object.defineProperty(ke.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ke,[])}catch(Bt){J=Bt}Reflect.construct(S,[],ke)}else{try{ke.call()}catch(Bt){J=Bt}S.call(ke.prototype)}}else{try{throw Error()}catch(Bt){J=Bt}S()}}catch(Bt){if(Bt&&J&&typeof Bt.stack=="string"){for(var rt=Bt.stack.split(` +`),Ct=J.stack.split(` +`),Zt=rt.length-1,vn=Ct.length-1;Zt>=1&&vn>=0&&rt[Zt]!==Ct[vn];)vn--;for(;Zt>=1&&vn>=0;Zt--,vn--)if(rt[Zt]!==Ct[vn]){if(Zt!==1||vn!==1)do if(Zt--,vn--,vn<0||rt[Zt]!==Ct[vn]){var bt=` +`+rt[Zt].replace(" at new "," at ");return S.displayName&&bt.includes("")&&(bt=bt.replace("",S.displayName)),typeof S=="function"&&Ku.set(S,bt),bt}while(Zt>=1&&vn>=0);break}}}finally{yu=!1,mu.current=Ze,ds(),Error.prepareStackTrace=ve}var En=S?S.displayName||S.name:"",zt=En?Hi(En):"";return typeof S=="function"&&Ku.set(S,zt),zt}function hs(S,U,W){return Vo(S,!1)}function Ic(S){var U=S.prototype;return!!(U&&U.isReactComponent)}function fi(S,U,W){if(S==null)return"";if(typeof S=="function")return Vo(S,Ic(S));if(typeof S=="string")return Hi(S);switch(S){case D:return Hi("Suspense");case A:return Hi("SuspenseList")}if(typeof S=="object")switch(S.$$typeof){case L:return hs(S.render);case $:return fi(S.type,U,W);case j:{var J=S,ve=J._payload,Ze=J._init;try{return fi(Ze(ve),U,W)}catch{}}}return""}var Go={},di=Nt.ReactDebugCurrentFrame;function Xu(S){if(S){var U=S._owner,W=fi(S.type,S._source,U?U.type:null);di.setExtraStackFrame(W)}else di.setExtraStackFrame(null)}function ps(S,U,W,J,ve){{var Ze=Function.call.bind(Sn);for(var ke in S)if(Ze(S,ke)){var rt=void 0;try{if(typeof S[ke]!="function"){var Ct=Error((J||"React class")+": "+W+" type `"+ke+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof S[ke]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw Ct.name="Invariant Violation",Ct}rt=S[ke](U,ke,J,W,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(Zt){rt=Zt}rt&&!(rt instanceof Error)&&(Xu(ve),Ue("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",J||"React class",W,ke,typeof rt),Xu(null)),rt instanceof Error&&!(rt.message in Go)&&(Go[rt.message]=!0,Xu(ve),Ue("Failed %s type: %s",W,rt.message),Xu(null))}}}function Ht(S){if(S){var U=S._owner,W=fi(S.type,S._source,U?U.type:null);qe(W)}else qe(null)}var Wo;Wo=!1;function Yo(){if(Se.current){var S=Fn(Se.current.type);if(S)return` + +Check the render method of \``+S+"`."}return""}function lt(S){if(S!==void 0){var U=S.fileName.replace(/^.*[\\\/]/,""),W=S.lineNumber;return` + +Check your code at `+U+":"+W+"."}return""}function vs(S){return S!=null?lt(S.__source):""}var kr={};function Ju(S){var U=Yo();if(!U){var W=typeof S=="string"?S:S.displayName||S.name;W&&(U=` + +Check the top-level render call using <`+W+">.")}return U}function gu(S,U){if(!(!S._store||S._store.validated||S.key!=null)){S._store.validated=!0;var W=Ju(U);if(!kr[W]){kr[W]=!0;var J="";S&&S._owner&&S._owner!==Se.current&&(J=" It was passed a child from "+Fn(S._owner.type)+"."),Ht(S),Ue('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',W,J),Ht(null)}}}function ms(S,U){if(typeof S=="object"){if(gn(S))for(var W=0;W",ve=" Did you accidentally export a JSX literal instead of a component?"):ke=typeof S,Ue("React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",ke,ve)}var rt=ge.apply(this,arguments);if(rt==null)return rt;if(J)for(var Ct=2;Ct10&&rn("Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table."),J._updatedFibers.clear()}}}var Qo=!1,Zu=null;function Vc(S){if(Zu===null)try{var U=("require"+Math.random()).slice(0,7),W=a&&a[U];Zu=W.call(a,"timers").setImmediate}catch{Zu=function(ve){Qo===!1&&(Qo=!0,typeof MessageChannel>"u"&&Ue("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."));var Ze=new MessageChannel;Ze.port1.onmessage=ve,Ze.port2.postMessage(void 0)}}return Zu(S)}var Vt=0,ln=!1;function qd(S){{var U=Vt;Vt++,de.current===null&&(de.current=[]);var W=de.isBatchingLegacy,J;try{if(de.isBatchingLegacy=!0,J=S(),!W&&de.didScheduleLegacyUpdate){var ve=de.current;ve!==null&&(de.didScheduleLegacyUpdate=!1,Xo(ve))}}catch(En){throw zi(U),En}finally{de.isBatchingLegacy=W}if(J!==null&&typeof J=="object"&&typeof J.then=="function"){var Ze=J,ke=!1,rt={then:function(En,zt){ke=!0,Ze.then(function(Bt){zi(U),Vt===0?Ko(Bt,En,zt):En(Bt)},function(Bt){zi(U),zt(Bt)})}};return!ln&&typeof Promise<"u"&&Promise.resolve().then(function(){}).then(function(){ke||(ln=!0,Ue("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"))}),rt}else{var Ct=J;if(zi(U),Vt===0){var Zt=de.current;Zt!==null&&(Xo(Zt),de.current=null);var vn={then:function(En,zt){de.current===null?(de.current=[],Ko(Ct,En,zt)):En(Ct)}};return vn}else{var bt={then:function(En,zt){En(Ct)}};return bt}}}}function zi(S){S!==Vt-1&&Ue("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "),Vt=S}function Ko(S,U,W){{var J=de.current;if(J!==null)try{Xo(J),Vc(function(){J.length===0?(de.current=null,U(S)):Ko(S,U,W)})}catch(ve){W(ve)}else U(S)}}var bu=!1;function Xo(S){if(!bu){bu=!0;var U=0;try{for(;U0;){var Pe=it-1>>>1,ct=re[Pe];if(x(ct,ge)>0)re[Pe]=ge,re[it]=ct,it=Pe;else return}}function C(re,ge,be){for(var it=be,Pe=re.length,ct=Pe>>>1;itbe&&(!re||Sr()));){var it=Be.callback;if(typeof it=="function"){Be.callback=null,nt=Be.priorityLevel;var Pe=Be.expirationTime<=be,ct=it(Pe);be=a.unstable_now(),typeof ct=="function"?Be.callback=ct:Be===y(Ie)&&p(Ie),hn(be)}else p(Ie);Be=y(Ie)}if(Be!==null)return!0;var Mt=y(ze);return Mt!==null&&ie(Hn,Mt.startTime-be),!1}function br(re,ge){switch(re){case L:case D:case A:case $:case j:break;default:re=A}var be=nt;nt=re;try{return ge()}finally{nt=be}}function Pr(re){var ge;switch(nt){case L:case D:case A:ge=A;break;default:ge=nt;break}var be=nt;nt=ge;try{return re()}finally{nt=be}}function Ca(re){var ge=nt;return function(){var be=nt;nt=ge;try{return re.apply(this,arguments)}finally{nt=be}}}function Pn(re,ge,be){var it=a.unstable_now(),Pe;if(typeof be=="object"&&be!==null){var ct=be.delay;typeof ct=="number"&&ct>0?Pe=it+ct:Pe=it}else Pe=it;var Mt;switch(re){case L:Mt=de;break;case D:Mt=Se;break;case j:Mt=qe;break;case $:Mt=De;break;case A:default:Mt=Ee;break}var bn=Pe+Mt,Ft={id:Lt++,callback:ge,priorityLevel:re,startTime:Pe,expirationTime:bn,sortIndex:-1};return Pe>it?(Ft.sortIndex=Pe,h(ze,Ft),y(Ie)===null&&Ft===y(ze)&&(Ue?xe():Ue=!0,ie(Hn,Pe-it))):(Ft.sortIndex=bn,h(Ie,Ft),!rn&&!Nt&&(rn=!0,ee(Mr))),Ft}function xn(){}function ar(){!rn&&!Nt&&(rn=!0,ee(Mr))}function zn(){return y(Ie)}function Bn(re){re.callback=null}function gn(){return nt}var an=!1,wn=null,kn=-1,un=c,In=-1;function Sr(){var re=a.unstable_now()-In;return!(re125){console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported");return}re>0?un=Math.floor(1e3/re):un=c}var ir=function(){if(wn!==null){var re=a.unstable_now();In=re;var ge=!0,be=!0;try{be=wn(ge,re)}finally{be?jn():(an=!1,wn=null)}}else an=!1},jn;if(typeof gr=="function")jn=function(){gr(ir)};else if(typeof MessageChannel<"u"){var Ir=new MessageChannel,Q=Ir.port2;Ir.port1.onmessage=ir,jn=function(){Q.postMessage(null)}}else jn=function(){kt(ir,0)};function ee(re){wn=re,an||(an=!0,jn())}function ie(re,ge){kn=kt(function(){re(a.unstable_now())},ge)}function xe(){yr(kn),kn=-1}var Ce=Fn,Ve=null;a.unstable_IdlePriority=j,a.unstable_ImmediatePriority=L,a.unstable_LowPriority=$,a.unstable_NormalPriority=A,a.unstable_Profiling=Ve,a.unstable_UserBlockingPriority=D,a.unstable_cancelCallback=Bn,a.unstable_continueExecution=ar,a.unstable_forceFrameRate=Sn,a.unstable_getCurrentPriorityLevel=gn,a.unstable_getFirstCallbackNode=zn,a.unstable_next=Pr,a.unstable_pauseExecution=xn,a.unstable_requestPaint=Ce,a.unstable_runWithPriority=br,a.unstable_scheduleCallback=Pn,a.unstable_shouldYield=Sr,a.unstable_wrapCallback=Ca,typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error)})()})(gD);(function(a){a.exports=gD})(wU);/** + * @license React + * react-dom.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var a=pe,n=iC,u=a.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,c=!1;function h(e){c=e}function y(e){if(!c){for(var t=arguments.length,r=new Array(t>1?t-1:0),o=1;o1?t-1:0),o=1;o2&&(e[0]==="o"||e[0]==="O")&&(e[1]==="n"||e[1]==="N")}function bn(e,t,r,o){if(r!==null&&r.type===Ir)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":{if(o)return!1;if(r!==null)return!r.acceptsBooleans;var s=e.toLowerCase().slice(0,5);return s!=="data-"&&s!=="aria-"}default:return!1}}function Ft(e,t,r,o){if(t===null||typeof t>"u"||bn(e,t,r,o))return!0;if(o)return!1;if(r!==null)switch(r.type){case ie:return!t;case xe:return t===!1;case Ce:return isNaN(t);case Ve:return isNaN(t)||t<1}return!1}function ur(e){return $t.hasOwnProperty(e)?$t[e]:null}function Jt(e,t,r,o,s,d,v){this.acceptsBooleans=t===ee||t===ie||t===xe,this.attributeName=o,this.attributeNamespace=s,this.mustUseProperty=r,this.propertyName=e,this.type=t,this.sanitizeURL=d,this.removeEmptyString=v}var $t={},fa=["children","dangerouslySetInnerHTML","defaultValue","defaultChecked","innerHTML","suppressContentEditableWarning","suppressHydrationWarning","style"];fa.forEach(function(e){$t[e]=new Jt(e,Ir,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0],r=e[1];$t[t]=new Jt(t,Q,!1,r,null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){$t[e]=new Jt(e,ee,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){$t[e]=new Jt(e,ee,!1,e,null,!1,!1)}),["allowFullScreen","async","autoFocus","autoPlay","controls","default","defer","disabled","disablePictureInPicture","disableRemotePlayback","formNoValidate","hidden","loop","noModule","noValidate","open","playsInline","readOnly","required","reversed","scoped","seamless","itemScope"].forEach(function(e){$t[e]=new Jt(e,ie,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){$t[e]=new Jt(e,ie,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){$t[e]=new Jt(e,xe,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){$t[e]=new Jt(e,Ve,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){$t[e]=new Jt(e,Ce,!1,e.toLowerCase(),null,!1,!1)});var pn=/[\-\:]([a-z])/g,or=function(e){return e[1].toUpperCase()};["accent-height","alignment-baseline","arabic-form","baseline-shift","cap-height","clip-path","clip-rule","color-interpolation","color-interpolation-filters","color-profile","color-rendering","dominant-baseline","enable-background","fill-opacity","fill-rule","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","glyph-name","glyph-orientation-horizontal","glyph-orientation-vertical","horiz-adv-x","horiz-origin-x","image-rendering","letter-spacing","lighting-color","marker-end","marker-mid","marker-start","overline-position","overline-thickness","paint-order","panose-1","pointer-events","rendering-intent","shape-rendering","stop-color","stop-opacity","strikethrough-position","strikethrough-thickness","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-anchor","text-decoration","text-rendering","underline-position","underline-thickness","unicode-bidi","unicode-range","units-per-em","v-alphabetic","v-hanging","v-ideographic","v-mathematical","vector-effect","vert-adv-y","vert-origin-x","vert-origin-y","word-spacing","writing-mode","xmlns:xlink","x-height"].forEach(function(e){var t=e.replace(pn,or);$t[t]=new Jt(t,Q,!1,e,null,!1,!1)}),["xlink:actuate","xlink:arcrole","xlink:role","xlink:show","xlink:title","xlink:type"].forEach(function(e){var t=e.replace(pn,or);$t[t]=new Jt(t,Q,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(pn,or);$t[t]=new Jt(t,Q,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){$t[e]=new Jt(e,Q,!1,e.toLowerCase(),null,!1,!1)});var da="xlinkHref";$t[da]=new Jt("xlinkHref",Q,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){$t[e]=new Jt(e,Q,!1,e.toLowerCase(),null,!0,!0)});var Ka=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i,Xa=!1;function Fi(e){!Xa&&Ka.test(e)&&(Xa=!0,p("A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed %s.",JSON.stringify(e)))}function Ja(e,t,r,o){if(o.mustUseProperty){var s=o.propertyName;return e[s]}else{In(r,t),o.sanitizeURL&&Fi(""+r);var d=o.attributeName,v=null;if(o.type===xe){if(e.hasAttribute(d)){var g=e.getAttribute(d);return g===""?!0:Ft(t,r,o,!1)?g:g===""+r?r:g}}else if(e.hasAttribute(d)){if(Ft(t,r,o,!1))return e.getAttribute(d);if(o.type===ie)return r;v=e.getAttribute(d)}return Ft(t,r,o,!1)?v===null?r:v:v===""+r?r:v}}function Vn(e,t,r,o){{if(!ct(t))return;if(!e.hasAttribute(t))return r===void 0?void 0:null;var s=e.getAttribute(t);return In(r,t),s===""+r?r:s}}function qa(e,t,r,o){var s=ur(t);if(!Mt(t,s,o)){if(Ft(t,r,s,o)&&(r=null),o||s===null){if(ct(t)){var d=t;r===null?e.removeAttribute(d):(In(r,t),e.setAttribute(d,""+r))}return}var v=s.mustUseProperty;if(v){var g=s.propertyName;if(r===null){var b=s.type;e[g]=b===ie?!1:""}else e[g]=r;return}var w=s.attributeName,O=s.attributeNamespace;if(r===null)e.removeAttribute(w);else{var F=s.type,k;F===ie||F===xe&&r===!0?k="":(In(r,w),k=""+r,s.sanitizeURL&&Fi(k.toString())),O?e.setAttributeNS(O,w,k):e.setAttribute(w,k)}}}var jr=Symbol.for("react.element"),P=Symbol.for("react.portal"),he=Symbol.for("react.fragment"),we=Symbol.for("react.strict_mode"),Ye=Symbol.for("react.profiler"),Et=Symbol.for("react.provider"),qt=Symbol.for("react.context"),je=Symbol.for("react.forward_ref"),ht=Symbol.for("react.suspense"),An=Symbol.for("react.suspense_list"),on=Symbol.for("react.memo"),Rt=Symbol.for("react.lazy"),Nr=Symbol.for("react.scope"),ci=Symbol.for("react.debug_trace_mode"),Yu=Symbol.for("react.offscreen"),xa=Symbol.for("react.legacy_hidden"),Ho=Symbol.for("react.cache"),kd=Symbol.for("react.tracing_marker"),Qu=Symbol.iterator,Fd="@@iterator";function $i(e){if(e===null||typeof e!="object")return null;var t=Qu&&e[Qu]||e[Fd];return typeof t=="function"?t:null}var Tt=Object.assign,vu=0,qi,zo,us,os,ls,ss,Bo;function Io(){}Io.__reactDisabledLog=!0;function cs(){{if(vu===0){qi=console.log,zo=console.info,us=console.warn,os=console.error,ls=console.group,ss=console.groupCollapsed,Bo=console.groupEnd;var e={configurable:!0,enumerable:!0,value:Io,writable:!0};Object.defineProperties(console,{info:e,log:e,warn:e,error:e,group:e,groupCollapsed:e,groupEnd:e})}vu++}}function fs(){{if(vu--,vu===0){var e={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Tt({},e,{value:qi}),info:Tt({},e,{value:zo}),warn:Tt({},e,{value:us}),error:Tt({},e,{value:os}),group:Tt({},e,{value:ls}),groupCollapsed:Tt({},e,{value:ss}),groupEnd:Tt({},e,{value:Bo})})}vu<0&&p("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var ds=u.ReactCurrentDispatcher,mu;function Vr(e,t,r){{if(mu===void 0)try{throw Error()}catch(s){var o=s.stack.trim().match(/\n( *(at )?)/);mu=o&&o[1]||""}return` +`+mu+e}}var Hi=!1,yu;{var Ku=typeof WeakMap=="function"?WeakMap:Map;yu=new Ku}function jo(e,t){if(!e||Hi)return"";{var r=yu.get(e);if(r!==void 0)return r}var o;Hi=!0;var s=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var d;d=ds.current,ds.current=null,cs();try{if(t){var v=function(){throw Error()};if(Object.defineProperty(v.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(v,[])}catch(Y){o=Y}Reflect.construct(e,[],v)}else{try{v.call()}catch(Y){o=Y}e.call(v.prototype)}}else{try{throw Error()}catch(Y){o=Y}e()}}catch(Y){if(Y&&o&&typeof Y.stack=="string"){for(var g=Y.stack.split(` +`),b=o.stack.split(` +`),w=g.length-1,O=b.length-1;w>=1&&O>=0&&g[w]!==b[O];)O--;for(;w>=1&&O>=0;w--,O--)if(g[w]!==b[O]){if(w!==1||O!==1)do if(w--,O--,O<0||g[w]!==b[O]){var F=` +`+g[w].replace(" at new "," at ");return e.displayName&&F.includes("")&&(F=F.replace("",e.displayName)),typeof e=="function"&&yu.set(e,F),F}while(w>=1&&O>=0);break}}}finally{Hi=!1,ds.current=d,fs(),Error.prepareStackTrace=s}var k=e?e.displayName||e.name:"",G=k?Vr(k):"";return typeof e=="function"&&yu.set(e,G),G}function Vo(e,t,r){return jo(e,!0)}function hs(e,t,r){return jo(e,!1)}function Ic(e){var t=e.prototype;return!!(t&&t.isReactComponent)}function fi(e,t,r){if(e==null)return"";if(typeof e=="function")return jo(e,Ic(e));if(typeof e=="string")return Vr(e);switch(e){case ht:return Vr("Suspense");case An:return Vr("SuspenseList")}if(typeof e=="object")switch(e.$$typeof){case je:return hs(e.render);case on:return fi(e.type,t,r);case Rt:{var o=e,s=o._payload,d=o._init;try{return fi(d(s),t,r)}catch{}}}return""}function Go(e){switch(e._debugOwner&&e._debugOwner.type,e._debugSource,e.tag){case $:return Vr(e.type);case De:return Vr("Lazy");case de:return Vr("Suspense");case ze:return Vr("SuspenseList");case C:case L:case Ee:return hs(e.type);case V:return hs(e.type.render);case x:return Vo(e.type);default:return""}}function di(e){try{var t="",r=e;do t+=Go(r),r=r.return;while(r);return t}catch(o){return` +Error generating stack: `+o.message+` +`+o.stack}}function Xu(e,t,r){var o=e.displayName;if(o)return o;var s=t.displayName||t.name||"";return s!==""?r+"("+s+")":r}function ps(e){return e.displayName||"Context"}function Ht(e){if(e==null)return null;if(typeof e.tag=="number"&&p("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case he:return"Fragment";case P:return"Portal";case Ye:return"Profiler";case we:return"StrictMode";case ht:return"Suspense";case An:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case qt:var t=e;return ps(t)+".Consumer";case Et:var r=e;return ps(r._context)+".Provider";case je:return Xu(e,e.render,"ForwardRef");case on:var o=e.displayName||null;return o!==null?o:Ht(e.type)||"Memo";case Rt:{var s=e,d=s._payload,v=s._init;try{return Ht(v(d))}catch{return null}}}return null}function Wo(e,t,r){var o=t.displayName||t.name||"";return e.displayName||(o!==""?r+"("+o+")":r)}function Yo(e){return e.displayName||"Context"}function lt(e){var t=e.tag,r=e.type;switch(t){case Nt:return"Cache";case X:var o=r;return Yo(o)+".Consumer";case me:var s=r;return Yo(s._context)+".Provider";case Ie:return"DehydratedFragment";case V:return Wo(r,r.render,"ForwardRef");case se:return"Fragment";case $:return r;case A:return"Portal";case D:return"Root";case j:return"Text";case De:return Ht(r);case Z:return r===we?"StrictMode":"Mode";case Be:return"Offscreen";case z:return"Profiler";case Lt:return"Scope";case de:return"Suspense";case ze:return"SuspenseList";case rn:return"TracingMarker";case x:case C:case qe:case L:case Se:case Ee:if(typeof r=="function")return r.displayName||r.name||null;if(typeof r=="string")return r;break}return null}var vs=u.ReactDebugCurrentFrame,kr=null,Ju=!1;function gu(){{if(kr===null)return null;var e=kr._debugOwner;if(e!==null&&typeof e<"u")return lt(e)}return null}function ms(){return kr===null?"":di(kr)}function lr(){vs.getCurrentStack=null,kr=null,Ju=!1}function Ln(e){vs.getCurrentStack=e===null?null:ms,kr=e,Ju=!1}function jc(){return kr}function wa(e){Ju=e}function Fr(e){return""+e}function hi(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return jn(e),e;default:return""}}var $d={button:!0,checkbox:!0,image:!0,hidden:!0,radio:!0,reset:!0,submit:!0};function Qo(e,t){$d[t.type]||t.onChange||t.onInput||t.readOnly||t.disabled||t.value==null||p("You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."),t.onChange||t.readOnly||t.disabled||t.checked==null||p("You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.")}function Zu(e){var t=e.type,r=e.nodeName;return r&&r.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Vc(e){return e._valueTracker}function Vt(e){e._valueTracker=null}function ln(e){var t="";return e&&(Zu(e)?t=e.checked?"true":"false":t=e.value),t}function qd(e){var t=Zu(e)?"checked":"value",r=Object.getOwnPropertyDescriptor(e.constructor.prototype,t);jn(e[t]);var o=""+e[t];if(!(e.hasOwnProperty(t)||typeof r>"u"||typeof r.get!="function"||typeof r.set!="function")){var s=r.get,d=r.set;Object.defineProperty(e,t,{configurable:!0,get:function(){return s.call(this)},set:function(g){jn(g),o=""+g,d.call(this,g)}}),Object.defineProperty(e,t,{enumerable:r.enumerable});var v={getValue:function(){return o},setValue:function(g){jn(g),o=""+g},stopTracking:function(){Vt(e),delete e[t]}};return v}}function zi(e){Vc(e)||(e._valueTracker=qd(e))}function Ko(e){if(!e)return!1;var t=Vc(e);if(!t)return!0;var r=t.getValue(),o=ln(e);return o!==r?(t.setValue(o),!0):!1}function bu(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var Xo=!1,Gc=!1,ys=!1,Wc=!1;function Gn(e){var t=e.type==="checkbox"||e.type==="radio";return t?e.checked!=null:e.value!=null}function S(e,t){var r=e,o=t.checked,s=Tt({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:o??r._wrapperState.initialChecked});return s}function U(e,t){Qo("input",t),t.checked!==void 0&&t.defaultChecked!==void 0&&!Gc&&(p("%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",gu()||"A component",t.type),Gc=!0),t.value!==void 0&&t.defaultValue!==void 0&&!Xo&&(p("%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",gu()||"A component",t.type),Xo=!0);var r=e,o=t.defaultValue==null?"":t.defaultValue;r._wrapperState={initialChecked:t.checked!=null?t.checked:t.defaultChecked,initialValue:hi(t.value!=null?t.value:o),controlled:Gn(t)}}function W(e,t){var r=e,o=t.checked;o!=null&&qa(r,"checked",o,!1)}function J(e,t){var r=e;{var o=Gn(t);!r._wrapperState.controlled&&o&&!Wc&&(p("A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),Wc=!0),r._wrapperState.controlled&&!o&&!ys&&(p("A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),ys=!0)}W(e,t);var s=hi(t.value),d=t.type;if(s!=null)d==="number"?(s===0&&r.value===""||r.value!=s)&&(r.value=Fr(s)):r.value!==Fr(s)&&(r.value=Fr(s));else if(d==="submit"||d==="reset"){r.removeAttribute("value");return}t.hasOwnProperty("value")?rt(r,t.type,s):t.hasOwnProperty("defaultValue")&&rt(r,t.type,hi(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(r.defaultChecked=!!t.defaultChecked)}function ve(e,t,r){var o=e;if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var s=t.type,d=s==="submit"||s==="reset";if(d&&(t.value===void 0||t.value===null))return;var v=Fr(o._wrapperState.initialValue);r||v!==o.value&&(o.value=v),o.defaultValue=v}var g=o.name;g!==""&&(o.name=""),o.defaultChecked=!o.defaultChecked,o.defaultChecked=!!o._wrapperState.initialChecked,g!==""&&(o.name=g)}function Ze(e,t){var r=e;J(r,t),ke(r,t)}function ke(e,t){var r=t.name;if(t.type==="radio"&&r!=null){for(var o=e;o.parentNode;)o=o.parentNode;In(r,"name");for(var s=o.querySelectorAll("input[name="+JSON.stringify(""+r)+'][type="radio"]'),d=0;d.")))}):t.dangerouslySetInnerHTML!=null&&(vn||(vn=!0,p("Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.")))),t.selected!=null&&!Ct&&(p("Use the `defaultValue` or `value` props on must be a scalar value if `multiple` is false.%s",r,_a())}}}}function Gt(e,t,r,o){var s=e.options;if(t){for(var d=r,v={},g=0;g.");var o=Tt({},t,{value:void 0,defaultValue:void 0,children:Fr(r._wrapperState.initialValue)});return o}function Id(e,t){var r=e;Qo("textarea",t),t.value!==void 0&&t.defaultValue!==void 0&&!gv&&(p("%s contains a textarea with both value and defaultValue props. Textarea elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled textarea and remove one of these props. More info: https://reactjs.org/link/controlled-components",gu()||"A component"),gv=!0);var o=t.value;if(o==null){var s=t.children,d=t.defaultValue;if(s!=null){p("Use the `defaultValue` or `value` props instead of setting children on