cronofy-elements 1.45.0 → 1.46.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.
@@ -136,15 +136,10 @@
136
136
  ]
137
137
  }
138
138
  ],
139
- required_duration: { minutes: 60 },
139
+ required_duration: { minutes: 15 },
140
140
  //start_interval: { minutes: 15 },
141
141
  query_periods: [
142
- { start: slotTimes(31,"03:00"), end: slotTimes(31,"12:00") },
143
- { start: slotTimes(32,"09:00"), end: slotTimes(32,"13:00") },
144
- { start: slotTimes(32,"14:00"), end: slotTimes(32,"17:00") },
145
- { start: slotTimes(35,"09:00"), end: slotTimes(35,"13:00") },
146
- { start: slotTimes(36,"14:00"), end: slotTimes(36,"17:00") },
147
- { start: slotTimes(38,"09:00"), end: slotTimes(65,"22:00") }
142
+ { start: slotTimes(31,"03:00"), end: slotTimes(150,"12:00") },
148
143
  ]
149
144
  },
150
145
  // tzid: "America/Mexico_City",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cronofy-elements",
3
- "version": "1.45.0",
3
+ "version": "1.46.0",
4
4
  "description": "Fast track scheduling with Cronofy's embeddable UI Elements",
5
5
  "main": "build/npm/CronofyElements.js",
