@@ -17,8 +17,15 @@ import { loadChallengeDetails } from './actions/challenges'
1717import { connect } from 'react-redux'
1818import { checkAllowedRoles } from './util/tc'
1919import { 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'
2023
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+ }
2229
2330class RedirectToChallenge extends React . Component {
2431 componentWillMount ( ) {
@@ -59,6 +66,19 @@ RedirectToChallenge.propTypes = {
5966const ConnectRedirectToChallenge = connect ( mapStateToProps , mapDispatchToProps ) ( RedirectToChallenge )
6067
6168class 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+
6282 componentWillMount ( ) {
6383 this . checkAuth ( )
6484 }
@@ -87,68 +107,97 @@ class Routes extends React.Component {
87107 }
88108 }
89109
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+
90124 render ( ) {
91125 if ( ! this . props . isLoggedIn ) {
92126 return null
93127 }
94128
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+ /> )
96146
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 } >
100149 < Switch >
150+ { ! isAllowed &&
101151 < Route exact path = '/'
102152 render = { ( ) => renderApp (
103- < Challenges menu = 'NULL' warnMessage = { warnMessage } /> ,
153+ < Challenges menu = 'NULL' warnMessage = { 'You are not authorized to use this application' } /> ,
104154 < TopBarContainer /> ,
105155 < Sidebar />
106156 ) ( ) }
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 */ }
108197 < Redirect to = '/' />
109198 </ 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 >
152201 )
153202 }
154203}
0 commit comments