@@ -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 modalStyles from './styles/modal.module.scss'
22+ import ConfirmationModal from './components/Modal/ConfirmationModal'
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,99 @@ 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 = ( < 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+ /> )
96150
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 >
101154 < Route exact path = '/'
102155 render = { ( ) => renderApp (
103- < Challenges menu = 'NULL' warnMessage = { warnMessage } /> ,
156+ < Challenges menu = 'NULL' warnMessage = { 'You are not authorized to use this application' } /> ,
104157 < TopBarContainer /> ,
105158 < Sidebar />
106159 ) ( ) }
107160 />
108161 < 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 >
152203 )
153204 }
154205}
0 commit comments