@endora-commerce/mod-promotions 0.100.0
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/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/admin/api/promotions-client.d.ts +98 -0
- package/dist/admin/api/promotions-client.d.ts.map +1 -0
- package/dist/admin/api/promotions-client.js +40 -0
- package/dist/admin/api/promotions-client.js.map +1 -0
- package/dist/admin/components/CouponGeneratorForm.d.ts +10 -0
- package/dist/admin/components/CouponGeneratorForm.d.ts.map +1 -0
- package/dist/admin/components/CouponGeneratorForm.js +56 -0
- package/dist/admin/components/CouponGeneratorForm.js.map +1 -0
- package/dist/admin/index.d.ts +43 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +69 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/PromotionEditPage.d.ts +9 -0
- package/dist/admin/pages/PromotionEditPage.d.ts.map +1 -0
- package/dist/admin/pages/PromotionEditPage.js +333 -0
- package/dist/admin/pages/PromotionEditPage.js.map +1 -0
- package/dist/admin/pages/PromotionRulesPage.d.ts +14 -0
- package/dist/admin/pages/PromotionRulesPage.d.ts.map +1 -0
- package/dist/admin/pages/PromotionRulesPage.js +86 -0
- package/dist/admin/pages/PromotionRulesPage.js.map +1 -0
- package/dist/admin/pages/PromotionStatsPage.d.ts +13 -0
- package/dist/admin/pages/PromotionStatsPage.d.ts.map +1 -0
- package/dist/admin/pages/PromotionStatsPage.js +44 -0
- package/dist/admin/pages/PromotionStatsPage.js.map +1 -0
- package/dist/admin/pages/PromotionsPage.d.ts +9 -0
- package/dist/admin/pages/PromotionsPage.d.ts.map +1 -0
- package/dist/admin/pages/PromotionsPage.js +79 -0
- package/dist/admin/pages/PromotionsPage.js.map +1 -0
- package/dist/backend/actions/amount-off-cart.d.ts +4 -0
- package/dist/backend/actions/amount-off-cart.d.ts.map +1 -0
- package/dist/backend/actions/amount-off-cart.js +16 -0
- package/dist/backend/actions/amount-off-cart.js.map +1 -0
- package/dist/backend/actions/buy-x-get-y-free.d.ts +8 -0
- package/dist/backend/actions/buy-x-get-y-free.d.ts.map +1 -0
- package/dist/backend/actions/buy-x-get-y-free.js +20 -0
- package/dist/backend/actions/buy-x-get-y-free.js.map +1 -0
- package/dist/backend/actions/buy-x-units-amount-off.d.ts +4 -0
- package/dist/backend/actions/buy-x-units-amount-off.d.ts.map +1 -0
- package/dist/backend/actions/buy-x-units-amount-off.js +19 -0
- package/dist/backend/actions/buy-x-units-amount-off.js.map +1 -0
- package/dist/backend/actions/buy-x-units-percent-off.d.ts +4 -0
- package/dist/backend/actions/buy-x-units-percent-off.d.ts.map +1 -0
- package/dist/backend/actions/buy-x-units-percent-off.js +15 -0
- package/dist/backend/actions/buy-x-units-percent-off.js.map +1 -0
- package/dist/backend/actions/buy-x-units-y-free.d.ts +7 -0
- package/dist/backend/actions/buy-x-units-y-free.d.ts.map +1 -0
- package/dist/backend/actions/buy-x-units-y-free.js +21 -0
- package/dist/backend/actions/buy-x-units-y-free.js.map +1 -0
- package/dist/backend/actions/every-nth-product-percent-off.d.ts +7 -0
- package/dist/backend/actions/every-nth-product-percent-off.d.ts.map +1 -0
- package/dist/backend/actions/every-nth-product-percent-off.js +17 -0
- package/dist/backend/actions/every-nth-product-percent-off.js.map +1 -0
- package/dist/backend/actions/free-delivery.d.ts +4 -0
- package/dist/backend/actions/free-delivery.d.ts.map +1 -0
- package/dist/backend/actions/free-delivery.js +11 -0
- package/dist/backend/actions/free-delivery.js.map +1 -0
- package/dist/backend/actions/percentage-off-cart.d.ts +4 -0
- package/dist/backend/actions/percentage-off-cart.d.ts.map +1 -0
- package/dist/backend/actions/percentage-off-cart.js +11 -0
- package/dist/backend/actions/percentage-off-cart.js.map +1 -0
- package/dist/backend/actions/spend-x-amount-off.d.ts +4 -0
- package/dist/backend/actions/spend-x-amount-off.d.ts.map +1 -0
- package/dist/backend/actions/spend-x-amount-off.js +17 -0
- package/dist/backend/actions/spend-x-amount-off.js.map +1 -0
- package/dist/backend/actions/spend-x-percent-off.d.ts +4 -0
- package/dist/backend/actions/spend-x-percent-off.d.ts.map +1 -0
- package/dist/backend/actions/spend-x-percent-off.js +18 -0
- package/dist/backend/actions/spend-x-percent-off.js.map +1 -0
- package/dist/backend/actions/types.d.ts +37 -0
- package/dist/backend/actions/types.d.ts.map +1 -0
- package/dist/backend/actions/types.js +2 -0
- package/dist/backend/actions/types.js.map +1 -0
- package/dist/backend/actions/util.d.ts +13 -0
- package/dist/backend/actions/util.d.ts.map +1 -0
- package/dist/backend/actions/util.js +18 -0
- package/dist/backend/actions/util.js.map +1 -0
- package/dist/backend/entities/coupon-batch.entity.d.ts +21 -0
- package/dist/backend/entities/coupon-batch.entity.d.ts.map +1 -0
- package/dist/backend/entities/coupon-batch.entity.js +78 -0
- package/dist/backend/entities/coupon-batch.entity.js.map +1 -0
- package/dist/backend/entities/promotion-coupon.entity.d.ts +17 -0
- package/dist/backend/entities/promotion-coupon.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion-coupon.entity.js +64 -0
- package/dist/backend/entities/promotion-coupon.entity.js.map +1 -0
- package/dist/backend/entities/promotion-rule.entity.d.ts +16 -0
- package/dist/backend/entities/promotion-rule.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion-rule.entity.js +57 -0
- package/dist/backend/entities/promotion-rule.entity.js.map +1 -0
- package/dist/backend/entities/promotion-usage-counter.entity.d.ts +15 -0
- package/dist/backend/entities/promotion-usage-counter.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion-usage-counter.entity.js +48 -0
- package/dist/backend/entities/promotion-usage-counter.entity.js.map +1 -0
- package/dist/backend/entities/promotion-usage.entity.d.ts +21 -0
- package/dist/backend/entities/promotion-usage.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion-usage.entity.js +89 -0
- package/dist/backend/entities/promotion-usage.entity.js.map +1 -0
- package/dist/backend/entities/promotion.entity.d.ts +63 -0
- package/dist/backend/entities/promotion.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion.entity.js +194 -0
- package/dist/backend/entities/promotion.entity.js.map +1 -0
- package/dist/backend/index.d.ts +124 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +182 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.d.ts +88 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +256 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/coupon-service.d.ts +40 -0
- package/dist/backend/services/coupon-service.d.ts.map +1 -0
- package/dist/backend/services/coupon-service.js +178 -0
- package/dist/backend/services/coupon-service.js.map +1 -0
- package/dist/backend/services/promotion-action-registry.d.ts +20 -0
- package/dist/backend/services/promotion-action-registry.d.ts.map +1 -0
- package/dist/backend/services/promotion-action-registry.js +57 -0
- package/dist/backend/services/promotion-action-registry.js.map +1 -0
- package/dist/backend/services/promotion-code-port.d.ts +24 -0
- package/dist/backend/services/promotion-code-port.d.ts.map +1 -0
- package/dist/backend/services/promotion-code-port.js +53 -0
- package/dist/backend/services/promotion-code-port.js.map +1 -0
- package/dist/backend/services/promotion-currency-reference.d.ts +13 -0
- package/dist/backend/services/promotion-currency-reference.d.ts.map +1 -0
- package/dist/backend/services/promotion-currency-reference.js +25 -0
- package/dist/backend/services/promotion-currency-reference.js.map +1 -0
- package/dist/backend/services/promotion-rule-evaluator.d.ts +32 -0
- package/dist/backend/services/promotion-rule-evaluator.d.ts.map +1 -0
- package/dist/backend/services/promotion-rule-evaluator.js +131 -0
- package/dist/backend/services/promotion-rule-evaluator.js.map +1 -0
- package/dist/backend/services/promotion-rule-service.d.ts +62 -0
- package/dist/backend/services/promotion-rule-service.d.ts.map +1 -0
- package/dist/backend/services/promotion-rule-service.js +95 -0
- package/dist/backend/services/promotion-rule-service.js.map +1 -0
- package/dist/backend/services/promotion-rule-store.d.ts +33 -0
- package/dist/backend/services/promotion-rule-store.d.ts.map +1 -0
- package/dist/backend/services/promotion-rule-store.js +88 -0
- package/dist/backend/services/promotion-rule-store.js.map +1 -0
- package/dist/backend/services/promotion-service.d.ts +312 -0
- package/dist/backend/services/promotion-service.d.ts.map +1 -0
- package/dist/backend/services/promotion-service.js +762 -0
- package/dist/backend/services/promotion-service.js.map +1 -0
- package/dist/backend/services/promotion-stats-service.d.ts +15 -0
- package/dist/backend/services/promotion-stats-service.d.ts.map +1 -0
- package/dist/backend/services/promotion-stats-service.js +66 -0
- package/dist/backend/services/promotion-stats-service.js.map +1 -0
- package/dist/backend/services/promotion-usage-service.d.ts +46 -0
- package/dist/backend/services/promotion-usage-service.d.ts.map +1 -0
- package/dist/backend/services/promotion-usage-service.js +168 -0
- package/dist/backend/services/promotion-usage-service.js.map +1 -0
- package/dist/manifest.d.ts +172 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +140 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260505T074605_promotions_criteria.d.ts +19 -0
- package/dist/migrations/20260505T074605_promotions_criteria.d.ts.map +1 -0
- package/dist/migrations/20260505T074605_promotions_criteria.js +23 -0
- package/dist/migrations/20260505T074605_promotions_criteria.js.map +1 -0
- package/dist/migrations/20260618T100727_promotions_engine.d.ts +19 -0
- package/dist/migrations/20260618T100727_promotions_engine.d.ts.map +1 -0
- package/dist/migrations/20260618T100727_promotions_engine.js +148 -0
- package/dist/migrations/20260618T100727_promotions_engine.js.map +1 -0
- package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.d.ts +42 -0
- package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.d.ts.map +1 -0
- package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.js +76 -0
- package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.js.map +1 -0
- package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.d.ts +27 -0
- package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.d.ts.map +1 -0
- package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.js +44 -0
- package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.js.map +1 -0
- package/dist/migrations/index.d.ts +39 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +44 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +106 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/promotions.md +96 -0
- package/i18n/en.json +34 -0
- package/i18n/pl.json +34 -0
- package/package.json +101 -0
- package/tailwind.css +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Endora sp. z o.o. and the Endora Commerce contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!-- Generated by `pnpm --filter backend run manifests:generate`. Delete this line to take the file over; a README without it is never regenerated. -->
|
|
2
|
+
|
|
3
|
+
# @endora-commerce/mod-promotions
|
|
4
|
+
|
|
5
|
+
Promotion rules engine: rule builder, actions, coupons, limits, statistics.
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
An **Endora Commerce module package**. Its module id is `promotions` — the identity of record in the platform's registries, in its migrations and in its setting, permission and translation codes.
|
|
10
|
+
|
|
11
|
+
A module is not imported by application code. The platform discovers the extension packages an instance has installed, reads the manifest on the root subpath and composes the module from it, so installing this package and starting the instance is the whole integration.
|
|
12
|
+
|
|
13
|
+
## Entry points
|
|
14
|
+
|
|
15
|
+
| Import | Contents |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `@endora-commerce/mod-promotions` | the module manifest — its id, version, dependencies, settings and activation |
|
|
18
|
+
| `@endora-commerce/mod-promotions/admin` | the Admin UI contribution: the screens, navigation and palette actions it adds |
|
|
19
|
+
| `@endora-commerce/mod-promotions/backend` | the composition root the platform calls, with the entities, services, routes and workers it registers |
|
|
20
|
+
| `@endora-commerce/mod-promotions/migrations` | the module’s own schema migrations, in the order the platform runs them |
|
|
21
|
+
| `@endora-commerce/mod-promotions/ports` | type-only — the port interfaces other modules resolve this one through |
|
|
22
|
+
| `@endora-commerce/mod-promotions/tailwind.css` | the Tailwind source declaration for the admin code above |
|
|
23
|
+
|
|
24
|
+
## Depends on
|
|
25
|
+
|
|
26
|
+
Everything below is a **peer** dependency, so the application resolves exactly one copy of each — two copies of a host or of React are a runtime failure rather than a type error. An entry marked *optional* is needed only by the layers that use it.
|
|
27
|
+
|
|
28
|
+
**Endora packages**
|
|
29
|
+
|
|
30
|
+
- `@endora-commerce/admin-kit` — *optional*
|
|
31
|
+
- `@endora-commerce/contracts`
|
|
32
|
+
- `@endora-commerce/platform`
|
|
33
|
+
|
|
34
|
+
**Third-party**
|
|
35
|
+
|
|
36
|
+
- `@mikro-orm/core` ^6
|
|
37
|
+
- `@mikro-orm/migrations` ^6
|
|
38
|
+
- `@mikro-orm/postgresql` ^6
|
|
39
|
+
- `fastify` ^5
|
|
40
|
+
- `lucide-react` ^1 — *optional*
|
|
41
|
+
- `react` ^19 — *optional*
|
|
42
|
+
- `react-router-dom` ^7 — *optional*
|
|
43
|
+
|
|
44
|
+
## What the tarball carries
|
|
45
|
+
|
|
46
|
+
- `dist/` — the compiled JavaScript and its type declarations
|
|
47
|
+
- `i18n/` — the translation bundles this package ships (`en`, `pl`), loaded by the platform
|
|
48
|
+
- `docs/` — the package’s own pages in the Endora Commerce documentation site
|
|
49
|
+
- `tailwind.css` — the Tailwind source declaration
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm add @endora-commerce/mod-promotions
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Licence
|
|
58
|
+
|
|
59
|
+
MIT — the text is in `LICENSE`, beside this file.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Promotion, PromotionAction, PromotionRule, PromotionRuleRecord, PromotionStatsGroupBy, PromotionUsageStats } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Feature 045 — admin API client for the promotions engine.
|
|
4
|
+
*/
|
|
5
|
+
export interface UpsertPromotionPayload {
|
|
6
|
+
name: string;
|
|
7
|
+
description?: string | null;
|
|
8
|
+
isActive?: boolean;
|
|
9
|
+
priority?: number;
|
|
10
|
+
stopFurther?: boolean;
|
|
11
|
+
action?: PromotionAction;
|
|
12
|
+
rule?: PromotionRule | null;
|
|
13
|
+
ruleId?: string | null;
|
|
14
|
+
code?: string | null;
|
|
15
|
+
minCartSubtotal?: number | null;
|
|
16
|
+
validFrom?: string | null;
|
|
17
|
+
validUntil?: string | null;
|
|
18
|
+
usageLimitGlobal?: number | null;
|
|
19
|
+
usageLimitPerOrganization?: number | null;
|
|
20
|
+
usageLimitPerCustomer?: number | null;
|
|
21
|
+
}
|
|
22
|
+
export type RuleTargetKind = 'customer-groups' | 'organizations' | 'categories' | 'payment-methods' | 'delivery-methods';
|
|
23
|
+
export interface RuleTargetItem {
|
|
24
|
+
id: string;
|
|
25
|
+
code?: string;
|
|
26
|
+
name: string;
|
|
27
|
+
slug?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface PromoRuleAttribute {
|
|
30
|
+
id: string;
|
|
31
|
+
key: string;
|
|
32
|
+
label: Record<string, string>;
|
|
33
|
+
labelDefault: string;
|
|
34
|
+
valueType: 'string' | 'number' | 'boolean' | 'date' | 'enum' | 'select' | 'multiselect' | 'price';
|
|
35
|
+
options?: Array<{
|
|
36
|
+
value: string;
|
|
37
|
+
label: Record<string, string>;
|
|
38
|
+
labelDefault: string;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
export declare const promotionsClient: {
|
|
42
|
+
list: () => Promise<Promotion[]>;
|
|
43
|
+
get: (id: string) => Promise<Promotion>;
|
|
44
|
+
create: (payload: UpsertPromotionPayload) => Promise<Promotion>;
|
|
45
|
+
update: (id: string, payload: UpsertPromotionPayload) => Promise<Promotion>;
|
|
46
|
+
remove: (id: string) => Promise<void>;
|
|
47
|
+
actionTypes: () => Promise<Array<{
|
|
48
|
+
type: string;
|
|
49
|
+
labelKey: string;
|
|
50
|
+
}>>;
|
|
51
|
+
ruleAttributes: () => Promise<PromoRuleAttribute[]>;
|
|
52
|
+
ruleTarget: (kind: RuleTargetKind) => Promise<RuleTargetItem[]>;
|
|
53
|
+
listCoupons: (id: string) => Promise<Coupon[]>;
|
|
54
|
+
bulkSetCouponActive: (id: string, couponIds: string[], isActive: boolean) => Promise<{
|
|
55
|
+
updated: number;
|
|
56
|
+
}>;
|
|
57
|
+
createCoupon: (id: string, code: string) => Promise<Coupon>;
|
|
58
|
+
stats: (id: string, groupBy?: PromotionStatsGroupBy) => Promise<PromotionUsageStats>;
|
|
59
|
+
generateBatch: (id: string, req: GenerateBatchRequest) => Promise<{
|
|
60
|
+
batch: {
|
|
61
|
+
id: string;
|
|
62
|
+
};
|
|
63
|
+
generated: number;
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
export declare const promotionRulesClient: {
|
|
67
|
+
list: () => Promise<PromotionRuleRecord[]>;
|
|
68
|
+
create: (input: {
|
|
69
|
+
name: string;
|
|
70
|
+
description?: string | null;
|
|
71
|
+
definition: PromotionRule;
|
|
72
|
+
}) => Promise<PromotionRuleRecord>;
|
|
73
|
+
update: (id: string, input: {
|
|
74
|
+
name: string;
|
|
75
|
+
description?: string | null;
|
|
76
|
+
definition: PromotionRule;
|
|
77
|
+
}) => Promise<PromotionRuleRecord>;
|
|
78
|
+
remove: (id: string) => Promise<void>;
|
|
79
|
+
};
|
|
80
|
+
export interface GenerateBatchRequest {
|
|
81
|
+
count: number;
|
|
82
|
+
length: number;
|
|
83
|
+
format: 'alnum' | 'digits' | 'letters';
|
|
84
|
+
prefix?: string | null;
|
|
85
|
+
suffix?: string | null;
|
|
86
|
+
dashEvery?: number;
|
|
87
|
+
limitScope: 'per_coupon' | 'shared_batch';
|
|
88
|
+
}
|
|
89
|
+
export interface Coupon {
|
|
90
|
+
id: string;
|
|
91
|
+
promotionId: string;
|
|
92
|
+
batchId: string | null;
|
|
93
|
+
code: string;
|
|
94
|
+
limitScope: 'per_coupon' | 'shared_batch';
|
|
95
|
+
isActive: boolean;
|
|
96
|
+
createdAt: string;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=promotions-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promotions-client.d.ts","sourceRoot":"","sources":["../../../src/admin/api/promotions-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { apiClient } from '@endora-commerce/admin-kit/lib';
|
|
2
|
+
export const promotionsClient = {
|
|
3
|
+
list: () => apiClient.get('/api/v1/admin/promotions').then((r) => r.data),
|
|
4
|
+
get: (id) => apiClient.get(`/api/v1/admin/promotions/${id}`).then((r) => r.data),
|
|
5
|
+
create: (payload) => apiClient.post('/api/v1/admin/promotions', payload).then((r) => r.data),
|
|
6
|
+
update: (id, payload) => apiClient.put(`/api/v1/admin/promotions/${id}`, payload).then((r) => r.data),
|
|
7
|
+
remove: (id) => apiClient.delete(`/api/v1/admin/promotions/${id}`),
|
|
8
|
+
actionTypes: () => apiClient
|
|
9
|
+
.get('/api/v1/admin/promotions/action-types')
|
|
10
|
+
.then((r) => r.data.items),
|
|
11
|
+
ruleAttributes: () => apiClient
|
|
12
|
+
.get('/api/v1/admin/promotions/rule-targets/attributes')
|
|
13
|
+
.then((r) => r.data.items),
|
|
14
|
+
ruleTarget: (kind) => apiClient
|
|
15
|
+
.get(`/api/v1/admin/promotions/rule-targets/${kind}`)
|
|
16
|
+
.then((r) => r.data.items),
|
|
17
|
+
listCoupons: (id) => apiClient.get(`/api/v1/admin/promotions/${id}/coupons`).then((r) => r.data),
|
|
18
|
+
bulkSetCouponActive: (id, couponIds, isActive) => apiClient
|
|
19
|
+
.post(`/api/v1/admin/promotions/${id}/coupons/bulk-active`, {
|
|
20
|
+
couponIds,
|
|
21
|
+
isActive,
|
|
22
|
+
})
|
|
23
|
+
.then((r) => r.data),
|
|
24
|
+
createCoupon: (id, code) => apiClient
|
|
25
|
+
.post(`/api/v1/admin/promotions/${id}/coupons`, { code })
|
|
26
|
+
.then((r) => r.data),
|
|
27
|
+
stats: (id, groupBy) => apiClient
|
|
28
|
+
.get(`/api/v1/admin/promotions/${id}/stats${groupBy ? `?groupBy=${groupBy}` : ''}`)
|
|
29
|
+
.then((r) => r.data),
|
|
30
|
+
generateBatch: (id, req) => apiClient
|
|
31
|
+
.post(`/api/v1/admin/promotions/${id}/coupon-batches`, req)
|
|
32
|
+
.then((r) => r.data),
|
|
33
|
+
};
|
|
34
|
+
export const promotionRulesClient = {
|
|
35
|
+
list: () => apiClient.get('/api/v1/admin/promotion-rules').then((r) => r.data),
|
|
36
|
+
create: (input) => apiClient.post('/api/v1/admin/promotion-rules', input).then((r) => r.data),
|
|
37
|
+
update: (id, input) => apiClient.put(`/api/v1/admin/promotion-rules/${id}`, input).then((r) => r.data),
|
|
38
|
+
remove: (id) => apiClient.delete(`/api/v1/admin/promotion-rules/${id}`),
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=promotions-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promotions-client.js","sourceRoot":"","sources":["../../../src/admin/api/promotions-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Feature 045 (US4) — bulk coupon generator form. Emits a batch and reports
|
|
4
|
+
* how many codes were created, with a CSV export link.
|
|
5
|
+
*/
|
|
6
|
+
export declare function CouponGeneratorForm({ promotionId, onGenerated, }: {
|
|
7
|
+
promotionId: string;
|
|
8
|
+
onGenerated: () => void;
|
|
9
|
+
}): ReactNode;
|
|
10
|
+
//# sourceMappingURL=CouponGeneratorForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CouponGeneratorForm.d.ts","sourceRoot":"","sources":["../../../src/admin/components/CouponGeneratorForm.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMjD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,WAAW,GACZ,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB,GAAG,SAAS,CA+FZ"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { ApiError, apiBaseUrl } from '@endora-commerce/admin-kit/lib';
|
|
4
|
+
import { Button, Input, Label, Select } from '@endora-commerce/admin-kit/ui';
|
|
5
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
6
|
+
import { promotionsClient } from '../api/promotions-client.js';
|
|
7
|
+
/**
|
|
8
|
+
* Feature 045 (US4) — bulk coupon generator form. Emits a batch and reports
|
|
9
|
+
* how many codes were created, with a CSV export link.
|
|
10
|
+
*/
|
|
11
|
+
export function CouponGeneratorForm({ promotionId, onGenerated, }) {
|
|
12
|
+
const t = useTranslation('core');
|
|
13
|
+
const [req, setReq] = useState({
|
|
14
|
+
count: 100,
|
|
15
|
+
length: 8,
|
|
16
|
+
format: 'alnum',
|
|
17
|
+
prefix: '',
|
|
18
|
+
suffix: '',
|
|
19
|
+
dashEvery: 0,
|
|
20
|
+
limitScope: 'per_coupon',
|
|
21
|
+
});
|
|
22
|
+
const [busy, setBusy] = useState(false);
|
|
23
|
+
const [err, setErr] = useState(null);
|
|
24
|
+
const [lastBatchId, setLastBatchId] = useState(null);
|
|
25
|
+
const set = (k, v) => setReq((r) => ({ ...r, [k]: v }));
|
|
26
|
+
const generate = async () => {
|
|
27
|
+
setErr(null);
|
|
28
|
+
setBusy(true);
|
|
29
|
+
try {
|
|
30
|
+
const res = await promotionsClient.generateBatch(promotionId, {
|
|
31
|
+
...req,
|
|
32
|
+
prefix: req.prefix?.trim() || null,
|
|
33
|
+
suffix: req.suffix?.trim() || null,
|
|
34
|
+
});
|
|
35
|
+
setLastBatchId(res.batch.id);
|
|
36
|
+
onGenerated();
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
setErr(e instanceof ApiError ? e.envelope.error.message : t('promotions.error.save'));
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
setBusy(false);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
// `apiBaseUrl` is the kit's, published by feature 091 for exactly this: a
|
|
46
|
+
// module package cannot read `import.meta.env` without acquiring
|
|
47
|
+
// `vite/client` types and a second copy of the fallback. It is the identical
|
|
48
|
+
// expression and the identical `http://localhost:3001` default this line
|
|
49
|
+
// carried, read in one place instead of two.
|
|
50
|
+
const apiBase = apiBaseUrl;
|
|
51
|
+
return (_jsxs("div", { className: "flex flex-col gap-3 rounded-md border border-line p-3", children: [_jsx("p", { className: "text-sm font-medium", children: t('promotions.generator.title') }), err ? _jsx("p", { className: "text-sm text-destructive", children: err }) : null, _jsxs("div", { className: "grid gap-3 md:grid-cols-3", children: [_jsx(Field, { label: t('promotions.generator.count'), children: _jsx(Input, { type: "number", value: req.count, onChange: (e) => set('count', Number(e.target.value) || 0) }) }), _jsx(Field, { label: t('promotions.generator.length'), children: _jsx(Input, { type: "number", value: req.length, onChange: (e) => set('length', Number(e.target.value) || 0) }) }), _jsx(Field, { label: t('promotions.generator.format'), children: _jsxs(Select, { value: req.format, onChange: (e) => set('format', e.target.value), children: [_jsx("option", { value: "alnum", children: "alphanumeric" }), _jsx("option", { value: "digits", children: "digits" }), _jsx("option", { value: "letters", children: "letters" })] }) }), _jsx(Field, { label: t('promotions.generator.prefix'), children: _jsx(Input, { value: req.prefix ?? '', onChange: (e) => set('prefix', e.target.value) }) }), _jsx(Field, { label: t('promotions.generator.suffix'), children: _jsx(Input, { value: req.suffix ?? '', onChange: (e) => set('suffix', e.target.value) }) }), _jsx(Field, { label: t('promotions.generator.dashEvery'), children: _jsx(Input, { type: "number", value: req.dashEvery ?? 0, onChange: (e) => set('dashEvery', Number(e.target.value) || 0) }) }), _jsx(Field, { label: t('promotions.generator.limitScope'), children: _jsxs(Select, { value: req.limitScope, onChange: (e) => set('limitScope', e.target.value), children: [_jsx("option", { value: "per_coupon", children: "per coupon" }), _jsx("option", { value: "shared_batch", children: "shared across batch" })] }) })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { type: "button", disabled: busy, onClick: () => void generate(), children: t('promotions.generator.generate') }), lastBatchId ? (_jsx("a", { className: "text-sm underline", href: `${apiBase}/api/v1/admin/promotions/${promotionId}/coupon-batches/${lastBatchId}/export`, children: t('promotions.generator.export') })) : null] })] }));
|
|
52
|
+
}
|
|
53
|
+
function Field({ label, children }) {
|
|
54
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { children: label }), children] }));
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=CouponGeneratorForm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CouponGeneratorForm.js","sourceRoot":"","sources":["../../../src/admin/components/CouponGeneratorForm.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `promotions`' admin surface — five routes and two sidebar entries, declared
|
|
3
|
+
* by the module that owns them (feature 091, Phase 4, the plan's batch 7;
|
|
4
|
+
* `specs/091-module-owned-admin-surfaces/contracts/admin-contribution.md`).
|
|
5
|
+
*
|
|
6
|
+
* **The heaviest declaration in the batch on the nav axis**: two sidebar rows
|
|
7
|
+
* for one module, because the Rule Builder is a screen in its own right and not
|
|
8
|
+
* a tab of the promotion list. Both take the read code, which is what
|
|
9
|
+
* `GET /api/v1/admin/promotions` and `GET /api/v1/admin/promotion-rules`
|
|
10
|
+
* enforce; the write and delete codes gate controls **inside** the screens and
|
|
11
|
+
* are not what opens them.
|
|
12
|
+
*
|
|
13
|
+
* **Three of the five routes have no sidebar row and never will** — a nav entry
|
|
14
|
+
* is a landing surface. `/promotions/new`, `/promotions/:id` and
|
|
15
|
+
* `/promotions/:id/stats` are reached from the list, and each is a deep link an
|
|
16
|
+
* operator following a stale bookmark can meet, so each is gated by its own
|
|
17
|
+
* declaration rather than by the list's.
|
|
18
|
+
*
|
|
19
|
+
* **Route order is not declaration order and must not be relied on.**
|
|
20
|
+
* `react-router` v7 *ranks* routes — a literal segment outranks a parameter —
|
|
21
|
+
* so `/promotions/new` wins over `/promotions/:id` whichever way round they are
|
|
22
|
+
* written here. `App.tsx`'s hand-written block put the literals first with a
|
|
23
|
+
* comment saying why; the comment described a rule the router already applies,
|
|
24
|
+
* and the registry renders these in the order this array gives.
|
|
25
|
+
*
|
|
26
|
+
* **Its two sidebar positions do not change.** Every other row in *Pricing* is
|
|
27
|
+
* still host-declared, so `composeNav` appends both after all of them — which
|
|
28
|
+
* is not where the hand-written table had them: `/promotions` and
|
|
29
|
+
* `/promotion-rules` were second and third of six, above `/taxes`,
|
|
30
|
+
* `/delivery-methods` and `/payment-methods`. That is the operator-visible
|
|
31
|
+
* consequence batches one and two both recorded, arriving here for the same
|
|
32
|
+
* structural reason, and the declared weights (200, 300 — the hand-written
|
|
33
|
+
* positions times a hundred) are what restore it once that section's other rows
|
|
34
|
+
* convert. The two keep their order relative to each other, which is the half
|
|
35
|
+
* that can be restored today.
|
|
36
|
+
*
|
|
37
|
+
* **This entry exports data and nothing else** (R2), like every `./admin`
|
|
38
|
+
* layer, and each component is a dynamic-import factory (R6) so Vite has a
|
|
39
|
+
* split point whether or not anybody remembers to ask for one.
|
|
40
|
+
*/
|
|
41
|
+
import type { AdminContributions } from '@endora-commerce/admin-kit/contributions';
|
|
42
|
+
export declare const contributions: AdminContributions;
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAiBnF,eAAO,MAAM,aAAa,EAAE,kBAuD3B,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** The module's landing route: the promotion list. */
|
|
2
|
+
const LIST_PATH = '/promotions';
|
|
3
|
+
/** The Rule Builder, a second landing surface with a sidebar row of its own. */
|
|
4
|
+
const RULES_PATH = '/promotion-rules';
|
|
5
|
+
/**
|
|
6
|
+
* The code every one of this module's five screens is opened by —
|
|
7
|
+
* `requireAdmin(PROMOTION_PERMISSIONS.READ)` on `GET /api/v1/admin/promotions`
|
|
8
|
+
* and on `GET /api/v1/admin/promotion-rules` (`routes.ts:102`). The write and
|
|
9
|
+
* delete codes gate the save and delete controls the screens render, which is a
|
|
10
|
+
* question about a control and not about a route.
|
|
11
|
+
*/
|
|
12
|
+
const PERMISSION = 'promotions:read';
|
|
13
|
+
export const contributions = {
|
|
14
|
+
routes: [
|
|
15
|
+
{
|
|
16
|
+
path: LIST_PATH,
|
|
17
|
+
component: () => import('./pages/PromotionsPage.js'),
|
|
18
|
+
requiredPermission: PERMISSION,
|
|
19
|
+
index: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: `${LIST_PATH}/new`,
|
|
23
|
+
component: () => import('./pages/PromotionEditPage.js'),
|
|
24
|
+
requiredPermission: PERMISSION,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
path: `${LIST_PATH}/:id`,
|
|
28
|
+
component: () => import('./pages/PromotionEditPage.js'),
|
|
29
|
+
requiredPermission: PERMISSION,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: `${LIST_PATH}/:id/stats`,
|
|
33
|
+
component: () => import('./pages/PromotionStatsPage.js'),
|
|
34
|
+
requiredPermission: PERMISSION,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: RULES_PATH,
|
|
38
|
+
component: () => import('./pages/PromotionRulesPage.js'),
|
|
39
|
+
requiredPermission: PERMISSION,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
nav: [
|
|
43
|
+
{
|
|
44
|
+
to: LIST_PATH,
|
|
45
|
+
// Module-relative (R8), resolved in this module's own namespace out of
|
|
46
|
+
// `packages/modules/promotions/i18n/`. It was `appShell.nav.promotions`
|
|
47
|
+
// in the shared `_i18n` bundle, one of the four shared files a module
|
|
48
|
+
// author had to edit.
|
|
49
|
+
labelKey: 'nav.promotions.label',
|
|
50
|
+
// The glyph `AppShell.tsx` rendered by hand for both rows. It joins
|
|
51
|
+
// `KnownIconNameSchema` and the kit's `icon-map.ts` in this merge request
|
|
52
|
+
// rather than the sidebar quietly changing to a name that happened to be
|
|
53
|
+
// on the allowlist.
|
|
54
|
+
icon: 'PercentDiamond',
|
|
55
|
+
section: 'pricing',
|
|
56
|
+
weight: 200,
|
|
57
|
+
requiredPermission: PERMISSION,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
to: RULES_PATH,
|
|
61
|
+
labelKey: 'nav.promotionRules.label',
|
|
62
|
+
icon: 'PercentDiamond',
|
|
63
|
+
section: 'pricing',
|
|
64
|
+
weight: 300,
|
|
65
|
+
requiredPermission: PERMISSION,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AA0CA,sDAAsD;AACtD,MAAM,SAAS,GAAG,aAAa,CAAC;AAEhC,gFAAgF;AAChF,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAErC,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C,MAAM,EAAE;QACN;YACE,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,2BAA2B,CAAC;YACpD,kBAAkB,EAAE,UAAU;YAC9B,KAAK,EAAE,IAAI;SACZ;QACD;YACE,IAAI,EAAE,GAAG,SAAS,MAAM;YACxB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,8BAA8B,CAAC;YACvD,kBAAkB,EAAE,UAAU;SAC/B;QACD;YACE,IAAI,EAAE,GAAG,SAAS,MAAM;YACxB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,8BAA8B,CAAC;YACvD,kBAAkB,EAAE,UAAU;SAC/B;QACD;YACE,IAAI,EAAE,GAAG,SAAS,YAAY;YAC9B,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+BAA+B,CAAC;YACxD,kBAAkB,EAAE,UAAU;SAC/B;QACD;YACE,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+BAA+B,CAAC;YACxD,kBAAkB,EAAE,UAAU;SAC/B;KACF;IACD,GAAG,EAAE;QACH;YACE,EAAE,EAAE,SAAS;YACb,uEAAuE;YACvE,wEAAwE;YACxE,sEAAsE;YACtE,sBAAsB;YACtB,QAAQ,EAAE,sBAAsB;YAChC,oEAAoE;YACpE,0EAA0E;YAC1E,yEAAyE;YACzE,oBAAoB;YACpB,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,UAAU;SAC/B;QACD;YACE,EAAE,EAAE,UAAU;YACd,QAAQ,EAAE,0BAA0B;YACpC,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,UAAU;SAC/B;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export declare const PromotionEditPage: () => ReactNode;
|
|
3
|
+
/**
|
|
4
|
+
* The default export the route declaration's dynamic-import factory takes
|
|
5
|
+
* (`contracts/admin-contribution.md` R6). The named export is kept because the
|
|
6
|
+
* screen is also the subject of this module's own admin tests.
|
|
7
|
+
*/
|
|
8
|
+
export default PromotionEditPage;
|
|
9
|
+
//# sourceMappingURL=PromotionEditPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PromotionEditPage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/PromotionEditPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAuDzF,eAAO,MAAM,iBAAiB,QAAO,SA4SpC,CAAC;AAqVF;;;;GAIG;AACH,eAAe,iBAAiB,CAAC"}
|