@bash-app/bash-common 1.0.13 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash-app/bash-common",
3
- "version": "1.0.13",
3
+ "version": "1.1.0",
4
4
  "description": "Common data and scripts to use on the frontend and backend",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -21,7 +21,9 @@
21
21
  "typescript": "^5.2.2"
22
22
  },
23
23
  "peerDependencies": {
24
- "tsx": "^4.10.3"
24
+ "tsx": "^4.10.3",
25
+ "dayjs": "^1.11.10",
26
+ "react-tailwindcss-datepicker": "^1.6.6"
25
27
  },
26
28
  "devDependencies": {
27
29
  "@prisma/client": "^5.13.0",
@@ -767,22 +767,48 @@ model AssociatedBash {
767
767
  }
768
768
 
769
769
  model Service {
770
- id String @id @default(cuid())
771
- ownerId String
772
- owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
773
- name String?
774
- coverPhoto String?
775
- media Media[]
776
- serviceType ServiceType
777
- subServiceType String?
778
- serviceLinks ServiceLink[]
779
- address String
780
- description String?
781
- canContact Boolean?
782
- genre GenreType?
783
- }
784
-
785
- enum GenreType {
770
+ id String @id @default(cuid())
771
+ ownerId String
772
+ owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
773
+ name String?
774
+ coverPhoto String?
775
+ media Media[]
776
+ serviceType ServiceType[]
777
+ subServiceType String?
778
+ serviceLinks ServiceLink[]
779
+ address String
780
+ description String?
781
+ canContact Boolean?
782
+ musicGenre MusicGenreType?
783
+ visibility VisibilityPreference @default(Public)
784
+ bashesInterestedIn BashEventType[]
785
+ crowdSizeId String @unique
786
+ crowdSize ServiceRange @relation("CrowdSize", fields: [crowdSizeId], references: [id], onDelete: Cascade)
787
+ serviceRangeId String @unique
788
+ serviceRange ServiceRange @relation("ServiceRange", fields: [serviceRangeId], references: [id], onDelete: Cascade)
789
+ availableDateTimes DateTime?
790
+ }
791
+
792
+ model ServiceRange {
793
+ id String @id @default(cuid())
794
+ minimum Int?
795
+ maximum Int?
796
+ crowdSize Service? @relation("CrowdSize")
797
+ serviceRange Service? @relation("ServiceRange")
798
+ }
799
+
800
+ model Availability {
801
+ id String @id @default(cuid())
802
+ startDateTime String
803
+ endDateTime String
804
+ }
805
+
806
+ enum VisibilityPreference {
807
+ Public
808
+ Private
809
+ }
810
+
811
+ enum MusicGenreType {
786
812
  Classical
787
813
  Rock
788
814
  HipHop
@@ -873,8 +899,8 @@ model VendorLink {
873
899
 
874
900
  model ServiceLink {
875
901
  id String @id @default(cuid())
876
- service Service @relation(fields: [servicesId], references: [id], onDelete: Cascade)
877
- link Link @relation(fields: [linkId], references: [id], onDelete: Cascade)
878
902
  servicesId String
903
+ service Service @relation(fields: [servicesId], references: [id], onDelete: Cascade)
879
904
  linkId String
905
+ link Link @relation(fields: [linkId], references: [id], onDelete: Cascade)
880
906
  }
@@ -40,6 +40,11 @@ export const URL_PARAMS_GOOGLE_ACCESS_CODE = 'code' as const;
40
40
  export const URL_PARAMS_LINKED_IN_CODE = 'code' as const;
41
41
  export const URL_PARAMS_STATE = 'state' as const;
42
42
 
43
+ export const URL_PARAMS_TICKET_LIST_DELIM = ',' as const;
44
+ export const URL_PARAMS_TICKET_ID_NUMBER_OF_TICKETS_DATE_DELIM = '__' as const
45
+ export const URL_PARAMS_NUMBER_OF_TICKETS_TICKETS_DATE_DELIM = '~~' as const
46
+ export const URL_PARAMS_TICKETS_DATE_DELIM = ';;' as const
47
+
43
48
  export const URL_INCLUDE_QUERY_PARAM_DELIM = ',' as const;
44
49
  export const URL_INCLUDE_PRISMA_DATA_KEYS_DELIM = '.' as const;
45
50
 
@@ -56,6 +61,7 @@ export const ASSET_KEY_DELIM = '__' as const;
56
61
  export const ASSET_MAX_MD5_BYTE_LENGTH = 1024 * 100; // 100kb
57
62
 
58
63
 
64
+
59
65
  export type DateTimeArgType = Date | string | undefined | null;
60
66
  export type RequiredStripeInfoMissingErrorDataType = { [k in keyof User]?: { type: string, label: string } };
61
67
 
package/src/index.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  export * from "./extendedSchemas";
2
2
  export * from "./definitions";
3
+ export * from "./utils/ticketListUtils";
4
+ export * from "./utils/dateTimeUtils";
5
+ export * from "./utils/recurrenceUtils";
@@ -0,0 +1,177 @@
1
+ import {DateType, DateValueType} from "react-tailwindcss-datepicker";
2
+ import dayjs from "dayjs";
3
+ import dayjsUtc from "dayjs/plugin/utc";
4
+ import dayjsTimeZone from "dayjs/plugin/timezone";
5
+ import {DateTimeArgType} from "../definitions";
6
+
7
+ dayjs.extend(dayjsUtc);
8
+ dayjs.extend(dayjsTimeZone);
9
+
10
+
11
+ const PARSE_TIME_REG = /^(\d{1,2}):(\d{2}) ?([APM]{0,2})$/i;
12
+
13
+ export const DATETIME_FORMAT_STANDARD = "MMM D, YYYY - h:mm A";
14
+ export const DATETIME_FORMAT_ISO_LIKE = "YYYY-MM-DD HH:mm"
15
+ export const DATE_FORMAT_STANDARD = "MM/DD/YYYY";
16
+ export const DATE_FORMAT_ISO = "YYYY-MM-DD";
17
+ export const TIME_FORMAT_AM_PM = "hh:mm A";
18
+
19
+ export interface ITime {
20
+ hours: number;
21
+ minutes: number;
22
+ ampm: string;
23
+ }
24
+
25
+ export function ensureIsDateTime(possiblyADate: DateTimeArgType): Date | undefined {
26
+ if (!possiblyADate) {
27
+ return undefined;
28
+ }
29
+ return typeof possiblyADate === 'string' ? new Date(possiblyADate) : possiblyADate;
30
+ }
31
+
32
+ export function normalizeDate(validDate: Date | string | undefined | null): Date | undefined {
33
+ if (validDate) {
34
+ if (typeof validDate === 'string') {
35
+ validDate = new Date(validDate);
36
+ }
37
+ validDate.setHours(validDate.getHours(), validDate.getMinutes(), 0);
38
+ return validDate;
39
+ }
40
+ }
41
+
42
+ export function normalizeDates(dates: Date[]): Date[] {
43
+ return dates
44
+ .map((date): Date | undefined => {
45
+ return normalizeDate(date);
46
+ })
47
+ .filter((dateTime: Date | undefined): boolean => {
48
+ return !!dateTime
49
+ }) as Date[];
50
+ }
51
+
52
+ export function formatDateTimeToISODateTimeString(date: DateTimeArgType): string | undefined {
53
+ if (!date) {
54
+ return undefined;
55
+ }
56
+ date = new Date(date);
57
+ return date.toISOString();
58
+ }
59
+
60
+ export function dateWithinDateRange(date: DateTimeArgType, dateRange: DateValueType | undefined): boolean {
61
+ const startDate = dayjs(dateRange?.startDate).startOf('day').toDate();
62
+ const endDate = dayjs(dateRange?.endDate).endOf('day').toDate();
63
+
64
+ return compareDateTime(date, startDate) >= 0 &&
65
+ compareDateTime(date, endDate) < 0;
66
+ }
67
+
68
+ export function findLatestDateTime(date1Arg: DateTimeArgType, date2Arg: DateTimeArgType): Date | undefined {
69
+ const date1 = dateTimeTypeToDate(date1Arg);
70
+ const date2 = dateTimeTypeToDate(date2Arg);
71
+ return compareDateTime(date1, date2) > 0 ? date1 : date2;
72
+ }
73
+
74
+ function dateTimeTypeToDate(date: DateTimeArgType): Date | undefined {
75
+ if (typeof date === 'string') {
76
+ return new Date(date);
77
+ }
78
+ else {
79
+ return date ?? undefined;
80
+ }
81
+ }
82
+
83
+ export function isDateTimeLaterThanNow(date: DateTimeArgType): boolean {
84
+ return compareDateTime(date, new Date()) > 0;
85
+ }
86
+
87
+ export function areDateTimesEqual(date1: DateTimeArgType,
88
+ date2: DateTimeArgType): boolean {
89
+ return compareDateTime(date1, date2) === 0;
90
+ }
91
+
92
+ /**
93
+ * Returns a 1 if date1 is later than date2; 0 if both dates are equal; -1 if date2 is later than date1
94
+ * @param date1
95
+ * @param date2
96
+ */
97
+ export function compareDateTime(date1: DateTimeArgType,
98
+ date2: DateTimeArgType): number {
99
+ if (!date1 && !date2) {
100
+ return 0;
101
+ }
102
+ if (!date1) {
103
+ return -1;
104
+ }
105
+ if (!date2) {
106
+ return 1;
107
+ }
108
+ if (typeof date1 === 'string') {
109
+ date1 = new Date(date1);
110
+ }
111
+ if (typeof date2 === 'string') {
112
+ date2 = new Date(date2);
113
+ }
114
+
115
+ const date1Time = date1.getTime();
116
+ const date2Time = date2.getTime();
117
+
118
+ if (date1Time > date2Time) {
119
+ return 1;
120
+ }
121
+ if (date1Time === date2Time) {
122
+ return 0;
123
+ }
124
+ else {
125
+ return -1;
126
+ }
127
+ }
128
+
129
+ export function setDateButPreserveTime(dateArg: DateType, dateWithTheTimeYouWantToKeep: DateType | undefined): Date {
130
+ const date = dayjs(dateArg).format(DATE_FORMAT_ISO);
131
+ const time = dayjs(dateWithTheTimeYouWantToKeep).format(TIME_FORMAT_AM_PM);
132
+ const combinedDateTime = dayjs(date + " " + time).toDate();
133
+ return combinedDateTime;
134
+ }
135
+
136
+ export function setTimeOnDate(date: DateType | Date | undefined, parsedTime: ITime): Date {
137
+ const dateTime = new Date(date ?? Date.now());
138
+ const parsedTimeDate = new Date();
139
+ parsedTimeDate.setHours(parsedTime.hours, parsedTime.minutes, 0); // Will change the date depending on time; corrected later
140
+ const correctTimeOnDate = setDateButPreserveTime(dateTime, parsedTimeDate);
141
+ return correctTimeOnDate;
142
+ }
143
+
144
+ export function ensureDateTimeIsLocalDateTime(dateArg: DateType | undefined): Date {
145
+ if (dayjs(dateArg).isUTC()) {
146
+ const dateLocalTimeZone = dayjs(dateArg);
147
+ const date = dateLocalTimeZone.toDate();
148
+ return date;
149
+ }
150
+ else {
151
+ return new Date(dateArg ?? Date.now());
152
+ }
153
+ }
154
+
155
+ export function formatDateTimeToTimeString(date: Date | string | undefined | null): string {
156
+ date = new Date(date ?? Date.now());
157
+ const result = dayjs(date).format("h:mm A");
158
+ return result;
159
+ }
160
+
161
+ export function parseTimeString(timeStr: string): ITime | null {
162
+ const match = timeStr.match(PARSE_TIME_REG);
163
+
164
+ if (match) {
165
+ const hours = parseInt(match[1], 10);
166
+ const minutes = parseInt(match[2], 10);
167
+ const ampm = match[3].toUpperCase();
168
+
169
+ if ((hours >= 0 && hours <= 12) && (minutes >= 0 && minutes <= 59) && (ampm === 'AM' || ampm === 'PM')) {
170
+ return {
171
+ hours: ampm === 'PM' && hours < 12 ? hours + 12 : hours,
172
+ minutes,
173
+ ampm };
174
+ }
175
+ }
176
+ return null;
177
+ }
@@ -0,0 +1,175 @@
1
+ import {DayOfWeek, Recurrence, RecurringFrequency} from "@prisma/client";
2
+ import dayjs, {Dayjs} from "dayjs";
3
+ import dayjsWeekDay from "dayjs/plugin/weekday";
4
+ import {DateTimeArgType} from "../definitions";
5
+ import {compareDateTime, DATETIME_FORMAT_ISO_LIKE} from "./dateTimeUtils";
6
+
7
+ dayjs.extend(dayjsWeekDay);
8
+
9
+
10
+ export function getRecurringBashEventPossibleDateTimes(startDateTime: DateTimeArgType,
11
+ recurrence: Recurrence | undefined | null): Date[] {
12
+ return getRecurringBashEventPossibleDatesTimesInternal(startDateTime, recurrence, true) as Date[];
13
+ }
14
+
15
+ export function getRecurringBashEventPossibleDayjsTimes(startDateTime: DateTimeArgType,
16
+ recurrence: Recurrence | undefined | null): Dayjs[] {
17
+ return getRecurringBashEventPossibleDatesTimesInternal(startDateTime, recurrence, false) as Dayjs[];
18
+ }
19
+
20
+
21
+ function getRecurringBashEventPossibleDatesTimesInternal(startDateTime: DateTimeArgType,
22
+ recurrence: Recurrence | undefined | null,
23
+ toDate: boolean): (Dayjs | Date)[] {
24
+ if (!recurrence || !recurrence.frequency || recurrence.frequency === RecurringFrequency.Never) {
25
+ return [];
26
+ }
27
+
28
+ let recurrenceDates: Dayjs[] = [];
29
+ const beginningDate = findEarliestPossibleBashEventDate(startDateTime) as Dayjs; // Don't allow dates before the current date
30
+
31
+ switch (recurrence.frequency) {
32
+ case RecurringFrequency.Weekly:
33
+ recurrenceDates = getWeeklyRecurringDates(beginningDate, recurrence);
34
+ break;
35
+ default:
36
+ console.error(`Only weekly frequency is currently supported`);
37
+ }
38
+ return recurrenceDates.map((date: Dayjs): string => date.format(DATETIME_FORMAT_ISO_LIKE))
39
+ .sort()
40
+ .map((dateStr: string): Dayjs | Date => {
41
+ if (toDate) {
42
+ return dayjs(dateStr).toDate();
43
+ }
44
+ else {
45
+ return dayjs(dateStr);
46
+ }
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Get the weekly recurring dates
52
+ * @param beginningDateTime The beginning DateTime of the event, adjusted to be no earlier than now.
53
+ * @param recurrence
54
+ */
55
+ function getWeeklyRecurringDates(beginningDateTime: Dayjs, recurrence: Recurrence): Dayjs[] {
56
+ if (recurrence.frequency !== RecurringFrequency.Weekly) {
57
+ throw new Error(`Cannot do a weekly recurrence if the frequency isn't weekly.`);
58
+ }
59
+
60
+ const interval = recurrence.interval ?? 1;
61
+ const recurrenceEnds = dayjs(recurrence.ends).endOf('day'); // Get the end of the day to not miss the last day
62
+ const numberOfDays = recurrenceEnds.diff(beginningDateTime, 'days');
63
+ const numberOfWeeks = Math.ceil((numberOfDays / 7) / interval); // Get the number of days and then round up so that we include the ending date
64
+
65
+ const recurrenceDates: Dayjs[] = [];
66
+ let theNextWeekOfRecurrences = beginningDateTime;
67
+
68
+ // Go week by week getting the next recurrence date
69
+ for (let i = 0; i < numberOfWeeks; i++) {
70
+ let weekday: Dayjs | null = null;
71
+ for (const dayOfWeekEnum of recurrence.repeatOnDays) {
72
+ const dayOfWeekNum = dayOfWeekEnumToDayNumber(dayOfWeekEnum);
73
+ weekday = theNextWeekOfRecurrences.weekday(dayOfWeekNum);
74
+ if (weekday > recurrenceEnds || weekday < beginningDateTime) {
75
+ continue; // Continue because repeatOnDays isn't sorted by the day of the week
76
+ }
77
+ // Set the time on the date so that it matches the time when the event starts
78
+ weekday = copyTimeToDate(beginningDateTime, weekday);
79
+ recurrenceDates.push(weekday);
80
+ }
81
+ if (weekday) {
82
+ theNextWeekOfRecurrences = getBeginningOfWeekInterval(weekday, interval);
83
+ }
84
+ }
85
+ return recurrenceDates;
86
+ }
87
+
88
+ function copyTimeToDate(dateWithTimeToCopy: Dayjs, dateWhoseTimeToChange: Dayjs): Dayjs {
89
+ if (dateWithTimeToCopy.second() !== 0) {
90
+ console.warn(`dateWithTimeToCopy has non-zero seconds: ${dateWithTimeToCopy.toString()} \nFixing...`);
91
+ dateWithTimeToCopy = dateWithTimeToCopy.set('seconds', 0);
92
+ }
93
+ dateWhoseTimeToChange = dateWhoseTimeToChange.set('hours', dateWithTimeToCopy.hour());
94
+ dateWhoseTimeToChange = dateWhoseTimeToChange.set('minutes', dateWithTimeToCopy.minute());
95
+ dateWhoseTimeToChange = dateWhoseTimeToChange.set('seconds', 0); // Should be 0
96
+ return dateWhoseTimeToChange;
97
+ }
98
+
99
+ function dayOfWeekEnumToDayNumber(dayEnum: DayOfWeek): number {
100
+ switch (dayEnum) {
101
+ case DayOfWeek.Sunday:
102
+ return 0;
103
+ case DayOfWeek.Monday:
104
+ return 1;
105
+ case DayOfWeek.Tuesday:
106
+ return 2;
107
+ case DayOfWeek.Wednesday:
108
+ return 3;
109
+ case DayOfWeek.Thursday:
110
+ return 4;
111
+ case DayOfWeek.Friday:
112
+ return 5;
113
+ case DayOfWeek.Saturday:
114
+ return 6;
115
+ default:
116
+ throw new Error(`How did this happen? Invalid DayOfWeek: ${dayEnum}`); // Shouldn't happen
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Get the next week determined by the interval by going to the end of the week interval and adding one day
122
+ * @param date
123
+ * @param interval
124
+ */
125
+ function getBeginningOfWeekInterval(date: Dayjs, interval: number): Dayjs {
126
+ if (!interval) {
127
+ interval = 1; // Avoid 0
128
+ }
129
+ // An interval of 2 would be (2 - 1) * 7 + 1 = 8 days to add to skip a week and go into the next week
130
+ const numberOfDaysToAdd = interval > 1 ? (interval - 1) * 7 + 1 : 1;
131
+ return date.endOf('week').add(numberOfDaysToAdd, 'day');
132
+ }
133
+
134
+ export function freqToGrammarString(freq: RecurringFrequency, interval: number | undefined, toLowerCase: boolean = false): string {
135
+ if (!interval) {
136
+ return freq;
137
+ }
138
+ const isPlural = interval > 1;
139
+ let result: string = freq;
140
+ switch (freq) {
141
+ case RecurringFrequency.Daily:
142
+ result = isPlural ? 'Days' : 'Day';
143
+ break;
144
+ case RecurringFrequency.Weekly:
145
+ result = isPlural ? 'Weeks' : 'Week';
146
+ break;
147
+ case RecurringFrequency.Monthly:
148
+ result = isPlural ? 'Months' : 'Month';
149
+ break;
150
+ case RecurringFrequency.Yearly:
151
+ result = isPlural ? 'Years' : 'Year';
152
+ break;
153
+ }
154
+ return toLowerCase ? result.toLowerCase() : result;
155
+ }
156
+
157
+ export function findEarliestPossibleBashEventDate(startDateTimeArg: DateTimeArgType): Dayjs | undefined {
158
+ return findEarliestOrLatestPossibleBashEventDate(startDateTimeArg, true);
159
+ }
160
+
161
+ function findEarliestOrLatestPossibleBashEventDate(startDateTimeArg: DateTimeArgType, findEarly: boolean): Dayjs | undefined {
162
+ if (!startDateTimeArg) {
163
+ return;
164
+ }
165
+ // Don't allow dates before the current date
166
+ const startDateTime = dayjs(startDateTimeArg);
167
+ const currentDateTime = dayjs();
168
+ const comparedDateTime = compareDateTime(currentDateTime.toDate(), startDateTimeArg);
169
+ if (findEarly) {
170
+ return comparedDateTime > 0 ? currentDateTime : startDateTime;
171
+ }
172
+ else {
173
+ return comparedDateTime < 0 ? currentDateTime : startDateTime;
174
+ }
175
+ }
@@ -0,0 +1,69 @@
1
+ import dayjs from "dayjs";
2
+ import {
3
+ NumberOfTicketsForDate,
4
+ URL_PARAMS_NUMBER_OF_TICKETS_TICKETS_DATE_DELIM,
5
+ URL_PARAMS_TICKET_ID_NUMBER_OF_TICKETS_DATE_DELIM,
6
+ URL_PARAMS_TICKET_LIST_DELIM,
7
+ URL_PARAMS_TICKETS_DATE_DELIM
8
+ } from "../definitions";
9
+ import { DATETIME_FORMAT_ISO_LIKE } from "./dateTimeUtils";
10
+
11
+
12
+ /**
13
+ * Returns A string structured like: [ticketTierId]__[numberOfTickets];;[date]~~[numberOfTickets];;[date]~~,...
14
+ * @param ticketList A map where the key is the ticketTierId
15
+ */
16
+ export function ticketListToString(ticketList: Map<string, NumberOfTicketsForDate[]>): string {
17
+ const ticketListArr: string[] = [];
18
+
19
+ for (const [ticketTierId, ticketListArgs] of ticketList.entries()) {
20
+ const ticketArgs: string[] = [`${ticketTierId}${URL_PARAMS_TICKET_ID_NUMBER_OF_TICKETS_DATE_DELIM}`];
21
+ for (const ticketNumAndDates of ticketListArgs) {
22
+ if (ticketNumAndDates.numberOfTickets > 0) {
23
+ ticketArgs.push(
24
+ `${ticketNumAndDates.numberOfTickets}${URL_PARAMS_TICKETS_DATE_DELIM}${ticketNumAndDates.ticketDateTime}`);
25
+ }
26
+ }
27
+ ticketListArr.push(ticketArgs.join(URL_PARAMS_NUMBER_OF_TICKETS_TICKETS_DATE_DELIM));
28
+ }
29
+ return ticketListArr.join(URL_PARAMS_TICKET_LIST_DELIM);
30
+ }
31
+
32
+ /**
33
+ * Returns map where the key is the ticketTierId
34
+ * @param ticketListStr A string structured like: [ticketTierId]__[numberOfTickets];;[date]~~[numberOfTickets];;[date]~~...,...
35
+ */
36
+ export function ticketListStrToTicketList(ticketListStr: string): Map<string, NumberOfTicketsForDate[]> {
37
+ const ticketList: Map<string, NumberOfTicketsForDate[]> = new Map();
38
+
39
+ // [ticketTierId]__[numberOfTickets];;[date]~~[numberOfTickets];;[date]~~...,...
40
+ const ticketListArgs = ticketListStr.split(URL_PARAMS_TICKET_LIST_DELIM);
41
+
42
+ ticketListArgs.forEach((tierIdAndTicketNumbersAndDates: string): void => {
43
+ const ticketNumAndDatesArr: NumberOfTicketsForDate[] = [];
44
+ // [ticketTierId]__[numberOfTickets];;[date]~~[numberOfTickets];;[date]~~...
45
+ const [ticketTierId, ticketNumAndDatesStr] = tierIdAndTicketNumbersAndDates.split(URL_PARAMS_TICKET_ID_NUMBER_OF_TICKETS_DATE_DELIM);
46
+ // [numberOfTickets];;[date]~~[numberOfTickets];;[date]~~...
47
+ const ticketNumAndDates = ticketNumAndDatesStr.split(URL_PARAMS_NUMBER_OF_TICKETS_TICKETS_DATE_DELIM)
48
+ .filter((ticketNumAndDateStr): boolean => !!ticketNumAndDateStr);
49
+
50
+ for (const ticketNumAndDateStr of ticketNumAndDates) {
51
+ // [numberOfTickets];;[date]
52
+ const [numberOfTickets, ticketDateTimeStr] = ticketNumAndDateStr.split(URL_PARAMS_TICKETS_DATE_DELIM);
53
+ let ticketDateTime: string | undefined;
54
+ if (!ticketDateTimeStr) {
55
+ ticketDateTime = undefined;
56
+ }
57
+ else {
58
+ ticketDateTime = dayjs(ticketDateTimeStr).format(DATETIME_FORMAT_ISO_LIKE);
59
+ }
60
+
61
+ ticketNumAndDatesArr.push({
62
+ numberOfTickets: parseInt(numberOfTickets),
63
+ ticketDateTime: ticketDateTime,
64
+ });
65
+ }
66
+ ticketList.set(ticketTierId, ticketNumAndDatesArr);
67
+ });
68
+ return ticketList;
69
+ }