diff --git a/packages/loot-core/src/server/main.js b/packages/loot-core/src/server/main.js index 89e0ba1a263b2c56095ed707c0a2eb1d19459d49..92237e705ba2468e8acf6e7e04f9777644ab8825 100644 --- a/packages/loot-core/src/server/main.js +++ b/packages/loot-core/src/server/main.js @@ -1240,6 +1240,22 @@ handlers['nordigen-poll-web-token'] = async function ({ return null; }; +handlers['nordigen-get-banks'] = async function (country) { + const userToken = await asyncStorage.getItem('user-token'); + + if (!userToken) { + return Promise.reject({ error: 'unauthorized' }); + } + + return post( + getServer().NORDIGEN_SERVER + '/get-banks', + { country }, + { + 'X-ACTUAL-TOKEN': userToken, + }, + ); +}; + handlers['nordigen-poll-web-token-stop'] = async function () { stopPolling = true; return 'ok'; diff --git a/packages/loot-design/src/components/modals/NordigenExternalMsg.js b/packages/loot-design/src/components/modals/NordigenExternalMsg.js index beb96ae03bcb9b43db92eb80829c6be66c9ff97a..35de670276b5cf4fe59456b4e5503b90f35951b9 100644 --- a/packages/loot-design/src/components/modals/NordigenExternalMsg.js +++ b/packages/loot-design/src/components/modals/NordigenExternalMsg.js @@ -1,17 +1,44 @@ -import React, { useState, useRef } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; + +import { send } from 'loot-core/src/platform/client/fetch'; import { colors } from '../../style'; import AnimatedLoading from '../../svg/AnimatedLoading'; import { Error } from '../alerts'; -import { - CustomSelect, - View, - Modal, - Button, - P, - ModalButtons, - Strong, -} from '../common'; +import Autocomplete from '../Autocomplete'; +import { View, Modal, Button, P } from '../common'; +import { FormField, FormLabel } from '../forms'; + +import { COUNTRY_OPTIONS } from './countries'; + +function useAvailableBanks(country) { + const [banks, setBanks] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + async function fetch() { + if (!country) { + setBanks([]); + setIsLoading(false); + return; + } + + setIsLoading(true); + + const results = await send('nordigen-get-banks', country); + + setBanks(results); + setIsLoading(false); + } + + fetch(); + }, [setBanks, setIsLoading, country]); + + return { + data: banks, + isLoading, + }; +} function renderError(error) { return ( @@ -31,10 +58,14 @@ export default function NordigenExternalMsg({ }) { let [waiting, setWaiting] = useState(null); let [success, setSuccess] = useState(false); - let [institutionId, setInstitutionId] = useState('default'); + let [institutionId, setInstitutionId] = useState(); + let [country, setCountry] = useState(); let [error, setError] = useState(null); let data = useRef(null); + const { data: bankOptions, isLoading: isBankOptionsLoading } = + useAvailableBanks(country); + async function onJump() { setError(null); setWaiting('browser'); @@ -65,20 +96,54 @@ export default function NordigenExternalMsg({ const renderLinkButton = () => { return ( <View> - <Strong>Choose your banks:</Strong> - <CustomSelect - options={[ - ['default', 'Choose your bank'], - ['ING_PL_INGBPLPW', 'ING PL'], - ['MBANK_RETAIL_BREXPLPW', 'MBANK'], - ['SANDBOXFINANCE_SFIN0000', 'DEMO - TEST'], - ]} - disabledKeys={['default']} - onChange={val => { - setInstitutionId(val); - }} - value={institutionId || 'default'} - /> + <FormField style={{ marginBottom: 10 }}> + <FormLabel title="Choose your country:" htmlFor="country-field" /> + <Autocomplete + strict + suggestions={COUNTRY_OPTIONS} + onSelect={setCountry} + value={country} + inputProps={{ + id: 'country-field', + placeholder: '(please select)', + }} + renderItems={(items, getItemProps, highlightedIndex) => ( + <ItemList + items={items} + getItemProps={getItemProps} + highlightedIndex={highlightedIndex} + /> + )} + /> + </FormField> + + {country && + (isBankOptionsLoading ? ( + 'Loading banks...' + ) : ( + <FormField> + <FormLabel title="Choose your bank:" htmlFor="bank-field" /> + <Autocomplete + strict + focused + suggestions={bankOptions} + onSelect={setInstitutionId} + value={institutionId} + inputProps={{ + id: 'bank-field', + placeholder: '(please select)', + }} + renderItems={(items, getItemProps, highlightedIndex) => ( + <ItemList + items={items} + getItemProps={getItemProps} + highlightedIndex={highlightedIndex} + /> + )} + /> + </FormField> + ))} + <Button primary style={{ @@ -88,7 +153,7 @@ export default function NordigenExternalMsg({ marginTop: 10, }} onClick={onJump} - disabled={institutionId === 'default'} + disabled={!institutionId || !country} > Link bank in browser → </Button> @@ -145,12 +210,41 @@ export default function NordigenExternalMsg({ ) : ( renderLinkButton() )} - - <ModalButtons style={{ marginTop: 10 }}> - <Button onClick={() => modalProps.onBack()}>Back</Button> - </ModalButtons> </View> )} </Modal> ); } + +export function ItemList({ items, getItemProps, highlightedIndex }) { + return ( + <View + style={[ + { + overflow: 'auto', + padding: '5px 0', + maxHeight: 175, + }, + ]} + > + {items.map((item, idx) => ( + <div + key={item.id} + {...(getItemProps ? getItemProps({ item }) : null)} + style={{ + backgroundColor: + highlightedIndex === idx ? colors.n4 : 'transparent', + padding: 4, + paddingLeft: 20, + borderRadius: 0, + }} + data-testid={ + 'item' + (highlightedIndex === idx ? '-highlighted' : '') + } + > + {item.name} + </div> + ))} + </View> + ); +} diff --git a/packages/loot-design/src/components/modals/countries.js b/packages/loot-design/src/components/modals/countries.js new file mode 100644 index 0000000000000000000000000000000000000000..91de293061541c04b6642212eab02a4e2fda0c7f --- /dev/null +++ b/packages/loot-design/src/components/modals/countries.js @@ -0,0 +1,126 @@ +export const COUNTRY_OPTIONS = [ + { + id: 'AT', + name: 'Austria', + }, + { + id: 'BE', + name: 'Belgium', + }, + { + id: 'BG', + name: 'Bulgaria', + }, + { + id: 'HR', + name: 'Croatia', + }, + { + id: 'CY', + name: 'Cyprus', + }, + { + id: 'CZ', + name: 'Czechia', + }, + { + id: 'DK', + name: 'Denmark', + }, + { + id: 'EE', + name: 'Estonia', + }, + { + id: 'FI', + name: 'Finland', + }, + { + id: 'FR', + name: 'France', + }, + { + id: 'DE', + name: 'Germany', + }, + { + id: 'GR', + name: 'Greece', + }, + { + id: 'HU', + name: 'Hungary', + }, + { + id: 'IS', + name: 'Iceland', + }, + { + id: 'IE', + name: 'Ireland', + }, + { + id: 'IT', + name: 'Italy', + }, + { + id: 'LV', + name: 'Latvia', + }, + { + id: 'LI', + name: 'Liechtenstein', + }, + { + id: 'LT', + name: 'Lithuania', + }, + { + id: 'LU', + name: 'Luxembourg', + }, + { + id: 'MT', + name: 'Malta', + }, + { + id: 'NL', + name: 'Netherlands', + }, + { + id: 'NO', + name: 'Norway', + }, + { + id: 'PL', + name: 'Poland', + }, + { + id: 'PT', + name: 'Portugal', + }, + { + id: 'RO', + name: 'Romania', + }, + { + id: 'SK', + name: 'Slovakia', + }, + { + id: 'SI', + name: 'Slovenia', + }, + { + id: 'ES', + name: 'Spain', + }, + { + id: 'SE', + name: 'Sweden', + }, + { + id: 'GB', + name: 'United Kingdom', + }, +];