@open-mercato/core 0.4.8-develop-4e71d95aba → 0.4.8-develop-2acbd97ec3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/auth/api/profile/route.js +67 -6
- package/dist/modules/auth/api/profile/route.js.map +2 -2
- package/dist/modules/auth/backend/auth/profile/page.js +39 -3
- package/dist/modules/auth/backend/auth/profile/page.js.map +2 -2
- package/dist/modules/auth/backend/profile/change-password/page.js +39 -3
- package/dist/modules/auth/backend/profile/change-password/page.js.map +2 -2
- package/dist/modules/catalog/components/products/variantForm.js +2 -3
- package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +15 -2
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/lib/priceValidation.js +45 -0
- package/dist/modules/catalog/lib/priceValidation.js.map +7 -0
- package/package.json +3 -3
- package/src/modules/auth/api/profile/route.ts +69 -6
- package/src/modules/auth/backend/auth/profile/page.tsx +42 -4
- package/src/modules/auth/backend/profile/change-password/page.tsx +42 -4
- package/src/modules/auth/i18n/de.json +6 -0
- package/src/modules/auth/i18n/en.json +6 -0
- package/src/modules/auth/i18n/es.json +6 -0
- package/src/modules/auth/i18n/pl.json +6 -0
- package/src/modules/catalog/components/products/variantForm.ts +2 -3
- package/src/modules/catalog/data/validators.ts +18 -2
- package/src/modules/catalog/lib/priceValidation.ts +57 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/catalog/data/validators.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { CATALOG_PRICE_DISPLAY_MODES, CATALOG_PRODUCT_TYPES } from './types'\nimport { REFERENCE_UNIT_CODES } from '../lib/unitCodes'\n\nconst uuid = () => z.string().uuid()\n\nconst scoped = z.object({\n organizationId: uuid(),\n tenantId: uuid(),\n})\n\nconst tenantScoped = z.object({\n tenantId: uuid(),\n})\n\nconst currencyCodeSchema = z\n .string()\n .trim()\n .regex(/^[A-Z]{3}$/, 'currency code must be a three-letter ISO code')\n\nconst metadataSchema = z.record(z.string(), z.unknown()).optional()\n\nconst slugSchema = z\n .string()\n .trim()\n .toLowerCase()\n .regex(/^[a-z0-9\\-_]+$/, 'code must contain lowercase letters, digits, hyphen, or underscore')\n .max(150)\n\nconst handleSchema = z\n .string()\n .trim()\n .toLowerCase()\n .regex(/^[a-z0-9\\-_]+$/, 'handle must contain lowercase letters, digits, hyphen, or underscore')\n .max(150)\n\nconst skuSchema = z\n .string()\n .trim()\n .regex(/^[A-Za-z0-9\\-_\\.]+$/, 'SKU may include letters, numbers, hyphen, underscore, or period')\n .max(191)\n\nconst variantOptionValuesSchema = z\n .record(\n z\n .string()\n .trim()\n .min(1)\n .max(191),\n z.string().trim().max(255)\n )\n .optional()\n\nconst optionChoiceSchema = z.object({\n code: slugSchema,\n label: z.string().trim().max(255).optional(),\n})\n\nconst optionDefinitionSchema = z.object({\n code: slugSchema,\n label: z.string().trim().min(1).max(255),\n description: z.string().trim().max(2000).optional(),\n inputType: z.enum(['select', 'text', 'textarea', 'number']),\n isRequired: z.boolean().optional(),\n isMultiple: z.boolean().optional(),\n choices: z.array(optionChoiceSchema).max(200).optional(),\n})\n\nconst optionSchema = z.object({\n version: z.number().int().min(1).optional(),\n name: z.string().trim().max(255).optional(),\n description: z.string().trim().max(4000).optional(),\n options: z.array(optionDefinitionSchema).max(64),\n})\n\nconst tagLabelSchema = z.string().trim().min(1).max(100)\n\nconst offerBaseSchema = z.object({\n channelId: uuid(),\n title: z.string().trim().min(1).max(255),\n description: z.string().trim().max(4000).optional(),\n defaultMediaId: uuid().optional().nullable(),\n defaultMediaUrl: z.string().trim().max(500).optional().nullable(),\n metadata: metadataSchema,\n isActive: z.boolean().optional(),\n})\n\nconst offerInputSchema = offerBaseSchema.extend({\n id: uuid().optional(),\n})\n\nexport const offerCreateSchema = scoped.merge(\n offerBaseSchema.extend({\n productId: uuid(),\n })\n)\n\nexport const offerUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(\n offerBaseSchema\n .extend({\n productId: uuid().optional(),\n })\n .partial()\n )\n\nconst productTypeSchema = z.enum(CATALOG_PRODUCT_TYPES)\nconst uomRoundingModeSchema = z.enum(['half_up', 'down', 'up'])\nconst unitPriceReferenceUnitSchema = z.enum(REFERENCE_UNIT_CODES)\nconst unitPriceConfigSchema = z.object({\n enabled: z.boolean().optional(),\n referenceUnit: unitPriceReferenceUnitSchema.nullable().optional(),\n baseQuantity: z.coerce.number().positive().optional(),\n})\n\nfunction productUomCrossFieldRefinement(\n input: {\n defaultUnit?: string | null\n defaultSalesUnit?: string | null\n unitPriceEnabled?: boolean\n unitPriceReferenceUnit?: string | null\n unitPriceBaseQuantity?: number\n unitPrice?: { enabled?: boolean; referenceUnit?: string | null; baseQuantity?: number }\n },\n ctx: z.RefinementCtx,\n) {\n const defaultUnit = typeof input.defaultUnit === 'string' ? input.defaultUnit.trim() : ''\n const defaultSalesUnit =\n typeof input.defaultSalesUnit === 'string' ? input.defaultSalesUnit.trim() : ''\n if (defaultSalesUnit && !defaultUnit) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['defaultSalesUnit'],\n message: 'catalog.products.validation.baseUnitRequired',\n })\n }\n const unitPriceEnabled = input.unitPrice?.enabled ?? input.unitPriceEnabled ?? false\n if (!unitPriceEnabled) return\n const referenceUnit =\n input.unitPrice?.referenceUnit ?? input.unitPriceReferenceUnit ?? null\n const baseQuantity =\n input.unitPrice?.baseQuantity ?? input.unitPriceBaseQuantity ?? null\n if (!referenceUnit) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['unitPrice'],\n message: 'catalog.products.validation.referenceUnitRequired',\n })\n }\n if (baseQuantity === null || baseQuantity === undefined || Number(baseQuantity) <= 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['unitPrice'],\n message: 'catalog.products.unitPrice.errors.baseQuantity',\n })\n }\n}\n\n// Base schema without refinements (used for .partial() in update schema)\nconst productBaseSchema = scoped.extend({\n title: z.string().trim().min(1).max(255),\n subtitle: z.string().trim().max(255).optional(),\n description: z.string().trim().max(4000).optional(),\n sku: skuSchema.optional(),\n handle: handleSchema.optional(),\n taxRateId: uuid().nullable().optional(),\n taxRate: z.coerce.number().min(0).max(100).optional().nullable(),\n productType: productTypeSchema.default('simple'),\n statusEntryId: uuid().optional(),\n primaryCurrencyCode: currencyCodeSchema.optional(),\n defaultUnit: z.string().trim().max(50).optional().nullable(),\n defaultSalesUnit: z.string().trim().max(50).optional().nullable(),\n defaultSalesUnitQuantity: z.coerce.number().positive().optional(),\n uomRoundingScale: z.coerce.number().int().min(0).max(6).optional(),\n uomRoundingMode: uomRoundingModeSchema.optional(),\n unitPriceEnabled: z.boolean().optional(),\n unitPriceReferenceUnit: unitPriceReferenceUnitSchema.nullable().optional(),\n unitPriceBaseQuantity: z.coerce.number().positive().optional(),\n unitPrice: unitPriceConfigSchema.optional(),\n defaultMediaId: uuid().optional().nullable(),\n defaultMediaUrl: z.string().trim().max(500).optional().nullable(),\n weightValue: z.coerce.number().min(0).optional().nullable(),\n weightUnit: z.string().trim().max(25).optional().nullable(),\n dimensions: z\n .object({\n width: z.coerce.number().min(0).optional(),\n height: z.coerce.number().min(0).optional(),\n depth: z.coerce.number().min(0).optional(),\n unit: z.string().trim().max(25).optional(),\n })\n .optional()\n .nullable(),\n optionSchemaId: uuid().nullable().optional(),\n optionSchema: optionSchema.optional(),\n customFieldsetCode: slugSchema.nullable().optional(),\n isConfigurable: z.boolean().optional(),\n isActive: z.boolean().optional(),\n metadata: metadataSchema,\n offers: z.array(offerInputSchema.omit({ id: true })).optional(),\n categoryIds: z.array(uuid()).max(100).optional(),\n tags: z.array(tagLabelSchema).max(100).optional(),\n})\n\nexport const productCreateSchema = productBaseSchema\n .superRefine(productUomCrossFieldRefinement)\n\nexport const productUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(productBaseSchema.partial())\n .extend({\n productType: productTypeSchema.optional(),\n })\n .superRefine(productUomCrossFieldRefinement)\n\nexport const variantCreateSchema = scoped.extend({\n productId: uuid(),\n name: z.string().trim().max(255).optional(),\n sku: z\n .string()\n .trim()\n .regex(/^[A-Za-z0-9\\-_\\.]+$/)\n .max(191)\n .optional(),\n barcode: z.string().trim().max(191).optional(),\n statusEntryId: uuid().optional(),\n isDefault: z.boolean().optional(),\n isActive: z.boolean().optional(),\n defaultMediaId: uuid().optional().nullable(),\n defaultMediaUrl: z.string().trim().max(500).optional().nullable(),\n weightValue: z.coerce.number().min(0).optional(),\n weightUnit: z.string().trim().max(25).optional(),\n taxRateId: uuid().nullable().optional(),\n taxRate: z.coerce.number().min(0).max(100).optional().nullable(),\n dimensions: z\n .object({\n width: z.coerce.number().min(0).optional(),\n height: z.coerce.number().min(0).optional(),\n depth: z.coerce.number().min(0).optional(),\n unit: z.string().trim().max(25).optional(),\n })\n .optional(),\n metadata: metadataSchema,\n optionValues: variantOptionValuesSchema,\n customFieldsetCode: slugSchema.nullable().optional(),\n})\n\nexport const variantUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(variantCreateSchema.partial())\n\nexport const optionSchemaTemplateCreateSchema = scoped.extend({\n name: z.string().trim().min(1).max(255),\n code: slugSchema.optional(),\n description: z.string().trim().max(4000).optional(),\n schema: optionSchema,\n metadata: metadataSchema,\n isActive: z.boolean().optional(),\n})\n\nexport const optionSchemaTemplateUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(optionSchemaTemplateCreateSchema.partial())\n\nconst priceDisplayModeSchema = z.enum(CATALOG_PRICE_DISPLAY_MODES)\n\nexport const priceKindCreateSchema = tenantScoped.extend({\n code: slugSchema,\n title: z.string().trim().min(1).max(255),\n displayMode: priceDisplayModeSchema.default('excluding-tax'),\n currencyCode: currencyCodeSchema.optional(),\n isPromotion: z.boolean().optional(),\n isActive: z.boolean().optional(),\n})\n\nexport const priceKindUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(priceKindCreateSchema.partial())\n\nexport const priceCreateSchema = scoped.extend({\n variantId: uuid().optional(),\n productId: uuid().optional(),\n offerId: uuid().optional(),\n currencyCode: currencyCodeSchema,\n priceKindId: uuid(),\n minQuantity: z.coerce.number().int().min(1).optional(),\n maxQuantity: z.coerce.number().int().min(1).optional(),\n unitPriceNet: z.coerce.number().min(0).optional(),\n unitPriceGross: z.coerce.number().min(0).optional(),\n taxRate: z.coerce.number().min(0).max(100).optional(),\n taxRateId: uuid().nullable().optional(),\n channelId: uuid().optional(),\n userId: uuid().optional(),\n userGroupId: uuid().optional(),\n customerId: uuid().optional(),\n customerGroupId: uuid().optional(),\n metadata: metadataSchema,\n startsAt: z.coerce.date().optional(),\n endsAt: z.coerce.date().optional(),\n})\n\nexport const priceUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(priceCreateSchema.partial())\n\nexport const categoryCreateSchema = scoped.extend({\n name: z.string().trim().min(1).max(255),\n slug: slugSchema.optional().nullable(),\n description: z.string().trim().max(2000).optional(),\n parentId: uuid().optional().nullable(),\n isActive: z.boolean().optional(),\n})\n\nexport const categoryUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(categoryCreateSchema.partial())\n\nexport const productUnitConversionCreateSchema = scoped.extend({\n productId: uuid(),\n unitCode: z.string().trim().min(1).max(50),\n toBaseFactor: z.coerce.number().positive().max(1_000_000),\n sortOrder: z.coerce.number().int().optional(),\n isActive: z.boolean().optional(),\n metadata: metadataSchema,\n})\n\nexport const productUnitConversionUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(productUnitConversionCreateSchema.omit({ productId: true }).partial())\n\nexport const productUnitConversionDeleteSchema = scoped.extend({\n id: uuid(),\n})\n\nexport type ProductCreateInput = z.infer<typeof productCreateSchema>\nexport type ProductUpdateInput = z.infer<typeof productUpdateSchema>\nexport type VariantCreateInput = z.infer<typeof variantCreateSchema>\nexport type VariantUpdateInput = z.infer<typeof variantUpdateSchema>\nexport type OptionSchemaTemplateCreateInput = z.infer<typeof optionSchemaTemplateCreateSchema>\nexport type OptionSchemaTemplateUpdateInput = z.infer<typeof optionSchemaTemplateUpdateSchema>\nexport type PriceKindCreateInput = z.infer<typeof priceKindCreateSchema>\nexport type PriceKindUpdateInput = z.infer<typeof priceKindUpdateSchema>\nexport type PriceCreateInput = z.infer<typeof priceCreateSchema>\nexport type PriceUpdateInput = z.infer<typeof priceUpdateSchema>\nexport type CategoryCreateInput = z.infer<typeof categoryCreateSchema>\nexport type CategoryUpdateInput = z.infer<typeof categoryUpdateSchema>\nexport type OfferInput = z.infer<typeof offerInputSchema>\nexport type OfferCreateInput = z.infer<typeof offerCreateSchema>\nexport type OfferUpdateInput = z.infer<typeof offerUpdateSchema>\nexport type ProductUnitConversionCreateInput = z.infer<typeof productUnitConversionCreateSchema>\nexport type ProductUnitConversionUpdateInput = z.infer<typeof productUnitConversionUpdateSchema>\nexport type ProductUnitConversionDeleteInput = z.infer<typeof productUnitConversionDeleteSchema>\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,6BAA6B,6BAA6B;AACnE,SAAS,4BAA4B;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { CATALOG_PRICE_DISPLAY_MODES, CATALOG_PRODUCT_TYPES } from './types'\nimport { REFERENCE_UNIT_CODES } from '../lib/unitCodes'\nimport {\n getCatalogPriceAmountValidationMessage,\n validateCatalogPriceAmountInput,\n} from '../lib/priceValidation'\n\nconst uuid = () => z.string().uuid()\n\nconst scoped = z.object({\n organizationId: uuid(),\n tenantId: uuid(),\n})\n\nconst tenantScoped = z.object({\n tenantId: uuid(),\n})\n\nconst currencyCodeSchema = z\n .string()\n .trim()\n .regex(/^[A-Z]{3}$/, 'currency code must be a three-letter ISO code')\n\nconst metadataSchema = z.record(z.string(), z.unknown()).optional()\n\nconst slugSchema = z\n .string()\n .trim()\n .toLowerCase()\n .regex(/^[a-z0-9\\-_]+$/, 'code must contain lowercase letters, digits, hyphen, or underscore')\n .max(150)\n\nconst handleSchema = z\n .string()\n .trim()\n .toLowerCase()\n .regex(/^[a-z0-9\\-_]+$/, 'handle must contain lowercase letters, digits, hyphen, or underscore')\n .max(150)\n\nconst skuSchema = z\n .string()\n .trim()\n .regex(/^[A-Za-z0-9\\-_\\.]+$/, 'SKU may include letters, numbers, hyphen, underscore, or period')\n .max(191)\n\nconst variantOptionValuesSchema = z\n .record(\n z\n .string()\n .trim()\n .min(1)\n .max(191),\n z.string().trim().max(255)\n )\n .optional()\n\nconst optionChoiceSchema = z.object({\n code: slugSchema,\n label: z.string().trim().max(255).optional(),\n})\n\nconst optionDefinitionSchema = z.object({\n code: slugSchema,\n label: z.string().trim().min(1).max(255),\n description: z.string().trim().max(2000).optional(),\n inputType: z.enum(['select', 'text', 'textarea', 'number']),\n isRequired: z.boolean().optional(),\n isMultiple: z.boolean().optional(),\n choices: z.array(optionChoiceSchema).max(200).optional(),\n})\n\nconst optionSchema = z.object({\n version: z.number().int().min(1).optional(),\n name: z.string().trim().max(255).optional(),\n description: z.string().trim().max(4000).optional(),\n options: z.array(optionDefinitionSchema).max(64),\n})\n\nconst tagLabelSchema = z.string().trim().min(1).max(100)\n\nconst offerBaseSchema = z.object({\n channelId: uuid(),\n title: z.string().trim().min(1).max(255),\n description: z.string().trim().max(4000).optional(),\n defaultMediaId: uuid().optional().nullable(),\n defaultMediaUrl: z.string().trim().max(500).optional().nullable(),\n metadata: metadataSchema,\n isActive: z.boolean().optional(),\n})\n\nconst offerInputSchema = offerBaseSchema.extend({\n id: uuid().optional(),\n})\n\nexport const offerCreateSchema = scoped.merge(\n offerBaseSchema.extend({\n productId: uuid(),\n })\n)\n\nexport const offerUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(\n offerBaseSchema\n .extend({\n productId: uuid().optional(),\n })\n .partial()\n )\n\nconst productTypeSchema = z.enum(CATALOG_PRODUCT_TYPES)\nconst uomRoundingModeSchema = z.enum(['half_up', 'down', 'up'])\nconst unitPriceReferenceUnitSchema = z.enum(REFERENCE_UNIT_CODES)\nconst unitPriceConfigSchema = z.object({\n enabled: z.boolean().optional(),\n referenceUnit: unitPriceReferenceUnitSchema.nullable().optional(),\n baseQuantity: z.coerce.number().positive().optional(),\n})\n\nconst catalogPriceAmountSchema = z\n .custom<number>((value) => validateCatalogPriceAmountInput(value).ok, {\n message: getCatalogPriceAmountValidationMessage(),\n })\n .transform((value) => {\n const result = validateCatalogPriceAmountInput(value)\n if (!result.ok) {\n throw new Error('catalogPriceAmountSchema transform reached invalid state')\n }\n return result.numeric\n })\n\nfunction productUomCrossFieldRefinement(\n input: {\n defaultUnit?: string | null\n defaultSalesUnit?: string | null\n unitPriceEnabled?: boolean\n unitPriceReferenceUnit?: string | null\n unitPriceBaseQuantity?: number\n unitPrice?: { enabled?: boolean; referenceUnit?: string | null; baseQuantity?: number }\n },\n ctx: z.RefinementCtx,\n) {\n const defaultUnit = typeof input.defaultUnit === 'string' ? input.defaultUnit.trim() : ''\n const defaultSalesUnit =\n typeof input.defaultSalesUnit === 'string' ? input.defaultSalesUnit.trim() : ''\n if (defaultSalesUnit && !defaultUnit) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['defaultSalesUnit'],\n message: 'catalog.products.validation.baseUnitRequired',\n })\n }\n const unitPriceEnabled = input.unitPrice?.enabled ?? input.unitPriceEnabled ?? false\n if (!unitPriceEnabled) return\n const referenceUnit =\n input.unitPrice?.referenceUnit ?? input.unitPriceReferenceUnit ?? null\n const baseQuantity =\n input.unitPrice?.baseQuantity ?? input.unitPriceBaseQuantity ?? null\n if (!referenceUnit) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['unitPrice'],\n message: 'catalog.products.validation.referenceUnitRequired',\n })\n }\n if (baseQuantity === null || baseQuantity === undefined || Number(baseQuantity) <= 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['unitPrice'],\n message: 'catalog.products.unitPrice.errors.baseQuantity',\n })\n }\n}\n\n// Base schema without refinements (used for .partial() in update schema)\nconst productBaseSchema = scoped.extend({\n title: z.string().trim().min(1).max(255),\n subtitle: z.string().trim().max(255).optional(),\n description: z.string().trim().max(4000).optional(),\n sku: skuSchema.optional(),\n handle: handleSchema.optional(),\n taxRateId: uuid().nullable().optional(),\n taxRate: z.coerce.number().min(0).max(100).optional().nullable(),\n productType: productTypeSchema.default('simple'),\n statusEntryId: uuid().optional(),\n primaryCurrencyCode: currencyCodeSchema.optional(),\n defaultUnit: z.string().trim().max(50).optional().nullable(),\n defaultSalesUnit: z.string().trim().max(50).optional().nullable(),\n defaultSalesUnitQuantity: z.coerce.number().positive().optional(),\n uomRoundingScale: z.coerce.number().int().min(0).max(6).optional(),\n uomRoundingMode: uomRoundingModeSchema.optional(),\n unitPriceEnabled: z.boolean().optional(),\n unitPriceReferenceUnit: unitPriceReferenceUnitSchema.nullable().optional(),\n unitPriceBaseQuantity: z.coerce.number().positive().optional(),\n unitPrice: unitPriceConfigSchema.optional(),\n defaultMediaId: uuid().optional().nullable(),\n defaultMediaUrl: z.string().trim().max(500).optional().nullable(),\n weightValue: z.coerce.number().min(0).optional().nullable(),\n weightUnit: z.string().trim().max(25).optional().nullable(),\n dimensions: z\n .object({\n width: z.coerce.number().min(0).optional(),\n height: z.coerce.number().min(0).optional(),\n depth: z.coerce.number().min(0).optional(),\n unit: z.string().trim().max(25).optional(),\n })\n .optional()\n .nullable(),\n optionSchemaId: uuid().nullable().optional(),\n optionSchema: optionSchema.optional(),\n customFieldsetCode: slugSchema.nullable().optional(),\n isConfigurable: z.boolean().optional(),\n isActive: z.boolean().optional(),\n metadata: metadataSchema,\n offers: z.array(offerInputSchema.omit({ id: true })).optional(),\n categoryIds: z.array(uuid()).max(100).optional(),\n tags: z.array(tagLabelSchema).max(100).optional(),\n})\n\nexport const productCreateSchema = productBaseSchema\n .superRefine(productUomCrossFieldRefinement)\n\nexport const productUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(productBaseSchema.partial())\n .extend({\n productType: productTypeSchema.optional(),\n })\n .superRefine(productUomCrossFieldRefinement)\n\nexport const variantCreateSchema = scoped.extend({\n productId: uuid(),\n name: z.string().trim().max(255).optional(),\n sku: z\n .string()\n .trim()\n .regex(/^[A-Za-z0-9\\-_\\.]+$/)\n .max(191)\n .optional(),\n barcode: z.string().trim().max(191).optional(),\n statusEntryId: uuid().optional(),\n isDefault: z.boolean().optional(),\n isActive: z.boolean().optional(),\n defaultMediaId: uuid().optional().nullable(),\n defaultMediaUrl: z.string().trim().max(500).optional().nullable(),\n weightValue: z.coerce.number().min(0).optional(),\n weightUnit: z.string().trim().max(25).optional(),\n taxRateId: uuid().nullable().optional(),\n taxRate: z.coerce.number().min(0).max(100).optional().nullable(),\n dimensions: z\n .object({\n width: z.coerce.number().min(0).optional(),\n height: z.coerce.number().min(0).optional(),\n depth: z.coerce.number().min(0).optional(),\n unit: z.string().trim().max(25).optional(),\n })\n .optional(),\n metadata: metadataSchema,\n optionValues: variantOptionValuesSchema,\n customFieldsetCode: slugSchema.nullable().optional(),\n})\n\nexport const variantUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(variantCreateSchema.partial())\n\nexport const optionSchemaTemplateCreateSchema = scoped.extend({\n name: z.string().trim().min(1).max(255),\n code: slugSchema.optional(),\n description: z.string().trim().max(4000).optional(),\n schema: optionSchema,\n metadata: metadataSchema,\n isActive: z.boolean().optional(),\n})\n\nexport const optionSchemaTemplateUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(optionSchemaTemplateCreateSchema.partial())\n\nconst priceDisplayModeSchema = z.enum(CATALOG_PRICE_DISPLAY_MODES)\n\nexport const priceKindCreateSchema = tenantScoped.extend({\n code: slugSchema,\n title: z.string().trim().min(1).max(255),\n displayMode: priceDisplayModeSchema.default('excluding-tax'),\n currencyCode: currencyCodeSchema.optional(),\n isPromotion: z.boolean().optional(),\n isActive: z.boolean().optional(),\n})\n\nexport const priceKindUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(priceKindCreateSchema.partial())\n\nexport const priceCreateSchema = scoped.extend({\n variantId: uuid().optional(),\n productId: uuid().optional(),\n offerId: uuid().optional(),\n currencyCode: currencyCodeSchema,\n priceKindId: uuid(),\n minQuantity: z.coerce.number().int().min(1).optional(),\n maxQuantity: z.coerce.number().int().min(1).optional(),\n unitPriceNet: catalogPriceAmountSchema.optional(),\n unitPriceGross: catalogPriceAmountSchema.optional(),\n taxRate: z.coerce.number().min(0).max(100).optional(),\n taxRateId: uuid().nullable().optional(),\n channelId: uuid().optional(),\n userId: uuid().optional(),\n userGroupId: uuid().optional(),\n customerId: uuid().optional(),\n customerGroupId: uuid().optional(),\n metadata: metadataSchema,\n startsAt: z.coerce.date().optional(),\n endsAt: z.coerce.date().optional(),\n})\n\nexport const priceUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(priceCreateSchema.partial())\n\nexport const categoryCreateSchema = scoped.extend({\n name: z.string().trim().min(1).max(255),\n slug: slugSchema.optional().nullable(),\n description: z.string().trim().max(2000).optional(),\n parentId: uuid().optional().nullable(),\n isActive: z.boolean().optional(),\n})\n\nexport const categoryUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(categoryCreateSchema.partial())\n\nexport const productUnitConversionCreateSchema = scoped.extend({\n productId: uuid(),\n unitCode: z.string().trim().min(1).max(50),\n toBaseFactor: z.coerce.number().positive().max(1_000_000),\n sortOrder: z.coerce.number().int().optional(),\n isActive: z.boolean().optional(),\n metadata: metadataSchema,\n})\n\nexport const productUnitConversionUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(productUnitConversionCreateSchema.omit({ productId: true }).partial())\n\nexport const productUnitConversionDeleteSchema = scoped.extend({\n id: uuid(),\n})\n\nexport type ProductCreateInput = z.infer<typeof productCreateSchema>\nexport type ProductUpdateInput = z.infer<typeof productUpdateSchema>\nexport type VariantCreateInput = z.infer<typeof variantCreateSchema>\nexport type VariantUpdateInput = z.infer<typeof variantUpdateSchema>\nexport type OptionSchemaTemplateCreateInput = z.infer<typeof optionSchemaTemplateCreateSchema>\nexport type OptionSchemaTemplateUpdateInput = z.infer<typeof optionSchemaTemplateUpdateSchema>\nexport type PriceKindCreateInput = z.infer<typeof priceKindCreateSchema>\nexport type PriceKindUpdateInput = z.infer<typeof priceKindUpdateSchema>\nexport type PriceCreateInput = z.infer<typeof priceCreateSchema>\nexport type PriceUpdateInput = z.infer<typeof priceUpdateSchema>\nexport type CategoryCreateInput = z.infer<typeof categoryCreateSchema>\nexport type CategoryUpdateInput = z.infer<typeof categoryUpdateSchema>\nexport type OfferInput = z.infer<typeof offerInputSchema>\nexport type OfferCreateInput = z.infer<typeof offerCreateSchema>\nexport type OfferUpdateInput = z.infer<typeof offerUpdateSchema>\nexport type ProductUnitConversionCreateInput = z.infer<typeof productUnitConversionCreateSchema>\nexport type ProductUnitConversionUpdateInput = z.infer<typeof productUnitConversionUpdateSchema>\nexport type ProductUnitConversionDeleteInput = z.infer<typeof productUnitConversionDeleteSchema>\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const CATALOG_PRICE_MAX_INTEGER_DIGITS = 12;
|
|
2
|
+
const CATALOG_PRICE_MAX_FRACTION_DIGITS = 4;
|
|
3
|
+
function normalizeCatalogPriceRawValue(value) {
|
|
4
|
+
if (typeof value === "number") {
|
|
5
|
+
if (!Number.isFinite(value)) return null;
|
|
6
|
+
return String(value);
|
|
7
|
+
}
|
|
8
|
+
if (typeof value !== "string") return null;
|
|
9
|
+
const normalized = value.trim().replace(/\s+/g, "");
|
|
10
|
+
return normalized.length ? normalized : null;
|
|
11
|
+
}
|
|
12
|
+
function validateCatalogPriceAmountInput(value) {
|
|
13
|
+
const raw = normalizeCatalogPriceRawValue(value);
|
|
14
|
+
if (!raw) return { ok: false, reason: "invalid_format" };
|
|
15
|
+
if (raw.startsWith("-")) return { ok: false, reason: "negative" };
|
|
16
|
+
if (!/^\d+(?:\.\d+)?$/.test(raw)) {
|
|
17
|
+
return { ok: false, reason: "invalid_format" };
|
|
18
|
+
}
|
|
19
|
+
const numeric = Number(raw);
|
|
20
|
+
if (!Number.isFinite(numeric)) return { ok: false, reason: "not_finite" };
|
|
21
|
+
if (numeric < 0) return { ok: false, reason: "negative" };
|
|
22
|
+
const [integerPartRaw, fractionPart = ""] = raw.split(".");
|
|
23
|
+
const integerPart = integerPartRaw.replace(/^0+(?=\d)/, "");
|
|
24
|
+
if (integerPart.length > CATALOG_PRICE_MAX_INTEGER_DIGITS) {
|
|
25
|
+
return { ok: false, reason: "too_many_integer_digits" };
|
|
26
|
+
}
|
|
27
|
+
if (fractionPart.length > CATALOG_PRICE_MAX_FRACTION_DIGITS) {
|
|
28
|
+
return { ok: false, reason: "too_many_fraction_digits" };
|
|
29
|
+
}
|
|
30
|
+
return { ok: true, numeric };
|
|
31
|
+
}
|
|
32
|
+
function isCatalogPriceAmountInputValid(value) {
|
|
33
|
+
return validateCatalogPriceAmountInput(value).ok;
|
|
34
|
+
}
|
|
35
|
+
function getCatalogPriceAmountValidationMessage() {
|
|
36
|
+
return `Price must be a valid non-negative amount with at most ${CATALOG_PRICE_MAX_INTEGER_DIGITS} digits before the decimal point and ${CATALOG_PRICE_MAX_FRACTION_DIGITS} decimal places.`;
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
CATALOG_PRICE_MAX_FRACTION_DIGITS,
|
|
40
|
+
CATALOG_PRICE_MAX_INTEGER_DIGITS,
|
|
41
|
+
getCatalogPriceAmountValidationMessage,
|
|
42
|
+
isCatalogPriceAmountInputValid,
|
|
43
|
+
validateCatalogPriceAmountInput
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=priceValidation.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/catalog/lib/priceValidation.ts"],
|
|
4
|
+
"sourcesContent": ["export const CATALOG_PRICE_MAX_INTEGER_DIGITS = 12\nexport const CATALOG_PRICE_MAX_FRACTION_DIGITS = 4\n\nexport type CatalogPriceAmountValidationReason =\n | 'invalid_format'\n | 'not_finite'\n | 'negative'\n | 'too_many_integer_digits'\n | 'too_many_fraction_digits'\n\nexport type CatalogPriceAmountValidationResult =\n | { ok: true; numeric: number }\n | { ok: false; reason: CatalogPriceAmountValidationReason }\n\nfunction normalizeCatalogPriceRawValue(value: unknown): string | null {\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) return null\n return String(value)\n }\n if (typeof value !== 'string') return null\n const normalized = value.trim().replace(/\\s+/g, '')\n return normalized.length ? normalized : null\n}\n\nexport function validateCatalogPriceAmountInput(\n value: unknown,\n): CatalogPriceAmountValidationResult {\n const raw = normalizeCatalogPriceRawValue(value)\n if (!raw) return { ok: false, reason: 'invalid_format' }\n if (raw.startsWith('-')) return { ok: false, reason: 'negative' }\n if (!/^\\d+(?:\\.\\d+)?$/.test(raw)) {\n return { ok: false, reason: 'invalid_format' }\n }\n\n const numeric = Number(raw)\n if (!Number.isFinite(numeric)) return { ok: false, reason: 'not_finite' }\n if (numeric < 0) return { ok: false, reason: 'negative' }\n\n const [integerPartRaw, fractionPart = ''] = raw.split('.')\n const integerPart = integerPartRaw.replace(/^0+(?=\\d)/, '')\n if (integerPart.length > CATALOG_PRICE_MAX_INTEGER_DIGITS) {\n return { ok: false, reason: 'too_many_integer_digits' }\n }\n if (fractionPart.length > CATALOG_PRICE_MAX_FRACTION_DIGITS) {\n return { ok: false, reason: 'too_many_fraction_digits' }\n }\n\n return { ok: true, numeric }\n}\n\nexport function isCatalogPriceAmountInputValid(value: unknown): boolean {\n return validateCatalogPriceAmountInput(value).ok\n}\n\nexport function getCatalogPriceAmountValidationMessage(): string {\n return `Price must be a valid non-negative amount with at most ${CATALOG_PRICE_MAX_INTEGER_DIGITS} digits before the decimal point and ${CATALOG_PRICE_MAX_FRACTION_DIGITS} decimal places.`\n}\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,mCAAmC;AACzC,MAAM,oCAAoC;AAajD,SAAS,8BAA8B,OAA+B;AACpE,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,aAAa,MAAM,KAAK,EAAE,QAAQ,QAAQ,EAAE;AAClD,SAAO,WAAW,SAAS,aAAa;AAC1C;AAEO,SAAS,gCACd,OACoC;AACpC,QAAM,MAAM,8BAA8B,KAAK;AAC/C,MAAI,CAAC,IAAK,QAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AACvD,MAAI,IAAI,WAAW,GAAG,EAAG,QAAO,EAAE,IAAI,OAAO,QAAQ,WAAW;AAChE,MAAI,CAAC,kBAAkB,KAAK,GAAG,GAAG;AAChC,WAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,EAC/C;AAEA,QAAM,UAAU,OAAO,GAAG;AAC1B,MAAI,CAAC,OAAO,SAAS,OAAO,EAAG,QAAO,EAAE,IAAI,OAAO,QAAQ,aAAa;AACxE,MAAI,UAAU,EAAG,QAAO,EAAE,IAAI,OAAO,QAAQ,WAAW;AAExD,QAAM,CAAC,gBAAgB,eAAe,EAAE,IAAI,IAAI,MAAM,GAAG;AACzD,QAAM,cAAc,eAAe,QAAQ,aAAa,EAAE;AAC1D,MAAI,YAAY,SAAS,kCAAkC;AACzD,WAAO,EAAE,IAAI,OAAO,QAAQ,0BAA0B;AAAA,EACxD;AACA,MAAI,aAAa,SAAS,mCAAmC;AAC3D,WAAO,EAAE,IAAI,OAAO,QAAQ,2BAA2B;AAAA,EACzD;AAEA,SAAO,EAAE,IAAI,MAAM,QAAQ;AAC7B;AAEO,SAAS,+BAA+B,OAAyB;AACtE,SAAO,gCAAgC,KAAK,EAAE;AAChD;AAEO,SAAS,yCAAiD;AAC/D,SAAO,0DAA0D,gCAAgC,wCAAwC,iCAAiC;AAC5K;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.8-develop-
|
|
3
|
+
"version": "0.4.8-develop-2acbd97ec3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -217,10 +217,10 @@
|
|
|
217
217
|
"semver": "^7.6.3"
|
|
218
218
|
},
|
|
219
219
|
"peerDependencies": {
|
|
220
|
-
"@open-mercato/shared": "0.4.8-develop-
|
|
220
|
+
"@open-mercato/shared": "0.4.8-develop-2acbd97ec3"
|
|
221
221
|
},
|
|
222
222
|
"devDependencies": {
|
|
223
|
-
"@open-mercato/shared": "0.4.8-develop-
|
|
223
|
+
"@open-mercato/shared": "0.4.8-develop-2acbd97ec3",
|
|
224
224
|
"@testing-library/dom": "^10.4.1",
|
|
225
225
|
"@testing-library/jest-dom": "^6.9.1",
|
|
226
226
|
"@testing-library/react": "^16.3.1",
|
|
@@ -20,14 +20,49 @@ const profileResponseSchema = z.object({
|
|
|
20
20
|
|
|
21
21
|
const passwordSchema = buildPasswordSchema()
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const updateSchemaBase = z.object({
|
|
24
24
|
email: z.string().email().optional(),
|
|
25
|
+
currentPassword: z.string().trim().min(1).optional(),
|
|
25
26
|
password: passwordSchema.optional(),
|
|
26
|
-
}).refine((data) => Boolean(data.email || data.password), {
|
|
27
|
-
message: 'Provide an email or password.',
|
|
28
|
-
path: ['email'],
|
|
29
27
|
})
|
|
30
28
|
|
|
29
|
+
function buildUpdateSchema(translate: (key: string, fallback: string) => string) {
|
|
30
|
+
return updateSchemaBase.superRefine((data, ctx) => {
|
|
31
|
+
if (!data.email && !data.password) {
|
|
32
|
+
ctx.addIssue({
|
|
33
|
+
code: z.ZodIssueCode.custom,
|
|
34
|
+
message: translate(
|
|
35
|
+
'auth.profile.form.errors.emailOrPasswordRequired',
|
|
36
|
+
'Provide an email or password.',
|
|
37
|
+
),
|
|
38
|
+
path: ['email'],
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
if (data.password && !data.currentPassword) {
|
|
42
|
+
ctx.addIssue({
|
|
43
|
+
code: z.ZodIssueCode.custom,
|
|
44
|
+
message: translate(
|
|
45
|
+
'auth.profile.form.errors.currentPasswordRequired',
|
|
46
|
+
'Current password is required.',
|
|
47
|
+
),
|
|
48
|
+
path: ['currentPassword'],
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
if (data.currentPassword && !data.password) {
|
|
52
|
+
ctx.addIssue({
|
|
53
|
+
code: z.ZodIssueCode.custom,
|
|
54
|
+
message: translate(
|
|
55
|
+
'auth.profile.form.errors.newPasswordRequired',
|
|
56
|
+
'New password is required.',
|
|
57
|
+
),
|
|
58
|
+
path: ['password'],
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const updateSchema = buildUpdateSchema((_key, fallback) => fallback)
|
|
65
|
+
|
|
31
66
|
const profileUpdateResponseSchema = z.object({
|
|
32
67
|
ok: z.literal(true),
|
|
33
68
|
email: z.string().email(),
|
|
@@ -83,7 +118,7 @@ export async function PUT(req: Request) {
|
|
|
83
118
|
}
|
|
84
119
|
try {
|
|
85
120
|
const body = await req.json().catch(() => ({}))
|
|
86
|
-
const parsed =
|
|
121
|
+
const parsed = buildUpdateSchema(translate).safeParse(body)
|
|
87
122
|
if (!parsed.success) {
|
|
88
123
|
return NextResponse.json(
|
|
89
124
|
{
|
|
@@ -94,6 +129,35 @@ export async function PUT(req: Request) {
|
|
|
94
129
|
)
|
|
95
130
|
}
|
|
96
131
|
const container = await createRequestContainer()
|
|
132
|
+
const em = (container.resolve('em') as EntityManager)
|
|
133
|
+
const authService = container.resolve('authService') as AuthService
|
|
134
|
+
if (parsed.data.password) {
|
|
135
|
+
const user = await findOneWithDecryption(
|
|
136
|
+
em,
|
|
137
|
+
User,
|
|
138
|
+
{ id: auth.sub, deletedAt: null },
|
|
139
|
+
undefined,
|
|
140
|
+
{ tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },
|
|
141
|
+
)
|
|
142
|
+
if (!user) {
|
|
143
|
+
return NextResponse.json({ error: translate('auth.users.form.errors.notFound', 'User not found') }, { status: 404 })
|
|
144
|
+
}
|
|
145
|
+
const currentPassword = parsed.data.currentPassword?.trim() ?? ''
|
|
146
|
+
const isCurrentPasswordValid = await authService.verifyPassword(user, currentPassword)
|
|
147
|
+
if (!isCurrentPasswordValid) {
|
|
148
|
+
const message = translate(
|
|
149
|
+
'auth.profile.form.errors.currentPasswordInvalid',
|
|
150
|
+
'Current password is incorrect.',
|
|
151
|
+
)
|
|
152
|
+
return NextResponse.json(
|
|
153
|
+
{
|
|
154
|
+
error: message,
|
|
155
|
+
issues: [{ path: ['currentPassword'], message }],
|
|
156
|
+
},
|
|
157
|
+
{ status: 400 },
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
97
161
|
const commandBus = (container.resolve('commandBus') as CommandBus)
|
|
98
162
|
const ctx = buildCommandContext(container, auth, req)
|
|
99
163
|
const { result } = await commandBus.execute<{ id: string; email?: string; password?: string }, User>(
|
|
@@ -107,7 +171,6 @@ export async function PUT(req: Request) {
|
|
|
107
171
|
ctx,
|
|
108
172
|
},
|
|
109
173
|
)
|
|
110
|
-
const authService = container.resolve('authService') as AuthService
|
|
111
174
|
const roles = await authService.getUserRoles(result, result.tenantId ? String(result.tenantId) : null)
|
|
112
175
|
const jwt = signJwt({
|
|
113
176
|
sub: String(result.id),
|
|
@@ -24,6 +24,7 @@ type ProfileUpdateResponse = {
|
|
|
24
24
|
|
|
25
25
|
type ProfileFormValues = {
|
|
26
26
|
email: string
|
|
27
|
+
currentPassword?: string
|
|
27
28
|
password?: string
|
|
28
29
|
confirmPassword?: string
|
|
29
30
|
}
|
|
@@ -70,13 +71,22 @@ export default function AuthProfilePage() {
|
|
|
70
71
|
|
|
71
72
|
const fields = React.useMemo<CrudField[]>(() => [
|
|
72
73
|
{ id: 'email', label: t('auth.profile.form.email', 'Email'), type: 'text', required: true },
|
|
74
|
+
{
|
|
75
|
+
id: 'currentPassword',
|
|
76
|
+
label: t('auth.profile.form.currentPassword', 'Current password'),
|
|
77
|
+
type: 'password',
|
|
78
|
+
},
|
|
73
79
|
{
|
|
74
80
|
id: 'password',
|
|
75
81
|
label: t('auth.profile.form.password', 'New password'),
|
|
76
|
-
type: '
|
|
82
|
+
type: 'password',
|
|
77
83
|
description: passwordDescription,
|
|
78
84
|
},
|
|
79
|
-
{
|
|
85
|
+
{
|
|
86
|
+
id: 'confirmPassword',
|
|
87
|
+
label: t('auth.profile.form.confirmPassword', 'Confirm new password'),
|
|
88
|
+
type: 'password',
|
|
89
|
+
},
|
|
80
90
|
], [passwordDescription, t])
|
|
81
91
|
|
|
82
92
|
const schema = React.useMemo(() => {
|
|
@@ -87,12 +97,37 @@ export default function AuthProfilePage() {
|
|
|
87
97
|
const optionalPasswordSchema = z.union([z.literal(''), passwordSchema]).optional()
|
|
88
98
|
return z.object({
|
|
89
99
|
email: z.string().trim().min(1, t('auth.profile.form.errors.emailRequired', 'Email is required.')),
|
|
100
|
+
currentPassword: z.string().optional(),
|
|
90
101
|
password: optionalPasswordSchema,
|
|
91
102
|
confirmPassword: z.string().optional(),
|
|
92
103
|
}).superRefine((values, ctx) => {
|
|
104
|
+
const currentPassword = values.currentPassword?.trim() ?? ''
|
|
93
105
|
const password = values.password?.trim() ?? ''
|
|
94
106
|
const confirmPassword = values.confirmPassword?.trim() ?? ''
|
|
95
|
-
|
|
107
|
+
const hasPasswordIntent = Boolean(currentPassword || password || confirmPassword)
|
|
108
|
+
|
|
109
|
+
if (hasPasswordIntent && !currentPassword) {
|
|
110
|
+
ctx.addIssue({
|
|
111
|
+
code: z.ZodIssueCode.custom,
|
|
112
|
+
message: t('auth.profile.form.errors.currentPasswordRequired', 'Current password is required.'),
|
|
113
|
+
path: ['currentPassword'],
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
if (hasPasswordIntent && !password) {
|
|
117
|
+
ctx.addIssue({
|
|
118
|
+
code: z.ZodIssueCode.custom,
|
|
119
|
+
message: t('auth.profile.form.errors.newPasswordRequired', 'New password is required.'),
|
|
120
|
+
path: ['password'],
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
if (hasPasswordIntent && !confirmPassword) {
|
|
124
|
+
ctx.addIssue({
|
|
125
|
+
code: z.ZodIssueCode.custom,
|
|
126
|
+
message: t('auth.profile.form.errors.confirmPasswordRequired', 'Please confirm the new password.'),
|
|
127
|
+
path: ['confirmPassword'],
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
if (password && confirmPassword && password !== confirmPassword) {
|
|
96
131
|
ctx.addIssue({
|
|
97
132
|
code: z.ZodIssueCode.custom,
|
|
98
133
|
message: t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'),
|
|
@@ -104,14 +139,16 @@ export default function AuthProfilePage() {
|
|
|
104
139
|
|
|
105
140
|
const handleSubmit = React.useCallback(async (values: ProfileFormValues) => {
|
|
106
141
|
const nextEmail = values.email?.trim() ?? ''
|
|
142
|
+
const currentPassword = values.currentPassword?.trim() ?? ''
|
|
107
143
|
const password = values.password?.trim() ?? ''
|
|
108
144
|
|
|
109
145
|
if (!password && nextEmail === email) {
|
|
110
146
|
throw createCrudFormError(t('auth.profile.form.errors.noChanges', 'No changes to save.'))
|
|
111
147
|
}
|
|
112
148
|
|
|
113
|
-
const payload: { email: string; password?: string } = { email: nextEmail }
|
|
149
|
+
const payload: { email: string; currentPassword?: string; password?: string } = { email: nextEmail }
|
|
114
150
|
if (password) payload.password = password
|
|
151
|
+
if (password) payload.currentPassword = currentPassword
|
|
115
152
|
|
|
116
153
|
const result = await readApiResultOrThrow<ProfileUpdateResponse>(
|
|
117
154
|
'/api/auth/profile',
|
|
@@ -158,6 +195,7 @@ export default function AuthProfilePage() {
|
|
|
158
195
|
fields={fields}
|
|
159
196
|
initialValues={{
|
|
160
197
|
email,
|
|
198
|
+
currentPassword: '',
|
|
161
199
|
password: '',
|
|
162
200
|
confirmPassword: '',
|
|
163
201
|
}}
|
|
@@ -23,6 +23,7 @@ type ProfileUpdateResponse = {
|
|
|
23
23
|
|
|
24
24
|
type ProfileFormValues = {
|
|
25
25
|
email: string
|
|
26
|
+
currentPassword?: string
|
|
26
27
|
password?: string
|
|
27
28
|
confirmPassword?: string
|
|
28
29
|
}
|
|
@@ -69,13 +70,22 @@ export default function ProfileChangePasswordPage() {
|
|
|
69
70
|
|
|
70
71
|
const fields = React.useMemo<CrudField[]>(() => [
|
|
71
72
|
{ id: 'email', label: t('auth.profile.form.email', 'Email'), type: 'text', required: true },
|
|
73
|
+
{
|
|
74
|
+
id: 'currentPassword',
|
|
75
|
+
label: t('auth.profile.form.currentPassword', 'Current password'),
|
|
76
|
+
type: 'password',
|
|
77
|
+
},
|
|
72
78
|
{
|
|
73
79
|
id: 'password',
|
|
74
80
|
label: t('auth.profile.form.password', 'New password'),
|
|
75
|
-
type: '
|
|
81
|
+
type: 'password',
|
|
76
82
|
description: passwordDescription,
|
|
77
83
|
},
|
|
78
|
-
{
|
|
84
|
+
{
|
|
85
|
+
id: 'confirmPassword',
|
|
86
|
+
label: t('auth.profile.form.confirmPassword', 'Confirm new password'),
|
|
87
|
+
type: 'password',
|
|
88
|
+
},
|
|
79
89
|
], [passwordDescription, t])
|
|
80
90
|
|
|
81
91
|
const schema = React.useMemo(() => {
|
|
@@ -86,12 +96,37 @@ export default function ProfileChangePasswordPage() {
|
|
|
86
96
|
const optionalPasswordSchema = z.union([z.literal(''), passwordSchema]).optional()
|
|
87
97
|
return z.object({
|
|
88
98
|
email: z.string().trim().min(1, t('auth.profile.form.errors.emailRequired', 'Email is required.')),
|
|
99
|
+
currentPassword: z.string().optional(),
|
|
89
100
|
password: optionalPasswordSchema,
|
|
90
101
|
confirmPassword: z.string().optional(),
|
|
91
102
|
}).superRefine((values, ctx) => {
|
|
103
|
+
const currentPassword = values.currentPassword?.trim() ?? ''
|
|
92
104
|
const password = values.password?.trim() ?? ''
|
|
93
105
|
const confirmPassword = values.confirmPassword?.trim() ?? ''
|
|
94
|
-
|
|
106
|
+
const hasPasswordIntent = Boolean(currentPassword || password || confirmPassword)
|
|
107
|
+
|
|
108
|
+
if (hasPasswordIntent && !currentPassword) {
|
|
109
|
+
ctx.addIssue({
|
|
110
|
+
code: z.ZodIssueCode.custom,
|
|
111
|
+
message: t('auth.profile.form.errors.currentPasswordRequired', 'Current password is required.'),
|
|
112
|
+
path: ['currentPassword'],
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
if (hasPasswordIntent && !password) {
|
|
116
|
+
ctx.addIssue({
|
|
117
|
+
code: z.ZodIssueCode.custom,
|
|
118
|
+
message: t('auth.profile.form.errors.newPasswordRequired', 'New password is required.'),
|
|
119
|
+
path: ['password'],
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
if (hasPasswordIntent && !confirmPassword) {
|
|
123
|
+
ctx.addIssue({
|
|
124
|
+
code: z.ZodIssueCode.custom,
|
|
125
|
+
message: t('auth.profile.form.errors.confirmPasswordRequired', 'Please confirm the new password.'),
|
|
126
|
+
path: ['confirmPassword'],
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
if (password && confirmPassword && password !== confirmPassword) {
|
|
95
130
|
ctx.addIssue({
|
|
96
131
|
code: z.ZodIssueCode.custom,
|
|
97
132
|
message: t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'),
|
|
@@ -103,14 +138,16 @@ export default function ProfileChangePasswordPage() {
|
|
|
103
138
|
|
|
104
139
|
const handleSubmit = React.useCallback(async (values: ProfileFormValues) => {
|
|
105
140
|
const nextEmail = values.email?.trim() ?? ''
|
|
141
|
+
const currentPassword = values.currentPassword?.trim() ?? ''
|
|
106
142
|
const password = values.password?.trim() ?? ''
|
|
107
143
|
|
|
108
144
|
if (!password && nextEmail === email) {
|
|
109
145
|
throw createCrudFormError(t('auth.profile.form.errors.noChanges', 'No changes to save.'))
|
|
110
146
|
}
|
|
111
147
|
|
|
112
|
-
const payload: { email: string; password?: string } = { email: nextEmail }
|
|
148
|
+
const payload: { email: string; currentPassword?: string; password?: string } = { email: nextEmail }
|
|
113
149
|
if (password) payload.password = password
|
|
150
|
+
if (password) payload.currentPassword = currentPassword
|
|
114
151
|
|
|
115
152
|
const result = await readApiResultOrThrow<ProfileUpdateResponse>(
|
|
116
153
|
'/api/auth/profile',
|
|
@@ -158,6 +195,7 @@ export default function ProfileChangePasswordPage() {
|
|
|
158
195
|
fields={fields}
|
|
159
196
|
initialValues={{
|
|
160
197
|
email,
|
|
198
|
+
currentPassword: '',
|
|
161
199
|
password: '',
|
|
162
200
|
confirmPassword: '',
|
|
163
201
|
}}
|
|
@@ -65,10 +65,16 @@
|
|
|
65
65
|
"auth.password.requirements.special": "Ein Sonderzeichen",
|
|
66
66
|
"auth.password.requirements.uppercase": "Ein Großbuchstabe",
|
|
67
67
|
"auth.profile.form.confirmPassword": "Neues Passwort bestätigen",
|
|
68
|
+
"auth.profile.form.currentPassword": "Aktuelles Passwort",
|
|
68
69
|
"auth.profile.form.email": "E-Mail",
|
|
70
|
+
"auth.profile.form.errors.confirmPasswordRequired": "Bitte bestätige das neue Passwort.",
|
|
71
|
+
"auth.profile.form.errors.currentPasswordInvalid": "Das aktuelle Passwort ist falsch.",
|
|
72
|
+
"auth.profile.form.errors.currentPasswordRequired": "Das aktuelle Passwort ist erforderlich.",
|
|
73
|
+
"auth.profile.form.errors.emailOrPasswordRequired": "Gib eine E-Mail-Adresse oder ein Passwort an.",
|
|
69
74
|
"auth.profile.form.errors.emailRequired": "E-Mail ist erforderlich.",
|
|
70
75
|
"auth.profile.form.errors.invalid": "Ungültige Profilaktualisierung.",
|
|
71
76
|
"auth.profile.form.errors.load": "Profil konnte nicht geladen werden.",
|
|
77
|
+
"auth.profile.form.errors.newPasswordRequired": "Neues Passwort ist erforderlich.",
|
|
72
78
|
"auth.profile.form.errors.noChanges": "Keine Änderungen zu speichern.",
|
|
73
79
|
"auth.profile.form.errors.passwordMismatch": "Die Passwörter stimmen nicht überein.",
|
|
74
80
|
"auth.profile.form.errors.passwordRequirements": "Das Passwort muss die Anforderungen erfüllen.",
|
|
@@ -65,10 +65,16 @@
|
|
|
65
65
|
"auth.password.requirements.special": "One special character",
|
|
66
66
|
"auth.password.requirements.uppercase": "One uppercase letter",
|
|
67
67
|
"auth.profile.form.confirmPassword": "Confirm new password",
|
|
68
|
+
"auth.profile.form.currentPassword": "Current password",
|
|
68
69
|
"auth.profile.form.email": "Email",
|
|
70
|
+
"auth.profile.form.errors.confirmPasswordRequired": "Please confirm the new password.",
|
|
71
|
+
"auth.profile.form.errors.currentPasswordInvalid": "Current password is incorrect.",
|
|
72
|
+
"auth.profile.form.errors.currentPasswordRequired": "Current password is required.",
|
|
73
|
+
"auth.profile.form.errors.emailOrPasswordRequired": "Provide an email or password.",
|
|
69
74
|
"auth.profile.form.errors.emailRequired": "Email is required.",
|
|
70
75
|
"auth.profile.form.errors.invalid": "Invalid profile update.",
|
|
71
76
|
"auth.profile.form.errors.load": "Failed to load profile.",
|
|
77
|
+
"auth.profile.form.errors.newPasswordRequired": "New password is required.",
|
|
72
78
|
"auth.profile.form.errors.noChanges": "No changes to save.",
|
|
73
79
|
"auth.profile.form.errors.passwordMismatch": "Passwords do not match.",
|
|
74
80
|
"auth.profile.form.errors.passwordRequirements": "Password must meet the requirements.",
|
|
@@ -65,10 +65,16 @@
|
|
|
65
65
|
"auth.password.requirements.special": "Un carácter especial",
|
|
66
66
|
"auth.password.requirements.uppercase": "Una letra mayúscula",
|
|
67
67
|
"auth.profile.form.confirmPassword": "Confirmar nueva contraseña",
|
|
68
|
+
"auth.profile.form.currentPassword": "Contraseña actual",
|
|
68
69
|
"auth.profile.form.email": "Correo electrónico",
|
|
70
|
+
"auth.profile.form.errors.confirmPasswordRequired": "Confirma la nueva contraseña.",
|
|
71
|
+
"auth.profile.form.errors.currentPasswordInvalid": "La contraseña actual es incorrecta.",
|
|
72
|
+
"auth.profile.form.errors.currentPasswordRequired": "La contraseña actual es obligatoria.",
|
|
73
|
+
"auth.profile.form.errors.emailOrPasswordRequired": "Proporciona un correo electrónico o una contraseña.",
|
|
69
74
|
"auth.profile.form.errors.emailRequired": "El correo electrónico es obligatorio.",
|
|
70
75
|
"auth.profile.form.errors.invalid": "Actualización de perfil inválida.",
|
|
71
76
|
"auth.profile.form.errors.load": "No se pudo cargar el perfil.",
|
|
77
|
+
"auth.profile.form.errors.newPasswordRequired": "La nueva contraseña es obligatoria.",
|
|
72
78
|
"auth.profile.form.errors.noChanges": "No hay cambios para guardar.",
|
|
73
79
|
"auth.profile.form.errors.passwordMismatch": "Las contraseñas no coinciden.",
|
|
74
80
|
"auth.profile.form.errors.passwordRequirements": "La contraseña debe cumplir los requisitos.",
|
|
@@ -65,10 +65,16 @@
|
|
|
65
65
|
"auth.password.requirements.special": "Jeden znak specjalny",
|
|
66
66
|
"auth.password.requirements.uppercase": "Jedna wielka litera",
|
|
67
67
|
"auth.profile.form.confirmPassword": "Potwierdź nowe hasło",
|
|
68
|
+
"auth.profile.form.currentPassword": "Obecne hasło",
|
|
68
69
|
"auth.profile.form.email": "Email",
|
|
70
|
+
"auth.profile.form.errors.confirmPasswordRequired": "Potwierdź nowe hasło.",
|
|
71
|
+
"auth.profile.form.errors.currentPasswordInvalid": "Obecne hasło jest nieprawidłowe.",
|
|
72
|
+
"auth.profile.form.errors.currentPasswordRequired": "Obecne hasło jest wymagane.",
|
|
73
|
+
"auth.profile.form.errors.emailOrPasswordRequired": "Podaj adres e-mail lub hasło.",
|
|
69
74
|
"auth.profile.form.errors.emailRequired": "Email jest wymagany.",
|
|
70
75
|
"auth.profile.form.errors.invalid": "Nieprawidłowa aktualizacja profilu.",
|
|
71
76
|
"auth.profile.form.errors.load": "Nie udało się wczytać profilu.",
|
|
77
|
+
"auth.profile.form.errors.newPasswordRequired": "Nowe hasło jest wymagane.",
|
|
72
78
|
"auth.profile.form.errors.noChanges": "Brak zmian do zapisania.",
|
|
73
79
|
"auth.profile.form.errors.passwordMismatch": "Hasła nie są zgodne.",
|
|
74
80
|
"auth.profile.form.errors.passwordRequirements": "Hasło musi spełniać wymagania.",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ProductMediaItem } from './ProductMediaManager'
|
|
4
4
|
import { createLocalId, type PriceKindSummary } from './productForm'
|
|
5
|
-
import {
|
|
5
|
+
import { isCatalogPriceAmountInputValid } from '../../lib/priceValidation'
|
|
6
6
|
|
|
7
7
|
export type OptionDefinition = {
|
|
8
8
|
id: string
|
|
@@ -107,8 +107,7 @@ export function findInvalidVariantPriceKinds(
|
|
|
107
107
|
const draft = priceDrafts?.[kind.id]
|
|
108
108
|
const amount = typeof draft?.amount === 'string' ? draft.amount.trim() : ''
|
|
109
109
|
if (!amount) continue
|
|
110
|
-
|
|
111
|
-
if (!Number.isFinite(numeric) || numeric < 0) invalid.push(kind.id)
|
|
110
|
+
if (!isCatalogPriceAmountInputValid(amount)) invalid.push(kind.id)
|
|
112
111
|
}
|
|
113
112
|
return invalid
|
|
114
113
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { CATALOG_PRICE_DISPLAY_MODES, CATALOG_PRODUCT_TYPES } from './types'
|
|
3
3
|
import { REFERENCE_UNIT_CODES } from '../lib/unitCodes'
|
|
4
|
+
import {
|
|
5
|
+
getCatalogPriceAmountValidationMessage,
|
|
6
|
+
validateCatalogPriceAmountInput,
|
|
7
|
+
} from '../lib/priceValidation'
|
|
4
8
|
|
|
5
9
|
const uuid = () => z.string().uuid()
|
|
6
10
|
|
|
@@ -116,6 +120,18 @@ const unitPriceConfigSchema = z.object({
|
|
|
116
120
|
baseQuantity: z.coerce.number().positive().optional(),
|
|
117
121
|
})
|
|
118
122
|
|
|
123
|
+
const catalogPriceAmountSchema = z
|
|
124
|
+
.custom<number>((value) => validateCatalogPriceAmountInput(value).ok, {
|
|
125
|
+
message: getCatalogPriceAmountValidationMessage(),
|
|
126
|
+
})
|
|
127
|
+
.transform((value) => {
|
|
128
|
+
const result = validateCatalogPriceAmountInput(value)
|
|
129
|
+
if (!result.ok) {
|
|
130
|
+
throw new Error('catalogPriceAmountSchema transform reached invalid state')
|
|
131
|
+
}
|
|
132
|
+
return result.numeric
|
|
133
|
+
})
|
|
134
|
+
|
|
119
135
|
function productUomCrossFieldRefinement(
|
|
120
136
|
input: {
|
|
121
137
|
defaultUnit?: string | null
|
|
@@ -295,8 +311,8 @@ export const priceCreateSchema = scoped.extend({
|
|
|
295
311
|
priceKindId: uuid(),
|
|
296
312
|
minQuantity: z.coerce.number().int().min(1).optional(),
|
|
297
313
|
maxQuantity: z.coerce.number().int().min(1).optional(),
|
|
298
|
-
unitPriceNet:
|
|
299
|
-
unitPriceGross:
|
|
314
|
+
unitPriceNet: catalogPriceAmountSchema.optional(),
|
|
315
|
+
unitPriceGross: catalogPriceAmountSchema.optional(),
|
|
300
316
|
taxRate: z.coerce.number().min(0).max(100).optional(),
|
|
301
317
|
taxRateId: uuid().nullable().optional(),
|
|
302
318
|
channelId: uuid().optional(),
|