@@ -17,8 +17,15 @@ import { loadChallengeDetails } from './actions/challenges'
17
17
import { connect } from 'react-redux'
18
18
import { checkAllowedRoles } from './util/tc'
19
19
import { setCookie , removeCookie , isBetaMode } from './util/cookie'
20
+ import IdleTimer from 'react-idle-timer'
21
+ import modalStyles from './styles/modal.module.scss'
22
+ import ConfirmationModal from './components/Modal/ConfirmationModal'
20
23
21
- const { ACCOUNTS_APP_LOGIN_URL } = process . env
24
+ const { ACCOUNTS_APP_LOGIN_URL , IDLE_TIMEOUT_MINUTES , IDLE_TIMEOUT_GRACE_MINUTES , COMMUNITY_APP_URL } = process . env
25
+
26
+ const theme = {
27
+ container : modalStyles . modalContainer
28
+ }
22
29
23
30
class RedirectToChallenge extends React . Component {
24
31
componentWillMount ( ) {
@@ -59,6 +66,19 @@ RedirectToChallenge.propTypes = {
59
66
const ConnectRedirectToChallenge = connect ( mapStateToProps , mapDispatchToProps ) ( RedirectToChallenge )
60
67
61
68
class Routes extends React . Component {
69
+ constructor ( props ) {
70
+ super ( props )
71
+ this . idleTimer = null
72
+ this . handleOnIdle = this . handleOnIdle . bind ( this )
73
+
74
+ this . logoutIntervalRef = null
75
+ this . state = {
76
+ showIdleModal : false ,
77
+ logsoutIn : IDLE_TIMEOUT_GRACE_MINUTES * 60 , // convert to seconds
78
+ logoutIntervalRef : null
79
+ }
80
+ }
81
+
62
82
componentWillMount ( ) {
63
83
this . checkAuth ( )
64
84
}
@@ -87,68 +107,99 @@ class Routes extends React.Component {
87
107
}
88
108
}
89
109
110
+ handleOnIdle ( ) {
111
+ this . idleTimer . pause ( )
112
+ const intervalId = setInterval ( ( ) => {
113
+ const remaining = this . state . logsoutIn
114
+ if ( remaining > 0 ) {
115
+ this . setState ( state => ( { ...state , logsoutIn : remaining - 1 } ) )
116
+ } else {
117
+ window . location = `${ COMMUNITY_APP_URL } /logout`
118
+ }
119
+ } , 1000 )
120
+
121
+ this . setState ( state => ( { ...state , showIdleModal : true , logoutIntervalRef : intervalId } ) )
122
+ }
123
+
90
124
render ( ) {
91
125
if ( ! this . props . isLoggedIn ) {
92
126
return null
93
127
}
94
128
95
- let isAllowed = checkAllowedRoles ( _ . get ( decodeToken ( this . props . token ) , 'roles' ) )
129
+ const isAllowed = checkAllowedRoles ( _ . get ( decodeToken ( this . props . token ) , 'roles' ) )
130
+ const modal = ( < ConfirmationModal
131
+ theme = { theme }
132
+ title = 'Session Timeout'
133
+ message = { `You've been idle for quite sometime. You'll be automatically logged out in ${ this . state . logsoutIn >= 60 ? Math . ceil ( this . state . logsoutIn / 60 ) + ' minute(s).' : this . state . logsoutIn + ' second(s)' } ` }
134
+ confirmText = 'Logout Now'
135
+ cancelText = 'Resume Session'
136
+ onCancel = { ( ) => {
137
+ clearInterval ( this . state . logoutIntervalRef )
138
+ if ( this . idleTimer . isIdle ( ) ) {
139
+ this . idleTimer . resume ( )
140
+ this . idleTimer . reset ( )
141
+ this . setState ( state => ( {
142
+ ...state , showIdleModal : false , logsoutIn : IDLE_TIMEOUT_GRACE_MINUTES * 60
143
+ } ) )
144
+ }
145
+ } }
146
+ onConfirm = { ( ) => {
147
+ window . location = `${ COMMUNITY_APP_URL } /logout`
148
+ } }
149
+ /> )
96
150
97
- if ( ! isAllowed ) {
98
- let warnMessage = 'You are not authorized to use this application'
99
- return (
100
- < Switch >
151
+ return (
152
+ < IdleTimer ref = { ref => { this . idleTimer = ref } } timeout = { 1000 * 60 * IDLE_TIMEOUT_MINUTES } onIdle = { this . handleOnIdle } debounce = { 250 } >
153
+ { ! isAllowed && < Switch >
101
154
< Route exact path = '/'
102
155
render = { ( ) => renderApp (
103
- < Challenges menu = 'NULL' warnMessage = { warnMessage } /> ,
156
+ < Challenges menu = 'NULL' warnMessage = { 'You are not authorized to use this application' } /> ,
104
157
< TopBarContainer /> ,
105
158
< Sidebar />
106
159
) ( ) }
107
160
/>
108
161
< Redirect to = '/' />
109
- </ Switch >
110
- )
111
- }
112
-
113
- return (
114
- < Switch >
115
- < Route exact path = '/'
116
- render = { ( ) => renderApp (
117
- < Challenges menu = 'NULL' /> ,
118
- < TopBarContainer /> ,
119
- < Sidebar />
120
- ) ( ) }
121
- />
122
- < Route exact path = '/self-service'
123
- render = { ( ) => renderApp (
124
- < Challenges selfService /> ,
125
- < TopBarContainer /> ,
126
- < Sidebar selfService />
127
- ) ( ) }
128
- />
129
- < Route exact path = '/projects/:projectId/challenges/new'
130
- render = { ( { match } ) => renderApp (
131
- < ChallengeEditor /> ,
132
- < TopBarContainer /> ,
133
- < Sidebar projectId = { match . params . projectId } menu = { 'New Challenge' } />
134
- ) ( ) } />
135
- < Route exact path = '/challenges/:challengeId' component = { ConnectRedirectToChallenge } />
136
- < Route
137
- path = '/projects/:projectId/challenges/:challengeId'
138
- render = { ( { match } ) => renderApp (
139
- < ChallengeEditor /> ,
140
- < TopBarContainer /> ,
141
- < Sidebar projectId = { match . params . projectId } menu = { 'New Challenge' } />
142
- ) ( ) } />
143
- < Route exact path = '/projects/:projectId/challenges'
144
- render = { ( { match } ) => renderApp (
145
- < Challenges projectId = { match . params . projectId } /> ,
146
- < TopBarContainer projectId = { match . params . projectId } /> ,
147
- < Sidebar projectId = { match . params . projectId } />
148
- ) ( ) } />
149
- { /* If path is not defined redirect to landing page */ }
150
- < Redirect to = '/' />
151
- </ Switch >
162
+ </ Switch > }
163
+ { isAllowed && < Switch >
164
+ < Route exact path = '/'
165
+ render = { ( ) => renderApp (
166
+ < Challenges menu = 'NULL' /> ,
167
+ < TopBarContainer /> ,
168
+ < Sidebar />
169
+ ) ( ) }
170
+ />
171
+ < Route exact path = '/self-service'
172
+ render = { ( ) => renderApp (
173
+ < Challenges selfService /> ,
174
+ < TopBarContainer /> ,
175
+ < Sidebar selfService />
176
+ ) ( ) }
177
+ />
178
+ < Route exact path = '/projects/:projectId/challenges/new'
179
+ render = { ( { match } ) => renderApp (
180
+ < ChallengeEditor /> ,
181
+ < TopBarContainer /> ,
182
+ < Sidebar projectId = { match . params . projectId } menu = { 'New Challenge' } />
183
+ ) ( ) } />
184
+ < Route exact path = '/challenges/:challengeId' component = { ConnectRedirectToChallenge } />
185
+ < Route
186
+ path = '/projects/:projectId/challenges/:challengeId'
187
+ render = { ( { match } ) => renderApp (
188
+ < ChallengeEditor /> ,
189
+ < TopBarContainer /> ,
190
+ < Sidebar projectId = { match . params . projectId } menu = { 'New Challenge' } />
191
+ ) ( ) } />
192
+ < Route exact path = '/projects/:projectId/challenges'
193
+ render = { ( { match } ) => renderApp (
194
+ < Challenges projectId = { match . params . projectId } /> ,
195
+ < TopBarContainer projectId = { match . params . projectId } /> ,
196
+ < Sidebar projectId = { match . params . projectId } />
197
+ ) ( ) } />
198
+ { /* If path is not defined redirect to landing page */ }
199
+ < Redirect to = '/' />
200
+ </ Switch > }
201
+ { this . state . showIdleModal && modal }
202
+ </ IdleTimer >
152
203
)
153
204
}
154
205
}
0 commit comments