diff --git a/.gitignore b/.gitignore index 7c913880..c2727221 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ yarn-error.log* .idea .env.example -.cosine \ No newline at end of file +.cosine +bun.lockb \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index ce255610..25e417a4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,8 @@ "svg.preview.background": "black", "cSpell.words": [ "offchain" - ] -} + ], + "editor.autoClosingBrackets": "always", + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b790fe..0108d1c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Sending XTZ directly to DAO address - Delegating DAO's XTZ to baker - Changing DAO configuration parameters +- Added Searchbar on Treasury Page ## [1.0.6] - 2022-01-05 ### Added @@ -72,4 +73,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Treasury page style ### Breaking changes -- Upgraded BaseDAO contracts to [v0.4](https://github.com/tezos-commons/baseDAO/tree/b3aa7886950d4f1eb65816ed726ce69e77e14472) - DAOs created prior to this upgrade are deprecated \ No newline at end of file +- Upgraded BaseDAO contracts to [v0.4](https://github.com/tezos-commons/baseDAO/tree/b3aa7886950d4f1eb65816ed726ce69e77e14472) - DAOs created prior to this upgrade are deprecated diff --git a/src/modules/explorer/components/FiltersDialog.tsx b/src/modules/explorer/components/FiltersDialog.tsx index 270f5042..f626cb5d 100644 --- a/src/modules/explorer/components/FiltersDialog.tsx +++ b/src/modules/explorer/components/FiltersDialog.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react" import { ResponsiveDialog } from "./ResponsiveDialog" import { Grid, styled } from "@material-ui/core" import { Typography } from "@mui/material" -import { Dropdown } from "./Dropdown" import { ProposalStatus } from "services/services/dao/mappers/proposal/types" import { SmallButton } from "modules/common/SmallButton" import { Order, ProposalType } from "./FiltersUserDialog" diff --git a/src/modules/explorer/components/FiltersTokensDialog.tsx b/src/modules/explorer/components/FiltersTokensDialog.tsx new file mode 100644 index 00000000..10d15a7e --- /dev/null +++ b/src/modules/explorer/components/FiltersTokensDialog.tsx @@ -0,0 +1,135 @@ +import React, { useEffect, useState } from "react" +import { Grid, TextField, Typography, styled, withStyles } from "@material-ui/core" +import { ResponsiveDialog } from "./ResponsiveDialog" +import { SmallButton } from "modules/common/SmallButton" +import { TokensFilters } from "../pages/Treasury" + +interface Props { + currentFilters: TokensFilters | undefined + open: boolean + handleClose: () => void + saveFilters: (options: TokensFilters) => void +} + +const SectionTitle = styled(Typography)({ + fontSize: "18px !important", + fontWeight: 600 +}) + +const Container = styled(Grid)({ + marginTop: 6, + gap: 24 +}) + +const CustomTextField = withStyles({ + root: { + "& .MuiInput-root": { + fontWeight: 300, + textAlign: "initial" + }, + "& .MuiInputBase-input": { + textAlign: "initial", + background: "#2F3438", + borderRadius: 8, + padding: 16 + }, + "& .MuiInputBase-root": { + textWeight: 300 + }, + "& .MuiInput-underline": { + borderBottom: "none !important" + }, + "& .MuiInput-underline:before": { + borderBottom: "none !important" + }, + "& .MuiInput-underline:hover:before": { + borderBottom: "none !important" + }, + "& .MuiInput-underline:after": { + borderBottom: "none !important" + }, + "& .MuiInput-underline:hover:not(.Mui-disabled):before": { + borderBottom: "none !important" + } + } +})(TextField) + +export const FilterTokenDialog: React.FC = ({ open, handleClose, saveFilters, currentFilters }) => { + const [token, setToken] = useState("") + const [balanceMin, setBalanceMin] = useState() + const [balanceMax, setBalanceMax] = useState() + + const ariaLabel = { "aria-label": "description" } + + useEffect(() => { + if (currentFilters) { + setToken(currentFilters?.token) + setBalanceMin(currentFilters.balanceMin) + setBalanceMax(currentFilters.balanceMax) + } + }, [currentFilters]) + + const showFilters = () => { + const filterObject: TokensFilters = { + token: token, + balanceMin: balanceMin, + balanceMax: balanceMax + } + saveFilters(filterObject) + handleClose() + } + + return ( + <> + + + + Token + + + setToken(event.target.value)} + style={{ width: "40%" }} + name="test" + value={token} + placeholder="Token" + inputProps={ariaLabel} + /> + + + + Balance + + + + setBalanceMin(event.target.value)} + name="test" + value={balanceMin} + placeholder="Min" + inputProps={ariaLabel} + type="number" + /> + + + setBalanceMax(event.target.value)} + name="test" + value={balanceMax} + placeholder="Min" + type="number" + inputProps={ariaLabel} + /> + + + + + + + Apply + + + + + ) +} diff --git a/src/modules/explorer/components/FiltersTransactionsDialog.tsx b/src/modules/explorer/components/FiltersTransactionsDialog.tsx new file mode 100644 index 00000000..ae0a9bb8 --- /dev/null +++ b/src/modules/explorer/components/FiltersTransactionsDialog.tsx @@ -0,0 +1,205 @@ +import React, { useEffect, useState } from "react" +import { Grid, TextField, Typography, styled, withStyles } from "@material-ui/core" +import { ResponsiveDialog } from "./ResponsiveDialog" +import { SmallButton } from "modules/common/SmallButton" +import { TransactionsFilters } from "../pages/Treasury" + +export enum TransactionStatus { + COMPLETED = "applied", + PENDING = "pending", + FAILED = "failed" +} + +interface Props { + currentFilters: TransactionsFilters | undefined + open: boolean + handleClose: () => void + saveFilters: (options: TransactionsFilters) => void +} + +const SectionTitle = styled(Typography)({ + fontSize: "18px !important", + fontWeight: 600 +}) + +const Container = styled(Grid)({ + marginTop: 6, + gap: 24 +}) + +const CustomTextField = withStyles({ + root: { + "& .MuiInput-root": { + fontWeight: 300, + textAlign: "initial" + }, + "& .MuiInputBase-input": { + textAlign: "initial", + background: "#2F3438", + borderRadius: 8, + padding: 16 + }, + "& .MuiInputBase-root": { + textWeight: 300 + }, + "& .MuiInput-underline": { + borderBottom: "none !important" + }, + "& .MuiInput-underline:before": { + borderBottom: "none !important" + }, + "& .MuiInput-underline:hover:before": { + borderBottom: "none !important" + }, + "& .MuiInput-underline:after": { + borderBottom: "none !important" + }, + "& .MuiInput-underline:hover:not(.Mui-disabled):before": { + borderBottom: "none !important" + } + } +})(TextField) + +const StatusButton = styled(Grid)(({ theme }) => ({ + "background": theme.palette.primary.main, + "padding": "8px 16px", + "borderRadius": 50, + "marginRight": 16, + "marginBottom": 16, + "cursor": "pointer", + "textTransform": "capitalize", + "&:hover": { + background: "rgba(129, 254, 183, .4)" + } +})) + +interface StatusOption { + label: string +} + +export const FilterTransactionsDialog: React.FC = ({ open, handleClose, saveFilters, currentFilters }) => { + const [status, setStatus] = useState([]) + const [token, setToken] = useState("") + const [sender, setSender] = useState("") + const [receiver, setReceiver] = useState("") + + const [filters, setFilters] = useState() + const ariaLabel = { "aria-label": "description" } + + useEffect(() => { + setStatus([]) + setStatusOptions() + if (currentFilters) { + setToken(currentFilters?.token) + setSender(currentFilters.sender) + setReceiver(currentFilters.receiver) + setFilters(currentFilters.status) + } + }, [currentFilters]) + + const setStatusOptions = () => { + const values = Object.values(TransactionStatus) + for (const item in values) { + const obj = { + label: values[item] + } + setStatus(oldArray => [...oldArray, obj]) + } + } + + const isSelected = (item: StatusOption) => { + return filters && filters.label === item.label ? true : false + } + + const saveStatus = (status: StatusOption) => { + if (status.label === filters?.label) { + setFilters(undefined) + } else { + setFilters(status) + } + } + + const showFilters = () => { + const filterObject: TransactionsFilters = { + token: token, + receiver: receiver, + sender: sender, + status: filters + } + saveFilters(filterObject) + handleClose() + } + + return ( + <> + + + + Sort by + + + + {status.length > 0 && + status.map((item, index) => { + return ( + + saveStatus(item)}>{item.label} + + ) + })} + + + + Token + + + setToken(event.target.value)} + style={{ width: "40%" }} + name="test" + value={token} + placeholder="Token" + inputProps={ariaLabel} + /> + + + + Receiving Address + + + setReceiver(event.target.value)} + name="test" + value={receiver} + placeholder="Address" + inputProps={ariaLabel} + /> + + + + Sending Address + + + setSender(event.target.value)} + name="test" + value={sender} + placeholder="Address" + inputProps={ariaLabel} + /> + + + + + + Apply + + + + + ) +} diff --git a/src/modules/explorer/components/ProposalsList.tsx b/src/modules/explorer/components/ProposalsList.tsx index 9fed9044..4b8fb093 100644 --- a/src/modules/explorer/components/ProposalsList.tsx +++ b/src/modules/explorer/components/ProposalsList.tsx @@ -88,6 +88,10 @@ export const ProposalsList: React.FC = ({ setFilteredProposals(listOfProposals) }, []) + useEffect(() => { + setFilteredProposals(listOfProposals) + }, [liteProposals, proposals]) + useEffect(() => { setFilteredProposals(listOfProposals) }, [showFullList]) diff --git a/src/modules/explorer/components/VotersProgress.tsx b/src/modules/explorer/components/VotersProgress.tsx index 6dc83233..427380cf 100644 --- a/src/modules/explorer/components/VotersProgress.tsx +++ b/src/modules/explorer/components/VotersProgress.tsx @@ -64,7 +64,11 @@ export const VotersProgress: React.FC = ({ showButton, daoId, propos title: "Voting Details", showTitle: false }) - const votesData = proposal ? proposal?.voters : [] + const votesData = + proposal?.voters.map(voter => ({ + ...voter, + value: voter.value.toString() + })) || [] try { const csv = generateCsv(csvConfig)(votesData) download(csvConfig)(csv) diff --git a/src/modules/explorer/pages/NFTs/index.tsx b/src/modules/explorer/pages/NFTs/index.tsx index b27385f8..a81369ac 100644 --- a/src/modules/explorer/pages/NFTs/index.tsx +++ b/src/modules/explorer/pages/NFTs/index.tsx @@ -94,7 +94,7 @@ export const NFTs: React.FC = () => { const onCloseTransfer = () => { setOpenTransfer(false) } - const value = isMobileSmall ? 6 : 3 + const value = isMobileSmall ? 6 : 4 const shouldDisable = useIsProposalButtonDisabled(daoId) const [currentPage, setCurrentPage] = useState(0) @@ -105,6 +105,7 @@ export const NFTs: React.FC = () => { const newOffset = (event.selected * value) % nftHoldings.length setOffset(newOffset) setCurrentPage(event.selected) + window.scrollTo({ top: 0, behavior: "smooth" }) } } diff --git a/src/modules/explorer/pages/Proposals/index.tsx b/src/modules/explorer/pages/Proposals/index.tsx index c6a9045e..e88818da 100644 --- a/src/modules/explorer/pages/Proposals/index.tsx +++ b/src/modules/explorer/pages/Proposals/index.tsx @@ -261,7 +261,7 @@ export const Proposals: React.FC = () => { direction="row" alignItems="center" > - + Filter & Sort diff --git a/src/modules/explorer/pages/Treasury/components/BalancesTable.tsx b/src/modules/explorer/pages/Treasury/components/BalancesTable.tsx index d81dd3f1..448e7004 100644 --- a/src/modules/explorer/pages/Treasury/components/BalancesTable.tsx +++ b/src/modules/explorer/pages/Treasury/components/BalancesTable.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from "react" +import React, { useEffect, useMemo, useState } from "react" import { Card, CardContent, Grid, styled, Typography, useMediaQuery, useTheme } from "@material-ui/core" import { ProposalFormContainer, ProposalFormDefaultValues } from "modules/explorer/components/ProposalForm" import { DAOHolding } from "services/bakingBad/tokenBalances" @@ -12,6 +12,18 @@ import { toShortAddress } from "services/contracts/utils" import { CopyButton } from "modules/common/CopyButton" import ReactPaginate from "react-paginate" import "../../DAOList/styles.css" +import FilterAltIcon from "@mui/icons-material/FilterAlt" +import CloseIcon from "@mui/icons-material/Close" +import NavigateNextIcon from "@mui/icons-material/NavigateNext" +import NavigateBeforeIcon from "@mui/icons-material/NavigateBefore" + +import { SearchInput } from "../../DAOList/components/Searchbar" +import { TokensFilters } from ".." +import { FilterTokenDialog } from "modules/explorer/components/FiltersTokensDialog" + +const FiltersContainer = styled(Grid)({ + cursor: "pointer" +}) const TokenSymbol = styled(Typography)(({ theme }) => ({ color: theme.palette.secondary.main, @@ -91,6 +103,7 @@ const titleDataMatcher = (title: (typeof titles)[number], rowData: RowData) => { interface TableProps { rows: RowData[] + searchText?: string tezosBalance: BigNumber openXTZTransferModal: () => void openTokenTransferModal: (tokenAddress: string) => void @@ -100,6 +113,7 @@ interface TableProps { const BalancesList: React.FC = ({ rows, + searchText, tezosBalance, openTokenTransferModal, openXTZTransferModal, @@ -108,20 +122,49 @@ const BalancesList: React.FC = ({ }) => { const [currentPage, setCurrentPage] = useState(0) const [offset, setOffset] = useState(0) - const value = isMobileSmall ? 6 : 2 + const value = 6 + const [list, setList] = useState(rows) + + useEffect(() => { + setList(rows) + }, [rows]) + + useEffect(() => { + if (searchText && searchText.length > 0) { + setOffset(0) + setCurrentPage(0) + } + }, [searchText]) + + // useEffect(() => { + // if (list) { + // const val = list.findIndex(row => row.symbol === "XTZ") + // if (val === -1) { + // const xtz: RowData = { + // symbol: "XTZ", + // address: "", + // amount: tezosBalance.toString() + // } + // const updatedList = rows.slice() + // updatedList.unshift(xtz) + // setList(updatedList) + // } + // } + // }, [tezosBalance, list, rows]) // Invoke when user click to request another page. const handlePageClick = (event: { selected: number }) => { if (rows) { const newOffset = (event.selected * value) % rows.length setOffset(newOffset) setCurrentPage(event.selected) + window.scrollTo({ top: 0, behavior: "smooth" }) } } const pageCount = Math.ceil(rows ? rows.length / value : 0) return ( - + {/* XTZ @@ -142,14 +185,20 @@ const BalancesList: React.FC = ({ - + */} - {rows.slice(offset, offset + value).map((row, i) => ( + {list.slice(offset, offset + value).map((row, i) => ( {row.symbol} - + {toShortAddress(row.address)} @@ -173,9 +222,9 @@ const BalancesList: React.FC = ({ ))} } breakLabel="..." - nextLabel=">" + nextLabel={} onPageChange={handlePageClick} pageRangeDisplayed={2} pageCount={pageCount} @@ -200,6 +249,10 @@ export const BalancesTable: React.FC = () => { const { data: tezosBalance } = useTezosBalance(daoId) const [openTransfer, setOpenTransfer] = useState(false) const [defaultValues, setDefaultValues] = useState() + const [openFiltersDialog, setOpenFiltersDialog] = useState(false) + const [searchText, setSearchText] = useState("") + const [filters, setFilters] = useState() + const filtersEnabled = Object.values(filters || {}).some(value => !!value) const onCloseTransfer = () => { setOpenTransfer(false) @@ -245,19 +298,106 @@ export const BalancesTable: React.FC = () => { setOpenTransfer(true) } + const handleFilters = (filters: TokensFilters) => { + setFilters(filters) + } + + const handleCloseFiltersModal = () => { + setOpenFiltersDialog(false) + } + const rows = useMemo(() => { + const handleFilterData = (holdings: DAOHolding[]) => { + let data = holdings.slice() + if (filters?.token && filters.token !== "") { + data = holdings.filter(trx => trx.token?.symbol.toLowerCase() === filters.token?.toLocaleLowerCase()) + } + if (filters?.balanceMin && filters.balanceMin !== "") { + data = holdings.filter(trx => trx.balance.isGreaterThanOrEqualTo(filters.balanceMin!)) + } + if (filters?.balanceMax && filters.balanceMax !== "") { + data = holdings.filter(trx => trx.balance.isLessThanOrEqualTo(filters.balanceMax!)) + } + return data + } + if (!tokenHoldings) { return [] } + let holdings = tokenHoldings.slice() + const xtz: DAOHolding = { + token: { + symbol: "XTZ", + id: "XTZ", + contract: "", + token_id: 0, + name: "", + decimals: 6, + network: "mainnet", + supply: tezosBalance || new BigNumber(0), + standard: "" + }, + balance: tezosBalance || new BigNumber(0) + } + holdings.unshift(xtz) - return tokenHoldings.map(createData) - }, [tokenHoldings]) + if (filters) { + holdings = handleFilterData(holdings) + } + + if (searchText) { + console.log({ holdings }) + holdings = holdings.filter(holding => { + const query = searchText.toLowerCase() + if (holding.token?.name.toLowerCase().includes(query)) return true + if (holding.token?.id.toLowerCase().includes(query)) return true + if (holding.token?.symbol.toLowerCase().includes(query)) return true + return false + }) + } + + return holdings.map(createData) + }, [tokenHoldings, searchText, filters, tezosBalance]) return ( <> + + setOpenFiltersDialog(true)} + xs={isSmall ? 12 : 2} + item + container + direction="row" + alignItems="center" + > + + Filter & Sort + + {filtersEnabled && ( + setFilters({ token: "", balanceMax: "", balanceMin: "" })} + xs={isSmall ? 12 : 2} + item + container + direction="row" + alignItems="center" + > + + Clear Filters + + )} + + { + setSearchText(text.trim()) + }} + /> + + { defaultValues={defaultValues} defaultTab={0} /> + ) } diff --git a/src/modules/explorer/pages/Treasury/components/TransfersTable.tsx b/src/modules/explorer/pages/Treasury/components/TransfersTable.tsx index 6b598071..61b81cef 100644 --- a/src/modules/explorer/pages/Treasury/components/TransfersTable.tsx +++ b/src/modules/explorer/pages/Treasury/components/TransfersTable.tsx @@ -1,5 +1,5 @@ +import React, { useEffect, useMemo, useState } from "react" import dayjs from "dayjs" -import React, { useMemo, useState } from "react" import { Grid, Link, @@ -20,9 +20,10 @@ import { ContentContainer } from "modules/explorer/components/ContentContainer" import { networkNameMap } from "services/bakingBad" import { toShortAddress } from "services/contracts/utils" import { ReactComponent as BulletIcon } from "assets/img/bullet.svg" -import { CopyButton } from "modules/common/CopyButton" import ReactPaginate from "react-paginate" import "../../DAOList/styles.css" +import OpenInNewIcon from "@mui/icons-material/OpenInNew" +import numbro from "numbro" const localizedFormat = require("dayjs/plugin/localizedFormat") dayjs.extend(localizedFormat) @@ -33,7 +34,8 @@ const createData = (transfer: TransferWithBN) => { date: dayjs(transfer.date).format("L"), address: transfer.recipient, amount: transfer.amount.dp(10, 1).toString(), - hash: transfer.hash + hash: transfer.hash, + type: transfer.type } } @@ -65,16 +67,70 @@ const ProposalsFooterMobile = styled(Grid)({ borderBottomRightRadius: 8 }) +const ItemContainer = styled(Grid)(({ theme }) => ({ + padding: "40px 48px", + gap: 8, + borderRadius: 8, + background: theme.palette.primary.main, + [theme.breakpoints.down("sm")]: { + padding: "30px 38px", + gap: 20 + } +})) + +const Container = styled(Grid)({ + gap: 24, + display: "grid" +}) + +const Title = styled(Typography)({ + color: "#fff", + fontSize: 24 +}) + +const Subtitle = styled(Typography)(({ theme }) => ({ + color: theme.palette.primary.light, + fontSize: 16, + fontWeight: 300 +})) + +const AmountText = styled(Typography)(({ theme }) => ({ + color: "#fff", + fontSize: 18, + fontWeight: 300, + lineHeight: "160%" +})) + +const BlockExplorer = styled(Typography)({ + "fontSize": 16, + "fontWeight": 400, + "cursor": "pointer", + "display": "flex", + "alignItems": "center", + "& svg": { + fontSize: 16, + marginRight: 6 + } +}) + interface RowData { token: string date: string amount: string address: string hash: string + type: string | undefined } const Titles = ["Token", "Date", "Recipient", "Amount"] +const formatConfig = { + average: true, + mantissa: 1, + thousandSeparated: true, + trimMantissa: true +} + const titleDataMatcher = (title: (typeof Titles)[number], rowData: RowData) => { switch (title) { case "Token": @@ -122,6 +178,7 @@ const MobileTransfersTable: React.FC<{ data: RowData[]; network: Network }> = ({ const newOffset = (event.selected * 5) % data.length setOffset(newOffset) setCurrentPage(event.selected) + window.scrollTo({ top: 0, behavior: "smooth" }) } } const pageCount = Math.ceil(data ? data.length / 5 : 0) @@ -183,84 +240,93 @@ const MobileTransfersTable: React.FC<{ data: RowData[]; network: Network }> = ({ ) } -const DesktopTransfersTable: React.FC<{ data: RowData[]; network: Network }> = ({ data: rows, network }) => { +const TransfersTableItems: React.FC<{ data: RowData[]; network: Network }> = ({ data: rows, network }) => { const [currentPage, setCurrentPage] = useState(0) const [offset, setOffset] = useState(0) + const theme = useTheme() + const isSmall = useMediaQuery(theme.breakpoints.down("xs")) + + useEffect(() => { + setOffset(0) + }, [rows]) + + const openBlockExplorer = (hash: string) => { + window.open(`https://${networkNameMap[network]}.tzkt.io/` + hash, "_blank") + } + // Invoke when user click to request another page. const handlePageClick = (event: { selected: number }) => { if (rows) { const newOffset = (event.selected * 5) % rows.length setOffset(newOffset) setCurrentPage(event.selected) + window.scrollTo({ top: 0, behavior: "smooth" }) } } const pageCount = Math.ceil(rows ? rows.length / 5 : 0) return ( <> - - - - {Titles.map((title, i) => ( - {title} - ))} - - - - {rows.slice(offset, offset + 5).map((row, i) => ( - - -
- {row.token} -
-
- {row.date} - - {toShortAddress(row.address)} - - - {row.amount} -
- ))} - {!(rows && rows.length > 0) ? ( - - - - No items - - - - ) : null} -
-
- - - + {rows && rows.length > 0 ? ( + <> + + {rows.slice(offset, offset + 5).map((row, i) => { + return ( + + + + + {row.token} + + + To {toShortAddress(row.address)} + {isSmall ? null : } + {dayjs(row.date).format("ll")} + + + + + + {row.type ? (row.type === "Deposit" ? "-" : "+") : null}{" "} + {isSmall ? numbro(row.amount).format(formatConfig) : row.amount} {row.token}{" "} + + + + openBlockExplorer(row.hash)}> + + View on Block Explorer + + + + + ) + })} + + + + + + + ) : ( + No items + )} ) } export const TransfersTable: React.FC<{ transfers: TransferWithBN[] }> = ({ transfers }) => { - const theme = useTheme() - const isSmall = useMediaQuery(theme.breakpoints.down("sm")) - const rows = useMemo(() => { if (!transfers) { return [] @@ -273,10 +339,10 @@ export const TransfersTable: React.FC<{ transfers: TransferWithBN[] }> = ({ tran return ( - {isSmall ? ( - + {rows && rows.length > 0 ? ( + ) : ( - + No items )} ) diff --git a/src/modules/explorer/pages/Treasury/index.tsx b/src/modules/explorer/pages/Treasury/index.tsx index b9a92a6c..7716984e 100644 --- a/src/modules/explorer/pages/Treasury/index.tsx +++ b/src/modules/explorer/pages/Treasury/index.tsx @@ -1,12 +1,11 @@ import { Button, Grid, Theme, Tooltip, Typography, useMediaQuery, useTheme } from "@material-ui/core" -import { ProposalFormContainer } from "modules/explorer/components/ProposalForm" import React, { useMemo, useState } from "react" import { useDAO } from "services/services/dao/hooks/useDAO" import { useDAOID } from "../DAO/router" import { BalancesTable } from "./components/BalancesTable" import { TransfersTable } from "./components/TransfersTable" -import { useTransfers } from "../../../../services/contracts/baseDAO/hooks/useTransfers" +import { TransferWithBN, useTransfers } from "../../../../services/contracts/baseDAO/hooks/useTransfers" import { InfoIcon } from "../../components/styled/InfoIcon" import { useIsProposalButtonDisabled } from "../../../../services/contracts/baseDAO/hooks/useCycleInfo" import { styled } from "@material-ui/core" @@ -19,6 +18,14 @@ import { SmallButton } from "modules/common/SmallButton" import { ContentContainer } from "modules/explorer/components/ContentContainer" import { CopyButton } from "modules/common/CopyButton" import { TreasuryDialog } from "./components/TreasuryDialog" +import { SearchInput } from "../DAOList/components/Searchbar" +import FilterAltIcon from "@mui/icons-material/FilterAlt" +import { FilterTransactionsDialog } from "modules/explorer/components/FiltersTransactionsDialog" +import { StatusOption } from "modules/explorer/components/FiltersUserDialog" + +const FiltersContainer = styled(Grid)({ + cursor: "pointer" +}) const ItemGrid = styled(Grid)({ width: "inherit" @@ -50,6 +57,19 @@ const TitleText = styled(Typography)({ } }) +export interface TransactionsFilters { + token: string | null + sender: string | null + receiver: string | null + status: StatusOption | undefined +} + +export interface TokensFilters { + token: string | null + balanceMin: string | undefined + balanceMax: string | undefined +} + const StyledTab = styled(Button)(({ theme, isSelected }: { theme: Theme; isSelected: boolean }) => ({ "fontSize": 18, "height": 40, @@ -89,6 +109,9 @@ export const Treasury: React.FC = () => { const { data: dao } = useDAO(daoId) const [openTransfer, setOpenTransfer] = useState(false) const [selectedTab, setSelectedTab] = React.useState(0) + const [searchText, setSearchText] = useState("") + const [filters, setFilters] = useState() + const [openFiltersDialog, setOpenFiltersDialog] = useState(false) const { data: transfers } = useTransfers(daoId) @@ -101,6 +124,54 @@ export const Treasury: React.FC = () => { setOpenTransfer(false) } + const currentTransfers = useMemo(() => { + const handleFilterData = (allTransfers: TransferWithBN[]) => { + let data = allTransfers.slice() + if (filters?.receiver && filters.receiver !== "") { + data = allTransfers.filter(trx => trx.recipient === filters.receiver) + } + if (filters?.sender && filters.sender !== "") { + data = allTransfers.filter(trx => trx.sender === filters.sender) + } + if (filters?.token && filters.token !== "") { + data = allTransfers.filter(trx => trx.token?.symbol.toLocaleLowerCase() === filters.token?.toLocaleLowerCase()) + } + if (filters?.status && filters.status.label !== "") { + data = allTransfers.filter(trx => trx.status === filters.status?.label) + } + return data + } + + if (transfers) { + let allTransfers = transfers.slice() + if (filters) { + allTransfers = handleFilterData(allTransfers) + } + + if (searchText) { + return allTransfers.filter( + formattedDao => formattedDao.name && formattedDao.name.toLowerCase().includes(searchText.toLowerCase()) + ) + } + + return allTransfers + } + + return [] + }, [searchText, transfers, filters]) + + const filterByName = (filter: string) => { + setSearchText(filter.trim()) + } + + const handleFilters = (filters: TransactionsFilters) => { + setFilters(filters) + } + + const handleCloseFiltersModal = () => { + setOpenFiltersDialog(false) + } + return ( <> @@ -131,7 +202,7 @@ export const Treasury: React.FC = () => { justifyContent="flex-end" alignItems="center" style={{ gap: 15 }} - direction={isMobileSmall ? "column" : "row"} + direction={isMobileSmall ? "row" : "row"} xs={isMobileSmall ? undefined : true} > {dao && ( @@ -139,7 +210,7 @@ export const Treasury: React.FC = () => { item xs container - justifyContent="flex-end" + justifyContent={"flex-end"} direction="row" style={isMobileSmall ? {} : { marginLeft: 30 }} > @@ -175,7 +246,9 @@ export const Treasury: React.FC = () => { } + startIcon={ + + } variant="contained" disableElevation={true} onClick={() => handleChangeTab(0)} @@ -186,7 +259,9 @@ export const Treasury: React.FC = () => { } + startIcon={ + + } disableElevation={true} variant="contained" onClick={() => handleChangeTab(1)} @@ -197,13 +272,15 @@ export const Treasury: React.FC = () => { } + startIcon={ + + } disableElevation={true} variant="contained" onClick={() => handleChangeTab(2)} isSelected={selectedTab === 2} > - History + Transactions @@ -219,7 +296,23 @@ export const Treasury: React.FC = () => { - + + setOpenFiltersDialog(true)} + xs={isMobileSmall ? 12 : 2} + item + container + direction="row" + alignItems="center" + > + + Filter & Sort + + + + + + @@ -230,6 +323,12 @@ export const Treasury: React.FC = () => { open={openTransfer} handleClose={onCloseTransfer} /> + ) } diff --git a/src/modules/explorer/pages/User/components/UserMovements.tsx b/src/modules/explorer/pages/User/components/UserMovements.tsx index a06179cc..30a9c817 100644 --- a/src/modules/explorer/pages/User/components/UserMovements.tsx +++ b/src/modules/explorer/pages/User/components/UserMovements.tsx @@ -30,20 +30,16 @@ import { useUserVotes } from "modules/lite/explorer/hooks/useUserVotes" import { usePolls } from "modules/lite/explorer/hooks/usePolls" import { useDAO } from "services/services/dao/hooks/useDAO" import { FilterUserTransactionsDialog, TrxStatus } from "modules/explorer/components/FiltersUserTransactionsDialog" +import { SearchInput } from "../../DAOList/components/Searchbar" const TabsContainer = styled(Grid)(({ theme }) => ({ borderRadius: 8, gap: 16 })) -const ProposalsFooter = styled(Grid)({ - padding: "16px 46px", - minHeight: 34 -}) - const TransactionsFooter = styled(Grid)({ - padding: "16px 46px", - minHeight: 34 + minHeight: 34, + paddingTop: 24 }) const TitleText = styled(Typography)({ @@ -89,8 +85,7 @@ const ActivityContainer = styled(Grid)(({ theme }) => ({ })) const ViewAll = styled(Grid)(({ theme }) => ({ - "cursor": "pointer", - "width": "fit-content", + "width": "100%", "marginTop": 32, "& svg": { marginRight: 10, @@ -98,6 +93,12 @@ const ViewAll = styled(Grid)(({ theme }) => ({ } })) +const ViewAllInner = styled(Grid)(({ theme }) => ({ + cursor: "pointer", + width: "fit-content", + display: "flex" +})) + const BackButtonText = styled(Grid)({ alignItems: "baseline", marginBottom: 16, @@ -145,6 +146,7 @@ export const UserMovements: React.FC<{ const isMobileSmall = useMediaQuery(theme.breakpoints.down("sm")) const [openFiltersDialog, setOpenFiltersDialog] = useState(false) const [openFiltersTrxDialog, setOpenFiltersTrxDialog] = useState(false) + const [searchText, setSearchText] = useState("") const [filters, setFilters] = useState() const [trxFilters, setTrxFilters] = useState() @@ -153,17 +155,61 @@ export const UserMovements: React.FC<{ const { data: polls } = usePolls(data?.liteDAOData?._id) const { data: userVotes } = useUserVotes() + const currentProposalsCreated = useMemo(() => { + if (proposalsCreated) { + let rows = proposalsCreated.slice() + if (searchText && selectedTab === 0) { + rows = rows.filter(holding => holding.id && holding.id.toLowerCase().includes(searchText.toLowerCase())) + } + return rows + } else { + return [] + } + }, [proposalsCreated, searchText, selectedTab]) + + const currentProposalsVoted = useMemo(() => { + if (proposalsCreated) { + let rows = proposalsCreated.slice() + + if (searchText && selectedTab === 1) { + rows = rows.filter(holding => holding.id && holding.id.toLowerCase().includes(searchText.toLowerCase())) + } + return rows + } else { + return [] + } + }, [proposalsCreated, searchText, selectedTab]) + const pollsPosted: Poll[] | undefined = useMemo(() => { - return polls?.filter(p => p.author === account) + let list = polls?.slice() + if (list) { + list = polls?.filter(p => p.author === account) + + if (searchText && selectedTab === 0) { + list = list!.filter(holding => holding.name && holding.name.toLowerCase().includes(searchText.toLowerCase())) + } + return list + } + return [] // eslint-disable-next-line react-hooks/exhaustive-deps - }, [account]) + }, [account, searchText]) + + const currentVotedPolls = useMemo(() => { + const votedPolls: any = [] + pollsPosted?.map((p: Poll) => { + if (userVotes && userVotes.filter(v => p._id === v.pollID).length > 0) { + return votedPolls.push(p) + } + }) - const votedPolls: any = [] - pollsPosted?.map((p: Poll) => { - if (userVotes && userVotes.filter(v => p._id === v.pollID).length > 0) { - return votedPolls.push(p) + let data = votedPolls.slice() + if (searchText && selectedTab === 1) { + data = data!.filter( + (holding: any) => holding.name && holding.name.toLowerCase().includes(searchText.toLowerCase()) + ) } - }) + return data + }, [searchText, selectedTab, pollsPosted, userVotes]) const useUserTransfers = (): TransferWithBN[] | undefined => { const { data: transfers } = useTransfers(daoId) @@ -183,6 +229,11 @@ export const UserMovements: React.FC<{ const [pageCount, setPageCount] = React.useState(count) const handleChangeTab = (newValue: number) => { + const bar = document.getElementById("standard-search") as HTMLInputElement + if (bar) { + bar.value = "" + setSearchText("") + } setSelectedTab(newValue) } @@ -214,10 +265,16 @@ export const UserMovements: React.FC<{ holdings = handleFilterData(holdings) } + if (searchText && searchText !== "") { + holdings = holdings.filter( + holding => holding.token && holding.token.name.toLowerCase().includes(searchText.toLowerCase()) + ) + } + return holdings } return [] - }, [transfers, trxFilters]) + }, [transfers, trxFilters, searchText]) useEffect(() => { setFilteredTransactions(currentTransfers) @@ -249,6 +306,10 @@ export const UserMovements: React.FC<{ setTrxFilters(filters) } + const filterByName = (text: string) => { + setSearchText(text.trim()) + } + return ( {showActivity ? ( @@ -315,14 +376,23 @@ export const UserMovements: React.FC<{ ) : ( - (selectedTab !== 2 ? setOpenFiltersDialog(true) : setOpenFiltersTrxDialog(true))} - > + - - Filter & Sort + + (selectedTab !== 2 ? setOpenFiltersDialog(true) : setOpenFiltersTrxDialog(true))} + /> + (selectedTab !== 2 ? setOpenFiltersDialog(true) : setOpenFiltersTrxDialog(true))} + > + Filter & Sort + + + + + )} @@ -330,48 +400,30 @@ export const UserMovements: React.FC<{ - {proposalsCreated && cycleInfo && ( + {currentProposalsCreated && cycleInfo && ( )} - {!(proposalsCreated && proposalsCreated.length > 0) && !(pollsPosted && pollsPosted.length > 0) ? ( - - - - No items - - - - ) : null} {/* TAB VOTES CONTENT */} - {votedPolls && cycleInfo && ( + {currentVotedPolls && cycleInfo && ( )} - {!(proposalsVoted && proposalsVoted.length > 0) && !(votedPolls && votedPolls.length > 0) ? ( - - - - No items - - - - ) : null} @@ -403,7 +455,7 @@ export const UserMovements: React.FC<{ ) : ( - + No items diff --git a/src/services/contracts/baseDAO/hooks/useTransfers.ts b/src/services/contracts/baseDAO/hooks/useTransfers.ts index 8bfc54b8..5e5fe81a 100644 --- a/src/services/contracts/baseDAO/hooks/useTransfers.ts +++ b/src/services/contracts/baseDAO/hooks/useTransfers.ts @@ -25,6 +25,7 @@ export interface TransferWithBN { hash: string type?: "Withdrawal" | "Deposit" token?: BNToken + status?: string } export const useTransfers = (contractAddress: string) => { diff --git a/src/theme/index.ts b/src/theme/index.ts index 164e773e..54f56b2a 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -213,7 +213,8 @@ export const theme = createTheme({ borderBottom: "none" }, "&:before": { - borderBottom: "none" + borderBottom: "none", + transition: "none" }, "&:hover:not($disabled):not($focused):not($error):before": { borderBottom: "none"