hey-pharmacist-ecommerce 1.1.30 → 1.1.31
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/index.d.mts +1451 -1303
- package/dist/index.d.ts +1451 -1303
- package/dist/index.js +10502 -5728
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7817 -3059
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/src/components/AccountReviewsTab.tsx +97 -0
- package/src/components/CouponCodeInput.tsx +190 -0
- package/src/components/Header.tsx +5 -1
- package/src/components/Notification.tsx +1 -1
- package/src/components/NotificationBell.tsx +33 -0
- package/src/components/NotificationCard.tsx +211 -0
- package/src/components/NotificationDrawer.tsx +195 -0
- package/src/components/OrderCard.tsx +164 -99
- package/src/components/ProductReviewsSection.tsx +30 -0
- package/src/components/RatingDistribution.tsx +86 -0
- package/src/components/ReviewCard.tsx +59 -0
- package/src/components/ReviewForm.tsx +207 -0
- package/src/components/ReviewPromptBanner.tsx +98 -0
- package/src/components/ReviewsList.tsx +151 -0
- package/src/components/StarRating.tsx +98 -0
- package/src/hooks/useDiscounts.ts +7 -0
- package/src/hooks/useOrders.ts +15 -0
- package/src/hooks/useReviews.ts +230 -0
- package/src/index.ts +25 -0
- package/src/lib/Apis/apis/discounts-api.ts +23 -72
- package/src/lib/Apis/apis/notifications-api.ts +196 -231
- package/src/lib/Apis/apis/products-api.ts +84 -0
- package/src/lib/Apis/apis/review-api.ts +283 -4
- package/src/lib/Apis/apis/stores-api.ts +180 -0
- package/src/lib/Apis/models/bulk-channel-toggle-dto.ts +52 -0
- package/src/lib/Apis/models/cart-body-populated.ts +3 -3
- package/src/lib/Apis/models/channel-settings-dto.ts +39 -0
- package/src/lib/Apis/models/{discount-paginated-response.ts → completed-order-dto.ts} +21 -16
- package/src/lib/Apis/models/create-discount-dto.ts +31 -92
- package/src/lib/Apis/models/create-review-dto.ts +4 -4
- package/src/lib/Apis/models/create-shippo-account-dto.ts +45 -0
- package/src/lib/Apis/models/create-store-dto.ts +6 -0
- package/src/lib/Apis/models/discount.ts +37 -98
- package/src/lib/Apis/models/discounts-insights-dto.ts +12 -0
- package/src/lib/Apis/models/index.ts +13 -7
- package/src/lib/Apis/models/{manual-discount.ts → manual-discount-dto.ts} +10 -10
- package/src/lib/Apis/models/manual-order-dto.ts +3 -3
- package/src/lib/Apis/models/populated-discount.ts +41 -101
- package/src/lib/Apis/models/preference-update-item.ts +59 -0
- package/src/lib/Apis/models/product-light-dto.ts +40 -0
- package/src/lib/Apis/models/{check-notifications-response-dto.ts → review-status-dto.ts} +8 -7
- package/src/lib/Apis/models/review.ts +9 -3
- package/src/lib/Apis/models/reviewable-order-dto.ts +58 -0
- package/src/lib/Apis/models/reviewable-product-dto.ts +81 -0
- package/src/lib/Apis/models/shippo-account-response-dto.ts +51 -0
- package/src/lib/Apis/models/store-entity.ts +6 -0
- package/src/lib/Apis/models/store.ts +6 -0
- package/src/lib/Apis/models/update-discount-dto.ts +31 -92
- package/src/lib/Apis/models/update-notification-settings-dto.ts +28 -0
- package/src/lib/Apis/models/update-review-dto.ts +4 -4
- package/src/lib/Apis/models/update-store-dto.ts +6 -0
- package/src/lib/Apis/models/{pick-type-class.ts → variant-light-dto.ts} +20 -14
- package/src/lib/utils/discount.ts +155 -0
- package/src/lib/validations/discount.ts +11 -0
- package/src/providers/CartProvider.tsx +2 -2
- package/src/providers/DiscountProvider.tsx +97 -0
- package/src/providers/EcommerceProvider.tsx +13 -5
- package/src/providers/NotificationCenterProvider.tsx +436 -0
- package/src/screens/CartScreen.tsx +1 -1
- package/src/screens/CheckoutScreen.tsx +39 -12
- package/src/screens/NotificationSettingsScreen.tsx +413 -0
- package/src/screens/OrderDetailScreen.tsx +283 -0
- package/src/screens/OrderReviewsScreen.tsx +308 -0
- package/src/screens/OrdersScreen.tsx +31 -7
- package/src/screens/ProductDetailScreen.tsx +24 -11
- package/src/screens/ProfileScreen.tsx +5 -0
- package/src/lib/Apis/models/create-notification-dto.ts +0 -75
- package/src/lib/Apis/models/notification.ts +0 -93
- package/src/lib/Apis/models/single-notification-dto.ts +0 -99
|
@@ -19,155 +19,112 @@
|
|
|
19
19
|
export interface UpdateDiscountDto {
|
|
20
20
|
_id?: string;
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Display name for the discount
|
|
23
23
|
* @type {string}
|
|
24
24
|
* @memberof UpdateDiscountDto
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
name?: string;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Optional description for internal use
|
|
29
29
|
* @type {string}
|
|
30
30
|
* @memberof UpdateDiscountDto
|
|
31
31
|
*/
|
|
32
32
|
description?: string;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* ITEM_DISCOUNT: Shows on products automatically. COUPON_CODE: Requires code at checkout
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @memberof UpdateDiscountDto
|
|
37
37
|
*/
|
|
38
|
-
|
|
38
|
+
type?: UpdateDiscountDtoTypeEnum;
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
40
|
+
* PERCENTAGE or FIXED_AMOUNT
|
|
41
41
|
* @type {string}
|
|
42
42
|
* @memberof UpdateDiscountDto
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
valueType?: UpdateDiscountDtoValueTypeEnum;
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
47
|
-
* @type {
|
|
48
|
-
* @memberof UpdateDiscountDto
|
|
49
|
-
*/
|
|
50
|
-
code?: string;
|
|
51
|
-
/**
|
|
52
|
-
*
|
|
53
|
-
* @type {boolean}
|
|
46
|
+
* The discount value (percentage 1-100 or fixed dollar amount)
|
|
47
|
+
* @type {number}
|
|
54
48
|
* @memberof UpdateDiscountDto
|
|
55
49
|
*/
|
|
56
|
-
|
|
50
|
+
value?: number;
|
|
57
51
|
/**
|
|
58
|
-
*
|
|
52
|
+
* Maximum discount amount for percentage discounts (optional, mainly for COUPON_CODE)
|
|
59
53
|
* @type {number}
|
|
60
54
|
* @memberof UpdateDiscountDto
|
|
61
55
|
*/
|
|
62
56
|
cappedAt?: number;
|
|
63
57
|
/**
|
|
64
|
-
*
|
|
65
|
-
* @type {
|
|
58
|
+
* Coupon code (required for COUPON_CODE type)
|
|
59
|
+
* @type {string}
|
|
66
60
|
* @memberof UpdateDiscountDto
|
|
67
61
|
*/
|
|
68
|
-
|
|
62
|
+
code?: string;
|
|
69
63
|
/**
|
|
70
|
-
*
|
|
64
|
+
* When the discount becomes active
|
|
71
65
|
* @type {Date}
|
|
72
66
|
* @memberof UpdateDiscountDto
|
|
73
67
|
*/
|
|
74
68
|
startsAt?: Date;
|
|
75
69
|
/**
|
|
76
|
-
*
|
|
70
|
+
* When the discount expires
|
|
77
71
|
* @type {Date}
|
|
78
72
|
* @memberof UpdateDiscountDto
|
|
79
73
|
*/
|
|
80
74
|
expiresAt?: Date;
|
|
81
75
|
/**
|
|
82
|
-
*
|
|
83
|
-
* @type {number}
|
|
84
|
-
* @memberof UpdateDiscountDto
|
|
85
|
-
*/
|
|
86
|
-
maxUses?: number;
|
|
87
|
-
/**
|
|
88
|
-
*
|
|
76
|
+
* Current lifecycle state
|
|
89
77
|
* @type {string}
|
|
90
78
|
* @memberof UpdateDiscountDto
|
|
91
79
|
*/
|
|
92
80
|
state?: UpdateDiscountDtoStateEnum;
|
|
93
81
|
/**
|
|
94
|
-
*
|
|
95
|
-
* @type {
|
|
82
|
+
* Quick toggle to enable/disable the discount
|
|
83
|
+
* @type {boolean}
|
|
96
84
|
* @memberof UpdateDiscountDto
|
|
97
85
|
*/
|
|
98
|
-
|
|
86
|
+
isActive?: boolean;
|
|
99
87
|
/**
|
|
100
|
-
*
|
|
88
|
+
* Products to apply discount to (all their variants will be discounted)
|
|
101
89
|
* @type {Array<string>}
|
|
102
90
|
* @memberof UpdateDiscountDto
|
|
103
91
|
*/
|
|
104
92
|
productsEligible?: Array<string>;
|
|
105
93
|
/**
|
|
106
|
-
*
|
|
94
|
+
* Specific variants to apply discount to
|
|
107
95
|
* @type {Array<string>}
|
|
108
96
|
* @memberof UpdateDiscountDto
|
|
109
97
|
*/
|
|
110
98
|
variantsEligible?: Array<string>;
|
|
111
99
|
/**
|
|
112
|
-
*
|
|
113
|
-
* @type {number}
|
|
114
|
-
* @memberof UpdateDiscountDto
|
|
115
|
-
*/
|
|
116
|
-
maxUsesPerUser?: number;
|
|
117
|
-
/**
|
|
118
|
-
*
|
|
119
|
-
* @type {string}
|
|
120
|
-
* @memberof UpdateDiscountDto
|
|
121
|
-
*/
|
|
122
|
-
minPurchaseRequired?: UpdateDiscountDtoMinPurchaseRequiredEnum;
|
|
123
|
-
/**
|
|
124
|
-
*
|
|
100
|
+
* Maximum total times this discount can be used (optional)
|
|
125
101
|
* @type {number}
|
|
126
102
|
* @memberof UpdateDiscountDto
|
|
127
103
|
*/
|
|
128
|
-
|
|
104
|
+
maxUses?: number;
|
|
129
105
|
/**
|
|
130
|
-
*
|
|
106
|
+
* Maximum times a single user can use this discount (optional)
|
|
131
107
|
* @type {number}
|
|
132
108
|
* @memberof UpdateDiscountDto
|
|
133
109
|
*/
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
*
|
|
137
|
-
* @type {string}
|
|
138
|
-
* @memberof UpdateDiscountDto
|
|
139
|
-
*/
|
|
140
|
-
customerEligibility?: UpdateDiscountDtoCustomerEligibilityEnum;
|
|
141
|
-
/**
|
|
142
|
-
*
|
|
143
|
-
* @type {Array<string>}
|
|
144
|
-
* @memberof UpdateDiscountDto
|
|
145
|
-
*/
|
|
146
|
-
usersEligible?: Array<string>;
|
|
147
|
-
/**
|
|
148
|
-
*
|
|
149
|
-
* @type {Array<string>}
|
|
150
|
-
* @memberof UpdateDiscountDto
|
|
151
|
-
*/
|
|
152
|
-
groupsEligible?: Array<string>;
|
|
110
|
+
maxUsesPerUser?: number;
|
|
153
111
|
}
|
|
154
112
|
|
|
155
113
|
/**
|
|
156
114
|
* @export
|
|
157
115
|
* @enum {string}
|
|
158
116
|
*/
|
|
159
|
-
export enum
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
VARIANT = 'VARIANT'
|
|
117
|
+
export enum UpdateDiscountDtoTypeEnum {
|
|
118
|
+
ITEMDISCOUNT = 'ITEM_DISCOUNT',
|
|
119
|
+
COUPONCODE = 'COUPON_CODE'
|
|
163
120
|
}
|
|
164
121
|
/**
|
|
165
122
|
* @export
|
|
166
123
|
* @enum {string}
|
|
167
124
|
*/
|
|
168
|
-
export enum
|
|
125
|
+
export enum UpdateDiscountDtoValueTypeEnum {
|
|
169
126
|
PERCENTAGE = 'PERCENTAGE',
|
|
170
|
-
|
|
127
|
+
FIXEDAMOUNT = 'FIXED_AMOUNT'
|
|
171
128
|
}
|
|
172
129
|
/**
|
|
173
130
|
* @export
|
|
@@ -179,22 +136,4 @@ export enum UpdateDiscountDtoStateEnum {
|
|
|
179
136
|
EXPIRED = 'EXPIRED',
|
|
180
137
|
SCHEDULED = 'SCHEDULED'
|
|
181
138
|
}
|
|
182
|
-
/**
|
|
183
|
-
* @export
|
|
184
|
-
* @enum {string}
|
|
185
|
-
*/
|
|
186
|
-
export enum UpdateDiscountDtoMinPurchaseRequiredEnum {
|
|
187
|
-
NOREQUIREMENT = 'NO_REQUIREMENT',
|
|
188
|
-
MINIMUMAMOUNT = 'MINIMUM_AMOUNT',
|
|
189
|
-
MINIMUMQUANTITY = 'MINIMUM_QUANTITY'
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* @export
|
|
193
|
-
* @enum {string}
|
|
194
|
-
*/
|
|
195
|
-
export enum UpdateDiscountDtoCustomerEligibilityEnum {
|
|
196
|
-
ALL = 'ALL',
|
|
197
|
-
SPECIFICUSERS = 'SPECIFIC_USERS',
|
|
198
|
-
SPECIFICGROUPS = 'SPECIFIC_GROUPS'
|
|
199
|
-
}
|
|
200
139
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/**
|
|
4
|
+
* Hey Pharamcist API
|
|
5
|
+
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
6
|
+
*
|
|
7
|
+
* OpenAPI spec version: 1.0
|
|
8
|
+
*
|
|
9
|
+
*
|
|
10
|
+
* NOTE: This class is auto generated by the swagger code generator program.
|
|
11
|
+
* https://github.com/swagger-api/swagger-codegen.git
|
|
12
|
+
* Do not edit the class manually.
|
|
13
|
+
*/
|
|
14
|
+
import { PreferenceUpdateItem } from './preference-update-item';
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @export
|
|
18
|
+
* @interface UpdateNotificationSettingsDto
|
|
19
|
+
*/
|
|
20
|
+
export interface UpdateNotificationSettingsDto {
|
|
21
|
+
_id?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Array of preference updates to apply
|
|
24
|
+
* @type {Array<PreferenceUpdateItem>}
|
|
25
|
+
* @memberof UpdateNotificationSettingsDto
|
|
26
|
+
*/
|
|
27
|
+
preferences: Array<PreferenceUpdateItem>;
|
|
28
|
+
}
|
|
@@ -23,13 +23,13 @@ export interface UpdateReviewDto {
|
|
|
23
23
|
* @type {string}
|
|
24
24
|
* @memberof UpdateReviewDto
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
productId?: string;
|
|
27
27
|
/**
|
|
28
28
|
*
|
|
29
29
|
* @type {string}
|
|
30
30
|
* @memberof UpdateReviewDto
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
productVariantId?: string;
|
|
33
33
|
/**
|
|
34
34
|
*
|
|
35
35
|
* @type {string}
|
|
@@ -56,8 +56,8 @@ export interface UpdateReviewDto {
|
|
|
56
56
|
reply?: string;
|
|
57
57
|
/**
|
|
58
58
|
*
|
|
59
|
-
* @type {
|
|
59
|
+
* @type {Date}
|
|
60
60
|
* @memberof UpdateReviewDto
|
|
61
61
|
*/
|
|
62
|
-
replyDate?:
|
|
62
|
+
replyDate?: Date;
|
|
63
63
|
}
|
|
@@ -49,6 +49,12 @@ export interface UpdateStoreDto {
|
|
|
49
49
|
* @memberof UpdateStoreDto
|
|
50
50
|
*/
|
|
51
51
|
adminUrl?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Shippo Platform managed account ID
|
|
54
|
+
* @type {string}
|
|
55
|
+
* @memberof UpdateStoreDto
|
|
56
|
+
*/
|
|
57
|
+
shippoAccountId?: string;
|
|
52
58
|
/**
|
|
53
59
|
*
|
|
54
60
|
* @type {string}
|
|
@@ -14,38 +14,44 @@
|
|
|
14
14
|
/**
|
|
15
15
|
*
|
|
16
16
|
* @export
|
|
17
|
-
* @interface
|
|
17
|
+
* @interface VariantLightDto
|
|
18
18
|
*/
|
|
19
|
-
export interface
|
|
19
|
+
export interface VariantLightDto {
|
|
20
20
|
_id?: string;
|
|
21
21
|
/**
|
|
22
22
|
*
|
|
23
23
|
* @type {string}
|
|
24
|
-
* @memberof
|
|
24
|
+
* @memberof VariantLightDto
|
|
25
25
|
*/
|
|
26
|
-
id
|
|
26
|
+
id: string;
|
|
27
27
|
/**
|
|
28
28
|
*
|
|
29
29
|
* @type {string}
|
|
30
|
-
* @memberof
|
|
30
|
+
* @memberof VariantLightDto
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
name: string;
|
|
33
33
|
/**
|
|
34
34
|
*
|
|
35
|
-
* @type {
|
|
36
|
-
* @memberof
|
|
35
|
+
* @type {number}
|
|
36
|
+
* @memberof VariantLightDto
|
|
37
37
|
*/
|
|
38
|
-
|
|
38
|
+
retailPrice: number;
|
|
39
39
|
/**
|
|
40
40
|
*
|
|
41
|
-
* @type {
|
|
42
|
-
* @memberof
|
|
41
|
+
* @type {number}
|
|
42
|
+
* @memberof VariantLightDto
|
|
43
|
+
*/
|
|
44
|
+
finalPrice: number;
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @type {number}
|
|
48
|
+
* @memberof VariantLightDto
|
|
43
49
|
*/
|
|
44
|
-
|
|
50
|
+
inventoryCount: number;
|
|
45
51
|
/**
|
|
46
52
|
*
|
|
47
53
|
* @type {string}
|
|
48
|
-
* @memberof
|
|
54
|
+
* @memberof VariantLightDto
|
|
49
55
|
*/
|
|
50
|
-
|
|
56
|
+
sku: string;
|
|
51
57
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discount Utility Functions
|
|
3
|
+
* Handles calculation and validation of discounts based on the V2 refactor
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Discount } from '@/lib/Apis/models/discount';
|
|
7
|
+
import {
|
|
8
|
+
DiscountTypeEnum,
|
|
9
|
+
DiscountValueTypeEnum,
|
|
10
|
+
DiscountStateEnum
|
|
11
|
+
} from '@/lib/Apis/models/discount';
|
|
12
|
+
|
|
13
|
+
export { DiscountTypeEnum, DiscountValueTypeEnum, DiscountStateEnum };
|
|
14
|
+
|
|
15
|
+
export interface DiscountsInsightsDto {
|
|
16
|
+
totalDiscounts: number;
|
|
17
|
+
totalActive: number;
|
|
18
|
+
totalInactive: number;
|
|
19
|
+
totalExpired: number;
|
|
20
|
+
totalScheduled: number;
|
|
21
|
+
totalItemDiscounts: number;
|
|
22
|
+
totalCouponCodes: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calculate discount amount for a given price
|
|
27
|
+
* @param price - Original price
|
|
28
|
+
* @param discount - Discount object
|
|
29
|
+
* @returns Calculated discount amount
|
|
30
|
+
*/
|
|
31
|
+
export function calculateDiscountAmount(price: number, discount: Discount): number {
|
|
32
|
+
// Skip state validation - trust that backend already validated the discount
|
|
33
|
+
// Just check if we have the required fields to calculate
|
|
34
|
+
if (!discount || !discount.value) {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let amount = 0;
|
|
39
|
+
const valueType = String(discount.valueType).toUpperCase();
|
|
40
|
+
|
|
41
|
+
if (valueType === 'PERCENTAGE') {
|
|
42
|
+
amount = (price * discount.value) / 100;
|
|
43
|
+
// Apply cap if specified (mainly for coupon codes)
|
|
44
|
+
if (discount.cappedAt && amount > discount.cappedAt) {
|
|
45
|
+
amount = discount.cappedAt;
|
|
46
|
+
}
|
|
47
|
+
} else if (valueType === 'FIXED_AMOUNT' || valueType === 'FIXEDAMOUNT') {
|
|
48
|
+
amount = discount.value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const finalAmount = Math.min(amount, price);
|
|
52
|
+
return Math.round(finalAmount * 100) / 100; // Round to 2 decimal places
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Calculate final price after discount
|
|
57
|
+
* @param price - Original price
|
|
58
|
+
* @param discount - Discount object
|
|
59
|
+
* @returns Final discounted price
|
|
60
|
+
*/
|
|
61
|
+
export function calculateFinalPrice(price: number, discount: Discount): number {
|
|
62
|
+
const discountAmount = calculateDiscountAmount(price, discount);
|
|
63
|
+
return Math.max(0, price - discountAmount);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if a discount is currently valid (active and not expired/scheduled)
|
|
68
|
+
* @param discount - Discount object
|
|
69
|
+
* @returns True if discount is valid and can be applied
|
|
70
|
+
*/
|
|
71
|
+
export function isDiscountValid(discount: Discount): boolean {
|
|
72
|
+
if (!discount.isActive) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const now = new Date();
|
|
77
|
+
|
|
78
|
+
// Check if scheduled
|
|
79
|
+
if (discount.startsAt && new Date(discount.startsAt) > now) {
|
|
80
|
+
return false; // Not yet started
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if expired
|
|
84
|
+
if (discount.expiresAt && new Date(discount.expiresAt) < now) {
|
|
85
|
+
return false; // Already expired
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check state
|
|
89
|
+
const activeStates = [
|
|
90
|
+
DiscountStateEnum.ACTIVE,
|
|
91
|
+
'ACTIVE',
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
if (!activeStates.includes(discount.state as any)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if a product is eligible for an item discount
|
|
103
|
+
* @param productId - Product ID
|
|
104
|
+
* @param variantId - Variant ID (optional)
|
|
105
|
+
* @param discount - Discount object
|
|
106
|
+
* @returns True if product/variant is eligible
|
|
107
|
+
*/
|
|
108
|
+
export function isProductEligibleForDiscount(
|
|
109
|
+
productId: string,
|
|
110
|
+
variantId: string | undefined,
|
|
111
|
+
discount: Discount
|
|
112
|
+
): boolean {
|
|
113
|
+
// Check variant eligibility first (most specific)
|
|
114
|
+
if (variantId && discount.variantsEligible?.includes(variantId)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check product eligibility
|
|
119
|
+
if (discount.productsEligible?.includes(productId)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Format discount display text
|
|
128
|
+
* @param discount - Discount object
|
|
129
|
+
* @returns Formatted string like "20% OFF" or "$10 OFF"
|
|
130
|
+
*/
|
|
131
|
+
export function formatDiscountDisplay(discount: Discount): string {
|
|
132
|
+
const valueType = String(discount.valueType).toUpperCase();
|
|
133
|
+
|
|
134
|
+
if (valueType === 'PERCENTAGE') {
|
|
135
|
+
return `${discount.value}% OFF`;
|
|
136
|
+
} else {
|
|
137
|
+
return `$${discount.value.toFixed(2)} OFF`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get discount state label
|
|
143
|
+
* @param state - Discount state
|
|
144
|
+
* @returns Human-readable state label
|
|
145
|
+
*/
|
|
146
|
+
export function getDiscountStateLabel(state: DiscountStateEnum | string): string {
|
|
147
|
+
const stateStr = String(state).toUpperCase();
|
|
148
|
+
const stateMap: Record<string, string> = {
|
|
149
|
+
'ACTIVE': 'Active',
|
|
150
|
+
'INACTIVE': 'Inactive',
|
|
151
|
+
'EXPIRED': 'Expired',
|
|
152
|
+
'SCHEDULED': 'Scheduled',
|
|
153
|
+
};
|
|
154
|
+
return stateMap[stateStr] || 'Unknown';
|
|
155
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const couponCodeSchema = z.object({
|
|
4
|
+
code: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(2, 'Coupon code must be at least 2 characters')
|
|
7
|
+
.max(50, 'Coupon code must not exceed 50 characters')
|
|
8
|
+
.toUpperCase(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type CouponCodeFormData = z.infer<typeof couponCodeSchema>;
|
|
@@ -128,7 +128,7 @@ export function CartProvider({ children }: CartProviderProps) {
|
|
|
128
128
|
}
|
|
129
129
|
return item;
|
|
130
130
|
});
|
|
131
|
-
const newSubtotal = newItems.reduce((acc, item) => acc + (item.productVariantData.finalPrice || 0) * item.quantity, 0);
|
|
131
|
+
const newSubtotal = Math.round(newItems.reduce((acc, item) => acc + (item.productVariantData.finalPrice || 0) * item.quantity, 0) * 100) / 100;
|
|
132
132
|
return {
|
|
133
133
|
...prev,
|
|
134
134
|
subTotal: newSubtotal,
|
|
@@ -174,7 +174,7 @@ export function CartProvider({ children }: CartProviderProps) {
|
|
|
174
174
|
setCart(prev => {
|
|
175
175
|
if (!prev) return null;
|
|
176
176
|
const newItems = prev.cartBody.items.filter(item => String(item.productVariantId) !== String(productId));
|
|
177
|
-
const newSubtotal = newItems.reduce((acc, item) => acc + (item.productVariantData.finalPrice || 0) * item.quantity, 0);
|
|
177
|
+
const newSubtotal = Math.round(newItems.reduce((acc, item) => acc + (item.productVariantData.finalPrice || 0) * item.quantity, 0) * 100) / 100;
|
|
178
178
|
return {
|
|
179
179
|
...prev,
|
|
180
180
|
subTotal: newSubtotal,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useState, useCallback } from 'react';
|
|
4
|
+
import { Discount } from '@/lib/Apis/models/discount';
|
|
5
|
+
import { DiscountsApi } from '@/lib/Apis/apis/discounts-api';
|
|
6
|
+
import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
|
|
7
|
+
import { calculateDiscountAmount } from '@/lib/utils/discount';
|
|
8
|
+
|
|
9
|
+
interface DiscountContextType {
|
|
10
|
+
appliedCoupon: Discount | null;
|
|
11
|
+
couponError: string | null;
|
|
12
|
+
isValidatingCoupon: boolean;
|
|
13
|
+
validateAndApplyCoupon: (code: string, userId?: string) => Promise<{ success: boolean; error?: string; discount?: Discount }>;
|
|
14
|
+
removeCoupon: () => void;
|
|
15
|
+
calculateCouponDiscount: (subtotal: number) => number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DiscountContext = createContext<DiscountContextType | undefined>(undefined);
|
|
19
|
+
|
|
20
|
+
export function DiscountProvider({ children }: { children: React.ReactNode }) {
|
|
21
|
+
const [appliedCoupon, setAppliedCoupon] = useState<Discount | null>(null);
|
|
22
|
+
const [couponError, setCouponError] = useState<string | null>(null);
|
|
23
|
+
const [isValidatingCoupon, setIsValidatingCoupon] = useState(false);
|
|
24
|
+
|
|
25
|
+
const validateAndApplyCoupon = useCallback(
|
|
26
|
+
async (code: string, userId?: string) => {
|
|
27
|
+
setIsValidatingCoupon(true);
|
|
28
|
+
setCouponError(null);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const api = new DiscountsApi(AXIOS_CONFIG);
|
|
32
|
+
const response = await api.validateCoupon(code, userId);
|
|
33
|
+
const discount = response.data;
|
|
34
|
+
|
|
35
|
+
if (!discount) {
|
|
36
|
+
setCouponError('Coupon code not found');
|
|
37
|
+
setAppliedCoupon(null);
|
|
38
|
+
return { success: false, error: 'Coupon code not found' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setAppliedCoupon(discount);
|
|
42
|
+
setCouponError(null);
|
|
43
|
+
return { success: true, discount };
|
|
44
|
+
} catch (error: any) {
|
|
45
|
+
const errorMessage =
|
|
46
|
+
error.response?.data?.message || 'Failed to validate coupon code';
|
|
47
|
+
setCouponError(errorMessage);
|
|
48
|
+
setAppliedCoupon(null);
|
|
49
|
+
return { success: false, error: errorMessage };
|
|
50
|
+
} finally {
|
|
51
|
+
setIsValidatingCoupon(false);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
[]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const removeCoupon = useCallback(() => {
|
|
58
|
+
setAppliedCoupon(null);
|
|
59
|
+
setCouponError(null);
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
const calculateCouponDiscount = useCallback(
|
|
63
|
+
(subtotal: number): number => {
|
|
64
|
+
if (!appliedCoupon) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
const discount = calculateDiscountAmount(subtotal, appliedCoupon);
|
|
68
|
+
return discount;
|
|
69
|
+
},
|
|
70
|
+
[appliedCoupon]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const value: DiscountContextType = {
|
|
74
|
+
appliedCoupon,
|
|
75
|
+
couponError,
|
|
76
|
+
isValidatingCoupon,
|
|
77
|
+
validateAndApplyCoupon,
|
|
78
|
+
removeCoupon,
|
|
79
|
+
calculateCouponDiscount,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<DiscountContext.Provider value={value}>
|
|
84
|
+
{children}
|
|
85
|
+
</DiscountContext.Provider>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function useDiscountsContext() {
|
|
90
|
+
const context = useContext(DiscountContext);
|
|
91
|
+
if (!context) {
|
|
92
|
+
throw new Error('useDiscountsContext must be used within DiscountProvider');
|
|
93
|
+
}
|
|
94
|
+
return context;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type { DiscountContextType };
|
|
@@ -7,10 +7,13 @@ import { AuthProvider } from './AuthProvider';
|
|
|
7
7
|
import { CartProvider } from './CartProvider';
|
|
8
8
|
import { WishlistProvider } from './WishlistProvider';
|
|
9
9
|
import { BasePathProvider } from './BasePathProvider';
|
|
10
|
+
import { DiscountProvider } from './DiscountProvider';
|
|
10
11
|
import { initializeApiAdapter } from '@/lib/api-adapter';
|
|
11
12
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
12
13
|
import { QueryClient } from '@tanstack/react-query';
|
|
13
14
|
import { NotificationProvider } from './NotificationProvider';
|
|
15
|
+
import { NotificationCenterProvider } from './NotificationCenterProvider';
|
|
16
|
+
import { NotificationDrawer } from '@/components/NotificationDrawer';
|
|
14
17
|
|
|
15
18
|
interface EcommerceProviderProps {
|
|
16
19
|
config: EcommerceConfig;
|
|
@@ -35,11 +38,16 @@ export function EcommerceProvider({ config, children, withToaster = true, basePa
|
|
|
35
38
|
<BasePathProvider basePath={basePath}>
|
|
36
39
|
<AuthProvider>
|
|
37
40
|
<NotificationProvider>
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
<NotificationCenterProvider>
|
|
42
|
+
<CartProvider>
|
|
43
|
+
<DiscountProvider>
|
|
44
|
+
<WishlistProvider>
|
|
45
|
+
{children}
|
|
46
|
+
<NotificationDrawer />
|
|
47
|
+
</WishlistProvider>
|
|
48
|
+
</DiscountProvider>
|
|
49
|
+
</CartProvider>
|
|
50
|
+
</NotificationCenterProvider>
|
|
43
51
|
</NotificationProvider>
|
|
44
52
|
</AuthProvider>
|
|
45
53
|
</BasePathProvider>
|