Strange, it takes too long to sync your calendar {primaryCalendar.email}.
+
+ Please, try re-connecting your calendar by clicking "
+ onContinue(POPUP_STAGES.MANAGE_CALENDAR)}
+ styleName="manage-calendar"
+ >
+ manage connected calendars". Would you have any issues, please don't hesitate to reach out to us
+ by support@topcoder.com.
+
+
+
+
+
+
);
+}
+
+CalendarSyncTimedOut.propTypes = {
+ userSettings: PT.object,
+};
+
+export default CalendarSyncTimedOut;
diff --git a/src/routes/PositionDetails/components/InterviewPopup/CalendarSyncTimedOut/styles.module.scss b/src/routes/PositionDetails/components/InterviewPopup/CalendarSyncTimedOut/styles.module.scss
new file mode 100644
index 0000000..a2bc7e0
--- /dev/null
+++ b/src/routes/PositionDetails/components/InterviewPopup/CalendarSyncTimedOut/styles.module.scss
@@ -0,0 +1,42 @@
+@import "styles/include";
+
+.timedout-wrapper {
+}
+
+.timedout-text {
+ margin-top: 70px;
+ margin-bottom: 232px;
+ @include font-roboto;
+ font-size: 16px;
+ line-height: 26px;
+ font-weight: 400;
+}
+
+.button-wrapper {
+ display: flex;
+ align-items: center;
+ margin-top: 16px;
+ justify-content: flex-end;
+}
+
+.confirm-text-bold {
+ font-weight: bold;
+}
+
+.manage-calendar {
+ color: #0d61bf;
+ @include font-roboto;
+ font-weight: 400;
+ font-size: 14px;
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.topcoder-support-email-link {
+ color: #0d61bf !important;
+ @include font-roboto;
+ font-weight: 400;
+ font-size: 14px;
+ cursor: pointer;
+ text-decoration: underline;
+}
diff --git a/src/routes/PositionDetails/components/InterviewPopup/Confirm/index.jsx b/src/routes/PositionDetails/components/InterviewPopup/Confirm/index.jsx
index a45de9b..82fe067 100644
--- a/src/routes/PositionDetails/components/InterviewPopup/Confirm/index.jsx
+++ b/src/routes/PositionDetails/components/InterviewPopup/Confirm/index.jsx
@@ -1,5 +1,6 @@
-import React from "react";
+import React, { useState, useEffect } from "react";
import PT from "prop-types";
+import cn from "classnames";
import { useDispatch } from "react-redux";
import { useParams } from "@reach/router";
import { loadPosition } from "../../../actions";
@@ -7,6 +8,7 @@ import Button from "components/Button";
import { toastr } from "react-redux-toastr";
import StepsIndicator from "../../StepsIndicator";
+import Spinner from "components/CenteredSpinner";
import { confirmInterview } from "../../../../../services/interviews";
import {
SCHEDULE_INTERVIEW_STEPS,
@@ -15,6 +17,7 @@ import {
POPUP_STAGES,
} from "constants";
import "./styles.module.scss";
+import { getPrimaryCalendar, isCalendarInSync } from "utils/helpers";
/**
* This component is used to get the confirmation before scheduling the interview
@@ -25,23 +28,76 @@ const Confirm = ({
onGoBack,
onContinue,
onShowingLoader,
+ onSetLoadingMessage,
+ userSettings,
+ getSettingsModular,
}) => {
const { teamId, positionId } = useParams();
+ const [loadingMessage, setLoadingMessage] = useState(null);
+ const [syncCalendarTimeoutId, setSyncCalendarTimeoutId] = useState(null);
+ // flag indicating the 1st attempt to refetch UserMeetingSettings is made, must repeat requests every 10secs
+ const [initiateInterviewConfirmation, setInitiateInterviewConfirmation] = useState(null);
+ // each timeout equals 10 seconds - so 120 seconds = 2mins = 12 timeouts - this excludes time taken for request response
+ const [totalSyncTimeouts, setTotalSyncTimeouts] = useState(0);
const dispatch = useDispatch();
const { handle, id: candidateId } = candidate;
const { duration } = scheduleDetails;
+ // check if primary calendar is syncing or synced and handle accordingly
+ useEffect(() => {
+ const primaryCalendar = getPrimaryCalendar(userSettings);
+ const calendarSynced = primaryCalendar ? isCalendarInSync(primaryCalendar) : false;
+
+ // to know if calendar is ready and synced to request interview, we check for primary calendar
+ // & its sync status, combined with either the timeoutid or initiate confirmation flag
+ if ((primaryCalendar && calendarSynced) && (syncCalendarTimeoutId || initiateInterviewConfirmation))
+ {
+ // clear timeout since calendar is now synced
+ clearTimeout(syncCalendarTimeoutId);
+
+ // reset state
+ setSyncCalendarTimeoutId(null);
+ setInitiateInterviewConfirmation(false);
+
+ // continue with interview scheduling
+ onContinueAhead(true);
+ }
+ else if ((primaryCalendar && !calendarSynced) && (syncCalendarTimeoutId || initiateInterviewConfirmation))
+ {
+ if (totalSyncTimeouts > 12)
+ {
+ onContinue(POPUP_STAGES.CALENDAR_SYNC_TIMED_OUT);
+ }
+ else
+ {
+ // since primary calendar is still not synced, reset timeout
+ setSyncCalendarTimeoutId(setTimeout(() => getSettingsModular(), 10000));
+ setTotalSyncTimeouts(totalSyncTimeouts + 1);
+ }
+ }
+
+ // clear timeout on component unmount
+ return () => clearTimeout(syncCalendarTimeoutId);
+ }, [userSettings]);
+
+ const onSetLoadingMessageLocal = (text) => setLoadingMessage(text);
+
/**
* This will trigger the API call to the server to request an interview
*/
- const onContinueAhead = () => {
+ const onContinueAhead = (showSyncCompletedMessage) => {
const params = {
hostTimezone: scheduleDetails.timezone,
duration: scheduleDetails.duration,
availableTime: scheduleDetails.slots,
};
+ onSetLoadingMessageLocal(null);
onShowingLoader(true);
+ // sync completed, let the user know scheduling is underway
+ if (showSyncCompletedMessage)
+ onSetLoadingMessage("Scheduling interview...");
+
confirmInterview(candidateId, params)
.then(() => dispatch(loadPosition(teamId, positionId)))
.then(() => onContinue(POPUP_STAGES.SUCCESS))
@@ -53,34 +109,65 @@ const Confirm = ({
});
};
+ const inviteInterviewCandidate = (userSettingsParam) => {
+ let primaryCalendar = getPrimaryCalendar(userSettingsParam);
+ let calendarSynced = isCalendarInSync(primaryCalendar);
+
+ if (primaryCalendar && !calendarSynced)
+ {
+ // show loading indicator with message
+ onSetLoadingMessageLocal(`Syncing your new calendar ${primaryCalendar.email}, it might take a few minutes...`);
+
+ // fetch UserMeetingSettings in the background, for the 1st time, no timeout is necessary,
+ // so we set initiateInterviewConfirmation value to true
+ getSettingsModular();
+ setInitiateInterviewConfirmation(true);
+ }
+ else if (primaryCalendar && calendarSynced)
+ {
+ onContinueAhead();
+ }
+ }
+
return (
-
-
-
- Send a {duration} Minute{" "}
- Interview invite to {handle}.
- This invite will allow {handle} to select and schedule an interview date
- and time based on your availability.
-
-
-
-
-
-
-
+ <>
+ {loadingMessage &&
+
+
{loadingMessage}
+
}
+ {!loadingMessage &&
+
+
+ Send a {duration} Minute{" "}
+ Interview invite to {handle}.
+ This invite will allow {handle} to select and schedule an interview date
+ and time based on your availability.
+