@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.
- package/.semaphore/utils.yml +1 -1
- package/index.ts +7 -0
- package/lib/FeatureFlag.d.ts +12 -0
- package/lib/FeatureFlag.js +51 -0
- package/lib/constants/allowedHtmlTags.d.ts +7 -0
- package/lib/constants/allowedHtmlTags.js +8 -0
- package/lib/constants/avatarColours.d.ts +35 -0
- package/lib/constants/avatarColours.js +36 -0
- package/lib/constants/countries.d.ts +253 -0
- package/lib/constants/countries.js +254 -0
- package/lib/constants/currencies.d.ts +29 -0
- package/lib/constants/currencies.js +36 -0
- package/lib/constants/customFields.d.ts +11 -0
- package/lib/constants/customFields.js +12 -0
- package/lib/constants/distancesUnits.d.ts +11 -0
- package/lib/constants/distancesUnits.js +13 -0
- package/lib/constants/groups.d.ts +13 -0
- package/lib/constants/groups.js +85 -0
- package/lib/constants/insuranceStatuses.d.ts +15 -0
- package/lib/constants/insuranceStatuses.js +7 -0
- package/lib/constants/insuranceTypes.d.ts +35 -0
- package/lib/constants/insuranceTypes.js +36 -0
- package/lib/constants/languages.d.ts +2 -0
- package/lib/constants/languages.js +194 -0
- package/lib/constants/locales.d.ts +119 -0
- package/lib/constants/locales.js +191 -0
- package/lib/constants/notifications.d.ts +47 -0
- package/lib/constants/notifications.js +31 -0
- package/lib/constants/opportunities.d.ts +72 -0
- package/lib/constants/opportunities.js +82 -0
- package/lib/constants/qualificationTypes.d.ts +4 -0
- package/lib/constants/qualificationTypes.js +18 -0
- package/lib/constants/rules.d.ts +13 -0
- package/lib/constants/rules.js +21 -0
- package/lib/constants/subscriptions.d.ts +9 -0
- package/lib/constants/subscriptions.js +10 -0
- package/lib/constants/surveys.d.ts +16 -0
- package/lib/constants/surveys.js +17 -0
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +76 -0
- package/lib/date-time.d.ts +28 -0
- package/lib/date-time.js +79 -0
- package/lib/forms.d.ts +1 -0
- package/lib/forms.js +728 -0
- package/lib/locality.d.ts +5 -0
- package/lib/locality.js +15 -0
- package/lib/money.d.ts +7 -0
- package/lib/money.js +32 -0
- package/lib/opportunity.d.ts +1 -0
- package/lib/opportunity.js +11 -0
- package/lib/opportunityUser.d.ts +8 -0
- package/lib/opportunityUser.js +29 -0
- package/lib/password.d.ts +8 -0
- package/lib/password.js +43 -0
- package/lib/rrule/date.d.ts +4 -0
- package/lib/rrule/date.js +30 -0
- package/lib/rrule/index.d.ts +2 -0
- package/lib/rrule/index.js +18 -0
- package/lib/rrule/parsing.d.ts +6 -0
- package/lib/rrule/parsing.js +130 -0
- package/lib/rrule/parsing.test.d.ts +1 -0
- package/lib/rrule/parsing.test.js +11 -0
- package/lib/rrule.d.ts +49 -0
- package/lib/rrule.js +303 -0
- package/lib/src/FeatureFlag.d.ts +12 -0
- package/lib/src/FeatureFlag.js +51 -0
- package/lib/src/date-time.d.ts +28 -0
- package/lib/src/date-time.js +79 -0
- package/lib/src/forms.d.ts +1 -0
- package/lib/src/forms.js +728 -0
- package/lib/src/locality.d.ts +5 -0
- package/lib/src/locality.js +15 -0
- package/lib/src/money.d.ts +7 -0
- package/lib/src/money.js +32 -0
- package/lib/src/opportunity.d.ts +1 -0
- package/lib/src/opportunity.js +11 -0
- package/lib/src/opportunityUser.d.ts +8 -0
- package/lib/src/opportunityUser.js +29 -0
- package/lib/src/password.d.ts +8 -0
- package/lib/src/password.js +43 -0
- package/package.json +15 -29
- package/src/FeatureFlag.ts +26 -10
- package/src/constants/allowedHtmlTags.ts +6 -0
- package/src/constants/avatarColours.ts +34 -0
- package/src/constants/countries.ts +252 -0
- package/src/constants/currencies.ts +33 -0
- package/src/constants/customFields.ts +10 -0
- package/src/constants/distancesUnits.ts +11 -0
- package/src/constants/groups.ts +83 -0
- package/src/constants/insuranceStatuses.ts +5 -0
- package/src/constants/insuranceTypes.ts +34 -0
- package/src/constants/languages.ts +192 -0
- package/src/constants/locales.ts +189 -0
- package/src/constants/notifications.ts +30 -0
- package/src/constants/opportunities.ts +84 -0
- package/src/constants/qualificationTypes.ts +17 -0
- package/src/constants/rules.ts +22 -0
- package/src/constants/subscriptions.ts +8 -0
- package/src/constants/surveys.ts +15 -0
- package/src/constants.ts +40 -0
- package/src/{date-time.js → date-time.ts} +18 -11
- package/src/forms.ts +807 -0
- package/src/{opportunityUser.js → opportunityUser.ts} +5 -1
- package/src/{password.js → password.ts} +2 -0
- package/src/rrule/date.ts +25 -0
- package/src/rrule/index.ts +2 -0
- package/src/rrule/parsing.ts +136 -0
- package/src/rrule.ts +367 -0
- package/tests/{date-time.test.js → date-time.test.ts} +48 -46
- package/tests/featureFlag.test.ts +36 -39
- package/tests/forms.test-data.json +43 -0
- package/tests/forms.test-schema.json +144 -0
- package/tests/forms.test-uischema.json +117 -0
- package/tests/forms.test.ts +674 -0
- package/tests/{money.test.js → money.test.ts} +2 -3
- package/tests/{password.test.js → password.test.ts} +7 -6
- package/tests/rrule/parsing.test.ts +12 -0
- package/tests/rrule.test.ts +625 -0
- package/tsconfig.json +15 -12
- package/bundle.js +0 -270
- package/index.js +0 -25
- package/rollup.config.js +0 -25
- /package/src/{locality.js → locality.ts} +0 -0
- /package/src/{money.js → money.ts} +0 -0
- /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 {};
|