diff options
author | tanhengyeow <E0032242@u.nus.edu> | 2020-06-26 11:32:45 +0800 |
---|---|---|
committer | tanhengyeow <E0032242@u.nus.edu> | 2020-06-26 11:32:45 +0800 |
commit | 67767c666bcd1ee6ca01df2747b55bf6e5a70485 (patch) | |
tree | 37be4dcd930e830e4140c8a860bb8e20816027a2 /frontend | |
parent | 501866d656f33814b63289054ba4de5b19cad0f1 (diff) | |
download | libeufin-67767c666bcd1ee6ca01df2747b55bf6e5a70485.tar.gz libeufin-67767c666bcd1ee6ca01df2747b55bf6e5a70485.tar.bz2 libeufin-67767c666bcd1ee6ca01df2747b55bf6e5a70485.zip |
Add support for adding and showing bank connections/bank accounts
Diffstat (limited to 'frontend')
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> |