From df62085e44a6b8c2e864b186cfabc9a1fbd48068 Mon Sep 17 00:00:00 2001 From: marshall <99344331+marshall2112@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:48:49 -0400 Subject: [PATCH 1/2] wip --- apps/dapp/src/assets/icons/cash.svg | 3 + apps/dapp/src/assets/icons/down-arrow.svg | 3 + apps/dapp/src/assets/icons/dropdown-arrow.svg | 1 + apps/dapp/src/assets/icons/infocircle.svg | 3 + apps/dapp/src/assets/icons/non-cash.svg | 3 + apps/dapp/src/assets/icons/order.svg | 5 + apps/dapp/src/assets/icons/search.svg | 3 + apps/dapp/src/assets/icons/vesting.svg | 3 + .../Layouts/V2Layout/Nav/NavLinks.tsx | 2 + .../Cash.tsx} | 4 +- .../TeamPayments/NonCash/Chart/Chart.tsx | 54 +++ .../TeamPayments/NonCash/Chart/LineChart.tsx | 312 ++++++++++++++++++ .../DataTables/ClaimHistoryDataTable.tsx | 225 +++++++++++++ .../NonCash/DataTables/ClaimableDataTable.tsx | 263 +++++++++++++++ .../TeamPayments/NonCash/IntroConnected.tsx | 167 ++++++++++ .../NonCash/IntroNotConnected.tsx | 151 +++++++++ .../Pages/TeamPayments/NonCash/NonCash.tsx | 8 + .../NonCash/Tables/ClaimHistoryTable.tsx | 46 +++ .../NonCash/Tables/ClaimableTable.tsx | 61 ++++ .../VestingDashboard/Chart/BarChart.tsx | 165 +++++++++ .../VestingDashboard/Chart/Chart.tsx | 198 +++++++++++ .../DataTables/ClaimHistoryDataTable.tsx | 235 +++++++++++++ .../Tables/ClaimHistoryTable.tsx | 50 +++ .../VestingDashboard/VestingDashboard.tsx | 102 ++++++ .../VestingDashboard/components/Input.tsx | 78 +++++ .../components/InputSelect.tsx | 207 ++++++++++++ .../components/Pages/TeamPayments/index.tsx | 124 +++++++ apps/dapp/src/main.tsx | 33 +- 28 files changed, 2504 insertions(+), 5 deletions(-) create mode 100644 apps/dapp/src/assets/icons/cash.svg create mode 100644 apps/dapp/src/assets/icons/down-arrow.svg create mode 100644 apps/dapp/src/assets/icons/dropdown-arrow.svg create mode 100644 apps/dapp/src/assets/icons/infocircle.svg create mode 100644 apps/dapp/src/assets/icons/non-cash.svg create mode 100644 apps/dapp/src/assets/icons/order.svg create mode 100644 apps/dapp/src/assets/icons/search.svg create mode 100644 apps/dapp/src/assets/icons/vesting.svg rename apps/dapp/src/components/Pages/{TeamPayments.tsx => TeamPayments/Cash.tsx} (99%) create mode 100644 apps/dapp/src/components/Pages/TeamPayments/NonCash/Chart/Chart.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/NonCash/Chart/LineChart.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/NonCash/DataTables/ClaimHistoryDataTable.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/NonCash/DataTables/ClaimableDataTable.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/NonCash/IntroConnected.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/NonCash/IntroNotConnected.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/NonCash/NonCash.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/NonCash/Tables/ClaimHistoryTable.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/NonCash/Tables/ClaimableTable.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Chart/BarChart.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Chart/Chart.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/DataTables/ClaimHistoryDataTable.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Tables/ClaimHistoryTable.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/VestingDashboard.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/components/Input.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/components/InputSelect.tsx create mode 100644 apps/dapp/src/components/Pages/TeamPayments/index.tsx diff --git a/apps/dapp/src/assets/icons/cash.svg b/apps/dapp/src/assets/icons/cash.svg new file mode 100644 index 000000000..98597c61e --- /dev/null +++ b/apps/dapp/src/assets/icons/cash.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/dapp/src/assets/icons/down-arrow.svg b/apps/dapp/src/assets/icons/down-arrow.svg new file mode 100644 index 000000000..5bc4c7e14 --- /dev/null +++ b/apps/dapp/src/assets/icons/down-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/dapp/src/assets/icons/dropdown-arrow.svg b/apps/dapp/src/assets/icons/dropdown-arrow.svg new file mode 100644 index 000000000..a06cf36e3 --- /dev/null +++ b/apps/dapp/src/assets/icons/dropdown-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/dapp/src/assets/icons/infocircle.svg b/apps/dapp/src/assets/icons/infocircle.svg new file mode 100644 index 000000000..4882d59d5 --- /dev/null +++ b/apps/dapp/src/assets/icons/infocircle.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/dapp/src/assets/icons/non-cash.svg b/apps/dapp/src/assets/icons/non-cash.svg new file mode 100644 index 000000000..0edb51839 --- /dev/null +++ b/apps/dapp/src/assets/icons/non-cash.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/dapp/src/assets/icons/order.svg b/apps/dapp/src/assets/icons/order.svg new file mode 100644 index 000000000..16c293fcd --- /dev/null +++ b/apps/dapp/src/assets/icons/order.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/dapp/src/assets/icons/search.svg b/apps/dapp/src/assets/icons/search.svg new file mode 100644 index 000000000..42d0dc6f6 --- /dev/null +++ b/apps/dapp/src/assets/icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/dapp/src/assets/icons/vesting.svg b/apps/dapp/src/assets/icons/vesting.svg new file mode 100644 index 000000000..cce97be54 --- /dev/null +++ b/apps/dapp/src/assets/icons/vesting.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/dapp/src/components/Layouts/V2Layout/Nav/NavLinks.tsx b/apps/dapp/src/components/Layouts/V2Layout/Nav/NavLinks.tsx index dba777f6b..58df4a691 100644 --- a/apps/dapp/src/components/Layouts/V2Layout/Nav/NavLinks.tsx +++ b/apps/dapp/src/components/Layouts/V2Layout/Nav/NavLinks.tsx @@ -108,6 +108,8 @@ const NavLinkText = styled.span` text-decoration: underline; } font-size: ${(props) => (props.small ? '0.75rem' : '1rem')}; + white-space: pre; + display: inline-block; `; const NavLinkCell = styled.div` diff --git a/apps/dapp/src/components/Pages/TeamPayments.tsx b/apps/dapp/src/components/Pages/TeamPayments/Cash.tsx similarity index 99% rename from apps/dapp/src/components/Pages/TeamPayments.tsx rename to apps/dapp/src/components/Pages/TeamPayments/Cash.tsx index 49cfeab80..27a386194 100644 --- a/apps/dapp/src/components/Pages/TeamPayments.tsx +++ b/apps/dapp/src/components/Pages/TeamPayments/Cash.tsx @@ -45,7 +45,7 @@ const reducerInitialState: ReducerState = { labelFixed: `COLLECT ${PAYMENT_TOKEN} FOR IMPACT`, }; -const TeamPayments = () => { +const TeamPaymentsCash = () => { const { collectingFixed, labelFixed, @@ -330,4 +330,4 @@ const EpochDropdownContainer = styled.div` width: 12rem; `; -export default TeamPayments; +export default TeamPaymentsCash; diff --git a/apps/dapp/src/components/Pages/TeamPayments/NonCash/Chart/Chart.tsx b/apps/dapp/src/components/Pages/TeamPayments/NonCash/Chart/Chart.tsx new file mode 100644 index 000000000..72d4cef6e --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/NonCash/Chart/Chart.tsx @@ -0,0 +1,54 @@ +import { useEffect, useState } from 'react'; +import styled, { useTheme } from 'styled-components'; +import LineChart from './LineChart'; + +const chartData = [ + { month: 'January', vest1: 100_000, vest2: null }, + { month: 'February', vest1: 200_000, vest2: null }, + { month: 'March', vest1: 400_000, vest2: null }, + { month: 'April', vest1: 600_000, vest2: null }, + { month: 'May', vest1: 750_000, vest2: null }, + { month: 'June', vest1: 850_000, vest2: null }, + { month: 'July', vest1: 875_000, vest2: 100_000 }, + { month: 'August', vest1: 900_090, vest2: 201_910 }, + { month: 'September', vest1: 1_100_000, vest2: 350_000 }, + { month: 'October', vest1: 1_300_000, vest2: 450_000 }, + { month: 'November', vest1: null, vest2: null }, + { month: 'December', vest1: null, vest2: null }, +]; + +export const Chart = () => { + const theme = useTheme(); + const [metrics, setMetrics] = useState(chartData); + + return ( + + val.charAt(0)} + yTickFormatter={(val: any) => { + const num = val / 1_000_000; + return `${num.toFixed(1)} M\nTGLD`; + }} + tooltipLabelFormatter={(month: string) => `${month} 2025`} + tooltipValuesFormatter={(value: number, name: string) => { + const label = name === 'vest1' ? 'Vest 1' : 'Vest 2'; + return [`${value.toLocaleString()}`, label]; + }} + legendFormatter={(value: any) => + value === 'vest1' ? 'VEST 1' : value === 'vest2' ? 'VEST 2' : value + } + yDomain={[0, 1_600_000]} + /> + + ); +}; + +const PageContainer = styled.div` + height: 100%; +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/NonCash/Chart/LineChart.tsx b/apps/dapp/src/components/Pages/TeamPayments/NonCash/Chart/LineChart.tsx new file mode 100644 index 000000000..5dfa4b44e --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/NonCash/Chart/LineChart.tsx @@ -0,0 +1,312 @@ +import type { DataKey, AxisDomain } from 'recharts/types/util/types'; +import React, { useState } from 'react'; +import styled, { useTheme } from 'styled-components'; +import { + CartesianGrid, + ResponsiveContainer, + Line, + XAxis, + YAxis, + Tooltip, + Legend, + ComposedChart, +} from 'recharts'; +import { useMediaQuery } from 'react-responsive'; +import { queryPhone } from 'styles/breakpoints'; +import box from 'assets/icons/box.svg?react'; +import checkbox from 'assets/icons/checkmark-in-box.svg?react'; +import { createGlobalStyle } from 'styled-components'; + +export const GlobalChartStyles = createGlobalStyle` + .recharts-tooltip-cursor { + stroke: ${({ theme }) => theme.palette.brand}; + stroke-width: 2; + } +`; + +type LineChartProps = { + chartData: T[]; + xDataKey: DataKey; + lines: { series: DataKey; color: string; hide?: boolean }[]; + xTickFormatter: (xValue: any, index: number) => string; + yTickFormatter?: (yValue: any, index: number) => string; + tooltipLabelFormatter: (value: any) => string; + tooltipValuesFormatter?: (value: number, name: string) => string[]; + legendFormatter?: (value: string) => string; + yDomain?: AxisDomain; +}; + +export default function LineChart( + props: React.PropsWithChildren> +) { + const { + chartData, + xDataKey, + lines, + xTickFormatter, + yTickFormatter, + tooltipLabelFormatter, + tooltipValuesFormatter, + legendFormatter, + yDomain, + } = props; + + const theme = useTheme(); + + const [hiddenLines, setHiddenLines] = useState( + Object.fromEntries(lines.map((l) => [l.series.toString(), l.hide || false])) + ); + + const toggleLineVisibility = (key: string) => { + const nextState = { + ...hiddenLines, + [key]: !hiddenLines[key], + }; + + const totalLines = Object.values(hiddenLines).length; + const hiddenCount = Object.values(nextState).filter(Boolean).length; + + if (totalLines - hiddenCount === 0) return; + setHiddenLines(nextState); + }; + + const isPhoneOrAbove = useMediaQuery({ query: queryPhone }); + + return ( + + + + {lines.map((line) => ( + + ))} + + { + const formatted = yTickFormatter + ? yTickFormatter(payload.value, payload.index) + : `${(payload.value / 1_000_000).toFixed(1)} M TGLD`; + + const lines = formatted.split('\n'); + return ( + + {lines.map((line, i) => ( + // eslint-disable-next-line react/no-array-index-key + + {line} + + ))} + + ); + }} + offset={10} + domain={yDomain} + tickMargin={20} + /> + { + if (!active || !payload || !payload.length) return null; + + const vest1 = + Number(payload.find((p) => p.dataKey === 'vest1')?.value) || 0; + const vest2 = + Number(payload.find((p) => p.dataKey === 'vest2')?.value) || 0; + const total = vest1 + vest2; + + return ( +
+
+ {label} 2025 +
+
+ Vest 1: {vest1.toLocaleString()} +
+
+ Vest 2: {vest2.toLocaleString()} +
+
+ Total TGLD Vested: {total.toLocaleString()} +
+
+ ); + }} + /> + {legendFormatter && ( + ( +
+ {payload?.map((entry) => { + if (!entry.dataKey) return null; + + const key = entry.dataKey.toString(); + const isHidden = hiddenLines[key]; + const color = + key === 'vest1' ? theme.palette.brandLight : '#D0BE75'; + + return ( +
toggleLineVisibility(key)} + style={{ + display: 'flex', + alignItems: 'center', + gap: '0.5rem', + cursor: 'pointer', + userSelect: 'none', + }} + > + {isHidden ? : } + + {key === 'vest1' ? 'VEST 1' : 'VEST 2'} + +
+ ); + })} +
+ )} + /> + )} +
+
+ ); +} + +const EmptyboxIcon = styled(box)` + width: 18px; + height: 18px; +`; +const CheckboxIcon = styled(checkbox)` + width: 18px; + height: 18px; +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/NonCash/DataTables/ClaimHistoryDataTable.tsx b/apps/dapp/src/components/Pages/TeamPayments/NonCash/DataTables/ClaimHistoryDataTable.tsx new file mode 100644 index 000000000..de4ea5250 --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/NonCash/DataTables/ClaimHistoryDataTable.tsx @@ -0,0 +1,225 @@ +import env from 'constants/env'; +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import * as breakpoints from 'styles/breakpoints'; +import { ScrollBar } from 'components/Pages/Core/DappPages/SpiceBazaar/components/CustomScrollBar'; +import DownArrow from 'assets/icons/down-arrow.svg?react'; + +export type Transaction = { + grantDate: string; + claimedTgld: string; + transactionLink: string; + transactionHash: string; +}; + +type TableHeader = { name: string }; + +type TableProps = { + tableHeaders: TableHeader[]; + transactions: Transaction[]; + loading: boolean; + refetch?: () => void; + dataRefetching?: boolean; +}; + +export const DataTable: React.FC = ({ + tableHeaders, + transactions, + loading, +}) => { + const [filter, setFilter] = useState('Last 5 Shown'); + const [filteredTransactions, setFilteredTransactions] = + useState(transactions); + const filterOptions = ['Last 5 Shown', 'Show All']; + + useEffect(() => { + const sortedTransactions = [...transactions].sort( + (a, b) => Number(b.grantDate) - Number(a.grantDate) + ); + + if (filter === 'Last 5 Shown') { + setFilteredTransactions(sortedTransactions.slice(0, 5)); + } else { + setFilteredTransactions(sortedTransactions); + } + }, [filter, transactions]); + + return ( + +
+ Claim History + + {filterOptions.map((option) => ( + setFilter(option)} + selected={filter === option} + > + {option} + + ))} + +
+ + + + + {tableHeaders.map((h) => ( + + {h.name === 'Grant Date' ? ( + + {h.name} + + + ) : ( + <>{h.name} + )} + + ))} + + + + {loading ? ( + + Loading... + + ) : filteredTransactions.length === 0 ? ( + + No data available + + ) : ( + filteredTransactions.map((transaction) => ( + + {transaction.grantDate} + {transaction.claimedTgld} + + + {transaction.transactionLink} + + + + )) + )} + + + +
+ ); +}; + +const PageContainer = styled.div` + display: flex; + flex-direction: column; + gap: 20px; +`; + +const Header = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + gap: 10px; + + ${breakpoints.phoneAndAbove(` + flex-direction: row; + `)} +`; + +const Title = styled.h3` + color: ${({ theme }) => theme.palette.brandLight}; + font-size: 24px; + line-height: 44px; + margin: 0; +`; + +const FilterContainer = styled.div` + display: flex; + flex-direction: row; + gap: 20px; + padding-right: 16px; +`; + +const FilterButton = styled.button<{ selected: boolean }>` + font-size: 16px; + line-height: 19px; + background: none; + color: ${({ selected, theme }) => + selected ? theme.palette.brandLight : theme.palette.brand}; + border: none; + cursor: pointer; +`; + +const TableData = styled.table` + border-spacing: 10px + width: 100%; + border-collapse: collapse; + min-width: 500px; + width: 100%; +`; + +const HeaderRow = styled.tr` + border-bottom: 1px solid ${({ theme }) => theme.palette.brand}; +`; + +const TableHeader = styled.th` + font-size: 13px; + font-weight: 700; + line-height: 20px; + text-align: left; + color: ${({ theme }) => theme.palette.brand}; + position: sticky; + top: 0; + z-index: 1; + padding: 10px 16px; + + &:first-child { + padding: 10px 0px 10px 0px; + } + + &:last-child { + padding: 10px 0px 10px 16px; + } +`; + +const TableHeaderWithIcon = styled.div` + display: flex; + align-items: center; + gap: 5px; +`; + +const DataRow = styled.tr` + border-bottom: 1px solid ${({ theme }) => theme.palette.brand}; +`; + +const DataCell = styled.td` + font-size: 13px; + font-weight: 700; + line-height: 20px; + text-align: left; + width: 33%; + color: ${({ theme }) => theme.palette.brandLight}; + + a { + color: ${({ theme }) => theme.palette.brandLight}; + } + + a:hover { + color: ${({ theme }) => theme.palette.brand}; + } + + padding: 20px 16px; + + &:first-child { + padding: 20px 0px 20px 0px; + } + + &:last-child { + padding: 20px 0px 20px 0px; + } +`; + +const DownArrowIcon = styled(DownArrow)``; diff --git a/apps/dapp/src/components/Pages/TeamPayments/NonCash/DataTables/ClaimableDataTable.tsx b/apps/dapp/src/components/Pages/TeamPayments/NonCash/DataTables/ClaimableDataTable.tsx new file mode 100644 index 000000000..84dc8bf80 --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/NonCash/DataTables/ClaimableDataTable.tsx @@ -0,0 +1,263 @@ +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { Button } from 'components/Button/Button'; +import * as breakpoints from 'styles/breakpoints'; +import { ScrollBar } from 'components/Pages/Core/DappPages/SpiceBazaar/components/CustomScrollBar'; +import DownArrow from 'assets/icons/down-arrow.svg?react'; + +export type Transaction = { + id: string; + grantStartDate: string; + grantEndDate: string; + cliff: string; + vestedAmount: number; + claimableAmount: number; + action: string; +}; + +type TableHeader = { name: string }; + +type TableProps = { + tableHeaders: TableHeader[]; + transactions: Transaction[]; + loading: boolean; + title: string; + refetch?: () => void; + dataRefetching?: boolean; +}; + +export const DataTable: React.FC = ({ + tableHeaders, + transactions, + loading, + title, + refetch, +}) => { + const [filter, setFilter] = useState('Last 5 Shown'); + const [filteredTransactions, setFilteredTransactions] = + useState(transactions); + const filterOptions = ['Last 5 Shown', 'Show All']; + + useEffect(() => { + const sortedTransactions = [...transactions].sort( + (a, b) => Number(b.id) - Number(a.id) + ); + + if (filter === 'Last 5 Shown') { + setFilteredTransactions(sortedTransactions.slice(0, 5)); + } else { + setFilteredTransactions(sortedTransactions); + } + }, [filter, transactions]); + + return ( + <> + +
+ + {title} + + + {filterOptions.map((option) => ( + setFilter(option)} + selected={filter === option} + > + {option} + + ))} + +
+ + + + + {tableHeaders.map((h) => ( + + {h.name === 'Grant Start Date' || + h.name === 'Grant End Date' ? ( + + {h.name} + + + ) : ( + <>{h.name} + )} + + ))} + + + + {loading ? ( + + Loading... + + ) : filteredTransactions.length === 0 ? ( + + No data available + + ) : ( + filteredTransactions.map((transaction) => { + return ( + + {transaction.id} + {transaction.grantStartDate} + {transaction.grantEndDate} + {transaction.cliff} + {transaction.vestedAmount} + {transaction.claimableAmount} + + + {transaction.action === 'Claim' && ( + + Claim + + )} + + + + ); + }) + )} + + + +
+ + ); +}; + +const PageContainer = styled.div` + display: flex; + flex-direction: column; + gap: 20px; +`; + +const Header = styled.div` + display: flex; + align-items: center; + flex-direction: column; + gap: 10px; + + ${breakpoints.phoneAndAbove(` + flex-direction: row; + justify-content: space-between; + `)} +`; + +const HeaderLeft = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + ${breakpoints.phoneAndAbove(` + flex-direction: row; + gap: 40px; + `)} +`; + +const Title = styled.h3` + color: ${({ theme }) => theme.palette.brandLight}; + font-size: 24px; + line-height: 44px; + margin: 0; +`; + +const FilterContainer = styled.div` + display: flex; + flex-direction: row; + gap: 20px; + padding-right: 16px; +`; + +const FilterButton = styled.button<{ selected: boolean }>` + font-size: 16px; + line-height: 19px; + background: none; + color: ${({ selected, theme }) => + selected ? theme.palette.brandLight : theme.palette.brand}; + border: none; + cursor: pointer; +`; + +const TableData = styled.table` + border-spacing: 10px; + min-width: 800px; + border-collapse: collapse; + width: 100%; +`; + +const HeaderRow = styled.tr` + border-bottom: 1px solid ${({ theme }) => theme.palette.brand}; +`; + +const TableHeader = styled.th<{ name: string }>` + padding: 10px 25px; + font-size: 13px; + font-weight: 700; + line-height: 20px; + text-align: left; + white-space: nowrap; + color: ${({ theme }) => theme.palette.brand}; + position: sticky; + top: 0; + z-index: 1; + + &:first-child { + padding: 10px 25px 10px 0px; + } +`; + +const TableHeaderWithIcon = styled.div` + display: flex; + align-items: center; + gap: 5px; +`; + +const DataRow = styled.tr` + border-bottom: 1px solid ${({ theme }) => theme.palette.brand}; +`; + +const DataCell = styled.td` + font-size: 13px; + font-weight: 700; + line-height: 20px; + text-align: left; + white-space: nowrap; + color: ${({ theme }) => theme.palette.brandLight}; + padding: 20px 25px; + + &:first-child { + padding: 20px 25px 20px 0px; + } +`; + +const ButtonContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + width: 100%; +`; + +const TradeButton = styled(Button)` + padding: 10px 20px; + width: ${(props) => props.width || '120px'}; + height: min-content; + background: ${({ theme }) => theme.palette.gradients.dark}; + border: ${({ disabled, theme }) => + disabled ? 'none' : `1px solid ${theme.palette.brandDark}`}; + box-shadow: ${({ disabled }) => + disabled ? 'none' : '0px 0px 20px 0px rgba(222, 92, 6, 0.4)'}; + border-radius: 10px; + font-weight: 700; + font-size: 12px; + line-height: 20px; + text-transform: uppercase; + color: ${({ theme }) => theme.palette.brandLight}; +`; + +const DownArrowIcon = styled(DownArrow)``; diff --git a/apps/dapp/src/components/Pages/TeamPayments/NonCash/IntroConnected.tsx b/apps/dapp/src/components/Pages/TeamPayments/NonCash/IntroConnected.tsx new file mode 100644 index 000000000..875f489a2 --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/NonCash/IntroConnected.tsx @@ -0,0 +1,167 @@ +import { useState } from 'react'; +import styled from 'styled-components'; +import { Chart } from 'components/Pages/TeamPayments/NonCash/Chart/Chart'; +import { ClaimableTGLD } from 'components/Pages/TeamPayments/NonCash/Tables/ClaimableTable'; +import { ClaimHistory } from 'components/Pages/TeamPayments/NonCash/Tables/ClaimHistoryTable'; +import { GlobalChartStyles } from 'components/Pages/TeamPayments/NonCash/Chart/LineChart'; +import InfoCircle from 'assets/icons/infocircle.svg?react'; + +export default function IntroConnected() { + const [hasClaimed, setHasClaimed] = useState(false); + const [active, setActive] = useState<'current' | 'previous'>('current'); + + return ( + + Non-Cash Compensation + + setActive('current')} + > + Current + + setActive('previous')} + > + Previous + + + + + + 1,000,000 + + Total TGLD Reward + <InfoIcon /> + + + + 500,000 + Unvested TGLD + + + + + 500,000 + Vested TGLD + + setHasClaimed(!hasClaimed)}> + {hasClaimed ? '0' : '45,213'} + + Unclaimed TGLD + <InfoIcon /> + + + + + + + Your Vests + + + {!hasClaimed && } + + + ); +} + +const PageContainer = styled.div` + margin-top: -40px; + display: flex; + flex-direction: column; + gap: 40px; + padding: 40px 0px 0px 0px; +`; + +const HeaderTitle = styled.h2` + display: flex; + align-items: center; + text-align: center; + color: ${({ theme }) => theme.palette.brandLight}; + gap: 15px; + margin: 0px; + font-size: 36px; +`; + +const StatusContainer = styled.div` + display: flex; + flex-direction: column; + gap: 20px; +`; + +const BoxContainer = styled.div` + display: flex; + flex-direction: row; + gap: 20px; +`; + +const Box = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex-grow: 1; + flex-basis: 0; + min-height: 136px; + gap: 12px; + padding: 10px; + border: 1px solid ${({ theme }) => theme.palette.brand}; + border-radius: 10px; + background: linear-gradient(to bottom, #0b0a0a, #1d1a1a); +`; + +const Title = styled.div` + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + font-size: 16px; + line-height: 19px; + color: ${({ theme }) => theme.palette.brand}; +`; + +const Sum = styled.div` + font-size: 24px; + font-weight: 700; + line-height: 29px; + color: ${({ theme }) => theme.palette.brandLight}; +`; + +const ChartContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const ChartTitle = styled.div` + color: ${({ theme }) => theme.palette.brandLight}; + font-size: 24px; +`; + +const InfoIcon = styled(InfoCircle)` + width: 24px; + height: 24px; +`; + +const Filter = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + gap: 40px; + padding: 0px 20px; +`; + +const ToggleButton = styled.button<{ active: boolean }>` + background: none; + border: none; + color: ${({ active, theme }) => + active ? theme.palette.brandLight : theme.palette.brand}; + text-decoration: ${({ active }) => (active ? 'underline' : 'none')}; +t ext-decoration-color: ${({ theme }) => + theme.palette.brandLight}; font-size: 16px; + line-height: 19px; + cursor: pointer; + padding: 0; + outline: none; +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/NonCash/IntroNotConnected.tsx b/apps/dapp/src/components/Pages/TeamPayments/NonCash/IntroNotConnected.tsx new file mode 100644 index 000000000..0f1f9902d --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/NonCash/IntroNotConnected.tsx @@ -0,0 +1,151 @@ +import React, { useState, useRef } from 'react'; +import styled from 'styled-components'; +import Image from 'components/Image/Image'; +import { Flex } from 'components/Layout/Flex'; +import eyeImage from 'assets/images/no-pupil-eye.png'; +import { Button } from 'components/Button/Button'; +import wallet from 'assets/icons/wallet.svg?react'; +import { useConnectWallet } from '@web3-onboard/react'; + +export default function IntroNotConnected() { + const [cursorCoords, setCursorCoords] = useState([0, 0]); + const imageRef = useRef(null); + const [{}, connect] = useConnectWallet(); + + const pupilStyle = { + transform: getPupilTransform(imageRef, cursorCoords), + }; + + function getPupilTransform( + imageRef: React.RefObject, + cursorCoords: number[] + ) { + const headerHeight = 80; + + if (imageRef.current) { + const x = 0 - window.innerWidth / 2 + cursorCoords[0]; + const y = + 0 - + window.innerHeight / 2 + + imageRef.current.offsetTop + + cursorCoords[1] - + headerHeight; + + const values = normalizeTransform(x, y); + return `translate(${values[0]}px, ${values[1]}px)`; + } + } + + function normalizeTransform(x: number, y: number) { + if (Math.abs(x) + Math.abs(y) <= 10) { + return [x, y]; + } + + const multipleOfLength = (Math.abs(x) + Math.abs(y)) / 10; + + return [x / multipleOfLength, y / multipleOfLength]; + } + + return ( + setCursorCoords([e.clientX, e.clientY])}> + + + Templar, you are seen. +
+
+ With each stone you lay the Temple stands taller. +
+
+ Now the Temple gives back to you. +
+ +
+ + + + +
+
+ { + connect(); + }} + style={{ margin: 'auto', whiteSpace: 'nowrap' }} + > + + Connect Wallet + +
+ ); +} + +const PageContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 40px; +`; + +const Copy = styled.p` + text-align: center; + margin-bottom: 3rem; +`; + +const EyeArea = styled(Flex)` + width: 18.75rem; + height: 18.75rem; +`; + +const Pupil = styled.div` + position: absolute; + height: 1rem; + width: 1rem; + background-color: ${({ theme }) => theme.palette.brand}; + border-radius: 1rem; +`; + +const WalletIcon = styled(wallet)` + min-width: 24px; + min-height: 24px; +`; + +const TradeButton = styled(Button)` + padding: 12px 20px 12px 20px; + width: ${(props) => props.width || 'min-content'}; + height: min-content; + background: linear-gradient(90deg, #58321a 20%, #95613f 84.5%); + border: 1px solid ${({ theme }) => theme.palette.brandDark}; + box-shadow: 0px 0px 20px 0px #de5c0666; + border-radius: 10px; + font-size: 16px; + line-height: 20px; + font-weight: 700; + text-transform: uppercase; + color: ${({ theme }) => theme.palette.brandLight}; + + /* Flex settings for centering button content */ + display: flex; + align-items: center; + justify-content: center; + + /* Flex settings for the span inside the button */ + & > span { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + } +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/NonCash/NonCash.tsx b/apps/dapp/src/components/Pages/TeamPayments/NonCash/NonCash.tsx new file mode 100644 index 000000000..22b7af88f --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/NonCash/NonCash.tsx @@ -0,0 +1,8 @@ +import IntroNotConnected from './IntroNotConnected'; +import { useWallet } from 'providers/WalletProvider'; +import IntroConnected from './IntroConnected'; +export default function TeamPaymentsNonCash() { + const { wallet } = useWallet(); + + return <>{wallet ? : }; +} diff --git a/apps/dapp/src/components/Pages/TeamPayments/NonCash/Tables/ClaimHistoryTable.tsx b/apps/dapp/src/components/Pages/TeamPayments/NonCash/Tables/ClaimHistoryTable.tsx new file mode 100644 index 000000000..65e78804f --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/NonCash/Tables/ClaimHistoryTable.tsx @@ -0,0 +1,46 @@ +import styled from 'styled-components'; +import { DataTable } from '../DataTables/ClaimHistoryDataTable'; + +enum TableHeaders { + GrantDate = 'Grant Date', + ClaimedTgld = 'Claimed TGLD', + TransactionLink = 'Transaction Link', +} + +const tableHeaders = [ + { name: TableHeaders.GrantDate }, + { name: TableHeaders.ClaimedTgld }, + { name: TableHeaders.TransactionLink }, +]; + +const data = [ + { + grantDate: 'July 2025', + claimedTgld: '1222000', + transactionLink: '0x192c453a2dbb0b...0e74a056', + transactionHash: '0x192c453a2dbb0b...0e74a056', + }, + { + grantDate: 'Jan 2025', + claimedTgld: '700000', + transactionLink: '0x342c4535430979a...0b6b8b25', + transactionHash: '0x342c4535430979a...0b6b8b25', + }, +]; + +export const ClaimHistory = () => { + return ( + + + + ); +}; + +const AuctionsHistoryContainer = styled.div` + display: flex; + flex-direction: column; +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/NonCash/Tables/ClaimableTable.tsx b/apps/dapp/src/components/Pages/TeamPayments/NonCash/Tables/ClaimableTable.tsx new file mode 100644 index 000000000..e3c6222f9 --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/NonCash/Tables/ClaimableTable.tsx @@ -0,0 +1,61 @@ +import styled from 'styled-components'; +import { DataTable } from '../DataTables/ClaimableDataTable'; + +enum TableHeaders { + Id = 'ID', + GrantStartDate = 'Grant Start Date', + GrantEndDate = 'Grant End Date', + Cliff = 'Cliff', + VestedAmount = 'Vested Amount', + ClaimableAmount = 'Claimable Amount', + Action = '', +} + +const tableHeaders = [ + { name: TableHeaders.Id }, + { name: TableHeaders.GrantStartDate }, + { name: TableHeaders.GrantEndDate }, + { name: TableHeaders.Cliff }, + { name: TableHeaders.VestedAmount }, + { name: TableHeaders.ClaimableAmount }, + { name: TableHeaders.Action }, +]; + +const data = [ + { + id: 'Sep 2025', + grantStartDate: 'Sep 2025', + grantEndDate: 'Sep 2026', + cliff: '3 months', + vestedAmount: 133000, + claimableAmount: 22000, + action: 'Claim', + }, + { + id: 'Aug 2025', + grantStartDate: 'Aug 2025', + grantEndDate: 'Aug 2026', + cliff: '3 months', + vestedAmount: 200000, + claimableAmount: 22000, + action: 'Claim', + }, +]; + +export const ClaimableTGLD = () => { + return ( + + + + ); +}; + +const AuctionsHistoryContainer = styled.div` + display: flex; + flex-direction: column; +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Chart/BarChart.tsx b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Chart/BarChart.tsx new file mode 100644 index 000000000..87f7fdcec --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Chart/BarChart.tsx @@ -0,0 +1,165 @@ +import type { DataKey, AxisDomain } from 'recharts/types/util/types'; +import { + ComposedChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + CartesianGrid, + Line, +} from 'recharts'; +import { useTheme } from 'styled-components'; + +export type BarChartProps = { + chartData: T[]; + xDataKey: DataKey; + xTickFormatter: (xValue: any, index: number) => string; + yTickFormatter?: (yValue: any, index: number) => string; + tooltipLabelFormatter: (value: any) => string; + tooltipValuesFormatter?: (props: any) => string[]; + yDomain?: AxisDomain; + series: { key: string; color: string }[]; + lineDataKey?: DataKey; +}; + +export default function CustomBarChart({ + chartData, + xDataKey, + xTickFormatter, + yTickFormatter, + tooltipLabelFormatter, + tooltipValuesFormatter, + yDomain, + series, + lineDataKey, +}: React.PropsWithChildren>) { + const theme = useTheme(); + + const activeBarStyle = { + fill: series[series.length - 1].color, + stroke: '#FFFFFF', + strokeWidth: 1, + }; + + const CustomTooltip = ({ payload }: any) => { + if (!tooltipValuesFormatter || !payload || payload.length === 0) + return null; + + const rawData = payload[0]?.payload; + if (!rawData) return null; + + const lines = tooltipValuesFormatter(rawData); + + return ( +
+
+ {tooltipLabelFormatter('')} +
+ {lines.map((line, i) => ( + // eslint-disable-next-line react/no-array-index-key +
{line}
+ ))} +
+ ); + }; + + return ( + + + + + + } offset={30} cursor={false} /> + {series.map((serie, index) => { + const isTopBar = index === series.length - 1; + const isBottomBar = index === 0; + return ( + + ); + })} + {lineDataKey && ( + + )} + + + ); +} diff --git a/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Chart/Chart.tsx b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Chart/Chart.tsx new file mode 100644 index 000000000..32f7252c2 --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Chart/Chart.tsx @@ -0,0 +1,198 @@ +import styled from 'styled-components'; +import { format } from 'date-fns'; +import { formatNumberAbbreviated } from 'utils/formatter'; +import CustomBarChart from './BarChart'; + +export type Metric = { + timestamp: number; + value1: number; + value2: number; + value3: number; + value4: number; + runway: number; +}; + +const tickFormatter = (timestamp: number): string => + format(new Date(timestamp), 'MMM'); + +const series: { key: keyof Metric; color: string }[] = [ + { key: 'value1', color: '#FFDEC9' }, + { key: 'value2', color: '#D0BE75' }, + { key: 'value3', color: '#BD7B4F' }, + { key: 'value4', color: '#95613F' }, +]; + +const vestedTGLDData = [ + { + timestamp: 1704067200000, + value1: 200000, + value2: 250000, + value3: 300000, + value4: 246920, + }, + { + timestamp: 1706745600000, + value1: 200000, + value2: 250000, + value3: 250000, + value4: 200000, + }, + { + timestamp: 1709251200000, + value1: 220000, + value2: 270000, + value3: 240000, + value4: 210000, + }, + { + timestamp: 1711939200000, + value1: 230000, + value2: 150000, + value3: 200000, + value4: 120000, + }, + { + timestamp: 1714521600000, + value1: 250000, + value2: 100000, + value3: 150000, + value4: 100000, + }, + { + timestamp: 1717200000000, + value1: 200000, + value2: 230000, + value3: 200000, + value4: 180000, + }, + { + timestamp: 1719792000000, + value1: 300000, + value2: 250000, + value3: 200000, + value4: 150000, + }, + { + timestamp: 1722470400000, + value1: 180000, + value2: 240000, + value3: 280000, + value4: 210000, + }, + { + timestamp: 1725062400000, + value1: 150000, + value2: 130000, + value3: 170000, + value4: 120000, + }, + { + timestamp: 1727740800000, + value1: 400000, + value2: 330000, + value3: 250000, + value4: 180000, + }, + { + timestamp: 1730332800000, + value1: 100000, + value2: 200000, + value3: 230000, + value4: 180000, + }, + { + timestamp: 1733011200000, + value1: 250000, + value2: 180000, + value3: 200000, + value4: 150000, + }, +]; + +export const ProjectedTGLDVesting = () => { + const metrics: Metric[] = vestedTGLDData.map((item) => ({ + ...item, + runway: item.value1 + item.value2 + item.value3 + item.value4 + 200000, + })); + + const totalPerMonth = metrics.map( + (item) => item.value1 + item.value2 + item.value3 + item.value4 + ); + + const tooltipFormatterFn = (data: Metric): string[] => { + const grantValue = data.value4 ?? 0; + const monthTotal = data.value1 + data.value2 + data.value3 + data.value4; + const percent = monthTotal + ? ((grantValue / monthTotal) * 100).toFixed(0) + : '0'; + const dateLabel = tickFormatter(data.timestamp); + + return [ + `Amount to be vested: ${grantValue.toLocaleString()} TGLD`, + `Percentage of total amount to be vested in ${dateLabel} 2024:`, + `${grantValue.toLocaleString()} / ${monthTotal.toLocaleString()} = ${percent}%`, + ]; + }; + + return ( + + + Projected TGLD Vesting By Month + + + TGLD balance runway + + + + + chartData={metrics} + series={series} + xDataKey="timestamp" + xTickFormatter={tickFormatter} + yTickFormatter={(val) => `${formatNumberAbbreviated(val).string} TGLD`} + tooltipLabelFormatter={() => 'Grant ID'} + tooltipValuesFormatter={tooltipFormatterFn} + yDomain={[0, 1_500_000]} + lineDataKey="runway" + /> + + ); +}; + +const PageContainer = styled.div` + height: 100%; + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; +`; + +const HeaderContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +const Title = styled.h3` + line-height: 45px; + font-size: 24px; + font-weight: 400; + color: ${({ theme }) => theme.palette.brandLight}; + margin: 0; +`; + +const Legend = styled.div` + display: flex; + align-items: center; + gap: 10px; +`; + +const DashedLine = styled.div` + width: 30px; + border-top: 2px dashed ${({ theme }) => theme.palette.brandLight}; +`; + +const LegendText = styled.span` + font-size: 12px; + color: ${({ theme }) => theme.palette.brandLight}; +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/DataTables/ClaimHistoryDataTable.tsx b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/DataTables/ClaimHistoryDataTable.tsx new file mode 100644 index 000000000..8224c175f --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/DataTables/ClaimHistoryDataTable.tsx @@ -0,0 +1,235 @@ +import env from 'constants/env'; +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import * as breakpoints from 'styles/breakpoints'; +import { ScrollBar } from 'components/Pages/Core/DappPages/SpiceBazaar/components/CustomScrollBar'; +import DownArrow from 'assets/icons/down-arrow.svg?react'; +import Order from 'assets/icons/order.svg?react'; + +export type Transaction = { + grantDate: string; + claimedTgld: string; + granteeAddress: string; + transactionLink: string; + transactionHash: string; +}; + +type TableHeader = { name: string }; + +type TableProps = { + tableHeaders: TableHeader[]; + transactions: Transaction[]; + loading: boolean; + refetch?: () => void; + dataRefetching?: boolean; +}; + +export const DataTable: React.FC = ({ + tableHeaders, + transactions, + loading, +}) => { + const [filter, setFilter] = useState('Last 5 Shown'); + const [filteredTransactions, setFilteredTransactions] = + useState(transactions); + const filterOptions = ['Last 5 Shown', 'Show All']; + + useEffect(() => { + const sortedTransactions = [...transactions].sort( + (a, b) => Number(b.grantDate) - Number(a.grantDate) + ); + + if (filter === 'Last 5 Shown') { + setFilteredTransactions(sortedTransactions.slice(0, 5)); + } else { + setFilteredTransactions(sortedTransactions); + } + }, [filter, transactions]); + + return ( + +
+ Claim History + + {filterOptions.map((option) => ( + setFilter(option)} + selected={filter === option} + > + {option} + + ))} + +
+ + + + + {tableHeaders.map((h) => ( + + {h.name === 'Grant Date' ? ( + + {h.name} + + + ) : h.name === 'Grantee Address' ? ( + + {h.name} + + + ) : ( + <>{h.name} + )} + + ))} + + + + {loading ? ( + + Loading... + + ) : filteredTransactions.length === 0 ? ( + + No data available + + ) : ( + filteredTransactions.map((transaction) => ( + + {transaction.grantDate} + {transaction.claimedTgld} + {transaction.granteeAddress} + + + {transaction.transactionLink} + + + + )) + )} + + + +
+ ); +}; + +const PageContainer = styled.div` + display: flex; + flex-direction: column; + gap: 20px; +`; + +const Header = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + gap: 10px; + + ${breakpoints.phoneAndAbove(` + flex-direction: row; + `)} +`; + +const Title = styled.h3` + color: ${({ theme }) => theme.palette.brandLight}; + font-size: 24px; + line-height: 44px; + margin: 0; +`; + +const FilterContainer = styled.div` + display: flex; + flex-direction: row; + gap: 20px; + padding-right: 16px; +`; + +const FilterButton = styled.button<{ selected: boolean }>` + font-size: 16px; + line-height: 19px; + background: none; + color: ${({ selected, theme }) => + selected ? theme.palette.brandLight : theme.palette.brand}; + border: none; + cursor: pointer; +`; + +const TableData = styled.table` + border-spacing: 10px + width: 100%; + border-collapse: collapse; + min-width: 500px; + width: 100%; +`; + +const HeaderRow = styled.tr` + border-bottom: 1px solid ${({ theme }) => theme.palette.brand}; +`; + +const TableHeader = styled.th` + font-size: 13px; + font-weight: 700; + line-height: 20px; + text-align: left; + color: ${({ theme }) => theme.palette.brand}; + position: sticky; + top: 0; + z-index: 1; + padding: 10px 16px; + + &:first-child { + padding: 10px 0px 10px 0px; + } + + &:last-child { + padding: 10px 0px 10px 16px; + } +`; + +const TableHeaderWithIcon = styled.div` + display: flex; + align-items: center; + gap: 5px; +`; + +const DataRow = styled.tr` + border-bottom: 1px solid ${({ theme }) => theme.palette.brand}; +`; + +const DataCell = styled.td` + font-size: 13px; + font-weight: 700; + line-height: 20px; + text-align: left; + width: 33%; + color: ${({ theme }) => theme.palette.brandLight}; + + a { + color: ${({ theme }) => theme.palette.brandLight}; + } + + a:hover { + color: ${({ theme }) => theme.palette.brand}; + } + + padding: 20px 16px; + + &:first-child { + padding: 20px 0px 20px 0px; + } + + &:last-child { + padding: 20px 0px 20px 0px; + } +`; + +const DownArrowIcon = styled(DownArrow)``; + +const OrderIcon = styled(Order)``; diff --git a/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Tables/ClaimHistoryTable.tsx b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Tables/ClaimHistoryTable.tsx new file mode 100644 index 000000000..544873327 --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/Tables/ClaimHistoryTable.tsx @@ -0,0 +1,50 @@ +import styled from 'styled-components'; +import { DataTable } from '../DataTables/ClaimHistoryDataTable'; + +enum TableHeaders { + GrantDate = 'Grant Date', + ClaimedTgld = 'Claimed TGLD', + GranteeAddress = 'Grantee Address', + TransactionLink = 'Transaction Link', +} + +const tableHeaders = [ + { name: TableHeaders.GrantDate }, + { name: TableHeaders.ClaimedTgld }, + { name: TableHeaders.GranteeAddress }, + { name: TableHeaders.TransactionLink }, +]; + +const data = [ + { + grantDate: 'July 2025', + claimedTgld: '1222000', + granteeAddress: 'x112938091', + transactionLink: '0x192c453a2dbb0b...0e74a056', + transactionHash: '0x192c453a2dbb0b...0e74a056', + }, + { + grantDate: 'Jan 2025', + claimedTgld: '700000', + granteeAddress: 'x817390910', + transactionLink: '0x342c4535430979a...0b6b8b25', + transactionHash: '0x342c4535430979a...0b6b8b25', + }, +]; + +export const ClaimHistory = () => { + return ( + + + + ); +}; + +const AuctionsHistoryContainer = styled.div` + display: flex; + flex-direction: column; +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/VestingDashboard.tsx b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/VestingDashboard.tsx new file mode 100644 index 000000000..25c4d8111 --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/VestingDashboard.tsx @@ -0,0 +1,102 @@ +import { ProjectedTGLDVesting } from 'components/Pages/TeamPayments/VestingDashboard/Chart/Chart'; +import { ClaimHistory } from 'components/Pages/TeamPayments/VestingDashboard/Tables/ClaimHistoryTable'; +import styled from 'styled-components'; +import { SearchInput } from './components/Input'; +import { useState } from 'react'; +import { AllDatesDropdown } from './components/InputSelect'; + +export default function VestingDashboard() { + const [searchValue, setSearchValue] = useState(''); + + return ( + + + + + Vesting Dashboard + + + + + 1,000,000 + Total TGLD Vested / Claimable + + + 500,000 + Total TGLD Claimed + + + + + + + ); +} + +const PageContainer = styled.div` + margin-top: -40px; + display: flex; + flex-direction: column; + gap: 40px; + padding: 40px 0px 0px 0px; +`; + +const HeaderTitle = styled.h2` + display: flex; + align-items: center; + text-align: center; + color: ${({ theme }) => theme.palette.brandLight}; + gap: 15px; + margin: 0px; + font-size: 36px; +`; + +const StatusContainer = styled.div` + display: flex; + flex-direction: column; + gap: 20px; +`; + +const BoxContainer = styled.div` + display: flex; + flex-direction: row; + gap: 20px; +`; + +const Box = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex-grow: 1; + flex-basis: 0; + min-height: 136px; + gap: 12px; + padding: 10px; + border: 1px solid ${({ theme }) => theme.palette.brand}; + border-radius: 10px; + background: linear-gradient(to bottom, #0b0a0a, #1d1a1a); +`; + +const Title = styled.div` + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + font-size: 16px; + line-height: 19px; + color: ${({ theme }) => theme.palette.brand}; +`; + +const Sum = styled.div` + font-size: 24px; + font-weight: 700; + line-height: 29px; + color: ${({ theme }) => theme.palette.brandLight}; +`; + +const SearchBar = styled.div` + display: flex; + flex-direction: row; + justify-content: flex-end; +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/components/Input.tsx b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/components/Input.tsx new file mode 100644 index 000000000..7222d73b3 --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/components/Input.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import styled from 'styled-components'; +import Search from 'assets/icons/search.svg?react'; + +interface SearchInputProps { + value: string; + onChange: (value: string) => void; + placeholder?: string; + disabled?: boolean; +} + +export const SearchInput: React.FC = ({ + value, + onChange, + placeholder = 'Search wallet address', + disabled = false, +}) => { + return ( + + + onChange(e.target.value)} + placeholder={placeholder} + disabled={disabled} + /> + + ); +}; + +const SearchInputWrapper = styled.div<{ disabled?: boolean }>` + display: flex; + align-items: center; + background-color: ${({ theme }) => theme.palette.dark}; + border: 1px solid ${({ theme }) => theme.palette.brand}; + border-radius: 5px; + padding: 5px 10px; + gap: 10px; + width: 220px; + box-sizing: border-box; + + ${({ disabled, theme }) => + disabled && + ` + background-color: ${theme.palette.brand25}; + cursor: not-allowed; + `} +`; + +const SearchIcon = styled(Search)` + widht: 18px; + height: 18px; +`; + +const StyledInput = styled.input` + ${({ theme }) => theme.typography.fonts.fontHeading}; + color: ${({ theme }) => theme.palette.brand}; + background-color: transparent; + border: none; + outline: none; + width: 100%; + font-family: Caviar Dreams; + font-weight: 400; + font-size: 16px; + line-height: 120%; + letter-spacing: 5%; + + &::placeholder { + color: ${({ theme }) => theme.palette.brand}; + opacity: 0.8; + } + + &:disabled { + cursor: not-allowed; + opacity: 0.6; + } +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/components/InputSelect.tsx b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/components/InputSelect.tsx new file mode 100644 index 000000000..c23dc61bd --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/VestingDashboard/components/InputSelect.tsx @@ -0,0 +1,207 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import DropdownArrow from 'assets/icons/dropdown-arrow.svg?react'; +import { theme } from 'styles/theme'; + +const months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', +]; + +const years = Array.from({ length: 11 }, (_, i) => 2020 + i); + +export const AllDatesDropdown = () => { + const [open, setOpen] = useState(false); + + return ( + + {!open && ( + setOpen(true)}> + All dates + + + )} + + {open && ( + + setOpen(false)}> + All dates + + + + + From + + + + + + + + + + + + To + + + + + + + + + + + + + )} + + ); +}; + +const Wrapper = styled.div` + position: relative; + width: 320px; + z-index: 10; + min-height: 34px; +`; + +const FloatingPanel = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + background-color: ${theme.palette.black}; + border: 1px solid ${theme.palette.brand}; + border-radius: 5px; + color: ${theme.palette.brandLight}; + z-index: 100; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); +`; + +const DropdownHeaderBase = styled.div` + box-sizing: border-box; + background-color: ${theme.palette.black}; + color: ${theme.palette.brandLight}; + padding: 5px 10px; + border-radius: 5px; + height: 34px; + font-size: 16px; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +`; + +const Trigger = styled(DropdownHeaderBase)` + border: 1px solid ${theme.palette.brand}; + + &:hover { + border-color: ${theme.palette.brandLight}; + } +`; + +const DropdownHeader = styled(DropdownHeaderBase)` + height: 32px; +`; + +const DropdownContent = styled.div` + display: flex; + padding: 10px; + flex-direction: column; + gap: 10px; +`; + +const FromTo = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +const FromToLabel = styled.div` + color: ${theme.palette.brandLight}; + font-size: 16px; + font-size: 16px; + line-height: 100%; + letter-spacing: -2%; +`; + +const MonthYearRow = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; +`; + +const SelectDropdown = styled.div` + display: flex; + + select { + background-color: ${theme.palette.black}; + color: ${theme.palette.brand}; + font-family: 'Caviar Dreams'; + font-weight: 700; + font-size: 16px; + letter-spacing: 5%; + border: 1px solid ${theme.palette.black}; + } +`; + +const DateInput = styled.input` + width: 100%; + background-color: ${theme.palette.black}; + color: ${theme.palette.brand}; + border: 1px solid ${theme.palette.brand}; + padding: 8px 10px; + border-radius: 5px; + font-family: Caviar Dreams; + font-weight: 700; + font-size: 12px; + line-height: 150%; + letter-spacing: 5%; + + &::placeholder { + color: ${theme.palette.brand}; + } +`; + +const Arrow = styled(DropdownArrow)<{ open: boolean }>` + transition: transform 200ms ease, color 200ms ease; + transform: ${({ open }) => (open ? 'rotate(180deg)' : 'rotate(0deg)')}; + color: ${({ open }) => + open ? theme.palette.brandLight : theme.palette.brand}; +`; diff --git a/apps/dapp/src/components/Pages/TeamPayments/index.tsx b/apps/dapp/src/components/Pages/TeamPayments/index.tsx new file mode 100644 index 000000000..f748d0de1 --- /dev/null +++ b/apps/dapp/src/components/Pages/TeamPayments/index.tsx @@ -0,0 +1,124 @@ +import { FunctionComponent, SVGProps, useState } from 'react'; +import * as breakpoints from 'styles/breakpoints'; +import { useMediaQuery } from 'react-responsive'; +import { Outlet, useLocation } from 'react-router-dom'; +import styled from 'styled-components'; +import { queryMinTablet } from 'styles/breakpoints'; +import Footer from '../../Layouts/V2Layout/Footer'; +import LeftNav from '../../Layouts/V2Layout/Nav/LeftNav'; +import MobileNav from '../../Layouts/V2Layout/Nav/MobileNav'; +import Cash from 'assets/icons/cash.svg?react'; +import NonCash from 'assets/icons/non-cash.svg?react'; +import Vesting from 'assets/icons/vesting.svg?react'; + +export type MenuNavItem = { + label: string; + linkTo: string; + Logo: FunctionComponent< + SVGProps & { title?: string | undefined } + >; + selected: boolean; +}; + +export type MenuNavItems = Array; + +enum DashboardLocPaths { + Cash = '/team-payments/cash', + NonCash = '/team-payments/non-cash', + VestingDashboard = '/team-payments/vesting-dashboard', +} + +const TeamPayments = () => { + const isTabletOrAbove = useMediaQuery({ query: queryMinTablet }); + const loc = useLocation(); + + const [isAdmin, setIsAdmin] = useState(false); //For manual swap between admin and contributor view + + const [selectedPath, setSelectedPath] = useState(loc.pathname); + + const onSelectMenuNavItems = (selectedMenuItem: MenuNavItem) => { + setSelectedPath(selectedMenuItem.linkTo); + }; + + const menuNavItems: MenuNavItem[] = isAdmin + ? [ + { + label: 'Vesting\nDashboard', + linkTo: DashboardLocPaths.VestingDashboard, + Logo: Vesting, + selected: selectedPath === DashboardLocPaths.VestingDashboard, + }, + ] + : [ + { + label: 'Cash', + linkTo: DashboardLocPaths.Cash, + Logo: Cash, + selected: selectedPath === DashboardLocPaths.Cash, + }, + { + label: 'Non-Cash', + linkTo: DashboardLocPaths.NonCash, + Logo: NonCash, + selected: selectedPath === DashboardLocPaths.NonCash, + }, + ]; + + return ( + + + {isTabletOrAbove ? ( + + ) : ( + + )} + + + + + +