Add login. Fetch user data. Learned basic React + Redux. Needs work.
This commit is contained in:
1 parent
cc6c9e4973
commit
d4d3f12aea
13 files changed
+433
-47
No files matched your search
+5
-2
@@ -16,7 +16,8 @@
|
||||
"react-native-htmlview": "^0.12.0",
|
||||
"react-navigation": "^1.0.0-beta.11",
|
||||
"react-redux": "^5.0.5",
|
||||
"redux": "^3.7.2"
|
||||
"redux": "^3.7.2",
|
||||
"redux-thunk": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "20.0.3",
|
||||
@@ -26,7 +27,9 @@
|
||||
},
|
||||
"jest": {
|
||||
"preset": "react-native",
|
||||
"setupFiles": ["<rootDir>/jest/setup.js"],
|
||||
"setupFiles": [
|
||||
"<rootDir>/jest/setup.js"
|
||||
],
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation|native-base|@shoutem)"
|
||||
]
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
import Dusken from "./Dusken";
|
||||
import React, {Component} from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore } from 'redux'
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import duskenApp from "./reducers";
|
||||
|
||||
let store = createStore(duskenApp);
|
||||
|
||||
let store = createStore(duskenApp, applyMiddleware(thunk));
|
||||
|
||||
export default class App extends Component {
|
||||
render() {
|
||||
|
||||
+3
-1
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import {connect} from "react-redux";
|
||||
import {TabNavigator, StackNavigator} from "react-navigation";
|
||||
|
||||
import LoginScreen from "./screens/LoginScreen";
|
||||
import EventsScreen from "./screens/EventScreen";
|
||||
import EventDetailsScreen from "./screens/EventDetailsScreen";
|
||||
import AboutScreen from "./screens/AboutScreen";
|
||||
@@ -22,7 +23,8 @@ const tabBarOptions = {
|
||||
const tabNav = TabNavigator(tabBarRoutes, tabBarOptions);
|
||||
|
||||
const DuskenContainer = StackNavigator({
|
||||
Root: { screen: tabNav},
|
||||
Root: { screen: tabNav },
|
||||
Login: { screen: LoginScreen },
|
||||
EventDetails: { screen: EventDetailsScreen },
|
||||
});
|
||||
|
||||
|
||||
+124
-6
@@ -1,11 +1,129 @@
|
||||
const LOGIN_START = 'LOGIN_START';
|
||||
const LOGIN_REQUEST = 'LOGIN_REQUEST';
|
||||
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
|
||||
const LOGIN_FAILURE = 'LOGIN_FAILURE';
|
||||
const LOGOUT = 'LOGOUT';
|
||||
const USER_DATA_REQUEST = 'USER_DATA_REQUEST';
|
||||
const USER_DATA_SUCCESS = 'USER_DATA_SUCCESS';
|
||||
const USER_DATA_FAILURE = 'USER_DATA_FAILURE';
|
||||
|
||||
function loginStart() {
|
||||
function loginRequest() {
|
||||
return {
|
||||
type: LOGIN_START
|
||||
type: LOGIN_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
function loginSuccess(username, token) {
|
||||
return {
|
||||
type: LOGIN_SUCCESS,
|
||||
username,
|
||||
token
|
||||
}
|
||||
}
|
||||
|
||||
function loginFailure(errorMessage) {
|
||||
return {
|
||||
type: LOGIN_FAILURE,
|
||||
errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
return {
|
||||
type: LOGOUT
|
||||
}
|
||||
}
|
||||
|
||||
function userDataRequest() {
|
||||
return {
|
||||
type: USER_DATA_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
function userDataSuccess(data) {
|
||||
return {
|
||||
type: USER_DATA_SUCCESS,
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
function userDataFailure(errorMessage) {
|
||||
return {
|
||||
type: USER_DATA_FAILURE,
|
||||
errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
LOGIN_START,
|
||||
loginStart
|
||||
};
|
||||
LOGIN_REQUEST, loginRequest,
|
||||
LOGIN_SUCCESS, loginSuccess,
|
||||
LOGIN_FAILURE, loginFailure,
|
||||
LOGOUT, logout,
|
||||
USER_DATA_REQUEST, userDataRequest,
|
||||
USER_DATA_SUCCESS, userDataSuccess,
|
||||
USER_DATA_FAILURE, userDataFailure,
|
||||
};
|
||||
|
||||
export function requestLogin(username, password) {
|
||||
return (dispatch) => {
|
||||
// We are now logging in
|
||||
dispatch(loginRequest())
|
||||
|
||||
return fetch('http://10.0.0.10:3000/auth/obtain-token/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
'username': username,
|
||||
'password': password
|
||||
})
|
||||
})
|
||||
.then(response => response.json().then(json => ({ json, response })))
|
||||
.then(({json, response}) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json)
|
||||
}
|
||||
return json
|
||||
})
|
||||
.then(
|
||||
data => {
|
||||
dispatch(loginSuccess(username, data.token))
|
||||
},
|
||||
data => {
|
||||
dispatch(loginFailure(data.error || 'Log in failed'))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function requestUserData(token) {
|
||||
return (dispatch) => {
|
||||
// We are now fetching user data
|
||||
dispatch(userDataRequest())
|
||||
|
||||
return fetch('http://10.0.0.10:3000/api/me/', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Token ' + token
|
||||
}
|
||||
})
|
||||
.then(response => response.json().then(json => ({ json, response })))
|
||||
.then(({json, response}) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json)
|
||||
}
|
||||
return json
|
||||
})
|
||||
.then(
|
||||
data => {
|
||||
dispatch(userDataSuccess(data))
|
||||
},
|
||||
data => {
|
||||
dispatch(userDataFailure('Det funka ikke :-('))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+48
-18
@@ -1,20 +1,38 @@
|
||||
import React, { Component } from 'react';
|
||||
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
|
||||
import { Spinner } from 'native-base';
|
||||
|
||||
import { loginStart } from "../../actions";
|
||||
// import { login } from "../../actions";
|
||||
|
||||
export default class Login extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loggedIn: false
|
||||
isAuthenticated: false,
|
||||
isLoggingIn: false,
|
||||
errorMessage: null,
|
||||
email: '',
|
||||
password: ''
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(this.state.loggedIn !== nextProps.loggedIn) {
|
||||
if(this.state.isAuthenticated !== nextProps.isAuthenticated) {
|
||||
this.setState({
|
||||
loggedIn: nextProps.loggedIn
|
||||
isAuthenticated: nextProps.isAuthenticated
|
||||
})
|
||||
if (nextProps.isAuthenticated) {
|
||||
this.onLogin();
|
||||
}
|
||||
}
|
||||
if(this.state.isLoggingIn !== nextProps.isLoggingIn) {
|
||||
this.setState({
|
||||
isLoggingIn: nextProps.isLoggingIn
|
||||
})
|
||||
}
|
||||
if(this.state.errorMessage !== nextProps.errorMessage) {
|
||||
this.setState({
|
||||
errorMessage: nextProps.errorMessage
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -25,48 +43,60 @@ export default class Login extends Component {
|
||||
};
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>The Norwegian Student Society</Text>
|
||||
<Text style={styles.text}>Det Norske Studentersamfund</Text>
|
||||
<Text style={styles.text}>Chateau Neuf</Text>
|
||||
<Image source={pic} style={{width: 200, height: 150, marginBottom: 40}} resizeMode='contain' />
|
||||
<TextInput
|
||||
placeholder="Email"
|
||||
placeholder="E-post"
|
||||
style={styles.input}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
returnKeyType="next"
|
||||
onChangeText={this.handleEmail}
|
||||
onSubmitEditing={(event) => {
|
||||
this.refs.passwordInput.focus();
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Password"
|
||||
placeholder="Passord"
|
||||
style={styles.input}
|
||||
secureTextEntry={true}
|
||||
autoCapitalize="none"
|
||||
ref='passwordInput'
|
||||
onChangeText={this.handlePassword}
|
||||
onSubmitEditing={this.onLoginPress}
|
||||
/>
|
||||
<View style={styles.button}>
|
||||
<Button
|
||||
title="Log in"
|
||||
title="Logg inn"
|
||||
onPress={this.onLoginPress}
|
||||
/>
|
||||
</View>
|
||||
<Text style={{paddingTop: 10}}>{this.loginState()}</Text>
|
||||
{this.spinIfLoggingIn}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
onLoginPress = () => {
|
||||
console.log("YO from onLoginPress");
|
||||
this.props.dispatch(loginStart())
|
||||
spinIfLoggingIn = () => {
|
||||
if (this.state.isLoggingIn) {
|
||||
return <Spinner color="#f58220"></Spinner>
|
||||
}
|
||||
}
|
||||
|
||||
handleEmail = (text) => {
|
||||
this.setState({ email: text })
|
||||
};
|
||||
|
||||
loginState() {
|
||||
if(this.state.loggedIn) {
|
||||
return 'LOGGED IN'
|
||||
}
|
||||
return 'NOT YET'
|
||||
handlePassword = (text) => {
|
||||
this.setState({ password: text })
|
||||
};
|
||||
|
||||
onLoginPress = () => {
|
||||
this.props.requestLogin(this.state.email, this.state.password)
|
||||
};
|
||||
|
||||
onLogin = () => {
|
||||
this.props.onLogin();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +108,7 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
input: {
|
||||
width: 240,
|
||||
width: 320,
|
||||
fontSize: 20,
|
||||
height: 52,
|
||||
},
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import {connect} from "react-redux";
|
||||
import { bindActionCreators } from "redux";
|
||||
import Login from "./Login";
|
||||
import { requestLogin } from "../../actions";
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
loggedIn: state.loggedIn
|
||||
})
|
||||
)(Login);
|
||||
(state) => ({
|
||||
isAuthenticated: state.isAuthenticated,
|
||||
isLoggingIn: state.isLoggingIn,
|
||||
errorMessage: state.errorMessage,
|
||||
}),
|
||||
{ requestLogin }
|
||||
)(Login);
|
||||
@@ -7,6 +7,10 @@ export default class Membership extends Component {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.card}>
|
||||
<View style={styles.button}>
|
||||
<Button onPress={this.onLoginPress} full><NBText>Logg inn</NBText></Button>
|
||||
</View>
|
||||
|
||||
<Text style={styles.paragraph}>Siden 1813 har Det Norske Studentersamfund samlet studenter til kulturell, politisk, faglig og ikke minst sosial aktivitet. Som medlem i DNS er du med på å understreke viktigheten til en felles møteplass for alle Oslos studenter, uavhengig av utdanningsinstitusjon, studie og hvem de er.</Text>
|
||||
<Text style={styles.paragraph}>Vi setter pris på felleskapet som er med på å bygge Chateau Neuf. DNS er en organisasjon av og for medlemmene. Derfor sørger vi for at det blir litt lettere å være student ved å ta en del av kaka for medlemmene våre. Med medlemskap får billigere inngang og servering på alt som skjer på Chateau Neuf.</Text>
|
||||
|
||||
@@ -23,12 +27,16 @@ export default class Membership extends Component {
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
onPurchasePress() {
|
||||
// FIXME: Move this to process.env.DUSKEN_URL and use babel thing to do search replace
|
||||
Linking.openURL('https://medlem.neuf.no');
|
||||
}
|
||||
};
|
||||
|
||||
onLoginPress = () => {
|
||||
this.props.onLoginPress();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,62 @@
|
||||
import React from 'react';
|
||||
import {connect} from "react-redux";
|
||||
|
||||
import Membership from "./Membership";
|
||||
import MembershipProof from "./MembershipProof";
|
||||
import { requestUserData } from "../../actions";
|
||||
|
||||
export default connect()(Membership);
|
||||
class MembershipContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isAuthenticated: props.isAuthenticated,
|
||||
userToken: props.userToken,
|
||||
isFetchingUserData: props.isFetchingUserData,
|
||||
user: props.user,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchUser();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(this.state.isAuthenticated !== nextProps.isAuthenticated) {
|
||||
this.setState({isAuthenticated: nextProps.isAuthenticated})
|
||||
}
|
||||
if(this.state.userToken !== nextProps.userToken) {
|
||||
this.setState({userToken: nextProps.userToken})
|
||||
this.fetchUser();
|
||||
}
|
||||
if(this.state.user !== nextProps.user) {
|
||||
this.setState({user: nextProps.user})
|
||||
}
|
||||
}
|
||||
|
||||
fetchUser = () => {
|
||||
if (!this.state.userToken) { return; }
|
||||
this.props.requestUserData(this.state.userToken)
|
||||
}
|
||||
|
||||
onLoginPress = () => {
|
||||
this.props.onLoginPress();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.user) {
|
||||
return (<MembershipProof user={this.state.user} fetchUser={this.fetchUser}/>)
|
||||
}
|
||||
return (<Membership onLoginPress={this.onLoginPress}/>)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => ({
|
||||
isAuthenticated: state.isAuthenticated,
|
||||
isFetchingUserData: state.isFetchingUserData,
|
||||
userToken: state.userToken,
|
||||
user: state.user,
|
||||
}),
|
||||
{ requestUserData }
|
||||
)(MembershipContainer);
|
||||
@@ -0,0 +1,90 @@
|
||||
import React, {Component} from 'react';
|
||||
import {StyleSheet, Platform, RefreshControl} from "react-native";
|
||||
import {Card, CardItem, Text, Content} from 'native-base';
|
||||
|
||||
export default class MembershipProof extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
user: props.user,
|
||||
refreshing: false,
|
||||
}
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(this.state.user !== nextProps.user) {
|
||||
this.setState({user: nextProps.user})
|
||||
}
|
||||
}
|
||||
|
||||
validTo() {
|
||||
if (this.state.user.last_membership.membership_type == 'lifelong') {
|
||||
return 'Livsvarig'
|
||||
}
|
||||
return this.state.user.last_membership.end_date;
|
||||
}
|
||||
|
||||
statusText() {
|
||||
if (!this.state.user.is_member) {
|
||||
if (!this.state.user.last_membership) {
|
||||
return 'Ikke medlem'
|
||||
}
|
||||
return 'Utløpt'
|
||||
}
|
||||
if (this.state.user.is_volunteer) {
|
||||
return 'Aktiv'
|
||||
}
|
||||
return 'Medlem'
|
||||
}
|
||||
|
||||
fetchUser = () => {
|
||||
this.props.fetchUser()
|
||||
};
|
||||
|
||||
// fetchUser = () => {
|
||||
// this.props.fetchUser().then(() => {
|
||||
// this.setState({refreshing: false});
|
||||
// });
|
||||
// };
|
||||
|
||||
// _onRefresh() {
|
||||
// console.log('onRefresh')
|
||||
// this.setState({refreshing: true});
|
||||
// this.fetchUser()
|
||||
// }
|
||||
|
||||
// refreshControl() {
|
||||
// return (
|
||||
// <RefreshControl
|
||||
// refreshing={this.state.refreshing}
|
||||
// onRefresh={this._onRefresh.bind(this)}
|
||||
// />)
|
||||
// }
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Content>
|
||||
<Card>
|
||||
<CardItem>
|
||||
<Text style={styles.nameText}>{this.state.user.first_name} {this.state.user.last_name}</Text>
|
||||
</CardItem>
|
||||
<CardItem>
|
||||
<Text>{this.statusText()}</Text>
|
||||
</CardItem>
|
||||
<CardItem>
|
||||
<Text>Gyldig til: {this.validTo()}</Text>
|
||||
</CardItem>
|
||||
</Card>
|
||||
</Content>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
nameText: {
|
||||
flex: 1,
|
||||
textAlign: 'center',
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
+51
-5
@@ -1,16 +1,62 @@
|
||||
import {LOGIN_START} from './actions';
|
||||
import {
|
||||
LOGIN_REQUEST,
|
||||
LOGIN_SUCCESS,
|
||||
LOGIN_FAILURE,
|
||||
LOGOUT,
|
||||
USER_DATA_REQUEST,
|
||||
USER_DATA_SUCCESS,
|
||||
USER_DATA_FAILURE,
|
||||
} from './actions';
|
||||
|
||||
const initialState = {
|
||||
loggedIn: false,
|
||||
isAuthenticated: false,
|
||||
isLoggingIn: false,
|
||||
isFetchingUserData: false,
|
||||
userToken: null,
|
||||
user: null,
|
||||
};
|
||||
|
||||
export default function duskenApp(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case LOGIN_START:
|
||||
case LOGIN_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
loggedIn: true // INSTANT LOGIN!
|
||||
isLoggingIn: true
|
||||
});
|
||||
|
||||
case LOGIN_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isLoggingIn: false,
|
||||
errorMessage: action.errorMessage
|
||||
});
|
||||
|
||||
case LOGIN_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isLoggingIn: false,
|
||||
isAuthenticated: true,
|
||||
userToken: action.token
|
||||
});
|
||||
|
||||
case LOGOUT:
|
||||
return initialState
|
||||
|
||||
case USER_DATA_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
isFetchingUserData: true
|
||||
});
|
||||
|
||||
case USER_DATA_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isFetchingUserData: false,
|
||||
errorMessage: action.errorMessage
|
||||
});
|
||||
|
||||
case USER_DATA_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isFetchingUserData: false,
|
||||
user: action.data
|
||||
});
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React, { Component } from 'react';
|
||||
import LoginContainer from "../modules/login/LoginContainer";
|
||||
import Icon from 'react-native-vector-icons/SimpleLineIcons';
|
||||
|
||||
export default class LoginScreen extends Component {
|
||||
static navigationOptions = {
|
||||
title: 'Innlogging',
|
||||
};
|
||||
|
||||
onLogin = () => {
|
||||
this.props.navigation.navigate('Membership');
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<LoginContainer onLogin={this.onLogin} />
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,13 @@ export default class MembershipScreen extends Component {
|
||||
),
|
||||
};
|
||||
|
||||
onLoginPress = () => {
|
||||
this.props.navigation.navigate('Login');
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MembershipContainer />
|
||||
<MembershipContainer onLoginPress={this.onLoginPress} />
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3258,9 +3258,9 @@ react-native-drawer-layout@1.3.2:
|
||||
dependencies:
|
||||
react-native-dismiss-keyboard "1.0.0"
|
||||
|
||||
"react-native-drawer@git+https://github.com/GeekyAnts/react-native-drawer.git":
|
||||
"react-native-drawer@https://github.com/GeekyAnts/react-native-drawer":
|
||||
version "2.3.0"
|
||||
resolved "git+https://github.com/GeekyAnts/react-native-drawer.git#8d9078516d177c9cb02d2579a6860706d2370d25"
|
||||
resolved "https://github.com/GeekyAnts/react-native-drawer#8d9078516d177c9cb02d2579a6860706d2370d25"
|
||||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
tween-functions "^1.0.1"
|
||||
@@ -3278,9 +3278,9 @@ react-native-htmlview@^0.12.0:
|
||||
entities "^1.1.1"
|
||||
htmlparser2-without-node-native "^3.9.0"
|
||||
|
||||
"react-native-keyboard-aware-scroll-view@git+https://github.com/GeekyAnts/react-native-keyboard-aware-scroll-view.git":
|
||||
"react-native-keyboard-aware-scroll-view@https://github.com/GeekyAnts/react-native-keyboard-aware-scroll-view":
|
||||
version "0.2.9"
|
||||
resolved "git+https://github.com/GeekyAnts/react-native-keyboard-aware-scroll-view.git#f4805f254d6cc8e3329f5fb224944401f66655db"
|
||||
resolved "https://github.com/GeekyAnts/react-native-keyboard-aware-scroll-view#f4805f254d6cc8e3329f5fb224944401f66655db"
|
||||
dependencies:
|
||||
create-react-class "^15.6.0"
|
||||
prop-types "^15.5.10"
|
||||
@@ -3507,6 +3507,10 @@ rebound@^0.0.13:
|
||||
version "0.0.13"
|
||||
resolved "https://registry.yarnpkg.com/rebound/-/rebound-0.0.13.tgz#4a225254caf7da756797b19c5817bf7a7941fac1"
|
||||
|
||||
redux-thunk@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
|
||||
|
||||
redux@^3.7.2:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
|
||||
|
||||
Reference in new issue
Block a user