cronofy-elements
Advanced tools
Comparing version 1.45.0 to 1.46.0
{ | ||
"name": "cronofy-elements", | ||
"version": "1.45.0", | ||
"version": "1.46.0", | ||
"description": "Fast track scheduling with Cronofy's embeddable UI Elements", | ||
@@ -5,0 +5,0 @@ "main": "build/npm/CronofyElements.js", |
@@ -12,2 +12,3 @@ import moment from "moment-timezone"; | ||
import { getMonthsInDisplay, parseTimeSlots } from "../utils/calendar"; | ||
import { uniqueItems } from "../../../helpers/utils"; | ||
@@ -66,5 +67,5 @@ export const statusReducer = (state, action) => { | ||
}; | ||
const monthsLoading = removeMonthFromLoading(state.monthsLoading, action.month); | ||
const months = removeMonthFromLoading(state.months, action.month); | ||
state.callback(notification); | ||
return { ...state, monthsLoading }; | ||
return { ...state, months }; | ||
} | ||
@@ -104,6 +105,6 @@ | ||
if (!action.slots.length > 0) { | ||
const monthsLoading = removeMonthFromLoading(state.monthsLoading, action.month); | ||
const months = removeMonthFromLoading(state.months, action.month); | ||
return { | ||
...state, | ||
monthsLoading, | ||
months, | ||
slotFetchCount: state.slotFetchCount + 1, | ||
@@ -113,8 +114,28 @@ }; | ||
// Add action slots to state slots | ||
const slotsObject = state.sequenced_availability | ||
? addSequencedSlotsToObject(state.slots, action.slots) | ||
: addSlotsToObject(state.slots, action.slots); | ||
const availableDays = getAvailableDays(slotsObject, action.tzid); | ||
const monthsLoading = removeMonthFromLoading(state.monthsLoading, action.month); | ||
// since we already know the available days for state slots | ||
// find available days for action.slots and add them to state availableDays | ||
// this way we don't have to loop through the entirety of the state slotsObject | ||
// every time new slots are rendered | ||
const actionSlotsObject = state.sequenced_availability | ||
? addSequencedSlotsToObject({}, action.slots) | ||
: addSlotsToObject({}, action.slots); | ||
const availableDays = uniqueItems([ | ||
...state.availableDays, | ||
...getAvailableDays(actionSlotsObject, action.tzid), | ||
]).sort(); | ||
// if month is passed in, we render that month as done | ||
// this change is for continiously loading slots as opposed to waiting for entire month | ||
// to be loaded | ||
let months = state.months; | ||
if (action.month) { | ||
months = removeMonthFromLoading(state.months, action.month); | ||
} | ||
const injectionPoint = state.sequenced_availability | ||
@@ -127,3 +148,3 @@ ? getLocalDayFromUtc(action.slots[0].sequence[0].start, action.tzid) | ||
availableDays, | ||
monthsLoading, | ||
months, | ||
slots: slotsObject, | ||
@@ -130,0 +151,0 @@ slotFetchCount: state.slotFetchCount + 1, |
@@ -8,3 +8,2 @@ import React, { useMemo } from "react"; | ||
getInitialSelectedTzid, | ||
getMonthsLoadingFromQuery, | ||
getDurationFromQuery, | ||
@@ -41,3 +40,2 @@ } from "./utils/slots"; | ||
const currentMonth = currentMonthObject.format("YYYY-MM"); | ||
const monthsLoading = getMonthsLoadingFromQuery(options.query, options.tzid); | ||
const monthsInView = getMonthsInDisplay(currentMonth, options.config.startDay); | ||
@@ -75,3 +73,2 @@ | ||
monthlyView, | ||
monthsLoading, | ||
selected: false, | ||
@@ -78,0 +75,0 @@ startDay: options.config.startDay, |
@@ -95,2 +95,3 @@ import moment from "moment-timezone"; | ||
current: currentMonth === month, | ||
loading: true, | ||
query: { | ||
@@ -105,4 +106,6 @@ ...query, | ||
export const getSlots = ({ query, auth, tzid, slots = [] }) => | ||
getAvailability( | ||
export const getSlots = ({ query, auth, tzid, sequence = false }) => { | ||
const availabilityFetch = sequence ? getSequencedAvailability : getAvailability; | ||
return availabilityFetch( | ||
auth.token, | ||
@@ -125,77 +128,6 @@ auth.domains.apiDomain, | ||
// result is not in the correct format: | ||
const returnedSlots = parseSlotsResult(res); | ||
const allSlots = [...slots, ...returnedSlots]; | ||
if (returnedSlots.length < 512) return [...slots, ...returnedSlots]; | ||
// If we get here, the API has returned the maximum number | ||
// of slots allowed, so we need to crop the query and try | ||
// again to ensure we haven't missed any slots. | ||
const startOfLastSlot = returnedSlots[returnedSlots.length - 1].start; | ||
const endOfLastPeriod = query.query_periods[query.query_periods.length - 1].end; | ||
const boundsForCropping = { | ||
start: startOfLastSlot, | ||
end: endOfLastPeriod, | ||
}; | ||
const croppedPeriods = cropPeriodsArbitrarily(query.query_periods, boundsForCropping); | ||
const croppedQuery = { ...query, query_periods: croppedPeriods }; | ||
// Rerun the query | ||
return getSlots({ | ||
query: croppedQuery, | ||
auth, | ||
tzid, | ||
slots: allSlots, | ||
}); | ||
return parseSlotsResult(res); | ||
}); | ||
}; | ||
export const getSequencedSlots = ({ query, auth, tzid, slots = [] }) => | ||
getSequencedAvailability( | ||
auth.token, | ||
auth.domains.apiDomain, | ||
query, | ||
"DateTimePicker", | ||
tzid, | ||
auth.demo | ||
).then(res => { | ||
if (res.errors) { | ||
throw { | ||
type: 422, | ||
message: errorMessages[422].message, | ||
body: res.errors, | ||
docsSlug: errorMessages[422].docsSlug, | ||
}; | ||
} | ||
// This will intentionally throw an error if the | ||
// result is not in the correct format: | ||
const returnedSlots = parseSlotsResult(res); | ||
const allSlots = [...slots, ...returnedSlots]; | ||
if (returnedSlots.length < 512) return [...slots, ...returnedSlots]; | ||
// If we get here, the API has returned the maximum number | ||
// of slots allowed, so we need to crop the query and try | ||
// again to ensure we haven't missed any slots. | ||
const startOfLastSlot = returnedSlots[returnedSlots.length - 1].start; | ||
const endOfLastPeriod = query.query_periods[query.query_periods.length - 1].end; | ||
const boundsForCropping = { | ||
start: startOfLastSlot, | ||
end: endOfLastPeriod, | ||
}; | ||
const croppedPeriods = cropPeriodsArbitrarily(query.query_periods, boundsForCropping); | ||
const croppedQuery = { ...query, query_periods: croppedPeriods }; | ||
// Rerun the query | ||
return getSequencedSlots({ | ||
query: croppedQuery, | ||
auth, | ||
tzid, | ||
slots: allSlots, | ||
}); | ||
}); | ||
export const parseQuery = query => { | ||
@@ -202,0 +134,0 @@ if (!query.bookable_events) { |
import React, { useEffect, useRef, useState } from "react"; | ||
import moment from "moment"; | ||
import { getSequencedSlots, getSlots } from "./utils/slots"; | ||
import { cropPeriodsArbitrarily, getSlots } from "./utils/slots"; | ||
@@ -27,16 +27,22 @@ import Calendar from "./Calendar"; | ||
const confirmButtonRef = useRef(); | ||
const firstRender = useRef(true); | ||
useEffect(() => { | ||
if (!status.query.query_periods) { | ||
return; | ||
} | ||
const fetchMonthSlots = (query, month) => { | ||
return getSlots({ | ||
query, | ||
auth: status.auth, | ||
tzid: status.tzid, | ||
sequence: status.sequenced_availability, | ||
}) | ||
.then(res => { | ||
if (res.length < 512) { | ||
dispatchStatus({ | ||
type: "SET_SLOTS", | ||
slots: res, | ||
tzid: tz.selectedTzid.tzid, | ||
month, | ||
}); | ||
return; | ||
} | ||
const fetchMonthSlots = (query, month) => { | ||
const fetch = status.sequenced_availability ? getSequencedSlots : getSlots; | ||
return fetch({ | ||
query, | ||
auth: status.auth, | ||
tzid: status.tzid, | ||
}).then(res => { | ||
dispatchStatus({ | ||
@@ -46,23 +52,55 @@ type: "SET_SLOTS", | ||
tzid: tz.selectedTzid.tzid, | ||
month: month, | ||
}); | ||
// If we get here, the API has returned the maximum number | ||
// of slots allowed, so we need to crop the query and try | ||
// again to ensure we haven't missed any slots. | ||
const startOfLastSlot = res[res.length - 1].start; | ||
const endOfLastPeriod = query.query_periods[query.query_periods.length - 1].end; | ||
const boundsForCropping = { | ||
start: startOfLastSlot, | ||
end: endOfLastPeriod, | ||
}; | ||
const croppedPeriods = cropPeriodsArbitrarily( | ||
query.query_periods, | ||
boundsForCropping | ||
); | ||
const croppedQuery = { ...query, query_periods: croppedPeriods }; | ||
// Rerun the query | ||
return fetchMonthSlots(croppedQuery, month); | ||
}) | ||
.catch(error => { | ||
if (error.type === 422) { | ||
dispatchStatus({ | ||
type: "ERROR_LOADING_SLOTS", | ||
error, | ||
month, | ||
}); | ||
return; | ||
} | ||
dispatchStatus({ type: "ERROR_GETTING_SLOTS", error }); | ||
}); | ||
}; | ||
}; | ||
useEffect(() => { | ||
if (!status.query.query_periods) { | ||
return; | ||
} | ||
const currentMonth = status.months.find(m => m.current); | ||
const croppedMonths = status.months.filter(el => { | ||
return status.monthlyView.monthsInView.some(f => { | ||
return f === el.month && el.loading && !el.current; | ||
}); | ||
}); | ||
// Get slots for current month | ||
fetchMonthSlots(currentMonth.query, currentMonth.month) | ||
.then(() => { | ||
// Get slots for remianing months | ||
const remainingMonths = status.months.filter(month => !month.current); | ||
remainingMonths.forEach(month => | ||
fetchMonthSlots(month.query, month.month).catch(res => { | ||
dispatchStatus({ | ||
type: "ERROR_LOADING_SLOTS", | ||
error: res, | ||
month: month.month, | ||
}); | ||
}) | ||
); | ||
// Get slots for remaining months | ||
croppedMonths.forEach(month => fetchMonthSlots(month.query, month.month)); | ||
}) | ||
@@ -75,2 +113,22 @@ .catch(error => { | ||
useEffect(() => { | ||
//stops this from firing on first render which would double up the initial API calls | ||
if (firstRender.current) { | ||
firstRender.current = false; | ||
return; | ||
} | ||
if (status.monthlyView.monthsInView) { | ||
//check if any of the months in view haven't been fetched yet | ||
const croppedMonths = status.months.filter(el => { | ||
return status.monthlyView.monthsInView.some(f => { | ||
return f === el.month && el.loading === true; | ||
}); | ||
}); | ||
//if they haven't, fetch them. | ||
if (croppedMonths.length > 0) { | ||
croppedMonths.forEach(month => fetchMonthSlots(month.query, month.month)); | ||
} | ||
} | ||
}, [status.monthlyView.monthsInView]); | ||
useEffect(() => { | ||
if (!status.slotInjectionPoint) { | ||
@@ -103,2 +161,11 @@ return; | ||
// No slots found but not all months called so try the next month | ||
if (!finishedCallingAllSlots && !hasAvailableDays && fetchCount >= 2) { | ||
const croppedMonths = status.months.filter(el => { | ||
return el.loading === true; | ||
}); | ||
fetchMonthSlots(croppedMonths[0].query, croppedMonths[0].month); | ||
return; | ||
} | ||
// No slots available after all queries have been made | ||
@@ -124,11 +191,21 @@ if (finishedCallingAllSlots && !hasAvailableDays) { | ||
// For allowing only this to be called once | ||
// For allowing only this to be called once slots have been fetched | ||
// Since we know the first month that is being fetched will be for the initial selected day | ||
if (status.selectedDay && fetchCount === 1) { | ||
dispatchStatus({ | ||
type: "SELECT_DAY", | ||
day: status.selectedDay, | ||
tzid: tz.selectedTzid.tzid, | ||
}); | ||
return; | ||
if (status.selectedDay && fetchCount >= 1) { | ||
const currentMonth = moment(status.selectedDay, "YYYY-MM-DD").format("YYYY-MM"); | ||
const month = status.months.filter(el => { | ||
return el.month === currentMonth; | ||
})[0]; | ||
const monthStillLoading = month ? month.loading : false; | ||
const dayIsAvailable = status.availableDays.includes(status.selectedDay); | ||
//Only set the selectedDay if the month is finished loading or if the day has slots available | ||
if (!monthStillLoading || dayIsAvailable) { | ||
dispatchStatus({ | ||
type: "SELECT_DAY", | ||
day: status.selectedDay, | ||
tzid: tz.selectedTzid.tzid, | ||
}); | ||
return; | ||
} | ||
} | ||
@@ -167,5 +244,5 @@ }, [status.slotFetchCount, status.availableDays]); | ||
useEffect(() => { | ||
if (status.monthsLoading) { | ||
if (status.months) { | ||
for (let month of status.monthlyView.monthsInView) { | ||
const monthLoading = status.monthsLoading.find(m => m.month === month); | ||
const monthLoading = status.months.find(m => m.month === month); | ||
if (monthLoading && monthLoading.loading) { | ||
@@ -179,3 +256,3 @@ setLoadingCalendar(true); | ||
} | ||
}, [status.monthlyView, status.monthsLoading]); | ||
}, [status.monthlyView, status.months]); | ||
@@ -182,0 +259,0 @@ return ( |
@@ -157,5 +157,35 @@ import { statusReducer } from "../../../src/js/components/DateTimePicker/contexts/status-reducer"; | ||
...startingState, | ||
monthsLoading: [ | ||
{ month: "2021-09", loading: true }, | ||
{ month: "2021-10", loading: true }, | ||
months: [ | ||
{ | ||
month: "2021-09", | ||
current: true, | ||
loading: false, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-29T08:00:00Z", | ||
end: "2021-09-30T22:59:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
{ | ||
month: "2021-10", | ||
current: false, | ||
loading: true, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-30T23:00:00Z", | ||
end: "2021-10-04T18:00:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
], | ||
@@ -195,19 +225,43 @@ }; | ||
const newMonthsLoading = [ | ||
const expectedResultForMonths = [ | ||
{ | ||
month: "2021-09", | ||
loading: true, | ||
current: true, | ||
loading: false, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-29T08:00:00Z", | ||
end: "2021-09-30T22:59:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
{ | ||
month: "2021-10", | ||
current: false, | ||
loading: false, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-30T23:00:00Z", | ||
end: "2021-10-04T18:00:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
]; | ||
delete oldState.monthsLoading; | ||
delete oldState.months; | ||
const { monthsLoading, ...newState } = result; | ||
const { months, ...newState } = result; | ||
// These values have been updated | ||
expect(monthsLoading).toStrictEqual(newMonthsLoading); | ||
expect(months).toStrictEqual(expectedResultForMonths); | ||
expect(callbackSpy).toHaveBeenCalled(); | ||
@@ -276,10 +330,34 @@ expect(callbackSpy).toHaveBeenCalledWith(expectedNotification); | ||
const newMonthsLoading = [ | ||
const expectedResultForMonths = [ | ||
{ | ||
month: "2021-09", | ||
current: true, | ||
loading: false, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-29T08:00:00Z", | ||
end: "2021-09-30T22:59:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
{ | ||
month: "2021-10", | ||
current: false, | ||
loading: true, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-30T23:00:00Z", | ||
end: "2021-10-04T18:00:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
@@ -292,3 +370,3 @@ ]; | ||
delete oldState.slotInjectionPoint; | ||
delete oldState.monthsLoading; | ||
delete oldState.months; | ||
@@ -300,3 +378,3 @@ const { | ||
slotInjectionPoint, | ||
monthsLoading, | ||
months, | ||
...newState | ||
@@ -315,3 +393,3 @@ } = result; | ||
expect(slotInjectionPoint).toBe("2021-09-29"); | ||
expect(monthsLoading).toStrictEqual(newMonthsLoading); | ||
expect(months).toStrictEqual(expectedResultForMonths); | ||
@@ -405,10 +483,34 @@ // All other values are unchanged | ||
availableDays: ["2021-09-29", "2021-09-30"], | ||
monthsLoading: [ | ||
months: [ | ||
{ | ||
month: "2021-09", | ||
current: true, | ||
loading: false, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-29T08:00:00Z", | ||
end: "2021-09-30T22:59:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
{ | ||
month: "2021-10", | ||
current: false, | ||
loading: true, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-30T23:00:00Z", | ||
end: "2021-10-04T18:00:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
@@ -418,10 +520,34 @@ ], | ||
const newMonthsLoading = [ | ||
const expectedResultForMonths = [ | ||
{ | ||
month: "2021-09", | ||
current: true, | ||
loading: false, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-29T08:00:00Z", | ||
end: "2021-09-30T22:59:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
{ | ||
month: "2021-10", | ||
current: false, | ||
loading: false, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-30T23:00:00Z", | ||
end: "2021-10-04T18:00:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
@@ -441,3 +567,3 @@ ]; | ||
delete oldState.availableDays; | ||
delete oldState.monthsLoading; | ||
delete oldState.months; | ||
@@ -449,3 +575,3 @@ const { | ||
availableDays, | ||
monthsLoading, | ||
months, | ||
...newState | ||
@@ -463,3 +589,3 @@ } = result; | ||
]); | ||
expect(monthsLoading).toStrictEqual(newMonthsLoading); | ||
expect(months).toStrictEqual(expectedResultForMonths); | ||
@@ -486,10 +612,34 @@ expect(newState).toStrictEqual(oldState); | ||
const newMonthsLoading = [ | ||
const expectedResultForMonths = [ | ||
{ | ||
month: "2021-09", | ||
current: true, | ||
loading: false, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-29T08:00:00Z", | ||
end: "2021-09-30T22:59:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
{ | ||
month: "2021-10", | ||
current: false, | ||
loading: true, | ||
query: { | ||
max_results: 512, | ||
participants: [{ sub: "acc_5f35432b3f07d06753082354" }], | ||
query_periods: [ | ||
{ | ||
start: "2021-09-30T23:00:00Z", | ||
end: "2021-10-04T18:00:00Z", | ||
}, | ||
], | ||
required_duration: { minutes: 60 }, | ||
}, | ||
}, | ||
@@ -502,3 +652,3 @@ ]; | ||
delete oldState.slotInjectionPoint; | ||
delete oldState.monthsLoading; | ||
delete oldState.months; | ||
@@ -510,3 +660,3 @@ const { | ||
slotInjectionPoint, | ||
monthsLoading, | ||
months, | ||
...newState | ||
@@ -525,3 +675,3 @@ } = result; | ||
expect(slotInjectionPoint).toBe("2021-09-29"); | ||
expect(monthsLoading).toStrictEqual(newMonthsLoading); | ||
expect(months).toStrictEqual(expectedResultForMonths); | ||
@@ -690,2 +840,3 @@ // All other values are unchanged | ||
current: false, | ||
loading: false, | ||
query: { | ||
@@ -706,2 +857,3 @@ max_results: 512, | ||
current: true, | ||
loading: true, | ||
query: { | ||
@@ -752,2 +904,3 @@ max_results: 512, | ||
current: false, | ||
loading: false, | ||
query: { | ||
@@ -768,2 +921,3 @@ max_results: 512, | ||
current: true, | ||
loading: true, | ||
query: { | ||
@@ -770,0 +924,0 @@ max_results: 512, |
@@ -319,2 +319,3 @@ import * as utils from "../../src/js/components/DateTimePicker/utils/slots"; | ||
current: true, | ||
loading: true, | ||
query: { | ||
@@ -337,2 +338,3 @@ query_periods: [ | ||
current: false, | ||
loading: true, | ||
query: { | ||
@@ -365,2 +367,3 @@ query_periods: [ | ||
current: true, | ||
loading: true, | ||
query: { | ||
@@ -383,2 +386,3 @@ query_periods: [ | ||
current: false, | ||
loading: true, | ||
query: { | ||
@@ -410,2 +414,3 @@ query_periods: [ | ||
current: true, | ||
loading: true, | ||
query: { | ||
@@ -428,2 +433,3 @@ query_periods: [ | ||
current: false, | ||
loading: true, | ||
query: { | ||
@@ -457,2 +463,3 @@ query_periods: [ | ||
current: false, | ||
loading: true, | ||
query: { | ||
@@ -475,2 +482,3 @@ query_periods: [ | ||
current: true, | ||
loading: true, | ||
query: { | ||
@@ -477,0 +485,0 @@ query_periods: [ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3319666
27585
60