sahab-utils 1.7.6 → 1.7.7

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.
@@ -811,6 +811,8 @@ export type AppSettings = {
811
811
  minimumBalanceToUse?: number;
812
812
  /** The one live cashback campaign, if any — nested here rather than its own collection. */
813
813
  cashback?: CashbackOffer;
814
+ /** Kill switch for the order Live Activity / Android Live Update pushes. Off unless true. */
815
+ liveActivitiesEnabled?: boolean;
814
816
  };
815
817
  export type OrderReview = {
816
818
  ratingId: string;
package/dist/index.d.ts CHANGED
@@ -3,5 +3,6 @@ export * from './ts/calcs';
3
3
  export * from './ts/cashback';
4
4
  export * from './ts/gifts';
5
5
  export * from './ts/loyalty';
6
+ export * from './ts/orderActivity';
6
7
  export * from './ts/utils';
7
8
  export * from './ts/wheel';
package/dist/index.js CHANGED
@@ -19,5 +19,6 @@ __exportStar(require("./ts/calcs"), exports);
19
19
  __exportStar(require("./ts/cashback"), exports);
20
20
  __exportStar(require("./ts/gifts"), exports);
21
21
  __exportStar(require("./ts/loyalty"), exports);
22
+ __exportStar(require("./ts/orderActivity"), exports);
22
23
  __exportStar(require("./ts/utils"), exports);
23
24
  __exportStar(require("./ts/wheel"), exports);
@@ -0,0 +1,74 @@
1
+ import { Order, OrderStatus } from '../constants/types';
2
+ export declare const ORDER_ACTIVITY_TYPE = "LiveActivityAttributes";
3
+ export declare const ORDER_ACTIVITY_NAME = "orderStatus";
4
+ export declare const ORDER_ACTIVITY_STEP_COUNT = 4;
5
+ /** The activity (and the widget's live state) starts this long before the booked time. */
6
+ export declare const ORDER_LIVE_WINDOW_MS: number;
7
+ /** How long the widget keeps saying the car is clean after the order finishes. */
8
+ export declare const ORDER_DONE_VISIBLE_MS: number;
9
+ export declare const ORDER_ACTIVITY_STALE_MS: number;
10
+ /** How long the widget trusts a status it was last told while the app was closed; after that it asks the customer to open the app. */
11
+ export declare const ORDER_WIDGET_FRESH_MS: number;
12
+ /** The id both OneSignal (server) and the app's token registration use for one order's activity. */
13
+ export declare const orderActivityId: (orderId: string) => string;
14
+ /** The deep link a push-started activity carries in its static attributes; the app reads the order id back out of it. */
15
+ export declare const orderDeepLink: (orderId: string) => string;
16
+ export declare const orderIdFromDeepLink: (url?: string) => string | undefined;
17
+ export type OrderActivityStep = 'SCHEDULED' | 'ON_THE_WAY' | 'IN_PROGRESS' | 'DONE' | 'CANCELLED';
18
+ export type OrderActivityLang = 'ar' | 'en';
19
+ export type OrderActivityContent = {
20
+ orderId: string;
21
+ step: OrderActivityStep;
22
+ stepIndex: number;
23
+ progress: number;
24
+ isTerminal: boolean;
25
+ isRtl: boolean;
26
+ providerName: string;
27
+ orderTimeMs: number;
28
+ orderTimeText: string;
29
+ title: string;
30
+ subtitle: string;
31
+ a11yLabel: string;
32
+ updatedAtMs: number;
33
+ };
34
+ export declare const ORDER_ACTIVITY_TEXT: {
35
+ titles: Record<OrderActivityLang, Record<OrderActivityStep, string>>;
36
+ stepPhrase: {
37
+ en: (n: number, label: string) => string;
38
+ ar: (n: number, label: string) => string;
39
+ };
40
+ };
41
+ export declare const ACTIVE_ORDER_STATUSES: OrderStatus[];
42
+ export declare function orderActivityLang(language?: string): OrderActivityLang;
43
+ export declare function isOrderActive(status?: OrderStatus): boolean;
44
+ export declare function orderActivityStep(status?: OrderStatus): OrderActivityStep | null;
45
+ /** Whether an order deserves a Live Activity right now: on the way / washing, or accepted and due within the live window. */
46
+ export declare function isOrderLive(status: OrderStatus | undefined, orderDateMs: number, nowMs: number): boolean;
47
+ /** What every surface (Live Activity, widget, Android notification) shows for an order. Never includes the driver's name. */
48
+ export declare function buildOrderActivityContent(order: Pick<Order, 'orderId' | 'orderStatus' | 'providerName'>, orderDateMs: number, lang: OrderActivityLang, nowMs?: number): OrderActivityContent | null;
49
+ export type OrderWidgetState = 'NONE' | 'UPCOMING' | 'LIVE' | 'DONE' | 'STALE';
50
+ /** An order as the widget sees it: dates already converted to epoch milliseconds by the caller. */
51
+ export type OrderWidgetInput = Pick<Order, 'orderId' | 'orderStatus' | 'providerName'> & {
52
+ orderDateMs: number;
53
+ finishMs?: number;
54
+ };
55
+ export type OrderWidgetContent = {
56
+ state: OrderWidgetState;
57
+ orderId: string;
58
+ title: string;
59
+ subtitle: string;
60
+ /** "+2 more" when the customer has other active orders, otherwise empty. */
61
+ moreText: string;
62
+ stepIndex: number;
63
+ progress: number;
64
+ a11yLabel: string;
65
+ };
66
+ /** The one order the widget (and a single-slot surface) should talk about: live orders first, most advanced first, then the soonest booking. */
67
+ export declare function pickPrimaryOrder<T extends Pick<OrderWidgetInput, 'orderStatus' | 'orderDateMs'>>(orders: T[], nowMs: number): T | null;
68
+ /**
69
+ * What the home-screen widget says at `nowMs`: the primary active order (upcoming until it is live), "your car is clean"
70
+ * for a while after the last finished order, otherwise an invitation to book.
71
+ */
72
+ export declare function buildOrderWidgetContent(active: OrderWidgetInput[], lastFinished: OrderWidgetInput | null, lang: OrderActivityLang, nowMs?: number, syncedAtMs?: number): OrderWidgetContent;
73
+ /** Future moments at which the widget's text changes on its own (a booking turning live, "clean" expiring, a status going stale), soonest first. */
74
+ export declare function orderWidgetChangeTimes(active: OrderWidgetInput[], lastFinished: OrderWidgetInput | null, nowMs?: number): number[];
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ACTIVE_ORDER_STATUSES = exports.ORDER_ACTIVITY_TEXT = exports.orderIdFromDeepLink = exports.orderDeepLink = exports.orderActivityId = exports.ORDER_WIDGET_FRESH_MS = exports.ORDER_ACTIVITY_STALE_MS = exports.ORDER_DONE_VISIBLE_MS = exports.ORDER_LIVE_WINDOW_MS = exports.ORDER_ACTIVITY_STEP_COUNT = exports.ORDER_ACTIVITY_NAME = exports.ORDER_ACTIVITY_TYPE = void 0;
4
+ exports.orderActivityLang = orderActivityLang;
5
+ exports.isOrderActive = isOrderActive;
6
+ exports.orderActivityStep = orderActivityStep;
7
+ exports.isOrderLive = isOrderLive;
8
+ exports.buildOrderActivityContent = buildOrderActivityContent;
9
+ exports.pickPrimaryOrder = pickPrimaryOrder;
10
+ exports.buildOrderWidgetContent = buildOrderWidgetContent;
11
+ exports.orderWidgetChangeTimes = orderWidgetChangeTimes;
12
+ const types_1 = require("../constants/types");
13
+ exports.ORDER_ACTIVITY_TYPE = 'LiveActivityAttributes';
14
+ exports.ORDER_ACTIVITY_NAME = 'orderStatus';
15
+ exports.ORDER_ACTIVITY_STEP_COUNT = 4;
16
+ /** The activity (and the widget's live state) starts this long before the booked time. */
17
+ exports.ORDER_LIVE_WINDOW_MS = 15 * 60 * 1000;
18
+ /** How long the widget keeps saying the car is clean after the order finishes. */
19
+ exports.ORDER_DONE_VISIBLE_MS = 2 * 60 * 60 * 1000;
20
+ exports.ORDER_ACTIVITY_STALE_MS = 3 * 60 * 60 * 1000;
21
+ /** How long the widget trusts a status it was last told while the app was closed; after that it asks the customer to open the app. */
22
+ exports.ORDER_WIDGET_FRESH_MS = 45 * 60 * 1000;
23
+ /** The id both OneSignal (server) and the app's token registration use for one order's activity. */
24
+ const orderActivityId = (orderId) => `order-${orderId}`;
25
+ exports.orderActivityId = orderActivityId;
26
+ /** The deep link a push-started activity carries in its static attributes; the app reads the order id back out of it. */
27
+ const orderDeepLink = (orderId) => `sahab://orders/${orderId}`;
28
+ exports.orderDeepLink = orderDeepLink;
29
+ const orderIdFromDeepLink = (url) => { var _a; return (_a = /^sahab:\/\/orders\/([^/?#]+)/.exec(url !== null && url !== void 0 ? url : '')) === null || _a === void 0 ? void 0 : _a[1]; };
30
+ exports.orderIdFromDeepLink = orderIdFromDeepLink;
31
+ const STEP_INDEX = { SCHEDULED: 0, ON_THE_WAY: 1, IN_PROGRESS: 2, DONE: 3, CANCELLED: 0 };
32
+ // Titles only: the subtitle is built from the provider name (and the time while still scheduled).
33
+ const TITLES = {
34
+ en: {
35
+ SCHEDULED: 'Wash booked',
36
+ ON_THE_WAY: 'On the way',
37
+ IN_PROGRESS: 'Washing your car',
38
+ DONE: 'Wash complete',
39
+ CANCELLED: 'Order cancelled',
40
+ },
41
+ ar: {
42
+ SCHEDULED: 'تم حجز الغسيل',
43
+ ON_THE_WAY: 'في الطريق إليك',
44
+ IN_PROGRESS: 'جاري غسل سيارتك',
45
+ DONE: 'اكتمل الغسيل',
46
+ CANCELLED: 'تم إلغاء الطلب',
47
+ },
48
+ };
49
+ const SOON_TITLE = { en: 'Wash starting soon', ar: 'غسيلك على وشك البدء' };
50
+ exports.ORDER_ACTIVITY_TEXT = {
51
+ titles: TITLES,
52
+ stepPhrase: {
53
+ en: (n, label) => `Step ${n} of ${exports.ORDER_ACTIVITY_STEP_COUNT}: ${label}`,
54
+ ar: (n, label) => `الخطوة ${n} من ${exports.ORDER_ACTIVITY_STEP_COUNT}: ${label}`,
55
+ },
56
+ };
57
+ exports.ACTIVE_ORDER_STATUSES = [types_1.OrderStatus.PENDING, types_1.OrderStatus.CONFIRMED, types_1.OrderStatus.STARTED, types_1.OrderStatus.IN_PROGRESS];
58
+ // First-strong isolate / pop-directional-isolate: keeps a Latin provider name from reordering Arabic text.
59
+ const FSI = '⁨';
60
+ const PDI = '⁩';
61
+ function orderActivityLang(language) {
62
+ return language === 'ar' ? 'ar' : 'en';
63
+ }
64
+ function isOrderActive(status) {
65
+ return status !== undefined && exports.ACTIVE_ORDER_STATUSES.includes(status);
66
+ }
67
+ function orderActivityStep(status) {
68
+ switch (status) {
69
+ case types_1.OrderStatus.PENDING:
70
+ case types_1.OrderStatus.CONFIRMED:
71
+ return 'SCHEDULED';
72
+ case types_1.OrderStatus.STARTED:
73
+ return 'ON_THE_WAY';
74
+ case types_1.OrderStatus.IN_PROGRESS:
75
+ return 'IN_PROGRESS';
76
+ case types_1.OrderStatus.COMPLETED:
77
+ case types_1.OrderStatus.RATED:
78
+ return 'DONE';
79
+ case types_1.OrderStatus.CANCELLED:
80
+ case types_1.OrderStatus.REJECTED:
81
+ case types_1.OrderStatus.EXPIRED:
82
+ return 'CANCELLED';
83
+ default:
84
+ return null;
85
+ }
86
+ }
87
+ const isWithinLiveWindow = (orderDateMs, nowMs) => orderDateMs - nowMs <= exports.ORDER_LIVE_WINDOW_MS;
88
+ /** Whether an order deserves a Live Activity right now: on the way / washing, or accepted and due within the live window. */
89
+ function isOrderLive(status, orderDateMs, nowMs) {
90
+ const step = orderActivityStep(status);
91
+ if (step === 'ON_THE_WAY' || step === 'IN_PROGRESS')
92
+ return true;
93
+ // The lower bound matches the activity's stale date: a no-show booking stops claiming "starting soon" after 3 h.
94
+ return (status === types_1.OrderStatus.CONFIRMED &&
95
+ hasDate(orderDateMs) &&
96
+ isWithinLiveWindow(orderDateMs, nowMs) &&
97
+ nowMs - orderDateMs <= exports.ORDER_ACTIVITY_STALE_MS);
98
+ }
99
+ // Gregorian and Latin digits pinned: ar-SA otherwise defaults to Hijri on some devices, and every surface must agree.
100
+ const localeOf = (lang) => (lang === 'ar' ? 'ar-SA-u-ca-gregory-nu-latn' : 'en-US');
101
+ const hasDate = (ms) => Number.isFinite(ms) && ms > 0;
102
+ function formatOrderTime(orderTimeMs, lang) {
103
+ if (!hasDate(orderTimeMs))
104
+ return '';
105
+ return new Intl.DateTimeFormat(localeOf(lang), { timeZone: 'Asia/Riyadh', hour: 'numeric', minute: '2-digit' }).format(new Date(orderTimeMs));
106
+ }
107
+ /** What every surface (Live Activity, widget, Android notification) shows for an order. Never includes the driver's name. */
108
+ function buildOrderActivityContent(order, orderDateMs, lang, nowMs = Date.now()) {
109
+ var _a, _b, _c;
110
+ const step = orderActivityStep(order.orderStatus);
111
+ if (!step || !order.orderId)
112
+ return null;
113
+ const stepIndex = STEP_INDEX[step];
114
+ const isCancelled = step === 'CANCELLED';
115
+ const providerName = ((_a = order.providerName) === null || _a === void 0 ? void 0 : _a[lang]) || ((_b = order.providerName) === null || _b === void 0 ? void 0 : _b.en) || ((_c = order.providerName) === null || _c === void 0 ? void 0 : _c.ar) || '';
116
+ const orderTimeText = formatOrderTime(orderDateMs, lang);
117
+ const title = step === 'SCHEDULED' && isWithinLiveWindow(orderDateMs, nowMs) ? SOON_TITLE[lang] : TITLES[lang][step];
118
+ const shownProvider = providerName && lang === 'ar' ? `${FSI}${providerName}${PDI}` : providerName;
119
+ const subtitle = step === 'SCHEDULED' && shownProvider ? `${shownProvider} · ${orderTimeText}` : shownProvider;
120
+ return {
121
+ orderId: order.orderId,
122
+ step,
123
+ stepIndex,
124
+ progress: stepIndex / (exports.ORDER_ACTIVITY_STEP_COUNT - 1),
125
+ isTerminal: step === 'DONE' || isCancelled,
126
+ isRtl: lang === 'ar',
127
+ providerName,
128
+ orderTimeMs: orderDateMs,
129
+ orderTimeText,
130
+ title,
131
+ subtitle,
132
+ a11yLabel: [isCancelled ? title : exports.ORDER_ACTIVITY_TEXT.stepPhrase[lang](stepIndex + 1, title), subtitle].filter(Boolean).join('. '),
133
+ updatedAtMs: nowMs,
134
+ };
135
+ }
136
+ // "and N more bookings", with the Arabic dual / few / many forms.
137
+ const ARABIC_MORE = {
138
+ one: () => 'وحجز آخر',
139
+ two: () => 'وحجزان آخران',
140
+ few: (n) => `و${n} حجوزات أخرى`,
141
+ many: (n) => `و${n} حجزًا آخر`,
142
+ other: (n) => `و${n} حجز آخر`,
143
+ };
144
+ const arabicPlural = (n) => {
145
+ const mod100 = n % 100;
146
+ if (n === 1)
147
+ return 'one';
148
+ if (n === 2)
149
+ return 'two';
150
+ if (mod100 >= 3 && mod100 <= 10)
151
+ return 'few';
152
+ return mod100 >= 11 ? 'many' : 'other';
153
+ };
154
+ const arabicMore = (n) => ARABIC_MORE[arabicPlural(n)](n);
155
+ const WIDGET_TEXT = {
156
+ en: {
157
+ none: 'No active booking',
158
+ book: 'Book a wash',
159
+ upcoming: 'Your next wash',
160
+ done: 'Your car is now clean',
161
+ stale: 'Check your booking',
162
+ staleHint: 'Open Sahab for the latest status',
163
+ more: (n) => `+${n} more`,
164
+ },
165
+ ar: {
166
+ none: 'لا يوجد حجز نشط',
167
+ book: 'احجز غسلة',
168
+ upcoming: 'غسلتك القادمة',
169
+ done: 'سيارتك الآن نظيفة',
170
+ stale: 'تحقق من حجزك',
171
+ staleHint: 'افتح سحاب لمعرفة آخر حالة',
172
+ more: arabicMore,
173
+ },
174
+ };
175
+ const providerOf = (order, lang) => {
176
+ var _a, _b, _c;
177
+ const name = ((_a = order.providerName) === null || _a === void 0 ? void 0 : _a[lang]) || ((_b = order.providerName) === null || _b === void 0 ? void 0 : _b.en) || ((_c = order.providerName) === null || _c === void 0 ? void 0 : _c.ar) || '';
178
+ return name && lang === 'ar' ? `${FSI}${name}${PDI}` : name;
179
+ };
180
+ // Today shows just the time; any other day adds the weekday and date so "5:30 PM" is never ambiguous.
181
+ function formatOrderWhen(orderTimeMs, lang, nowMs) {
182
+ if (!hasDate(orderTimeMs))
183
+ return '';
184
+ const day = (ms) => new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Riyadh' }).format(new Date(ms));
185
+ if (day(orderTimeMs) === day(nowMs))
186
+ return formatOrderTime(orderTimeMs, lang);
187
+ return new Intl.DateTimeFormat(localeOf(lang), {
188
+ timeZone: 'Asia/Riyadh',
189
+ weekday: 'short',
190
+ day: 'numeric',
191
+ month: 'short',
192
+ hour: 'numeric',
193
+ minute: '2-digit',
194
+ }).format(new Date(orderTimeMs));
195
+ }
196
+ const LIVE_RANK = { IN_PROGRESS: 2, ON_THE_WAY: 1, SCHEDULED: 0 };
197
+ /** The one order the widget (and a single-slot surface) should talk about: live orders first, most advanced first, then the soonest booking. */
198
+ function pickPrimaryOrder(orders, nowMs) {
199
+ const live = orders.filter((o) => isOrderLive(o.orderStatus, o.orderDateMs, nowMs));
200
+ const stepRank = (o) => { var _a, _b; return (_b = LIVE_RANK[(_a = orderActivityStep(o.orderStatus)) !== null && _a !== void 0 ? _a : 'SCHEDULED']) !== null && _b !== void 0 ? _b : 0; };
201
+ const pool = live.length ? live : orders;
202
+ return pool.reduce((best, o) => {
203
+ if (!best)
204
+ return o;
205
+ if (live.length)
206
+ return stepRank(o) > stepRank(best) || (stepRank(o) === stepRank(best) && o.orderDateMs < best.orderDateMs) ? o : best;
207
+ return o.orderDateMs < best.orderDateMs ? o : best;
208
+ }, null);
209
+ }
210
+ // A status the app saw at `syncedAtMs` can move on without the widget hearing about it (only pushes reach the closed app),
211
+ // so on the way / washing is trusted for a while after the sync and a booking for a while after its time.
212
+ const widgetFreshUntil = (o, syncedAtMs) => {
213
+ const step = orderActivityStep(o.orderStatus);
214
+ const from = step === 'ON_THE_WAY' || step === 'IN_PROGRESS' ? syncedAtMs : Math.max(syncedAtMs, hasDate(o.orderDateMs) ? o.orderDateMs : 0);
215
+ return from + exports.ORDER_WIDGET_FRESH_MS;
216
+ };
217
+ /**
218
+ * What the home-screen widget says at `nowMs`: the primary active order (upcoming until it is live), "your car is clean"
219
+ * for a while after the last finished order, otherwise an invitation to book.
220
+ */
221
+ function buildOrderWidgetContent(active, lastFinished, lang, nowMs = Date.now(), syncedAtMs = nowMs) {
222
+ var _a, _b, _c, _d;
223
+ const text = WIDGET_TEXT[lang];
224
+ const primary = pickPrimaryOrder(active, nowMs);
225
+ const finish = (state, orderId, title, subtitle, stepIndex, moreText = '') => ({
226
+ state,
227
+ orderId,
228
+ title,
229
+ subtitle,
230
+ moreText,
231
+ stepIndex,
232
+ progress: stepIndex / (exports.ORDER_ACTIVITY_STEP_COUNT - 1),
233
+ a11yLabel: [title, subtitle, moreText].filter(Boolean).join('. '),
234
+ });
235
+ if (primary) {
236
+ const moreText = active.length > 1 ? text.more(active.length - 1) : '';
237
+ if (nowMs > widgetFreshUntil(primary, syncedAtMs)) {
238
+ const provider = providerOf(primary, lang);
239
+ return finish('STALE', (_a = primary.orderId) !== null && _a !== void 0 ? _a : '', text.stale, provider ? `${provider} · ${text.staleHint}` : text.staleHint, 0, moreText);
240
+ }
241
+ if (isOrderLive(primary.orderStatus, primary.orderDateMs, nowMs)) {
242
+ const c = buildOrderActivityContent({ orderId: primary.orderId, orderStatus: primary.orderStatus, providerName: primary.providerName }, primary.orderDateMs, lang, nowMs);
243
+ if (c)
244
+ return finish('LIVE', c.orderId, c.title, c.subtitle, c.stepIndex, moreText);
245
+ }
246
+ const provider = providerOf(primary, lang);
247
+ const when = formatOrderWhen(primary.orderDateMs, lang, nowMs);
248
+ return finish('UPCOMING', (_b = primary.orderId) !== null && _b !== void 0 ? _b : '', text.upcoming, provider ? `${provider} · ${when}` : when, 0, moreText);
249
+ }
250
+ if (lastFinished && nowMs - ((_c = lastFinished.finishMs) !== null && _c !== void 0 ? _c : lastFinished.orderDateMs) <= exports.ORDER_DONE_VISIBLE_MS) {
251
+ return finish('DONE', (_d = lastFinished.orderId) !== null && _d !== void 0 ? _d : '', text.done, providerOf(lastFinished, lang), exports.ORDER_ACTIVITY_STEP_COUNT - 1);
252
+ }
253
+ return finish('NONE', '', text.none, text.book, 0);
254
+ }
255
+ const RIYADH_OFFSET_MS = 3 * 60 * 60 * 1000;
256
+ const DAY_MS = 24 * 60 * 60 * 1000;
257
+ const nextRiyadhMidnight = (nowMs) => Math.floor((nowMs + RIYADH_OFFSET_MS) / DAY_MS) * DAY_MS + DAY_MS - RIYADH_OFFSET_MS;
258
+ /** Future moments at which the widget's text changes on its own (a booking turning live, "clean" expiring, a status going stale), soonest first. */
259
+ function orderWidgetChangeTimes(active, lastFinished, nowMs = Date.now()) {
260
+ var _a;
261
+ const times = active.filter((o) => orderActivityStep(o.orderStatus) === 'SCHEDULED').map((o) => o.orderDateMs - exports.ORDER_LIVE_WINDOW_MS);
262
+ if (lastFinished)
263
+ times.push(((_a = lastFinished.finishMs) !== null && _a !== void 0 ? _a : lastFinished.orderDateMs) + exports.ORDER_DONE_VISIBLE_MS + 1);
264
+ // "Sat 9:00 AM" becomes just "9:00 AM" once the booked day arrives, so the next Riyadh midnight is a change too.
265
+ if (active.length)
266
+ times.push(nextRiyadhMidnight(nowMs));
267
+ active.forEach((o) => times.push(widgetFreshUntil(o, nowMs) + 1));
268
+ return [...new Set(times)].filter((t) => t > nowMs).sort((a, b) => a - b);
269
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "sahab-utils",
3
- "version": "1.7.6",
3
+ "version": "1.7.7",
4
4
  "description": "This package contains types and utils functions",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "deploy": "npm run build && npm publish",
10
- "test": "npm run build && node tests/loyalty.test.js && node tests/gifts.test.js && node tests/cashback.test.js && node tests/utils.test.js && node tests/wheel.test.js && node tests/calcs.test.js"
10
+ "test": "npm run build && node tests/loyalty.test.js && node tests/gifts.test.js && node tests/cashback.test.js && node tests/utils.test.js && node tests/wheel.test.js && node tests/calcs.test.js && node tests/orderActivity.test.js"
11
11
  },
12
12
  "repository": {
13
13
  "type": "git",