bookito-widget 0.42.16-beta.2 → 0.42.16-beta.21
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/CHANGELOG.md +82 -4
- package/dist/3140.js +1 -1
- package/dist/Carlito.eot +0 -0
- package/dist/Carlito.svg +1 -1
- package/dist/Carlito.ttf +0 -0
- package/dist/Carlito.woff +0 -0
- package/dist/Carlito.woff2 +0 -0
- package/dist/bookito-widget.umd.js +73 -161
- package/dist/bookito-widget.window.js +73 -161
- package/package.json +1 -1
- package/src/BookitoEventDetail/index.tsx +11 -5
- package/src/BookitoEventModal/index.tsx +22 -19
- package/src/BookitoForm/EventInfo.tsx +1 -3
- package/src/BookitoForm/NewBookingForm.tsx +14 -11
- package/src/BookitoWidget/index.tsx +46 -10
- package/src/BookitoWidgetProvider/BookitoWidgetProvider.tsx +1 -1
- package/src/Card/index.tsx +23 -15
- package/src/CardHolder/index.tsx +5 -3
- package/src/calendar-styles.css +16 -0
- package/src/types.ts +7 -0
- package/src/utils/index.tsx +0 -47
- package/src/BookitoForm/BookingForm.tsx +0 -470
- package/src/BookitoForm/helper.tsx +0 -266
- package/src/BookitoForm/index.tsx +0 -1
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { getImageFormatUrl, useBookitoSettings } from '@carlito/api';
|
|
2
|
-
import { LocalizedEventData } from '@carlito/bookito-client';
|
|
1
|
+
import { getImageFormatUrl, useApp, useBookitoSettings } from '@carlito/api';
|
|
2
|
+
import { LocalizedEventData, isEventBookable } from '@carlito/bookito-client';
|
|
3
3
|
import { Trans } from 'react-i18next';
|
|
4
4
|
import { BookingForm } from '../BookitoForm/NewBookingForm';
|
|
5
|
-
import {
|
|
6
|
-
import { BookitoWidgetViewMode } from '../types';
|
|
5
|
+
import { BookitoWidgetViewMode, ResolvedOccurrence } from '../types';
|
|
7
6
|
import { useMemo } from 'react';
|
|
8
7
|
|
|
9
8
|
export type Props = {
|
|
@@ -13,6 +12,8 @@ export type Props = {
|
|
|
13
12
|
availableOnDates: string[];
|
|
14
13
|
isModalView?: boolean;
|
|
15
14
|
viewMode?: BookitoWidgetViewMode;
|
|
15
|
+
/** Server-resolved occurrence for `date` (resolveOccurrences=true). */
|
|
16
|
+
resolvedOccurrence?: ResolvedOccurrence;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
function BookitoEventDetail({
|
|
@@ -22,8 +23,10 @@ function BookitoEventDetail({
|
|
|
22
23
|
date,
|
|
23
24
|
isModalView = false,
|
|
24
25
|
viewMode,
|
|
26
|
+
resolvedOccurrence,
|
|
25
27
|
}: Props) {
|
|
26
28
|
const { description, bannerImg, title } = event;
|
|
29
|
+
const { timezone } = useApp();
|
|
27
30
|
|
|
28
31
|
const { bookitoSettings } = useBookitoSettings({ hotelId: authId });
|
|
29
32
|
const { isBookable } = useMemo(
|
|
@@ -32,8 +35,9 @@ function BookitoEventDetail({
|
|
|
32
35
|
event,
|
|
33
36
|
onDate: date,
|
|
34
37
|
settings: bookitoSettings,
|
|
38
|
+
timezone,
|
|
35
39
|
}),
|
|
36
|
-
[bookitoSettings, date, event]
|
|
40
|
+
[bookitoSettings, date, event, timezone]
|
|
37
41
|
);
|
|
38
42
|
|
|
39
43
|
let isBookableEvent = isBookable;
|
|
@@ -81,6 +85,8 @@ function BookitoEventDetail({
|
|
|
81
85
|
authId={authId}
|
|
82
86
|
availableOnDates={availableOnDates}
|
|
83
87
|
shouldPreselectDateInCalendar={shouldPreselectDateInCalendar ?? !isModalView}
|
|
88
|
+
resolvedOccurrence={resolvedOccurrence}
|
|
89
|
+
timezone={timezone}
|
|
84
90
|
/>
|
|
85
91
|
) : (
|
|
86
92
|
<p className="bookito-event-detail__description text-sm lg:text-base leading-relaxed ">
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { useBookitoSettings } from '@carlito/api';
|
|
1
|
+
import { useBookitoSettings, useHotelDate } from '@carlito/api';
|
|
2
2
|
import { SupportedLanguages, useListLocalizedEvents } from '@carlito/bookito-client';
|
|
3
|
-
import { useLayoutEffect } from 'react';
|
|
4
|
-
import { useTranslation } from 'react-i18next';
|
|
5
3
|
import BookitoEventDetail from '../BookitoEventDetail';
|
|
6
4
|
import Modal from '../Modal';
|
|
7
5
|
import ModalLoader from './ModalLoader';
|
|
@@ -14,31 +12,21 @@ export type BookitoEventModalProps = {
|
|
|
14
12
|
openModal: boolean;
|
|
15
13
|
};
|
|
16
14
|
|
|
17
|
-
const BOOKABLE_DAYS_IN_ADVANCE = 90;
|
|
18
|
-
const defaultToDate = new Date();
|
|
19
|
-
|
|
20
|
-
defaultToDate.setDate(defaultToDate.getDate() + BOOKABLE_DAYS_IN_ADVANCE);
|
|
21
|
-
|
|
22
15
|
function BookitoEventModal(props: BookitoEventModalProps) {
|
|
23
|
-
const { i18n } = useTranslation();
|
|
24
|
-
useLayoutEffect(() => {
|
|
25
|
-
if (props?.language) {
|
|
26
|
-
i18n.changeLanguage(props?.language);
|
|
27
|
-
}
|
|
28
|
-
}, [props?.language, i18n]);
|
|
29
|
-
|
|
30
16
|
const bookitoSettings = useBookitoSettings({
|
|
31
17
|
hotelId: props.authId,
|
|
32
18
|
});
|
|
33
19
|
|
|
20
|
+
const daysBookableInAdvance = bookitoSettings.bookitoSettings?.daysBookableInAdvance ?? 90;
|
|
21
|
+
const toDate = useHotelDate({ days: daysBookableInAdvance });
|
|
22
|
+
|
|
34
23
|
const { data, isLoading } = useListLocalizedEvents(
|
|
35
24
|
{
|
|
36
25
|
authId: props.authId,
|
|
37
26
|
id: props.eventId,
|
|
38
|
-
toDate:
|
|
39
|
-
daysBookableInAdvance: (
|
|
40
|
-
|
|
41
|
-
).toString(),
|
|
27
|
+
toDate: toDate.toISOString(),
|
|
28
|
+
daysBookableInAdvance: daysBookableInAdvance.toString(),
|
|
29
|
+
resolveOccurrences: true,
|
|
42
30
|
},
|
|
43
31
|
props.openModal && Boolean(bookitoSettings.bookitoSettings)
|
|
44
32
|
);
|
|
@@ -49,6 +37,20 @@ function BookitoEventModal(props: BookitoEventModalProps) {
|
|
|
49
37
|
? new Date(data?.availableOnDates?.[0])
|
|
50
38
|
: new Date();
|
|
51
39
|
|
|
40
|
+
// First server-resolved occurrence for this event (resolveOccurrences=true), used as the
|
|
41
|
+
// preselected occurrence in the booking form so no client RRULE runs.
|
|
42
|
+
const resolvedOccurrence = (() => {
|
|
43
|
+
if (!data?.dates) return undefined;
|
|
44
|
+
const sortedByDay = Object.entries(data.dates).sort(
|
|
45
|
+
([a], [b]) => new Date(a).getTime() - new Date(b).getTime()
|
|
46
|
+
);
|
|
47
|
+
for (const [, day] of sortedByDay) {
|
|
48
|
+
const occurrence = day.events.find((e) => e.id === props.eventId)?.occurrences?.[0];
|
|
49
|
+
if (occurrence) return occurrence;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
})();
|
|
53
|
+
|
|
52
54
|
return props.openModal ? (
|
|
53
55
|
<Modal onCloseModal={props.onCloseModal}>
|
|
54
56
|
{isLoading && <ModalLoader />}
|
|
@@ -59,6 +61,7 @@ function BookitoEventModal(props: BookitoEventModalProps) {
|
|
|
59
61
|
authId={props.authId}
|
|
60
62
|
availableOnDates={data.availableOnDates || []}
|
|
61
63
|
isModalView={true}
|
|
64
|
+
resolvedOccurrence={resolvedOccurrence}
|
|
62
65
|
/>
|
|
63
66
|
)}
|
|
64
67
|
</Modal>
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { LocalizedEventData } from '@carlito/bookito-client';
|
|
3
3
|
import humanizeDuration, { SupportedLanguage } from 'humanize-duration';
|
|
4
|
-
import { applyTimeOfDate } from '@carlito/date-utils';
|
|
5
|
-
import { isEventAllDay } from './helper';
|
|
6
4
|
import { Trans } from 'react-i18next';
|
|
7
5
|
import { formatPrice, useApp } from '@carlito/api';
|
|
8
6
|
|
|
@@ -8,18 +8,13 @@ import {
|
|
|
8
8
|
} from '@carlito/shared-ui';
|
|
9
9
|
import { analyticsEvent } from '@carlito/app-analytics';
|
|
10
10
|
import { SUPPORTED_LANGUAGE_CODES } from '@carlito/intl';
|
|
11
|
-
import {
|
|
12
|
-
extractApiErrorCodes,
|
|
13
|
-
findOccurrenceForDate,
|
|
14
|
-
LocalizedEventData,
|
|
15
|
-
} from '@carlito/bookito-client';
|
|
11
|
+
import { extractApiErrorCodes, LocalizedEventData } from '@carlito/bookito-client';
|
|
16
12
|
import { useGetHotelConfiguration } from '@carlito/hub-react';
|
|
17
13
|
import { Formik } from 'formik';
|
|
18
14
|
import EventInfo from './EventInfo';
|
|
19
15
|
import FormSubmitButton from './FormSubmitButton';
|
|
20
|
-
import { applyTimeOfDate } from '@carlito/date-utils';
|
|
21
|
-
import { isEventAllDay } from './helper';
|
|
22
16
|
import { useTranslation } from 'react-i18next';
|
|
17
|
+
import { ResolvedOccurrence } from '../types';
|
|
23
18
|
|
|
24
19
|
function getUserLanguage() {
|
|
25
20
|
const browserLanguages = [...navigator.languages];
|
|
@@ -40,6 +35,9 @@ export type BookingFormProps = {
|
|
|
40
35
|
authId: string;
|
|
41
36
|
availableOnDates: string[];
|
|
42
37
|
shouldPreselectDateInCalendar?: boolean;
|
|
38
|
+
/** Server-resolved occurrence for the preselected date (resolveOccurrences=true). */
|
|
39
|
+
resolvedOccurrence?: ResolvedOccurrence;
|
|
40
|
+
timezone?: string;
|
|
43
41
|
};
|
|
44
42
|
|
|
45
43
|
export function BookingForm({
|
|
@@ -48,17 +46,21 @@ export function BookingForm({
|
|
|
48
46
|
authId,
|
|
49
47
|
availableOnDates,
|
|
50
48
|
shouldPreselectDateInCalendar = true,
|
|
49
|
+
resolvedOccurrence,
|
|
50
|
+
timezone,
|
|
51
51
|
}: BookingFormProps) {
|
|
52
52
|
const userLanguage = getUserLanguage();
|
|
53
53
|
|
|
54
54
|
const { t } = useTranslation();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
55
|
+
// Occurrence times come from the server-resolved occurrence (resolveOccurrences=true) —
|
|
56
|
+
// no client RRULE. When absent, the calendar simply isn't preselected.
|
|
57
|
+
const startDateTime = resolvedOccurrence ? new Date(resolvedOccurrence.startDateTime) : undefined;
|
|
58
|
+
const durationMs = resolvedOccurrence?.durationMs;
|
|
59
|
+
const isAllDay = resolvedOccurrence?.isAllDay;
|
|
58
60
|
const overallDuration =
|
|
59
61
|
(event?.timeBookability === 'custom'
|
|
60
62
|
? event?.timeBookabilitySettings?.timeBlockDurationMs
|
|
61
|
-
:
|
|
63
|
+
: durationMs) ?? undefined;
|
|
62
64
|
|
|
63
65
|
const initialValues: Partial<BookitoBookingFormValues> = useMemo(() => {
|
|
64
66
|
return {
|
|
@@ -123,6 +125,7 @@ export function BookingForm({
|
|
|
123
125
|
activeLanguage={userLanguage}
|
|
124
126
|
event={event}
|
|
125
127
|
hotelId={authId}
|
|
128
|
+
timezone={timezone}
|
|
126
129
|
isTimeSlotEnabled={isTimeSlotEnabled}
|
|
127
130
|
availableOnDates={availableOnDates}
|
|
128
131
|
renderQuantityControls={({
|
|
@@ -17,8 +17,8 @@ import ExperienceCardHolder from '../CardHolder';
|
|
|
17
17
|
import Modal from '../Modal';
|
|
18
18
|
import CarlitoLogo from '../assets/carlito-logo.svg';
|
|
19
19
|
import styles from '../index.module.css';
|
|
20
|
-
import { BookitoWidgetViewMode } from '../types';
|
|
21
|
-
import { useApp } from '@carlito/api';
|
|
20
|
+
import { BookitoWidgetViewMode, ResolvedOccurrence } from '../types';
|
|
21
|
+
import { useApp, useHotelDate } from '@carlito/api';
|
|
22
22
|
const { getCssModuleClassName } = cssModuleClassNames(styles);
|
|
23
23
|
|
|
24
24
|
// Helper function to extract actual data from InfiniteData or return the data directly
|
|
@@ -44,15 +44,17 @@ export type BookitoWidgetProps = {
|
|
|
44
44
|
excludeNonBookable?: boolean;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
type SelectedEvent = {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
type SelectedEvent = {
|
|
48
|
+
event: LocalizedEventData;
|
|
49
|
+
date: Date;
|
|
50
|
+
occurrence?: ResolvedOccurrence;
|
|
51
|
+
};
|
|
51
52
|
|
|
52
53
|
const BookitoWidget = (props: BookitoWidgetProps) => {
|
|
53
54
|
const { viewMode = 'Date' } = props;
|
|
54
55
|
const { i18n } = useTranslation();
|
|
55
56
|
const { timezone } = useApp();
|
|
57
|
+
const dateToday = useHotelDate();
|
|
56
58
|
const [canFetchCategoryList, setFetchingCategoryList] = useState(false);
|
|
57
59
|
const [categoryTitle, setCategoryTitle] = useState('');
|
|
58
60
|
|
|
@@ -87,6 +89,7 @@ const BookitoWidget = (props: BookitoWidgetProps) => {
|
|
|
87
89
|
authId: props?.authId,
|
|
88
90
|
categoryId: props?.categoryId,
|
|
89
91
|
excludeNonBookable: props.excludeNonBookable,
|
|
92
|
+
resolveOccurrences: true,
|
|
90
93
|
});
|
|
91
94
|
|
|
92
95
|
const listEventCategoriesQuery = useListLocalizedEventCategories(
|
|
@@ -153,11 +156,30 @@ const BookitoWidget = (props: BookitoWidgetProps) => {
|
|
|
153
156
|
return null;
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
// Earliest server-resolved occurrence per event across the loaded range
|
|
160
|
+
// (resolveOccurrences=true), so the flat list renders times without client RRULE.
|
|
161
|
+
const occurrencesByEventId: Record<string, ResolvedOccurrence> = {};
|
|
162
|
+
if (eventsData.dates) {
|
|
163
|
+
const sortedDays = Object.entries(eventsData.dates).sort(
|
|
164
|
+
([a], [b]) => new Date(a).getTime() - new Date(b).getTime()
|
|
165
|
+
);
|
|
166
|
+
for (const [, day] of sortedDays) {
|
|
167
|
+
for (const entry of day.events) {
|
|
168
|
+
const occurrence = entry.occurrences?.[0];
|
|
169
|
+
if (occurrence && !occurrencesByEventId[entry.id]) {
|
|
170
|
+
occurrencesByEventId[entry.id] = occurrence;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
156
176
|
return (
|
|
157
177
|
<ExperienceCardHolder
|
|
158
|
-
onDate={dateToday}
|
|
159
178
|
events={eventsData.events}
|
|
160
|
-
|
|
179
|
+
occurrences={occurrencesByEventId}
|
|
180
|
+
openModal={(event) =>
|
|
181
|
+
setSelectedEvent({ event, date: dateToday, occurrence: occurrencesByEventId[event.id] })
|
|
182
|
+
}
|
|
161
183
|
/>
|
|
162
184
|
);
|
|
163
185
|
};
|
|
@@ -192,6 +214,16 @@ const BookitoWidget = (props: BookitoWidgetProps) => {
|
|
|
192
214
|
return null;
|
|
193
215
|
}
|
|
194
216
|
|
|
217
|
+
// Server-resolved occurrence per event for this day (resolveOccurrences=true),
|
|
218
|
+
// so Cards render times without client RRULE.
|
|
219
|
+
const occurrencesByEventId: Record<string, ResolvedOccurrence> = {};
|
|
220
|
+
for (const entry of events) {
|
|
221
|
+
const occurrence = entry.occurrences?.[0];
|
|
222
|
+
if (occurrence) {
|
|
223
|
+
occurrencesByEventId[entry.id] = occurrence;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
195
227
|
// Interpret dateStr as local date in hotel's timezone
|
|
196
228
|
const date = DateTime.fromISO(dateStr, { zone: timezone }).startOf('day').toJSDate();
|
|
197
229
|
|
|
@@ -203,13 +235,16 @@ const BookitoWidget = (props: BookitoWidgetProps) => {
|
|
|
203
235
|
weekday: 'long',
|
|
204
236
|
month: 'long',
|
|
205
237
|
day: 'numeric',
|
|
238
|
+
timeZone: timezone,
|
|
206
239
|
})
|
|
207
240
|
)}
|
|
208
241
|
</span>
|
|
209
242
|
<ExperienceCardHolder
|
|
210
|
-
onDate={date}
|
|
211
243
|
events={eventsToRender}
|
|
212
|
-
|
|
244
|
+
occurrences={occurrencesByEventId}
|
|
245
|
+
openModal={(event) =>
|
|
246
|
+
setSelectedEvent({ event, date, occurrence: occurrencesByEventId[event.id] })
|
|
247
|
+
}
|
|
213
248
|
/>
|
|
214
249
|
</div>
|
|
215
250
|
);
|
|
@@ -254,6 +289,7 @@ const BookitoWidget = (props: BookitoWidgetProps) => {
|
|
|
254
289
|
authId={props?.authId}
|
|
255
290
|
availableOnDates={getAvailabilityDates(selectedEvent.event.id)}
|
|
256
291
|
viewMode={viewMode}
|
|
292
|
+
resolvedOccurrence={selectedEvent.occurrence}
|
|
257
293
|
/>
|
|
258
294
|
</Modal>
|
|
259
295
|
)}
|
|
@@ -11,7 +11,7 @@ const BookitoWidgetProvider: React.FC<{
|
|
|
11
11
|
}> = (props) => {
|
|
12
12
|
return (
|
|
13
13
|
<ApolloProvider client={client}>
|
|
14
|
-
<AppProvider hotelId={props.hotelId}>
|
|
14
|
+
<AppProvider defaultLanguage={props.language} hotelId={props.hotelId}>
|
|
15
15
|
<ContentLanguageProvider
|
|
16
16
|
skipHotelLanguages={true}
|
|
17
17
|
browserLocale={props.language ?? (i18n.language as SupportedLocalesCode)}
|
package/src/Card/index.tsx
CHANGED
|
@@ -1,37 +1,45 @@
|
|
|
1
1
|
import { LocalizedEventData } from '@carlito/bookito-client';
|
|
2
2
|
import { LineClamper } from '@carlito/dashboard-ui';
|
|
3
|
-
import { applyTimeOfDate } from '@carlito/date-utils';
|
|
4
3
|
import { useApp } from '@carlito/api';
|
|
5
4
|
import classNames from 'classnames';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
5
|
+
import formatPrice from '../utils';
|
|
6
|
+
import { ResolvedOccurrence } from '../types';
|
|
8
7
|
|
|
9
|
-
type CardProps = {
|
|
8
|
+
type CardProps = {
|
|
9
|
+
event: LocalizedEventData;
|
|
10
|
+
openModal: () => void;
|
|
11
|
+
occurrence?: ResolvedOccurrence;
|
|
12
|
+
};
|
|
10
13
|
|
|
11
|
-
const ExperienceCard = ({ event, openModal,
|
|
14
|
+
const ExperienceCard = ({ event, openModal, occurrence }: CardProps) => {
|
|
12
15
|
const { title, basePrice, description, bannerImg } = event;
|
|
13
|
-
const { hotelCurrency } = useApp();
|
|
14
|
-
|
|
15
|
-
const occurrence = findOccurrenceForDate(onDate, event) ?? event.occurrences[0];
|
|
16
|
+
const { hotelCurrency, timezone } = useApp();
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// Times come straight from the server-resolved occurrence (resolveOccurrences=true):
|
|
19
|
+
// startDateTime is an absolute instant and isAllDay is precomputed — no client RRULE.
|
|
20
|
+
// When no occurrence is supplied the time block is simply hidden.
|
|
21
|
+
const startDateTime = occurrence ? new Date(occurrence.startDateTime) : undefined;
|
|
22
|
+
const endDateTime =
|
|
23
|
+
startDateTime && occurrence
|
|
24
|
+
? new Date(startDateTime.getTime() + occurrence.durationMs)
|
|
25
|
+
: undefined;
|
|
20
26
|
|
|
21
|
-
const startDateTimeStr = startDateTime
|
|
27
|
+
const startDateTimeStr = startDateTime?.toLocaleString(undefined, {
|
|
22
28
|
hour: '2-digit',
|
|
23
29
|
minute: '2-digit',
|
|
30
|
+
timeZone: timezone,
|
|
24
31
|
});
|
|
25
32
|
|
|
26
|
-
const endDateTimeStr = endDateTime
|
|
33
|
+
const endDateTimeStr = endDateTime?.toLocaleString(undefined, {
|
|
27
34
|
hour: '2-digit',
|
|
28
35
|
minute: '2-digit',
|
|
36
|
+
timeZone: timezone,
|
|
29
37
|
});
|
|
30
38
|
|
|
31
|
-
const isAllDay =
|
|
39
|
+
const isAllDay = occurrence?.isAllDay ?? false;
|
|
32
40
|
|
|
33
41
|
const shouldShowPrice = Boolean(basePrice?.amount);
|
|
34
|
-
const shouldShowTimes = !isAllDay;
|
|
42
|
+
const shouldShowTimes = !isAllDay && Boolean(startDateTimeStr && endDateTimeStr);
|
|
35
43
|
|
|
36
44
|
return (
|
|
37
45
|
<div
|
package/src/CardHolder/index.tsx
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { LocalizedEventData } from '@carlito/bookito-client';
|
|
2
2
|
import ExperienceCard from '../Card';
|
|
3
|
+
import { ResolvedOccurrence } from '../types';
|
|
3
4
|
|
|
4
5
|
type Props = {
|
|
5
6
|
events: LocalizedEventData[];
|
|
6
7
|
openModal(event: LocalizedEventData): void;
|
|
7
|
-
|
|
8
|
+
/** Server-resolved occurrence per event id (resolveOccurrences=true). */
|
|
9
|
+
occurrences?: Record<string, ResolvedOccurrence>;
|
|
8
10
|
};
|
|
9
11
|
|
|
10
|
-
const ExperienceCardHolder = ({ events, openModal,
|
|
12
|
+
const ExperienceCardHolder = ({ events, openModal, occurrences }: Props) => {
|
|
11
13
|
return (
|
|
12
14
|
<div
|
|
13
15
|
className="bookito-widget__events-list flex max-h-[200px]"
|
|
@@ -20,7 +22,7 @@ const ExperienceCardHolder = ({ events, openModal, onDate }: Props) => {
|
|
|
20
22
|
key={event.id}
|
|
21
23
|
event={event}
|
|
22
24
|
openModal={() => openModal(event)}
|
|
23
|
-
|
|
25
|
+
occurrence={occurrences?.[event.id]}
|
|
24
26
|
/>
|
|
25
27
|
))}
|
|
26
28
|
</div>
|
package/src/calendar-styles.css
CHANGED
|
@@ -4,3 +4,19 @@
|
|
|
4
4
|
border: none;
|
|
5
5
|
background: none;
|
|
6
6
|
}
|
|
7
|
+
.calendar-tile {
|
|
8
|
+
flex: 0 0 2.8rem !important;
|
|
9
|
+
height: 2.8rem;
|
|
10
|
+
display: flex;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
align-items: center;
|
|
13
|
+
}
|
|
14
|
+
.calendar-tile.active {
|
|
15
|
+
border: 1px solid var(--theme-color-primary) !important;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.react-calendar__month-view__weekdays,
|
|
19
|
+
.react-calendar__month-view__days {
|
|
20
|
+
justify-content: space-around;
|
|
21
|
+
row-gap: 5px;
|
|
22
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
+
import { ConditionallyLocalizedListEventsResponse } from '@carlito/bookito-client';
|
|
2
|
+
|
|
1
3
|
export type BookitoWidgetViewMode = 'List' | 'Date';
|
|
4
|
+
|
|
5
|
+
/** Server-resolved occurrence (resolveOccurrences=true): `dates[].events[].occurrences[]`. */
|
|
6
|
+
export type ResolvedOccurrence = NonNullable<
|
|
7
|
+
ConditionallyLocalizedListEventsResponse<true>['dates'][string]['events'][number]['occurrences']
|
|
8
|
+
>[number];
|
package/src/utils/index.tsx
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { LocalizedEventData, getRRuleConfiguration } from '@carlito/bookito-client';
|
|
2
|
-
import { RRule } from 'rrule';
|
|
3
|
-
|
|
4
1
|
export default function formatPrice(cost: number, locale = 'de-DE', currency: string = 'EUR') {
|
|
5
2
|
return new Intl.NumberFormat(locale, {
|
|
6
3
|
style: 'currency',
|
|
@@ -8,47 +5,3 @@ export default function formatPrice(cost: number, locale = 'de-DE', currency: st
|
|
|
8
5
|
currencyDisplay: 'narrowSymbol',
|
|
9
6
|
}).format(cost);
|
|
10
7
|
}
|
|
11
|
-
|
|
12
|
-
export function findOccurrenceForDate(
|
|
13
|
-
targetDate: Date,
|
|
14
|
-
event: Pick<LocalizedEventData, 'occurrences'>
|
|
15
|
-
) {
|
|
16
|
-
for (const occurrence of event.occurrences) {
|
|
17
|
-
const startDateTime = new Date(occurrence.startDateTime);
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
startDateTime.getDate() === targetDate.getDate() &&
|
|
21
|
-
startDateTime.getMonth() === targetDate.getMonth() &&
|
|
22
|
-
startDateTime.getFullYear() === targetDate.getFullYear()
|
|
23
|
-
) {
|
|
24
|
-
return occurrence;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (occurrence.repetition) {
|
|
28
|
-
const rruleConfig = getRRuleConfiguration(
|
|
29
|
-
{
|
|
30
|
-
...occurrence.repetition,
|
|
31
|
-
repeatUntil: occurrence.repetition.repeatUntil
|
|
32
|
-
? new Date(occurrence.repetition.repeatUntil)
|
|
33
|
-
: undefined,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
dtStart: startDateTime,
|
|
37
|
-
}
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const rule = new RRule(rruleConfig);
|
|
41
|
-
|
|
42
|
-
const dates = rule.between(
|
|
43
|
-
new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()),
|
|
44
|
-
new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate(), 23, 59, 59),
|
|
45
|
-
true
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
if (dates.length > 0) {
|
|
49
|
-
return occurrence;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|