From a6f822b05e6547ee7576bb4a1c66d8dbf7a08632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Thu, 4 Mar 2021 20:32:53 +0000 Subject: [PATCH 01/11] feat(table): add a simple table component with tailwind TRELLO-24 #Done --- src/components/layouts/Table/Table.tsx | 7 ++++ src/components/layouts/Table/TableBody.tsx | 7 ++++ src/components/layouts/Table/TableCell.tsx | 7 ++++ src/components/layouts/Table/TableHeader.tsx | 7 ++++ .../layouts/Table/TableHeaderCell.tsx | 15 ++++++++ src/components/layouts/Table/TableRow.tsx | 7 ++++ src/components/layouts/Table/index.ts | 6 ++++ src/pages/ipfs/Ipfs.tsx | 8 ++--- tailwind.config.js | 34 ++++++++++--------- 9 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 src/components/layouts/Table/Table.tsx create mode 100644 src/components/layouts/Table/TableBody.tsx create mode 100644 src/components/layouts/Table/TableCell.tsx create mode 100644 src/components/layouts/Table/TableHeader.tsx create mode 100644 src/components/layouts/Table/TableHeaderCell.tsx create mode 100644 src/components/layouts/Table/TableRow.tsx create mode 100644 src/components/layouts/Table/index.ts diff --git a/src/components/layouts/Table/Table.tsx b/src/components/layouts/Table/Table.tsx new file mode 100644 index 0000000..0c5c99a --- /dev/null +++ b/src/components/layouts/Table/Table.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const Table: React.FC<{ className?: string }> = ({ className = '', children }): JSX.Element => { + return {children}
; +}; + +export default Table; diff --git a/src/components/layouts/Table/TableBody.tsx b/src/components/layouts/Table/TableBody.tsx new file mode 100644 index 0000000..fbe97b3 --- /dev/null +++ b/src/components/layouts/Table/TableBody.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const TableBody: React.FC<{ className?: string }> = ({ className = '', children }): JSX.Element => { + return {children}; +}; + +export default TableBody; diff --git a/src/components/layouts/Table/TableCell.tsx b/src/components/layouts/Table/TableCell.tsx new file mode 100644 index 0000000..d6760d7 --- /dev/null +++ b/src/components/layouts/Table/TableCell.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const TableCell: React.FC<{ className?: string }> = ({ className = '', children }): JSX.Element => { + return {children}; +}; + +export default TableCell; diff --git a/src/components/layouts/Table/TableHeader.tsx b/src/components/layouts/Table/TableHeader.tsx new file mode 100644 index 0000000..93ff9d8 --- /dev/null +++ b/src/components/layouts/Table/TableHeader.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const TableHeader: React.FC<{ className?: string }> = ({ className = '', children }): JSX.Element => { + return {children}; +}; + +export default TableHeader; diff --git a/src/components/layouts/Table/TableHeaderCell.tsx b/src/components/layouts/Table/TableHeaderCell.tsx new file mode 100644 index 0000000..9db24d1 --- /dev/null +++ b/src/components/layouts/Table/TableHeaderCell.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +const TableHeaderCell: React.FC<{ className?: string }> = ({ className = '', children }): JSX.Element => { + return ( + + {children} + + ); +}; +9; + +export default TableHeaderCell; diff --git a/src/components/layouts/Table/TableRow.tsx b/src/components/layouts/Table/TableRow.tsx new file mode 100644 index 0000000..a641fd8 --- /dev/null +++ b/src/components/layouts/Table/TableRow.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const TableRow: React.FC<{ className?: string }> = ({ className = '', children }): JSX.Element => { + return {children}; +}; + +export default TableRow; diff --git a/src/components/layouts/Table/index.ts b/src/components/layouts/Table/index.ts new file mode 100644 index 0000000..5507ba5 --- /dev/null +++ b/src/components/layouts/Table/index.ts @@ -0,0 +1,6 @@ +export { default as Table } from './Table'; +export { default as TableBody } from './TableBody'; +export { default as TableCell } from './TableCell'; +export { default as TableHeader } from './TableHeader'; +export { default as TableHeaderCell } from './TableHeaderCell'; +export { default as TableRow } from './TableRow'; diff --git a/src/pages/ipfs/Ipfs.tsx b/src/pages/ipfs/Ipfs.tsx index 39e2d2f..a7f5d1b 100644 --- a/src/pages/ipfs/Ipfs.tsx +++ b/src/pages/ipfs/Ipfs.tsx @@ -1,16 +1,14 @@ -import React, { ReactNode } from 'react'; -import { Link } from 'react-router-dom'; -import { Button, Grid, Header, Message, Segment } from 'semantic-ui-react'; - import ace from 'brace'; import 'brace/mode/json'; import 'brace/theme/github'; - // No typings for jsoneditor-react for now just using @ts-ignore for getting around the issue // Later on with advanced design we might not used that library. If we do we can as well define typings for it // @ts-ignore import { JsonEditor as Editor } from 'jsoneditor-react'; import 'jsoneditor-react/es/editor.min.css'; +import React, { ReactNode } from 'react'; +import { Link } from 'react-router-dom'; +import { Button, Grid, Header, Message, Segment } from 'semantic-ui-react'; interface IpfsProps { match: { params: { hash: string } }; diff --git a/tailwind.config.js b/tailwind.config.js index 296c1b9..e3c4511 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,23 +1,25 @@ module.exports = { theme: { - colors: { - primary: { - DEFAULT: 'var(--color-primary)', - light: 'var(--color-primary-light)', + extend: { + colors: { + primary: { + DEFAULT: 'var(--color-primary)', + light: 'var(--color-primary-light)', + }, + secondary: { + DEFAULT: 'var(--color-secondary)', + light: 'var(--color-secondary-light)', + lightest: 'var(--color-secondary-lightest)', + darkest: 'var(--color-secondary-darkest)', + }, + dark: 'var(--color-dark)', + light: 'var(--color-light)', }, - secondary: { - DEFAULT: 'var(--color-secondary)', - light: 'var(--color-secondary-light)', - lightest: 'var(--color-secondary-lightest)', - darkest: 'var(--color-secondary-darkest)', + fontFamily: { + sans: ['Poppins'], + serif: ['Georgia'], + mono: ['Roboto'], }, - dark: 'var(--color-dark)', - light: 'var(--color-light)', - }, - fontFamily: { - sans: ['Poppins'], - serif: ['Georgia'], - mono: ['Roboto'], }, }, purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], From c1262bab733d1bba269945e0d9396be9824dc2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Sun, 7 Mar 2021 19:09:19 +0000 Subject: [PATCH 02/11] pr feedback --- src/components/{layouts => common}/Table/Table.tsx | 0 src/components/{layouts => common}/Table/TableBody.tsx | 0 src/components/{layouts => common}/Table/TableCell.tsx | 0 src/components/{layouts => common}/Table/TableHeader.tsx | 0 src/components/{layouts => common}/Table/TableHeaderCell.tsx | 0 src/components/{layouts => common}/Table/TableRow.tsx | 0 src/components/{layouts => common}/Table/index.ts | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename src/components/{layouts => common}/Table/Table.tsx (100%) rename src/components/{layouts => common}/Table/TableBody.tsx (100%) rename src/components/{layouts => common}/Table/TableCell.tsx (100%) rename src/components/{layouts => common}/Table/TableHeader.tsx (100%) rename src/components/{layouts => common}/Table/TableHeaderCell.tsx (100%) rename src/components/{layouts => common}/Table/TableRow.tsx (100%) rename src/components/{layouts => common}/Table/index.ts (100%) diff --git a/src/components/layouts/Table/Table.tsx b/src/components/common/Table/Table.tsx similarity index 100% rename from src/components/layouts/Table/Table.tsx rename to src/components/common/Table/Table.tsx diff --git a/src/components/layouts/Table/TableBody.tsx b/src/components/common/Table/TableBody.tsx similarity index 100% rename from src/components/layouts/Table/TableBody.tsx rename to src/components/common/Table/TableBody.tsx diff --git a/src/components/layouts/Table/TableCell.tsx b/src/components/common/Table/TableCell.tsx similarity index 100% rename from src/components/layouts/Table/TableCell.tsx rename to src/components/common/Table/TableCell.tsx diff --git a/src/components/layouts/Table/TableHeader.tsx b/src/components/common/Table/TableHeader.tsx similarity index 100% rename from src/components/layouts/Table/TableHeader.tsx rename to src/components/common/Table/TableHeader.tsx diff --git a/src/components/layouts/Table/TableHeaderCell.tsx b/src/components/common/Table/TableHeaderCell.tsx similarity index 100% rename from src/components/layouts/Table/TableHeaderCell.tsx rename to src/components/common/Table/TableHeaderCell.tsx diff --git a/src/components/layouts/Table/TableRow.tsx b/src/components/common/Table/TableRow.tsx similarity index 100% rename from src/components/layouts/Table/TableRow.tsx rename to src/components/common/Table/TableRow.tsx diff --git a/src/components/layouts/Table/index.ts b/src/components/common/Table/index.ts similarity index 100% rename from src/components/layouts/Table/index.ts rename to src/components/common/Table/index.ts From bc3ad166e315c898fa19dba0060f37f64cf40358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Tue, 2 Mar 2021 20:45:44 +0000 Subject: [PATCH 03/11] feat(card): add card component + extend tailwind instead of overwrite --- src/components/layouts/Card/Card.tsx | 7 +++++++ src/components/layouts/Card/CardBody.tsx | 7 +++++++ src/components/layouts/Card/CardFooter.tsx | 7 +++++++ src/components/layouts/Card/CardHeader.tsx | 7 +++++++ src/components/layouts/Card/index.ts | 4 ++++ 5 files changed, 32 insertions(+) create mode 100644 src/components/layouts/Card/Card.tsx create mode 100644 src/components/layouts/Card/CardBody.tsx create mode 100644 src/components/layouts/Card/CardFooter.tsx create mode 100644 src/components/layouts/Card/CardHeader.tsx create mode 100644 src/components/layouts/Card/index.ts diff --git a/src/components/layouts/Card/Card.tsx b/src/components/layouts/Card/Card.tsx new file mode 100644 index 0000000..6410dd9 --- /dev/null +++ b/src/components/layouts/Card/Card.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const Card: React.FC<{}> = ({ children }) => { + return
{children}
; +}; + +export default Card; diff --git a/src/components/layouts/Card/CardBody.tsx b/src/components/layouts/Card/CardBody.tsx new file mode 100644 index 0000000..5b4bc13 --- /dev/null +++ b/src/components/layouts/Card/CardBody.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const CardBody: React.FC<{}> = ({ children }) => { + return
{children}
; +}; + +export default CardBody; diff --git a/src/components/layouts/Card/CardFooter.tsx b/src/components/layouts/Card/CardFooter.tsx new file mode 100644 index 0000000..921085a --- /dev/null +++ b/src/components/layouts/Card/CardFooter.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const CardFooter: React.FC<{}> = ({ children }) => { + return
{children}
; +}; + +export default CardFooter; diff --git a/src/components/layouts/Card/CardHeader.tsx b/src/components/layouts/Card/CardHeader.tsx new file mode 100644 index 0000000..f456e6e --- /dev/null +++ b/src/components/layouts/Card/CardHeader.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const CardHeader: React.FC<{}> = ({ children }) => { + return
{children}
; +}; + +export default CardHeader; diff --git a/src/components/layouts/Card/index.ts b/src/components/layouts/Card/index.ts new file mode 100644 index 0000000..95e3ff7 --- /dev/null +++ b/src/components/layouts/Card/index.ts @@ -0,0 +1,4 @@ +export { default as Card } from './Card'; +export { default as CardBody } from './CardBody'; +export { default as CardFooter } from './CardFooter'; +export { default as CardHeader } from './CardHeader'; From 0a515515eb93a0181d0be69b74bad1a23cf8500e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Mon, 8 Mar 2021 20:06:11 +0000 Subject: [PATCH 04/11] pr feedback --- src/components/{layouts => common}/Card/Card.tsx | 0 src/components/{layouts => common}/Card/CardBody.tsx | 0 src/components/{layouts => common}/Card/CardFooter.tsx | 0 src/components/{layouts => common}/Card/CardHeader.tsx | 0 src/components/{layouts => common}/Card/index.ts | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename src/components/{layouts => common}/Card/Card.tsx (100%) rename src/components/{layouts => common}/Card/CardBody.tsx (100%) rename src/components/{layouts => common}/Card/CardFooter.tsx (100%) rename src/components/{layouts => common}/Card/CardHeader.tsx (100%) rename src/components/{layouts => common}/Card/index.ts (100%) diff --git a/src/components/layouts/Card/Card.tsx b/src/components/common/Card/Card.tsx similarity index 100% rename from src/components/layouts/Card/Card.tsx rename to src/components/common/Card/Card.tsx diff --git a/src/components/layouts/Card/CardBody.tsx b/src/components/common/Card/CardBody.tsx similarity index 100% rename from src/components/layouts/Card/CardBody.tsx rename to src/components/common/Card/CardBody.tsx diff --git a/src/components/layouts/Card/CardFooter.tsx b/src/components/common/Card/CardFooter.tsx similarity index 100% rename from src/components/layouts/Card/CardFooter.tsx rename to src/components/common/Card/CardFooter.tsx diff --git a/src/components/layouts/Card/CardHeader.tsx b/src/components/common/Card/CardHeader.tsx similarity index 100% rename from src/components/layouts/Card/CardHeader.tsx rename to src/components/common/Card/CardHeader.tsx diff --git a/src/components/layouts/Card/index.ts b/src/components/common/Card/index.ts similarity index 100% rename from src/components/layouts/Card/index.ts rename to src/components/common/Card/index.ts From e8dc5aa86a83c0e67cb4c44e60de4d74978cd128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Sun, 14 Mar 2021 10:49:03 +0000 Subject: [PATCH 05/11] feat(tracked-contracts): ability to search through the chain to select and view the tracked contracts TRELLO-19 #Done --- src/App.tsx | 2 + src/components/common/Card/Card.tsx | 4 +- src/components/common/Input/SearchInput.tsx | 46 +++++++++ src/components/common/Input/index.ts | 3 + .../common/Table/TableHeaderCell.tsx | 1 - src/components/common/index.ts | 2 + .../layouts/NavigationBar/NavigationBar.tsx | 3 + .../TrackedContractsList.tsx | 96 +++++++++++++++++++ src/pages/tracked-contracts/index.ts | 1 + src/pages/tracked-contracts/mock-data.ts | 33 +++++++ 10 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/components/common/Input/SearchInput.tsx create mode 100644 src/components/common/Input/index.ts create mode 100644 src/components/common/index.ts create mode 100644 src/pages/tracked-contracts/TrackedContractsList.tsx create mode 100644 src/pages/tracked-contracts/index.ts create mode 100644 src/pages/tracked-contracts/mock-data.ts diff --git a/src/App.tsx b/src/App.tsx index a661741..e2a976a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import './App.scss'; import BaseLayout from './components/layouts/BaseLayout'; import Ipfs from './pages/ipfs/Ipfs'; import IpfsList from './pages/ipfs/IpfsList'; +import TrackedContractsList from './pages/tracked-contracts/TrackedContractsList'; const App = (): JSX.Element => { return ( @@ -11,6 +12,7 @@ const App = (): JSX.Element => { + diff --git a/src/components/common/Card/Card.tsx b/src/components/common/Card/Card.tsx index 6410dd9..0e01879 100644 --- a/src/components/common/Card/Card.tsx +++ b/src/components/common/Card/Card.tsx @@ -1,7 +1,7 @@ import React from 'react'; -const Card: React.FC<{}> = ({ children }) => { - return
{children}
; +const Card: React.FC<{ className?: string }> = ({ className = '', children }) => { + return
{children}
; }; export default Card; diff --git a/src/components/common/Input/SearchInput.tsx b/src/components/common/Input/SearchInput.tsx new file mode 100644 index 0000000..db8e72c --- /dev/null +++ b/src/components/common/Input/SearchInput.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { FaSearch, FaTimes } from 'react-icons/fa'; + +interface SearchInput { + value: any; + onChange: (value: string) => void; + name?: string; + id?: string; + className?: string; + placeholder?: string; +} + +const SearchInput: React.FC = ({ + value, + onChange, + name = 'search', + id = 'search', + className = '', + placeholder = '', +}): JSX.Element => { + return ( +
+
+ {/** TODO: Remove this and actually use the other icons library once we get the query manager merged in */} + {value ? ( + + ) : ( + + )} +
+ onChange(event.target.value)} + /> +
+ ); +}; + +export default SearchInput; diff --git a/src/components/common/Input/index.ts b/src/components/common/Input/index.ts new file mode 100644 index 0000000..8300e87 --- /dev/null +++ b/src/components/common/Input/index.ts @@ -0,0 +1,3 @@ +import SearchInput from './SearchInput'; + +export default SearchInput; diff --git a/src/components/common/Table/TableHeaderCell.tsx b/src/components/common/Table/TableHeaderCell.tsx index 9db24d1..a920c29 100644 --- a/src/components/common/Table/TableHeaderCell.tsx +++ b/src/components/common/Table/TableHeaderCell.tsx @@ -10,6 +10,5 @@ const TableHeaderCell: React.FC<{ className?: string }> = ({ className = '', chi ); }; -9; export default TableHeaderCell; diff --git a/src/components/common/index.ts b/src/components/common/index.ts new file mode 100644 index 0000000..21643e1 --- /dev/null +++ b/src/components/common/index.ts @@ -0,0 +1,2 @@ +export * from './Card'; +export * from './Table'; diff --git a/src/components/layouts/NavigationBar/NavigationBar.tsx b/src/components/layouts/NavigationBar/NavigationBar.tsx index 68fee8e..e1aee0e 100644 --- a/src/components/layouts/NavigationBar/NavigationBar.tsx +++ b/src/components/layouts/NavigationBar/NavigationBar.tsx @@ -19,6 +19,9 @@ const NavigationBar = (): JSX.Element => {
  • IPFS
  • +
  • + Tracked Contracts +
  • diff --git a/src/pages/tracked-contracts/TrackedContractsList.tsx b/src/pages/tracked-contracts/TrackedContractsList.tsx new file mode 100644 index 0000000..f71b7e4 --- /dev/null +++ b/src/pages/tracked-contracts/TrackedContractsList.tsx @@ -0,0 +1,96 @@ +import React, { useEffect, useState } from 'react'; +import { Card, CardBody, CardHeader } from '../../components/common'; +import SearchInput from '../../components/common/Input'; +import mockDataChain from './mock-data'; + +type ChainType = 'evm' | 'substrate'; + +interface Chain { + name: string; + type: ChainType; + project: string; + url: string; + credentials: { + // TODO: Ask potentially BE to have structure camelCase in case or we can just disable the warning in eslint + /* eslint-disable camelcase */ + private_key?: string; + public_key?: string; + }; + tracked_contracts: string[]; +} + +const TrackedContractsList: React.FC<{}> = () => { + const [chains, setChains] = useState([]); + const [activeChain, setActiveChain] = useState(); + const [searchChain, setSearchChain] = useState(''); + + useEffect(() => { + setChains(mockDataChain as Chain[]); + }, []); + + const allChains = chains + .filter((data) => { + if (!searchChain) { + return data; + } + if (data.name.includes(searchChain.trim().toLocaleLowerCase())) { + return data; + } + return null; + }) + .map((chain) => { + return ( +
    setActiveChain(chain)} + onKeyPress={() => setActiveChain(chain)} + tabIndex={0} + > + {chain.name} +
    + ); + }); + + const activeContracts = + activeChain && activeChain.tracked_contracts.length + ? activeChain.tracked_contracts.map((contract) => { + return contract; + }) + : 'No Tracked Contracts'; + + return ( +
    +
    +
    + +
    + setSearchChain(val)} + placeholder="Search" + className="py-3 text-lg leading-relaxed max-w-full" + /> +
    + +
    {allChains}
    +
    +
    +
    +
    + + + Tracked Contracts + {/*
    */} + {activeContracts} +
    +
    +
    +
    +
    + ); +}; + +export default TrackedContractsList; diff --git a/src/pages/tracked-contracts/index.ts b/src/pages/tracked-contracts/index.ts new file mode 100644 index 0000000..36fe015 --- /dev/null +++ b/src/pages/tracked-contracts/index.ts @@ -0,0 +1 @@ +export { default as TrackedContractsList } from './TrackedContractsList'; diff --git a/src/pages/tracked-contracts/mock-data.ts b/src/pages/tracked-contracts/mock-data.ts new file mode 100644 index 0000000..a88caa5 --- /dev/null +++ b/src/pages/tracked-contracts/mock-data.ts @@ -0,0 +1,33 @@ +// We can remove this when we can hook to the BE and get that data it's just for getting the UX ready for it +const mockDataChain = [ + { + name: 'eth.mainnet', + type: 'evm', + project: 'eth', + url: 'https://mainnet.infura.io/v3/', + credentials: {}, + tracked_contracts: [], + }, + { + name: 'eth.dev-mainnet-fork', + type: 'evm', + project: 'eth', + url: 'ws://localhost:8545', + credentials: { + private_key: '', + }, + tracked_contracts: ['0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87'], + }, + { + name: 'dev-canvas', + type: 'substrate', + project: 'canvas', + url: 'ws://127.0.0.1:9944', + credentials: { + private_key: '', + public_key: '', + }, + tracked_contracts: ['5FnDzvXcnu3RCtQ3f3RFqaQnVLfcWLZ9FvUURNoPMdmsKZXP'], + }, +]; +export default mockDataChain; From ac912e70cc1a162e0d194b4fc08d1d1821eb704d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Tue, 16 Mar 2021 08:47:54 +0000 Subject: [PATCH 06/11] pr feedback --- src/pages/tracked-contracts/TrackedChain.tsx | 23 ++++++++++++ .../TrackedContractsList.tsx | 36 ++++--------------- 2 files changed, 30 insertions(+), 29 deletions(-) create mode 100644 src/pages/tracked-contracts/TrackedChain.tsx diff --git a/src/pages/tracked-contracts/TrackedChain.tsx b/src/pages/tracked-contracts/TrackedChain.tsx new file mode 100644 index 0000000..1500d58 --- /dev/null +++ b/src/pages/tracked-contracts/TrackedChain.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +interface TrackedChainProps { + name: string; + active: boolean; + onClick: () => void; +} + +const TrackedChain: React.FC = ({ name, active, onClick }) => { + return ( +
    onClick()} + onKeyPress={() => onClick()} + tabIndex={0} + > + {name} +
    + ); +}; + +export default TrackedChain; diff --git a/src/pages/tracked-contracts/TrackedContractsList.tsx b/src/pages/tracked-contracts/TrackedContractsList.tsx index f71b7e4..ccdbff4 100644 --- a/src/pages/tracked-contracts/TrackedContractsList.tsx +++ b/src/pages/tracked-contracts/TrackedContractsList.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { Card, CardBody, CardHeader } from '../../components/common'; import SearchInput from '../../components/common/Input'; import mockDataChain from './mock-data'; +import TrackedChain from './TrackedChain'; type ChainType = 'evm' | 'substrate'; @@ -28,38 +29,15 @@ const TrackedContractsList: React.FC<{}> = () => { setChains(mockDataChain as Chain[]); }, []); + // Filter out by the search but also keep the current active in the search const allChains = chains - .filter((data) => { - if (!searchChain) { - return data; - } - if (data.name.includes(searchChain.trim().toLocaleLowerCase())) { - return data; - } - return null; - }) - .map((chain) => { - return ( -
    setActiveChain(chain)} - onKeyPress={() => setActiveChain(chain)} - tabIndex={0} - > - {chain.name} -
    - ); - }); + .filter((chain) => chain.name.includes(searchChain.trim().toLocaleLowerCase()) || activeChain?.name === chain.name) + .map((chain) => ( + setActiveChain(chain)} /> + )); const activeContracts = - activeChain && activeChain.tracked_contracts.length - ? activeChain.tracked_contracts.map((contract) => { - return contract; - }) - : 'No Tracked Contracts'; + activeChain && activeChain.tracked_contracts.length ? activeChain.tracked_contracts : 'No Tracked Contracts'; return (
    From a3f1135068caa1fcac148ba75135ddcfa202adf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Thu, 1 Apr 2021 11:20:27 +0100 Subject: [PATCH 07/11] basic ui implementation --- package-lock.json | 38 +++--- package.json | 2 + src/components/common/Buttons/Buttons.tsx | 28 +++++ src/components/common/Buttons/index.ts | 3 + src/components/common/Toggle/Toggle.tsx | 42 +++++++ src/components/common/Toggle/index.ts | 3 + .../tracked-contracts/TrackedContract.tsx | 109 ++++++++++++++++++ .../TrackedContractsList.tsx | 83 +++++++++++-- src/pages/tracked-contracts/mock-data.ts | 4 +- 9 files changed, 286 insertions(+), 26 deletions(-) create mode 100644 src/components/common/Buttons/Buttons.tsx create mode 100644 src/components/common/Buttons/index.ts create mode 100644 src/components/common/Toggle/Toggle.tsx create mode 100644 src/components/common/Toggle/index.ts create mode 100644 src/pages/tracked-contracts/TrackedContract.tsx diff --git a/package-lock.json b/package-lock.json index 8be03db..0c7d67c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2068,6 +2068,11 @@ "@babel/types": "^7.3.0" } }, + "@types/classnames": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz", + "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==" + }, "@types/eslint": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz", @@ -4002,6 +4007,11 @@ } } }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, "clean-css": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", @@ -7819,9 +7829,9 @@ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, "immer": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.9.tgz", - "integrity": "sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A==" + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz", + "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==" }, "import-cwd": { "version": "2.1.0", @@ -10816,9 +10826,9 @@ } }, "open": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.0.tgz", - "integrity": "sha512-PGoBCX/lclIWlpS/R2PQuIR4NJoXh6X5AwVzE7WXnWRGvHg7+4TBCgsujUgiPpm0K1y4qvQeWnCWVTpTKZBtvA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "requires": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -12985,9 +12995,9 @@ } }, "react-dev-utils": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.1.tgz", - "integrity": "sha512-rlgpCupaW6qQqvu0hvv2FDv40QG427fjghV56XyPcP5aKtOAPzNAhQ7bHqk1YdS2vpW1W7aSV3JobedxuPlBAA==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", + "integrity": "sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A==", "requires": { "@babel/code-frame": "7.10.4", "address": "1.1.2", @@ -13002,13 +13012,13 @@ "global-modules": "2.0.0", "globby": "11.0.1", "gzip-size": "5.1.1", - "immer": "7.0.9", + "immer": "8.0.1", "is-root": "2.1.0", "loader-utils": "2.0.0", "open": "^7.0.2", "pkg-up": "3.1.0", "prompts": "2.4.0", - "react-error-overlay": "^6.0.8", + "react-error-overlay": "^6.0.9", "recursive-readdir": "2.2.2", "shell-quote": "1.7.2", "strip-ansi": "6.0.0", @@ -13126,9 +13136,9 @@ } }, "react-error-overlay": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", - "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==" + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", + "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" }, "react-fast-compare": { "version": "3.2.0", diff --git a/package.json b/package.json index c160f8b..7cc7c82 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "@types/classnames": "^2.2.11", "brace": "^0.11.1", + "classnames": "^2.2.6", "jsoneditor": "^9.1.9", "jsoneditor-react": "^3.1.0", "node-sass": "^4.14.1", diff --git a/src/components/common/Buttons/Buttons.tsx b/src/components/common/Buttons/Buttons.tsx new file mode 100644 index 0000000..c5963dc --- /dev/null +++ b/src/components/common/Buttons/Buttons.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +const BLUE_COLOR = 'blue'; +const GREEN_COLOR = 'green'; +const GRAY_COLOR = 'gray'; +const RED_COLOR = 'red'; + +type Color = typeof BLUE_COLOR | typeof GREEN_COLOR | typeof GRAY_COLOR | typeof RED_COLOR; + +interface Button { + onClick?: () => void; + color?: Color; + className?: string; + disabled?: boolean; +} + +const Button: React.FC +); + +export default Button; diff --git a/src/components/common/Buttons/index.ts b/src/components/common/Buttons/index.ts new file mode 100644 index 0000000..2da0511 --- /dev/null +++ b/src/components/common/Buttons/index.ts @@ -0,0 +1,3 @@ +import Button from './Buttons'; + +export default Button; diff --git a/src/components/common/Toggle/Toggle.tsx b/src/components/common/Toggle/Toggle.tsx new file mode 100644 index 0000000..c2c1a7a --- /dev/null +++ b/src/components/common/Toggle/Toggle.tsx @@ -0,0 +1,42 @@ +import classnames from 'classnames'; +import React from 'react'; + +export interface ToggleProps { + color?: string; + enabled: boolean; + onClick: () => void; +} + +const Toggle: React.FC = ({ enabled, onClick, color }) => { + return ( + + ); +}; + +export default Toggle; diff --git a/src/components/common/Toggle/index.ts b/src/components/common/Toggle/index.ts new file mode 100644 index 0000000..2948603 --- /dev/null +++ b/src/components/common/Toggle/index.ts @@ -0,0 +1,3 @@ +import Toggle from './Toggle'; + +export default Toggle; diff --git a/src/pages/tracked-contracts/TrackedContract.tsx b/src/pages/tracked-contracts/TrackedContract.tsx new file mode 100644 index 0000000..9906fc3 --- /dev/null +++ b/src/pages/tracked-contracts/TrackedContract.tsx @@ -0,0 +1,109 @@ +import React, { useEffect, useState } from 'react'; +import { FaCheck, FaTimes, FaTrash } from 'react-icons/fa'; +import Button from '../../components/common/Buttons'; +import Toggle from '../../components/common/Toggle'; + +export interface TrackedContract { + address?: string; + enabled?: boolean; + edited?: boolean; +} + +interface TrackedContractProps { + trackedContract: TrackedContract; + // saved: (contract: TrackedContract) => void; // Instead do a callback as "saved to refresh the view or the array" + remove: () => void; + focused?: boolean; +} + +const TrackedContractRow: React.FC = ({ trackedContract, remove, focused }) => { + const [inputFocused, setInputFocused] = useState(focused || false); + const [edited, setEdited] = useState(false); + const [contract, setContract] = useState(trackedContract); + const initialContract = trackedContract; + + let trackInputRef: any = null; + + useEffect(() => { + if (focused && trackInputRef) { + trackInputRef.focus(); + } + }, [focused, trackInputRef]); + + // const saved = () => { callParent to update the array view without recharging the page } + // + + // Handle the UI state for the contrat changing + const handleContractChange = (value: TrackedContract): void => { + setContract({ ...contract, ...value }); + setEdited(true); + }; + + // reset the state to lose focus on the row + const loseFocus = (): void => { + setEdited(false); + trackInputRef.blur(); + }; + + // Save the contract + const saveClick = (): void => { + // save contract as it is + // then set it as not edited anymore + + loseFocus(); + }; + + // cancel the changes on the contract + const cancelClick = (): void => { + setContract(initialContract); + loseFocus(); + }; + + // Templates + const saveCancelButtons = ( + <> + + + + ); + + const removeButton = ( + + ); + + return ( +
    +
    + { + trackInputRef = e; + }} + value={contract.address} + onFocus={() => setInputFocused(true)} + onBlur={() => setInputFocused(false)} + onChange={(event) => handleContractChange({ address: event.target.value })} + /> +
    +
    + {inputFocused || edited ?
    {saveCancelButtons}
    : ''} + { + handleContractChange({ enabled: !contract.enabled }); + }} + /> + {removeButton} +
    +
    + ); +}; + +export default TrackedContractRow; diff --git a/src/pages/tracked-contracts/TrackedContractsList.tsx b/src/pages/tracked-contracts/TrackedContractsList.tsx index ccdbff4..de60de5 100644 --- a/src/pages/tracked-contracts/TrackedContractsList.tsx +++ b/src/pages/tracked-contracts/TrackedContractsList.tsx @@ -1,8 +1,11 @@ import React, { useEffect, useState } from 'react'; +import { FaPlus } from 'react-icons/fa'; import { Card, CardBody, CardHeader } from '../../components/common'; +import Button from '../../components/common/Buttons'; import SearchInput from '../../components/common/Input'; import mockDataChain from './mock-data'; import TrackedChain from './TrackedChain'; +import TrackedContractRow, { TrackedContract } from './TrackedContract'; type ChainType = 'evm' | 'substrate'; @@ -17,18 +20,29 @@ interface Chain { private_key?: string; public_key?: string; }; - tracked_contracts: string[]; + tracked_contracts: TrackedContract[]; } const TrackedContractsList: React.FC<{}> = () => { const [chains, setChains] = useState([]); const [activeChain, setActiveChain] = useState(); const [searchChain, setSearchChain] = useState(''); + const [focused, setFocused] = useState(false); + + // TODO: current view, we should block user from switching subtrate if they did a change currently + // have a call from the tracked contract when something changed to let the parent know + // We should have a modal displaying that there is some current changes to update + // this is just a safe precaution useEffect(() => { setChains(mockDataChain as Chain[]); }, []); + // const selectChain = (chain: Chain): void => { + // setActiveChain(chain); + // setActiveContracts(chain.tracked_contracts.length ? chain.tracked_contracts : ['No Tracked Contracts']); + // }; + // Filter out by the search but also keep the current active in the search const allChains = chains .filter((chain) => chain.name.includes(searchChain.trim().toLocaleLowerCase()) || activeChain?.name === chain.name) @@ -36,14 +50,40 @@ const TrackedContractsList: React.FC<{}> = () => { setActiveChain(chain)} /> )); - const activeContracts = - activeChain && activeChain.tracked_contracts.length ? activeChain.tracked_contracts : 'No Tracked Contracts'; + // const activeContracts = + // activeChain && activeChain.tracked_contracts.length ? activeChain.tracked_contracts : ['No Tracked Contracts']; + + // TODO: Optmistic update , need to refresh the state + const addContract = (entry: TrackedContract): void => { + let emptyContractExisting = false; + if (activeChain?.tracked_contracts.length) { + const lastEntry = activeChain?.tracked_contracts[activeChain?.tracked_contracts.length - 1]; + emptyContractExisting = lastEntry.address === ''; + } + + if (activeChain && !emptyContractExisting) { + setActiveChain({ + ...activeChain, + tracked_contracts: [...activeChain.tracked_contracts, entry], + }); + setFocused(true); + } + }; + + const removeContract = (index: number): void => { + if (activeChain?.tracked_contracts.length) { + activeChain.tracked_contracts.splice(index, 1); + setActiveChain({ + ...activeChain, + }); + } + }; return (
    -
    -
    - +
    +
    +
    = () => {
    -
    +
    - Tracked Contracts - {/*
    */} - {activeContracts} + +
    +
    Tracked Contracts
    +
    + +
    +
    +
    +
    + {activeChain?.tracked_contracts.map((contract: TrackedContract, index: number) => { + return ( + removeContract(index)} + /> + ); + })} +
    diff --git a/src/pages/tracked-contracts/mock-data.ts b/src/pages/tracked-contracts/mock-data.ts index a88caa5..5a82c15 100644 --- a/src/pages/tracked-contracts/mock-data.ts +++ b/src/pages/tracked-contracts/mock-data.ts @@ -16,7 +16,7 @@ const mockDataChain = [ credentials: { private_key: '', }, - tracked_contracts: ['0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87'], + tracked_contracts: [{ address: '0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87', enabled: true }], }, { name: 'dev-canvas', @@ -27,7 +27,7 @@ const mockDataChain = [ private_key: '', public_key: '', }, - tracked_contracts: ['5FnDzvXcnu3RCtQ3f3RFqaQnVLfcWLZ9FvUURNoPMdmsKZXP'], + tracked_contracts: [{ address: '5FnDzvXcnu3RCtQ3f3RFqaQnVLfcWLZ9FvUURNoPMdmsKZXP', enabled: false }], }, ]; export default mockDataChain; From 6463125cad7feb281d90c7a4f9b8cb50d0707a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Thu, 1 Apr 2021 16:13:57 +0100 Subject: [PATCH 08/11] hook with the BE --- package-lock.json | 8 +++ package.json | 1 + src/api/api.ts | 70 +++++++++++++++++++ src/api/chains.ts | 14 ++++ src/api/contracts.ts | 17 +++++ src/components/common/Buttons/Buttons.tsx | 2 +- src/interfaces/Chain.ts | 8 +++ src/interfaces/Contract.ts | 6 ++ src/interfaces/index.ts | 2 + .../tracked-contracts/TrackedContract.tsx | 67 ++++++++++++------ .../TrackedContractsList.tsx | 69 +++++++----------- src/pages/tracked-contracts/mock-data.ts | 1 + tailwind.config.js | 4 +- 13 files changed, 202 insertions(+), 67 deletions(-) create mode 100644 src/api/api.ts create mode 100644 src/api/chains.ts create mode 100644 src/api/contracts.ts create mode 100644 src/interfaces/Chain.ts create mode 100644 src/interfaces/Contract.ts create mode 100644 src/interfaces/index.ts diff --git a/package-lock.json b/package-lock.json index 0c7d67c..8810576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3013,6 +3013,14 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.1.tgz", "integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==" }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", diff --git a/package.json b/package.json index 7cc7c82..e6d60f0 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/classnames": "^2.2.11", + "axios": "^0.21.1", "brace": "^0.11.1", "classnames": "^2.2.6", "jsoneditor": "^9.1.9", diff --git a/src/api/api.ts b/src/api/api.ts new file mode 100644 index 0000000..d37af94 --- /dev/null +++ b/src/api/api.ts @@ -0,0 +1,70 @@ +import axios from 'axios'; + +// const OBTAIN_TOKEN = '/token/obtain/'; +// const REFRESH_TOKEN_URL = '/token/refresh/'; + +export const ACCESS_TOKEN = 'access_token'; +export const REFRESH_TOKEN = 'refresh_token'; + +// interface JWTTokenResponse { +// refresh: string; +// access: string; +// } + +export const axiosInstance = axios.create({ + baseURL: 'http://127.0.0.1:7424/api/', + timeout: 5000, + headers: { + // 'Authorization': localStorage.getItem('access_token') ? 'JWT ' + localStorage.getItem('access_token') : null, + 'Content-Type': 'application/json', + accept: 'application/json', + }, +}); + +// export const saveTokens = (jwtToken: JWTTokenResponse): JWTTokenResponse => { +// localStorage.setItem(ACCESS_TOKEN, jwtToken.access); +// localStorage.setItem(REFRESH_TOKEN, jwtToken.refresh); +// return jwtToken; +// }; + +const localGet = (key: string, defaultValue = ''): string => { + const value = localStorage.getItem(key); + if (value === null) return defaultValue; + return value; +}; + +export const getAccessToken = (): string => localGet(ACCESS_TOKEN); +export const getRefreshToken = (): string => localGet(REFRESH_TOKEN); + +axiosInstance.interceptors.response.use( + (res) => res, + (err) => { + // const originalRequest = err.config; + // If refresh tokens is expired redirect to login page + // if (err.response.status === 401 && originalRequest.url === REFRESH_TOKEN_URL) { + // window.location.href = '/login/'; + // return Promise.reject(err); + // } + + // // If access token is expired update it + // if (err.response.status === 401 && err.response.statusText === 'Unauthorized') { + // return axiosInstance + // .post(REFRESH_TOKEN_URL, {refresh: localStorage.getItem(REFRESH_TOKEN)}) + // .then(res => res.data) + // .then(saveTokens) + // .then(res => { + // axiosInstance.defaults.headers['Authorization'] = 'JWT ' + res.access; + // originalRequest.headers['Authorization'] = 'JWT ' + res.access; + + // return axiosInstance(originalRequest); + // }) + // } + + return Promise.reject(err); + }, +); + +// export const obtainTokenApi = async (email: string, password: string): Promise => +// axiosInstance +// .post(OBTAIN_TOKEN, { email, password }) +// .then((res) => res.data); diff --git a/src/api/chains.ts b/src/api/chains.ts new file mode 100644 index 0000000..fa4730e --- /dev/null +++ b/src/api/chains.ts @@ -0,0 +1,14 @@ +import { Chain } from '../interfaces'; +import { axiosInstance } from './api'; + +// Let it be implicit here to avoid importing AxiosResponse etc. + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +const chainsApi = '/chains'; + +const getAllChains = () => axiosInstance.get(`${chainsApi}`); +const getChain = (chain: string) => axiosInstance.get(`${chainsApi}/${chain}`); +const setChainStatus = (chain: string) => axiosInstance.put(`${chainsApi}/${chain}`); + +export default { getAllChains, getChain, setChainStatus }; diff --git a/src/api/contracts.ts b/src/api/contracts.ts new file mode 100644 index 0000000..10372a5 --- /dev/null +++ b/src/api/contracts.ts @@ -0,0 +1,17 @@ +import { Contract } from '../interfaces'; +import { axiosInstance } from './api'; + +const contractsApi = '/contracts'; + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +// Implementation : https://github.com/paralink-network/paralink-node/blob/master/src/api/contracts.py +const getAllContracts = () => axiosInstance.get(`${contractsApi}`); +const getChainContracts = (chain: string) => axiosInstance.get(`${contractsApi}/${chain}`); +const createContract = (contract: Omit) => axiosInstance.post(`${contractsApi}`, contract); +const setContractStatus = (id: number, contract: Pick) => + axiosInstance.put(`${contractsApi}/${id}`, contract); +const deleteContract = (id: number) => axiosInstance.delete(`${contractsApi}/${id}`); + +export default { getAllContracts, getChainContracts, createContract, setContractStatus, deleteContract }; diff --git a/src/components/common/Buttons/Buttons.tsx b/src/components/common/Buttons/Buttons.tsx index c5963dc..5a931f4 100644 --- a/src/components/common/Buttons/Buttons.tsx +++ b/src/components/common/Buttons/Buttons.tsx @@ -18,7 +18,7 @@ const Button: React.FC - ); const removeButton = ( - ); @@ -95,9 +118,9 @@ const TrackedContractRow: React.FC = ({ trackedContract, r
    {inputFocused || edited ?
    {saveCancelButtons}
    : ''} { - handleContractChange({ enabled: !contract.enabled }); + handleContractChange({ active: !contract.active }); }} /> {removeButton} diff --git a/src/pages/tracked-contracts/TrackedContractsList.tsx b/src/pages/tracked-contracts/TrackedContractsList.tsx index de60de5..aa8e401 100644 --- a/src/pages/tracked-contracts/TrackedContractsList.tsx +++ b/src/pages/tracked-contracts/TrackedContractsList.tsx @@ -1,27 +1,12 @@ import React, { useEffect, useState } from 'react'; import { FaPlus } from 'react-icons/fa'; +import ChainsApi from '../../api/chains'; import { Card, CardBody, CardHeader } from '../../components/common'; import Button from '../../components/common/Buttons'; import SearchInput from '../../components/common/Input'; -import mockDataChain from './mock-data'; +import { Chain, Contract } from '../../interfaces'; import TrackedChain from './TrackedChain'; -import TrackedContractRow, { TrackedContract } from './TrackedContract'; - -type ChainType = 'evm' | 'substrate'; - -interface Chain { - name: string; - type: ChainType; - project: string; - url: string; - credentials: { - // TODO: Ask potentially BE to have structure camelCase in case or we can just disable the warning in eslint - /* eslint-disable camelcase */ - private_key?: string; - public_key?: string; - }; - tracked_contracts: TrackedContract[]; -} +import TrackedContractRow from './TrackedContract'; const TrackedContractsList: React.FC<{}> = () => { const [chains, setChains] = useState([]); @@ -33,46 +18,48 @@ const TrackedContractsList: React.FC<{}> = () => { // have a call from the tracked contract when something changed to let the parent know // We should have a modal displaying that there is some current changes to update // this is just a safe precaution - useEffect(() => { - setChains(mockDataChain as Chain[]); + ChainsApi.getAllChains().then((res: any) => { + setChains(res.data.chains); + // Select the first chain as active + setActiveChain(res.data.chains[0]); + }); }, []); - // const selectChain = (chain: Chain): void => { - // setActiveChain(chain); - // setActiveContracts(chain.tracked_contracts.length ? chain.tracked_contracts : ['No Tracked Contracts']); - // }; - // Filter out by the search but also keep the current active in the search const allChains = chains .filter((chain) => chain.name.includes(searchChain.trim().toLocaleLowerCase()) || activeChain?.name === chain.name) .map((chain) => ( - setActiveChain(chain)} /> + setActiveChain(chain)} + /> )); - // const activeContracts = - // activeChain && activeChain.tracked_contracts.length ? activeChain.tracked_contracts : ['No Tracked Contracts']; - - // TODO: Optmistic update , need to refresh the state - const addContract = (entry: TrackedContract): void => { + const addContract = (): void => { + // Check if there is already an empty entry in the array to fill let emptyContractExisting = false; - if (activeChain?.tracked_contracts.length) { - const lastEntry = activeChain?.tracked_contracts[activeChain?.tracked_contracts.length - 1]; + if (activeChain?.contracts.length) { + const lastEntry = activeChain?.contracts[activeChain?.contracts.length - 1]; emptyContractExisting = lastEntry.address === ''; } + const entry = { address: '', active: false, chain: activeChain?.name || '' }; if (activeChain && !emptyContractExisting) { setActiveChain({ ...activeChain, - tracked_contracts: [...activeChain.tracked_contracts, entry], + contracts: [...activeChain.contracts, entry], }); setFocused(true); } }; + // Remove a contract from the chain visually const removeContract = (index: number): void => { - if (activeChain?.tracked_contracts.length) { - activeChain.tracked_contracts.splice(index, 1); + if (activeChain?.contracts.length) { + activeChain.contracts.splice(index, 1); setActiveChain({ ...activeChain, }); @@ -104,22 +91,18 @@ const TrackedContractsList: React.FC<{}> = () => {
    Tracked Contracts
    -
    - {activeChain?.tracked_contracts.map((contract: TrackedContract, index: number) => { + {activeChain?.contracts.map((contract: Contract, index: number) => { return ( removeContract(index)} /> diff --git a/src/pages/tracked-contracts/mock-data.ts b/src/pages/tracked-contracts/mock-data.ts index 5a82c15..5dd0fa9 100644 --- a/src/pages/tracked-contracts/mock-data.ts +++ b/src/pages/tracked-contracts/mock-data.ts @@ -1,4 +1,5 @@ // We can remove this when we can hook to the BE and get that data it's just for getting the UX ready for it +// TODO: Some of those aren't set up in the BE const mockDataChain = [ { name: 'eth.mainnet', diff --git a/tailwind.config.js b/tailwind.config.js index e3c4511..340cc67 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -25,7 +25,9 @@ module.exports = { purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], darkMode: false, // or 'media' or 'class' variants: { - extend: {}, + extend: { + opacity: ['disabled'], + }, }, plugins: [], }; From ba28515988678cca550b21aa72d54a8cb0edec38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Thu, 1 Apr 2021 17:05:59 +0100 Subject: [PATCH 09/11] setup correclty the ref --- src/pages/tracked-contracts/TrackedContract.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/pages/tracked-contracts/TrackedContract.tsx b/src/pages/tracked-contracts/TrackedContract.tsx index c26c1ae..a90ecb3 100644 --- a/src/pages/tracked-contracts/TrackedContract.tsx +++ b/src/pages/tracked-contracts/TrackedContract.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { FaCheck, FaTimes, FaTrash } from 'react-icons/fa'; import ContractsApi from '../../api/contracts'; import Button from '../../components/common/Buttons'; @@ -20,12 +20,12 @@ const TrackedContractRow: React.FC = ({ trackedContract, r const initialContract = trackedContract; // Reference to the input - let trackInputRef: any = null; + const trackInputRef = useRef(null); useEffect(() => { // When adding a new line we want straight awayt to focus the new line - if (focused && trackInputRef) { - trackInputRef.focus(); + if (focused && trackInputRef.current) { + trackInputRef.current.focus(); } }, [focused, trackInputRef]); @@ -40,8 +40,8 @@ const TrackedContractRow: React.FC = ({ trackedContract, r setEdited(false); setInputFocused(false); console.log('trackinputRef', trackInputRef); - if (trackInputRef) { - trackInputRef.blur(); + if (trackInputRef.current) { + trackInputRef.current.blur(); } }; @@ -106,9 +106,7 @@ const TrackedContractRow: React.FC = ({ trackedContract, r { - trackInputRef = e; - }} + ref={trackInputRef} value={contract.address} onFocus={() => setInputFocused(true)} onBlur={() => setInputFocused(false)} From 492fb9e1d113220d8b6d7fe802da3722f0ec7556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CZiyedB=E2=80=9D?= Date: Sun, 9 May 2021 12:01:37 +0100 Subject: [PATCH 10/11] update with latest model --- package-lock.json | 8 ++ package.json | 1 + src/App.tsx | 23 ++-- src/api/contracts.ts | 7 +- src/interfaces/Contract.ts | 2 +- .../tracked-contracts/TrackedContract.tsx | 117 ++++++++++++++---- .../TrackedContractsList.tsx | 20 ++- src/utils/ContractUtil.ts | 14 +++ src/utils/index.ts | 1 + 9 files changed, 153 insertions(+), 40 deletions(-) create mode 100644 src/utils/ContractUtil.ts create mode 100644 src/utils/index.ts diff --git a/package-lock.json b/package-lock.json index 8810576..2f2356a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13274,6 +13274,14 @@ "workbox-webpack-plugin": "5.1.4" } }, + "react-toastify": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.4.tgz", + "integrity": "sha512-Rol7+Cn39hZp5hQ/k6CbMNE2CKYV9E5OQdC/hBLtIQU2xz7DdAm7xil4NITQTHR6zEbE5RVFbpgSwTD7xRGLeQ==", + "requires": { + "clsx": "^1.1.1" + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index e6d60f0..0fb9855 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "react-icons": "^4.2.0", "react-router-dom": "^5.2.0", "react-scripts": "4.0.1", + "react-toastify": "^7.0.4", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.0.3", "typescript": "^4.1.3", diff --git a/src/App.tsx b/src/App.tsx index e2a976a..47da363 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,7 @@ import React from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; +import { ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; import './App.scss'; import BaseLayout from './components/layouts/BaseLayout'; import Ipfs from './pages/ipfs/Ipfs'; @@ -8,15 +10,18 @@ import TrackedContractsList from './pages/tracked-contracts/TrackedContractsList const App = (): JSX.Element => { return ( - - - - - - - - - + <> + + + + + + + + + + + ); }; diff --git a/src/api/contracts.ts b/src/api/contracts.ts index 10372a5..8aecfc7 100644 --- a/src/api/contracts.ts +++ b/src/api/contracts.ts @@ -10,8 +10,9 @@ const contractsApi = '/contracts'; const getAllContracts = () => axiosInstance.get(`${contractsApi}`); const getChainContracts = (chain: string) => axiosInstance.get(`${contractsApi}/${chain}`); const createContract = (contract: Omit) => axiosInstance.post(`${contractsApi}`, contract); -const setContractStatus = (id: number, contract: Pick) => - axiosInstance.put(`${contractsApi}/${id}`, contract); -const deleteContract = (id: number) => axiosInstance.delete(`${contractsApi}/${id}`); +const setContractStatus = (chain: string, address: string, contract: Pick) => + axiosInstance.put(`${contractsApi}/${chain}/${address}`, contract); +const deleteContract = (chain: string, address: string) => + axiosInstance.delete(`${contractsApi}/${chain}/${address}`); export default { getAllContracts, getChainContracts, createContract, setContractStatus, deleteContract }; diff --git a/src/interfaces/Contract.ts b/src/interfaces/Contract.ts index 122333a..3ed929c 100644 --- a/src/interfaces/Contract.ts +++ b/src/interfaces/Contract.ts @@ -1,6 +1,6 @@ export interface Contract { - id?: number; active: boolean; address: string; chain: string; + newContract?: boolean; } diff --git a/src/pages/tracked-contracts/TrackedContract.tsx b/src/pages/tracked-contracts/TrackedContract.tsx index a90ecb3..3e59312 100644 --- a/src/pages/tracked-contracts/TrackedContract.tsx +++ b/src/pages/tracked-contracts/TrackedContract.tsx @@ -1,21 +1,25 @@ import React, { useEffect, useRef, useState } from 'react'; -import { FaCheck, FaTimes, FaTrash } from 'react-icons/fa'; +import { FaCheck, FaSpinner, FaTimes, FaTrash } from 'react-icons/fa'; +import { toast } from 'react-toastify'; import ContractsApi from '../../api/contracts'; import Button from '../../components/common/Buttons'; import Toggle from '../../components/common/Toggle'; import { Contract } from '../../interfaces'; +import ContractUtil from '../../utils/ContractUtil'; interface TrackedContractProps { trackedContract: Contract; // saved: (contract: TrackedContract) => void; // TODO: Once we have the BE working use this function to do a callback to the parent to update with the id or the new status remove: () => void; // This helps remove the line from the array and refresh the parent view + update: (contract: Contract) => void; focused?: boolean; } -const TrackedContractRow: React.FC = ({ trackedContract, remove, focused }) => { - const [inputFocused, setInputFocused] = useState(focused || false); +const TrackedContractRow: React.FC = ({ trackedContract, remove, update, focused }) => { + const [inputFocused, setInputFocused] = useState(false); const [edited, setEdited] = useState(false); const [contract, setContract] = useState(trackedContract); + const [loadingUpdate, setLoadingUpdate] = useState(false); // Keep the original contract. const initialContract = trackedContract; @@ -29,6 +33,18 @@ const TrackedContractRow: React.FC = ({ trackedContract, r } }, [focused, trackInputRef]); + const showNotification = (title: string, type: 'success' | 'error'): void => { + let displayNotification: any = toast; + if (type === 'success') { + displayNotification = toast.success; + } else if (type === 'error') { + displayNotification = toast.error; + } + displayNotification(title, { + autoClose: 2000, + }); + }; + // Handle the UI state for the contrat changing const handleContractChange = (value: Partial): void => { setContract({ ...contract, ...value }); @@ -39,7 +55,7 @@ const TrackedContractRow: React.FC = ({ trackedContract, r const loseFocus = (): void => { setEdited(false); setInputFocused(false); - console.log('trackinputRef', trackInputRef); + if (trackInputRef.current) { trackInputRef.current.blur(); } @@ -47,30 +63,66 @@ const TrackedContractRow: React.FC = ({ trackedContract, r // Save the contract const saveClick = (): void => { + setLoadingUpdate(true); // save contract as it is // then set it as not edited anymore - if (!contract.id) { + if (contract.newContract) { // TODO: Should have the BE send us the response of the new object so we can update the view ( example with the id ) - ContractsApi.createContract(contract).then(() => { - loseFocus(); - }); + const payload = { + address: contract.address, + chain: contract.chain, + active: contract.active, + }; + + ContractsApi.createContract(payload) + .then(() => { + contract.newContract = false; + loseFocus(); + update(contract); + showNotification(`Contract ${ContractUtil.displayContract(contract.address)} has been added`, 'success'); + }) + .catch((e) => { + console.error('error', e); + showNotification(`Error when adding ${ContractUtil.displayContract(contract.address)}`, 'error'); + }) + .finally(() => setLoadingUpdate(false)); } else { // TODO: same as above - ContractsApi.setContractStatus(contract.id, { active: contract.active }).then(() => { - loseFocus(); - }); + ContractsApi.setContractStatus(contract.chain, contract.address, { active: contract.active }) + .then(() => { + loseFocus(); + showNotification(`Contract ${ContractUtil.displayContract(contract.address)} updated`, 'success'); + }) + .catch((e) => { + console.error('error', e); + showNotification(`Error when trying to update ${ContractUtil.displayContract(contract.address)}`, 'error'); + }) + .finally(() => setLoadingUpdate(false)); } }; const deleteClick = (): void => { - if (contract.id) { + if (!contract.newContract) { // TODO: Handle the errors and exception // This should be done when we have our notification component // basically we'd make the notification component appear with the error // Potentially add an outline to the line as red until it's touched ( Optional ) - ContractsApi.deleteContract(contract.id).then(() => { - remove(); - }); + setLoadingUpdate(true); + ContractsApi.deleteContract(contract.chain, contract.address) + .then(() => { + remove(); + showNotification(`Contract ${ContractUtil.displayContract(contract.address)} deleted`, 'success'); + }) + .catch((e) => { + console.error('delete error', e); + showNotification( + `Error when trying to delete Contract ${ContractUtil.displayContract(contract.address)}`, + 'error', + ); + }) + .finally(() => { + setLoadingUpdate(false); + }); } else { remove(); } @@ -94,6 +146,14 @@ const TrackedContractRow: React.FC = ({ trackedContract, r ); + const loading = ( + <> + + + + + ); + const removeButton = (