summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authortanhengyeow <E0032242@u.nus.edu>2020-06-26 11:32:45 +0800
committertanhengyeow <E0032242@u.nus.edu>2020-06-26 11:32:45 +0800
commit67767c666bcd1ee6ca01df2747b55bf6e5a70485 (patch)
tree37be4dcd930e830e4140c8a860bb8e20816027a2 /frontend
parent501866d656f33814b63289054ba4de5b19cad0f1 (diff)
downloadlibeufin-67767c666bcd1ee6ca01df2747b55bf6e5a70485.tar.gz
libeufin-67767c666bcd1ee6ca01df2747b55bf6e5a70485.tar.bz2
libeufin-67767c666bcd1ee6ca01df2747b55bf6e5a70485.zip
Add support for adding and showing bank connections/bank accounts
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/components/bank-accounts/AddBankConnectionDrawer.tsx247
-rw-r--r--frontend/src/components/bank-accounts/BankConnectionCard.tsx29
-rw-r--r--frontend/src/components/bank-accounts/BankConnectionDrawer.tsx164
-rw-r--r--frontend/src/components/bank-accounts/Index.tsx110
4 files changed, 494 insertions, 56 deletions
diff --git a/frontend/src/components/bank-accounts/AddBankConnectionDrawer.tsx b/frontend/src/components/bank-accounts/AddBankConnectionDrawer.tsx
new file mode 100644
index 00000000..57b85cca
--- /dev/null
+++ b/frontend/src/components/bank-accounts/AddBankConnectionDrawer.tsx
@@ -0,0 +1,247 @@
+import React, { useState } from 'react';
+import { Button, Drawer, Input, Form, Steps } from 'antd';
+const { Step } = Steps;
+
+const layout = {
+ labelCol: { span: 4 },
+};
+
+const AddBankConnectionDrawer = (props) => {
+ const { visible, onClose } = props;
+ const [currentStep, setCurrentStep] = useState(0);
+ const [printLink, setPrintLink] = useState('');
+
+ const [name, setName] = useState('');
+ const [serverURL, setServerURL] = useState('');
+ const [hostID, setHostID] = useState('');
+ const [partnerID, setPartnerID] = useState('');
+ const [userID, setUserID] = useState('');
+ const [systemID, setSystemID] = useState('');
+
+ const steps = [{ title: 'Fill up details' }, { title: 'Print document' }];
+
+ const createBankConnection = async () => {
+ const authHeader = await window.localStorage.getItem('authHeader');
+ await fetch(`/bank-connections`, {
+ headers: new Headers({
+ Authorization: `Basic ${authHeader}`,
+ 'Content-Type': 'application/json',
+ }),
+ method: 'POST',
+ body: JSON.stringify({
+ name: name,
+ source: 'new',
+ type: 'ebics',
+ data: {
+ ebicsURL: serverURL,
+ hostID: hostID,
+ partnerID: partnerID,
+ userID: userID,
+ },
+ }),
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw 'Cannot create bank connection';
+ }
+ })
+ .catch((err) => {
+ throw new Error(err);
+ });
+ };
+
+ const connectBankConnection = async () => {
+ const authHeader = await window.localStorage.getItem('authHeader');
+ await fetch(`/bank-connections/${name}/connect`, {
+ headers: new Headers({
+ Authorization: `Basic ${authHeader}`,
+ }),
+ method: 'POST',
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw 'Cannot connect bank connection';
+ }
+ })
+ .catch((err) => {
+ throw new Error(err);
+ });
+ };
+
+ const fetchKeyLetter = async () => {
+ const authHeader = await window.localStorage.getItem('authHeader');
+ await fetch(`/bank-connections/${name}/keyletter`, {
+ headers: new Headers({
+ Authorization: `Basic ${authHeader}`,
+ }),
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.blob();
+ }
+ throw 'Cannot fetch keyletter';
+ })
+ .then(async (blob) => {
+ const pdfLink = URL.createObjectURL(blob);
+ setPrintLink(pdfLink);
+ })
+ .catch((err) => {
+ throw new Error(err);
+ });
+ };
+
+ const updateBankAccounts = async () => {
+ const authHeader = await window.localStorage.getItem('authHeader');
+ await fetch(`/bank-connections/${name}/fetch-accounts`, {
+ headers: new Headers({
+ Authorization: `Basic ${authHeader}`,
+ }),
+ method: 'POST',
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw 'Cannot update bank accounts';
+ }
+ })
+ .catch((err) => {
+ throw new Error(err);
+ });
+ };
+
+ const next = async () => {
+ await createBankConnection();
+ await connectBankConnection();
+ await fetchKeyLetter();
+ await updateBankAccounts();
+
+ setServerURL('');
+ setHostID('');
+ setPartnerID('');
+ setUserID('');
+ setSystemID('');
+ setCurrentStep(currentStep + 1);
+ };
+
+ const closeDrawer = () => {
+ setCurrentStep(0);
+ onClose();
+ };
+
+ return (
+ <Drawer
+ title="Add bank connection"
+ placement="right"
+ closable={false}
+ onClose={onClose}
+ visible={visible}
+ width={850}
+ >
+ <div className="steps-row">
+ <Steps current={currentStep}>
+ {steps.map((item) => (
+ <Step key={item.title} title={item.title} />
+ ))}
+ </Steps>
+ </div>
+ <div>
+ {currentStep < steps.length - 1 ? (
+ <Form {...layout} name="basic">
+ <Form.Item
+ label="Server URL"
+ name="Server URL"
+ rules={[
+ { required: true, message: 'Please input the Server URL!' },
+ ]}
+ >
+ <Input onChange={(e) => setServerURL(e.target.value)} />
+ </Form.Item>
+ <Form.Item
+ label="Name"
+ name="Name"
+ rules={[
+ {
+ required: true,
+ message: 'Please input the name of the bank connection!',
+ },
+ ]}
+ >
+ <Input onChange={(e) => setName(e.target.value)} />
+ </Form.Item>
+ <Form.Item
+ label="Host ID"
+ name="Host ID"
+ rules={[{ required: true, message: 'Please input the Host ID!' }]}
+ >
+ <Input onChange={(e) => setHostID(e.target.value)} />
+ </Form.Item>
+ <Form.Item
+ label="Partner ID"
+ name="Partner ID"
+ rules={[
+ { required: true, message: 'Please input the Partner ID!' },
+ ]}
+ >
+ <Input onChange={(e) => setPartnerID(e.target.value)} />
+ </Form.Item>
+ <Form.Item
+ label="User ID"
+ name="User ID"
+ rules={[{ required: true, message: 'Please input the User ID!' }]}
+ >
+ <Input onChange={(e) => setUserID(e.target.value)} />
+ </Form.Item>
+ <Form.Item label="System ID" name="System ID">
+ <Input onChange={(e) => setSystemID(e.target.value)} />
+ </Form.Item>
+ </Form>
+ ) : (
+ <div
+ style={{
+ fontSize: 24,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ }}
+ >
+ <div>Please print out this document and send it to the bank.</div>
+ <div>
+ <a href={printLink} target="_blank">
+ Link to document
+ </a>{' '}
+ </div>
+ </div>
+ )}
+ </div>
+ <div className="steps-action">
+ <Button
+ style={{ marginRight: '20px' }}
+ size="large"
+ onClick={() => closeDrawer()}
+ >
+ Cancel
+ </Button>
+ {currentStep < steps.length - 1 ? (
+ <Button
+ style={{ marginRight: '40px' }}
+ type="primary"
+ size="large"
+ onClick={() => next()}
+ >
+ Next
+ </Button>
+ ) : (
+ <Button
+ style={{ marginRight: '40px' }}
+ type="primary"
+ size="large"
+ onClick={() => closeDrawer()}
+ >
+ Done
+ </Button>
+ )}
+ </div>
+ </Drawer>
+ );
+};
+
+export default AddBankConnectionDrawer;
diff --git a/frontend/src/components/bank-accounts/BankConnectionCard.tsx b/frontend/src/components/bank-accounts/BankConnectionCard.tsx
new file mode 100644
index 00000000..36f21f9a
--- /dev/null
+++ b/frontend/src/components/bank-accounts/BankConnectionCard.tsx
@@ -0,0 +1,29 @@
+import React, { useState } from 'react';
+import { Card } from 'antd';
+import BankConnectionDrawer from './BankConnectionDrawer';
+
+const BankConnectionCard = (props) => {
+ const { type, name, updateBankAccountsTab } = props;
+ const [visible, setVisible] = useState(false);
+ const showDrawer = () => {
+ setVisible(true);
+ };
+ const onClose = () => {
+ setVisible(false);
+ };
+ return (
+ <>
+ <Card title={type} bordered={false} onClick={() => showDrawer()}>
+ <p>Name: {name}</p>
+ </Card>
+ <BankConnectionDrawer
+ updateBankAccountsTab={updateBankAccountsTab}
+ name={name}
+ visible={visible}
+ onClose={onClose}
+ />
+ </>
+ );
+};
+
+export default BankConnectionCard;
diff --git a/frontend/src/components/bank-accounts/BankConnectionDrawer.tsx b/frontend/src/components/bank-accounts/BankConnectionDrawer.tsx
new file mode 100644
index 00000000..cb22ebfe
--- /dev/null
+++ b/frontend/src/components/bank-accounts/BankConnectionDrawer.tsx
@@ -0,0 +1,164 @@
+import React, { useState } from 'react';
+import { Button, Drawer, Table } from 'antd';
+
+const columns = [
+ {
+ title: 'Account ID',
+ dataIndex: 'offeredAccountId',
+ },
+ {
+ title: 'Owner name',
+ dataIndex: 'ownerName',
+ },
+ {
+ title: 'IBAN',
+ dataIndex: 'iban',
+ },
+ {
+ title: 'BIC',
+ dataIndex: 'bic',
+ },
+];
+
+const BankConnectionDrawer = (props) => {
+ const { visible, onClose, name, updateBankAccountsTab } = props;
+ const [printLink, setPrintLink] = useState('');
+ const [accountsList, setAccountsList] = useState([]);
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+
+ const onSelectChange = (selectedRowKeys) => {
+ setSelectedRowKeys(selectedRowKeys);
+ };
+
+ const fetchKeyLetter = async () => {
+ const authHeader = await window.localStorage.getItem('authHeader');
+ await fetch(`/bank-connections/${name}/keyletter`, {
+ headers: new Headers({
+ Authorization: `Basic ${authHeader}`,
+ }),
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.blob();
+ }
+ throw 'Cannot fetch keyletter';
+ })
+ .then(async (blob) => {
+ const pdfLink = URL.createObjectURL(blob);
+ setPrintLink(pdfLink);
+ });
+ };
+
+ const fetchBankAccounts = async () => {
+ const authHeader = await window.localStorage.getItem('authHeader');
+
+ await fetch(`/bank-connections/${name}/accounts`, {
+ headers: new Headers({
+ Authorization: `Basic ${authHeader}`,
+ }),
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw 'Cannot fetch bank accounts';
+ }
+ return response.json();
+ })
+ .then((response) => {
+ setAccountsList(
+ response.map((account, index) => ({
+ ...account,
+ key: index,
+ }))
+ );
+ })
+ .catch((err) => {
+ throw new Error(err);
+ });
+ };
+
+ const importBankAccounts = async () => {
+ for (let i = 0; i < selectedRowKeys.length; i++) {
+ const { offeredAccountId } = accountsList[i];
+ await importBankAccount(offeredAccountId);
+ }
+ await updateBankAccountsTab(); // refresh bank accounts tab
+ onClose();
+ };
+
+ const importBankAccount = async (offeredAccountId) => {
+ const authHeader = await window.localStorage.getItem('authHeader');
+ await fetch(`/bank-connections/${name}/import-account`, {
+ headers: new Headers({
+ Authorization: `Basic ${authHeader}`,
+ 'Content-Type': 'application/json',
+ }),
+ method: 'POST',
+ body: JSON.stringify({
+ offeredAccountId: offeredAccountId ? offeredAccountId : '',
+ nexusBankAccountId: offeredAccountId,
+ }),
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw 'Cannot import bank account';
+ }
+ })
+ .catch((err) => {
+ throw new Error(err);
+ });
+ };
+
+ React.useEffect(() => {
+ fetchKeyLetter();
+ fetchBankAccounts();
+ }, []);
+
+ return (
+ <Drawer
+ title={name}
+ placement="right"
+ closable={false}
+ onClose={onClose}
+ visible={visible}
+ width={850}
+ >
+ <div
+ style={{
+ position: 'absolute',
+ right: 20,
+ }}
+ >
+ <a href={printLink} target="_blank">
+ Print document link
+ </a>{' '}
+ </div>
+ <h2>Import Bank Accounts</h2>
+ <Table
+ rowSelection={{
+ selectedRowKeys,
+ onChange: onSelectChange,
+ }}
+ columns={columns}
+ dataSource={accountsList}
+ />
+ <div className="steps-action">
+ <Button
+ style={{ marginRight: '20px' }}
+ size="large"
+ onClick={() => onClose()}
+ >
+ Cancel
+ </Button>
+ <Button
+ style={{ marginRight: '20px' }}
+ size="large"
+ onClick={() => importBankAccounts()}
+ >
+ Import selected
+ </Button>
+ </div>
+ </Drawer>
+ );
+};
+
+export default BankConnectionDrawer;
diff --git a/frontend/src/components/bank-accounts/Index.tsx b/frontend/src/components/bank-accounts/Index.tsx
index 69f2f2c2..2ec785c7 100644
--- a/frontend/src/components/bank-accounts/Index.tsx
+++ b/frontend/src/components/bank-accounts/Index.tsx
@@ -1,9 +1,11 @@
import React, { useState } from 'react';
-import { Button, Card, Col, Row, Tabs } from 'antd';
+import { Button, Card, Col, Collapse, Row, Tabs } from 'antd';
import './BankAccounts.less';
-// import AddBankConnectionDrawer from './AddBankConnectionDrawer';
+import AddBankConnectionDrawer from './AddBankConnectionDrawer';
+import BankConnectionCard from './BankConnectionCard';
const { TabPane } = Tabs;
+const { Panel } = Collapse;
const BankAccounts = () => {
const [connectionsList, setConnectionsList] = useState([]);
@@ -17,7 +19,6 @@ const BankAccounts = () => {
}),
})
.then((response) => {
- console.log(response);
if (response.ok) {
return response.json();
}
@@ -27,7 +28,6 @@ const BankAccounts = () => {
setConnectionsList(response.bankConnections);
})
.catch((err) => {
- console.log(err);
throw new Error(err);
});
};
@@ -49,7 +49,6 @@ const BankAccounts = () => {
setAccountsList(response.accounts);
})
.catch((err) => {
- console.log(err);
throw new Error(err);
});
};
@@ -69,61 +68,60 @@ const BankAccounts = () => {
fetchBankAccounts();
};
+ const bankAccountsContent =
+ accountsList.length > 0 ? (
+ <Row gutter={[40, 40]}>
+ {accountsList.map((bankAccount) => (
+ <Col span={8}>
+ <Card title={bankAccount['account']} bordered={false}>
+ <p>Holder: {bankAccount['holder']}</p>
+ <p>IBAN: {bankAccount['iban']}</p>
+ <p>BIC: {bankAccount['bic']}</p>
+ </Card>
+ </Col>
+ ))}
+ </Row>
+ ) : (
+ <div style={{ display: 'flex', justifyContent: 'center' }}>
+ <b>
+ No bank accounts found. Import your bank accounts from a bank
+ connection.
+ </b>
+ </div>
+ );
+
return (
<div className="bank-accounts">
<Tabs defaultActiveKey="1" type="card" size="large">
- <TabPane tab="Bank connections" key="1">
- <div className="buttons-row">
- <Button type="primary" size="middle" onClick={showDrawer}>
- Add bank connection
- </Button>
- {/* <AddBankConnectionDrawer visible={visible} onClose={onClose} /> */}
- <Button type="primary" size="middle">
- Import from backup
- </Button>
- <Button type="primary" size="middle">
- Reload connections
- </Button>
- </div>
- <Row gutter={[40, 40]}>
- {connectionsList
- ? connectionsList.map((bankConnection) => (
- <Col span={8}>
- <Card
- title={String(bankConnection['type']).toUpperCase()}
- bordered={false}
- >
- <p>Name: {bankConnection['name']}</p>
- </Card>
- </Col>
- ))
- : null}
- </Row>
- </TabPane>
- <TabPane tab="Your accounts" key="2">
- <div className="buttons-row">
- <Button type="primary" size="middle">
- Add bank account
- </Button>
- </div>
- <Row gutter={[40, 40]}>
- {accountsList
- ? accountsList.map((bankAccount) => (
- <Col span={8}>
- <Card
- title={String(bankAccount['account']).toUpperCase()}
- bordered={false}
- >
- <p>Holder: {bankAccount['holder']}</p>
- <p>IBAN: {bankAccount['iban']}</p>
- <p>BIC: {bankAccount['bic']}</p>
- </Card>
- </Col>
- ))
- : null}
- </Row>
+ <TabPane tab="Your accounts" key="1">
+ <Collapse defaultActiveKey="2">
+ <Panel header="Bank connections" key="1">
+ <div className="buttons-row">
+ <Button type="primary" size="middle" onClick={showDrawer}>
+ Add bank connection
+ </Button>
+ <AddBankConnectionDrawer visible={visible} onClose={onClose} />
+ </div>
+ <Row gutter={[40, 40]}>
+ {connectionsList
+ ? connectionsList.map((bankConnection) => (
+ <Col span={8}>
+ <BankConnectionCard
+ type={String(bankConnection['type']).toUpperCase()}
+ name={bankConnection['name']}
+ updateBankAccountsTab={() => fetchBankAccounts()}
+ />
+ </Col>
+ ))
+ : null}
+ </Row>
+ </Panel>
+ <Panel header="Bank accounts" key="2">
+ {bankAccountsContent}
+ </Panel>
+ </Collapse>
</TabPane>
- <TabPane tab="Recipient accounts" key="3">
+ <TabPane tab="Recipient accounts" key="2">
Placeholder
</TabPane>
</Tabs>