cronofy-elements 1.40.1 → 1.40.2

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": "cronofy-elements",
3
- "version": "1.40.1",
3
+ "version": "1.40.2",
4
4
  "description": "Fast track scheduling with Cronofy's embeddable UI Elements",
5
5
  "main": "build/npm/CronofyElements.js",
6
6
  "scripts": {
@@ -14,10 +14,12 @@ import DayHeadings from "./DayHeadings";
14
14
 
15
15
  import { useStatus } from "./contexts/status-context";
16
16
  import { useTheme } from "./contexts/theme-context";
17
+ import { useTz } from "./contexts/tz-context";
17
18
 
18
19
  const Calendar = () => {
19
20
  const [status, dispatchStatus] = useStatus();
20
21
  const theme = useTheme();
22
+ const [tz] = useTz();
21
23
 
22
24
  const weekdays = getWeekDays(status.startDay);
23
25
 
@@ -27,8 +29,7 @@ const Calendar = () => {
27
29
 
28
30
  // Handles the keyboard navigating for the calendar table
29
31
  const handleGridNavigation = (date, e) => {
30
- const currentMonth = status.months.find(month => month.current).month;
31
- const monthlySlots = status.monthlySlots.find(month => month.month === currentMonth);
32
+ const monthlySlots = status.monthlyView;
32
33
  const availableDaysInGrid = getAvailableDaysInMonthDisplay(monthlySlots.days);
33
34
  if (!date) {
34
35
  dispatchStatus({
@@ -70,9 +71,10 @@ const Calendar = () => {
70
71
  availableDays: status.availableDays,
71
72
  });
72
73
  dispatchStatus({
73
- type: "SET_FOCUS_FOR_NEW_MONTH",
74
- focusedDay: result.date,
74
+ type: "SELECT_MONTH",
75
75
  month: result.month,
76
+ focusedDay: result.date,
77
+ tzid: tz.selectedTzid.tzid,
76
78
  });
77
79
  }
78
80
  }
@@ -80,8 +82,8 @@ const Calendar = () => {
80
82
  }
81
83
  };
82
84
 
83
- const currentMonth = status.months.find(month => month.current).month;
84
- const monthlySlots = status.monthlySlots.find(month => month.month === currentMonth);
85
+ const monthlySlots = status.monthlyView;
86
+ const currentMonth = monthlySlots.month;
85
87
 
86
88
  const grid = monthlySlots.days.map((row, i) => (
87
89
  <tr className={theme.classBuilder("calendar-grid--row")} key={`${currentMonth}_row_${i}`}>
@@ -5,16 +5,22 @@ import { calculateMonthNav } from "./utils/calendar";
5
5
  import { useI18n } from "../../contexts/i18n-context";
6
6
  import { useStatus } from "./contexts/status-context";
7
7
  import { useTheme } from "./contexts/theme-context";
8
+ import { useTz } from "./contexts/tz-context";
8
9
 
9
10
  const CalendarHeader = () => {
10
11
  const i18n = useI18n();
11
12
  const [status, dispatchStatus] = useStatus();
12
13
  const theme = useTheme();
14
+ const [tz] = useTz();
13
15
 
14
16
  const [monthState, setMonthState] = useState(() => calculateMonthNav(status.months));
15
17
 
16
18
  const handleMonthNav = target => {
17
- dispatchStatus({ type: "SELECT_MONTH", month: target });
19
+ dispatchStatus({
20
+ type: "SELECT_MONTH",
21
+ month: target,
22
+ tzid: tz.selectedTzid.tzid,
23
+ });
18
24
  };
19
25
 
20
26
  useEffect(() => {
@@ -33,7 +33,7 @@ const Confirm = ({ confirmButtonRef }) => {
33
33
  <div id="confirm-slot-details">
34
34
  <p className={theme.classBuilder("confirm-details--info")}>
35
35
  {i18n.customFormatedTimeZone(
36
- moment(status.selected.start, "YYYY-MM-DD"),
36
+ moment(status.selected.start, "YYYY-MM-DDTHH:mm:00Z"),
37
37
  tz.selectedTzid.tzid,
38
38
  "dddd, MMMM Do YYYY"
39
39
  )}
@@ -1,4 +1,5 @@
1
1
  import React, { useState } from "react";
2
+ import moment from "moment-timezone";
2
3
 
3
4
  import {
4
5
  getMonthObjectsFromQuery,
@@ -23,15 +24,18 @@ const DateTimePicker = ({ options }) => {
23
24
  const [statusOptions] = useState(() => {
24
25
  const query = !options.demo ? parseQuery(options.query) : queryForDateTimePicker;
25
26
  const months = getMonthObjectsFromQuery(query, options.tzid);
26
- const monthlySlots = months.map(month => ({
27
- month: month.month,
27
+ const currentMonth = months.length ? months[0].month : moment().format("YYYY-MM");
28
+
29
+ const monthlyView = {
30
+ month: currentMonth,
28
31
  days: parseTimeSlots({
29
32
  slots: {},
30
- month: month.month,
33
+ month: currentMonth,
31
34
  tzid: options.tzid,
32
35
  startDay: options.config.startDay,
33
36
  }),
34
- }));
37
+ };
38
+
35
39
  return {
36
40
  auth: {
37
41
  token: options.token,
@@ -47,15 +51,14 @@ const DateTimePicker = ({ options }) => {
47
51
  mode: options.config.mode, // confirm (default) | no_confirm
48
52
  query,
49
53
  months,
50
- monthlySlots,
54
+ monthlyView,
51
55
  selected: false,
52
- selectedDay: false,
53
56
  startDay: options.config.startDay,
54
- focusedDay: false,
55
57
  focusedSlot: false,
56
- availableDays: false,
58
+ availableDays: [],
57
59
  slots: {},
58
60
  slotFetchCount: 0,
61
+ slotInjectionPoint: undefined,
59
62
  tzid: options.tzid,
60
63
  populated: false,
61
64
  };
@@ -19,42 +19,36 @@ const Wrapper = () => {
19
19
  const [status, dispatchStatus] = useStatus();
20
20
  const [tz] = useTz();
21
21
  const theme = useTheme();
22
+
22
23
  const confirmButtonRef = useRef();
23
24
 
24
25
  useEffect(() => {
25
26
  if (!status.query.query_periods) {
26
- dispatchStatus({ type: "SET_DEFAULT_SLOTS", slots: {} });
27
27
  return;
28
28
  }
29
29
 
30
- const currentMonth = status.months.find(month => month.current);
31
- // Get all slots for current month
32
- getSlots({
33
- query: currentMonth.query,
34
- auth: status.auth,
35
- tzid: status.tzid,
36
- })
37
- .then(res => {
30
+ const fetchMonthSlots = query => {
31
+ return getSlots({
32
+ query,
33
+ auth: status.auth,
34
+ tzid: status.tzid,
35
+ }).then(res => {
38
36
  dispatchStatus({
39
- type: "SET_INITIAL_SLOTS",
37
+ type: "SET_SLOTS",
40
38
  slots: res,
41
39
  tzid: tz.selectedTzid.tzid,
42
40
  });
41
+ });
42
+ };
43
+
44
+ const currentMonth = status.months.find(m => m.current);
45
+
46
+ // Get slots for current month
47
+ fetchMonthSlots(currentMonth.query)
48
+ .then(() => {
49
+ // Get slots for remianing months
43
50
  const remainingMonths = status.months.filter(month => !month.current);
44
- remainingMonths.forEach(month => {
45
- getSlots({
46
- query: month.query,
47
- auth: status.auth,
48
- tzid: status.tzid,
49
- }).then(res => {
50
- dispatchStatus({
51
- type: "SET_ADDITIONAL_SLOTS",
52
- month: month.month,
53
- slots: res,
54
- tzid: tz.selectedTzid.tzid,
55
- });
56
- });
57
- });
51
+ remainingMonths.forEach(month => fetchMonthSlots(month.query));
58
52
  })
59
53
  .catch(error => {
60
54
  dispatchStatus({ type: "ERROR_GETTING_SLOTS", error });
@@ -71,26 +65,43 @@ const Wrapper = () => {
71
65
  return;
72
66
  }
73
67
 
74
- // Set grid display if there are available slots
75
- if (slotsCount > 0) {
76
- dispatchStatus({ type: "RECALCULATE_MONTH_SLOTS", tzid: tz.selectedTzid.tzid });
68
+ // Set grid display if there are available slots within the current month
69
+ if (slotsCount < 1) {
70
+ return;
77
71
  }
78
- }, [status.slotFetchCount]);
72
+
73
+ const weeksInMonth = status.monthlyView.days;
74
+ const firstDay = moment(weeksInMonth[0][0].date, "YYYY-MM-DD");
75
+ const lastWeekInMonth = weeksInMonth[weeksInMonth.length - 1];
76
+ const lastDay = moment(lastWeekInMonth[lastWeekInMonth.length - 1].date, "YYYY-MM-DD");
77
+
78
+ const injectionPoint = status.slotInjectionPoint
79
+ ? moment(status.slotInjectionPoint, "YYYY-MM-DD")
80
+ : undefined;
81
+
82
+ if (injectionPoint && !injectionPoint.isBetween(firstDay, lastDay)) {
83
+ return;
84
+ }
85
+
86
+ dispatchStatus({
87
+ type: "RECALCULATE_MONTH_VIEW",
88
+ tzid: tz.selectedTzid.tzid,
89
+ });
90
+ }, [status.slotFetchCount, status.slotInjectionPoint]);
79
91
 
80
92
  useEffect(() => {
81
93
  if (status.selectedDay) {
82
- if (!status.populated) {
83
- dispatchStatus({
84
- type: "SELECT_DAY_INITIAL",
85
- day: status.selectedDay,
86
- });
87
- }
88
- const currentMonthParts = status.months.find(month => month.current).month.split("-");
89
94
  const selectedDayParts = status.selectedDay.split("-");
95
+ const currentMonthParts = status.monthlyView.month.split("-");
90
96
  const inCurrentMonth = currentMonthParts[1] === selectedDayParts[1];
97
+
91
98
  if (!inCurrentMonth) {
92
99
  const newMonth = moment(status.selectedDay, "YYYY-MM-DD").format("YYYY-MM");
93
- dispatchStatus({ type: "SELECT_MONTH", month: newMonth });
100
+ dispatchStatus({
101
+ type: "SELECT_MONTH",
102
+ month: newMonth,
103
+ tzid: tz.selectedTzid.tzid,
104
+ });
94
105
  }
95
106
  }
96
107
  }, [status.selectedDay]);
@@ -2,16 +2,16 @@ import React, { createContext, useContext, useReducer } from "react";
2
2
 
3
3
  import { statusReducer } from "./status-reducer";
4
4
 
5
- const statusContext = createContext();
5
+ const StatusContext = createContext();
6
6
 
7
7
  export const StatusProvider = ({ children, options }) => {
8
8
  const [status, dispatchStatus] = useReducer(statusReducer, options);
9
9
  return (
10
- <statusContext.Provider value={[status, dispatchStatus]}>{children}</statusContext.Provider>
10
+ <StatusContext.Provider value={[status, dispatchStatus]}>{children}</StatusContext.Provider>
11
11
  );
12
12
  };
13
13
  export const useStatus = () => {
14
- const context = useContext(statusContext);
14
+ const context = useContext(StatusContext);
15
15
  if (context === undefined) {
16
16
  throw new Error("useStatus must be used within a StatusProvider");
17
17
  }
@@ -30,10 +30,12 @@ export const statusReducer = (state, action) => {
30
30
  selected: false,
31
31
  };
32
32
  }
33
+
33
34
  case "CONFIRM_SELECTION": {
34
35
  sendSlotNotification(action.tzid);
35
36
  return state;
36
37
  }
38
+
37
39
  case "ERROR_GETTING_SLOTS": {
38
40
  const notification = {
39
41
  notification: {
@@ -44,80 +46,73 @@ export const statusReducer = (state, action) => {
44
46
  state.callback(notification);
45
47
  return { ...state, error: action.error, columnView: "error" };
46
48
  }
49
+
47
50
  case "NO_SLOTS_FOUND": {
48
51
  return { ...state, columnView: "no-slots" };
49
52
  }
50
- case "RECALCULATE_MONTH_SLOTS": {
51
- const monthlySlots = state.months.map(month => ({
52
- month: month.month,
53
+
54
+ case "RECALCULATE_MONTH_VIEW": {
55
+ const monthlyView = {
56
+ month: state.monthlyView.month,
53
57
  days: parseTimeSlots({
54
58
  slots: state.slots,
55
- month: month.month,
59
+ month: state.monthlyView.month,
56
60
  tzid: action.tzid,
57
61
  startDay: state.startDay,
58
62
  }),
59
- }));
60
- return { ...state, monthlySlots };
61
- }
62
- case "SET_INITIAL_SLOTS": {
63
- const hasNewSlots = Object.keys(action.slots).length >= 1;
64
- const slotsObject = hasNewSlots
65
- ? addSlotsToObject(state.slots, action.slots)
66
- : state.slots;
67
- const hasAnySlots = Object.keys(slotsObject).length >= 1;
68
- const selectedDay = hasAnySlots
69
- ? getFirstAvailableDay(slotsObject, action.tzid)
70
- : false;
71
- const availableDays = hasAnySlots ? getAvailableDays(slotsObject, state.tzid) : false;
63
+ };
64
+
72
65
  return {
73
66
  ...state,
74
- selectedDay,
75
- focusedDay: selectedDay,
76
- slots: slotsObject,
77
- availableDays: availableDays,
78
- slotFetchCount: state.slotFetchCount + 1,
67
+ monthlyView,
79
68
  };
80
69
  }
81
- case "SET_DEFAULT_SLOTS":
82
- case "SET_ADDITIONAL_SLOTS": {
83
- const hasNewSlots = Object.keys(action.slots).length >= 1;
84
- const slotsObject = hasNewSlots
85
- ? addSlotsToObject(state.slots, action.slots)
86
- : state.slots;
87
- const hasAnySlots = Object.keys(slotsObject).length >= 1;
88
- const selectedDay =
89
- hasAnySlots && !state.selectedDay
90
- ? getFirstAvailableDay(slotsObject, action.tzid)
91
- : state.selectedDay;
70
+
71
+ case "SET_SLOTS": {
72
+ if (!action.slots.length > 0) {
73
+ return state;
74
+ }
75
+
76
+ const slotsObject = addSlotsToObject(state.slots, action.slots);
77
+
78
+ let daySlots = state.daySlots;
79
+ let columnView = state.columnView;
80
+ let focusedDay = state.focusedDay;
81
+ let selectedDay = state.selectedDay;
82
+ let availableDays = getAvailableDays(addSlotsToObject({}, action.slots), action.tzid);
83
+
84
+ const availableDaysSet = new Set([...state.availableDays, ...availableDays]);
85
+ availableDays = [...availableDaysSet].sort();
86
+
87
+ if (!state.selectedDay) {
88
+ columnView = "slots";
89
+ focusedDay = availableDays[0];
90
+ selectedDay = availableDays[0];
91
+ daySlots = getSlotsByDay(slotsObject, selectedDay, action.tzid);
92
+ }
93
+
92
94
  return {
93
95
  ...state,
94
- slots: slotsObject,
96
+ daySlots,
97
+ columnView,
98
+ focusedDay,
95
99
  selectedDay,
96
- focusedDay: selectedDay,
100
+ availableDays,
101
+
102
+ slots: slotsObject,
97
103
  slotFetchCount: state.slotFetchCount + 1,
104
+ slotInjectionPoint: getLocalDayFromUtc(action.slots[0].start, action.tzid),
98
105
  };
99
106
  }
107
+
100
108
  case "SET_FOCUS": {
101
- return { ...state, focusedDay: action.focusedDay };
102
- }
103
- case "SET_FOCUS_FOR_NEW_MONTH": {
104
- const months = state.months.map(month => ({
105
- ...month,
106
- current: month.month === action.month,
107
- }));
108
- return { ...state, months, focusedDay: action.focusedDay };
109
- }
110
- case "SELECT_DAY_INITIAL":
111
- const initialDaySlots = getSlotsByDay(state.slots, action.day, action.tzid);
112
109
  return {
113
110
  ...state,
114
- selectedDay: action.day,
115
- focusedDay: action.day,
116
- columnView: "slots",
117
- daySlots: initialDaySlots,
118
- populated: true,
111
+ focusedDay: action.focusedDay,
119
112
  };
120
- case "SELECT_DAY":
113
+ }
114
+
115
+ case "SELECT_DAY": {
121
116
  const daySlots = getSlotsByDay(state.slots, action.day, action.tzid);
122
117
  return {
123
118
  ...state,
@@ -128,16 +123,33 @@ export const statusReducer = (state, action) => {
128
123
  daySlots,
129
124
  populated: true,
130
125
  };
131
- case "SELECT_MONTH":
126
+ }
127
+ case "SELECT_MONTH": {
132
128
  const months = state.months.map(month => ({
133
129
  ...month,
134
130
  current: month.month === action.month,
135
131
  }));
132
+
133
+ let focusedDay = action.focusedDay ?? false;
134
+
135
+ const monthlyView = {
136
+ month: action.month,
137
+ days: parseTimeSlots({
138
+ tzid: action.tzid,
139
+ slots: state.slots,
140
+ month: action.month,
141
+ startDay: state.startDay,
142
+ }),
143
+ };
144
+
136
145
  return {
137
146
  ...state,
138
147
  months,
139
- focusedDay: false,
148
+ focusedDay,
149
+ monthlyView,
140
150
  };
151
+ }
152
+
141
153
  case "SELECT_SLOT": {
142
154
  state = {
143
155
  ...state,
@@ -151,24 +163,26 @@ export const statusReducer = (state, action) => {
151
163
  }
152
164
  return state;
153
165
  }
166
+
154
167
  case "SELECT_TZID": {
155
168
  const availableDays = getAvailableDays(state.slots, action.tzid);
156
169
  const daySlots = getSlotsByDay(state.slots, state.selectedDay, action.tzid);
157
- const monthlySlots = state.months.map(month => ({
158
- month: month.month,
170
+
171
+ const monthlyView = {
172
+ month: state.monthlyView.month,
159
173
  days: parseTimeSlots({
160
174
  slots: state.slots,
161
- month: month.month,
175
+ month: state.monthlyView.month,
162
176
  tzid: action.tzid,
163
177
  startDay: state.startDay,
164
178
  }),
165
- }));
179
+ };
166
180
 
167
181
  state = {
168
182
  ...state,
169
183
  availableDays,
170
184
  daySlots,
171
- monthlySlots,
185
+ monthlyView,
172
186
  };
173
187
 
174
188
  if (state.selected.start) {
@@ -16,7 +16,13 @@ export const objectToArray = (object, keepKey = true) => {
16
16
  export const truncateString = (str, length = 240) =>
17
17
  str.length <= length - 2 ? str : `${str.slice(0, length)}...`;
18
18
 
19
- export const uniqueItems = array => array.filter((elem, pos, arr) => arr.indexOf(elem) == pos);
19
+ export const uniqueItems = array => {
20
+ if (!Array.isArray(array)) {
21
+ return [];
22
+ }
23
+
24
+ return [...new Set(array)];
25
+ };
20
26
 
21
27
  export const objectIsEmpty = obj => {
22
28
  for (let key in obj) {