Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Begin porting the web POC to react native #10

Merged
merged 45 commits into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
bef1ed1
Remove all async and clean up pages
tgolen Aug 8, 2020
7a8f83b
Fix an error with merge
tgolen Aug 8, 2020
d9cb5b3
Add some style to the header
tgolen Aug 8, 2020
f55a6d4
Add styles and display a header
tgolen Aug 8, 2020
07db03a
Add the personal details action
tgolen Aug 8, 2020
4496f10
Add personal details to the header
tgolen Aug 8, 2020
ae9f529
WIP on direct binding
tgolen Aug 8, 2020
b0a1575
Implement direct binding
tgolen Aug 8, 2020
f9a094a
Fix unsubscribing
tgolen Aug 9, 2020
0366c4f
Fix infinite session redirect
tgolen Aug 9, 2020
f153fb5
Build an HOC for subscribing the state to the store
tgolen Aug 9, 2020
307dfd2
Add a little more comment
tgolen Aug 9, 2020
e31528b
Add initial sidebar and main views and lay them out
tgolen Aug 9, 2020
a5cbde6
List reports in the sidebar
tgolen Aug 9, 2020
2490b9c
Update src/page/HomePage/HeaderView.js
tgolen Aug 9, 2020
a02d992
Update src/style/StyleSheet.js
tgolen Aug 9, 2020
a3de49e
Fix typo
tgolen Aug 9, 2020
743db5f
Merge branch 'tgolen-port-webpoc' of https://github.com/AndrewGable/R…
tgolen Aug 9, 2020
2f8016b
Use Link instead of NavLink
tgolen Aug 9, 2020
67ee483
Use a simple subscription ID
tgolen Aug 9, 2020
e42283d
Rename subscribeToState to bind
tgolen Aug 9, 2020
b2a9b7e
Trigger key changed after storage is set
tgolen Aug 9, 2020
8df561f
Move the sidebar link into it's own component
tgolen Aug 9, 2020
74d5455
remove calc() from styles
tgolen Aug 9, 2020
da8177c
Show the list of report names as decent links
tgolen Aug 9, 2020
f2ed6a8
Remvoe subscribe and unsubscribe for now
tgolen Aug 9, 2020
04b5751
Only create regex object when binding
tgolen Aug 9, 2020
cbc7368
Rename unsubscribeFromState to unbind
tgolen Aug 9, 2020
9b8a3b8
Reorder the params for binding
tgolen Aug 9, 2020
9a26bc3
Remove persistent storage
tgolen Aug 9, 2020
af2333f
Use merge in as many places as possible
tgolen Aug 9, 2020
cad8207
Remove weird code
tgolen Aug 9, 2020
2130a64
Improve the styles in the sidebar
tgolen Aug 9, 2020
99e3a73
Add a report view and show the report name
tgolen Aug 9, 2020
3b8fcaf
Show the report name on the header
tgolen Aug 9, 2020
1843eb7
Load the report history when looking at a report
tgolen Aug 9, 2020
fb063ce
Rename the HOC to WithStore
tgolen Aug 9, 2020
732ac89
Do better at prefilling data from the right keys
tgolen Aug 9, 2020
c9e45c1
Improve the signature of bind
tgolen Aug 9, 2020
545be1f
Add a view for displaying history items
tgolen Aug 9, 2020
1807767
Add some more views for displaying history items
tgolen Aug 9, 2020
22aa79e
Display history fragments
tgolen Aug 9, 2020
c8384ab
Better comment
tgolen Aug 9, 2020
a813cde
Fix a semi-broken home page
tgolen Aug 9, 2020
798135c
Apply mobile fixes
AndrewGable Aug 10, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
},
"dependencies": {
"@react-native-community/async-storage": "^1.11.0",
"html-entities": "^1.3.1",
"jquery": "^3.5.1",
"lodash.get": "^4.4.2",
"moment": "^2.27.0",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-beforeunload": "^2.2.2",
"react-dom": "^16.13.1",
"react-native": "0.63.2",
"react-native-web": "^0.13.5",
Expand Down
2 changes: 2 additions & 0 deletions src/CONFIG.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {Platform} from 'react-native';

// eslint-disable-next-line no-undef
const IS_IN_PRODUCTION = Platform.OS === 'web' ? process.env.NODE_ENV === 'production' : !__DEV__;

export default {
IS_IN_PRODUCTION,
PUSHER: {
APP_KEY: IS_IN_PRODUCTION ? '268df511a204fbb60884' : 'ac6d22b891daae55283a',
AUTH_URL: IS_IN_PRODUCTION ? 'https://www.expensify.com' : 'https://www.expensify.com.dev',
Expand Down
5 changes: 5 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const CONST = {
CLOUDFRONT_URL: 'https://d2k5nsl2zxldvw.cloudfront.net',
};

export default CONST;
30 changes: 14 additions & 16 deletions src/Expensify.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {Component} from 'react';
import {Beforeunload} from 'react-beforeunload';
import SignInPage from './page/SignInPage';
import HomePage from './page/HomePage/HomePage';
import * as Store from './store/Store';
Expand All @@ -24,7 +25,7 @@ export default class Expensify extends Component {
};
}

async componentDidMount() {
componentDidMount() {
// Listen for when the app wants to redirect to a specific URL
Store.subscribe(STOREKEYS.APP_REDIRECT_TO, (redirectTo) => {
this.setState({redirectTo});
Expand All @@ -34,25 +35,22 @@ export default class Expensify extends Component {
verifyAuthToken();

// Initialize this client as being an active client
await ActiveClientManager.init();

// TODO: Refactor window events
// window.addEventListener('beforeunload', () => {
// ActiveClientManager.removeClient();
// });
ActiveClientManager.init();
}

render() {
return (
<Router>
{/* If there is ever a property for redirecting, we do the redirect here */}
{this.state.redirectTo && <Redirect to={this.state.redirectTo} />}

<Switch>
<Route path="/signin" component={SignInPage} />
<Route path="/" component={HomePage} />
</Switch>
</Router>
<Beforeunload onBeforeunload={ActiveClientManager.removeClient}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mobile doesn't play well with this, it seems to use window quite a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dang, this was supposed to be compatible :(

Copy link
Contributor

@AndrewGable AndrewGable Aug 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not an expert on this event, but could we use AppState instead? It is supported by RN and RN4W out of the box: https://github.com/necolas/react-native-web#modules

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tested this and yes it does change to inactive when the tab is changed, then back to active when it comes back. This sounds like possibly we need to handle it using a native/web solution (like Router).

<Router>
{/* If there is ever a property for redirecting, we do the redirect here */}
{this.state.redirectTo && <Redirect to={this.state.redirectTo} />}

<Switch>
<Route path="/signin" component={SignInPage} />
<Route path="/" component={HomePage} />
</Switch>
</Router>
</Beforeunload>
);
}
}
49 changes: 49 additions & 0 deletions src/components/WithStoreSubscribeToState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* This is a higher order component that provides the ability to map a state property directly to
* something in the store. That way, as soon as the store changes, the state will be set and the view
* will automatically change to reflect the new data.
*/
import React from 'react';
import _ from 'underscore';
import * as Store from '../store/Store';

export default function (mapStoreToStates) {
return WrappedComponent => class WithStoreSubscribeToState extends React.Component {
constructor(props) {
super(props);

this.subscribedEventGuids = [];

// Initialize the state with each of our property names
this.state = _.reduce(_.keys(mapStoreToStates), (finalResult, propertyName) => ({
...finalResult,
[propertyName]: null,
}), {});
}

componentDidMount() {
this.subscribedEventGuids = _.reduce(mapStoreToStates, (finalResult, mapStoreToState, propertyName) => {
const {key, path} = mapStoreToState;
return [
...finalResult,
Store.subscribeToState(key, propertyName, path, null, this.wrappedComponent),
];
}, []);

_.each(mapStoreToStates, (mapStoreToState) => {
if (mapStoreToState.loader) {
mapStoreToState.loader(...mapStoreToState.loaderParams || []);
}
});
}

componentWillUnmount() {
_.each(this.subscribedEventGuids, Store.unsubscribeFromState);
}

render() {
// eslint-disable-next-line react/jsx-props-no-spreading
return <WrappedComponent {...this.props} {...this.state} ref={el => this.wrappedComponent = el} />;
}
};
}
32 changes: 17 additions & 15 deletions src/lib/ActiveClientManager.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import _ from 'underscore';
import Guid from './Guid';
import * as Store from '../store/Store';
import STOREKEYS from '../store/STOREKEYS';
Expand All @@ -6,33 +7,34 @@ const clientID = Guid();

/**
* Add our client ID to the list of active IDs
*
* @returns {Promise}
*/
const init = async () => {
const activeClientIDs = (await Store.get(STOREKEYS.ACTIVE_CLIENT_IDS)) || [];
activeClientIDs.push(clientID);
Store.set(STOREKEYS.ACTIVE_CLIENT_IDS, activeClientIDs);
};
const init = () => Store.merge(STOREKEYS.ACTIVE_CLIENT_IDS, clientID);
AndrewGable marked this conversation as resolved.
Show resolved Hide resolved

/**
* Remove this client ID from the array of active client IDs when this client is exited
*
* @returns {Promise}
*/
function removeClient() {
const activeClientIDs = Store.get(STOREKEYS.ACTIVE_CLIENT_IDS) || [];
const newActiveClientIDs = activeClientIDs.filter(activeClientID => activeClientID !== clientID);
Store.set(STOREKEYS.ACTIVE_CLIENT_IDS, newActiveClientIDs);
return Store.get(STOREKEYS.ACTIVE_CLIENT_IDS)
.then(activeClientIDs => _.without(activeClientIDs, clientID))
.then(newActiveClientIDs => Store.set(STOREKEYS.ACTIVE_CLIENT_IDS, newActiveClientIDs));
}

/**
* Checks if the current client is the leader (the first one in the list of active clients)
*
* @returns {boolean}
* @returns {Promise}
*/
function isClientTheLeader() {
const activeClientIDs = Store.get(STOREKEYS.ACTIVE_CLIENT_IDS) || [];
if (!activeClientIDs.length) {
return false;
}
return activeClientIDs[0] === clientID;
return Store.get(STOREKEYS.ACTIVE_CLIENT_IDS)
.then(activeClientIDs => _.first(activeClientIDs) === clientID);
}

export {init, removeClient, isClientTheLeader};
export {
init,
removeClient,
isClientTheLeader
};
4 changes: 2 additions & 2 deletions src/lib/PersistentStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function multiGet(keys) {
...finalData,
[keyValuePair[0]]: JSON.parse(keyValuePair[1]),
}), {}))
.catch(err => console.error(`Unable to get item from persistent storage. Keys: ${JSON.stringify(keys)} Error: ${err}`));
.catch(err => console.error(`Unable to get item from persistent storage. Error: ${err}`, keys));
}

/**
Expand Down Expand Up @@ -82,7 +82,7 @@ function clear() {
* @returns {Promise}
*/
function merge(key, val) {
return AsyncStorage.mergeItem(key, val);
return AsyncStorage.mergeItem(key, JSON.stringify(val));
}

export {
Expand Down
4 changes: 3 additions & 1 deletion src/lib/Router/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
BrowserRouter as Router,
HashRouter as Router,
Link,
NavLink,
Route,
Redirect,
Switch,
Expand All @@ -9,6 +10,7 @@ import {

export {
Link,
NavLink,
Route,
Redirect,
Router,
Expand Down
7 changes: 4 additions & 3 deletions src/lib/Str.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* globals $, _ */

import _ from 'underscore';
import {AllHtmlEntities} from 'html-entities';
import Guid from './Guid';


const Str = {
/**
* Returns the proper phrase depending on the count that is passed.
Expand Down Expand Up @@ -39,7 +40,7 @@ const Str = {
* @return {String} The decoded string.
*/
htmlDecode(s) {
return $('<textarea/>').html(s).text();
return AllHtmlEntities.decode(s);
},

/**
Expand Down
Loading