1
- import React from "react" ;
1
+ import React , { useState , useEffect } from "react" ;
2
2
import PT from "prop-types" ;
3
+ import cn from "classnames" ;
3
4
import { useDispatch } from "react-redux" ;
4
5
import { useParams } from "@reach/router" ;
5
6
import { loadPosition } from "../../../actions" ;
6
7
import Button from "components/Button" ;
7
8
import { toastr } from "react-redux-toastr" ;
8
9
9
10
import StepsIndicator from "../../StepsIndicator" ;
11
+ import Spinner from "components/CenteredSpinner" ;
10
12
import { confirmInterview } from "../../../../../services/interviews" ;
11
13
import {
12
14
SCHEDULE_INTERVIEW_STEPS ,
@@ -15,6 +17,7 @@ import {
15
17
POPUP_STAGES ,
16
18
} from "constants" ;
17
19
import "./styles.module.scss" ;
20
+ import { getPrimaryCalendar , isCalendarInSync } from "utils/helpers" ;
18
21
19
22
/**
20
23
* This component is used to get the confirmation before scheduling the interview
@@ -25,12 +28,62 @@ const Confirm = ({
25
28
onGoBack,
26
29
onContinue,
27
30
onShowingLoader,
31
+ userSettings,
32
+ getSettingsModular,
28
33
} ) => {
29
34
const { teamId, positionId } = useParams ( ) ;
35
+ const [ loadingMessage , setLoadingMessage ] = useState ( null ) ;
36
+ const [ syncCalendarTimeoutId , setSyncCalendarTimeoutId ] = useState ( null ) ;
37
+ // flag indicating the 1st attempt to refetch UserMeetingSettings is made, must repeat requests every 10secs
38
+ const [ initiateInterviewConfirmation , setInitiateInterviewConfirmation ] = useState ( null ) ;
39
+ // each timeout equals 10 seconds - so 120 seconds = 2mins = 12 timeouts - this excludes time taken for request response
40
+ const [ totalSyncTimeouts , setTotalSyncTimeouts ] = useState ( 0 ) ;
30
41
const dispatch = useDispatch ( ) ;
31
42
const { handle, id : candidateId } = candidate ;
32
43
const { duration } = scheduleDetails ;
33
44
45
+ // check if primary calendar is syncing or synced and handle accordingly
46
+ useEffect ( ( ) => {
47
+ const primaryCalendar = getPrimaryCalendar ( userSettings ) ;
48
+ const calendarSynced = primaryCalendar ? isCalendarInSync ( primaryCalendar ) : false ;
49
+
50
+ // to know if calendar is ready and synced to request interview, we check for primary calendar
51
+ // & its sync status, combined with either the timeoutid or initiate confirmation flag
52
+ if ( ( primaryCalendar && calendarSynced ) && ( syncCalendarTimeoutId || initiateInterviewConfirmation ) )
53
+ {
54
+ // clear timeout since calendar is now synced
55
+ clearTimeout ( syncCalendarTimeoutId ) ;
56
+
57
+ // reset state
58
+ setSyncCalendarTimeoutId ( null ) ;
59
+ setInitiateInterviewConfirmation ( false ) ;
60
+
61
+ // disable loading indicator
62
+ onSetLoadingMessage ( `Scheduling interview...` ) ;
63
+
64
+ // continue with interview scheduling
65
+ onContinueAhead ( ) ;
66
+ }
67
+ else if ( ( primaryCalendar && ! calendarSynced ) && ( syncCalendarTimeoutId || initiateInterviewConfirmation ) )
68
+ {
69
+ if ( totalSyncTimeouts > 12 )
70
+ {
71
+ onContinue ( POPUP_STAGES . CALENDAR_SYNC_TIMED_OUT ) ;
72
+ }
73
+ else
74
+ {
75
+ // since primary calendar is still not synced, reset timeout
76
+ setSyncCalendarTimeoutId ( setTimeout ( ( ) => getSettingsModular ( ) , 10000 ) ) ;
77
+ setTotalSyncTimeouts ( totalSyncTimeouts + 1 ) ;
78
+ }
79
+ }
80
+
81
+ // clear timeout on component unmount
82
+ return ( ) => clearTimeout ( syncCalendarTimeoutId ) ;
83
+ } , [ userSettings ] ) ;
84
+
85
+ const onSetLoadingMessage = ( text ) => setLoadingMessage ( text ) ;
86
+
34
87
/**
35
88
* This will trigger the API call to the server to request an interview
36
89
*/
@@ -40,6 +93,7 @@ const Confirm = ({
40
93
duration : scheduleDetails . duration ,
41
94
availableTime : scheduleDetails . slots ,
42
95
} ;
96
+ onSetLoadingMessage ( null ) ;
43
97
onShowingLoader ( true ) ;
44
98
45
99
confirmInterview ( candidateId , params )
@@ -53,34 +107,65 @@ const Confirm = ({
53
107
} ) ;
54
108
} ;
55
109
110
+ const inviteInterviewCandidate = ( userSettingsParam ) => {
111
+ let primaryCalendar = getPrimaryCalendar ( userSettingsParam ) ;
112
+ let calendarSynced = isCalendarInSync ( primaryCalendar ) ;
113
+
114
+ if ( primaryCalendar && ! calendarSynced )
115
+ {
116
+ // show loading indicator with message
117
+ onSetLoadingMessage ( `Syncing your new calendar ${ primaryCalendar . email } , it might take a few minutes...` ) ;
118
+
119
+ // fetch UserMeetingSettings in the background, for the 1st time, no timeout is necessary,
120
+ // so we set initiateInterviewConfirmation value to true
121
+ getSettingsModular ( ) ;
122
+ setInitiateInterviewConfirmation ( true ) ;
123
+ }
124
+ else if ( primaryCalendar && calendarSynced )
125
+ {
126
+ onContinueAhead ( ) ;
127
+ }
128
+ }
129
+
56
130
return (
57
- < div styleName = "confirm-wrapper" >
58
- < StepsIndicator steps = { SCHEDULE_INTERVIEW_STEPS } currentStep = "confirm" />
59
- < div styleName = "confirm-text" >
60
- Send a < span styleName = "confirm-text-bold" > { duration } Minute</ span > { " " }
61
- Interview invite to < span styleName = "confirm-text-bold" > { handle } </ span > .
62
- This invite will allow < span styleName = "confirm-text-bold" > { handle } </ span > to select and schedule an interview date
63
- and time based on your availability.
64
- </ div >
65
-
66
- < div styleName = "button-wrapper" >
67
- < Button
68
- styleName = "back-button"
69
- onClick = { ( ) => onGoBack ( ) }
70
- size = { BUTTON_SIZE . MEDIUM }
71
- type = { BUTTON_TYPE . SECONDARY }
72
- >
73
- Back
74
- </ Button >
75
- < Button
76
- onClick = { ( ) => onContinueAhead ( ) }
77
- size = { BUTTON_SIZE . MEDIUM }
78
- type = { BUTTON_TYPE . PRIMARY }
79
- >
80
- Confirm
81
- </ Button >
82
- </ div >
83
- </ div >
131
+ < >
132
+ { loadingMessage && < div
133
+ styleName = { cn ( "spinner-wrapper" , {
134
+ "show-spinner" : loadingMessage ,
135
+ } ) }
136
+ >
137
+ < Spinner stype = "Oval" width = { 80 } height = { 80 } />
138
+ < p styleName = "loading-message" > { loadingMessage } </ p >
139
+ </ div > }
140
+ { ! loadingMessage && < div styleName = "confirm-wrapper" >
141
+ < StepsIndicator steps = { SCHEDULE_INTERVIEW_STEPS } currentStep = "confirm" />
142
+ < div styleName = "confirm-text" >
143
+ Send a < span styleName = "confirm-text-bold" > { duration } Minute</ span > { " " }
144
+ Interview invite to < span styleName = "confirm-text-bold" > { handle } </ span > .
145
+ This invite will allow < span styleName = "confirm-text-bold" > { handle } </ span > to select and schedule an interview date
146
+ and time based on your availability.
147
+ </ div >
148
+
149
+ < div styleName = "button-wrapper" >
150
+ < Button
151
+ styleName = "back-button"
152
+ onClick = { ( ) => onGoBack ( ) }
153
+ size = { BUTTON_SIZE . MEDIUM }
154
+ type = { BUTTON_TYPE . SECONDARY }
155
+ >
156
+ Back
157
+ </ Button >
158
+ < Button
159
+ onClick = { ( ) => inviteInterviewCandidate ( userSettings ) }
160
+ disabled = { initiateInterviewConfirmation }
161
+ size = { BUTTON_SIZE . MEDIUM }
162
+ type = { BUTTON_TYPE . PRIMARY }
163
+ >
164
+ Confirm
165
+ </ Button >
166
+ </ div >
167
+ </ div > }
168
+ </ >
84
169
) ;
85
170
} ;
86
171
@@ -90,6 +175,8 @@ Confirm.propTypes = {
90
175
onGoBack : PT . func ,
91
176
onContinue : PT . func ,
92
177
onShowingLoader : PT . func ,
178
+ userSettings : PT . object ,
179
+ getSettingsModular : PT . func ,
93
180
} ;
94
181
95
182
export default Confirm ;
0 commit comments