Merge pull request #79 from blockworks-foundation/combine-history-tabs
combine account tabs for history
This commit is contained in:
commit
b83d9f5fe6
|
@ -71,7 +71,7 @@ const TokenList = () => {
|
|||
|
||||
return (
|
||||
<ContentBox hideBorder hidePadding>
|
||||
<div className="flex w-full items-center justify-end border-b border-th-bkg-3 py-3 px-6 xl:-mt-[36px] xl:mb-4 xl:w-auto xl:border-0 xl:py-0">
|
||||
<div className="flex w-full items-center justify-end border-b border-th-bkg-3 py-3 px-6 lg:-mt-[36px] lg:mb-4 lg:w-auto lg:border-0 lg:py-0">
|
||||
<Switch
|
||||
checked={showZeroBalances}
|
||||
disabled={!mangoAccount}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { useMemo, useState } from 'react'
|
||||
import TabButtons from '../shared/TabButtons'
|
||||
import TokenList from '../TokenList'
|
||||
import SwapHistoryTable from '../swap/SwapHistoryTable'
|
||||
import ActivityFeed from './ActivityFeed'
|
||||
import UnsettledTrades from '@components/trade/UnsettledTrades'
|
||||
import { useUnsettledSpotBalances } from 'hooks/useUnsettledSpotBalances'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import TradeHistory from '@components/trade/TradeHistory'
|
||||
import PerpPositions from '@components/trade/PerpPositions'
|
||||
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
||||
import OpenOrders from '@components/trade/OpenOrders'
|
||||
import HistoryTabs from './HistoryTabs'
|
||||
|
||||
const AccountTabs = () => {
|
||||
const [activeTab, setActiveTab] = useState('balances')
|
||||
|
@ -32,9 +32,7 @@ const AccountTabs = () => {
|
|||
['trade:positions', openPerpPositions.length],
|
||||
['trade:orders', Object.values(openOrders).flat().length],
|
||||
['trade:unsettled', unsettledTradeCount],
|
||||
['activity:activity', 0],
|
||||
['swap:swap-history', 0],
|
||||
['trade-history', 0],
|
||||
['history', 0],
|
||||
]
|
||||
}, [openPerpPositions, unsettledPerpPositions, unsettledSpotBalances])
|
||||
|
||||
|
@ -71,12 +69,8 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
|
|||
unsettledPerpPositions={unsettledPerpPositions}
|
||||
/>
|
||||
)
|
||||
case 'activity:activity':
|
||||
return <ActivityFeed />
|
||||
case 'swap:swap-history':
|
||||
return <SwapHistoryTable />
|
||||
case 'trade-history':
|
||||
return <TradeHistory />
|
||||
case 'history':
|
||||
return <HistoryTabs />
|
||||
default:
|
||||
return <TokenList />
|
||||
}
|
||||
|
|
|
@ -1,106 +1,29 @@
|
|||
import MangoDateRangePicker from '@components/forms/DateRangePicker'
|
||||
import Input from '@components/forms/Input'
|
||||
import Label from '@components/forms/Label'
|
||||
import MultiSelectDropdown from '@components/forms/MultiSelectDropdown'
|
||||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import Button, { IconButton } from '@components/shared/Button'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import {
|
||||
AdjustmentsVerticalIcon,
|
||||
ArrowLeftIcon,
|
||||
ArrowPathIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore, { LiquidationFeedItem } from '@store/mangoStore'
|
||||
import dayjs from 'dayjs'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import ActivityFeedTable from './ActivityFeedTable'
|
||||
|
||||
interface AdvancedFilters {
|
||||
symbol: string[]
|
||||
'start-date': string
|
||||
'end-date': string
|
||||
'usd-lower': string
|
||||
'usd-upper': string
|
||||
}
|
||||
|
||||
const DEFAULT_ADVANCED_FILTERS = {
|
||||
symbol: [],
|
||||
'start-date': '',
|
||||
'end-date': '',
|
||||
'usd-lower': '',
|
||||
'usd-upper': '',
|
||||
}
|
||||
|
||||
const DEFAULT_PARAMS = [
|
||||
'deposit',
|
||||
'perp_trade',
|
||||
'liquidate_token_with_token',
|
||||
'openbook_trade',
|
||||
'swap',
|
||||
'withdraw',
|
||||
]
|
||||
|
||||
const ActivityFeed = () => {
|
||||
const activityFeed = mangoStore((s) => s.activityFeed.feed)
|
||||
const actions = mangoStore.getState().actions
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [showActivityDetail, setShowActivityDetail] = useState(null)
|
||||
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilters>(
|
||||
DEFAULT_ADVANCED_FILTERS
|
||||
)
|
||||
const [params, setParams] = useState<string[]>(DEFAULT_PARAMS)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const handleShowActivityDetails = (activity: any) => {
|
||||
setShowActivityDetail(activity)
|
||||
}
|
||||
|
||||
const advancedParamsString = useMemo(() => {
|
||||
let advancedParams = ''
|
||||
Object.entries(advancedFilters).map((entry) => {
|
||||
if (entry[1].length) {
|
||||
advancedParams = advancedParams + `&${entry[0]}=${entry[1]}`
|
||||
}
|
||||
})
|
||||
return advancedParams
|
||||
}, [advancedFilters])
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
return !params.length || params.length === 6
|
||||
? advancedParamsString
|
||||
: `&activity-type=${params.toString()}${advancedParamsString}`
|
||||
}, [advancedParamsString, params])
|
||||
|
||||
return !showActivityDetail ? (
|
||||
<>
|
||||
<ActivityFilters
|
||||
filters={params}
|
||||
setFilters={setParams}
|
||||
params={queryParams}
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
/>
|
||||
<ActivityFeedTable
|
||||
activityFeed={activityFeed}
|
||||
handleShowActivityDetails={handleShowActivityDetails}
|
||||
params={queryParams}
|
||||
/>
|
||||
</>
|
||||
<ActivityFeedTable
|
||||
activityFeed={activityFeed}
|
||||
handleShowActivityDetails={handleShowActivityDetails}
|
||||
/>
|
||||
) : (
|
||||
<ActivityDetails
|
||||
activity={showActivityDetail}
|
||||
|
@ -111,269 +34,6 @@ const ActivityFeed = () => {
|
|||
|
||||
export default ActivityFeed
|
||||
|
||||
const ActivityFilters = ({
|
||||
filters,
|
||||
setFilters,
|
||||
params,
|
||||
advancedFilters,
|
||||
setAdvancedFilters,
|
||||
}: {
|
||||
filters: string[]
|
||||
setFilters: (x: string[]) => void
|
||||
params: string
|
||||
advancedFilters: AdvancedFilters
|
||||
setAdvancedFilters: (x: AdvancedFilters) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const actions = mangoStore.getState().actions
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [showMobileFilters, setShowMobileFilters] = useState(false)
|
||||
const [hasFilters, setHasFilters] = useState(false)
|
||||
|
||||
const handleUpdateResults = useCallback(() => {
|
||||
const set = mangoStore.getState().set
|
||||
if (params) {
|
||||
setHasFilters(true)
|
||||
} else {
|
||||
setHasFilters(false)
|
||||
}
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress, 0, params)
|
||||
}
|
||||
}, [actions, params, mangoAccountAddress])
|
||||
|
||||
const handleResetFilters = useCallback(async () => {
|
||||
const set = mangoStore.getState().set
|
||||
setHasFilters(false)
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccountAddress) {
|
||||
await actions.fetchActivityFeed(mangoAccountAddress)
|
||||
setAdvancedFilters(DEFAULT_ADVANCED_FILTERS)
|
||||
setFilters(DEFAULT_PARAMS)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const handleUpdateMobileResults = () => {
|
||||
handleUpdateResults()
|
||||
setShowMobileFilters(false)
|
||||
}
|
||||
|
||||
return mangoAccountAddress ? (
|
||||
<Disclosure>
|
||||
<div className="relative">
|
||||
{hasFilters ? (
|
||||
<div className="absolute right-14 top-2">
|
||||
<Tooltip content={t('activity:reset-filters')}>
|
||||
<IconButton
|
||||
className={`${loadActivityFeed ? 'animate-spin' : ''}`}
|
||||
onClick={() => handleResetFilters()}
|
||||
size="small"
|
||||
>
|
||||
<ArrowPathIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
onClick={() => setShowMobileFilters(!showMobileFilters)}
|
||||
role="button"
|
||||
className={`default-transition h-12 w-full bg-th-bkg-2 px-4 hover:bg-th-bkg-3 md:px-6`}
|
||||
>
|
||||
<Disclosure.Button
|
||||
className={`flex h-full w-full items-center justify-between rounded-none`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<AdjustmentsVerticalIcon className="h-5 w-5 text-th-fgd-4" />
|
||||
<span className="font-bold text-th-fgd-1">
|
||||
{t('activity:filter-results')}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
showMobileFilters ? 'rotate-180' : 'rotate-360'
|
||||
} h-6 w-6 flex-shrink-0`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
</div>
|
||||
<Disclosure.Panel className="bg-th-bkg-2 px-6 pb-6">
|
||||
<FiltersForm
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
filters={filters}
|
||||
setFilters={setFilters}
|
||||
/>
|
||||
<Button
|
||||
className="w-full md:w-auto"
|
||||
size="large"
|
||||
onClick={handleUpdateMobileResults}
|
||||
>
|
||||
{t('activity:update')}
|
||||
</Button>
|
||||
</Disclosure.Panel>
|
||||
</Disclosure>
|
||||
) : null
|
||||
}
|
||||
|
||||
interface FiltersFormProps {
|
||||
advancedFilters: any
|
||||
setAdvancedFilters: (x: any) => void
|
||||
filters: string[]
|
||||
setFilters: (x: string[]) => void
|
||||
}
|
||||
|
||||
const FiltersForm = ({
|
||||
advancedFilters,
|
||||
setAdvancedFilters,
|
||||
filters,
|
||||
setFilters,
|
||||
}: FiltersFormProps) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const { group } = useMangoGroup()
|
||||
const [dateFrom, setDateFrom] = useState<Date | null>(null)
|
||||
const [dateTo, setDateTo] = useState<Date | null>(null)
|
||||
const [valueFrom, setValueFrom] = useState(advancedFilters['usd-lower'] || '')
|
||||
const [valueTo, setValueTo] = useState(advancedFilters['usd-upper'] || '')
|
||||
|
||||
const symbols = useMemo(() => {
|
||||
if (!group) return []
|
||||
return Array.from(group.banksMapByName, ([key]) => key)
|
||||
}, [group])
|
||||
|
||||
useEffect(() => {
|
||||
if (advancedFilters['start-date']) {
|
||||
setDateFrom(new Date(advancedFilters['start-date']))
|
||||
}
|
||||
if (advancedFilters['end-date']) {
|
||||
setDateTo(new Date(advancedFilters['end-date']))
|
||||
}
|
||||
}, [])
|
||||
|
||||
const toggleTypeOption = (option: string) => {
|
||||
if (filters.includes(option)) {
|
||||
setFilters(filters.filter((opt) => opt !== option))
|
||||
} else {
|
||||
setFilters(filters.concat(option))
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSymbolOption = (option: string) => {
|
||||
setAdvancedFilters((prevSelected: any) => {
|
||||
const newSelections = prevSelected.symbol ? [...prevSelected.symbol] : []
|
||||
if (newSelections.includes(option)) {
|
||||
return {
|
||||
...prevSelected,
|
||||
symbol: newSelections.filter((item) => item !== option),
|
||||
}
|
||||
} else {
|
||||
newSelections.push(option)
|
||||
return { ...prevSelected, symbol: newSelections }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (dateFrom && dateTo) {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'start-date': dayjs(dateFrom).set('hour', 0).toISOString(),
|
||||
'end-date': dayjs(dateTo)
|
||||
.set('hour', 23)
|
||||
.set('minute', 59)
|
||||
.toISOString(),
|
||||
})
|
||||
} else {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'start-date': '',
|
||||
'end-date': '',
|
||||
})
|
||||
}
|
||||
}, [dateFrom, dateTo])
|
||||
|
||||
useEffect(() => {
|
||||
if (valueFrom && valueTo) {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'usd-lower': Math.floor(valueFrom),
|
||||
'usd-upper': Math.ceil(valueTo),
|
||||
})
|
||||
} else {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'usd-lower': '',
|
||||
'usd-upper': '',
|
||||
})
|
||||
}
|
||||
}, [valueFrom, valueTo])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-x-8 pt-4">
|
||||
<div className="col-span-2 lg:col-span-1">
|
||||
<Label text={t('activity:activity-type')} />
|
||||
<MultiSelectDropdown
|
||||
options={DEFAULT_PARAMS}
|
||||
selected={filters}
|
||||
toggleOption={toggleTypeOption}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 lg:col-span-1">
|
||||
<Label text={t('tokens')} />
|
||||
<MultiSelectDropdown
|
||||
options={symbols}
|
||||
selected={advancedFilters.symbol || []}
|
||||
toggleOption={toggleSymbolOption}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 w-full">
|
||||
<MangoDateRangePicker
|
||||
startDate={dateFrom}
|
||||
setStartDate={setDateFrom}
|
||||
endDate={dateTo}
|
||||
setEndDate={setDateTo}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end pb-6">
|
||||
<div className="w-full">
|
||||
<Label text={t('activity:value-from')} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueFrom}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValueFrom(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-12 items-center justify-center">
|
||||
<ChevronRightIcon className="mx-1 h-5 w-5 flex-shrink-0 text-th-fgd-3" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Label text={t('activity:value-to')} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueTo || ''}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValueTo(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ActivityDetails = ({
|
||||
activity,
|
||||
setShowActivityDetail,
|
||||
|
|
|
@ -34,16 +34,15 @@ const formatFee = (value: number) => {
|
|||
const ActivityFeedTable = ({
|
||||
activityFeed,
|
||||
handleShowActivityDetails,
|
||||
params,
|
||||
}: {
|
||||
activityFeed: any
|
||||
handleShowActivityDetails: (x: LiquidationFeedItem) => void
|
||||
params: string
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const actions = mangoStore.getState().actions
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
const queryParams = mangoStore((s) => s.activityFeed.queryParams)
|
||||
const [offset, setOffset] = useState(0)
|
||||
const { connected } = useWallet()
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
|
@ -63,9 +62,9 @@ const ActivityFeedTable = ({
|
|||
actions.fetchActivityFeed(
|
||||
mangoAccountAddress,
|
||||
offset + PAGINATION_PAGE_LENGTH,
|
||||
params
|
||||
queryParams
|
||||
)
|
||||
}, [actions, offset, params, mangoAccountAddress])
|
||||
}, [actions, offset, queryParams, mangoAccountAddress])
|
||||
|
||||
const getCreditAndDebit = (activity: any) => {
|
||||
const { activity_type } = activity
|
||||
|
@ -197,7 +196,7 @@ const ActivityFeedTable = ({
|
|||
<>
|
||||
{showTableView ? (
|
||||
<Table className="min-w-full">
|
||||
<thead className="sticky top-0 z-10">
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="bg-th-bkg-1 text-left">{t('date')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
import MangoDateRangePicker from '@components/forms/DateRangePicker'
|
||||
import Input from '@components/forms/Input'
|
||||
import Label from '@components/forms/Label'
|
||||
import MultiSelectDropdown from '@components/forms/MultiSelectDropdown'
|
||||
import Button, { IconButton } from '@components/shared/Button'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import {
|
||||
AdjustmentsVerticalIcon,
|
||||
ArrowPathIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import dayjs from 'dayjs'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
interface AdvancedFilters {
|
||||
symbol: string[]
|
||||
'start-date': string
|
||||
'end-date': string
|
||||
'usd-lower': string
|
||||
'usd-upper': string
|
||||
}
|
||||
|
||||
const DEFAULT_ADVANCED_FILTERS = {
|
||||
symbol: [],
|
||||
'start-date': '',
|
||||
'end-date': '',
|
||||
'usd-lower': '',
|
||||
'usd-upper': '',
|
||||
}
|
||||
|
||||
const DEFAULT_PARAMS = [
|
||||
'deposit',
|
||||
'perp_trade',
|
||||
'liquidate_token_with_token',
|
||||
'openbook_trade',
|
||||
'swap',
|
||||
'withdraw',
|
||||
]
|
||||
|
||||
const ActivityFilters = () => {
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilters>(
|
||||
DEFAULT_ADVANCED_FILTERS
|
||||
)
|
||||
const [params, setParams] = useState<string[]>(DEFAULT_PARAMS)
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
const [showFilters, setShowFilters] = useState(false)
|
||||
const [hasFilters, setHasFilters] = useState(false)
|
||||
|
||||
const advancedParamsString = useMemo(() => {
|
||||
let advancedParams = ''
|
||||
Object.entries(advancedFilters).map((entry) => {
|
||||
if (entry[1].length) {
|
||||
advancedParams = advancedParams + `&${entry[0]}=${entry[1]}`
|
||||
}
|
||||
})
|
||||
return advancedParams
|
||||
}, [advancedFilters])
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
return !params.length || params.length === 6
|
||||
? advancedParamsString
|
||||
: `&activity-type=${params.toString()}${advancedParamsString}`
|
||||
}, [advancedParamsString, params])
|
||||
|
||||
useEffect(() => {
|
||||
const set = mangoStore.getState().set
|
||||
if (queryParams.length) {
|
||||
set((state) => {
|
||||
state.activityFeed.queryParams = queryParams
|
||||
})
|
||||
} else {
|
||||
set((state) => {
|
||||
state.activityFeed.queryParams = ''
|
||||
})
|
||||
}
|
||||
}, [queryParams])
|
||||
|
||||
const handleUpdateResults = useCallback(() => {
|
||||
const set = mangoStore.getState().set
|
||||
if (queryParams) {
|
||||
setHasFilters(true)
|
||||
} else {
|
||||
setHasFilters(false)
|
||||
}
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress, 0, queryParams)
|
||||
}
|
||||
}, [actions, queryParams, mangoAccountAddress])
|
||||
|
||||
const handleResetFilters = useCallback(async () => {
|
||||
const set = mangoStore.getState().set
|
||||
setHasFilters(false)
|
||||
setShowFilters(false)
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccountAddress) {
|
||||
await actions.fetchActivityFeed(mangoAccountAddress)
|
||||
setAdvancedFilters(DEFAULT_ADVANCED_FILTERS)
|
||||
setParams(DEFAULT_PARAMS)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const handleUpdateMobileResults = () => {
|
||||
handleUpdateResults()
|
||||
setShowFilters(false)
|
||||
}
|
||||
|
||||
return mangoAccountAddress ? (
|
||||
<Disclosure>
|
||||
<div className="flex items-center">
|
||||
{hasFilters ? (
|
||||
<Tooltip content={t('activity:reset-filters')}>
|
||||
<IconButton
|
||||
className={`${loadActivityFeed ? 'animate-spin' : ''}`}
|
||||
onClick={() => handleResetFilters()}
|
||||
size="small"
|
||||
>
|
||||
<ArrowPathIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<div
|
||||
onClick={() => setShowFilters(!showFilters)}
|
||||
role="button"
|
||||
className={`default-transition mr-4 ml-3 rounded-md border border-th-button px-2 py-1.5 md:mr-6 md:hover:border-th-button-hover`}
|
||||
>
|
||||
<Disclosure.Button className="flex h-full w-full items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<AdjustmentsVerticalIcon className="hidden h-5 w-5 text-th-fgd-4 sm:block" />
|
||||
<span className="text-sm font-medium text-th-fgd-1">
|
||||
{t('activity:filter-results')}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
showFilters ? 'rotate-180' : 'rotate-360'
|
||||
} ml-1.5 h-5 w-5 flex-shrink-0`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
</div>
|
||||
{showFilters ? (
|
||||
<Disclosure.Panel
|
||||
className="absolute top-14 z-10 w-full border-t border-th-bkg-3 bg-th-bkg-2 px-6 pb-6 shadow-md"
|
||||
static
|
||||
>
|
||||
<FiltersForm
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
filters={params}
|
||||
setFilters={setParams}
|
||||
/>
|
||||
<Button
|
||||
className="w-full md:w-auto"
|
||||
size="large"
|
||||
onClick={handleUpdateMobileResults}
|
||||
>
|
||||
{t('activity:update')}
|
||||
</Button>
|
||||
</Disclosure.Panel>
|
||||
) : null}
|
||||
</Disclosure>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default ActivityFilters
|
||||
|
||||
interface FiltersFormProps {
|
||||
advancedFilters: any
|
||||
setAdvancedFilters: (x: any) => void
|
||||
filters: string[]
|
||||
setFilters: (x: string[]) => void
|
||||
}
|
||||
|
||||
const FiltersForm = ({
|
||||
advancedFilters,
|
||||
setAdvancedFilters,
|
||||
filters,
|
||||
setFilters,
|
||||
}: FiltersFormProps) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const { group } = useMangoGroup()
|
||||
const [dateFrom, setDateFrom] = useState<Date | null>(null)
|
||||
const [dateTo, setDateTo] = useState<Date | null>(null)
|
||||
const [valueFrom, setValueFrom] = useState(advancedFilters['usd-lower'] || '')
|
||||
const [valueTo, setValueTo] = useState(advancedFilters['usd-upper'] || '')
|
||||
|
||||
const symbols = useMemo(() => {
|
||||
if (!group) return []
|
||||
return Array.from(group.banksMapByName, ([key]) => key)
|
||||
}, [group])
|
||||
|
||||
useEffect(() => {
|
||||
if (advancedFilters['start-date']) {
|
||||
setDateFrom(new Date(advancedFilters['start-date']))
|
||||
}
|
||||
if (advancedFilters['end-date']) {
|
||||
setDateTo(new Date(advancedFilters['end-date']))
|
||||
}
|
||||
}, [])
|
||||
|
||||
const toggleTypeOption = (option: string) => {
|
||||
if (filters.includes(option)) {
|
||||
setFilters(filters.filter((opt) => opt !== option))
|
||||
} else {
|
||||
setFilters(filters.concat(option))
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSymbolOption = (option: string) => {
|
||||
setAdvancedFilters((prevSelected: any) => {
|
||||
const newSelections = prevSelected.symbol ? [...prevSelected.symbol] : []
|
||||
if (newSelections.includes(option)) {
|
||||
return {
|
||||
...prevSelected,
|
||||
symbol: newSelections.filter((item) => item !== option),
|
||||
}
|
||||
} else {
|
||||
newSelections.push(option)
|
||||
return { ...prevSelected, symbol: newSelections }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (dateFrom && dateTo) {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'start-date': dayjs(dateFrom).set('hour', 0).toISOString(),
|
||||
'end-date': dayjs(dateTo)
|
||||
.set('hour', 23)
|
||||
.set('minute', 59)
|
||||
.toISOString(),
|
||||
})
|
||||
} else {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'start-date': '',
|
||||
'end-date': '',
|
||||
})
|
||||
}
|
||||
}, [dateFrom, dateTo])
|
||||
|
||||
useEffect(() => {
|
||||
if (valueFrom && valueTo) {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'usd-lower': Math.floor(valueFrom),
|
||||
'usd-upper': Math.ceil(valueTo),
|
||||
})
|
||||
} else {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'usd-lower': '',
|
||||
'usd-upper': '',
|
||||
})
|
||||
}
|
||||
}, [valueFrom, valueTo])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-x-8 pt-4">
|
||||
<div className="col-span-2 mb-4 lg:col-span-1 lg:mb-0">
|
||||
<Label text={t('activity:activity-type')} />
|
||||
<MultiSelectDropdown
|
||||
options={DEFAULT_PARAMS}
|
||||
selected={filters}
|
||||
toggleOption={toggleTypeOption}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 lg:col-span-1">
|
||||
<Label text={t('tokens')} />
|
||||
<MultiSelectDropdown
|
||||
options={symbols}
|
||||
selected={advancedFilters.symbol || []}
|
||||
toggleOption={toggleSymbolOption}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 w-full">
|
||||
<MangoDateRangePicker
|
||||
startDate={dateFrom}
|
||||
setStartDate={setDateFrom}
|
||||
endDate={dateTo}
|
||||
setEndDate={setDateTo}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end pb-6">
|
||||
<div className="w-full">
|
||||
<Label text={t('activity:value-from')} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueFrom}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValueFrom(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-12 items-center justify-center">
|
||||
<ChevronRightIcon className="mx-1 h-5 w-5 flex-shrink-0 text-th-fgd-3" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Label text={t('activity:value-to')} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueTo || ''}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValueTo(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import SwapHistoryTable from '../swap/SwapHistoryTable'
|
||||
import ActivityFeed from './ActivityFeed'
|
||||
import TradeHistory from '@components/trade/TradeHistory'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import ActivityFilters from './ActivityFilters'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
const TABS = ['activity:activity', 'activity:swaps', 'activity:trades']
|
||||
|
||||
const HistoryTabs = () => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const [activeTab, setActiveTab] = useState('activity:activity')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
|
||||
useEffect(() => {
|
||||
if (actions && mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress)
|
||||
}
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex h-14 items-center justify-between bg-th-bkg-2">
|
||||
<div className="hide-scroll flex space-x-2 pl-4 md:pl-6">
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
className={`default-transition rounded-md py-1.5 px-2.5 text-sm font-medium md:hover:bg-th-bkg-4 ${
|
||||
activeTab === tab
|
||||
? 'bg-th-bkg-4 text-th-active'
|
||||
: 'bg-th-bkg-3 text-th-fgd-3'
|
||||
}`}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
key={tab}
|
||||
>
|
||||
{t(tab)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{activeTab === 'activity:activity' ? <ActivityFilters /> : null}
|
||||
</div>
|
||||
<TabContent activeTab={activeTab} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const TabContent = ({ activeTab }: { activeTab: string }) => {
|
||||
switch (activeTab) {
|
||||
case 'activity:activity':
|
||||
return <ActivityFeed />
|
||||
case 'activity:swaps':
|
||||
return <SwapHistoryTable />
|
||||
case 'activity:trades':
|
||||
return <TradeHistory />
|
||||
default:
|
||||
return <ActivityFeed />
|
||||
}
|
||||
}
|
||||
|
||||
export default HistoryTabs
|
|
@ -25,7 +25,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
|
||||
const Index: NextPage = () => {
|
||||
return (
|
||||
<div className="pb-20 md:pb-0">
|
||||
<div className="min-h-[calc(100vh-64px)] pb-20 md:pb-0">
|
||||
<AccountPage />
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"activity": "Activity",
|
||||
"activity": "Activity Feed",
|
||||
"activity-type": "Activity Type",
|
||||
"activity-value": "Activity Value",
|
||||
"advanced-filters": "Advanced Filters",
|
||||
|
@ -10,7 +10,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter Results",
|
||||
"filter-results": "Filter",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
|
||||
"trades": "Trades",
|
||||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
|
||||
"history": "History",
|
||||
"insufficient-sol": "Solana requires 0.0432 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
|
|
@ -236,6 +236,7 @@ export type MangoStore = {
|
|||
activityFeed: {
|
||||
feed: Array<DepositWithdrawFeedItem | LiquidationFeedItem>
|
||||
loading: boolean
|
||||
queryParams: string
|
||||
}
|
||||
connected: boolean
|
||||
connection: Connection
|
||||
|
@ -381,6 +382,7 @@ const mangoStore = create<MangoStore>()(
|
|||
activityFeed: {
|
||||
feed: [],
|
||||
loading: true,
|
||||
queryParams: '',
|
||||
},
|
||||
connected: false,
|
||||
connection,
|
||||
|
@ -589,11 +591,8 @@ const mangoStore = create<MangoStore>()(
|
|||
set((state) => {
|
||||
state.activityFeed.feed = combinedFeed
|
||||
})
|
||||
} catch {
|
||||
notify({
|
||||
title: 'Failed to fetch account activity feed',
|
||||
type: 'error',
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('Failed to fetch account activity feed', e)
|
||||
} finally {
|
||||
set((state) => {
|
||||
state.activityFeed.loading = false
|
||||
|
|
Loading…
Reference in New Issue