6
6
  "scripts": {
@@ -5,7 +5,6 @@ import {
5
5
  getMonthObjectsFromQuery,
6
6
  parseTzList,
7
7
  getInitialSelectedTzid,
8
- getMonthsLoadingFromQuery,
9
8
  getDurationFromQuery,
10
9
  } from "./utils/slots";
11
10
  import { getMonthsInDisplay, parseTimeSlots } from "./utils/calendar";
@@ -37,7 +36,6 @@ const DateTimePicker = ({ options }) => {
37
36
  const startDateObject = moment(options.config.startDate, "YYYY-MM-DD");
38
37
  const currentMonthObject = selectedDateObject ?? startDateObject;
39
38
  const currentMonth = currentMonthObject.format("YYYY-MM");
40
- const monthsLoading = getMonthsLoadingFromQuery(options.query, options.tzid);
41
39
  const monthsInView = getMonthsInDisplay(currentMonth, options.config.startDay);
42
40
 
43
41
  const monthlyView = {
@@ -70,7 +68,6 @@ const DateTimePicker = ({ options }) => {
70
68
  query: options.query,
71
69
  months,
72
70
  monthlyView,
73
- monthsLoading,
74
71
  selected: false,
75
72
  startDay: options.config.startDay,
76
73
  focusedDay: options.config.selectedDay,
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect, useRef, useState } from "react";
2
2
  import moment from "moment";
3
3
 
4
- import { getSequencedSlots, getSlots } from "./utils/slots";
4
+ import { cropPeriodsArbitrarily, getSlots } from "./utils/slots";
5
5
 
6
6
  import Calendar from "./Calendar";
7
7
  import Confirm from "./Confirm";
@@ -24,51 +24,109 @@ const Wrapper = () => {
24
24
  const [loadingCalendar, setLoadingCalendar] = useState(true);
25
25
 
26
26
  const confirmButtonRef = useRef();
27
+ const firstRender = useRef(true);
27
28
 
28
- useEffect(() => {
29
- if (!status.query.query_periods) {
30
- return;
31
- }
32
-
33
- const fetchMonthSlots = (query, month) => {
34
- const fetch = status.sequenced_availability ? getSequencedSlots : getSlots;
29
+ const fetchMonthSlots = (query, month) => {
30
+ return getSlots({
31
+ query,
32
+ auth: status.auth,
33
+ tzid: status.tzid,
34
+ sequence: status.sequenced_availability,
35
+ })
36
+ .then(res => {
37
+ if (res.length < 512) {
38
+ dispatchStatus({
39
+ type: "SET_SLOTS",
40
+ slots: res,
41
+ tzid: tz.selectedTzid.tzid,
42
+ month,
43
+ });
44
+ return;
45
+ }
35
46
 
36
- return fetch({
37
- query,
38
- auth: status.auth,
39
- tzid: status.tzid,
40
- }).then(res => {
41
47
  dispatchStatus({
42
48
  type: "SET_SLOTS",
43
49
  slots: res,
44
50
  tzid: tz.selectedTzid.tzid,
45
- month: month,
46
51
  });
52
+
53
+ // If we get here, the API has returned the maximum number
54
+ // of slots allowed, so we need to crop the query and try
55
+ // again to ensure we haven't missed any slots.
56
+ const startOfLastSlot = res[res.length - 1].start;
57
+ const endOfLastPeriod = query.query_periods[query.query_periods.length - 1].end;
58
+
59
+ const boundsForCropping = {
60
+ start: startOfLastSlot,
61
+ end: endOfLastPeriod,
62
+ };
63
+
64
+ const croppedPeriods = cropPeriodsArbitrarily(
65
+ query.query_periods,
66
+ boundsForCropping
67
+ );
68
+ const croppedQuery = { ...query, query_periods: croppedPeriods };
69
+
70
+ // Rerun the query
71
+ return fetchMonthSlots(croppedQuery, month);
72
+ })
73
+ .catch(error => {
74
+ if (error.type === 422) {
75
+ dispatchStatus({
76
+ type: "ERROR_LOADING_SLOTS",
77
+ error,
78
+ month,
79
+ });
80
+ return;
81
+ }
82
+ dispatchStatus({ type: "ERROR_GETTING_SLOTS", error });
47
83
  });
48
- };
84
+ };
85
+
86
+ useEffect(() => {
87
+ if (!status.query.query_periods) {
88
+ return;
89
+ }
49
90
 
50
91
  const currentMonth = status.months.find(m => m.current);
51
92
 
93
+ const croppedMonths = status.months.filter(el => {
94
+ return status.monthlyView.monthsInView.some(f => {
95
+ return f === el.month && el.loading && !el.current;
96
+ });
97
+ });
98
+
52
99
  // Get slots for current month
53
100
  fetchMonthSlots(currentMonth.query, currentMonth.month)
54
101
  .then(() => {
55
- // Get slots for remianing months
56
- const remainingMonths = status.months.filter(month => !month.current);
57
- remainingMonths.forEach(month =>
58
- fetchMonthSlots(month.query, month.month).catch(res => {
59
- dispatchStatus({
60
- type: "ERROR_LOADING_SLOTS",
61
- error: res,
62
- month: month.month,
63
- });
64
- })
65
- );
102
+ // Get slots for remaining months
103
+ croppedMonths.forEach(month => fetchMonthSlots(month.query, month.month));
66
104
  })
67
105
  .catch(error => {
68
106
  dispatchStatus({ type: "ERROR_GETTING_SLOTS", error });
69
107
  });
70
108
  }, []);
71
109
 
110
+ useEffect(() => {
111
+ //stops this from firing on first render which would double up the initial API calls
112
+ if (firstRender.current) {
113
+ firstRender.current = false;
114
+ return;
115
+ }
116
+ if (status.monthlyView.monthsInView) {
117
+ //check if any of the months in view haven't been fetched yet
118
+ const croppedMonths = status.months.filter(el => {
119
+ return status.monthlyView.monthsInView.some(f => {
120
+ return f === el.month && el.loading === true;
121
+ });
122
+ });
123
+ //if they haven't, fetch them.
124
+ if (croppedMonths.length > 0) {
125
+ croppedMonths.forEach(month => fetchMonthSlots(month.query, month.month));
126
+ }
127
+ }
128
+ }, [status.monthlyView.monthsInView]);
129
+
72
130
  useEffect(() => {
73
131
  if (!status.slotInjectionPoint) {
74
132
  return;
@@ -97,6 +155,15 @@ const Wrapper = () => {
97
155
  const finishedCallingAllSlots = fetchCount === monthsCount;
98
156
  const hasAvailableDays = status.availableDays.length;
99
157
 
158
+ // No slots found but not all months called so try the next month
159
+ if (!finishedCallingAllSlots && !hasAvailableDays && fetchCount >= 2) {
160
+ const croppedMonths = status.months.filter(el => {
161
+ return el.loading === true;
162
+ });
163
+ fetchMonthSlots(croppedMonths[0].query, croppedMonths[0].month);
164
+ return;
165
+ }
166
+
100
167
  // No slots available after all queries have been made
101
168
  if (finishedCallingAllSlots && !hasAvailableDays) {
102
169
  dispatchStatus({ type: "NO_SLOTS_FOUND" });
@@ -117,15 +184,25 @@ const Wrapper = () => {
117
184
  return;
118
185
  }
119
186
 
120
- // For allowing only this to be called once
187
+ // For allowing only this to be called once slots have been fetched
121
188
  // Since we know the first month that is being fetched will be for the initial selected day
122
- if (status.selectedDay && fetchCount === 1) {
123
- dispatchStatus({
124
- type: "SELECT_DAY",
125
- day: status.selectedDay,
126
- tzid: tz.selectedTzid.tzid,
127
- });
128
- return;
189
+ if (status.selectedDay && fetchCount >= 1) {
190
+ const currentMonth = moment(status.selectedDay, "YYYY-MM-DD").format("YYYY-MM");
191
+ const month = status.months.filter(el => {
192
+ return el.month === currentMonth;
193
+ })[0];
194
+ const monthStillLoading = month ? month.loading : false;
195
+ const dayIsAvailable = status.availableDays.includes(status.selectedDay);
196
+
197
+ //Only set the selectedDay if the month is finished loading or if the day has slots available
198
+ if (!monthStillLoading || dayIsAvailable) {
199
+ dispatchStatus({
200
+ type: "SELECT_DAY",
201
+ day: status.selectedDay,
202
+ tzid: tz.selectedTzid.tzid,
203
+ });
204
+ return;
205
+ }
129
206
  }
130
207
  }, [status.slotFetchCount, status.availableDays]);
131
208
 
@@ -159,9 +236,9 @@ const Wrapper = () => {
159
236
  }, [tz.selectedTzid.tzid]);
160
237
 
161
238
  useEffect(() => {
162
- if (status.monthsLoading) {
239
+ if (status.months) {
163
240
  for (let month of status.monthlyView.monthsInView) {
164
- const monthLoading = status.monthsLoading.find(m => m.month === month);
241
+ const monthLoading = status.months.find(m => m.month === month);
165
242
  if (monthLoading && monthLoading.loading) {
166
243
  setLoadingCalendar(true);
167
244
  return;
@@ -170,7 +247,7 @@ const Wrapper = () => {
170
247
  }
171
248
  }
172
249
  }
173
- }, [status.monthlyView, status.monthsLoading]);
250
+ }, [status.monthlyView, status.months]);
174
251
 
175
252
  return (
176
253
  <section className={theme.classBuilder()} style={theme.customProperties}>
@@ -9,6 +9,7 @@ import {
9
9
  addSequencedSlotsToObject,
10
10
  } from "../utils/slots";
11
11
  import { getMonthsInDisplay, parseTimeSlots } from "../utils/calendar";
12
+ import { uniqueItems } from "../../../helpers/utils";
12
13
 
13
14
  export const statusReducer = (state, action) => {
14
15
  const { type, ...actionBody } = action;
@@ -62,9 +63,9 @@ export const statusReducer = (state, action) => {
62
63
  body: action.error.body,
63
64
  },
64
65
  };
65
- const monthsLoading = removeMonthFromLoading(state.monthsLoading, action.month);
66
+ const months = removeMonthFromLoading(state.months, action.month);
66
67
  state.callback(notification);
67
- return { ...state, monthsLoading };
68
+ return { ...state, months };
68
69
  }
69
70
 
70
71
  case "NO_SLOTS_FOUND": {
@@ -99,19 +100,39 @@ export const statusReducer = (state, action) => {
99
100
 
100
101
  case "SET_SLOTS": {
101
102
  if (!action.slots.length > 0) {
102
- const monthsLoading = removeMonthFromLoading(state.monthsLoading, action.month);
103
+ const months = removeMonthFromLoading(state.months, action.month);
103
104
  return {
104
105
  ...state,
105
- monthsLoading,
106
+ months,
106
107
  slotFetchCount: state.slotFetchCount + 1,
107
108
  };
108
109
  }
109
110
 
111
+ // Add action slots to state slots
110
112
  const slotsObject = state.sequenced_availability
111
113
  ? addSequencedSlotsToObject(state.slots, action.slots)
112
114
  : addSlotsToObject(state.slots, action.slots);
113
- const availableDays = getAvailableDays(slotsObject, action.tzid);
114
- const monthsLoading = removeMonthFromLoading(state.monthsLoading, action.month);
115
+
116
+ // since we already know the available days for state slots
117
+ // find available days for action.slots and add them to state availableDays
118
+ // this way we don't have to loop through the entirety of the state slotsObject
119
+ // every time new slots are rendered
120
+ const actionSlotsObject = state.sequenced_availability
121
+ ? addSequencedSlotsToObject({}, action.slots)
122
+ : addSlotsToObject({}, action.slots);
123
+
124
+ const availableDays = uniqueItems([
125
+ ...state.availableDays,
126
+ ...getAvailableDays(actionSlotsObject, action.tzid),
127
+ ]).sort();
128
+
129
+ // if month is passed in, we render that month as done
130
+ // this change is for continiously loading slots as opposed to waiting for entire month
131
+ // to be loaded
132
+ let months = state.months;
133
+ if (action.month) {
134
+ months = removeMonthFromLoading(state.months, action.month);
135
+ }
115
136
 
116
137
  const injectionPoint = state.sequenced_availability
117
138
  ? getLocalDayFromUtc(action.slots[0].sequence[0].start, action.tzid)
@@ -120,7 +141,7 @@ export const statusReducer = (state, action) => {
120
141
  return {
121
142
  ...state,
122
143
  availableDays,
123
- monthsLoading,
144
+ months,
124
145
  slots: slotsObject,
125
146
  slotFetchCount: state.slotFetchCount + 1,
126
147
  slotInjectionPoint: injectionPoint,
@@ -92,6 +92,7 @@ export const getMonthObjectsFromQuery = (query, tzid, current = false) => {
92
92
  return {
93
93
  month,
94
94
  current: currentMonth === month,
95
+ loading: true,
95
96
  query: {
96
97
  ...query,
97
98
  query_periods: croppedQueryPeriods,
@@ -101,55 +102,10 @@ export const getMonthObjectsFromQuery = (query, tzid, current = false) => {
101
102
  });
102
103
  };
103
104
 
104
- export const getSlots = ({ query, auth, tzid, slots = [] }) =>
105
- getAvailability(
106
- auth.token,
107
- auth.domains.apiDomain,
108
- query,
109
- "DateTimePicker",
110
- tzid,
111
- auth.demo
112
- ).then(res => {
113
- if (res.status === 422) {
114
- throw {
115
- type: 422,
116
- message: errorMessages[422].message,
117
- body: res.errors,
118
- docsSlug: errorMessages[422].docsSlug,
119
- };
120
- }
121
- // This will intentionally throw an error if the
122
- // result is not in the correct format:
123
- const returnedSlots = parseSlotsResult(res);
124
-
125
- const allSlots = [...slots, ...returnedSlots];
126
-
127
- if (returnedSlots.length < 512) return [...slots, ...returnedSlots];
128
-
129
- // If we get here, the API has returned the maximum number
130
- // of slots allowed, so we need to crop the query and try
131
- // again to ensure we haven't missed any slots.
132
-
133
- const startOfLastSlot = returnedSlots[returnedSlots.length - 1].start;
134
- const endOfLastPeriod = query.query_periods[query.query_periods.length - 1].end;
135
- const boundsForCropping = {
136
- start: startOfLastSlot,
137
- end: endOfLastPeriod,
138
- };
139
- const croppedPeriods = cropPeriodsArbitrarily(query.query_periods, boundsForCropping);
140
- const croppedQuery = { ...query, query_periods: croppedPeriods };
141
-
142
- // Rerun the query
143
- return getSlots({
144
- query: croppedQuery,
145
- auth,
146
- tzid,
147
- slots: allSlots,
148
- });
149
- });
105
+ export const getSlots = ({ query, auth, tzid, sequence = false }) => {
106
+ const availabilityFetch = sequence ? getSequencedAvailability : getAvailability;
150
107
 
151
- export const getSequencedSlots = ({ query, auth, tzid, slots = [] }) =>
152
- getSequencedAvailability(
108
+ return availabilityFetch(
153
109
  auth.token,
154
110
  auth.domains.apiDomain,
155
111
  query,
@@ -157,7 +113,7 @@ export const getSequencedSlots = ({ query, auth, tzid, slots = [] }) =>
157
113
  tzid,
158
114
  auth.demo
159
115
  ).then(res => {
160
- if (res.errors) {
116
+ if (res.status === 422) {
161
117
  throw {
162
118
  type: 422,
163
119
  message: errorMessages[422].message,
@@ -167,33 +123,9 @@ export const getSequencedSlots = ({ query, auth, tzid, slots = [] }) =>
167
123
  }
168
124
  // This will intentionally throw an error if the
169
125
  // result is not in the correct format:
170
- const returnedSlots = parseSlotsResult(res);
171
-
172
- const allSlots = [...slots, ...returnedSlots];
173
-
174
- if (returnedSlots.length < 512) return [...slots, ...returnedSlots];
175
-
176
- // If we get here, the API has returned the maximum number
177
- // of slots allowed, so we need to crop the query and try
178
- // again to ensure we haven't missed any slots.
179
-
180
- const startOfLastSlot = returnedSlots[returnedSlots.length - 1].start;
181
- const endOfLastPeriod = query.query_periods[query.query_periods.length - 1].end;
182
- const boundsForCropping = {
183
- start: startOfLastSlot,
184
- end: endOfLastPeriod,
185
- };
186
- const croppedPeriods = cropPeriodsArbitrarily(query.query_periods, boundsForCropping);
187
- const croppedQuery = { ...query, query_periods: croppedPeriods };
188
-
189
- // Rerun the query
190
- return getSequencedSlots({
191
- query: croppedQuery,
192
- auth,
193
- tzid,
194
- slots: allSlots,
195
- });
126
+ return parseSlotsResult(res);
196
127
  });
128
+ };
197
129
 
198
130
  export const parseQuery = query => {
199
131
  if (!query.bookable_events) {