Skip to content

Commit f279333

Browse files
committed
Initial commit.
0 parents  commit f279333

34 files changed

+8787
-0
lines changed

.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CLIENT_ID=
2+
AUTHORIZATION_ENDPOINT_URL=https://auth.monzo.com
3+
TOKEN_ENDPOINT_URL=https://murmuring-mesa-43528.herokuapp.com/oauth/token
4+
MONZO_API_URL=https://murmuring-mesa-43528.herokuapp.com

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
build

.eslintrc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"parser": "babel-eslint",
3+
"parserOptions": {
4+
"ecmaVersion": 6,
5+
"sourceType": "module",
6+
"ecmaFeatures": {
7+
"jsx": true,
8+
"generators": true,
9+
"experimentalObjectRestSpread": true
10+
}
11+
},
12+
"plugins": ["import", "jsx-a11y", "react"],
13+
"settings": {
14+
"react": {
15+
"createClass": "createReactClass",
16+
"pragma": "React",
17+
"version": "16.0",
18+
"flowVersion": "0.53"
19+
},
20+
"propWrapperFunctions": [ "forbidExtraProps" ]
21+
},
22+
"env": {
23+
"es6": true,
24+
"browser": true,
25+
"jest": true,
26+
"node": true,
27+
"mocha": true
28+
},
29+
"extends": [
30+
"es2015",
31+
"eslint:recommended",
32+
"plugin:react/recommended"
33+
],
34+
"rules": {
35+
"comma-dangle": ["error", "always-multiline"],
36+
"eol-last": ["error", "always"],
37+
"indent": ["error", 2, { "SwitchCase": 1 }],
38+
"object-curly-spacing": ["error", "always"],
39+
"quotes": ["error", "double"],
40+
"semi": ["error"],
41+
"space-before-blocks": ["error", "always"],
42+
"react/jsx-uses-react": "error",
43+
"react/jsx-uses-vars": "error"
44+
}
45+
}

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# See https://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# expo
4+
.expo/
5+
6+
# dependencies
7+
/node_modules
8+
9+
# misc
10+
.env.local
11+
.env.development.local
12+
.env.test.local
13+
.env.production.local
14+
15+
npm-debug.log*
16+
yarn-debug.log*
17+
yarn-error.log*
18+
19+
*.jks
20+
*.p12
21+
*.key
22+
*.mobileprovision
23+
*.orig.*
24+
web-build/
25+
web-report/

.vscode/settings.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"editor.renderWhitespace": "all",
3+
"editor.tabSize": 2,
4+
"eslint.alwaysShowStatus": true,
5+
"eslint.autoFixOnSave": true,
6+
"eslint.packageManager": "yarn",
7+
"eslint.provideLintTask": true,
8+
"files.trimTrailingWhitespace": true,
9+
"files.insertFinalNewline": true,
10+
"git.ignoreLimitWarning": true
11+
}

.watchmanconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

