@voyantjs/promotions-ui 0.35.0 → 0.37.1
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/i18n/en.d.ts +3 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +144 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/messages.d.ts +132 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +1 -0
- package/dist/i18n/provider.d.ts +26 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +44 -0
- package/dist/i18n/ro.d.ts +3 -0
- package/dist/i18n/ro.d.ts.map +1 -0
- package/dist/i18n/ro.js +144 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/promotion-dialog.d.ts +0 -9
- package/dist/promotion-dialog.d.ts.map +1 -1
- package/dist/promotion-dialog.js +35 -30
- package/dist/promotions-page.d.ts.map +1 -1
- package/dist/promotions-page.js +66 -54
- package/package.json +24 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/i18n/en.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAEzD,eAAO,MAAM,cAAc,EAAE,oBAgJ5B,CAAA"}
|
package/dist/i18n/en.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export const promotionsUiEn = {
|
|
2
|
+
common: {
|
|
3
|
+
cancel: "Cancel",
|
|
4
|
+
create: "Create",
|
|
5
|
+
saveChanges: "Save changes",
|
|
6
|
+
saving: "Saving...",
|
|
7
|
+
active: "Active",
|
|
8
|
+
discountTypeLabels: {
|
|
9
|
+
percentage: "Percentage",
|
|
10
|
+
fixed_amount: "Fixed amount",
|
|
11
|
+
},
|
|
12
|
+
applicationModeLabels: {
|
|
13
|
+
auto: "Auto-applied",
|
|
14
|
+
code: "Code-redeemed",
|
|
15
|
+
},
|
|
16
|
+
statusLabels: {
|
|
17
|
+
active: "Active",
|
|
18
|
+
scheduled: "Scheduled",
|
|
19
|
+
expired: "Expired",
|
|
20
|
+
archived: "Archived",
|
|
21
|
+
},
|
|
22
|
+
scopeKindLabels: {
|
|
23
|
+
global: "Global",
|
|
24
|
+
products: "Products",
|
|
25
|
+
categories: "Categories",
|
|
26
|
+
destinations: "Destinations",
|
|
27
|
+
markets: "Markets",
|
|
28
|
+
audiences: "Audiences",
|
|
29
|
+
},
|
|
30
|
+
audienceLabels: {
|
|
31
|
+
staff: "Staff",
|
|
32
|
+
customer: "Customer",
|
|
33
|
+
partner: "Partner",
|
|
34
|
+
supplier: "Supplier",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
promotionsPage: {
|
|
38
|
+
title: "Promotions",
|
|
39
|
+
description: "Auto-applied catalog discounts and code-redeemed offers.",
|
|
40
|
+
newPromotion: "New promotion",
|
|
41
|
+
searchLabel: "Search promotions",
|
|
42
|
+
searchPlaceholder: "Search name, slug, description, or code",
|
|
43
|
+
modePlaceholder: "Mode",
|
|
44
|
+
allModes: "All modes",
|
|
45
|
+
statusPlaceholder: "Status",
|
|
46
|
+
allStatuses: "All statuses",
|
|
47
|
+
scopePlaceholder: "Scope",
|
|
48
|
+
allScopes: "All scopes",
|
|
49
|
+
validityRangePlaceholder: "Validity range",
|
|
50
|
+
clearFilters: "Clear",
|
|
51
|
+
loadFailed: "Failed to load promotions.",
|
|
52
|
+
loadFailedPrefix: "Failed to load: {message}",
|
|
53
|
+
empty: "No promotions match the current filters.",
|
|
54
|
+
columns: {
|
|
55
|
+
name: "Name",
|
|
56
|
+
mode: "Mode",
|
|
57
|
+
scope: "Scope",
|
|
58
|
+
discount: "Discount",
|
|
59
|
+
validity: "Validity",
|
|
60
|
+
code: "Code",
|
|
61
|
+
status: "Status",
|
|
62
|
+
},
|
|
63
|
+
badges: {
|
|
64
|
+
auto: "Auto",
|
|
65
|
+
code: "Code",
|
|
66
|
+
stackable: "Stackable",
|
|
67
|
+
},
|
|
68
|
+
pagination: {
|
|
69
|
+
showing: "Showing {shown} of {total}",
|
|
70
|
+
previous: "Previous",
|
|
71
|
+
next: "Next",
|
|
72
|
+
page: "Page {page} of {pageCount}",
|
|
73
|
+
},
|
|
74
|
+
summaries: {
|
|
75
|
+
globalScope: "Global",
|
|
76
|
+
productsScope: "{count} {noun}",
|
|
77
|
+
categoriesScope: "{count} {noun}",
|
|
78
|
+
destinationsScope: "{count} {noun}",
|
|
79
|
+
marketsScope: "Markets: {markets}",
|
|
80
|
+
audiencesScope: "Audiences: {audiences}",
|
|
81
|
+
productNouns: { singular: "product", plural: "products" },
|
|
82
|
+
categoryNouns: { singular: "category", plural: "categories" },
|
|
83
|
+
destinationNouns: { singular: "destination", plural: "destinations" },
|
|
84
|
+
unknownPercentage: "?",
|
|
85
|
+
anytime: "Anytime",
|
|
86
|
+
until: "Until {date}",
|
|
87
|
+
from: "From {date}",
|
|
88
|
+
range: "{from} - {until}",
|
|
89
|
+
noCode: "-",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
promotionDialog: {
|
|
93
|
+
titles: {
|
|
94
|
+
create: "New promotion",
|
|
95
|
+
edit: "Edit promotion",
|
|
96
|
+
},
|
|
97
|
+
description: "Set discount, scope, and validity. Code-gated offers require a non-empty code; leave it blank for auto-applied offers.",
|
|
98
|
+
fields: {
|
|
99
|
+
name: "Name",
|
|
100
|
+
slug: "Slug",
|
|
101
|
+
description: "Description",
|
|
102
|
+
type: "Type",
|
|
103
|
+
percent: "Percent",
|
|
104
|
+
amount: "Amount",
|
|
105
|
+
currency: "Currency",
|
|
106
|
+
scope: "Scope",
|
|
107
|
+
scopeIds: "{scope}",
|
|
108
|
+
audiences: "Audiences",
|
|
109
|
+
validFrom: "Valid from",
|
|
110
|
+
validUntil: "Valid until",
|
|
111
|
+
code: "Code (optional)",
|
|
112
|
+
minPax: "Min pax (optional)",
|
|
113
|
+
stackable: "Stackable with other offers",
|
|
114
|
+
active: "Active",
|
|
115
|
+
},
|
|
116
|
+
placeholders: {
|
|
117
|
+
name: "Spring Sale 2026",
|
|
118
|
+
slug: "spring-sale-2026",
|
|
119
|
+
description: "Internal note: what this offer is for",
|
|
120
|
+
percent: "20",
|
|
121
|
+
amount: "5.00",
|
|
122
|
+
currency: "USD",
|
|
123
|
+
code: "EARLYBIRD2026",
|
|
124
|
+
minPax: "4",
|
|
125
|
+
productIds: "Add product references",
|
|
126
|
+
categoryIds: "Add category references",
|
|
127
|
+
destinationIds: "Add destination references",
|
|
128
|
+
marketIds: "Add market references",
|
|
129
|
+
},
|
|
130
|
+
hints: {
|
|
131
|
+
globalScope: "Applies to every product.",
|
|
132
|
+
commaSeparatedIds: "comma-separated references",
|
|
133
|
+
},
|
|
134
|
+
validation: {
|
|
135
|
+
nameRequired: "Name is required",
|
|
136
|
+
slugRequired: "Slug is required",
|
|
137
|
+
discountPercentRequired: "Discount percent is required for percentage offers",
|
|
138
|
+
discountAmountRequired: "Discount amount is required for fixed-amount offers",
|
|
139
|
+
currencyRequired: "Currency is required for fixed-amount offers",
|
|
140
|
+
scopeInvalid: "invalid",
|
|
141
|
+
scopeInvalidPrefix: "Scope: {message}",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { promotionsUiEn } from "./en.js";
|
|
2
|
+
export type { PromotionsUiMessages } from "./messages.js";
|
|
3
|
+
export { getPromotionsUiI18n, type PromotionsUiMessageOverrides, PromotionsUiMessagesProvider, promotionsUiMessageDefinitions, resolvePromotionsUiMessages, usePromotionsUiI18n, usePromotionsUiI18nOrDefault, usePromotionsUiMessages, usePromotionsUiMessagesOrDefault, } from "./provider.js";
|
|
4
|
+
export { promotionsUiRo } from "./ro.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACzD,OAAO,EACL,mBAAmB,EACnB,KAAK,4BAA4B,EACjC,4BAA4B,EAC5B,8BAA8B,EAC9B,2BAA2B,EAC3B,mBAAmB,EACnB,4BAA4B,EAC5B,uBAAuB,EACvB,gCAAgC,GACjC,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { promotionsUiEn } from "./en.js";
|
|
2
|
+
export { getPromotionsUiI18n, PromotionsUiMessagesProvider, promotionsUiMessageDefinitions, resolvePromotionsUiMessages, usePromotionsUiI18n, usePromotionsUiI18nOrDefault, usePromotionsUiMessages, usePromotionsUiMessagesOrDefault, } from "./provider.js";
|
|
3
|
+
export { promotionsUiRo } from "./ro.js";
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { PromotionalOfferApplicationMode, PromotionalOfferListStatus, PromotionalOfferScopeKind } from "@voyantjs/promotions-react";
|
|
2
|
+
export type PromotionsUiMessages = {
|
|
3
|
+
common: {
|
|
4
|
+
cancel: string;
|
|
5
|
+
create: string;
|
|
6
|
+
saveChanges: string;
|
|
7
|
+
saving: string;
|
|
8
|
+
active: string;
|
|
9
|
+
discountTypeLabels: Record<"percentage" | "fixed_amount", string>;
|
|
10
|
+
applicationModeLabels: Record<PromotionalOfferApplicationMode, string>;
|
|
11
|
+
statusLabels: Record<PromotionalOfferListStatus, string>;
|
|
12
|
+
scopeKindLabels: Record<PromotionalOfferScopeKind, string>;
|
|
13
|
+
audienceLabels: Record<"staff" | "customer" | "partner" | "supplier", string>;
|
|
14
|
+
};
|
|
15
|
+
promotionsPage: {
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
newPromotion: string;
|
|
19
|
+
searchLabel: string;
|
|
20
|
+
searchPlaceholder: string;
|
|
21
|
+
modePlaceholder: string;
|
|
22
|
+
allModes: string;
|
|
23
|
+
statusPlaceholder: string;
|
|
24
|
+
allStatuses: string;
|
|
25
|
+
scopePlaceholder: string;
|
|
26
|
+
allScopes: string;
|
|
27
|
+
validityRangePlaceholder: string;
|
|
28
|
+
clearFilters: string;
|
|
29
|
+
loadFailed: string;
|
|
30
|
+
loadFailedPrefix: string;
|
|
31
|
+
empty: string;
|
|
32
|
+
columns: {
|
|
33
|
+
name: string;
|
|
34
|
+
mode: string;
|
|
35
|
+
scope: string;
|
|
36
|
+
discount: string;
|
|
37
|
+
validity: string;
|
|
38
|
+
code: string;
|
|
39
|
+
status: string;
|
|
40
|
+
};
|
|
41
|
+
badges: {
|
|
42
|
+
auto: string;
|
|
43
|
+
code: string;
|
|
44
|
+
stackable: string;
|
|
45
|
+
};
|
|
46
|
+
pagination: {
|
|
47
|
+
showing: string;
|
|
48
|
+
previous: string;
|
|
49
|
+
next: string;
|
|
50
|
+
page: string;
|
|
51
|
+
};
|
|
52
|
+
summaries: {
|
|
53
|
+
globalScope: string;
|
|
54
|
+
productsScope: string;
|
|
55
|
+
categoriesScope: string;
|
|
56
|
+
destinationsScope: string;
|
|
57
|
+
marketsScope: string;
|
|
58
|
+
audiencesScope: string;
|
|
59
|
+
productNouns: {
|
|
60
|
+
singular: string;
|
|
61
|
+
plural: string;
|
|
62
|
+
};
|
|
63
|
+
categoryNouns: {
|
|
64
|
+
singular: string;
|
|
65
|
+
plural: string;
|
|
66
|
+
};
|
|
67
|
+
destinationNouns: {
|
|
68
|
+
singular: string;
|
|
69
|
+
plural: string;
|
|
70
|
+
};
|
|
71
|
+
unknownPercentage: string;
|
|
72
|
+
anytime: string;
|
|
73
|
+
until: string;
|
|
74
|
+
from: string;
|
|
75
|
+
range: string;
|
|
76
|
+
noCode: string;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
promotionDialog: {
|
|
80
|
+
titles: {
|
|
81
|
+
create: string;
|
|
82
|
+
edit: string;
|
|
83
|
+
};
|
|
84
|
+
description: string;
|
|
85
|
+
fields: {
|
|
86
|
+
name: string;
|
|
87
|
+
slug: string;
|
|
88
|
+
description: string;
|
|
89
|
+
type: string;
|
|
90
|
+
percent: string;
|
|
91
|
+
amount: string;
|
|
92
|
+
currency: string;
|
|
93
|
+
scope: string;
|
|
94
|
+
scopeIds: string;
|
|
95
|
+
audiences: string;
|
|
96
|
+
validFrom: string;
|
|
97
|
+
validUntil: string;
|
|
98
|
+
code: string;
|
|
99
|
+
minPax: string;
|
|
100
|
+
stackable: string;
|
|
101
|
+
active: string;
|
|
102
|
+
};
|
|
103
|
+
placeholders: {
|
|
104
|
+
name: string;
|
|
105
|
+
slug: string;
|
|
106
|
+
description: string;
|
|
107
|
+
percent: string;
|
|
108
|
+
amount: string;
|
|
109
|
+
currency: string;
|
|
110
|
+
code: string;
|
|
111
|
+
minPax: string;
|
|
112
|
+
productIds: string;
|
|
113
|
+
categoryIds: string;
|
|
114
|
+
destinationIds: string;
|
|
115
|
+
marketIds: string;
|
|
116
|
+
};
|
|
117
|
+
hints: {
|
|
118
|
+
globalScope: string;
|
|
119
|
+
commaSeparatedIds: string;
|
|
120
|
+
};
|
|
121
|
+
validation: {
|
|
122
|
+
nameRequired: string;
|
|
123
|
+
slugRequired: string;
|
|
124
|
+
discountPercentRequired: string;
|
|
125
|
+
discountAmountRequired: string;
|
|
126
|
+
currencyRequired: string;
|
|
127
|
+
scopeInvalid: string;
|
|
128
|
+
scopeInvalidPrefix: string;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/i18n/messages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type LocaleMessageOverrides, type PackageI18nValue } from "@voyantjs/i18n";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { PromotionsUiMessages } from "./messages.js";
|
|
4
|
+
export declare const promotionsUiMessageDefinitions: {
|
|
5
|
+
en: PromotionsUiMessages;
|
|
6
|
+
ro: PromotionsUiMessages;
|
|
7
|
+
};
|
|
8
|
+
export type PromotionsUiMessageOverrides = LocaleMessageOverrides<PromotionsUiMessages>;
|
|
9
|
+
export declare function resolvePromotionsUiMessages({ locale, overrides, }: {
|
|
10
|
+
locale: string | null | undefined;
|
|
11
|
+
overrides?: PromotionsUiMessageOverrides | null;
|
|
12
|
+
}): PromotionsUiMessages;
|
|
13
|
+
export declare function getPromotionsUiI18n({ locale, overrides, }: {
|
|
14
|
+
locale?: string | null | undefined;
|
|
15
|
+
overrides?: PromotionsUiMessageOverrides | null;
|
|
16
|
+
}): PackageI18nValue<PromotionsUiMessages>;
|
|
17
|
+
export declare function PromotionsUiMessagesProvider({ children, locale, overrides, }: {
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
locale: string | null | undefined;
|
|
20
|
+
overrides?: PromotionsUiMessageOverrides | null;
|
|
21
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare const usePromotionsUiI18n: () => PackageI18nValue<PromotionsUiMessages>;
|
|
23
|
+
export declare const usePromotionsUiMessages: () => PromotionsUiMessages;
|
|
24
|
+
export declare function usePromotionsUiI18nOrDefault(): PackageI18nValue<PromotionsUiMessages>;
|
|
25
|
+
export declare function usePromotionsUiMessagesOrDefault(): PromotionsUiMessages;
|
|
26
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/i18n/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EAEtB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAKzD,eAAO,MAAM,8BAA8B;;;CAGe,CAAA;AAE1D,MAAM,MAAM,4BAA4B,GAAG,sBAAsB,CAAC,oBAAoB,CAAC,CAAA;AAUvF,wBAAgB,2BAA2B,CAAC,EAC1C,MAAM,EACN,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACjC,SAAS,CAAC,EAAE,4BAA4B,GAAG,IAAI,CAAA;CAChD,wBAOA;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,SAAS,GACV,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAClC,SAAS,CAAC,EAAE,4BAA4B,GAAG,IAAI,CAAA;CAChD,GAAG,gBAAgB,CAAC,oBAAoB,CAAC,CASzC;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,QAAQ,EACR,MAAM,EACN,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAA;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACjC,SAAS,CAAC,EAAE,4BAA4B,GAAG,IAAI,CAAA;CAChD,2CAWA;AAED,eAAO,MAAM,mBAAmB,8CAA8B,CAAA;AAC9D,eAAO,MAAM,uBAAuB,4BAAkC,CAAA;AAEtE,wBAAgB,4BAA4B,2CAE3C;AAED,wBAAgB,gCAAgC,yBAE/C"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createLocaleFormatters, createPackageMessagesContext, resolvePackageMessages, } from "@voyantjs/i18n";
|
|
4
|
+
import { promotionsUiEn } from "./en.js";
|
|
5
|
+
import { promotionsUiRo } from "./ro.js";
|
|
6
|
+
const fallbackLocale = "en";
|
|
7
|
+
export const promotionsUiMessageDefinitions = {
|
|
8
|
+
en: promotionsUiEn,
|
|
9
|
+
ro: promotionsUiRo,
|
|
10
|
+
};
|
|
11
|
+
const promotionsUiContext = createPackageMessagesContext("PromotionsUiMessages");
|
|
12
|
+
const defaultPromotionsUiI18n = {
|
|
13
|
+
messages: promotionsUiEn,
|
|
14
|
+
...createLocaleFormatters(fallbackLocale),
|
|
15
|
+
};
|
|
16
|
+
export function resolvePromotionsUiMessages({ locale, overrides, }) {
|
|
17
|
+
return resolvePackageMessages({
|
|
18
|
+
definitions: promotionsUiMessageDefinitions,
|
|
19
|
+
fallbackLocale,
|
|
20
|
+
locale,
|
|
21
|
+
overrides,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function getPromotionsUiI18n({ locale, overrides, }) {
|
|
25
|
+
const resolvedLocale = locale ?? fallbackLocale;
|
|
26
|
+
return {
|
|
27
|
+
messages: resolvePromotionsUiMessages({
|
|
28
|
+
locale: resolvedLocale,
|
|
29
|
+
overrides,
|
|
30
|
+
}),
|
|
31
|
+
...createLocaleFormatters(resolvedLocale),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function PromotionsUiMessagesProvider({ children, locale, overrides, }) {
|
|
35
|
+
return (_jsx(promotionsUiContext.ResolvedMessagesProvider, { definitions: promotionsUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, overrides: overrides, children: children }));
|
|
36
|
+
}
|
|
37
|
+
export const usePromotionsUiI18n = promotionsUiContext.useI18n;
|
|
38
|
+
export const usePromotionsUiMessages = promotionsUiContext.useMessages;
|
|
39
|
+
export function usePromotionsUiI18nOrDefault() {
|
|
40
|
+
return promotionsUiContext.useOptionalI18n() ?? defaultPromotionsUiI18n;
|
|
41
|
+
}
|
|
42
|
+
export function usePromotionsUiMessagesOrDefault() {
|
|
43
|
+
return usePromotionsUiI18nOrDefault().messages;
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ro.d.ts","sourceRoot":"","sources":["../../src/i18n/ro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAEzD,eAAO,MAAM,cAAc,EAAE,oBAgJ5B,CAAA"}
|
package/dist/i18n/ro.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export const promotionsUiRo = {
|
|
2
|
+
common: {
|
|
3
|
+
cancel: "Anuleaza",
|
|
4
|
+
create: "Creeaza",
|
|
5
|
+
saveChanges: "Salveaza modificarile",
|
|
6
|
+
saving: "Se salveaza...",
|
|
7
|
+
active: "Activa",
|
|
8
|
+
discountTypeLabels: {
|
|
9
|
+
percentage: "Procent",
|
|
10
|
+
fixed_amount: "Suma fixa",
|
|
11
|
+
},
|
|
12
|
+
applicationModeLabels: {
|
|
13
|
+
auto: "Aplicata automat",
|
|
14
|
+
code: "Cu cod",
|
|
15
|
+
},
|
|
16
|
+
statusLabels: {
|
|
17
|
+
active: "Activa",
|
|
18
|
+
scheduled: "Programata",
|
|
19
|
+
expired: "Expirata",
|
|
20
|
+
archived: "Arhivata",
|
|
21
|
+
},
|
|
22
|
+
scopeKindLabels: {
|
|
23
|
+
global: "Global",
|
|
24
|
+
products: "Produse",
|
|
25
|
+
categories: "Categorii",
|
|
26
|
+
destinations: "Destinatii",
|
|
27
|
+
markets: "Piete",
|
|
28
|
+
audiences: "Audiente",
|
|
29
|
+
},
|
|
30
|
+
audienceLabels: {
|
|
31
|
+
staff: "Echipa",
|
|
32
|
+
customer: "Client",
|
|
33
|
+
partner: "Partener",
|
|
34
|
+
supplier: "Furnizor",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
promotionsPage: {
|
|
38
|
+
title: "Promotii",
|
|
39
|
+
description: "Discounturi aplicate automat in catalog si oferte valorificate cu cod.",
|
|
40
|
+
newPromotion: "Promotie noua",
|
|
41
|
+
searchLabel: "Cauta promotii",
|
|
42
|
+
searchPlaceholder: "Cauta dupa nume, slug, descriere sau cod",
|
|
43
|
+
modePlaceholder: "Mod",
|
|
44
|
+
allModes: "Toate modurile",
|
|
45
|
+
statusPlaceholder: "Status",
|
|
46
|
+
allStatuses: "Toate statusurile",
|
|
47
|
+
scopePlaceholder: "Domeniu",
|
|
48
|
+
allScopes: "Toate domeniile",
|
|
49
|
+
validityRangePlaceholder: "Interval valabilitate",
|
|
50
|
+
clearFilters: "Sterge",
|
|
51
|
+
loadFailed: "Promotiile nu au putut fi incarcate.",
|
|
52
|
+
loadFailedPrefix: "Incarcarea a esuat: {message}",
|
|
53
|
+
empty: "Nicio promotie nu se potriveste filtrelor curente.",
|
|
54
|
+
columns: {
|
|
55
|
+
name: "Nume",
|
|
56
|
+
mode: "Mod",
|
|
57
|
+
scope: "Domeniu",
|
|
58
|
+
discount: "Discount",
|
|
59
|
+
validity: "Valabilitate",
|
|
60
|
+
code: "Cod",
|
|
61
|
+
status: "Status",
|
|
62
|
+
},
|
|
63
|
+
badges: {
|
|
64
|
+
auto: "Automat",
|
|
65
|
+
code: "Cod",
|
|
66
|
+
stackable: "Cumulabila",
|
|
67
|
+
},
|
|
68
|
+
pagination: {
|
|
69
|
+
showing: "Afisate {shown} din {total}",
|
|
70
|
+
previous: "Anterior",
|
|
71
|
+
next: "Urmator",
|
|
72
|
+
page: "Pagina {page} din {pageCount}",
|
|
73
|
+
},
|
|
74
|
+
summaries: {
|
|
75
|
+
globalScope: "Global",
|
|
76
|
+
productsScope: "{count} {noun}",
|
|
77
|
+
categoriesScope: "{count} {noun}",
|
|
78
|
+
destinationsScope: "{count} {noun}",
|
|
79
|
+
marketsScope: "Piete: {markets}",
|
|
80
|
+
audiencesScope: "Audiente: {audiences}",
|
|
81
|
+
productNouns: { singular: "produs", plural: "produse" },
|
|
82
|
+
categoryNouns: { singular: "categorie", plural: "categorii" },
|
|
83
|
+
destinationNouns: { singular: "destinatie", plural: "destinatii" },
|
|
84
|
+
unknownPercentage: "?",
|
|
85
|
+
anytime: "Oricand",
|
|
86
|
+
until: "Pana la {date}",
|
|
87
|
+
from: "Din {date}",
|
|
88
|
+
range: "{from} - {until}",
|
|
89
|
+
noCode: "-",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
promotionDialog: {
|
|
93
|
+
titles: {
|
|
94
|
+
create: "Promotie noua",
|
|
95
|
+
edit: "Editeaza promotia",
|
|
96
|
+
},
|
|
97
|
+
description: "Seteaza discountul, domeniul si valabilitatea. Ofertele pe baza de cod necesita un cod completat; lasa campul gol pentru oferte aplicate automat.",
|
|
98
|
+
fields: {
|
|
99
|
+
name: "Nume",
|
|
100
|
+
slug: "Slug",
|
|
101
|
+
description: "Descriere",
|
|
102
|
+
type: "Tip",
|
|
103
|
+
percent: "Procent",
|
|
104
|
+
amount: "Suma",
|
|
105
|
+
currency: "Moneda",
|
|
106
|
+
scope: "Domeniu",
|
|
107
|
+
scopeIds: "{scope}",
|
|
108
|
+
audiences: "Audiente",
|
|
109
|
+
validFrom: "Valabila din",
|
|
110
|
+
validUntil: "Valabila pana la",
|
|
111
|
+
code: "Cod (optional)",
|
|
112
|
+
minPax: "Pax minim (optional)",
|
|
113
|
+
stackable: "Cumulabila cu alte oferte",
|
|
114
|
+
active: "Activa",
|
|
115
|
+
},
|
|
116
|
+
placeholders: {
|
|
117
|
+
name: "Reducere primavara 2026",
|
|
118
|
+
slug: "reducere-primavara-2026",
|
|
119
|
+
description: "Nota interna: scopul acestei oferte",
|
|
120
|
+
percent: "20",
|
|
121
|
+
amount: "5.00",
|
|
122
|
+
currency: "USD",
|
|
123
|
+
code: "EARLYBIRD2026",
|
|
124
|
+
minPax: "4",
|
|
125
|
+
productIds: "Adauga referinte de produse",
|
|
126
|
+
categoryIds: "Adauga referinte de categorii",
|
|
127
|
+
destinationIds: "Adauga referinte de destinatii",
|
|
128
|
+
marketIds: "Adauga referinte de piete",
|
|
129
|
+
},
|
|
130
|
+
hints: {
|
|
131
|
+
globalScope: "Se aplica tuturor produselor.",
|
|
132
|
+
commaSeparatedIds: "Referinte separate prin virgula",
|
|
133
|
+
},
|
|
134
|
+
validation: {
|
|
135
|
+
nameRequired: "Numele este obligatoriu",
|
|
136
|
+
slugRequired: "Slug-ul este obligatoriu",
|
|
137
|
+
discountPercentRequired: "Procentul de discount este obligatoriu pentru ofertele procentuale",
|
|
138
|
+
discountAmountRequired: "Suma discountului este obligatorie pentru ofertele cu suma fixa",
|
|
139
|
+
currencyRequired: "Moneda este obligatorie pentru ofertele cu suma fixa",
|
|
140
|
+
scopeInvalid: "invalid",
|
|
141
|
+
scopeInvalidPrefix: "Domeniu: {message}",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { PromotionsUiMessages } from "./i18n/index.js";
|
|
2
|
+
export { getPromotionsUiI18n, type PromotionsUiMessageOverrides, PromotionsUiMessagesProvider, promotionsUiMessageDefinitions, resolvePromotionsUiMessages, usePromotionsUiI18n, usePromotionsUiI18nOrDefault, usePromotionsUiMessages, usePromotionsUiMessagesOrDefault, } from "./i18n/index.js";
|
|
1
3
|
export { PromotionDialog, type PromotionDialogProps } from "./promotion-dialog.js";
|
|
2
4
|
export { loadPromotionsPage, type PromotionDialogRenderProps, PromotionsPage, type PromotionsPageProps, } from "./promotions-page.js";
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,EACL,kBAAkB,EAClB,KAAK,0BAA0B,EAC/B,cAAc,EACd,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,EACL,mBAAmB,EACnB,KAAK,4BAA4B,EACjC,4BAA4B,EAC5B,8BAA8B,EAC9B,2BAA2B,EAC3B,mBAAmB,EACnB,4BAA4B,EAC5B,uBAAuB,EACvB,gCAAgC,GACjC,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,EACL,kBAAkB,EAClB,KAAK,0BAA0B,EAC/B,cAAc,EACd,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
export { getPromotionsUiI18n, PromotionsUiMessagesProvider, promotionsUiMessageDefinitions, resolvePromotionsUiMessages, usePromotionsUiI18n, usePromotionsUiI18nOrDefault, usePromotionsUiMessages, usePromotionsUiMessagesOrDefault, } from "./i18n/index.js";
|
|
1
2
|
export { PromotionDialog } from "./promotion-dialog.js";
|
|
2
3
|
export { loadPromotionsPage, PromotionsPage, } from "./promotions-page.js";
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Create / edit dialog for a promotional offer.
|
|
3
|
-
*
|
|
4
|
-
* The discriminated-union scope picker is the trickiest piece here:
|
|
5
|
-
* a `kind` dropdown plus per-kind sub-fields rendered conditionally.
|
|
6
|
-
* Comma-separated text input for ID lists (productIds, marketIds, etc.)
|
|
7
|
-
* keeps the v1 UX simple — a future PR can swap in proper combobox-with-
|
|
8
|
-
* search components per scope kind.
|
|
9
|
-
*/
|
|
10
1
|
import { type PromotionalOfferRecord } from "@voyantjs/promotions-react";
|
|
11
2
|
export interface PromotionDialogProps {
|
|
12
3
|
open: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promotion-dialog.d.ts","sourceRoot":"","sources":["../src/promotion-dialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"promotion-dialog.d.ts","sourceRoot":"","sources":["../src/promotion-dialog.tsx"],"names":[],"mappings":"AAaA,OAAO,EACL,KAAK,sBAAsB,EAO5B,MAAM,4BAA4B,CAAA;AA0BnC,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,oDAAoD;IACpD,KAAK,CAAC,EAAE,sBAAsB,CAAA;CAC/B;AA4KD,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,oBAAoB,2CA2SlF"}
|
package/dist/promotion-dialog.js
CHANGED
|
@@ -9,11 +9,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
9
9
|
* keeps the v1 UX simple — a future PR can swap in proper combobox-with-
|
|
10
10
|
* search components per scope kind.
|
|
11
11
|
*/
|
|
12
|
+
import { formatMessage } from "@voyantjs/i18n";
|
|
12
13
|
import { promotionalOfferScopeSchema, useCreatePromotion, useUpdatePromotion, } from "@voyantjs/promotions-react";
|
|
13
14
|
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/ui/components";
|
|
14
15
|
import { CurrencyInput } from "@voyantjs/ui/components/currency-input";
|
|
15
16
|
import { DateTimePicker } from "@voyantjs/ui/components/date-time-picker";
|
|
16
17
|
import { useEffect, useState } from "react";
|
|
18
|
+
import { usePromotionsUiMessagesOrDefault } from "./i18n/provider.js";
|
|
17
19
|
const SCOPE_KINDS = [
|
|
18
20
|
"global",
|
|
19
21
|
"products",
|
|
@@ -108,27 +110,31 @@ function parseIds(raw) {
|
|
|
108
110
|
.map((s) => s.trim())
|
|
109
111
|
.filter((s) => s.length > 0);
|
|
110
112
|
}
|
|
111
|
-
function buildPayload(state) {
|
|
113
|
+
function buildPayload(state, messages) {
|
|
112
114
|
if (!state.name.trim())
|
|
113
|
-
return { error:
|
|
115
|
+
return { error: messages.validation.nameRequired };
|
|
114
116
|
if (!state.slug.trim())
|
|
115
|
-
return { error:
|
|
117
|
+
return { error: messages.validation.slugRequired };
|
|
116
118
|
if (state.discountType === "percentage" && !state.discountPercent) {
|
|
117
|
-
return { error:
|
|
119
|
+
return { error: messages.validation.discountPercentRequired };
|
|
118
120
|
}
|
|
119
121
|
if (state.discountType === "fixed_amount") {
|
|
120
122
|
if (state.discountAmountCents == null || state.discountAmountCents <= 0) {
|
|
121
|
-
return { error:
|
|
123
|
+
return { error: messages.validation.discountAmountRequired };
|
|
122
124
|
}
|
|
123
125
|
if (!state.currency.trim())
|
|
124
|
-
return { error:
|
|
126
|
+
return { error: messages.validation.currencyRequired };
|
|
125
127
|
}
|
|
126
128
|
// Validate scope shape via Zod so the user gets clear errors when (e.g.)
|
|
127
129
|
// a products scope has an empty ID list.
|
|
128
130
|
const scope = buildScope(state);
|
|
129
131
|
const scopeResult = promotionalOfferScopeSchema.safeParse(scope);
|
|
130
132
|
if (!scopeResult.success) {
|
|
131
|
-
return {
|
|
133
|
+
return {
|
|
134
|
+
error: formatMessage(messages.validation.scopeInvalidPrefix, {
|
|
135
|
+
message: scopeResult.error.issues[0]?.message ?? messages.validation.scopeInvalid,
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
132
138
|
}
|
|
133
139
|
const payload = {
|
|
134
140
|
name: state.name.trim(),
|
|
@@ -149,6 +155,8 @@ function buildPayload(state) {
|
|
|
149
155
|
return payload;
|
|
150
156
|
}
|
|
151
157
|
export function PromotionDialog({ open, onOpenChange, offer }) {
|
|
158
|
+
const messages = usePromotionsUiMessagesOrDefault();
|
|
159
|
+
const dialogMessages = messages.promotionDialog;
|
|
152
160
|
const [state, setState] = useState(emptyForm());
|
|
153
161
|
const [error, setError] = useState(null);
|
|
154
162
|
const createMutation = useCreatePromotion();
|
|
@@ -167,7 +175,7 @@ export function PromotionDialog({ open, onOpenChange, offer }) {
|
|
|
167
175
|
}
|
|
168
176
|
async function handleSave() {
|
|
169
177
|
setError(null);
|
|
170
|
-
const result = buildPayload(state);
|
|
178
|
+
const result = buildPayload(state, dialogMessages);
|
|
171
179
|
if ("error" in result) {
|
|
172
180
|
setError(result.error);
|
|
173
181
|
return;
|
|
@@ -185,47 +193,44 @@ export function PromotionDialog({ open, onOpenChange, offer }) {
|
|
|
185
193
|
setError(err instanceof Error ? err.message : String(err));
|
|
186
194
|
}
|
|
187
195
|
}
|
|
188
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-h-[90vh] max-w-2xl overflow-y-auto", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: isEdit ?
|
|
196
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-h-[90vh] max-w-2xl overflow-y-auto", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: isEdit ? dialogMessages.titles.edit : dialogMessages.titles.create }), _jsx(DialogDescription, { children: dialogMessages.description })] }), _jsxs("div", { className: "grid gap-4 py-2", children: [_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-name", children: dialogMessages.fields.name }), _jsx(Input, { id: "promotion-name", value: state.name, onChange: (e) => setField("name", e.target.value), placeholder: dialogMessages.placeholders.name })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-slug", children: dialogMessages.fields.slug }), _jsx(Input, { id: "promotion-slug", value: state.slug, onChange: (e) => setField("slug", e.target.value), placeholder: dialogMessages.placeholders.slug })] })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-description", children: dialogMessages.fields.description }), _jsx(Textarea, { id: "promotion-description", value: state.description, onChange: (e) => setField("description", e.target.value), rows: 2, placeholder: dialogMessages.placeholders.description })] }), _jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { children: dialogMessages.fields.type }), _jsxs(Select, { value: state.discountType, onValueChange: (v) => {
|
|
189
197
|
if (v === "percentage" || v === "fixed_amount")
|
|
190
198
|
setField("discountType", v);
|
|
191
|
-
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "percentage", children:
|
|
199
|
+
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "percentage", children: messages.common.discountTypeLabels.percentage }), _jsx(SelectItem, { value: "fixed_amount", children: messages.common.discountTypeLabels.fixed_amount })] })] })] }), state.discountType === "percentage" ? (_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-percent", children: dialogMessages.fields.percent }), _jsx(Input, { id: "promotion-percent", type: "number", step: "0.01", min: "0", max: "100", value: state.discountPercent, onChange: (e) => setField("discountPercent", e.target.value), placeholder: dialogMessages.placeholders.percent })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-amount", children: dialogMessages.fields.amount }), _jsx(CurrencyInput, { id: "promotion-amount", value: state.discountAmountCents, onChange: (value) => setField("discountAmountCents", value), currency: state.currency, placeholder: dialogMessages.placeholders.amount })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-currency", children: dialogMessages.fields.currency }), _jsx(Input, { id: "promotion-currency", value: state.currency, onChange: (e) => setField("currency", e.target.value), placeholder: dialogMessages.placeholders.currency, maxLength: 3 })] })] }))] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { children: dialogMessages.fields.scope }), _jsxs(Select, { value: state.scopeKind, onValueChange: (v) => {
|
|
192
200
|
if (v != null && SCOPE_KINDS.includes(v)) {
|
|
193
201
|
setField("scopeKind", v);
|
|
194
202
|
}
|
|
195
|
-
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: SCOPE_KINDS.map((kind) => (_jsx(SelectItem, { value: kind, children: kind }, kind))) })] }), state.scopeKind === "global" ? (_jsx("p", { className: "text-sm text-muted-foreground", children:
|
|
203
|
+
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: SCOPE_KINDS.map((kind) => (_jsx(SelectItem, { value: kind, children: messages.common.scopeKindLabels[kind] }, kind))) })] }), state.scopeKind === "global" ? (_jsx("p", { className: "text-sm text-muted-foreground", children: dialogMessages.hints.globalScope })) : null, (state.scopeKind === "products" ||
|
|
196
204
|
state.scopeKind === "categories" ||
|
|
197
205
|
state.scopeKind === "destinations" ||
|
|
198
|
-
state.scopeKind === "markets") && (_jsxs("div", { className: "grid gap-1.5", children: [
|
|
206
|
+
state.scopeKind === "markets") && (_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-scope-ids", children: formatMessage(dialogMessages.fields.scopeIds, {
|
|
207
|
+
scope: scopeIdsLabel(state.scopeKind, messages.common.scopeKindLabels),
|
|
208
|
+
}) }), _jsx(Input, { id: "promotion-scope-ids", value: state.scopeIds, onChange: (e) => setField("scopeIds", e.target.value), placeholder: scopeIdsPlaceholder(state.scopeKind, dialogMessages.placeholders) })] })), state.scopeKind === "audiences" && (_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: dialogMessages.fields.audiences }), _jsx("div", { className: "flex flex-wrap gap-3", children: AUDIENCE_OPTIONS.map((audience) => {
|
|
199
209
|
const selected = state.scopeAudiences.includes(audience);
|
|
200
210
|
return (_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: selected, onChange: (e) => {
|
|
201
211
|
const next = e.target.checked
|
|
202
212
|
? [...state.scopeAudiences, audience]
|
|
203
213
|
: state.scopeAudiences.filter((a) => a !== audience);
|
|
204
214
|
setField("scopeAudiences", next);
|
|
205
|
-
} }), audience] }, audience));
|
|
206
|
-
}) })] }))] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-valid-from", children:
|
|
215
|
+
} }), messages.common.audienceLabels[audience]] }, audience));
|
|
216
|
+
}) })] }))] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-valid-from", children: dialogMessages.fields.validFrom }), _jsx(DateTimePicker, { value: state.validFrom, onChange: (nextValue) => setField("validFrom", nextValue ?? "") })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-valid-until", children: dialogMessages.fields.validUntil }), _jsx(DateTimePicker, { value: state.validUntil, onChange: (nextValue) => setField("validUntil", nextValue ?? "") })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-code", children: dialogMessages.fields.code }), _jsx(Input, { id: "promotion-code", value: state.code, onChange: (e) => setField("code", e.target.value), placeholder: dialogMessages.placeholders.code })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "promotion-min-pax", children: dialogMessages.fields.minPax }), _jsx(Input, { id: "promotion-min-pax", type: "number", min: "1", step: "1", value: state.minPax, onChange: (e) => setField("minPax", e.target.value), placeholder: dialogMessages.placeholders.minPax })] })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "promotion-stackable", checked: state.stackable, onCheckedChange: (v) => setField("stackable", Boolean(v)) }), _jsx(Label, { htmlFor: "promotion-stackable", children: dialogMessages.fields.stackable })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "promotion-active", checked: state.active, onCheckedChange: (v) => setField("active", Boolean(v)) }), _jsx(Label, { htmlFor: "promotion-active", children: dialogMessages.fields.active })] })] }), error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: isPending, children: messages.common.cancel }), _jsx(Button, { onClick: handleSave, disabled: isPending, children: isPending
|
|
217
|
+
? messages.common.saving
|
|
218
|
+
: isEdit
|
|
219
|
+
? messages.common.saveChanges
|
|
220
|
+
: messages.common.create })] })] }) }));
|
|
207
221
|
}
|
|
208
|
-
function scopeIdsLabel(kind) {
|
|
209
|
-
|
|
210
|
-
case "products":
|
|
211
|
-
return "Product IDs";
|
|
212
|
-
case "categories":
|
|
213
|
-
return "Category IDs";
|
|
214
|
-
case "destinations":
|
|
215
|
-
return "Destination IDs";
|
|
216
|
-
case "markets":
|
|
217
|
-
return "Market IDs";
|
|
218
|
-
}
|
|
222
|
+
function scopeIdsLabel(kind, labels) {
|
|
223
|
+
return labels[kind];
|
|
219
224
|
}
|
|
220
|
-
function scopeIdsPlaceholder(kind) {
|
|
225
|
+
function scopeIdsPlaceholder(kind, placeholders) {
|
|
221
226
|
switch (kind) {
|
|
222
227
|
case "products":
|
|
223
|
-
return
|
|
228
|
+
return placeholders.productIds;
|
|
224
229
|
case "categories":
|
|
225
|
-
return
|
|
230
|
+
return placeholders.categoryIds;
|
|
226
231
|
case "destinations":
|
|
227
|
-
return
|
|
232
|
+
return placeholders.destinationIds;
|
|
228
233
|
case "markets":
|
|
229
|
-
return
|
|
234
|
+
return placeholders.marketIds;
|
|
230
235
|
}
|
|
231
236
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promotions-page.d.ts","sourceRoot":"","sources":["../src/promotions-page.tsx"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"promotions-page.d.ts","sourceRoot":"","sources":["../src/promotions-page.tsx"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAKL,KAAK,sBAAsB,EAG3B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EAEzB,MAAM,4BAA4B,CAAA;AAsBnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AA4BtC,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,KAAK,CAAC,EAAE,sBAAsB,CAAA;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,sBAAsB,KAAK,IAAI,CAAA;IAClF,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAA;CACzE;AAED,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,WAAW,EACxB,MAAM,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,EACzC,KAAK,GAAE,mBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAQhC;AAED,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,QAA4B,EAC5B,eAAe,EACf,qBAAqB,GACtB,GAAE,mBAAwB,2CAgS1B"}
|
package/dist/promotions-page.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { formatMessage } from "@voyantjs/i18n";
|
|
3
4
|
import { createPromotionsClientOptions, getPromotionsListQueryOptions, usePromotionsList, } from "@voyantjs/promotions-react";
|
|
4
5
|
import { Badge, Button, DateRangePicker, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components";
|
|
5
6
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
6
7
|
import { Plus, Search, X } from "lucide-react";
|
|
7
8
|
import { useState } from "react";
|
|
9
|
+
import { usePromotionsUiMessagesOrDefault } from "./i18n/provider.js";
|
|
8
10
|
import { PromotionDialog } from "./promotion-dialog.js";
|
|
9
11
|
const DEFAULT_PAGE_SIZE = 25;
|
|
10
12
|
const ALL = "__all__";
|
|
@@ -23,6 +25,8 @@ export function loadPromotionsPage(queryClient, client, query = {}) {
|
|
|
23
25
|
return queryClient.ensureQueryData(getPromotionsListQueryOptions({ limit: DEFAULT_PAGE_SIZE, offset: 0, ...query }, createPromotionsClientOptions(client)));
|
|
24
26
|
}
|
|
25
27
|
export function PromotionsPage({ className, pageSize = DEFAULT_PAGE_SIZE, onOpenPromotion, renderPromotionDialog, } = {}) {
|
|
28
|
+
const messages = usePromotionsUiMessagesOrDefault();
|
|
29
|
+
const pageMessages = messages.promotionsPage;
|
|
26
30
|
const [search, setSearch] = useState("");
|
|
27
31
|
const [applicationMode, setApplicationMode] = useState(ALL);
|
|
28
32
|
const [status, setStatus] = useState(ALL);
|
|
@@ -86,62 +90,101 @@ export function PromotionsPage({ className, pageSize = DEFAULT_PAGE_SIZE, onOpen
|
|
|
86
90
|
void refetch();
|
|
87
91
|
},
|
|
88
92
|
})) : (_jsx(PromotionDialog, { open: dialogOpen, onOpenChange: setDialogOpen, offer: editingOffer }));
|
|
89
|
-
return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), children: [_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold tracking-tight", children:
|
|
93
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), children: [_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: pageMessages.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: pageMessages.description })] }), _jsxs(Button, { onClick: openCreate, children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), pageMessages.newPromotion] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsxs("div", { className: "relative min-w-[14rem] max-w-sm flex-1", children: [_jsx(Label, { htmlFor: "promotions-search", className: "sr-only", children: pageMessages.searchLabel }), _jsx(Search, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground", "aria-hidden": "true" }), _jsx(Input, { id: "promotions-search", placeholder: pageMessages.searchPlaceholder, value: search, onChange: (event) => {
|
|
90
94
|
setSearch(event.target.value);
|
|
91
95
|
resetPage();
|
|
92
96
|
}, className: "pl-9" })] }), _jsxs(Select, { value: applicationMode, onValueChange: (value) => {
|
|
93
97
|
setApplicationMode(value ?? ALL);
|
|
94
98
|
resetPage();
|
|
95
|
-
}, children: [_jsx(SelectTrigger, { className: "w-[10.5rem]", children: _jsx(SelectValue, { placeholder:
|
|
99
|
+
}, children: [_jsx(SelectTrigger, { className: "w-[10.5rem]", children: _jsx(SelectValue, { placeholder: pageMessages.modePlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL, children: pageMessages.allModes }), applicationModes.map((mode) => (_jsx(SelectItem, { value: mode, children: messages.common.applicationModeLabels[mode] }, mode)))] })] }), _jsxs(Select, { value: status, onValueChange: (value) => {
|
|
96
100
|
setStatus(value ?? ALL);
|
|
97
101
|
resetPage();
|
|
98
|
-
}, children: [_jsx(SelectTrigger, { className: "w-[10.5rem]", children: _jsx(SelectValue, { placeholder:
|
|
102
|
+
}, children: [_jsx(SelectTrigger, { className: "w-[10.5rem]", children: _jsx(SelectValue, { placeholder: pageMessages.statusPlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL, children: pageMessages.allStatuses }), statusFilters.map((value) => (_jsx(SelectItem, { value: value, children: messages.common.statusLabels[value] }, value)))] })] }), _jsxs(Select, { value: scopeKind, onValueChange: (value) => {
|
|
99
103
|
setScopeKind(value ?? ALL);
|
|
100
104
|
resetPage();
|
|
101
|
-
}, children: [_jsx(SelectTrigger, { className: "w-[11rem]", children: _jsx(SelectValue, { placeholder:
|
|
105
|
+
}, children: [_jsx(SelectTrigger, { className: "w-[11rem]", children: _jsx(SelectValue, { placeholder: pageMessages.scopePlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL, children: pageMessages.allScopes }), scopeKinds.map((value) => (_jsx(SelectItem, { value: value, children: messages.common.scopeKindLabels[value] }, value)))] })] }), _jsx(DateRangePicker, { value: validityRange, onChange: (value) => {
|
|
102
106
|
setValidityRange(value);
|
|
103
107
|
resetPage();
|
|
104
|
-
}, placeholder:
|
|
108
|
+
}, placeholder: pageMessages.validityRangePlaceholder, className: "w-[15rem]" }), hasActiveFilters ? (_jsxs(Button, { variant: "ghost", onClick: clearFilters, children: [_jsx(X, { className: "mr-2 size-4", "aria-hidden": "true" }), pageMessages.clearFilters] })) : null] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: pageMessages.columns.name }), _jsx(TableHead, { children: pageMessages.columns.mode }), _jsx(TableHead, { children: pageMessages.columns.scope }), _jsx(TableHead, { children: pageMessages.columns.discount }), _jsx(TableHead, { children: pageMessages.columns.validity }), _jsx(TableHead, { children: pageMessages.columns.code }), _jsx(TableHead, { children: pageMessages.columns.status })] }) }), _jsx(TableBody, { children: showSkeleton ? (_jsx(PromotionRowSkeleton, { rows: 6 })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: TABLE_COLUMN_COUNT, className: "h-24 text-center text-sm text-destructive", children: formatMessage(pageMessages.loadFailedPrefix, {
|
|
109
|
+
message: error instanceof Error ? error.message : String(error),
|
|
110
|
+
}) }) })) : offers.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: TABLE_COLUMN_COUNT, className: "h-24 text-center text-sm text-muted-foreground", children: pageMessages.empty }) })) : (offers.map((offer) => (_jsxs(TableRow, { className: "cursor-pointer", onClick: () => openOffer(offer), children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: offer.name }), _jsx("div", { className: "font-mono text-xs text-muted-foreground", children: offer.slug })] }), _jsx(TableCell, { children: _jsx(Badge, { variant: offer.code == null ? "secondary" : "outline", children: offer.code == null ? pageMessages.badges.auto : pageMessages.badges.code }) }), _jsx(TableCell, { className: "text-muted-foreground", children: summarizeScope(offer.scope, messages) }), _jsx(TableCell, { children: summarizeDiscount(offer, pageMessages) }), _jsx(TableCell, { className: "text-muted-foreground", children: summarizeValidity(offer.validFrom, offer.validUntil, pageMessages) }), _jsx(TableCell, { className: "font-mono text-xs", children: offer.code ?? pageMessages.summaries.noCode }), _jsx(TableCell, { children: _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Badge, { variant: statusBadgeVariant(getOfferStatus(offer)), children: messages.common.statusLabels[getOfferStatus(offer)] }), offer.stackable ? (_jsx(Badge, { variant: "secondary", children: pageMessages.badges.stackable })) : null] }) })] }, offer.id)))) })] }) }), _jsx(PaginationBar, { shown: offers.length, total: total, page: page, pageCount: pageCount, onPrevious: () => setPageIndex((prev) => Math.max(0, prev - 1)), onNext: () => setPageIndex((prev) => prev + 1), canGoBack: pageIndex > 0, canGoForward: (pageIndex + 1) * pageSize < total, messages: pageMessages.pagination }), dialog] }));
|
|
105
111
|
}
|
|
106
|
-
function PaginationBar({ shown, total, page, pageCount, onPrevious, onNext, canGoBack, canGoForward, }) {
|
|
107
|
-
return (_jsxs("div", { className: "flex items-center justify-between text-sm text-muted-foreground", children: [
|
|
112
|
+
function PaginationBar({ shown, total, page, pageCount, onPrevious, onNext, canGoBack, canGoForward, messages, }) {
|
|
113
|
+
return (_jsxs("div", { className: "flex items-center justify-between text-sm text-muted-foreground", children: [_jsx("span", { children: formatMessage(messages.showing, {
|
|
114
|
+
shown,
|
|
115
|
+
total,
|
|
116
|
+
}) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", disabled: !canGoBack, onClick: onPrevious, children: messages.previous }), _jsx("span", { children: formatMessage(messages.page, {
|
|
117
|
+
page,
|
|
118
|
+
pageCount,
|
|
119
|
+
}) }), _jsx(Button, { variant: "outline", size: "sm", disabled: !canGoForward, onClick: onNext, children: messages.next })] })] }));
|
|
108
120
|
}
|
|
109
121
|
function PromotionRowSkeleton({ rows }) {
|
|
110
122
|
return (_jsx(_Fragment, { children: Array.from({ length: rows }).map((_, index) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-40" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-5 w-16 rounded-full" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-28" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-20" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-32" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-20" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-5 w-20 rounded-full" }) })] }, `promotion-skeleton-${index}`))) }));
|
|
111
123
|
}
|
|
112
|
-
function summarizeScope(scope) {
|
|
124
|
+
function summarizeScope(scope, messages) {
|
|
125
|
+
const summary = messages.promotionsPage.summaries;
|
|
113
126
|
switch (scope.kind) {
|
|
114
127
|
case "global":
|
|
115
|
-
return
|
|
128
|
+
return summary.globalScope;
|
|
116
129
|
case "products":
|
|
117
|
-
return
|
|
130
|
+
return formatMessage(summary.productsScope, {
|
|
131
|
+
count: scope.productIds.length,
|
|
132
|
+
noun: scope.productIds.length === 1
|
|
133
|
+
? summary.productNouns.singular
|
|
134
|
+
: summary.productNouns.plural,
|
|
135
|
+
});
|
|
118
136
|
case "categories":
|
|
119
|
-
return
|
|
137
|
+
return formatMessage(summary.categoriesScope, {
|
|
138
|
+
count: scope.categoryIds.length,
|
|
139
|
+
noun: scope.categoryIds.length === 1
|
|
140
|
+
? summary.categoryNouns.singular
|
|
141
|
+
: summary.categoryNouns.plural,
|
|
142
|
+
});
|
|
120
143
|
case "destinations":
|
|
121
|
-
return
|
|
144
|
+
return formatMessage(summary.destinationsScope, {
|
|
145
|
+
count: scope.destinationIds.length,
|
|
146
|
+
noun: scope.destinationIds.length === 1
|
|
147
|
+
? summary.destinationNouns.singular
|
|
148
|
+
: summary.destinationNouns.plural,
|
|
149
|
+
});
|
|
122
150
|
case "markets":
|
|
123
|
-
return
|
|
151
|
+
return formatMessage(summary.marketsScope, {
|
|
152
|
+
markets: scope.marketIds.join(", "),
|
|
153
|
+
});
|
|
124
154
|
case "audiences":
|
|
125
|
-
return
|
|
155
|
+
return formatMessage(summary.audiencesScope, {
|
|
156
|
+
audiences: scope.audiences
|
|
157
|
+
.map((audience) => messages.common.audienceLabels[audience])
|
|
158
|
+
.join(", "),
|
|
159
|
+
});
|
|
126
160
|
}
|
|
127
161
|
}
|
|
128
|
-
function summarizeDiscount(offer) {
|
|
162
|
+
function summarizeDiscount(offer, messages) {
|
|
129
163
|
if (offer.discountType === "percentage") {
|
|
130
|
-
return `${offer.discountPercent ??
|
|
164
|
+
return `${offer.discountPercent ?? messages.summaries.unknownPercentage}%`;
|
|
131
165
|
}
|
|
132
166
|
const cents = offer.discountAmountCents ?? 0;
|
|
133
167
|
const currency = offer.currency ?? "";
|
|
134
168
|
return `${(cents / 100).toFixed(2)} ${currency}`.trim();
|
|
135
169
|
}
|
|
136
|
-
function summarizeValidity(from, until) {
|
|
170
|
+
function summarizeValidity(from, until, messages) {
|
|
137
171
|
if (from == null && until == null)
|
|
138
|
-
return
|
|
172
|
+
return messages.summaries.anytime;
|
|
139
173
|
const fmt = (iso) => iso.slice(0, 10);
|
|
140
|
-
if (from == null)
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
174
|
+
if (from == null) {
|
|
175
|
+
return formatMessage(messages.summaries.until, {
|
|
176
|
+
date: fmt(until ?? ""),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (until == null) {
|
|
180
|
+
return formatMessage(messages.summaries.from, {
|
|
181
|
+
date: fmt(from),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return formatMessage(messages.summaries.range, {
|
|
185
|
+
from: fmt(from),
|
|
186
|
+
until: fmt(until),
|
|
187
|
+
});
|
|
145
188
|
}
|
|
146
189
|
function getOfferStatus(offer) {
|
|
147
190
|
if (!offer.active)
|
|
@@ -165,34 +208,3 @@ function statusBadgeVariant(status) {
|
|
|
165
208
|
return "outline";
|
|
166
209
|
}
|
|
167
210
|
}
|
|
168
|
-
function applicationModeLabel(mode) {
|
|
169
|
-
return mode === "auto" ? "Auto-applied" : "Code-redeemed";
|
|
170
|
-
}
|
|
171
|
-
function statusLabel(status) {
|
|
172
|
-
switch (status) {
|
|
173
|
-
case "active":
|
|
174
|
-
return "Active";
|
|
175
|
-
case "scheduled":
|
|
176
|
-
return "Scheduled";
|
|
177
|
-
case "expired":
|
|
178
|
-
return "Expired";
|
|
179
|
-
case "archived":
|
|
180
|
-
return "Archived";
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function scopeKindLabel(scopeKind) {
|
|
184
|
-
switch (scopeKind) {
|
|
185
|
-
case "global":
|
|
186
|
-
return "Global";
|
|
187
|
-
case "products":
|
|
188
|
-
return "Products";
|
|
189
|
-
case "categories":
|
|
190
|
-
return "Categories";
|
|
191
|
-
case "destinations":
|
|
192
|
-
return "Destinations";
|
|
193
|
-
case "markets":
|
|
194
|
-
return "Markets";
|
|
195
|
-
case "audiences":
|
|
196
|
-
return "Audiences";
|
|
197
|
-
}
|
|
198
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/promotions-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,6 +15,21 @@
|
|
|
15
15
|
"import": "./dist/index.js",
|
|
16
16
|
"default": "./dist/index.js"
|
|
17
17
|
},
|
|
18
|
+
"./i18n": {
|
|
19
|
+
"types": "./dist/i18n/index.d.ts",
|
|
20
|
+
"import": "./dist/i18n/index.js",
|
|
21
|
+
"default": "./dist/i18n/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./i18n/en": {
|
|
24
|
+
"types": "./dist/i18n/en.d.ts",
|
|
25
|
+
"import": "./dist/i18n/en.js",
|
|
26
|
+
"default": "./dist/i18n/en.js"
|
|
27
|
+
},
|
|
28
|
+
"./i18n/ro": {
|
|
29
|
+
"types": "./dist/i18n/ro.d.ts",
|
|
30
|
+
"import": "./dist/i18n/ro.js",
|
|
31
|
+
"default": "./dist/i18n/ro.js"
|
|
32
|
+
},
|
|
18
33
|
"./components/*": {
|
|
19
34
|
"types": "./dist/*.d.ts",
|
|
20
35
|
"import": "./dist/*.js",
|
|
@@ -26,8 +41,11 @@
|
|
|
26
41
|
"react": "^19.0.0",
|
|
27
42
|
"react-dom": "^19.0.0",
|
|
28
43
|
"zod": "^4.3.6",
|
|
29
|
-
"@voyantjs/promotions-react": "0.
|
|
30
|
-
"@voyantjs/ui": "0.
|
|
44
|
+
"@voyantjs/promotions-react": "0.37.1",
|
|
45
|
+
"@voyantjs/ui": "0.37.1"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@voyantjs/i18n": "0.37.1"
|
|
31
49
|
},
|
|
32
50
|
"devDependencies": {
|
|
33
51
|
"@tanstack/react-query": "^5.96.2",
|
|
@@ -39,8 +57,9 @@
|
|
|
39
57
|
"typescript": "^6.0.2",
|
|
40
58
|
"vitest": "^4.1.2",
|
|
41
59
|
"zod": "^4.3.6",
|
|
42
|
-
"@voyantjs/
|
|
43
|
-
"@voyantjs/
|
|
60
|
+
"@voyantjs/i18n": "0.37.1",
|
|
61
|
+
"@voyantjs/promotions-react": "0.37.1",
|
|
62
|
+
"@voyantjs/ui": "0.37.1",
|
|
44
63
|
"@voyantjs/voyant-typescript-config": "0.1.0"
|
|
45
64
|
},
|
|
46
65
|
"files": [
|