Skip to content

Commit 029678e

Browse files
committed
Display one redeem button to all redeemable REP ; show total REP that was redeemed ; many code improvements
1 parent 2b8041d commit 029678e

File tree

6 files changed

+138
-109
lines changed

6 files changed

+138
-109
lines changed

src/components/Scheme/CL4R/CL4R.scss

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,30 @@
77
width: 70%;
88
display: flex;
99
flex-direction: column;
10-
.currentPeriod{
11-
font-size: $title;
12-
}
13-
.nextPeriod {
14-
margin-bottom: 20px;
15-
color: $gray-label;
16-
}
17-
.nextPeriod * {
18-
color: $gray-label;
10+
.top {
11+
display: flex;
12+
justify-content: space-between;
13+
.countersWrapper {
14+
display: flex;
15+
flex-direction: column;
16+
.currentPeriod{
17+
font-size: $title;
18+
}
19+
.nextPeriod {
20+
margin-bottom: 20px;
21+
color: $gray-label;
22+
}
23+
.nextPeriod * {
24+
color: $gray-label;
25+
}
26+
}
27+
.redeemWrapper {
28+
display: flex;
29+
flex-direction: column;
30+
.redeemedAmountLabel {
31+
color: $gray-label;
32+
}
33+
}
1934
}
2035
.tableTitleWrapper {
2136
margin-bottom: 10px;
@@ -85,21 +100,21 @@
85100
.releasableLable {
86101
margin-top: 20px;
87102
}
88-
.lockButton {
89-
border: none;
90-
background-color: $accent-1;
91-
color: white;
92-
border-radius: 30px;
93-
font-weight: bold;
94-
margin: 10px 0px;
95-
height: 30px;
96-
&:hover {
97-
opacity: 0.8;
98-
}
99-
&.disabled {
100-
pointer-events: none;
101-
background-color: $gray-1;
102-
}
103+
}
104+
.actionButton {
105+
border: none;
106+
background-color: $accent-1;
107+
color: white;
108+
border-radius: 30px;
109+
font-weight: bold;
110+
margin: 10px 0px;
111+
height: 30px;
112+
&:hover {
113+
opacity: 0.8;
114+
}
115+
&.disabled {
116+
pointer-events: none;
117+
background-color: $gray-1;
103118
}
104119
}
105120
}

src/components/Scheme/CL4R/CL4R.tsx

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as css from "./CL4R.scss";
33
import gql from "graphql-tag";
44
import Loading from "components/Shared/Loading";
55
import withSubscription, { ISubscriptionProps } from "components/Shared/withSubscription";
6-
import { getArcByDAOAddress, standardPolling, getNetworkByDAOAddress, toWei, ethErrorHandler, fromWei } from "lib/util";
6+
import { getArcByDAOAddress, standardPolling, getNetworkByDAOAddress, toWei, ethErrorHandler, fromWei, formatTokens } from "lib/util";
77
import { Address, CL4RScheme, IDAOState, ISchemeState, Token } from "@daostack/arc.js";
88
import { RouteComponentProps } from "react-router-dom";
99
import LockRow from "./LockRow";
@@ -18,7 +18,7 @@ import { lock, releaseLocking, extendLocking, redeemLocking, approveTokens } fro
1818
import { showNotification } from "@store/notifications/notifications.reducer";
1919
import { connect } from "react-redux";
2020
import Tooltip from "rc-tooltip";
21-
import { getCL4RParams, ICL4RParams } from "./CL4RHelper";
21+
import { calculateTotalRedeemedAmount, getCL4RParams, ICL4RParams, secondsToDays } from "./CL4RHelper";
2222
import BN from "bn.js";
2323

2424
interface IDispatchProps {
@@ -58,27 +58,25 @@ const CL4R = (props: IProps) => {
5858
const [cl4rScheme, setCL4RScheme] = React.useState<CL4RScheme>();
5959
const [isLocking, setIsLocking] = React.useState(false);
6060
const [isApprovingToken, setIsApprovingToken] = React.useState(false);
61+
const [isRedeeming, setIsRedeeming] = React.useState(false);
6162
const [currentTime, setCurrentTime] = React.useState(moment().unix());
6263
const isAllowance = data[1]?.gt(new BN(0));
6364
const isEnoughBalance = fromWei(data[2]) >= lockAmount;
6465
const cl4Rlocks = (data as any)[0].data.cl4Rlocks;
65-
66-
const getLockingBatch = React.useCallback((lockingTime: number, startTime: number, batchTime: number): number => {
67-
const timeElapsed = lockingTime - startTime;
68-
return Math.trunc(timeElapsed / batchTime);
69-
}, [schemeParams]);
66+
const [redeemableAmount, setRedeemableAmount] = React.useState(0);
67+
const [allLockingIdsForRedeem, setAllLockingIdsForRedeem] = React.useState<Set<number>>(new Set());
7068

7169
const isLockingStarted = React.useMemo(() => {
7270
return (moment().isAfter(moment.unix(Number(schemeParams.startTime))));
7371
}, [schemeParams]);
7472

7573
const endTime = React.useMemo(() => {
76-
return Number(schemeParams.startTime) + (Number(schemeParams.batchTime) * Number(schemeParams.batchesIndexCap));
74+
return Number(schemeParams.startTime) + (schemeParams.batchTime * schemeParams.batchesIndexCap);
7775
}, [schemeParams]);
7876

7977
const isLockingEnded = React.useMemo(() => {
8078
return (moment().isAfter(moment.unix(endTime)));
81-
}, [schemeParams]);
79+
}, [schemeParams, currentTime]);
8280

8381
const handleRelease = React.useCallback(async (lockingId: number, setIsReleasing: any) => {
8482
if (!await enableWalletProvider({ showNotification: props.showNotification }, getNetworkByDAOAddress(daoState.address))) { return; }
@@ -90,9 +88,9 @@ const CL4R = (props: IProps) => {
9088
props.extendLocking(cl4rScheme, extendPeriod, batchIndexToLockIn, lockingId, schemeParams.agreementHash, setIsExtending);
9189
}, [cl4rScheme, schemeParams]);
9290

93-
const handleRedeem = React.useCallback(async (lockingId: number[], setIsRedeeming: any) => {
91+
const handleRedeem = React.useCallback(async (lockingIds: number[], setIsRedeeming: any) => {
9492
if (!await enableWalletProvider({ showNotification: props.showNotification }, getNetworkByDAOAddress(daoState.address))) { return; }
95-
props.redeemLocking(cl4rScheme, props.currentAccountAddress, lockingId, setIsRedeeming);
93+
props.redeemLocking(cl4rScheme, props.currentAccountAddress, lockingIds, setIsRedeeming);
9694
}, [cl4rScheme, schemeParams]);
9795

9896
const handleTokenApproving = React.useCallback(async () => {
@@ -111,17 +109,22 @@ const CL4R = (props: IProps) => {
111109
getSchemeInfo();
112110
}, []);
113111

112+
const redeemedAmount = React.useMemo(() => {
113+
return calculateTotalRedeemedAmount(cl4Rlocks);
114+
}, [schemeParams, cl4Rlocks]);
115+
114116
const durations = [] as any;
115-
for (let duration = 1; duration <= Number(schemeParams.maxLockingBatches); duration++) {
116-
if (moment().unix() + (duration * Number(schemeParams.batchTime)) <= endTime) {
117+
for (let duration = 1; duration <= schemeParams.maxLockingBatches; duration++) {
118+
if (moment().unix() + (duration * schemeParams.batchTime) <= endTime) {
117119
durations.push(<option key={duration} value={duration} selected={duration === 1}>{duration}</option>);
118120
}
119121
}
120122

121123
const startTime = Number(schemeParams.startTime);
122124
const timeElapsed = currentTime - startTime;
123-
const currentLockingBatch = isLockingEnded ? Number(schemeParams.batchesIndexCap) : Math.trunc(timeElapsed / Number(schemeParams.batchTime));
124-
const nextBatchStartTime = moment.unix(startTime + ((currentLockingBatch + 1) * Number(schemeParams.batchTime)));
125+
const currentLockingBatch = isLockingEnded ? schemeParams.batchesIndexCap : Math.trunc(timeElapsed / schemeParams.batchTime);
126+
const nextBatchStartTime = moment.unix(startTime + ((currentLockingBatch + 1) * schemeParams.batchTime));
127+
const redeemable = moment().isSameOrAfter(moment.unix(Number(schemeParams.redeemEnableTime)));
125128

126129
const handleLock = React.useCallback(async () => {
127130
if (!await enableWalletProvider({ showNotification: props.showNotification }, getNetworkByDAOAddress(daoState.address))) { return; }
@@ -138,10 +141,11 @@ const CL4R = (props: IProps) => {
138141
cl4rScheme={cl4rScheme}
139142
currentLockingBatch={currentLockingBatch}
140143
isLockingEnded={isLockingEnded}
141-
getLockingBatch={getLockingBatch}
142-
handleRedeem={handleRedeem} />);
144+
redeemableAmount={redeemableAmount}
145+
setRedeemableAmount={setRedeemableAmount}
146+
allLockingIdsForRedeem={allLockingIdsForRedeem}
147+
setAllLockingIdsForRedeem={setAllLockingIdsForRedeem} />);
143148
}
144-
145149
if (isLockingEnded) {
146150
periods.splice(-1, 1);
147151
}
@@ -155,7 +159,6 @@ const CL4R = (props: IProps) => {
155159
lockData={lock}
156160
handleRelease={handleRelease}
157161
handleExtend={handleExtend}
158-
getLockingBatch={getLockingBatch}
159162
endTime={endTime}
160163
currentLockingBatch={currentLockingBatch}
161164
isLockingEnded={isLockingEnded} />;
@@ -167,7 +170,7 @@ const CL4R = (props: IProps) => {
167170
prefix = "Starts in";
168171
}
169172

170-
if (currentLockingBatch + 1 === Number(schemeParams.batchesIndexCap)) {
173+
if (currentLockingBatch + 1 === schemeParams.batchesIndexCap) {
171174
prefix = "Ends in";
172175
}
173176

@@ -182,22 +185,38 @@ const CL4R = (props: IProps) => {
182185
});
183186

184187
const lockButtonClass = classNames({
185-
[css.lockButton]: true,
188+
[css.actionButton]: true,
186189
[css.disabled]: !lockAmount || !lockDuration || isLocking || !isEnoughBalance,
187190
});
188191

189192
const approveTokenButtonClass = classNames({
190-
[css.lockButton]: true,
193+
[css.actionButton]: true,
191194
[css.disabled]: isApprovingToken,
192195
});
193196

197+
const actionButtonClass = classNames({
198+
[css.actionButton]: true,
199+
[css.disabled]: isRedeeming || !redeemable || redeemableAmount === 0,
200+
});
201+
194202
return (
195203
!loading ? <div className={css.wrapper}>
196204
<div className={css.leftWrapper}>
197-
<div className={css.currentPeriod}>Current Period: {isLockingEnded ? Number(schemeParams.batchesIndexCap) : currentLockingBatch + 1} of {schemeParams.batchesIndexCap}</div>
198-
<div className={css.nextPeriod}>{isLockingEnded ? "Locking Ended" : <div>{prefix} <Countdown toDate={nextBatchStartTime} onEnd={() => setCurrentTime(moment().unix())} /></div>}</div>
205+
<div className={css.top}>
206+
<div className={css.countersWrapper}>
207+
<div className={css.currentPeriod}>Current Period: {isLockingEnded ? schemeParams.batchesIndexCap : currentLockingBatch + 1} of {schemeParams.batchesIndexCap}</div>
208+
<div className={css.nextPeriod}>{isLockingEnded ? "Locking Ended" : <div>{prefix} <Countdown toDate={nextBatchStartTime} onEnd={() => setCurrentTime(moment().unix())} /></div>}</div>
209+
</div>
210+
<div className={css.redeemWrapper}>
211+
<button
212+
className={actionButtonClass}
213+
onClick={() => handleRedeem(Array.from(allLockingIdsForRedeem), setIsRedeeming)}
214+
disabled={isRedeeming || !redeemable || redeemableAmount === 0}>{`Redeem ${formatTokens(toWei(redeemableAmount))} REP`}</button>
215+
<div className={css.redeemedAmountLabel}>{`Total Redeemed: ${formatTokens(redeemedAmount, "REP")}`}</div>
216+
</div>
217+
</div>
199218
<div className={css.tableTitleWrapper}>
200-
<div className={periodsClass} onClick={() => setShowYourLocks(false)}>All Periods</div>
219+
<div className={periodsClass} onClick={() => { setShowYourLocks(false); setRedeemableAmount(0); }}>All Periods</div>
201220
<div className={locksClass} onClick={() => setShowYourLocks(true)}>Your Locks</div>
202221
</div>
203222
{
@@ -224,7 +243,6 @@ const CL4R = (props: IProps) => {
224243
<th>You Locked</th>
225244
<th>Total Reputation</th>
226245
<th>You Will Receive</th>
227-
<th>Action</th>
228246
</tr>
229247
</thead>
230248
<tbody>
@@ -237,15 +255,15 @@ const CL4R = (props: IProps) => {
237255
<div className={css.lockTitle}>New Lock</div>
238256
<div className={css.lockDurationLabel}>
239257
<span style={{ marginRight: "5px" }}>Lock Duration</span>
240-
<Tooltip trigger={["hover"]} overlay={`Period: ${schemeParams.batchTime} seconds`}><img width="15px" src="/assets/images/Icon/question-help.svg" /></Tooltip>
258+
<Tooltip trigger={["hover"]} overlay={`Period: ${secondsToDays(schemeParams.batchTime).toFixed(2)} days`}><img width="15px" src="/assets/images/Icon/question-help.svg" /></Tooltip>
241259
</div>
242260
<select onChange={(e: any) => setLockDuration(e.target.value)} disabled={!isAllowance}>
243261
{durations}
244262
</select>
245263
<span style={{ marginBottom: "5px" }}>Lock Amount ({schemeParams.tokenSymbol})</span>
246264
<input type="number" onChange={(e: any) => setLockAmount(e.target.value)} disabled={!isAllowance} />
247265
{!isEnoughBalance && <span className={css.lowBalanceLabel}>{`Not enough ${schemeParams.tokenSymbol}!`}</span>}
248-
{<span className={css.releasableLable}>Releasable: {moment().add(lockDuration * Number(schemeParams.batchTime), "seconds").format("DD.MM.YYYY HH:mm")}</span>}
266+
{<span className={css.releasableLable}>Releasable: {moment().add(lockDuration * schemeParams.batchTime, "seconds").format("DD.MM.YYYY HH:mm")}</span>}
249267
{isAllowance && <button onClick={handleLock} className={lockButtonClass} disabled={!lockAmount || !lockDuration}>Lock</button>}
250268
{!isAllowance && <Tooltip trigger={["hover"]} overlay={`Upon activation, the smart contract will be authorized to receive up to 100,000 ${schemeParams.tokenSymbol}`}>
251269
<button onClick={handleTokenApproving} className={approveTokenButtonClass} disabled={isApprovingToken}>Enable Locking</button>

src/components/Scheme/CL4R/CL4RHelper.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
import { getArcByDAOAddress } from "lib/util";
1+
import { formatTokens, getArcByDAOAddress } from "lib/util";
22
import gql from "graphql-tag";
33
import * as React from "react";
44
import moment from "moment-timezone";
55
import { Address } from "@daostack/arc.js";
6+
import BN from "bn.js";
67

78
export interface ICL4RParams {
89
id: Address;
9-
batchTime: string;
10+
batchTime: number;
1011
startTime: string;
1112
token: Address;
1213
redeemEnableTime: string;
1314
tokenName: string;
1415
tokenSymbol: string;
15-
maxLockingBatches: string;
16+
maxLockingBatches: number;
1617
repRewardConstA: string;
1718
repRewardConstB: string;
18-
batchesIndexCap: string;
19+
batchesIndexCap: number;
1920
agreementHash: string;
2021
}
2122

@@ -62,26 +63,42 @@ export const getCL4RParams = async (daoAddress: string, schemeId: string) => {
6263
}
6364
`;
6465
const schemeInfoParams = await arc.sendQuery(schemeInfoQuery);
65-
return schemeInfoParams.data.controllerSchemes[0].continuousLocking4ReputationParams;
66+
const schemeInfoParamsObject = schemeInfoParams.data.controllerSchemes[0].continuousLocking4ReputationParams as ICL4RParams;
67+
schemeInfoParamsObject.batchTime = Number(schemeInfoParamsObject.batchTime);
68+
schemeInfoParamsObject.maxLockingBatches = Number(schemeInfoParamsObject.maxLockingBatches);
69+
schemeInfoParamsObject.batchesIndexCap = Number(schemeInfoParamsObject.batchesIndexCap);
70+
return schemeInfoParamsObject;
71+
};
72+
73+
export const secondsToDays = (seconds: number): number => {
74+
return seconds / 86400;
6675
};
6776

6877
export const renderCL4RParams = (CL4RParams: ICL4RParams) => {
6978
const activationTime = moment.unix(Number(CL4RParams.startTime)).utc();
7079
const redeemEnableTime = moment.unix(Number(CL4RParams.redeemEnableTime)).utc();
71-
const endTime = moment.unix(Number(CL4RParams.startTime) + (Number(CL4RParams.batchTime) * Number(CL4RParams.batchesIndexCap)));
80+
const endTime = moment.unix(Number(CL4RParams.startTime) + (CL4RParams.batchTime * CL4RParams.batchesIndexCap));
7281
return (<React.Fragment>
7382
<div>ID</div><div>{CL4RParams.id}</div>
74-
<div>Token Name</div><div>{CL4RParams.tokenName}</div>
83+
<div>Token</div><div>{`${CL4RParams.token} (${CL4RParams.tokenName})`}</div>
7584
<div>Token Symbol</div><div>{CL4RParams.tokenSymbol}</div>
76-
<div>Token</div><div>{CL4RParams.token}</div>
7785
<div>Start Time</div><div>{activationTime.format("h:mm A [UTC] on MMMM Do, YYYY")} {moment().isSameOrAfter(activationTime) && endTime.isAfter(moment()) ? "(active)" : "(inactive)"}</div>
7886
<div>End Time</div><div>{endTime.format("h:mm A [UTC] on MMMM Do, YYYY")}</div>
7987
<div>Redeem Enable Time</div><div>{`${redeemEnableTime.format("h:mm A [UTC] on MMMM Do, YYYY")} ${redeemEnableTime.isSameOrBefore(moment()) ? "(redeemable)" : "(not redeemable)"}`}</div>
80-
<div>Batch Time</div><div>{CL4RParams.batchTime} seconds</div>
88+
<div>Batch Time</div><div>{`${secondsToDays(CL4RParams.batchTime).toFixed(2)} days`}</div>
8189
<div>Max Locking Batches</div><div>{CL4RParams.maxLockingBatches}</div>
8290
<div>Batches Index Cap</div><div>{CL4RParams.batchesIndexCap}</div>
83-
<div>Reputation Reward Const A</div><div>{CL4RParams.repRewardConstA}</div>
84-
<div>Reputation Reward Const B</div><div>{CL4RParams.repRewardConstB}</div>
91+
<div>Reputation Reward Const A</div><div>{formatTokens(new BN(CL4RParams.repRewardConstA), "REP")}</div>
92+
<div>Reputation Reward Const B</div><div>{formatTokens(new BN(CL4RParams.repRewardConstB))}</div>
8593
<div>Agreement Hash</div><div>{CL4RParams.agreementHash}</div>
8694
</React.Fragment>);
8795
};
96+
97+
export const getLockingBatch = (lockingTime: number, startTime: number, batchTime: number) => {
98+
const timeElapsed = lockingTime - startTime;
99+
return Math.trunc(timeElapsed / batchTime);
100+
};
101+
102+
export const calculateTotalRedeemedAmount = (cl4Rlocks: Array<ICL4RLock>) => {
103+
return cl4Rlocks.map((value: ICL4RLock) => value.redeemed).flat().map((value: ICL4RRedeem) => new BN(value.amount)).reduce((a: BN, b: BN) => a.add(b), new BN(0));
104+
};

src/components/Scheme/CL4R/LockRow.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,35 @@ import { formatTokens, numberWithCommas } from "lib/util";
44
import moment from "moment-timezone";
55
import * as React from "react";
66
import * as css from "./LockRow.scss";
7-
import { ICL4RLock, ICL4RParams } from "./CL4RHelper";
7+
import { getLockingBatch, ICL4RLock, ICL4RParams } from "./CL4RHelper";
88

99
interface IProps {
1010
schemeParams: ICL4RParams;
1111
lockData: ICL4RLock;
1212
handleRelease: (lockingId: number, setIsReleasing: any) => any;
1313
handleExtend: (extendPeriod: number, batchIndexToLockIn: number, lockingId: number, setIsExtending: any) => any;
14-
getLockingBatch: any;
1514
currentLockingBatch: number;
1615
isLockingEnded: boolean;
1716
endTime: number;
1817
}
1918

2019
const LockRow = (props: IProps) => {
21-
const { lockData, schemeParams, handleRelease, handleExtend, getLockingBatch, currentLockingBatch, isLockingEnded } = props;
20+
const { lockData, schemeParams, handleRelease, handleExtend, currentLockingBatch, isLockingEnded } = props;
2221
const [isReleasing, setIsReleasing] = React.useState(false);
2322
const [isExtending, setIsExtending] = React.useState(false);
2423
const [lockDuration, setLockDuration] = React.useState(1);
2524
const lockingBatch = getLockingBatch(Number(lockData.lockingTime), Number(schemeParams.startTime), Number(schemeParams.batchTime));
2625

2726
// This is to avoid an option to extend more then the max locking batch.
2827
const extendDurations = [] as any;
29-
for (let duration = 1; duration <= Number(schemeParams.maxLockingBatches); duration++) {
30-
if ((duration + Number(lockData.period) <= Number(schemeParams.maxLockingBatches)) && (lockingBatch + Number(lockData.period) + duration < schemeParams.batchesIndexCap)) {
28+
for (let duration = 1; duration <= schemeParams.maxLockingBatches; duration++) {
29+
if ((duration + Number(lockData.period) <= schemeParams.maxLockingBatches) && (lockingBatch + Number(lockData.period) + duration < schemeParams.batchesIndexCap)) {
3130
extendDurations.push(<option key={duration} value={duration} selected={duration === 1}>{duration}</option>);
3231
}
3332
}
3433

3534
const releasable = React.useMemo(() => {
36-
return moment.unix(Number(lockData.lockingTime)).add(Number(lockData.period) * Number(schemeParams.batchTime), "seconds");
35+
return moment.unix(Number(lockData.lockingTime)).add(Number(lockData.period) * schemeParams.batchTime, "seconds");
3736
}, [lockData]);
3837

3938
const release = React.useMemo(() => {

0 commit comments

Comments
 (0)