App.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React from "react";
2+
import { View } from "react-native";
3+
import { createStackNavigator, createAppContainer } from "react-navigation";
4+
import { connect, Provider } from "react-redux";
5+
import { createStore, combineReducers, applyMiddleware } from "redux";
6+
import thunk from "redux-thunk";
7+
import {
8+
createReduxContainer,
9+
createReactNavigationReduxMiddleware,
10+
createNavigationReducer,
11+
} from "react-navigation-redux-helpers";
12+
import { AppScreen, LoadingScreen, LoginScreen } from "./screens";
13+
import * as reducers from "./reducers";
14+
15+
const RootNavigator = createStackNavigator({
16+
Loading: LoadingScreen,
17+
Login: LoginScreen,
18+
App: AppScreen,
19+
});
20+
21+
const store = createStore(
22+
combineReducers({
23+
...reducers,
24+
nav: createNavigationReducer(RootNavigator),
25+
}),
26+
applyMiddleware(
27+
thunk,
28+
createReactNavigationReduxMiddleware(state => state.nav),
29+
)
30+
);
31+
32+
const AppContainer = createAppContainer(RootNavigator);
33+
const AppNavigator = createReduxContainer(AppContainer);
34+
const mapStateToProps = (state) => ({
35+
state: state.nav,
36+
});
37+
const AppWithNavigationState = connect(mapStateToProps)(AppNavigator);
38+
39+
const App = () => (
40+
<View style={{ flex: 1 }}>
41+
<Provider store={store}>
42+
<AppWithNavigationState />
43+
</Provider>
44+
</View>
45+
);
46+
47+
export default App;
48+
49+
// export default class App extends Component {
50+
51+
// render() {
52+
// return (
53+
// <Provider store={store}>
54+
// <Navigation />
55+
// </Provider>
56+
// );
57+
// }
58+
59+
// }

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Steven Atkinson
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# #5 - Developers React To Monzo API (App)
2+
3+
![Behind the Buzzword](behind-the-buzzword-logo.jpeg)
4+
5+
A React Native App that uses the Monzo API
6+
7+
## Building
8+
9+
Run the following commands:
10+
11+
```bash
12+
yarn install
13+
yarn start
14+
```
15+
16+
## Running on your device
17+
18+
After running the start script you will be able to open the Expo app on your device and scan the QR code.
19+
20+
## Challenges
21+
22+
### Part 1
23+
24+
1. Create an OAuth Client on the Monzo Developer Portal
25+
1. Replace the mock authentication service with calls to the Monzo Authentication API
26+
27+
### Part 2
28+
29+
1. Build a new page for selecting a Monzo account
30+
1. Build a new page for displaying all the transactions
31+
1. Implement a feature to be able to mark a transaction as an expense
32+
33+
## Additional
34+
35+
You may not want to store the OAuth secret inside the app...so you can delegate exchanging the authorisation code to another web service.
36+
If you are new to OAuth then you can use [this repository](https://github.com/behind-the-buzzword/5-developers-react-to-monzo-api-server) to deploy a server to Heroku, and that will handle the exchange.

app.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"expo": {
3+
"name": "react-native-monzo-app",
4+
"slug": "react-native-monzo-app",
5+
"privacy": "public",
6+
"sdkVersion": "33.0.0",
7+
"platforms": [
8+
"ios",
9+
"android",
10+
"web"
11+
],
12+
"version": "1.0.0",
13+
"orientation": "portrait",
14+
"icon": "./assets/icon.png",
15+
"splash": {
16+
"image": "./assets/splash.png",
17+
"resizeMode": "contain",
18+
"backgroundColor": "#ffffff"
19+
},
20+
"updates": {
21+
"fallbackToCacheTimeout": 0
22+
},
23+
"assetBundlePatterns": [
24+
"**/*"
25+
],
26+
"ios": {
27+
"supportsTablet": true
28+
}
29+
}
30+
}

assets/behind-the-buzzword-logo.jpeg

15.7 KB
Loading

assets/icon.png

1.07 KB
Loading

assets/monzo-logo.png

32 KB
Loading

assets/splash.png

7.01 KB
Loading

babel.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = function(api) {
2+
api.cache(true);
3+
return {
4+
presets: ["babel-preset-expo", "module:react-native-dotenv"],
5+
};
6+
};

behind-the-buzzword-logo.jpeg

