mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-28 10:33:02 -05:00
🐛 (nordigen) check server status before linking accs (#742)
Related to: https://github.com/actualbudget/actual/issues/724#issuecomment-1455160250 Depends on https://github.com/actualbudget/docs/pull/126 to be merged first. Two changes here: 1. show "link account" only if actual-server is used (user is "online"); 2. allow linking accounts only if Nordigen is configured (using new API to get the status for it); Also ported the `CreateAccount` modal to a functional component.
This commit is contained in:
committed by
GitHub
parent
0e61bfc47a
commit
6e7c95b5be
@@ -19,6 +19,8 @@ import NordigenExternalMsg from 'loot-design/src/components/modals/NordigenExter
|
||||
import PlaidExternalMsg from 'loot-design/src/components/modals/PlaidExternalMsg';
|
||||
import SelectLinkedAccounts from 'loot-design/src/components/modals/SelectLinkedAccounts';
|
||||
|
||||
import useSyncServerStatus from '../hooks/useSyncServerStatus';
|
||||
|
||||
import ConfirmCategoryDelete from './modals/ConfirmCategoryDelete';
|
||||
import CreateAccount from './modals/CreateAccount';
|
||||
import CreateEncryptionKey from './modals/CreateEncryptionKey';
|
||||
@@ -39,6 +41,8 @@ function Modals({
|
||||
budgetId,
|
||||
actions,
|
||||
}) {
|
||||
const syncServerStatus = useSyncServerStatus();
|
||||
|
||||
return modalStack.map(({ name, options = {} }, idx) => {
|
||||
const modalProps = {
|
||||
onClose: actions.popModal,
|
||||
@@ -57,7 +61,11 @@ function Modals({
|
||||
</Route>
|
||||
|
||||
<Route path="/add-account">
|
||||
<CreateAccount modalProps={modalProps} actions={actions} />
|
||||
<CreateAccount
|
||||
modalProps={modalProps}
|
||||
actions={actions}
|
||||
syncServerStatus={syncServerStatus}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/add-local-account">
|
||||
|
||||
@@ -60,6 +60,7 @@ import Pencil1 from 'loot-design/src/svg/v2/Pencil1';
|
||||
import SvgRemove from 'loot-design/src/svg/v2/Remove';
|
||||
import SearchAlternate from 'loot-design/src/svg/v2/SearchAlternate';
|
||||
|
||||
import useSyncServerStatus from '../../hooks/useSyncServerStatus';
|
||||
import { authorizeBank } from '../../nordigen';
|
||||
import { useActiveLocation } from '../ActiveLocation';
|
||||
import AnimatedRefresh from '../AnimatedRefresh';
|
||||
@@ -259,6 +260,7 @@ function AccountMenu({
|
||||
onMenuSelect,
|
||||
}) {
|
||||
let [tooltip, setTooltip] = useState('default');
|
||||
const syncServerStatus = useSyncServerStatus();
|
||||
|
||||
return tooltip === 'reconcile' ? (
|
||||
<ReconcileTooltip
|
||||
@@ -291,8 +293,14 @@ function AccountMenu({
|
||||
account &&
|
||||
!account.closed &&
|
||||
(canSync
|
||||
? { name: 'unlink', text: 'Unlink Account' }
|
||||
: { name: 'link', text: 'Link Account' }),
|
||||
? {
|
||||
name: 'unlink',
|
||||
text: 'Unlink Account',
|
||||
}
|
||||
: syncServerStatus === 'online' && {
|
||||
name: 'link',
|
||||
text: 'Link Account',
|
||||
}),
|
||||
account.closed
|
||||
? { name: 'reopen', text: 'Reopen Account' }
|
||||
: { name: 'close', text: 'Close Account' },
|
||||
|
||||
@@ -1,87 +1,95 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import * as actions from 'loot-core/src/client/actions';
|
||||
import { View, Text, Modal, Button } from 'loot-design/src/components/common';
|
||||
import { pushModal } from 'loot-core/src/client/actions/modals';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Modal,
|
||||
P,
|
||||
Button,
|
||||
ButtonWithLoading,
|
||||
} from 'loot-design/src/components/common';
|
||||
import { colors } from 'loot-design/src/style';
|
||||
|
||||
import { authorizeBank } from '../../nordigen';
|
||||
|
||||
class CreateAccount extends React.Component {
|
||||
onConnect = async () => {
|
||||
authorizeBank(this.props.pushModal);
|
||||
export default function CreateAccount({ modalProps, syncServerStatus }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onConnect = () => {
|
||||
authorizeBank((modal, params) => dispatch(pushModal(modal, params)));
|
||||
};
|
||||
|
||||
onCreateLocalAccount = () => {
|
||||
const { pushModal } = this.props;
|
||||
pushModal('add-local-account');
|
||||
const onCreateLocalAccount = () => {
|
||||
dispatch(pushModal('add-local-account'));
|
||||
};
|
||||
|
||||
render() {
|
||||
const { modalProps } = this.props;
|
||||
return (
|
||||
<Modal title="Add Account" {...modalProps}>
|
||||
{() => (
|
||||
<View style={{ maxWidth: 500 }}>
|
||||
<Text style={{ marginBottom: 10, lineHeight: '1.4em', fontSize: 15 }}>
|
||||
<strong>Link your bank accounts</strong> to automatically download
|
||||
transactions. We offer hundreds of banks to sync with, and our
|
||||
service will provide reliable, up-to-date information.
|
||||
</Text>
|
||||
|
||||
return (
|
||||
<Modal title="Add Account" {...modalProps}>
|
||||
{() => (
|
||||
<View style={{ maxWidth: 500 }}>
|
||||
<Text
|
||||
style={{ marginBottom: 10, lineHeight: '1.4em', fontSize: 15 }}
|
||||
>
|
||||
<strong>Link your bank accounts</strong> to automatically download
|
||||
transactions. We offer hundreds of banks to sync with, and our
|
||||
service will provide reliable, up-to-date information.
|
||||
</Text>
|
||||
<ButtonWithLoading
|
||||
primary
|
||||
disabled={syncServerStatus !== 'online'}
|
||||
style={{
|
||||
padding: '10px 0',
|
||||
fontSize: 15,
|
||||
fontWeight: 600,
|
||||
marginTop: 10,
|
||||
}}
|
||||
onClick={onConnect}
|
||||
>
|
||||
Link bank account
|
||||
</ButtonWithLoading>
|
||||
|
||||
<Button
|
||||
primary
|
||||
style={{
|
||||
padding: '10px 0',
|
||||
fontSize: 15,
|
||||
fontWeight: 600,
|
||||
marginTop: 10,
|
||||
}}
|
||||
onClick={this.onConnect}
|
||||
>
|
||||
Link bank account
|
||||
</Button>
|
||||
{syncServerStatus !== 'online' && (
|
||||
<P style={{ color: colors.r5, marginTop: 5 }}>
|
||||
Nordigen integration is only available for budgets using
|
||||
actual-server.{' '}
|
||||
<a
|
||||
href="https://actualbudget.github.io/docs/Installing/overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn more.
|
||||
</a>
|
||||
</P>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
marginTop: 30,
|
||||
marginBottom: 10,
|
||||
lineHeight: '1.4em',
|
||||
fontSize: 15,
|
||||
}}
|
||||
>
|
||||
You can also create a local account if you want to track
|
||||
transactions manually. You can add transactions manually or import
|
||||
QIF/OFX/QFX files.
|
||||
</View>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
padding: '10px 0',
|
||||
fontSize: 15,
|
||||
fontWeight: 600,
|
||||
marginTop: 10,
|
||||
color: colors.n3,
|
||||
}}
|
||||
onClick={this.onCreateLocalAccount}
|
||||
>
|
||||
Create local account
|
||||
</Button>
|
||||
<View
|
||||
style={{
|
||||
marginTop: 30,
|
||||
marginBottom: 10,
|
||||
lineHeight: '1.4em',
|
||||
fontSize: 15,
|
||||
}}
|
||||
>
|
||||
You can also create a local account if you want to track
|
||||
transactions manually. You can add transactions manually or import
|
||||
QIF/OFX/QFX files.
|
||||
</View>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
currentModal: state.modals.currentModal,
|
||||
}),
|
||||
dispatch => bindActionCreators(actions, dispatch),
|
||||
)(CreateAccount);
|
||||
<Button
|
||||
style={{
|
||||
padding: '10px 0',
|
||||
fontSize: 15,
|
||||
fontWeight: 600,
|
||||
marginTop: 10,
|
||||
color: colors.n3,
|
||||
}}
|
||||
onClick={onCreateLocalAccount}
|
||||
>
|
||||
Create local account
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
14
packages/desktop-client/src/hooks/useSyncServerStatus.js
Normal file
14
packages/desktop-client/src/hooks/useSyncServerStatus.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useServerURL } from '../components/ServerContext';
|
||||
|
||||
export default function useSyncServerStatus() {
|
||||
const serverUrl = useServerURL();
|
||||
const userData = useSelector(state => state.user.data);
|
||||
|
||||
if (!serverUrl) {
|
||||
return 'no-server';
|
||||
}
|
||||
|
||||
return !userData || userData.offline ? 'offline' : 'online';
|
||||
}
|
||||
@@ -1240,6 +1240,22 @@ handlers['nordigen-poll-web-token'] = async function ({
|
||||
return null;
|
||||
};
|
||||
|
||||
handlers['nordigen-status'] = async function () {
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
|
||||
if (!userToken) {
|
||||
return Promise.reject({ error: 'unauthorized' });
|
||||
}
|
||||
|
||||
return post(
|
||||
getServer().NORDIGEN_SERVER + '/status',
|
||||
{},
|
||||
{
|
||||
'X-ACTUAL-TOKEN': userToken,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
handlers['nordigen-get-banks'] = async function (country) {
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
|
||||
|
||||
@@ -40,6 +40,29 @@ function useAvailableBanks(country) {
|
||||
};
|
||||
}
|
||||
|
||||
function useNordigenStatus() {
|
||||
const [configured, setConfigured] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetch() {
|
||||
setIsLoading(true);
|
||||
|
||||
const results = await send('nordigen-status');
|
||||
|
||||
setConfigured(results.configured || false);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
fetch();
|
||||
}, [setConfigured, setIsLoading]);
|
||||
|
||||
return {
|
||||
configured,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
function renderError(error) {
|
||||
return (
|
||||
<Error style={{ alignSelf: 'center' }}>
|
||||
@@ -65,6 +88,8 @@ export default function NordigenExternalMsg({
|
||||
|
||||
const { data: bankOptions, isLoading: isBankOptionsLoading } =
|
||||
useAvailableBanks(country);
|
||||
const { configured: isConfigured, isLoading: isConfigurationLoading } =
|
||||
useNordigenStatus();
|
||||
|
||||
async function onJump() {
|
||||
setError(null);
|
||||
@@ -100,6 +125,7 @@ export default function NordigenExternalMsg({
|
||||
<FormLabel title="Choose your country:" htmlFor="country-field" />
|
||||
<Autocomplete
|
||||
strict
|
||||
disabled={isConfigurationLoading}
|
||||
suggestions={COUNTRY_OPTIONS}
|
||||
onSelect={setCountry}
|
||||
value={country}
|
||||
@@ -178,14 +204,16 @@ export default function NordigenExternalMsg({
|
||||
|
||||
{error && renderError(error)}
|
||||
|
||||
{waiting ? (
|
||||
{waiting || isConfigurationLoading ? (
|
||||
<View style={{ alignItems: 'center', marginTop: 15 }}>
|
||||
<AnimatedLoading
|
||||
color={colors.n1}
|
||||
style={{ width: 20, height: 20 }}
|
||||
/>
|
||||
<View style={{ marginTop: 10, color: colors.n4 }}>
|
||||
{waiting === 'browser'
|
||||
{isConfigurationLoading
|
||||
? 'Checking Nordigen configuration..'
|
||||
: waiting === 'browser'
|
||||
? 'Waiting on Nordigen...'
|
||||
: waiting === 'accounts'
|
||||
? 'Loading accounts...'
|
||||
@@ -207,8 +235,20 @@ export default function NordigenExternalMsg({
|
||||
>
|
||||
Success! Click to continue →
|
||||
</Button>
|
||||
) : (
|
||||
) : isConfigured ? (
|
||||
renderLinkButton()
|
||||
) : (
|
||||
<P style={{ color: colors.r5 }}>
|
||||
Nordigen integration has not been configured so linking accounts
|
||||
is not available.{' '}
|
||||
<a
|
||||
href="https://actualbudget.github.io/docs/Accounts/connecting-your-bank/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn more.
|
||||
</a>
|
||||
</P>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user