-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
settings page for voting duration (#121)
https://github.com/user-attachments/assets/af591569-480e-4e9b-aa9e-16304380e33b Lowering voting duration should warn and show proposals that will expire because of the change https://github.com/user-attachments/assets/d5829905-0643-4f57-96fe-b4abebe52c60 When a voting duration change proposal is submitted, a toast will show: https://github.com/user-attachments/assets/1f004b39-d66c-4b1d-aa4a-57d5ed2047b2 The last test here uses a RPC sandbox, interacting with a real instance of the smart contract. Not on the mainnet, but in a near-workspaces-rs sandbox. This new sandbox facilitates real interaction with the smart contracts in tests, without having to do real mainnet transactions. Also there is a "playground" script which you can use to check how a contract works. Currently only the sputnik-dao contract is deployed. resolves #116 resolves #127
- Loading branch information
1 parent
e7777fc
commit 22161e3
Showing
23 changed files
with
5,135 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
#!/bin/bash | ||
|
||
sudo apt update | ||
sudo apt install pkg-config | ||
|
||
npm install | ||
npx playwright install-deps | ||
npx playwright install |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ test-results | |
build | ||
web4/treasury-web4 | ||
package-lock.json | ||
sandboxrpc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
{ | ||
"rust-analyzer.linkedProjects": ["web4/treasury-web4/Cargo.toml"] | ||
"rust-analyzer.linkedProjects": [ | ||
"web4/treasury-web4/Cargo.toml", | ||
"sandboxrpc/Cargo.toml" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
309 changes: 309 additions & 0 deletions
309
instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,309 @@ | ||
const instance = props.instance; | ||
if (!instance) { | ||
return <></>; | ||
} | ||
|
||
const { treasuryDaoID } = VM.require(`${instance}/widget/config.data`); | ||
|
||
const daoPolicy = Near.view(treasuryDaoID, "get_policy", {}); | ||
|
||
if (!daoPolicy) { | ||
return <></>; | ||
} | ||
|
||
const lastProposalId = Near.view(treasuryDaoID, "get_last_proposal_id"); | ||
|
||
const deposit = daoPolicy?.proposal_bond || 100000000000000000000000; | ||
|
||
const currentDurationDays = | ||
Number( | ||
daoPolicy.proposal_period.substr( | ||
0, | ||
daoPolicy.proposal_period.length - "000000000".length | ||
) | ||
) / | ||
(60 * 60 * 24); | ||
|
||
const [durationDays, setDurationDays] = useState(currentDurationDays); | ||
const [proposalsThatWillExpire, setProposalsThatWillExpire] = useState([]); | ||
const [showToastStatus, setToastStatus] = useState(null); | ||
const [isSubmittingChangeRequest, setSubmittingChangeRequest] = useState(false); | ||
|
||
const Container = styled.div` | ||
font-size: 14px; | ||
.border-right { | ||
border-right: 1px solid rgba(226, 230, 236, 1); | ||
} | ||
.card-title { | ||
font-size: 18px; | ||
font-weight: 600; | ||
padding-block: 5px; | ||
border-bottom: 1px solid rgba(226, 230, 236, 1); | ||
} | ||
.selected-role { | ||
background-color: rgba(244, 244, 244, 1); | ||
} | ||
.cursor-pointer { | ||
cursor: pointer; | ||
} | ||
.tag { | ||
background-color: rgba(244, 244, 244, 1); | ||
font-size: 12px; | ||
padding-block: 5px; | ||
} | ||
label { | ||
color: rgba(153, 153, 153, 1); | ||
font-size: 12px; | ||
} | ||
.fw-bold { | ||
font-weight: 500 !important; | ||
} | ||
.p-0 { | ||
padding: 0 !important; | ||
} | ||
.text-md { | ||
font-size: 13px; | ||
} | ||
.theme-btn { | ||
background-color: var(--theme-color) !important; | ||
color: white; | ||
} | ||
.warning { | ||
background-color: rgba(255, 158, 0, 0.1); | ||
color: rgba(177, 113, 8, 1); | ||
font-weight: 500; | ||
} | ||
.text-sm { | ||
font-size: 12px !important; | ||
} | ||
.text-muted { | ||
color: rgba(153, 153, 153, 1); | ||
} | ||
.text-red { | ||
color: #d95c4a; | ||
} | ||
.toast { | ||
background: white !important; | ||
} | ||
.toast-header { | ||
background-color: #2c3e50 !important; | ||
color: white !important; | ||
} | ||
`; | ||
|
||
const ToastContainer = styled.div` | ||
a { | ||
color: black !important; | ||
text-decoration: underline !important; | ||
&:hover { | ||
color: black !important; | ||
} | ||
} | ||
`; | ||
|
||
const cancelChangeRequest = () => { | ||
setDurationDays(currentDurationDays); | ||
}; | ||
|
||
const submitChangeRequest = () => { | ||
setSubmittingChangeRequest(true); | ||
Near.call({ | ||
contractName: treasuryDaoID, | ||
methodName: "add_proposal", | ||
deposit, | ||
args: { | ||
proposal: { | ||
description: "Change proposal period", | ||
kind: { | ||
ChangePolicyUpdateParameters: { | ||
parameters: { | ||
proposal_period: | ||
(60 * 60 * 24 * durationDays).toString() + "000000000", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}); | ||
}; | ||
|
||
useEffect(() => { | ||
Near.asyncView(treasuryDaoID, "get_proposal", { | ||
id: lastProposalId - 1, | ||
}).then((proposal) => { | ||
const proposal_period = | ||
proposal?.kind?.ChangePolicyUpdateParameters?.parameters?.proposal_period; | ||
|
||
if ( | ||
proposal_period && | ||
isSubmittingChangeRequest && | ||
Number(proposal_period.substring(0, proposal_period.length - 9)) / | ||
(24 * 60 * 60) === | ||
Number(durationDays) | ||
) { | ||
setToastStatus(true); | ||
setSubmittingChangeRequest(false); | ||
} | ||
}); | ||
}, [isSubmittingChangeRequest, lastProposalId]); | ||
|
||
const changeDurationDays = (newDurationDays) => { | ||
setDurationDays(newDurationDays); | ||
const limit = 10; | ||
if (newDurationDays < currentDurationDays) { | ||
const fetchProposalsThatWillExpire = ( | ||
lastIndex, | ||
newProposalsThatWillExpire | ||
) => { | ||
Near.asyncView(treasuryDaoID, "get_proposals", { | ||
from_index: lastIndex - limit, | ||
limit, | ||
}).then((/** @type Array */ proposals) => { | ||
const now = new Date().getTime(); | ||
|
||
let fetchMore = false; | ||
for (const proposal of proposals.reverse()) { | ||
const submissionTimeMillis = Number( | ||
proposal.submission_time.substr( | ||
0, | ||
proposal.submission_time.length - 6 | ||
) | ||
); | ||
const currentExpiryTime = | ||
submissionTimeMillis + 24 * 60 * 60 * 1000 * currentDurationDays; | ||
const newExpiryTime = | ||
submissionTimeMillis + 24 * 60 * 60 * 1000 * newDurationDays; | ||
if ( | ||
currentExpiryTime >= now && | ||
newExpiryTime < now && | ||
proposal.status === "InProgress" | ||
) { | ||
newProposalsThatWillExpire.push({ | ||
currentExpiryTime, | ||
newExpiryTime, | ||
submissionTimeMillis, | ||
...proposal, | ||
}); | ||
} | ||
fetchMore = currentExpiryTime >= now; | ||
} | ||
setProposalsThatWillExpire(newProposalsThatWillExpire); | ||
if (fetchMore) { | ||
fetchProposalsThatWillExpire( | ||
lastIndex - limit, | ||
newProposalsThatWillExpire | ||
); | ||
} | ||
}); | ||
}; | ||
fetchProposalsThatWillExpire(lastProposalId, []); | ||
} | ||
}; | ||
|
||
return ( | ||
<Container> | ||
<div className="card rounded-3" style={{ maxWidth: "50rem" }}> | ||
<div className="card-title px-3">Voting Duration</div> | ||
<div className="card-body"> | ||
<p> | ||
Set the number of days a vote is active. A decision expires if voting | ||
is not completed within this period. | ||
</p> | ||
<p> | ||
<label for="exampleInputEmail1" class="px-3"> | ||
Number of days | ||
</label> | ||
<input | ||
type="number" | ||
class="form-control" | ||
aria-describedby="votingDurationHelp" | ||
placeholder="Enter voting duration days" | ||
value={durationDays} | ||
onChange={(event) => changeDurationDays(event.target.value)} | ||
></input> | ||
<small id="votingDurationHelp" class="form-text text-muted px-3"> | ||
Enter number of days that a vote should be active | ||
</small> | ||
</p> | ||
|
||
{proposalsThatWillExpire.length > 0 ? ( | ||
<p> | ||
<div class="alert alert-danger" role="alert"> | ||
The following proposals will expire because of the changed | ||
duration | ||
</div> | ||
<table className="table table-sm"> | ||
<thead> | ||
<tr className="text-grey"> | ||
<th>Id</th> | ||
<th>Description</th> | ||
<th>Submission date</th> | ||
<th>Current expiry</th> | ||
<th>New expiry</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{proposalsThatWillExpire.map((proposal) => ( | ||
<tr class="proposal-that-will-expire"> | ||
<td>{proposal.id}</td> | ||
<td>{proposal.description}</td> | ||
<td> | ||
{new Date(proposal.submissionTimeMillis) | ||
.toJSON() | ||
.substring(0, "yyyy-mm-dd".length)} | ||
</td> | ||
<td> | ||
{new Date(proposal.currentExpiryTime) | ||
.toJSON() | ||
.substring(0, "yyyy-mm-dd".length)} | ||
</td> | ||
<td> | ||
{new Date(proposal.newExpiryTime) | ||
.toJSON() | ||
.substring(0, "yyyy-mm-dd".length)} | ||
</td> | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
</p> | ||
) : ( | ||
"" | ||
)} | ||
|
||
<button class="btn btn-light" onClick={cancelChangeRequest}> | ||
Cancel | ||
</button> | ||
<button class="btn btn-success" onClick={submitChangeRequest}> | ||
Submit Request | ||
</button> | ||
</div> | ||
</div> | ||
<ToastContainer className="toast-container position-fixed bottom-0 end-0 p-3"> | ||
<div className={`toast ${showToastStatus ? "show" : ""}`}> | ||
<div className="toast-header px-2"> | ||
<strong className="me-auto">Just Now</strong> | ||
<i className="bi bi-x-lg h6" onClick={() => setToastStatus(null)}></i> | ||
</div> | ||
<div className="toast-body"> | ||
<p>Voting duration change request submitted</p> | ||
</div> | ||
</div> | ||
</ToastContainer> | ||
</Container> | ||
); |
Oops, something went wrong.