-
Trevor Farlow authored
* Split the Settings component into multiple files (#434) * Remove need for isMobile in CSS: lean on media queries in styles.js and glamor Co-authored-by:
Matiss Janis Aboltins <matiss@mja.lv> Co-authored-by:
Jed Fox <git@jedfox.com>
Trevor Farlow authored* Split the Settings component into multiple files (#434) * Remove need for isMobile in CSS: lean on media queries in styles.js and glamor Co-authored-by:
Matiss Janis Aboltins <matiss@mja.lv> Co-authored-by:
Jed Fox <git@jedfox.com>
FinancesApp.js 10.88 KiB
import React, { useMemo } from 'react';
import { DndProvider } from 'react-dnd';
import Backend from 'react-dnd-html5-backend';
import { connect } from 'react-redux';
import {
Router,
Route,
Redirect,
Switch,
useLocation,
NavLink
} from 'react-router-dom';
import { CompatRouter } from 'react-router-dom-v5-compat';
import { createBrowserHistory } from 'history';
import hotkeys from 'hotkeys-js';
import * as actions from 'loot-core/src/client/actions';
import { AccountsProvider } from 'loot-core/src/client/data-hooks/accounts';
import { PayeesProvider } from 'loot-core/src/client/data-hooks/payees';
import { SpreadsheetProvider } from 'loot-core/src/client/SpreadsheetProvider';
import checkForUpgradeNotifications from 'loot-core/src/client/upgrade-notifications';
import * as undo from 'loot-core/src/platform/client/undo';
import { BudgetMonthCountProvider } from 'loot-design/src/components/budget/BudgetMonthCountContext';
import { View } from 'loot-design/src/components/common';
import { colors, styles } from 'loot-design/src/style';
import Cog from 'loot-design/src/svg/v1/Cog';
import PiggyBank from 'loot-design/src/svg/v1/PiggyBank';
import Wallet from 'loot-design/src/svg/v1/Wallet';
import { isMobile } from '../util';
import { getLocationState, makeLocationState } from '../util/location-state';
import Account from './accounts/Account';
import { default as MobileAccount } from './accounts/MobileAccount';
import { default as MobileAccounts } from './accounts/MobileAccounts';
import { ActiveLocationProvider } from './ActiveLocation';
import BankSyncStatus from './BankSyncStatus';
import Budget from './budget';
import { default as MobileBudget } from './budget/MobileBudget';
import FloatableSidebar, { SidebarProvider } from './FloatableSidebar';
import GlobalKeys from './GlobalKeys';
import { ManageRulesPage } from './ManageRulesPage';
import Modals from './Modals';
import Notifications from './Notifications';
import { PageTypeProvider } from './Page';
import { ManagePayeesPage } from './payees/ManagePayeesPage';
import Reports from './reports';
import Schedules from './schedules';
import DiscoverSchedules from './schedules/DiscoverSchedules';
import EditSchedule from './schedules/EditSchedule';
import LinkSchedule from './schedules/LinkSchedule';
import PostsOfflineNotification from './schedules/PostsOfflineNotification';
import Settings from './settings';
import Titlebar, { TitlebarProvider } from './Titlebar';
import FixSplitsTool from './tools/FixSplitsTool';
// import Debugger from './Debugger';
function PageRoute({ path, component: Component }) {
return (
<Route
path={path}
children={props => {
return (
<View
style={{
flex: 1,
display: props.match ? 'flex' : 'none'
}}
>
<Component {...props} />
</View>
);
}}
/>
);
}
function Routes({ isMobile, location }) {
return (
<Switch location={location}>
<Route path="/">
<Route path="/" exact render={() => <Redirect to="/budget" />} />
<PageRoute path="/reports" component={Reports} />
<PageRoute
path="/budget"
component={isMobile ? MobileBudget : Budget}
/>
<Route path="/schedules" exact component={Schedules} />
<Route path="/schedule/edit" exact component={EditSchedule} />
<Route path="/schedule/edit/:id" component={EditSchedule} />
<Route path="/schedule/link" component={LinkSchedule} />
<Route path="/schedule/discover" component={DiscoverSchedules} />
<Route
path="/schedule/posts-offline-notification"
component={PostsOfflineNotification}
/>
<Route path="/rules" exact component={ManageRulesPage} />
<Route path="/payees" exact component={ManagePayeesPage} />
<Route path="/tools/fix-splits" exact component={FixSplitsTool} />
<Route
path="/accounts/:id"
exact
children={props => {
const AcctCmp = isMobile ? MobileAccount : Account;
return (
props.match && <AcctCmp key={props.match.params.id} {...props} />
);
}}
/>
<Route
path="/accounts"
exact
component={isMobile ? MobileAccounts : Account}
/>
<Route path="/settings" component={Settings} />
</Route>
</Switch>
);
}
function StackedRoutes({ isMobile }) {
let location = useLocation();
let locationPtr = getLocationState(location, 'locationPtr');
let locations = [location];
while (locationPtr) {
locations.unshift(locationPtr);
locationPtr = getLocationState(locationPtr, 'locationPtr');
}
let base = locations[0];
let stack = locations.slice(1);
return (
<ActiveLocationProvider location={locations[locations.length - 1]}>
<Routes location={base} isMobile={isMobile} />
{stack.map((location, idx) => (
<PageTypeProvider
key={location.key}
type="modal"
current={idx === stack.length - 1}
>
<Routes location={location} isMobile={isMobile} />
</PageTypeProvider>
))}
</ActiveLocationProvider>
);
}
function NavTab({ icon: TabIcon, name, path }) {
return (
<NavLink
to={path}
style={{
alignItems: 'center',
color: '#8E8E8F',
display: 'flex',
flexDirection: 'column',
textDecoration: 'none'
}}
activeStyle={{ color: colors.p5 }}
>
<TabIcon
width={22}
height={22}
style={{ color: 'inherit', marginBottom: '5px' }}
/>
{name}
</NavLink>
);
}
function MobileNavTabs() {
return (
<div
style={{
backgroundColor: 'white',
borderTop: `1px solid ${colors.n10}`,
bottom: 0,
...styles.shadow,
display: 'flex',
height: '80px',
justifyContent: 'space-around',
paddingTop: 10,
width: '100%'
}}
>
<NavTab name="Budget" path="/budget" icon={Wallet} isActive={false} />
<NavTab
name="Accounts"
path="/accounts"
icon={PiggyBank}
isActive={false}
/>
<NavTab name="Settings" path="/settings" icon={Cog} isActive={false} />
</div>
);
}
class FinancesApp extends React.Component {
constructor(props) {
super(props);
this.state = { isMobile: isMobile(window.innerWidth) };
this.history = createBrowserHistory();
let oldPush = this.history.push;
this.history.push = (to, state) => {
return oldPush.call(this.history, to, makeLocationState(state));
};
// I'm not sure if this is the best approach but we need this to
// globally. We could instead move various workflows inside global
// React components, but that's for another day.
window.__history = this.history;
undo.setUndoState('url', window.location.href);
this.cleanup = this.history.listen(location => {
undo.setUndoState('url', window.location.href);
});
this.handleWindowResize = this.handleWindowResize.bind(this);
}
handleWindowResize() {
this.setState({
isMobile: isMobile(window.innerWidth),
windowWidth: window.innerWidth
});
}
componentDidMount() {
// TODO: quick hack fix for showing the demo
if (this.history.location.pathname === '/subscribe') {
this.history.push('/');
}
// Get the accounts and check if any exist. If there are no
// accounts, we want to redirect the user to the All Accounts
// screen which will prompt them to add an account
this.props.getAccounts().then(accounts => {
if (accounts.length === 0) {
this.history.push('/accounts');
}
});
// The default key handler scope
hotkeys.setScope('app');
// Wait a little bit to make sure the sync button will get the
// sync start event. This can be improved later.
setTimeout(async () => {
await this.props.sync();
// Check for upgrade notifications. We do this after syncing
// because these states are synced across devices, so they will
// only see it once for this file
checkForUpgradeNotifications(
this.props.addNotification,
this.props.resetSync,
this.history
);
}, 100);
window.addEventListener('resize', this.handleWindowResize);
}
componentWillUnmount() {
this.cleanup();
window.removeEventListener('resize', this.handleWindowResize);
}
render() {
return (
<Router history={this.history}>
<CompatRouter>
<View style={{ height: '100%', backgroundColor: colors.n10 }}>
<GlobalKeys />
<View style={{ flexDirection: 'row', flex: 1 }}>
{!this.state.isMobile && <FloatableSidebar />}
<div
style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
position: 'relative',
width: '100%'
}}
>
{!this.state.isMobile && (
<Titlebar
style={{
WebkitAppRegion: 'drag',
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 1000
}}
/>
)}
<div
style={{
flex: 1,
display: 'flex',
overflow: 'auto',
position: 'relative'
}}
>
<Notifications />
<BankSyncStatus />
<StackedRoutes isMobile={this.state.isMobile} />
{/*window.Actual.IS_DEV && <Debugger />*/}
<Modals history={this.history} />
</div>
{this.state.isMobile && (
<Switch>
<Route path="/budget" component={MobileNavTabs} />
<Route path="/accounts" component={MobileNavTabs} />
<Route path="/settings" component={MobileNavTabs} />
</Switch>
)}
</div>
</View>
</View>
</CompatRouter>
</Router>
);
}
}
function FinancesAppWithContext(props) {
let app = useMemo(() => <FinancesApp {...props} />, [props]);
return (
<SpreadsheetProvider>
<TitlebarProvider>
<SidebarProvider>
<BudgetMonthCountProvider>
<PayeesProvider>
<AccountsProvider>
<DndProvider backend={Backend}>{app}</DndProvider>
</AccountsProvider>
</PayeesProvider>
</BudgetMonthCountProvider>
</SidebarProvider>
</TitlebarProvider>
</SpreadsheetProvider>
);
}
export default connect(null, actions)(FinancesAppWithContext);