15.7 KB
Loading
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import reducer from "../reducer";
2+
import {
3+
GET_ACCOUNTS_PENDING,
4+
GET_ACCOUNTS_SUCCESS,
5+
GET_ACCOUNTS_FAILURE,
6+
} from "../types";
7+
8+
describe("reducer", () => {
9+
it("has a default state", () => {
10+
expect(reducer()).toStrictEqual({
11+
busy: false,
12+
items: [],
13+
});
14+
});
15+
16+
it("handles GET_ACCOUNTS_PENDING", () => {
17+
const state = {};
18+
const action = {
19+
type: GET_ACCOUNTS_PENDING,
20+
};
21+
expect(reducer(state, action)).toStrictEqual({
22+
busy: true,
23+
items: [],
24+
});
25+
});
26+
27+
it("handles GET_ACCOUNTS_SUCCESS", () => {
28+
const state = {};
29+
const action = {
30+
type: GET_ACCOUNTS_SUCCESS,
31+
payload: {
32+
accounts: [{
33+
id: "acc_00009237aqC8c5umZmrRdh",
34+
description: "Peter Pan's Account",
35+
created: "2015-11-13T12:17:42Z",
36+
}],
37+
},
38+
};
39+
expect(reducer(state, action)).toStrictEqual({
40+
busy: false,
41+
items: [{
42+
id: "acc_00009237aqC8c5umZmrRdh",
43+
description: "Peter Pan's Account",
44+
created: "2015-11-13T12:17:42Z",
45+
}],
46+
});
47+
});
48+
49+
it("handles GET_ACCOUNTS_FAILURE", () => {
50+
const state = {};
51+
const action = {
52+
type: GET_ACCOUNTS_FAILURE,
53+
payload: {
54+
error: {
55+
code: "error.code",
56+
message: "Something went wrong",
57+
},
58+
},
59+
};
60+
expect(reducer(state, action)).toStrictEqual({
61+
busy: false,
62+
error: {
63+
code: "error.code",
64+
message: "Something went wrong",
65+
},
66+
});
67+
});
68+
});

ducks/accounts/actions.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {
2+
MONZO_API_URL,
3+
} from "react-native-dotenv";
4+
import {
5+
GET_ACCOUNTS_PENDING,
6+
GET_ACCOUNTS_SUCCESS,
7+
GET_ACCOUNTS_FAILURE,
8+
} from "./types";
9+
10+
export const getAccounts = () => async (dispatch, getState) => {
11+
try {
12+
dispatch({ type: GET_ACCOUNTS_PENDING });
13+
14+
const state = await getState();
15+
const token = state.auth.user.access_token;
16+
const response = await fetch(`${MONZO_API_URL}/accounts`, {
17+
method: "GET",
18+
headers: {
19+
"Authorization": `Bearer ${token}`,
20+
"Accept": "application/json",
21+
},
22+
});
23+
const body = await response.json();
24+
25+
if (body.code === "bad_request.invalid_token") {
26+
throw body;
27+
}
28+
29+
dispatch({
30+
type: GET_ACCOUNTS_SUCCESS,
31+
payload: {
32+
accounts: body.accounts,
33+
},
34+
});
35+
}
36+
catch (err) {
37+
dispatch({
38+
type: GET_ACCOUNTS_FAILURE,
39+
payload: {
40+
error: err,
41+
},
42+
});
43+
}
44+
};

ducks/accounts/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as actions from "./actions";
2+
import * as types from "./types";
3+
import reducer from "./reducer";
4+
5+
export { actions, types };
6+
export default reducer;

ducks/accounts/reducer.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {
2+
GET_ACCOUNTS_PENDING,
3+
GET_ACCOUNTS_SUCCESS,
4+
GET_ACCOUNTS_FAILURE,
5+
} from "./types";
6+
7+
const initialState = {
8+
busy: false,
9+
items: [],
10+
};
11+
12+
export default function reducer(state = initialState, { type, payload } = {}) {
13+
switch (type) {
14+
case GET_ACCOUNTS_PENDING:
15+
return { ...state, busy: true, items: [] };
16+
17+
case GET_ACCOUNTS_SUCCESS:
18+
return { ...state, busy: false, items: payload.accounts };
19+
20+
case GET_ACCOUNTS_FAILURE:
21+
return { ...state, busy: false, error: payload.error };
22+
23+
default:
24+
return state;
25+
}
26+
}

0 commit comments

Comments
 (0)