🐛 (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:
Matiss Janis Aboltins
2023-03-13 18:27:45 +00:00
committed by GitHub
parent 0e61bfc47a
commit 6e7c95b5be
6 changed files with 172 additions and 78 deletions

View File

@@ -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">

View File

@@ -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' },

View File

@@ -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>
);
}

View 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';
}

View File

@@ -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');

View File

@@ -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 &rarr;
</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>
)}