@becollective/utils 1.10.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/.semaphore/utils.yml +1 -1
  2. package/index.ts +7 -0
  3. package/lib/FeatureFlag.d.ts +12 -0
  4. package/lib/FeatureFlag.js +51 -0
  5. package/lib/constants/allowedHtmlTags.d.ts +7 -0
  6. package/lib/constants/allowedHtmlTags.js +8 -0
  7. package/lib/constants/avatarColours.d.ts +35 -0
  8. package/lib/constants/avatarColours.js +36 -0
  9. package/lib/constants/countries.d.ts +253 -0
  10. package/lib/constants/countries.js +254 -0
  11. package/lib/constants/currencies.d.ts +29 -0
  12. package/lib/constants/currencies.js +36 -0
  13. package/lib/constants/customFields.d.ts +11 -0
  14. package/lib/constants/customFields.js +12 -0
  15. package/lib/constants/distancesUnits.d.ts +11 -0
  16. package/lib/constants/distancesUnits.js +13 -0
  17. package/lib/constants/groups.d.ts +13 -0
  18. package/lib/constants/groups.js +85 -0
  19. package/lib/constants/insuranceStatuses.d.ts +15 -0
  20. package/lib/constants/insuranceStatuses.js +7 -0
  21. package/lib/constants/insuranceTypes.d.ts +35 -0
  22. package/lib/constants/insuranceTypes.js +36 -0
  23. package/lib/constants/languages.d.ts +2 -0
  24. package/lib/constants/languages.js +194 -0
  25. package/lib/constants/locales.d.ts +119 -0
  26. package/lib/constants/locales.js +191 -0
  27. package/lib/constants/notifications.d.ts +47 -0
  28. package/lib/constants/notifications.js +31 -0
  29. package/lib/constants/opportunities.d.ts +72 -0
  30. package/lib/constants/opportunities.js +82 -0
  31. package/lib/constants/qualificationTypes.d.ts +4 -0
  32. package/lib/constants/qualificationTypes.js +18 -0
  33. package/lib/constants/rules.d.ts +13 -0
  34. package/lib/constants/rules.js +21 -0
  35. package/lib/constants/subscriptions.d.ts +9 -0
  36. package/lib/constants/subscriptions.js +10 -0
  37. package/lib/constants/surveys.d.ts +16 -0
  38. package/lib/constants/surveys.js +17 -0
  39. package/lib/constants.d.ts +1 -0
  40. package/lib/constants.js +76 -0
  41. package/lib/date-time.d.ts +28 -0
  42. package/lib/date-time.js +79 -0
  43. package/lib/forms.d.ts +1 -0
  44. package/lib/forms.js +728 -0
  45. package/lib/locality.d.ts +5 -0
  46. package/lib/locality.js +15 -0
  47. package/lib/money.d.ts +7 -0
  48. package/lib/money.js +32 -0
  49. package/lib/opportunity.d.ts +1 -0
  50. package/lib/opportunity.js +11 -0
  51. package/lib/opportunityUser.d.ts +8 -0
  52. package/lib/opportunityUser.js +29 -0
  53. package/lib/password.d.ts +8 -0
  54. package/lib/password.js +43 -0
  55. package/lib/rrule/date.d.ts +4 -0
  56. package/lib/rrule/date.js +30 -0
  57. package/lib/rrule/index.d.ts +2 -0
  58. package/lib/rrule/index.js +18 -0
  59. package/lib/rrule/parsing.d.ts +6 -0
  60. package/lib/rrule/parsing.js +130 -0
  61. package/lib/rrule/parsing.test.d.ts +1 -0
  62. package/lib/rrule/parsing.test.js +11 -0
  63. package/lib/rrule.d.ts +49 -0
  64. package/lib/rrule.js +303 -0
  65. package/lib/src/FeatureFlag.d.ts +12 -0
  66. package/lib/src/FeatureFlag.js +51 -0
  67. package/lib/src/date-time.d.ts +28 -0
  68. package/lib/src/date-time.js +79 -0
  69. package/lib/src/forms.d.ts +1 -0
  70. package/lib/src/forms.js +728 -0
  71. package/lib/src/locality.d.ts +5 -0
  72. package/lib/src/locality.js +15 -0
  73. package/lib/src/money.d.ts +7 -0
  74. package/lib/src/money.js +32 -0
  75. package/lib/src/opportunity.d.ts +1 -0
  76. package/lib/src/opportunity.js +11 -0
  77. package/lib/src/opportunityUser.d.ts +8 -0
  78. package/lib/src/opportunityUser.js +29 -0
  79. package/lib/src/password.d.ts +8 -0
  80. package/lib/src/password.js +43 -0
  81. package/package.json +15 -29
  82. package/src/FeatureFlag.ts +26 -10
  83. package/src/constants/allowedHtmlTags.ts +6 -0
  84. package/src/constants/avatarColours.ts +34 -0
  85. package/src/constants/countries.ts +252 -0
  86. package/src/constants/currencies.ts +33 -0
  87. package/src/constants/customFields.ts +10 -0
  88. package/src/constants/distancesUnits.ts +11 -0
  89. package/src/constants/groups.ts +83 -0
  90. package/src/constants/insuranceStatuses.ts +5 -0
  91. package/src/constants/insuranceTypes.ts +34 -0
  92. package/src/constants/languages.ts +192 -0
  93. package/src/constants/locales.ts +189 -0
  94. package/src/constants/notifications.ts +30 -0
  95. package/src/constants/opportunities.ts +84 -0
  96. package/src/constants/qualificationTypes.ts +17 -0
  97. package/src/constants/rules.ts +22 -0
  98. package/src/constants/subscriptions.ts +8 -0
  99. package/src/constants/surveys.ts +15 -0
  100. package/src/constants.ts +40 -0
  101. package/src/{date-time.js → date-time.ts} +18 -11
  102. package/src/forms.ts +807 -0
  103. package/src/{opportunityUser.js → opportunityUser.ts} +5 -1
  104. package/src/{password.js → password.ts} +2 -0
  105. package/src/rrule/date.ts +25 -0
  106. package/src/rrule/index.ts +2 -0
  107. package/src/rrule/parsing.ts +136 -0
  108. package/src/rrule.ts +367 -0
  109. package/tests/{date-time.test.js → date-time.test.ts} +48 -46
  110. package/tests/featureFlag.test.ts +36 -39
  111. package/tests/forms.test-data.json +43 -0
  112. package/tests/forms.test-schema.json +144 -0
  113. package/tests/forms.test-uischema.json +117 -0
  114. package/tests/forms.test.ts +674 -0
  115. package/tests/{money.test.js → money.test.ts} +2 -3
  116. package/tests/{password.test.js → password.test.ts} +7 -6
  117. package/tests/rrule/parsing.test.ts +12 -0
  118. package/tests/rrule.test.ts +625 -0
  119. package/tsconfig.json +15 -12
  120. package/bundle.js +0 -270
  121. package/index.js +0 -25
  122. package/rollup.config.js +0 -25
  123. /package/src/{locality.js → locality.ts} +0 -0
  124. /package/src/{money.js → money.ts} +0 -0
  125. /package/src/{opportunity.js → opportunity.ts} +0 -0
