@@ -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 AlertModal from './components/Modal/AlertModal'
22
+ import modalStyles from './styles/modal.module.scss'
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,97 @@ 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 = ( < AlertModal
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
+ closeText = 'Resume Session'
135
+ onClose = { ( ) => {
136
+ clearInterval ( this . state . logoutIntervalRef )
137
+ if ( this . idleTimer . isIdle ( ) ) {
138
+ this . idleTimer . resume ( )
139
+ this . idleTimer . reset ( )
140
+ this . setState ( state => ( {
141
+ ...state , showIdleModal : false , logsoutIn : 120
142
+ } ) )
143
+ }
144
+ } }
145
+ /> )
96
146
97
- if ( ! isAllowed ) {
98
- let warnMessage = 'You are not authorized to use this application'
99
- return (
147
+ return (
148
+ < IdleTimer ref = { ref => { this . idleTimer = ref } } timeout = { 1000 * 60 * IDLE_TIMEOUT_MINUTES } onIdle = { this . handleOnIdle } debounce = { 250 } >
100
149
< Switch >
150
+ { ! isAllowed &&
101
151
< Route exact path = '/'
102
152
render = { ( ) => renderApp (
103
- < Challenges menu = 'NULL' warnMessage = { warnMessage } /> ,
153
+ < Challenges menu = 'NULL' warnMessage = { 'You are not authorized to use this application' } /> ,
104
154
< TopBarContainer /> ,
105
155
< Sidebar />
106
156
) ( ) }
107
- />
157
+ /> }
158
+
159
+ { isAllowed && < >
160
+ < Route exact path = '/'
161
+ render = { ( ) => renderApp (
162
+ < Challenges menu = 'NULL' /> ,
163
+ < TopBarContainer /> ,
164
+ < Sidebar />
165
+ ) ( ) }
166
+ />
167
+ < Route exact path = '/self-service'
168
+ render = { ( ) => renderApp (
169
+ < Challenges selfService /> ,
170
+ < TopBarContainer /> ,
171
+ < Sidebar selfService />
172
+ ) ( ) }
173
+ />
174
+ < Route exact path = '/projects/:projectId/challenges/new'
175
+ render = { ( { match } ) => renderApp (
176
+ < ChallengeEditor /> ,
177
+ < TopBarContainer /> ,
178
+ < Sidebar projectId = { match . params . projectId } menu = { 'New Challenge' } />
179
+ ) ( ) } />
180
+ < Route exact path = '/challenges/:challengeId' component = { ConnectRedirectToChallenge } />
181
+ < Route
182
+ path = '/projects/:projectId/challenges/:challengeId'
183
+ render = { ( { match } ) => renderApp (
184
+ < ChallengeEditor /> ,
185
+ < TopBarContainer /> ,
186
+ < Sidebar projectId = { match . params . projectId } menu = { 'New Challenge' } />
187
+ ) ( ) } />
188
+ < Route exact path = '/projects/:projectId/challenges'
189
+ render = { ( { match } ) => renderApp (
190
+ < Challenges projectId = { match . params . projectId } /> ,
191
+ < TopBarContainer projectId = { match . params . projectId } /> ,
192
+ < Sidebar projectId = { match . params . projectId } />
193
+ ) ( ) } />
194
+ </ > }
195
+
196
+ { /* If path is not defined redirect to landing page */ }
108
197
< Redirect to = '/' />
109
198
</ 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 >
199
+ { this . state . showIdleModal && modal }
200
+ </ IdleTimer >
152
201
)
153
202
}
154
203
}
0 commit comments