Skip to content

Commit 1b5cfeb

Browse files
authored
Merge pull request #1956 from streamr-dev/FRONT-1933-collect-all
[FRONT-1933] Add collect all button
2 parents c254bd4 + 4b31406 commit 1b5cfeb

File tree

4 files changed

+216
-184
lines changed

4 files changed

+216
-184
lines changed

src/hooks/operators.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -585,8 +585,8 @@ export function useCollectEarnings() {
585585
const { fetch: fetchUncollectedEarnings } = useUncollectedEarningsStore()
586586

587587
return useCallback(
588-
(params: { chainId: number; sponsorshipId: string; operatorId: string }) => {
589-
const { chainId, sponsorshipId, operatorId } = params
588+
(params: { chainId: number; sponsorshipIds: string[]; operatorId: string }) => {
589+
const { chainId, sponsorshipIds, operatorId } = params
590590

591591
void (async () => {
592592
try {
@@ -606,7 +606,7 @@ export function useCollectEarnings() {
606606
return
607607
}
608608

609-
await collectEarnings(chainId, sponsorshipId, operatorId, {
609+
await collectEarnings(chainId, sponsorshipIds, operatorId, {
610610
onReceipt: ({ blockNumber }) =>
611611
waitForIndexedBlock(chainId, blockNumber),
612612
})
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import React from 'react'
2+
import moment from 'moment'
3+
import styled from 'styled-components'
4+
import { SponsorshipDecimals } from '~/components/Decimals'
5+
import Spinner from '~/components/Spinner'
6+
import { FundedUntilCell, StreamIdCell } from '~/components/Table'
7+
import { Tooltip, TooltipIconWrap } from '~/components/Tooltip'
8+
import { Operator } from '~/parsers/Operator'
9+
import { ScrollTable } from '~/shared/components/ScrollTable/ScrollTable'
10+
import SvgIcon from '~/shared/components/SvgIcon'
11+
import { Route as R, routeOptions } from '~/utils/routes'
12+
import {
13+
useCanCollectEarningsCallback,
14+
useUncollectedEarnings,
15+
} from '~/shared/stores/uncollectedEarnings'
16+
import { useCollectEarnings } from '~/hooks/operators'
17+
import { useEditSponsorshipFunding } from '~/hooks/sponsorships'
18+
import { Button } from '~/components/Button'
19+
import { useCurrentChainId, useCurrentChainSymbolicName } from '~/utils/chains'
20+
21+
export function SponsorshipTable({
22+
operator,
23+
isController,
24+
}: {
25+
operator: Operator
26+
isController: boolean
27+
}) {
28+
const canCollect = useCanCollectEarningsCallback()
29+
30+
const editSponsorshipFunding = useEditSponsorshipFunding()
31+
32+
const collectEarnings = useCollectEarnings()
33+
34+
const currentChainId = useCurrentChainId()
35+
36+
const chainName = useCurrentChainSymbolicName()
37+
38+
const allSponsorshipIds = operator.stakes.map(({ sponsorshipId }) => sponsorshipId)
39+
40+
const canCollectAllEarnings = allSponsorshipIds.every((sponsorshipId) =>
41+
canCollect(operator.id, sponsorshipId),
42+
)
43+
44+
return (
45+
<ScrollTable
46+
elements={operator.stakes}
47+
columns={[
48+
{
49+
displayName: 'Stream ID',
50+
valueMapper: ({ streamId }) => <StreamIdCell streamId={streamId} />,
51+
align: 'start',
52+
isSticky: true,
53+
key: 'streamId',
54+
},
55+
{
56+
displayName: 'Staked',
57+
valueMapper: (element) => {
58+
const minimumStakeReachTime = moment(
59+
element.joinedAt.getTime() +
60+
element.minimumStakingPeriodSeconds * 1000,
61+
)
62+
63+
return (
64+
<>
65+
<SponsorshipDecimals abbr amount={element.amountWei} />
66+
{minimumStakeReachTime.isAfter(Date.now()) && (
67+
<Tooltip
68+
content={
69+
<>
70+
Minimum stake period:{' '}
71+
{minimumStakeReachTime.fromNow(true)} left
72+
</>
73+
}
74+
>
75+
<TooltipIconWrap
76+
className="ml-1"
77+
$color="#ADADAD"
78+
$svgSize={{
79+
width: '18px',
80+
height: '18px',
81+
}}
82+
>
83+
<SvgIcon name="lockClosed" />
84+
</TooltipIconWrap>
85+
</Tooltip>
86+
)}
87+
</>
88+
)
89+
},
90+
align: 'start',
91+
isSticky: false,
92+
key: 'staked',
93+
},
94+
{
95+
displayName: 'APY',
96+
valueMapper: (element) =>
97+
`${element.spotAPY.multipliedBy(100).toFixed(0)}%`,
98+
align: 'start',
99+
isSticky: false,
100+
key: 'apy',
101+
},
102+
{
103+
displayName: 'Funded until',
104+
valueMapper: (element) => (
105+
<FundedUntilCell
106+
projectedInsolvencyAt={element.projectedInsolvencyAt}
107+
remainingBalance={element.remainingWei}
108+
/>
109+
),
110+
align: 'start',
111+
isSticky: false,
112+
key: 'fundedUntil',
113+
},
114+
{
115+
displayName: 'Uncollected earnings',
116+
valueMapper: (element) => (
117+
<UncollectedEarnings
118+
operatorId={operator.id}
119+
sponsorshipId={element.sponsorshipId}
120+
/>
121+
),
122+
align: 'end',
123+
isSticky: false,
124+
key: 'earnings',
125+
},
126+
]}
127+
linkMapper={({ sponsorshipId: id }) =>
128+
R.sponsorship(id, routeOptions(chainName))
129+
}
130+
actions={[
131+
(element) => ({
132+
displayName: 'Edit',
133+
disabled: !isController,
134+
async callback() {
135+
if (!operator) {
136+
return
137+
}
138+
139+
editSponsorshipFunding({
140+
chainId: currentChainId,
141+
sponsorshipOrSponsorshipId: element.sponsorshipId,
142+
operator,
143+
})
144+
},
145+
}),
146+
(element) => ({
147+
displayName: 'Collect earnings',
148+
callback() {
149+
if (!operator.id) {
150+
return
151+
}
152+
153+
collectEarnings({
154+
chainId: currentChainId,
155+
operatorId: operator.id,
156+
sponsorshipIds: [element.sponsorshipId],
157+
})
158+
},
159+
disabled: !canCollect(operator.id, element.sponsorshipId),
160+
}),
161+
]}
162+
footerComponent={
163+
canCollectAllEarnings && (
164+
<Footer>
165+
<Button
166+
kind="secondary"
167+
onClick={async () => {
168+
collectEarnings({
169+
chainId: currentChainId,
170+
operatorId: operator.id,
171+
sponsorshipIds: allSponsorshipIds,
172+
})
173+
}}
174+
>
175+
Collect all
176+
</Button>
177+
</Footer>
178+
)
179+
}
180+
/>
181+
)
182+
}
183+
184+
const Footer = styled.div`
185+
display: flex;
186+
justify-content: right;
187+
padding: 32px;
188+
gap: 10px;
189+
`
190+
191+
function UncollectedEarnings({
192+
operatorId,
193+
sponsorshipId,
194+
}: {
195+
operatorId: string | undefined
196+
sponsorshipId: string
197+
}) {
198+
const value = useUncollectedEarnings(operatorId, sponsorshipId)
199+
200+
return typeof value !== 'undefined' ? (
201+
<SponsorshipDecimals abbr amount={value?.uncollectedEarnings || 0n} />
202+
) : (
203+
<Spinner color="blue" />
204+
)
205+
}

0 commit comments

Comments
 (0)