package/lib/rrule.js ADDED
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.recurrencesBetweenV2 = exports.recurrencesBetween = exports.recurrenceToTextInTimezone = exports.recurrenceToText = exports.isValidRrule = exports.buildBcRruleString = exports.validateRruleConfigV2 = exports.validateRruleConfig = exports.parse = exports.toRruleStr = exports.toRruleObj = void 0;
4
+ const rrule_1 = require("rrule");
5
+ const luxon_1 = require("luxon");
6
+ const index_1 = require("./rrule/index");
7
+ Object.defineProperty(exports, "toRruleObj", { enumerable: true, get: function () { return index_1.toRruleObj; } });
8
+ Object.defineProperty(exports, "toRruleStr", { enumerable: true, get: function () { return index_1.toRruleStr; } });
9
+ const date_1 = require("./rrule/date");
10
+ const isEmpty = (obj) => {
11
+ if (Array.isArray(obj) || typeof obj === 'string') {
12
+ return obj.length === 0;
13
+ }
14
+ return !obj;
15
+ };
16
+ const omit = (obj, keys) => {
17
+ return Object.fromEntries(Object.entries(obj).filter(([key]) => !keys.includes(key)));
18
+ };
19
+ const omitBy = (obj, predicate) => {
20
+ return Object.fromEntries(Object.entries(obj).filter(([key, value]) => !predicate(value, key)));
21
+ };
22
+ const logError = (...args) => {
23
+ const logger = console.error || console.log;
24
+ logger(...args);
25
+ };
26
+ // Customised recurrenceRule parser for BeCollective
27
+ // We store DURATION in our rules, which are not supported by rrule
28
+ // We need to return this data and also remove it from our rrule configuration before parsing it
29
+ const parse = function parse(str) {
30
+ const icalObj = (0, index_1.toRruleObj)(str);
31
+ const duration = (0, index_1.getDuration)(icalObj);
32
+ // TODO delete unused?
33
+ // const until = icalObj.UNTIL;
34
+ const dtend = icalObj.DTEND;
35
+ const nondurationItems = omit(icalObj, ['DURATION', 'DTEND']);
36
+ const rrule = (0, rrule_1.rrulestr)((0, index_1.toRruleStr)(omitBy(nondurationItems, isEmpty)));
37
+ return { duration, durationString: icalObj.DURATION, rrule, dtend };
38
+ };
39
+ exports.parse = parse;
40
+ const validateRruleConfig = (obj) => {
41
+ const config = obj || {};
42
+ const REQUIRED_FIELDS = [
43
+ { field: 'DTSTART', name: 'Start time' },
44
+ { field: 'RRULE:FREQ', name: 'Frequency (Month/Weekly)' },
45
+ { field: 'INTERVAL', name: 'Interval' },
46
+ { field: 'UNTIL', name: 'Until' },
47
+ { field: 'Duration', name: 'Duration' },
48
+ ];
49
+ const errors = [];
50
+ REQUIRED_FIELDS.forEach((fieldConfig) => {
51
+ if (!config[fieldConfig.field]) {
52
+ errors.push(`Please enter a ${fieldConfig.name}`);
53
+ }
54
+ });
55
+ if (config.DTSTART && config.UNTIL && luxon_1.DateTime.fromISO(config.DTSTART) > luxon_1.DateTime.fromISO(config.UNTIL)) {
56
+ errors.push('DTSTART must be earlier than or equal to UNTIL');
57
+ }
58
+ return errors;
59
+ };
60
+ exports.validateRruleConfig = validateRruleConfig;
61
+ const validateRruleConfigV2 = (bcRruleInstance) => {
62
+ const rruleInstance = bcRruleInstance.rrule;
63
+ const REQUIRED_RRULE_FIELDS = [
64
+ { field: 'dtstart', name: 'Start time' },
65
+ { field: 'interval', name: 'Interval' },
66
+ { field: 'until', name: 'Until' },
67
+ ];
68
+ const REQUIRED_BC_FIELDS = [{ field: 'duration', name: 'Duration' }];
69
+ const REQUIRED_BC_FREQ = {
70
+ field: 'freq',
71
+ name: 'Frequency (Month/Weekly/Yearly)',
72
+ values: [rrule_1.RRule.YEARLY, rrule_1.RRule.WEEKLY, rrule_1.RRule.MONTHLY],
73
+ };
74
+ const errors = [];
75
+ REQUIRED_RRULE_FIELDS.forEach((fieldConfig) => {
76
+ if (!rruleInstance.options[fieldConfig.field]) {
77
+ errors.push(`Please enter a ${fieldConfig.name}`);
78
+ }
79
+ });
80
+ if (!REQUIRED_BC_FREQ.values.includes(rruleInstance.options[REQUIRED_BC_FREQ.field])) {
81
+ errors.push(`Please enter a ${REQUIRED_BC_FREQ.name}`);
82
+ }
83
+ REQUIRED_BC_FIELDS.forEach((fieldConfig) => {
84
+ if (!bcRruleInstance[fieldConfig.field]) {
85
+ errors.push(`Please enter a ${fieldConfig.name}`);
86
+ }
87
+ });
88
+ if (rruleInstance.options.dtstart &&
89
+ rruleInstance.options.until &&
90
+ luxon_1.DateTime.fromISO(rruleInstance.options.dtstart) > luxon_1.DateTime.fromISO(rruleInstance.options.until)) {
91
+ errors.push('DTSTART must be earlier than or equal to UNTIL');
92
+ }
93
+ return errors;
94
+ };
95
+ exports.validateRruleConfigV2 = validateRruleConfigV2;
96
+ /**
97
+ * a helper function that put offical rrule and becollective rrule fields together
98
+ * @param {Object} rruleInstance an RRule instance.
99
+ * @param {*} duration non rrule standard duration field.
100
+ */
101
+ const buildBcRruleString = (rruleInstance, duration = '0H') => {
102
+ let rruleString = rruleInstance.toString();
103
+ if (duration) {
104
+ rruleString = `${rruleString};DURATION=${duration}`;
105
+ }
106
+ const bcRrule = (0, exports.parse)(rruleString);
107
+ const errors = (0, exports.validateRruleConfigV2)(bcRrule);
108
+ if (errors.length === 0) {
109
+ return rruleString;
110
+ }
111
+ else {
112
+ throw new Error(JSON.stringify(errors));
113
+ }
114
+ };
115
+ exports.buildBcRruleString = buildBcRruleString;
116
+ const isValidRrule = (obj) => (0, exports.validateRruleConfig)(obj).length === 0;
117
+ exports.isValidRrule = isValidRrule;
118
+ /* === TIMEZONE ISSUES WITH RRULE ===
119
+
120
+ The rrule library does not store, use or account for timezone. The timezone information is lost and this leads to bugs displaying times and with
121
+ generating recurrences. This happens because rrule determines the start time by interpreting DTSTART in the timezone of the runtime
122
+ environment. This is fine when the runtime and an opportunity share the same timezone. However, inconsistencies occur when they
123
+ differ, and daylight savings is introduced.
124
+
125
+ INCONSISTENT START & END TIMES
126
+ -------------------------------------------
127
+ We want to specify a start and end time (5pm - 7pm)
128
+ Technically we need to express this start and end time as UTC timestamps (2017-07-03 T07:00 - 2017-07-03 T09:00)
129
+
130
+ This presents a problem when we interpret rrule strings to display and generate recurrences.
131
+
132
+ Consider that we want a recurrenceRule to apply for ‘Asia/Taipei (+0800 UTC)’ and start at 1500 hours, we would have the following
133
+ UTC timestamp:
134
+
135
+ 2017-07-03 T07:00
136
+
137
+ If our runtime is using Australia/Melbourne time, rrule would determine the start time of each recurrence to be at T17:00
138
+
139
+ In Asia/Taipei time, that is T15:00 which is correct
140
+
141
+ However, fast forward to October when daylight savings kicks in for Australia/Melbourne, and suddenly T17:00 translates to T14:00
142
+ in Asia/Taipei.
143
+
144
+ As illustrated above, we need to workaround the rrule library by incorporating timezone information in the way we display and
145
+ generate recurrences.
146
+
147
+ OFF BY ONE DATES
148
+ ———————————
149
+ This bug occurs because the start time is determined using runtime’s timezone. When finding recurrences between a set of dates,
150
+ the correct dates and times are found for the runtime’s local timezone. But when the client uses these dates, the timezone
151
+ conversion could cause the date to change.
152
+
153
+ Example generated start time: 2017-10-10 T20:30 UTC
154
+ If a Melbourne client interprets this timestamp, it would translate to: 2017-10-11 T:07:30 AEST
155
+
156
+ So time is correct but date is WRONG
157
+
158
+
159
+ --- SUMMARY & TLDR ---;
160
+ The rrule spec causes us to lose timezone information and leads to inconsistent interpretations of DTSTART and UNTIL times. This is
161
+ further complicated by the fact that a rule is parsed and interpreted based on code runtime’s timezone, which means we are
162
+ potentially shuffling and converting things between 3 timezones: UTC -> runtime -> opportunity. Hence we must implement several
163
+ “tricks” so that we ignore the time portion from dates and the date portion from times.
164
+
165
+ ***********************************************************/
166
+ // Return correct and consistent human readable values for a recurrenceRule
167
+ // We need to ignore DTSTART date component and use only the time component so start time is updated according to daylight
168
+ // savings
169
+ const recurrenceToText = (recurrenceRule, timezone, isFlexible = false) => {
170
+ const TIME_FORMAT = 'h:mm a';
171
+ try {
172
+ const { duration, rrule: rule } = (0, exports.parse)(recurrenceRule);
173
+ const dtstartToTimezone = luxon_1.DateTime.fromJSDate(rule.origOptions.dtstart).setZone(timezone);
174
+ const untilToTimezone = luxon_1.DateTime.fromJSDate(rule.origOptions.until).setZone(timezone);
175
+ const rrule = new rrule_1.RRule({
176
+ ...rule.origOptions,
177
+ dtstart: luxon_1.DateTime.utc(dtstartToTimezone.year, dtstartToTimezone.month, dtstartToTimezone.day, dtstartToTimezone.hour, dtstartToTimezone.minute, dtstartToTimezone.second)
178
+ .toJSDate(),
179
+ tzid: timezone,
180
+ until: luxon_1.DateTime.utc(untilToTimezone.year, untilToTimezone.month, untilToTimezone.day, untilToTimezone.hour, untilToTimezone.minute, untilToTimezone.second)
181
+ .toJSDate(),
182
+ });
183
+ if (isFlexible) {
184
+ const hours = duration / 1000 / 60 / 60;
185
+ let ruleText = rrule.toText();
186
+ if (rule.origOptions.count === 1) {
187
+ // When it is not a repeating rule
188
+ ruleText = ruleText.replace(/every year until/i, 'in total by');
189
+ ruleText = ruleText.replace(/ \(~ approximate\)/i, '');
190
+ }
191
+ return {
192
+ text: `${hours} hours ${ruleText}`,
193
+ timezone,
194
+ };
195
+ }
196
+ // Get today's start time in the recurrenceRule's timezone, then convert that timestamp to our local time
197
+ const end = dtstartToTimezone.plus(duration);
198
+ return {
199
+ text: rrule.toText().charAt(0).toUpperCase() + rrule.toText().slice(1),
200
+ time: `${dtstartToTimezone.toFormat(TIME_FORMAT)} to ${end.toFormat(TIME_FORMAT)}`,
201
+ timezone,
202
+ };
203
+ }
204
+ catch (e) {
205
+ return { text: '', time: '', timezone };
206
+ }
207
+ };
208
+ exports.recurrenceToText = recurrenceToText;
209
+ // Return the text based on the specified timezone
210
+ // This method doesn't convert rrule to it's owner opportunity's timezone
211
+ const recurrenceToTextInTimezone = (recurrenceRule, timezone) => {
212
+ const TIME_FORMAT = 'h:mm A';
213
+ const localTimezone = timezone || 'UTC';
214
+ try {
215
+ const { duration, rrule: rule } = (0, exports.parse)(recurrenceRule);
216
+ const start = luxon_1.DateTime.fromISO((0, index_1.getStartTime)(recurrenceRule, localTimezone), 'HHmm').setZone(localTimezone);
217
+ const end = luxon_1.DateTime.fromISO((0, index_1.getEndTime)(start.toJSDate(), duration)).setZone(localTimezone);
218
+ return {
219
+ text: rule.toText().charAt(0).toUpperCase() + rule.toText().slice(1),
220
+ time: `${start.toFormat(TIME_FORMAT)} to ${end.toFormat(TIME_FORMAT)}`,
221
+ localTimezone,
222
+ };
223
+ }
224
+ catch (e) {
225
+ return { text: '', time: '', localTimezone };
226
+ }
227
+ };
228
+ exports.recurrenceToTextInTimezone = recurrenceToTextInTimezone;
229
+ const recurrencesBetween = (fromDate, toDate, recurrenceRule, timezone) => {
230
+ try {
231
+ // We need to deal with time and dates separately because timestamps will be converted and lead to errors
232
+ const time = (0, index_1.getStartTime)(recurrenceRule, timezone);
233
+ const { duration, rrule: rule } = (0, exports.parse)(recurrenceRule);
234
+ const recurrences = rule.between(fromDate, toDate);
235
+ return recurrences.map((start) => {
236
+ // eslint-disable-next-line no-undef
237
+ const fixed = (0, date_1.transpose)(start, time, timezone);
238
+ return {
239
+ from: fixed,
240
+ to: (0, index_1.getEndTime)(fixed, duration),
241
+ };
242
+ });
243
+ }
244
+ catch (e) {
245
+ logError('Error while generating recurrences between', fromDate.toString(), 'to', toDate.toString());
246
+ logError('RRULE:', recurrenceRule, 'TIMEZONE:', timezone);
247
+ logError('***\n\n', e, '\n\n***');
248
+ return [];
249
+ }
250
+ };
251
+ exports.recurrencesBetween = recurrencesBetween;
252
+ /**
253
+ *
254
+ * @param {*} rrule
255
+ * @param {*} timezone
256
+ * @param {*} from
257
+ * @param {*} isFlexible
258
+ * @returns an array of date in true utc (DST offset is considered)
259
+ */
260
+ const recurrencesBetweenV2 = (rrule, timezone, from = new Date(), isFlexible = false) => {
261
+ const { duration, rrule: rule } = (0, exports.parse)(rrule);
262
+ const dtstartToTimezone = luxon_1.DateTime.fromJSDate(rule.origOptions.dtstart).setZone(timezone);
263
+ const untilToTimezone = luxon_1.DateTime.fromJSDate(rule.origOptions.until).setZone(timezone);
264
+ const rruleToConvert = new rrule_1.RRule({
265
+ ...rule.origOptions,
266
+ dtstart: luxon_1.DateTime.utc(dtstartToTimezone.year, dtstartToTimezone.month, dtstartToTimezone.day, dtstartToTimezone.hour, dtstartToTimezone.minute, dtstartToTimezone.second).toJSDate(),
267
+ tzid: timezone,
268
+ until: luxon_1.DateTime.utc(untilToTimezone.year, untilToTimezone.month, untilToTimezone.day, untilToTimezone.hour, untilToTimezone.minute, untilToTimezone.second).toJSDate(),
269
+ });
270
+ const occurrences = rruleToConvert
271
+ .all()
272
+ .map((date) => luxon_1.DateTime.fromJSDate(date).toUTC().setZone('local', { keepLocalTime: true }));
273
+ if (!occurrences.length) {
274
+ return [];
275
+ }
276
+ const output = [];
277
+ if (isFlexible) {
278
+ let i = 0;
279
+ occurrences.map(o => {
280
+ output.push({ from: o.toJSDate(), to: o.toJSDate() });
281
+ // update "to" field
282
+ if (i > 0) {
283
+ output[i - 1].to = o.toUTC().minus({ seconds: 1 }).toJSDate();
284
+ }
285
+ if (i === occurrences.length - 1) {
286
+ output[i].to = luxon_1.DateTime.fromJSDate(rule.options.until).toUTC().minus({ seconds: 1 }).toJSDate();
287
+ }
288
+ i++;
289
+ });
290
+ return output.filter(((o) => from ? luxon_1.DateTime.fromJSDate(o.to) > luxon_1.DateTime.fromJSDate(from) : luxon_1.DateTime.fromJSDate(o.to) < luxon_1.DateTime.fromJSDate(rule.options.until)));
291
+ }
292
+ else {
293
+ const output = [];
294
+ occurrences.map(o => {
295
+ output.push({
296
+ from: o.toJSDate(),
297
+ to: o.toUTC().plus(duration).toJSDate(),
298
+ });
299
+ });
300
+ return output.filter(((o) => from ? luxon_1.DateTime.fromJSDate(o.to) > luxon_1.DateTime.fromJSDate(from) : luxon_1.DateTime.fromJSDate(o.to) < luxon_1.DateTime.fromJSDate(rule.options.until)));
301
+ }
302
+ };
303
+ exports.recurrencesBetweenV2 = recurrencesBetweenV2;
@@ -0,0 +1,12 @@
1
+ export declare class FeatureFlag {
2
+ ttl: number;
3
+ expiry: number;
4
+ constructor(ttl?: any);
5
+ isFeatureActive(feature: string, options: {
6
+ env: string;
7
+ region?: string;
8
+ }): Promise<boolean>;
9
+ clearCache(): void;
10
+ setCacheTtl(ttl: number): void;
11
+ }
12
+ export default FeatureFlag;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FeatureFlag = void 0;
4
+ const cache = {
5
+ store: new Map(),
6
+ memoize: async (feature, options) => {
7
+ const stringified = JSON.stringify({ feature, options });
8
+ const response = cache.store.get(stringified);
9
+ if (response) {
10
+ return JSON.parse(response);
11
+ }
12
+ const fetchResponse = await fetchFeatureActive(feature, options);
13
+ cache.store.set(stringified, JSON.stringify(fetchResponse));
14
+ return fetchResponse;
15
+ },
16
+ };
17
+ const fetchFeatureActive = async (feature, options) => {
18
+ const getFlagByFeatureUrl = `https://lb-central.becollective.com/api/v2/feature-flags/${feature}`;
19
+ let isFeatureActive = false;
20
+ const featureFlags = await fetch(getFlagByFeatureUrl, { method: 'get' });
21
+ const json = await featureFlags.json();
22
+ const environments = json.Environments;
23
+ if (environments) {
24
+ isFeatureActive = environments.includes(options.env);
25
+ }
26
+ return isFeatureActive;
27
+ };
28
+ class FeatureFlag {
29
+ constructor(ttl) {
30
+ this.ttl = ttl || 1000 * 60 * 5; // default to 5 minutes
31
+ this.expiry = Date.now() + ttl;
32
+ }
33
+ async isFeatureActive(feature, options) {
34
+ // Lazy expiration upon function call
35
+ const now = Date.now();
36
+ if (now >= this.expiry) {
37
+ cache.store.clear();
38
+ this.expiry = now + this.ttl;
39
+ }
40
+ return cache.memoize(feature, options);
41
+ }
42
+ clearCache() {
43
+ cache.store.clear();
44
+ }
45
+ setCacheTtl(ttl) {
46
+ this.ttl = ttl;
47
+ this.expiry = Date.now() + ttl;
48
+ }
49
+ }
50
+ exports.FeatureFlag = FeatureFlag;
51
+ exports.default = FeatureFlag;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Get current age based on the given birth date
3
+ * @param {string} dateOfBirth
4
+ * @param {string} unit the measurement of the the difference
5
+ * The supported measurements are:
6
+ * years, months, weeks, days, hours, minutes, and seconds.
7
+ * @returns {number} age
8
+ */
9
+ export declare const getAge: (dateOfBirth: any, unit?: string) => number;
10
+ /**
11
+ * Check if a user requires parent(guardian) consent
12
+ * i.e. Is under the age of 16 years, based on GDPR
13
+ * https://gdpr-info.eu/art-8-gdpr/
14
+ * @param {string} dateOfBirth moment.js compatible date time
15
+ * @returns {boolean}
16
+ * NOTE:
17
+ * We used to not allow child (under 16) to register,
18
+ * and birthdate is not a required field for ghost user
19
+ * So an existing user could have no birthdate, we assume these users are adults
20
+ * TODO:
21
+ * Remove above logic after we migrated all legacy
22
+ * data to have a consistent birthdate property.
23
+ * Then we should throw error if birthdate is not valid
24
+ */
25
+ export declare const isUnderSixteen: (dateOfBirth: any) => boolean;
26
+ export declare const isUnderAge: (dateOfBirth: any, limit: any, maxAcknowledgedAge?: number | null) => boolean;
27
+ export declare const datesByThemselves: (a: any, b: any) => number;
28
+ export declare const getShiftText: (from: any, to: any, timezone: any) => string;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getShiftText = exports.datesByThemselves = exports.isUnderAge = exports.isUnderSixteen = exports.getAge = void 0;
4
+ const luxon_1 = require("luxon");
5
+ /**
6
+ * Get current age based on the given birth date
7
+ * @param {string} dateOfBirth
8
+ * @param {string} unit the measurement of the the difference
9
+ * The supported measurements are:
10
+ * years, months, weeks, days, hours, minutes, and seconds.
11
+ * @returns {number} age
12
+ */
13
+ const getAge = (dateOfBirth, unit = 'years') => {
14
+ if (!dateOfBirth) {
15
+ throw new Error('Invalid date');
16
+ }
17
+ const dob = luxon_1.DateTime.fromJSDate(new Date(dateOfBirth));
18
+ if (!dob.isValid) {
19
+ throw new Error('Invalid date');
20
+ }
21
+ const age = dob.diffNow(unit);
22
+ const ageUnit = -age.as(unit);
23
+ // Note: luxon returns scientific notation, ie 6.976154236428209e-9
24
+ return Number(ageUnit.toString().split('.').shift());
25
+ };
26
+ exports.getAge = getAge;
27
+ /**
28
+ * Check if a user requires parent(guardian) consent
29
+ * i.e. Is under the age of 16 years, based on GDPR
30
+ * https://gdpr-info.eu/art-8-gdpr/
31
+ * @param {string} dateOfBirth moment.js compatible date time
32
+ * @returns {boolean}
33
+ * NOTE:
34
+ * We used to not allow child (under 16) to register,
35
+ * and birthdate is not a required field for ghost user
36
+ * So an existing user could have no birthdate, we assume these users are adults
37
+ * TODO:
38
+ * Remove above logic after we migrated all legacy
39
+ * data to have a consistent birthdate property.
40
+ * Then we should throw error if birthdate is not valid
41
+ */
42
+ const isUnderSixteen = (dateOfBirth) => {
43
+ if (!dateOfBirth || !luxon_1.DateTime.fromJSDate(new Date(dateOfBirth)).isValid) {
44
+ return false;
45
+ }
46
+ const age = (0, exports.getAge)(dateOfBirth);
47
+ return isNaN(age) ? false : age < 16;
48
+ };
49
+ exports.isUnderSixteen = isUnderSixteen;
50
+ const isUnderAge = (dateOfBirth, limit, maxAcknowledgedAge = null) => {
51
+ if ((!dateOfBirth || !luxon_1.DateTime.fromJSDate(new Date(dateOfBirth)).isValid) && !maxAcknowledgedAge) {
52
+ return false;
53
+ }
54
+ if (dateOfBirth) {
55
+ const age = (0, exports.getAge)(dateOfBirth);
56
+ return isNaN(age) ? true : age < limit;
57
+ }
58
+ return maxAcknowledgedAge ? maxAcknowledgedAge < limit : true;
59
+ };
60
+ exports.isUnderAge = isUnderAge;
61
+ const datesByThemselves = (a, b) => {
62
+ if (!a.from)
63
+ return 1;
64
+ return new Date(a.from).getTime() - new Date(b.from).getTime();
65
+ };
66
+ exports.datesByThemselves = datesByThemselves;
67
+ const getShiftText = (from, to, timezone) => {
68
+ try {
69
+ if (!from || !to || !timezone)
70
+ return '';
71
+ const startToTimezone = luxon_1.DateTime.fromJSDate(from).setZone(timezone);
72
+ const endToTimezone = luxon_1.DateTime.fromJSDate(to).setZone(timezone);
73
+ return `${startToTimezone.toFormat('cccc, DD, h:mma')}-${endToTimezone.toFormat('h:mma')}`;
74
+ }
75
+ catch (e) {
76
+ return '';
77
+ }
78
+ };
79
+ exports.getShiftText = getShiftText;
@@ -0,0 +1 @@
1
+ export {};