Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
thalia
ThaliApp
Commits
194500ef
Verified
Commit
194500ef
authored
Aug 12, 2018
by
Sébastiaan Versteeg
Browse files
Add status to session with animated activity indicator on login screen
parent
b1ade63b
Changes
7
Hide whitespace changes
Inline
Side-by-side
__tests__/sagas/session.spec.js
View file @
194500ef
...
...
@@ -47,15 +47,6 @@ describe('session saga', () => {
const
error
=
new
Error
(
'
error
'
);
describe
(
'
logging in
'
,
()
=>
{
it
(
'
should show a snackbar on start
'
,
()
=>
expectSaga
(
sessionSaga
)
.
dispatch
(
sessionActions
.
signIn
(
'
username
'
,
'
password
'
))
.
silentRun
()
.
then
(()
=>
{
expect
(
Snackbar
.
show
).
toBeCalledWith
(
{
title
:
'
Logging in
'
,
duration
:
Snackbar
.
LENGTH_INDEFINITE
},
);
}));
it
(
'
should put the result data when the request succeeds
'
,
()
=>
expectSaga
(
sessionSaga
)
.
provide
([
[
matchers
.
call
.
like
({
fn
:
apiRequest
,
args
:
[
'
token-auth
'
]
}),
{
token
:
'
abc123
'
}],
...
...
@@ -74,7 +65,6 @@ describe('session saga', () => {
.
dispatch
(
sessionActions
.
signIn
(
'
username
'
,
'
password
'
))
.
silentRun
()
.
then
(()
=>
{
expect
(
Snackbar
.
dismiss
).
toBeCalled
();
expect
(
Snackbar
.
show
).
toBeCalledWith
(
{
title
:
'
Login successful
'
},
);
...
...
@@ -94,6 +84,14 @@ describe('session saga', () => {
]);
}));
it
(
'
should put token invalid when the request fails
'
,
()
=>
expectSaga
(
sessionSaga
)
.
provide
([
[
matchers
.
call
.
fn
(
apiRequest
),
throwError
(
error
)],
])
.
put
(
sessionActions
.
tokenInvalid
())
.
dispatch
(
sessionActions
.
signIn
(
'
username
'
,
'
password
'
))
.
silentRun
());
it
(
'
should show a snackbar when the request fails
'
,
()
=>
expectSaga
(
sessionSaga
)
.
provide
([
[
matchers
.
call
.
fn
(
apiRequest
),
throwError
(
error
)],
...
...
@@ -101,7 +99,6 @@ describe('session saga', () => {
.
dispatch
(
sessionActions
.
signIn
(
'
username
'
,
'
password
'
))
.
silentRun
()
.
then
(()
=>
{
expect
(
Snackbar
.
dismiss
).
toBeCalled
();
expect
(
Snackbar
.
show
).
toBeCalledWith
(
{
title
:
'
Login failed
'
},
);
...
...
__tests__/ui/components/navigator/Sidebar.spec.js
View file @
194500ef
...
...
@@ -24,7 +24,7 @@ describe('Sidebar component', () => {
it
(
'
renders correctly
'
,
()
=>
{
const
tree
=
renderer
.
create
(
<
Sidebar
store
=
{
store
}
navigation
=
{
mockNavigation
}
/>
)
.
create
(
<
Sidebar
store
=
{
store
}
navigation
=
{
mockNavigation
}
activeItemKey
=
"
unknown
"
/>
)
.
toJSON
();
expect
(
tree
).
toMatchSnapshot
();
});
...
...
app/app.js
View file @
194500ef
import
React
,
{
Component
}
from
'
react
'
;
import
{
Linking
,
Platform
}
from
'
react-native
'
;
import
{
Linking
,
Platform
,
NativeModules
}
from
'
react-native
'
;
import
{
applyMiddleware
,
createStore
}
from
'
redux
'
;
import
{
Provider
}
from
'
react-redux
'
;
import
{
I18nextProvider
}
from
'
react-i18next
'
;
...
...
@@ -18,6 +18,13 @@ import * as deepLinkingActions from './actions/deepLinking';
import
{
register
}
from
'
./actions/pushNotifications
'
;
import
NavigationService
from
'
./navigation
'
;
const
{
UIManager
}
=
NativeModules
;
/* istanbul ignore next */
// eslint-disable-next-line no-unused-expressions
UIManager
.
setLayoutAnimationEnabledExperimental
&&
UIManager
.
setLayoutAnimationEnabledExperimental
(
true
);
const
sagaMiddleware
=
createSagaMiddleware
();
const
store
=
createStore
(
reducers
,
applyMiddleware
(
sagaMiddleware
));
sagaMiddleware
.
run
(
sagas
);
...
...
app/reducers/session.js
View file @
194500ef
...
...
@@ -2,7 +2,12 @@ import { defaultProfileImage } from '../utils/url';
import
*
as
sessionActions
from
'
../actions/session
'
;
export
const
STATUS_SIGNED_OUT
=
'
SIGNED_OUT
'
;
export
const
STATUS_SIGNED_IN
=
'
SIGNED_IN
'
;
export
const
STATUS_SIGNING_IN
=
'
SIGNING_IN
'
;
const
initialState
=
{
status
:
STATUS_SIGNED_OUT
,
token
:
''
,
username
:
''
,
displayName
:
''
,
...
...
@@ -11,9 +16,15 @@ const initialState = {
export
default
function
session
(
state
=
initialState
,
action
=
{})
{
switch
(
action
.
type
)
{
case
sessionActions
.
SIGN_IN
:
return
{
...
state
,
status
:
STATUS_SIGNING_IN
,
};
case
sessionActions
.
SIGNED_IN
:
return
{
...
state
,
status
:
STATUS_SIGNED_IN
,
username
:
action
.
payload
.
username
,
token
:
action
.
payload
.
token
,
};
...
...
app/sagas/session.js
View file @
194500ef
import
{
call
,
put
,
takeEvery
,
select
,
}
from
'
redux-saga/effects
'
;
import
{
delay
}
from
'
redux-saga
'
;
import
{
AsyncStorage
}
from
'
react-native
'
;
import
Snackbar
from
'
react-native-snackbar
'
;
import
{
Sentry
}
from
'
react-native-sentry
'
;
...
...
@@ -52,8 +53,6 @@ function* init() {
function
*
signIn
(
action
)
{
const
{
user
,
pass
}
=
action
.
payload
;
Snackbar
.
show
({
title
:
'
Logging in
'
,
duration
:
Snackbar
.
LENGTH_INDEFINITE
});
const
data
=
{
method
:
'
POST
'
,
headers
:
{
...
...
@@ -65,6 +64,9 @@ function* signIn(action) {
password
:
pass
,
}),
};
const
currentTimestamp
=
Date
.
now
();
try
{
const
response
=
yield
call
(
apiRequest
,
'
token-auth
'
,
data
);
const
{
token
}
=
response
;
...
...
@@ -76,11 +78,16 @@ function* signIn(action) {
yield
put
(
sessionActions
.
signedIn
(
user
,
token
));
yield
put
(
sessionActions
.
fetchUserInfo
());
yield
put
(
pushNotificationsActions
.
register
());
Snackbar
.
dismiss
();
Snackbar
.
show
({
title
:
'
Login successful
'
});
}
catch
(
e
)
{
// Delay failure to make sure animation is finished
const
now
=
Date
.
now
();
if
(
now
-
currentTimestamp
<
150
)
{
yield
call
(
delay
,
now
-
currentTimestamp
);
}
yield
put
(
sessionActions
.
tokenInvalid
());
Sentry
.
captureException
(
e
);
Snackbar
.
dismiss
();
Snackbar
.
show
({
title
:
'
Login failed
'
});
}
}
...
...
app/ui/screens/user/Login.js
View file @
194500ef
import
React
,
{
Component
}
from
'
react
'
;
import
PropTypes
from
'
prop-types
'
;
import
{
ActivityIndicator
,
Image
,
Keyboard
,
KeyboardAvoidingView
,
...
...
@@ -8,6 +9,7 @@ import {
Text
,
TextInput
,
View
,
LayoutAnimation
,
}
from
'
react-native
'
;
import
{
connect
}
from
'
react-redux
'
;
import
{
translate
}
from
'
react-i18next
'
;
...
...
@@ -18,9 +20,17 @@ import DismissKeyboardView from '../../components/dismissKeyboardView/DismissKey
import
Button
from
'
../../components/button/Button
'
;
import
styles
from
'
./style/Login
'
;
import
Colors
from
'
../../style/Colors
'
;
import
{
STATUS_SIGNING_IN
}
from
'
../../../reducers/session
'
;
const
image
=
require
(
'
../../../assets/img/logo.png
'
);
const
configureNextAnimation
=
()
=>
{
/* istanbul ignore next */
LayoutAnimation
.
configureNext
(
LayoutAnimation
.
create
(
150
,
LayoutAnimation
.
Types
.
linear
,
LayoutAnimation
.
Properties
.
opacity
));
};
class
Login
extends
Component
{
constructor
(
props
)
{
super
(
props
);
...
...
@@ -30,8 +40,61 @@ class Login extends Component {
};
}
componentDidMount
()
{
configureNextAnimation
();
}
render
()
{
const
{
login
,
t
}
=
this
.
props
;
configureNextAnimation
();
const
{
login
,
t
,
status
}
=
this
.
props
;
let
content
=
(
<
View
>
<
View
>
<
TextInput
style
=
{
styles
.
input
}
placeholder
=
{
t
(
'
Username
'
)}
autoCapitalize
=
"
none
"
underlineColorAndroid
=
{
Colors
.
textColour
}
onChangeText
=
{
username
=>
this
.
setState
({
username
})}
/
>
<
TextInput
style
=
{
styles
.
input
}
placeholder
=
{
t
(
'
Password
'
)}
underlineColorAndroid
=
{
Colors
.
textColour
}
autoCapitalize
=
"
none
"
secureTextEntry
onChangeText
=
{
password
=>
this
.
setState
({
password
})}
onSubmitEditing
=
{()
=>
{
login
(
this
.
state
.
username
,
this
.
state
.
password
);
}}
/
>
<
/View
>
<
Button
title
=
{
t
(
'
LOGIN
'
)}
onPress
=
{()
=>
login
(
this
.
state
.
username
,
this
.
state
.
password
)}
color
=
{
Colors
.
darkGrey
}
style
=
{
styles
.
loginButton
}
textStyle
=
{
styles
.
loginButtonText
}
underlayColor
=
{
Colors
.
white
}
/
>
<
Text
style
=
{
styles
.
forgotpass
}
onPress
=
{()
=>
Linking
.
openURL
(
`
${
url
}
/password_reset/`
)}
>
{
t
(
'
Forgot password?
'
)}
<
/Text
>
<
/View
>
);
if
(
status
===
STATUS_SIGNING_IN
)
{
content
=
(
<
ActivityIndicator
style
=
{
styles
.
activityIndicator
}
color
=
{
Colors
.
white
}
size
=
"
large
"
animating
/>
);
}
return
(
<
KeyboardAvoidingView
style
=
{
styles
.
rootWrapper
}
...
...
@@ -42,37 +105,7 @@ class Login extends Component {
contentStyle
=
{
styles
.
wrapper
}
>
<
Image
style
=
{
styles
.
logo
}
source
=
{
image
}
/
>
<
View
>
<
TextInput
style
=
{
styles
.
input
}
placeholder
=
{
t
(
'
Username
'
)}
autoCapitalize
=
"
none
"
underlineColorAndroid
=
{
Colors
.
textColour
}
onChangeText
=
{
username
=>
this
.
setState
({
username
})}
/
>
<
TextInput
style
=
{
styles
.
input
}
placeholder
=
{
t
(
'
Password
'
)}
underlineColorAndroid
=
{
Colors
.
textColour
}
autoCapitalize
=
"
none
"
secureTextEntry
onChangeText
=
{
password
=>
this
.
setState
({
password
})}
onSubmitEditing
=
{()
=>
{
login
(
this
.
state
.
username
,
this
.
state
.
password
);
}}
/
>
<
/View
>
<
Button
title
=
{
t
(
'
LOGIN
'
)}
onPress
=
{()
=>
login
(
this
.
state
.
username
,
this
.
state
.
password
)}
color
=
{
Colors
.
darkGrey
}
style
=
{
styles
.
loginButton
}
textStyle
=
{
styles
.
loginButtonText
}
underlayColor
=
{
Colors
.
white
}
/
>
<
Text
style
=
{
styles
.
forgotpass
}
onPress
=
{()
=>
Linking
.
openURL
(
`
${
url
}
/password_reset/`
)}
>
{
t
(
'
Forgot password?
'
)}
<
/Text
>
{
content
}
<
/DismissKeyboardView
>
<
/KeyboardAvoidingView
>
);
...
...
@@ -82,6 +115,7 @@ class Login extends Component {
Login
.
propTypes
=
{
login
:
PropTypes
.
func
.
isRequired
,
t
:
PropTypes
.
func
.
isRequired
,
status
:
PropTypes
.
string
.
isRequired
,
};
const
mapStateToProps
=
state
=>
state
.
session
;
...
...
app/ui/screens/user/style/Login.js
View file @
194500ef
...
...
@@ -61,6 +61,9 @@ const styles = StyleSheet.create({
flex
:
1
,
justifyContent
:
'
center
'
,
},
activityIndicator
:
{
marginTop
:
24
,
},
});
export
default
styles
;
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment