Tokens available:{" "}
{option.value === "NEAR"
? getNearAvailableBalance(option.tokenBalance)
@@ -226,7 +223,7 @@ return (
>
+
+
+
+ {showInProgress ? (
+
+
+
+ Processing your request ...
+
+
+ ) : (
+
+
+
+ Something went wrong. Please try resubmitting the request.
+
+
+ )}
+
+
+
+ ) : null;
+}
+return { TransactionLoader };
diff --git a/instances/treasury-devdao.near/widget/components/ValidatorsDropDownWithSearch.jsx b/instances/treasury-devdao.near/widget/components/ValidatorsDropDownWithSearch.jsx
index 8a3b5e3e..5b08120c 100644
--- a/instances/treasury-devdao.near/widget/components/ValidatorsDropDownWithSearch.jsx
+++ b/instances/treasury-devdao.near/widget/components/ValidatorsDropDownWithSearch.jsx
@@ -61,12 +61,6 @@ const Container = styled.div`
width: 100%;
}
- .dropdown-item.active,
- .dropdown-item:active {
- background-color: #f0f0f0 !important;
- color: black;
- }
-
.custom-select {
position: relative;
}
@@ -77,15 +71,7 @@ const Container = styled.div`
}
.selected {
- background-color: #f0f0f0;
- }
-
- input {
- background-color: #f8f9fa;
- }
-
- .cursor-pointer {
- cursor: pointer;
+ background-color: var(--grey-04);
}
.text-wrap {
@@ -93,18 +79,10 @@ const Container = styled.div`
white-space: normal;
}
- .text-muted {
- color: rgba(142, 142, 147, 1) !important;
- }
-
.text-orange {
color: rgba(255, 149, 0, 1) !important;
}
- .text-dark-grey {
- color: rgba(85, 85, 85, 1) !important;
- }
-
.disabled {
background-color: rgba(244, 244, 244, 1) !important;
color: #999999 !important;
@@ -132,7 +110,7 @@ const BalanceDisplay = ({ balance, label }) => {
return (
-
{label}
+
{label}
{formatNearAmount(balance)} NEAR
);
@@ -161,7 +139,7 @@ return (
>
{selectedOption.pool_id ?? defaultLabel}
@@ -211,7 +189,7 @@ return (
}}
>
- {fee}% Fee
+ {fee}% Fee
Active
{pool_id}
diff --git a/instances/treasury-devdao.near/widget/components/VoteActions.jsx b/instances/treasury-devdao.near/widget/components/VoteActions.jsx
index d10e460f..29afd4ca 100644
--- a/instances/treasury-devdao.near/widget/components/VoteActions.jsx
+++ b/instances/treasury-devdao.near/widget/components/VoteActions.jsx
@@ -4,6 +4,9 @@ if (!instance) {
}
const { treasuryDaoID } = VM.require(`${instance}/widget/config.data`);
+const { TransactionLoader } = VM.require(
+ `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.TransactionLoader`
+) || { TransactionLoader: () => <>> };
const votes = props.votes ?? {};
const proposalId = props.proposalId;
@@ -37,6 +40,7 @@ const [isInsufficientBalance, setInsufficientBal] = useState(false);
const [showWarning, setShowWarning] = useState(false);
const [isReadyToBeWithdrawn, setIsReadyToBeWithdrawn] = useState(true);
const [showConfirmModal, setConfirmModal] = useState(null);
+const [showErrorToast, setShowErrorToast] = useState(false);
useEffect(() => {
if (!avoidCheckForBalance) {
@@ -74,41 +78,47 @@ function getProposalData() {
(result) => result
);
}
-
-function getProposalStatus(votes) {
- const votesArray = Object.values(votes);
- return {
- isApproved:
- votesArray.filter((i) => i === "Approve").length >= requiredVotes,
- isRejected:
- votesArray.filter((i) => i === "Reject").length >= requiredVotes,
- };
-}
-
useEffect(() => {
if (isTxnCreated) {
+ let checkTxnTimeout = null;
+ let errorTimeout = null;
+
const checkForVoteOnProposal = () => {
getProposalData().then((proposal) => {
if (JSON.stringify(proposal.votes) !== JSON.stringify(votes)) {
checkProposalStatus();
+ clearTimeout(errorTimeout);
setTxnCreated(false);
} else {
- setTimeout(() => checkForVoteOnProposal(), 1000);
+ checkTxnTimeout = setTimeout(checkForVoteOnProposal, 1000);
}
});
};
+
checkForVoteOnProposal();
+
+ // if in 20 seconds there is no change, show error condition
+ errorTimeout = setTimeout(() => {
+ setShowErrorToast(true);
+ setTxnCreated(false);
+ clearTimeout(checkTxnTimeout);
+ }, 20000);
+
+ return () => {
+ clearTimeout(checkTxnTimeout);
+ clearTimeout(errorTimeout);
+ };
}
}, [isTxnCreated]);
const Container = styled.div`
.reject-btn {
- background-color: #2c3e50;
+ background-color: var(--other-primary);
border-radius: 5px;
color: white;
&:hover {
- background-color: #2c3e50;
+ background-color: var(--other-primary);
color: white;
}
}
@@ -120,23 +130,15 @@ const Container = styled.div`
}
.approve-btn {
- background-color: #04a46e;
+ background-color: var(--other-green);
border-radius: 5px;
color: white;
&:hover {
- background-color: #04a46e;
+ background-color: var(--other-green);
color: white;
}
}
-
- .fw-semi-bold {
- font-weight: 500;
- }
-
- .text-dark-grey {
- color: rgba(85, 85, 85, 1) !important;
- }
`;
useEffect(() => {
@@ -152,7 +154,10 @@ const InsufficientBalanceWarning = () => {
The request cannot be approved because the treasury balance is
@@ -162,8 +167,15 @@ const InsufficientBalanceWarning = () => {
) : null;
};
+
return (
+ setShowErrorToast(false)}
+ />
+
}
>
-
+
) : (
hasVotingPermission && (
{
+ ActionButton: () => (
+
+ ),
+ checkForDeposit: false,
+ treasuryDaoID,
+ disabled: isTxnCreated,
+ callbackAction: () => {
if (isInsufficientBalance) {
setShowWarning(true);
} else {
@@ -231,23 +255,31 @@ return (
setConfirmModal(true);
}
},
- loading: isTxnCreated && vote === actions.APPROVE,
- disabled: isTxnCreated,
}}
/>
{
+ ActionButton: () => (
+
+ ),
+ disabled: isTxnCreated,
+ checkForDeposit: false,
+ treasuryDaoID,
+ callbackAction: () => {
setVote(actions.REJECT);
setConfirmModal(true);
},
- loading: isTxnCreated && vote === actions.REJECT,
- disabled: isTxnCreated,
}}
/>
@@ -255,20 +287,30 @@ return (
)}
{/* currently showing delete btn only for proposal creator */}
{hasDeletePermission && proposalCreator === accountId && (
-
{
- setVote(actions.REMOVE);
- setConfirmModal(true);
+ (
+
+
+
+ ),
+ checkForDeposit: false,
+ treasuryDaoID,
+ disabled: isTxnCreated,
+ callbackAction: () => {
+ setVote(actions.REMOVE);
+ setConfirmModal(true);
+ },
}}
- data-testid="delete-btn"
- disabled={isTxnCreated}
- >
-
-
+ />
)}
)}
diff --git a/instances/treasury-devdao.near/widget/components/Votes.jsx b/instances/treasury-devdao.near/widget/components/Votes.jsx
index d9070667..e8068da2 100644
--- a/instances/treasury-devdao.near/widget/components/Votes.jsx
+++ b/instances/treasury-devdao.near/widget/components/Votes.jsx
@@ -8,9 +8,9 @@ const getPercentage = (value) =>
const Container = styled.div`
.bar {
- background-color: #e2e6ec;
+ background-color: var(--grey-04);
width: 100px;
- height: 20px;
+ height: 10px;
overflow: hidden;
}
.flex-item {
@@ -22,7 +22,7 @@ const Container = styled.div`
}
.green {
- color: #04a46e;
+ color: var(--other-green);
text-align: left;
}
@@ -49,7 +49,10 @@ return (
diff --git a/instances/treasury-devdao.near/widget/components/templates/AppLayout.jsx b/instances/treasury-devdao.near/widget/components/templates/AppLayout.jsx
index 02e2b253..53df5cd8 100644
--- a/instances/treasury-devdao.near/widget/components/templates/AppLayout.jsx
+++ b/instances/treasury-devdao.near/widget/components/templates/AppLayout.jsx
@@ -1,48 +1,6 @@
-const data = fetch(`https://httpbin.org/headers`);
-const gatewayURL = data?.body?.headers?.Origin ?? "";
-
-// we need fixed positioning for near social and not for org
-const ParentContainer = gatewayURL.includes("near.org")
- ? styled.div`
- width: 100%;
- `
- : styled.div`
- position: fixed;
- inset: 73px 0px 0px;
- width: 100%;
- overflow-y: scroll;
- background: var(--theme-bg-color) !important;
- `;
-
-const Theme = styled.div`
- display: flex;
- flex-direction: column;
- padding-top: calc(-1 * var(--body-top-padding));
- background: var(--theme-bg-color) !important;
-
- // remove up/down arrow in input of type = number
- /* For Chrome, Safari, and Edge */
- input[type="number"]::-webkit-outer-spin-button,
- input[type="number"]::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
- }
-
- /* For Firefox */
- input[type="number"] {
- -moz-appearance: textfield;
- }
-
- .card {
- border-color: #e2e6ec !important;
- border-width: 1px !important;
- border-radius: 14px;
- }
-`;
-
-const Container = styled.div`
- width: 100%;
-`;
+const { BalanceBanner } = VM.require(
+ `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.BalanceBanner`
+) || { BalanceBanner: () => <>> };
const AppHeader = ({ page, instance }) => (
(
/>
);
-function AppLayout({ page, instance, children }) {
- return (
-
-
-
-
- {children}
-
+function AppLayout({ page, instance, children, treasuryDaoID, accountId }) {
+ const config = treasuryDaoID
+ ? useCache(
+ () => Near.asyncView(treasuryDaoID, "get_config"),
+ treasuryDaoID + "_get_config",
+ { subscribe: false }
+ )
+ : null;
+ const metadata = JSON.parse(atob(config.metadata ?? ""));
+
+ const data = fetch(`https://httpbin.org/headers`);
+ const gatewayURL = data?.body?.headers?.Origin ?? "";
+ const isDarkTheme = metadata.theme === "dark";
+
+ const getColors = (isDarkTheme) => `
+ ${metadata.primaryColor ? `--theme-color: ${metadata.primaryColor};` : ""}
+ --bg-header-color: ${isDarkTheme ? "#222222" : "#2C3E50"};
+ --bg-page-color: ${isDarkTheme ? "#222222" : "#FFFFFF"};
+ --bg-system-color: ${isDarkTheme ? "#131313" : "#f4f4f4"};
+ --text-color: ${isDarkTheme ? "#CACACA" : "#1B1B18"};
+ --text-secondary-color: ${isDarkTheme ? "#878787" : "#999999"};
+ --text-alt-color: ${isDarkTheme ? "#FFFFFF" : "#FFFFFF"};
+ --link-inactive-color: ${isDarkTheme ? "" : "white"};
+ --link-active-color: ${isDarkTheme ? "" : "white"};
+ --border-color: ${isDarkTheme ? "#3B3B3B" : "rgba(226, 230, 236, 1)"};
+ --grey-01: ${isDarkTheme ? "#F4F4F4" : "#1B1B18"};
+ --grey-02: ${isDarkTheme ? "#B3B3B3" : "#555555"};
+ --grey-03: ${isDarkTheme ? "#555555" : "#B3B3B3"};
+ --grey-04: ${isDarkTheme ? "#323232" : "#F4F4F4"};
+ --grey-05: ${isDarkTheme ? "#1B1B18" : "#F7F7F7"};
+ --icon-color: ${isDarkTheme ? "#CACACA" : "#060606"};
+ --other-primary:#2775C9;
+ --other-warning:#B17108;
+ --other-green:#3CB179;
+ --other-red:#D95C4A;
+
+ // bootstrap theme color
+ --bs-body-bg: var(--bg-page-color);
+ --bs-border-color: var(--border-color);
+ --bs-dropdown-link-hover-color: var(--grey-04);
+`;
+
+ const ParentContainer = styled.div`
+ ${() => getColors(isDarkTheme)}
+ width: 100%;
+ background: var(--bg-system-color) !important;
+ ${() =>
+ gatewayURL.includes("near.org")
+ ? `
+ /* Styles specific to near.org */
+ position: static;
+ `
+ : `
+ /* Styles specific to other URLs */
+ position: fixed;
+ inset: 73px 0px 0px;
+ overflow-y: scroll;
+ `}
+ `;
+
+ const Theme = styled.div`
+ padding-top: calc(-1 * var(--body-top-padding));
+
+ // remove up/down arrow in input of type = number
+ /* For Chrome, Safari, and Edge */
+ input[type="number"]::-webkit-outer-spin-button,
+ input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+
+ /* For Firefox */
+ input[type="number"] {
+ -moz-appearance: textfield;
+ }
+
+ .card {
+ border-color: var(--border-color) !important;
+ border-width: 1px !important;
+ border-radius: 14px;
+ background-color: var(--bg-page-color) !important;
+ }
+
+ .dropdown-menu {
+ background-color: var(--bg-page-color) !important;
+ color: var(--text-color) !important;
+ }
+
+ .dropdown-item.active,
+ .dropdown-item:active {
+ background-color: var(--grey-04) !important;
+ color: inherit !important;
+ }
+
+ .offcanvas {
+ background-color: var(--bg-page-color) !important;
+ color: var(--text-color) !important;
+ }
+
+ color: var(--text-color);
+
+ a {
+ text-decoration: none;
+ color: var(--link-inactive-color) !important;
+ &.active {
+ color: var(--link-active-color) !important;
+ font-weight: 700 !important;
+ }
+
+ &:hover {
+ text-decoration: none;
+ color: var(--link-active-color) !important;
+ font-weight: 700 !important;
+ }
+ }
+
+ button.primary {
+ background: var(--theme-color) !important;
+ color: var(--text-alt-color) !important;
+ border: none !important;
+ padding-block: 0.7rem !important;
+ i {
+ color: var(--text-alt-color) !important;
+ }
+ }
+
+ .primary-button {
+ background: var(--theme-color) !important;
+ color: var(--text-alt-color) !important;
+ border: none !important;
+ i {
+ color: var(--text-alt-color) !important;
+ }
+ }
+
+ .text-lg {
+ font-size: 15px;
+ }
+
+ .fw-semi-bold {
+ font-weight: 500;
+ }
+
+ .text-secondary {
+ color: var(--text-secondary-color) !important;
+ }
+
+ .max-w-100 {
+ max-width: 100%;
+ }
+
+ .custom-truncate {
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ line-height: 1.5;
+ max-height: 4.5em;
+ text-align: left;
+ }
+
+ .display-none {
+ display: none;
+ }
+
+ .text-right {
+ text-align: end;
+ }
+
+ .text-left {
+ text-align: left;
+ }
+ .text-underline {
+ text-decoration: underline !important;
+ }
+
+ .bg-highlight {
+ background-color: rgb(185, 185, 185, 0.2);
+ }
+
+ .cursor-pointer {
+ cursor: pointer;
+ }
+
+ .theme-btn {
+ background: var(--theme-color) !important;
+ color: white !important;
+ border: none;
+ }
+
+ .theme-btn.btn:hover {
+ color: white !important;
+ }
+
+ .toast-container {
+ right: 10px !important;
+ bottom: 10px !important;
+ }
+ .toast {
+ border-radius: 10px;
+ overflow: hidden;
+ color: var(--text-color) !important;
+ background: var(--bg-page-color) !important;
+ border-color: var(--border-color) !important;
+
+ a {
+ color: inherit !important;
+ &:active {
+ color: inherit !important;
+ }
+ &:hover {
+ color: inherit !important;
+ }
+ }
+ }
+
+ .toast-header {
+ background-color: var(--bg-system-color) !important;
+ color: var(--text-secondary-color) !important;
+ }
+
+ .text-md {
+ font-size: 15px;
+ }
+
+ .primary-text-color {
+ color: var(--theme-color);
+ a {
+ color: var(--theme-color) !important;
+ }
+
+ i {
+ color: var(--theme-color) !important;
+ }
+ }
+
+ .btn-outline.btn:hover {
+ color: inherit !important;
+ }
+
+ .primary-text-color.btn:hover {
+ color: inherit !important;
+ }
+
+ .btn-outline-plain {
+ padding-block: 8px !important;
+ padding-inline: 10px !important;
+ border-radius: 0.375rem !important;
+ border: 1.5px solid var(--border-color) !important;
+ background-color: var(--bg-page-color) !important;
+ color: var(--text-color) !important;
+
+ &:hover {
+ background-color: white;
+ color: black;
+ }
+ }
+
+ h6,
+ .h6,
+ h5,
+ .h5,
+ h4,
+ .h4,
+ h3,
+ .h3,
+ h2,
+ .h2,
+ h1,
+ .h1 {
+ color: var(--text-color) !important;
+ }
+
+ .btn:disabled,
+ .btn.disabled,
+ fieldset:disabled {
+ border-color: transparent !important;
+ }
+
+ .table {
+ border-color: var(--border-color) !important;
+ color: var(--text-color) !important;
+ }
+
+ .bg-white {
+ background-color: var(--bg-page-color) !important;
+ color: var(--text-color) !important;
+ }
+
+ .bg-dropdown {
+ background-color: var(--bg-page-color) !important;
+ color: var(--text-color) !important;
+ }
+
+ .bg-custom-overlay {
+ background-color: var(--bg-page-color) !important;
+ color: var(--text-color) !important;
+ }
+
+ .fill-accent {
+ fill: var(--theme-color);
+ }
+
+ .use-max-bg {
+ color: #007aff;
+ cursor: pointer;
+ }
+
+ .bg-validator-info {
+ background: rgba(0, 16, 61, 0.06);
+ color: #1b1b18;
+ padding-inline: 0.8rem;
+ padding-block: 0.5rem;
+ font-weight: 500;
+ font-size: 13px;
+ }
+
+ .bg-validator-warning {
+ background: rgba(255, 158, 0, 0.1);
+ color: var(--other-warning);
+ padding-inline: 0.8rem;
+ padding-block: 0.5rem;
+ font-weight: 500;
+ font-size: 13px;
+ }
+
+ .bg-withdraw-warning {
+ background: rgba(255, 158, 0, 0.1);
+ color: var(--other-warning);
+ padding-inline: 0.8rem;
+ padding-block: 0.5rem;
+ font-weight: 500;
+ font-size: 13px;
+ }
+
+ .text-sm {
+ font-size: 13px;
+ }
+
+ .text-secondary a {
+ color: inherit !important;
+ }
+
+ .text-delete {
+ color: var(--other-red) !important;
+
+ i {
+ color: var(--other-red) !important;
+ }
+ }
+
+ .btn-outline.btn:hover {
+ color: inherit !important;
+ }
+
+ .border-right {
+ border-right: 1px solid var(--border-color);
+ }
+
+ .cursor-pointer {
+ cursor: pointer;
+ }
+
+ .success-icon {
+ color: var(--other-green) !important;
+ }
+
+ .warning-icon {
+ color: var(--other-warning) !important;
+ }
+
+ .error-icon {
+ color: var(--other-red) !important;
+ }
+ `;
+
+ return !config ? (
+ <>>
+ ) : (
+
+
+
+
+ {children}
);
diff --git a/instances/treasury-devdao.near/widget/config/css.jsx b/instances/treasury-devdao.near/widget/config/css.jsx
index 5446ba50..aae9f279 100644
--- a/instances/treasury-devdao.near/widget/config/css.jsx
+++ b/instances/treasury-devdao.near/widget/config/css.jsx
@@ -1,66 +1,5 @@
const Theme = styled.div`
--theme-color: #05a36e;
- --theme-bg-color: #f4f4f4;
- --text-color: white;
- --link-inactive-color: white;
- --link-active-color: white;
- --border-color: rgba(226, 230, 236, 1);
- --light-grey-color: rgba(185, 185, 185, 1);
- --dark-grey-color: rgba(103, 103, 103, 1);
-
- a {
- text-decoration: none;
- color: var(--link-inactive-color) !important;
- &.active {
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
-
- &:hover {
- text-decoration: none;
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
- }
-
- button {
- &.primary {
- background: var(--theme-color);
- color: var(--text-color);
- border: none !important;
- padding-block: 0.7rem !important;
- }
- }
-
- .text-light-grey {
- color: var(--light-grey-color);
- }
-
- .text-muted {
- color: var(--dark-grey-color);
- }
-
- .text-md {
- font-size: 15px;
- }
-
- .primary-text-color {
- color: var(--theme-color);
- }
-
- .btn-outline-plain {
- padding-block: 8px;
- padding-inline: 10px;
- border-radius: 0.375rem;
- border: 1.5px solid #e2e6ec;
- background-color: white;
- color: black;
-
- &:hover {
- background-color: white;
- color: black;
- }
- }
`;
return { Theme };
diff --git a/instances/treasury-devdao.near/widget/lib/common.jsx b/instances/treasury-devdao.near/widget/lib/common.jsx
index 3ac04750..8ec3cc59 100644
--- a/instances/treasury-devdao.near/widget/lib/common.jsx
+++ b/instances/treasury-devdao.near/widget/lib/common.jsx
@@ -1,5 +1,7 @@
function getApproversAndThreshold(treasuryDaoID, kind, isDeleteCheck) {
- const daoPolicy = Near.view(treasuryDaoID, "get_policy", {});
+ const daoPolicy = treasuryDaoID
+ ? Near.view(treasuryDaoID, "get_policy", {})
+ : null;
const groupWithPermission = (daoPolicy.roles ?? []).filter((role) => {
const permissions = isDeleteCheck
? ["*:*", `${kind}:*`, `${kind}:VoteRemove`, "*:VoteRemove"]
@@ -84,7 +86,10 @@ function getRoleWiseData(treasuryDaoID) {
}
function getPolicyApproverGroup(treasuryDaoID) {
- const daoPolicy = Near.view(treasuryDaoID, "get_policy", {});
+ const daoPolicy = treasuryDaoID
+ ? Near.view(treasuryDaoID, "get_policy", {})
+ : null;
+
const groupWithPermission = (daoPolicy.roles ?? []).filter((role) => {
const policyPermissions = [
"*:*",
@@ -359,7 +364,10 @@ function getMembersAndPermissions(treasuryDaoID) {
}
function getDaoRoles(treasuryDaoID) {
- const daoPolicy = Near.view(treasuryDaoID, "get_policy", {});
+ const daoPolicy = treasuryDaoID
+ ? Near.view(treasuryDaoID, "get_policy", {})
+ : null;
+
if (Array.isArray(daoPolicy.roles)) {
return daoPolicy.roles.map((role) => role.name);
}
@@ -372,7 +380,10 @@ function hasPermission(treasuryDaoID, accountId, kindName, actionType) {
return false;
}
const isAllowed = false;
- const daoPolicy = Near.view(treasuryDaoID, "get_policy", {});
+ const daoPolicy = treasuryDaoID
+ ? Near.view(treasuryDaoID, "get_policy", {})
+ : null;
+
if (Array.isArray(daoPolicy.roles)) {
const permissions = daoPolicy.roles.map((role) => {
if (
@@ -426,10 +437,8 @@ function formatNearAmount(amount) {
.toFixed(2);
}
-function getNearBalances(treasuryDaoID) {
- const resp = fetch(
- `https://api.fastnear.com/v1/account/${treasuryDaoID}/full`
- );
+function getNearBalances(accountId) {
+ const resp = fetch(`https://api.fastnear.com/v1/account/${accountId}/full`);
const storage = Big(resp?.body?.state?.storage_bytes ?? "0")
.mul(Big(10).pow(19))
.toFixed();
@@ -483,7 +492,7 @@ function formatSubmissionTimeStamp(submissionTime, proposalPeriod) {
? "Expired"
: `${totalDays}d ${remainingHours}h ${remainingMinutes}m`}
-
+
{hours}:{minutes} {day} {month} {year}
diff --git a/instances/treasury-devdao.near/widget/lib/modal.jsx b/instances/treasury-devdao.near/widget/lib/modal.jsx
index 336bf784..36921f0b 100644
--- a/instances/treasury-devdao.near/widget/lib/modal.jsx
+++ b/instances/treasury-devdao.near/widget/lib/modal.jsx
@@ -25,11 +25,6 @@ const ModalDiv = styled.div`
.btn {
font-size: 14px;
}
-
- .theme-btn {
- background: var(--theme-color) !important;
- color: white;
- }
`;
const ModalBackdrop = styled.div`
@@ -100,7 +95,7 @@ const NoButton = styled.button`
const ModalHeader = ({ children }) => (
- {children}
+ {children}
);
diff --git a/instances/treasury-devdao.near/widget/lib/skeleton.jsx b/instances/treasury-devdao.near/widget/lib/skeleton.jsx
index c7407f97..4c37b049 100644
--- a/instances/treasury-devdao.near/widget/lib/skeleton.jsx
+++ b/instances/treasury-devdao.near/widget/lib/skeleton.jsx
@@ -1,5 +1,5 @@
const Skeleton = styled.div`
- background: #efefef;
+ background: var(--grey-04);
animation: pulse 1.5s ease-in-out infinite;
@keyframes pulse {
diff --git a/instances/treasury-devdao.near/widget/pages/dashboard/Chart.jsx b/instances/treasury-devdao.near/widget/pages/dashboard/Chart.jsx
index 317153ba..333ea80d 100644
--- a/instances/treasury-devdao.near/widget/pages/dashboard/Chart.jsx
+++ b/instances/treasury-devdao.near/widget/pages/dashboard/Chart.jsx
@@ -1,4 +1,10 @@
-const { nearPrice, ftTokens, accountId, title } = props;
+const { nearPrice, ftTokens, accountId, title, instance } = props;
+
+if (!instance) {
+ return <>>;
+}
+
+const { treasuryDaoID } = VM.require(`${instance}/widget/config.data`);
const { Skeleton } = VM.require(
"${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.skeleton"
@@ -18,10 +24,10 @@ const [selectedPeriod, setSelectedPeriod] = useState({
const [selectedToken, setSelectedToken] = useState("near");
const [isLoading, setIsLoading] = useState(true);
const [balanceDate, setBalanceDate] = useState({ balance: 0, date: "" });
-const nearTokenIcon = "${REPL_NEAR_TOKEN_ICON}";
+
const nearTokenInfo = {
contract: "near",
- ft_meta: { symbol: "NEAR", icon: nearTokenIcon },
+ ft_meta: { symbol: "NEAR" },
};
const tokens = Array.isArray(ftTokens)
@@ -43,8 +49,20 @@ function formatCurrency(amount) {
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
+const config = treasuryDaoID ? Near.view(treasuryDaoID, "get_config") : null;
+const metadata = JSON.parse(atob(config.metadata ?? ""));
+
+const isDarkTheme = metadata.theme === "dark";
+const bgPageColor = isDarkTheme ? "#222222" : "#FFFFFF";
+const borderColor = isDarkTheme ? "#3B3B3B" : "rgba(226, 230, 236, 1)";
+const iconColor = isDarkTheme ? "#CACACA" : "#060606";
+const textColor = isDarkTheme ? "#CACACA" : "#1B1B18";
+const fillStyle = isDarkTheme
+ ? "rgba(27, 27, 24, 0.1)"
+ : "rgba(255, 255, 255, 0.7)";
+
const code = `
-
+
@@ -53,24 +71,24 @@ const code = `
-
-
-
-
+
+
+`;
+
+const SubmitToast = () => {
+ return (
+ showToastStatus && (
+
+
+
+ Just Now
+ setToastStatus(null)}
+ >
+
+
+
+
+ )
+ );
+};
+
+function getLastProposalId() {
+ return Near.asyncView(treasuryDaoID, "get_last_proposal_id").then(
+ (result) => result
+ );
+}
+
+useEffect(() => {
+ getLastProposalId().then((i) => setLastProposalId(i));
+}, []);
+
+useEffect(() => {
+ if (isTxnCreated) {
+ let checkTxnTimeout = null;
+ let errorTimeout = null;
+
+ const checkForNewProposal = () => {
+ getLastProposalId().then((id) => {
+ if (lastProposalId !== id) {
+ setToastStatus(true);
+ setTxnCreated(false);
+ clearTimeout(errorTimeout);
+ } else {
+ checkTxnTimeout = setTimeout(() => checkForNewProposal(), 1000);
+ }
+ });
+ };
+ checkForNewProposal();
+
+ // if in 20 seconds there is no change, show error condition
+ errorTimeout = setTimeout(() => {
+ setShowErrorToast(true);
+ setTxnCreated(false);
+ clearTimeout(checkTxnTimeout);
+ }, 20000);
+
+ return () => {
+ clearTimeout(checkTxnTimeout);
+ clearTimeout(errorTimeout);
+ };
+ }
+}, [isTxnCreated]);
+
+function toBase64(json) {
+ return Buffer.from(JSON.stringify(json)).toString("base64");
+}
+
+function onSubmitClick() {
+ setTxnCreated(true);
+ const deposit = daoPolicy?.proposal_bond || 100000000000000000000000;
+
+ const description = {
+ title: "Update Config - Theme & logo",
+ };
+ Near.call([
+ {
+ contractName: treasuryDaoID,
+ methodName: "add_proposal",
+ args: {
+ proposal: {
+ description: encodeToMarkdown(description),
+ kind: {
+ ChangeConfig: {
+ config: {
+ name: config.name,
+ purpose: config.purpose,
+ metadata: toBase64({
+ ...metadata,
+ primaryColor: color,
+ flagLogo: image,
+ theme: selectedTheme.value,
+ }),
+ },
+ },
+ },
+ },
+ },
+ gas: 200000000000000,
+ },
+ ]);
+}
+
+function setDefault() {
+ setImage(metadata?.flagLogo ?? defaultImage);
+ setColor(metadata?.primaryColor ?? defaultColor);
+ setSelectedTheme(
+ ThemeOptions.find((i) => i.value === metadata?.theme) ?? ThemeOptions[0]
+ );
+}
+
+useEffect(() => {
+ if (metadata) {
+ setDefault();
+ }
+}, [metadata]);
+
+return (
+
+
+ setShowErrorToast(false)}
+ />
+
+
Theme & Logo
+ {!metadata ? (
+
+
+
+ ) : (
+
+
+
+
+ {error && (
+
+
+ {error}
+
+ )}
+
+
+ Theme
+
+
+
+
+ (
+
+ ),
+ checkForDeposit: true,
+ treasuryDaoID,
+ disabled: !hasCreatePermission || error || isTxnCreated,
+ callbackAction: onSubmitClick,
+ }}
+ />
+
+
+ )}
+
+
+);
diff --git a/instances/treasury-devdao.near/widget/pages/settings/Thresholds.jsx b/instances/treasury-devdao.near/widget/pages/settings/Thresholds.jsx
index 2690a2d0..0f50f89f 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/Thresholds.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/Thresholds.jsx
@@ -1,22 +1,34 @@
-const { encodeToMarkdown } = VM.require(
+const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url") || {
+ href: () => {},
+};
+const { TransactionLoader } = VM.require(
+ `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.TransactionLoader`
+) || { TransactionLoader: () => <>> };
+
+const { encodeToMarkdown, hasPermission, getRoleWiseData } = VM.require(
"${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common"
-);
+) || {
+ encodeToMarkdown: () => {},
+ hasPermission: () => {},
+};
const { instance } = props;
if (!instance) {
return <>>;
}
const { treasuryDaoID } = VM.require(`${instance}/widget/config.data`);
-const { getRoleWiseData, hasPermission } = VM.require(
- "${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common"
-) || { hasPermission: () => {} };
-const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url") || {
- href: () => {},
-};
if (typeof getRoleWiseData !== "function") {
return <>>;
}
+
+const hasEditPermission = hasPermission(
+ treasuryDaoID,
+ context.accountId,
+ "ChangeConfig",
+ "AddProposal"
+);
+
const [selectedGroup, setSelectedGroup] = useState(null);
const [selectedVoteOption, setSelectedVoteOption] = useState(null);
const [selectedVoteValue, setSelectedVoteValue] = useState(null);
@@ -28,12 +40,13 @@ const [valueError, setValueError] = useState(null);
const [showConfirmModal, setConfirmModal] = useState(null);
const [rolesData, setRolesData] = useState(null);
const [refreshData, setRefreshData] = useState(false);
+const [showErrorToast, setShowErrorToast] = useState(false);
const hasCreatePermission = hasPermission(
treasuryDaoID,
context.accountId,
"policy",
- "vote"
+ "AddProposal"
);
useEffect(() => {
@@ -78,16 +91,33 @@ useEffect(() => {
useEffect(() => {
if (isTxnCreated) {
+ let checkTxnTimeout = null;
+ let errorTimeout = null;
+
const checkForNewProposal = () => {
getLastProposalId().then((id) => {
if (lastProposalId !== id) {
setToastStatus(true);
+ setTxnCreated(false);
+ clearTimeout(errorTimeout);
} else {
- setTimeout(() => checkForNewProposal(), 1000);
+ checkTxnTimeout = setTimeout(() => checkForNewProposal(), 1000);
}
});
};
checkForNewProposal();
+
+ // if in 20 seconds there is no change, show error condition
+ errorTimeout = setTimeout(() => {
+ setShowErrorToast(true);
+ setTxnCreated(false);
+ clearTimeout(checkTxnTimeout);
+ }, 20000);
+
+ return () => {
+ clearTimeout(checkTxnTimeout);
+ clearTimeout(errorTimeout);
+ };
}
}, [isTxnCreated]);
@@ -105,27 +135,20 @@ useEffect(() => {
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);
+ border-bottom: 1px solid var(--border-color);
}
.selected-role {
- background-color: rgba(244, 244, 244, 1);
- }
-
- .cursor-pointer {
- cursor: pointer;
+ background-color: var(--grey-04);
}
.tag {
- background-color: rgba(244, 244, 244, 1);
+ background-color: var(--grey-04);
font-size: 12px;
padding-block: 5px;
}
@@ -135,10 +158,6 @@ const Container = styled.div`
font-size: 12px;
}
- .fw-bold {
- font-weight: 500 !important;
- }
-
.p-0 {
padding: 0 !important;
}
@@ -147,14 +166,9 @@ const Container = styled.div`
font-size: 13px;
}
- .theme-btn {
- background: var(--theme-color) !important;
- color: white;
- }
-
.warning {
background-color: rgba(255, 158, 0, 0.1);
- color: rgba(177, 113, 8, 1);
+ color: var(--other-warning);
font-weight: 500;
}
@@ -162,38 +176,11 @@ const Container = styled.div`
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;
- }
-
.dropdown-toggle:after {
top: 20% !important;
}
`;
-const ToastContainer = styled.div`
- a {
- color: black !important;
- text-decoration: underline !important;
- &:hover {
- color: black !important;
- }
- }
-`;
-
const proposalKinds = [
"config",
"policy",
@@ -267,30 +254,35 @@ function onSubmitClick() {
const SubmitToast = () => {
return (
showToastStatus && (
-
+
Just Now
setToastStatus(null)}
>
-
+
)
);
};
@@ -313,6 +305,11 @@ const requiredVotes = selectedGroup
return (
+ setShowErrorToast(false)}
+ />
{Array.isArray(rolesData) && rolesData.length ? (
{isPercentageSelected && (
-
+
This is equivalent to
{requiredVotes} votes
@@ -480,17 +477,37 @@ return (
onClick: () => {
resetForm();
},
- disabled: isTxnCreated,
+ disabled: isTxnCreated || !hasCreatePermission,
}}
/>
setConfirmModal(true),
- loading: isTxnCreated,
+ ActionButton: () => (
+
+ ),
+ checkForDeposit: true,
+ disabled:
+ !selectedVoteValue ||
+ valueError ||
+ !hasCreatePermission ||
+ isTxnCreated,
+ treasuryDaoID,
+ callbackAction: () => {
+ setConfirmModal(true);
+ },
}}
/>
diff --git a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx
index c20bc240..6415453d 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx
@@ -1,6 +1,12 @@
-const { encodeToMarkdown } = VM.require(
+const { encodeToMarkdown, hasPermission, getRoleWiseData } = VM.require(
"${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common"
-);
+) || {
+ encodeToMarkdown: () => {},
+ hasPermission: () => {},
+};
+const { TransactionLoader } = VM.require(
+ `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.TransactionLoader`
+) || { TransactionLoader: () => <>> };
const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url") || {
href: () => {},
@@ -16,9 +22,19 @@ const { Modal, ModalContent, ModalHeader, ModalFooter } = VM.require(
"${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.modal"
);
-const daoPolicy = Near.view(treasuryDaoID, "get_policy", {});
+const daoPolicy = treasuryDaoID
+ ? Near.view(treasuryDaoID, "get_policy", {})
+ : null;
+
const lastProposalId = Near.view(treasuryDaoID, "get_last_proposal_id");
+const hasCreatePermission = hasPermission(
+ treasuryDaoID,
+ context.accountId,
+ "policy",
+ "AddProposal"
+);
+
if (!daoPolicy || lastProposalId === null) {
return (
@@ -49,93 +65,23 @@ const [isSubmittingChangeRequest, setSubmittingChangeRequest] = useState(false);
const [showAffectedProposalsModal, setShowAffectedProposalsModal] =
useState(false);
+const [showErrorToast, setShowErrorToast] = useState(false);
const [showLoader, setLoading] = 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;
+ border-bottom: 1px solid var(--border-color);
}
label {
- color: rgba(153, 153, 153, 1);
+ color: var(--text-secondary);
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 = () => {
@@ -319,23 +265,34 @@ const submitChangeRequest = () => {
};
useEffect(() => {
- Near.asyncView(treasuryDaoID, "get_proposal", {
- id: lastProposalId - 1,
- }).then((proposal) => {
- const proposal_period =
- proposal?.kind?.ChangePolicyUpdateParameters?.parameters?.proposal_period;
+ if (isSubmittingChangeRequest) {
+ let errorTimeout = null;
+ 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);
+ clearTimeout(errorTimeout);
+ }
+ });
- if (
- proposal_period &&
- isSubmittingChangeRequest &&
- Number(proposal_period.substring(0, proposal_period.length - 9)) /
- (24 * 60 * 60) ===
- Number(durationDays)
- ) {
- setToastStatus(true);
+ // if in 20 seconds there is no change, show error condition
+ errorTimeout = setTimeout(() => {
+ setShowErrorToast(true);
setSubmittingChangeRequest(false);
- }
- });
+ }, 20000);
+ }
}, [isSubmittingChangeRequest, lastProposalId]);
const changeDurationDays = (newDurationDays) => {
@@ -347,6 +304,11 @@ const showImpactedRequests =
return (
+ setShowErrorToast(false)}
+ />
Voting Duration
@@ -365,6 +327,7 @@ return (
placeholder="Enter voting duration days"
value={durationDays}
onChange={(event) => changeDurationDays(event.target.value)}
+ disabled={!hasCreatePermission}
>
@@ -515,38 +478,66 @@ return (
}}
/>
(
+
+ ),
+ checkForDeposit: true,
+ disabled:
+ durationDays === currentDurationDays ||
+ showLoader ||
+ !hasCreatePermission ||
+ isSubmittingChangeRequest,
+ treasuryDaoID,
+ callbackAction: submitChangeRequest,
}}
/>
-
+
Just Now
- setToastStatus(null)}>
+ setToastStatus(null)}
+ >
-
Voting duration change request submitted.
-
- View it
-
+
+
+
+
Voting duration change request submitted.
+
+ View it
+
+
+
-
+
);
diff --git a/instances/treasury-devdao.near/widget/pages/settings/feed/History.jsx b/instances/treasury-devdao.near/widget/pages/settings/feed/History.jsx
index 90b28e75..52704f0d 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/feed/History.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/feed/History.jsx
@@ -70,7 +70,9 @@ useEffect(() => {
});
}, [currentPage, rowsPerPage]);
-const policy = Near.view(treasuryDaoID, "get_policy", {});
+const policy = treasuryDaoID
+ ? Near.view(treasuryDaoID, "get_policy", {})
+ : null;
const settingsApproverGroup = getApproversAndThreshold(treasuryDaoID, "policy");
diff --git a/instances/treasury-devdao.near/widget/pages/settings/feed/PendingRequests.jsx b/instances/treasury-devdao.near/widget/pages/settings/feed/PendingRequests.jsx
index 1e11bcca..131d7a57 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/feed/PendingRequests.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/feed/PendingRequests.jsx
@@ -54,7 +54,9 @@ useEffect(() => {
fetchProposals();
}, [currentPage, rowsPerPage]);
-const policy = Near.view(treasuryDaoID, "get_policy", {});
+const policy = treasuryDaoID
+ ? Near.view(treasuryDaoID, "get_policy", {})
+ : null;
const settingsApproverGroup = getApproversAndThreshold(treasuryDaoID, "policy");
diff --git a/instances/treasury-devdao.near/widget/pages/settings/feed/SettingsDropdown.jsx b/instances/treasury-devdao.near/widget/pages/settings/feed/SettingsDropdown.jsx
index a0483f58..0fb8046d 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/feed/SettingsDropdown.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/feed/SettingsDropdown.jsx
@@ -77,36 +77,16 @@ const Container = styled.div`
z-index: 9999;
}
- .dropdown-item.active,
- .dropdown-item:active {
- background-color: #f0f0f0 !important;
- color: black;
- }
-
.custom-select {
position: relative;
}
-
- .cursor-pointer {
- cursor: pointer;
- }
-
- .text-grey {
- color: #b3b3b3;
- }
-
- .text-sm {
- font-size: 13px;
- }
`;
const Item = ({ option }) => {
return (
{option.title}
@@ -133,7 +113,7 @@ return (
{isOpen && (
-
Shown in table
+
Shown in table
{settingsOptions.map((option) => {
if (
!isPendingPage &&
diff --git a/instances/treasury-devdao.near/widget/pages/settings/feed/Table.jsx b/instances/treasury-devdao.near/widget/pages/settings/feed/Table.jsx
index b1f794d3..8f720c14 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/feed/Table.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/feed/Table.jsx
@@ -30,6 +30,7 @@ const isPendingRequests = props.isPendingRequests;
const settingsApproverGroup = props.settingsApproverGroup;
const [showToastStatus, setToastStatus] = useState(false);
const [voteProposalId, setVoteProposalId] = useState(null);
+const [showRefreshPageText, setShowRefreshPageText] = useState(false);
const refreshTableData = props.refreshTableData;
const deleteGroup = props.deleteGroup;
@@ -53,18 +54,7 @@ const columnsVisibility = JSON.parse(
const Container = styled.div`
font-size: 13px;
min-height: 60vh;
- .text-grey {
- color: #b9b9b9 !important;
- }
- .text-size-2 {
- font-size: 15px;
- }
- .text-dark-grey {
- color: #687076;
- }
- .text-grey-100 {
- background-color: #f5f5f5;
- }
+
td {
padding: 0.5rem;
color: inherit;
@@ -72,66 +62,9 @@ const Container = styled.div`
background: inherit;
}
- .max-w-100 {
- max-width: 100%;
- }
-
table {
overflow-x: auto;
}
-
- .bold {
- font-weight: 500;
- }
-
- .custom-truncate {
- display: -webkit-box;
- -webkit-line-clamp: 3;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
- line-height: 1.5;
- max-height: 4.5em;
- text-align: left;
- }
-
- .display-none {
- display: none;
- }
-
- .text-right {
- text-align: end;
- }
-
- .text-left {
- text-align: left;
- }
- .text-underline {
- text-decoration: underline !important;
- }
-
- .bg-highlight {
- background-color: rgb(185, 185, 185, 0.2);
- }
-
- .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;
- }
- }
`;
function checkProposalStatus(proposalId) {
@@ -140,6 +73,16 @@ function checkProposalStatus(proposalId) {
})
.then((result) => {
setToastStatus(result.status);
+ // if status is approved and it's a theme change request, tell user to refresh page to view changes
+ if (
+ result.status === "Approved" &&
+ Object.keys(result.kind ?? {})?.[0] &&
+ Object.keys(result.kind)[0] === "ChangeConfig"
+ ) {
+ setShowRefreshPageText(true);
+ } else {
+ setShowRefreshPageText(false);
+ }
setVoteProposalId(proposalId);
refreshTableData();
})
@@ -219,7 +162,9 @@ const ToastStatusContent = () => {
content = "Your vote is counted, the request is highlighted.";
break;
case "Approved":
- content = "The request has been successfully executed.";
+ content =
+ "The request has been successfully executed." +
+ (showRefreshPageText ? " Refresh the page to see the updates." : "");
break;
case "Rejected":
content = "The request has been rejected.";
@@ -233,25 +178,34 @@ const ToastStatusContent = () => {
}
return (
- {content}
-
- {showToastStatus !== "InProgress" && (
-
- View in History
-
- )}
+
+ {showToastStatus === "Approved" && (
+
+ )}
+
+ {content}
+
+ {showToastStatus !== "InProgress" &&
+ showToastStatus !== "Removed" && (
+
+ View in History
+
+ )}
+
+
);
};
@@ -260,15 +214,18 @@ const VoteSuccessToast = () => {
return showToastStatus &&
(typeof voteProposalId === "number" ||
typeof highlightProposalId === "number") ? (
-
+
Just Now
- setToastStatus(null)}>
+ setToastStatus(null)}
+ >
-
+
) : null;
};
@@ -290,7 +247,7 @@ const ProposalsComponent = () => {
: ""
}
>
- {item.id}
+ {item.id}
{
)}
-
+
{title ?? item.description}
-
+
+ ),
children: (
{
{item.proposer}
),
+ instance,
}}
/>
@@ -380,6 +346,7 @@ const ProposalsComponent = () => {
props={{
votes: item.votes,
approversGroup: settingsApproverGroup?.approverAccounts,
+ instance,
}}
/>
@@ -484,7 +451,7 @@ ${JSON.stringify(showDetailsProposalKind.transactionDetails, null, 2)}
) : (
-
+
#
Created Date
{!isPendingRequests && Status }
diff --git a/instances/treasury-devdao.near/widget/pages/settings/index.jsx b/instances/treasury-devdao.near/widget/pages/settings/index.jsx
index dfca14ba..4c272998 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/index.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/index.jsx
@@ -21,6 +21,11 @@ const [leftNavbarOptions, setLeftBarOptions] = useState([
href: `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/pages.settings.VotingDurationPage`,
props: { instance },
},
+ {
+ title: "Theme & Logo",
+ href: `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/pages.settings.Theme`,
+ props: { instance },
+ },
]);
return (
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateButton.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateButton.jsx
index e2a865c9..9c101231 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateButton.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateButton.jsx
@@ -1,3 +1,11 @@
+const { StakeIcon, UnstakeIcon, WithdrawIcon } = VM.require(
+ "${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.Icons"
+) || {
+ StakeIcon: () => <>>,
+ UnstakeIcon: () => <>>,
+ WithdrawIcon: () => <>>,
+};
+
const { hasPermission } = VM.require(
"${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common"
) || {
@@ -44,11 +52,15 @@ function toggleWithdrawPage() {
setShowWithdrawRequest((prev) => !prev);
}
+const toggleDropdown = () => {
+ setCreateBtnOpen((prev) => !prev);
+};
+
const CreateBtn = () => {
const btnOptions = [
{
label: "Stake",
- icon: "${REPL_STAKE_ICON}",
+ icon: ,
value: createBtnOption.STAKE,
onClick: () => {
setShowUnStakeRequest(false);
@@ -58,7 +70,7 @@ const CreateBtn = () => {
},
{
label: "Unstake",
- icon: "${REPL_UNSTAKE_ICON}",
+ icon: ,
value: createBtnOption.UNSTAKE,
onClick: () => {
setShowStakeRequest(false);
@@ -68,7 +80,7 @@ const CreateBtn = () => {
},
{
label: "Withdraw",
- icon: "${REPL_WITHDRAW_ICON}",
+ icon: ,
value: createBtnOption.WITHDRAW,
onClick: () => {
setShowWithdrawRequest(true);
@@ -77,10 +89,6 @@ const CreateBtn = () => {
},
];
- const toggleDropdown = () => {
- setCreateBtnOpen((prev) => !prev);
- };
-
const DropdowntBtnContainer = styled.div`
font-size: 13px;
min-width: 150px;
@@ -90,11 +98,9 @@ const CreateBtn = () => {
}
.select-header {
- color:white;
display: flex;
justify-content: space-between;
cursor: pointer;
- background-color: var(--theme-color);
border-radius: 5px;
}
@@ -108,8 +114,9 @@ const CreateBtn = () => {
top: 100%;
left: 0;
width: 100%;
- border: 1px solid #ccc;
- background-color: #fff;
+ border: 1px solid var(--border-color);
+ background-color: var(--bg-page-color) !important;
+ color: var(--text-color) !important;
padding: 0.5rem;
z-index: 99;
font-size: 13px;
@@ -122,6 +129,7 @@ const CreateBtn = () => {
display: block;
opacity: 1;
transform: translateY(0);
+ z-index:1000;
}
}
@@ -138,16 +146,16 @@ const CreateBtn = () => {
}
.option {
+ color: var(--text-color) !important;
margin-block: 5px;
padding: 10px;
cursor: pointer;
- border-bottom: 1px solid #f0f0f0;
+ border-bottom: 1px solid var(--border-color);
transition: background-color 0.3s ease;
- border-radius: 0.375rem !important;
}
.option:hover {
- background-color: #f0f0f0; /* Custom hover effect color */
+ background-color: var(--bs-dropdown-link-hover-bg);
}
.option:last-child {
@@ -155,7 +163,7 @@ const CreateBtn = () => {
}
.selected {
- background-color: #f0f0f0;
+ background-color: var(--grey-04);
}
.disabled {
@@ -183,18 +191,20 @@ const CreateBtn = () => {
tabIndex="0"
onBlur={() => setCreateBtnOpen(false)}
>
-
+
Create Request
{
>
{btnOptions.map((option) => (
-
-
+
+ {option.icon}
{option.label}
@@ -277,7 +287,17 @@ return (
className="d-flex gap-2 align-items-center"
style={{ paddingBottom: "7px" }}
>
- {hasCreatePermission &&
}
+ {hasCreatePermission && (
+
+ )}
<>> };
+const { TransactionLoader } = VM.require(
+ `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.TransactionLoader`
+) || { TransactionLoader: () => <>> };
const { encodeToMarkdown } = VM.require(
"${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common"
);
@@ -55,6 +60,7 @@ const [lockupStakedPoolsWithBalance, setLockupStakedPoolsWithBalance] =
useState(null);
const [lockupStakedPoolId, setLockupStakedPoolId] = useState(null);
const [lockupAlreadyStaked, setLockupAlreadyStaked] = useState(false);
+const [showErrorToast, setShowErrorToast] = useState(false);
function formatNearAmount(amount) {
return Big(amount ?? "0")
@@ -124,19 +130,35 @@ function cleanInputs() {
useEffect(() => {
if (isTxnCreated) {
+ let checkTxnTimeout = null;
+ let errorTimeout = null;
+
const checkForNewProposal = () => {
getLastProposalId().then((id) => {
if (lastProposalId !== id) {
cleanInputs();
onCloseCanvas();
refreshData();
+ clearTimeout(errorTimeout);
setTxnCreated(false);
} else {
- setTimeout(() => checkForNewProposal(), 1000);
+ checkTxnTimeout = setTimeout(() => checkForNewProposal(), 1000);
}
});
};
checkForNewProposal();
+
+ // if in 20 seconds there is no change, show error condition
+ errorTimeout = setTimeout(() => {
+ setShowErrorToast(true);
+ setTxnCreated(false);
+ clearTimeout(checkTxnTimeout);
+ }, 20000);
+
+ return () => {
+ clearTimeout(checkTxnTimeout);
+ clearTimeout(errorTimeout);
+ };
}
}, [isTxnCreated]);
@@ -152,7 +174,7 @@ const BalanceDisplay = ({ label, balance, tooltipInfo, noBorder }) => {
placement="top"
overlay={{tooltipInfo} }
>
-
+
@@ -335,59 +357,11 @@ function onSubmitClick() {
const Container = styled.div`
font-size: 14px;
- .text-grey {
- color: #b9b9b9 !important;
- }
-
- .text-grey a {
- color: inherit !important;
- }
-
label {
font-weight: 600;
margin-bottom: 3px;
font-size: 15px;
}
-
- .p-2 {
- padding: 0px !important;
- }
-
- .theme-btn {
- background: var(--theme-color) !important;
- color: white;
- }
-
- .primary-text-color a {
- color: var(--theme-color) !important;
- }
-
- .btn:hover {
- color: black !important;
- }
-
- .text-sm {
- font-size: 13px;
- }
-
- .use-max-bg {
- background-color: #ecf8fb;
- color: #1d62a8;
- cursor: pointer;
- }
-
- .text-dark-grey {
- color: rgba(85, 85, 85, 1) !important;
- }
-
- .bg-validator-info {
- background: rgba(0, 16, 61, 0.06);
- color: #1b1b18;
- padding-inline: 0.8rem;
- padding-block: 0.5rem;
- font-weight: 500;
- font-size: 13px;
- }
`;
useEffect(() => {
@@ -425,6 +399,11 @@ useEffect(() => {
return (
+ setShowErrorToast(false)}
+ />
- ),
+ prefix: ,
},
}}
/>
@@ -616,7 +590,8 @@ return (
src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}
props={{
classNames: { root: "theme-btn" },
- disabled: !validatorAccount || !amount || amountError,
+ disabled:
+ !validatorAccount || !amount || amountError || isTxnCreated,
label: "Submit",
onClick: onSubmitClick,
loading: isTxnCreated,
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateUnstakeRequest.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateUnstakeRequest.jsx
index e7d90095..ff06a336 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateUnstakeRequest.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateUnstakeRequest.jsx
@@ -1,5 +1,12 @@
const { getNearBalances, LOCKUP_MIN_BALANCE_FOR_STORAGE, TooltipText } =
VM.require("${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common");
+const { TransactionLoader } = VM.require(
+ `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.TransactionLoader`
+) || { TransactionLoader: () => <>> };
+
+const { NearToken } = VM.require(
+ "${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.Icons"
+) || { NearToken: () => <>> };
const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url") || {
href: () => {},
@@ -59,6 +66,7 @@ const [lockupStakedPoolsWithBalance, setLockupStakedPoolsWithBalance] =
const [showWarning, setShowWarning] = useState(false);
const [lockupStakedPoolId, setLockupStakedPoolId] = useState(null);
+const [showErrorToast, setShowErrorToast] = useState(false);
function formatNearAmount(amount) {
return Big(amount ?? "0")
@@ -180,19 +188,35 @@ function cleanInputs() {
useEffect(() => {
if (isTxnCreated) {
+ let checkTxnTimeout = null;
+ let errorTimeout = null;
+
const checkForNewProposal = () => {
getLastProposalId().then((id) => {
if (lastProposalId !== id) {
cleanInputs();
onCloseCanvas();
refreshData();
+ clearTimeout(errorTimeout);
setTxnCreated(false);
} else {
- setTimeout(() => checkForNewProposal(), 1000);
+ checkTxnTimeout = setTimeout(() => checkForNewProposal(), 1000);
}
});
};
checkForNewProposal();
+
+ // if in 20 seconds there is no change, show error condition
+ errorTimeout = setTimeout(() => {
+ setShowErrorToast(true);
+ setTxnCreated(false);
+ clearTimeout(checkTxnTimeout);
+ }, 20000);
+
+ return () => {
+ clearTimeout(checkTxnTimeout);
+ clearTimeout(errorTimeout);
+ };
}
}, [isTxnCreated]);
@@ -208,7 +232,7 @@ const BalanceDisplay = ({ label, balance, tooltipInfo, noBorder }) => {
placement="top"
overlay={{tooltipInfo} }
>
-
+
@@ -342,14 +366,6 @@ function onSubmitClick() {
const Container = styled.div`
font-size: 14px;
- .text-grey {
- color: #b9b9b9 !important;
- }
-
- .text-grey a {
- color: inherit !important;
- }
-
label {
font-weight: 600;
margin-bottom: 3px;
@@ -359,51 +375,6 @@ const Container = styled.div`
.p-2 {
padding: 0px !important;
}
-
- .theme-btn {
- background: var(--theme-color) !important;
- color: white;
- }
-
- .primary-text-color a {
- color: var(--theme-color) !important;
- }
-
- .btn:hover {
- color: black !important;
- }
-
- .text-sm {
- font-size: 13px;
- }
-
- .use-max-bg {
- background-color: #ecf8fb;
- color: #1d62a8;
- cursor: pointer;
- }
-
- .text-dark-grey {
- color: rgba(85, 85, 85, 1) !important;
- }
-
- .bg-validator-info {
- background: rgba(0, 16, 61, 0.06);
- color: #1b1b18;
- padding-inline: 0.8rem;
- padding-block: 0.5rem;
- font-weight: 500;
- font-size: 13px;
- }
-
- .bg-validator-warning {
- background: rgba(255, 158, 0, 0.1);
- color: rgba(177, 113, 8, 1);
- padding-inline: 0.8rem;
- padding-block: 0.5rem;
- font-weight: 500;
- font-size: 13px;
- }
`;
useEffect(() => {
@@ -437,6 +408,11 @@ useEffect(() => {
return (
+ setShowErrorToast(false)}
+ />
- ),
+ prefix: ,
},
}}
/>
{validatorAccount && (
-
+
Available to unstake:
{formatNearAmount(
@@ -631,7 +602,8 @@ return (
src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}
props={{
classNames: { root: "theme-btn" },
- disabled: !validatorAccount || !amount || amountError,
+ disabled:
+ !validatorAccount || !amount || amountError || isTxnCreated,
label: "Submit",
onClick: onSubmitClick,
loading: isTxnCreated,
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateWithdrawRequest.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateWithdrawRequest.jsx
index 5f7f7d59..1e6e14ff 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateWithdrawRequest.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateWithdrawRequest.jsx
@@ -1,5 +1,8 @@
const { getNearBalances, LOCKUP_MIN_BALANCE_FOR_STORAGE, TooltipText } =
VM.require("${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common");
+const { TransactionLoader } = VM.require(
+ `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.TransactionLoader`
+) || { TransactionLoader: () => <>> };
const instance = props.instance;
const onCloseCanvas = props.onCloseCanvas ?? (() => {});
@@ -55,6 +58,7 @@ const [lockupStakedPoolsWithBalance, setLockupStakedPoolsWithBalance] =
const [hasUnstakedAssets, setHasUnstakedAssets] = useState(true);
const [isReadyToWithdraw, setIsReadyToWithdraw] = useState(true);
const [showLoader, setShowLoader] = useState(true);
+const [showErrorToast, setShowErrorToast] = useState(false);
function formatNearAmount(amount) {
return Big(amount ?? "0")
@@ -114,18 +118,35 @@ useEffect(() => {
useEffect(() => {
if (isTxnCreated) {
+ let checkTxnTimeout = null;
+ let errorTimeout = null;
+
const checkForNewProposal = () => {
getLastProposalId().then((id) => {
if (lastProposalId !== id) {
+ cleanInputs();
onCloseCanvas();
refreshData();
+ clearTimeout(errorTimeout);
setTxnCreated(false);
} else {
- setTimeout(() => checkForNewProposal(), 1000);
+ checkTxnTimeout = setTimeout(() => checkForNewProposal(), 1000);
}
});
};
checkForNewProposal();
+
+ // if in 20 seconds there is no change, show error condition
+ errorTimeout = setTimeout(() => {
+ setShowErrorToast(true);
+ setTxnCreated(false);
+ clearTimeout(checkTxnTimeout);
+ }, 20000);
+
+ return () => {
+ clearTimeout(checkTxnTimeout);
+ clearTimeout(errorTimeout);
+ };
}
}, [isTxnCreated]);
@@ -141,7 +162,7 @@ const BalanceDisplay = ({ label, balance, tooltipInfo, noBorder }) => {
placement="top"
overlay={{tooltipInfo} }
>
-
+
@@ -302,14 +323,14 @@ const Pools = () => {
}
>
- {fee}% Fee
+ {fee}% Fee
Active
{pool_id}
-
+
Available for withdrawal:{" "}
@@ -336,14 +357,6 @@ const Pools = () => {
const Container = styled.div`
font-size: 14px;
- .text-grey {
- color: #b9b9b9 !important;
- }
-
- .text-grey a {
- color: inherit !important;
- }
-
label {
font-weight: 600;
margin-bottom: 3px;
@@ -354,51 +367,6 @@ const Container = styled.div`
padding: 0px !important;
}
- .theme-btn {
- background: var(--theme-color) !important;
- color: white;
- }
-
- .primary-text-color a {
- color: var(--theme-color) !important;
- }
-
- .btn:hover {
- color: black !important;
- }
-
- .text-sm {
- font-size: 13px;
- }
-
- .use-max-bg {
- background-color: #ecf8fb;
- color: #1d62a8;
- cursor: pointer;
- }
-
- .text-dark-grey {
- color: rgba(85, 85, 85, 1) !important;
- }
-
- .bg-validator-info {
- background: rgba(0, 16, 61, 0.06);
- color: #1b1b18;
- padding-inline: 0.8rem;
- padding-block: 0.5rem;
- font-weight: 500;
- font-size: 13px;
- }
-
- .bg-withdraw-warning {
- background: rgba(255, 158, 0, 0.1);
- color: rgba(177, 113, 8, 1);
- padding-inline: 0.8rem;
- padding-block: 0.5rem;
- font-weight: 500;
- font-size: 13px;
- }
-
.text-green {
color: #34c759;
}
@@ -410,6 +378,11 @@ const Container = styled.div`
return (
+ setShowErrorToast(false)}
+ />
{
});
}, [currentPage, rowsPerPage]);
-const policy = Near.view(treasuryDaoID, "get_policy", {});
+const policy = treasuryDaoID
+ ? Near.view(treasuryDaoID, "get_policy", {})
+ : null;
const functionCallApproversGroup = getApproversAndThreshold(
treasuryDaoID,
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/PendingRequests.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/PendingRequests.jsx
index 83637da2..277397f7 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/PendingRequests.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/PendingRequests.jsx
@@ -64,7 +64,9 @@ useEffect(() => {
fetchProposals();
}, [refreshTableData]);
-const policy = Near.view(treasuryDaoID, "get_policy", {});
+const policy = treasuryDaoID
+ ? Near.view(treasuryDaoID, "get_policy", {})
+ : null;
const functionCallApproversGroup = getApproversAndThreshold(
treasuryDaoID,
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/SettingsDropdown.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/SettingsDropdown.jsx
index 986874b2..cc4be62b 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/SettingsDropdown.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/SettingsDropdown.jsx
@@ -88,36 +88,16 @@ const Container = styled.div`
z-index: 9999;
}
- .dropdown-item.active,
- .dropdown-item:active {
- background-color: #f0f0f0 !important;
- color: black;
- }
-
.custom-select {
position: relative;
}
-
- .cursor-pointer {
- cursor: pointer;
- }
-
- .text-grey {
- color: #b3b3b3;
- }
-
- .text-sm {
- font-size: 13px;
- }
`;
const Item = ({ option }) => {
return (
{option.title}
@@ -144,7 +124,7 @@ return (
{isOpen && (
-
Shown in table
+
Shown in table
{settingsOptions.map((option) => {
if (
!isPendingPage &&
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/Table.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/Table.jsx
index 7b69ef77..50e2266a 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/Table.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/Table.jsx
@@ -81,18 +81,7 @@ const hasDeletePermission = (deleteGroup?.approverAccounts ?? []).includes(
const Container = styled.div`
font-size: 13px;
min-height: 60vh;
- .text-grey {
- color: #b9b9b9 !important;
- }
- .text-size-2 {
- font-size: 15px;
- }
- .text-dark-grey {
- color: #687076;
- }
- .text-grey-100 {
- background-color: #f5f5f5;
- }
+
td {
padding: 0.5rem;
color: inherit;
@@ -100,59 +89,12 @@ const Container = styled.div`
background: inherit;
}
- .max-w-100 {
- max-width: 100%;
- }
-
table {
overflow-x: auto;
}
- .bold {
- font-weight: 500;
- }
-
- .custom-truncate {
- display: -webkit-box;
- -webkit-line-clamp: 3;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
- line-height: 1.5;
- max-height: 4.5em;
- text-align: left;
- }
-
- .display-none {
- display: none;
- }
-
- .text-right {
- text-align: end;
- }
-
- .text-left {
- text-align: left;
- }
- .text-underline {
- text-decoration: underline !important;
- }
-
- .bg-highlight {
- background-color: rgb(185, 185, 185, 0.2);
- }
-
- .toast {
- background: white !important;
- }
-
- .toast-header {
- background-color: #2c3e50 !important;
- color: white !important;
- }
-
.text-warning {
- color: rgba(177, 113, 8, 1) !important;
+ color: var(--other-warning) !important;
}
.markdown-href p {
@@ -162,22 +104,8 @@ const Container = styled.div`
.markdown-href a {
color: inherit !important;
}
-
- .fw-semi-bold {
- font-weight: 500;
- }
`;
-
-const ToastContainer = styled.div`
- a {
- color: black !important;
- text-decoration: underline !important;
- &:hover {
- color: black !important;
- }
- }
-`;
-
+div;
function checkProposalStatus(proposalId) {
Near.asyncView(treasuryDaoID, "get_proposal", {
id: proposalId,
@@ -275,25 +203,34 @@ const ToastStatusContent = () => {
}
return (
- {content}
-
- {showToastStatus !== "InProgress" && showToastStatus !== "Removed" && (
-
- View in History
-
- )}
+
+ {showToastStatus === "Approved" && (
+
+ )}
+
+ {content}
+
+ {showToastStatus !== "InProgress" &&
+ showToastStatus !== "Removed" && (
+
+ View in History
+
+ )}
+
+
);
};
@@ -302,15 +239,18 @@ const VoteSuccessToast = () => {
return showToastStatus &&
(typeof voteProposalId === "number" ||
typeof highlightProposalId === "number") ? (
-
+
Just Now
- setToastStatus(null)}>
+ setToastStatus(null)}
+ >
-
+
) : null;
};
@@ -368,7 +308,7 @@ const ProposalsComponent = () => {
: ""
}
>
- {item.id}
+ {item.id}
{
/>
)}
-
+
{
{lockupContract && (
-
+
{" "}
{isLockup ? "Lockup" : "Sputnik DAO"}
+ ),
children: (
{
{treasuryWallet}
),
+ instance,
}}
/>
@@ -435,14 +381,20 @@ const ProposalsComponent = () => {
src={`${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/pages.stake-delegation.Validator`}
props={{
validatorId: validatorAccount,
+ instance,
}}
/>
-
+
+ ),
children: (
{
{item.proposer}
),
+ instance,
}}
/>
@@ -501,6 +454,7 @@ const ProposalsComponent = () => {
props={{
votes: item.votes,
approversGroup: functionCallApproversGroup?.approverAccounts,
+ instance,
}}
/>
@@ -579,7 +533,7 @@ return (
) : (
-
+
#
Created Date
{!isPendingRequests && Status }
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/Type.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/Type.jsx
index a4d5df84..95bf0a17 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/Type.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/Type.jsx
@@ -1,3 +1,12 @@
+const { StakeIcon, UnstakeIcon, WithdrawIcon, Whitelist } = VM.require(
+ "${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.Icons"
+) || {
+ StakeIcon: () => <>>,
+ UnstakeIcon: () => <>>,
+ WithdrawIcon: () => <>>,
+ Whitelist: () => <>>,
+};
+
const type = props.type;
const classes =
@@ -8,7 +17,7 @@ const Badge = () => {
case "unstake": {
return (
-
+
Unstake
);
@@ -17,7 +26,7 @@ const Badge = () => {
case "withdraw_all_from_staking_pool": {
return (
-
+
Withdraw
);
@@ -25,7 +34,7 @@ const Badge = () => {
case "select_staking_pool": {
return (
-
+
Whitelist
);
@@ -33,7 +42,7 @@ const Badge = () => {
default: {
return (
-
+
Stake
);
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/Validator.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/Validator.jsx
index 81a9d5cf..fde57b6e 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/Validator.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/Validator.jsx
@@ -10,14 +10,20 @@ const pikespeakKey = isBosGateway()
if (!pikespeakKey) {
return (
+ ),
children: (
{validatorId}
),
+ instance: props.instance,
}}
/>
);
@@ -44,10 +50,6 @@ const Container = styled.div`
color: #34c759;
}
- .text-grey {
- color: #b9b9b9 !important;
- }
-
.text-sm {
font-size: 12px;
}
@@ -57,7 +59,7 @@ return (
{validatorId}
-
{fee}% Fee
+
{fee}% Fee
{isActive &&
Active
}
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/WalletDropdown.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/WalletDropdown.jsx
index 71e6d792..f6f8f396 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/WalletDropdown.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/WalletDropdown.jsx
@@ -91,7 +91,9 @@ return (
/>
{item.label}
- {item.balance && ${item.balance}
}
+ {item.balance && (
+ ${item.balance}
+ )}
);
},
diff --git a/instances/treasury-infinex.near/widget/app.jsx b/instances/treasury-infinex.near/widget/app.jsx
index 2f617073..4859aafb 100644
--- a/instances/treasury-infinex.near/widget/app.jsx
+++ b/instances/treasury-infinex.near/widget/app.jsx
@@ -11,6 +11,7 @@ const { AppLayout } = VM.require(
) || { AppLayout: () => <>> };
const instance = "${REPL_INSTANCE}";
+const treasuryDaoID = "${REPL_TREASURY}";
const { Theme } = VM.require(`${instance}/widget/config.css`) || {
Theme: () => <>>,
@@ -95,7 +96,12 @@ function Page() {
return (
-
+
diff --git a/instances/treasury-infinex.near/widget/config/css.jsx b/instances/treasury-infinex.near/widget/config/css.jsx
index 77e83bdb..f7a131f3 100644
--- a/instances/treasury-infinex.near/widget/config/css.jsx
+++ b/instances/treasury-infinex.near/widget/config/css.jsx
@@ -1,70 +1,5 @@
const Theme = styled.div`
- --theme-color: rgb(21 22 25);
- --theme-bg-color: #f4f4f4;
- --text-color: white;
- --link-inactive-color: white;
- --link-active-color: white;
- --border-color: rgba(226, 230, 236, 1);
- --light-grey-color: rgba(185, 185, 185, 1);
- --dark-grey-color: rgba(103, 103, 103, 1);
-
- a {
- text-decoration: none;
- color: var(--link-inactive-color) !important;
- &.active {
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
-
- &:hover {
- text-decoration: none;
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
- }
-
- button {
- &.primary {
- background: var(--theme-color);
- color: white;
- border: none !important;
- padding-block: 0.7rem !important;
- }
- }
-
- .text-light-grey {
- color: var(--light-grey-color);
- }
-
- .text-muted {
- color: var(--dark-grey-color);
- }
-
- .text-md {
- font-size: 15px;
- }
-
- .primary-text-color {
- color: var(--theme-color);
- }
-
- .btn-outline-plain {
- padding-block: 8px;
- padding-inline: 10px;
- border-radius: 0.375rem;
- border: 1.5px solid #e2e6ec;
- background-color: white;
- color: black;
-
- &:hover {
- background-color: white;
- color: black;
- }
- }
-
- .fill-accent {
- fill: #fe6f39;
- }
+ --theme-color: #f76218;
`;
return { Theme };
diff --git a/instances/treasury-templar.near/widget/app.jsx b/instances/treasury-templar.near/widget/app.jsx
index a9233012..d2fcc990 100644
--- a/instances/treasury-templar.near/widget/app.jsx
+++ b/instances/treasury-templar.near/widget/app.jsx
@@ -11,6 +11,7 @@ const { AppLayout } = VM.require(
) || { AppLayout: () => <>> };
const instance = "${REPL_INSTANCE}";
+const treasuryDaoID = "${REPL_TREASURY}";
const { Theme } = VM.require(`${instance}/widget/config.css`) || {
Theme: () => <>>,
@@ -84,7 +85,12 @@ function Page() {
return (
-
+
diff --git a/instances/treasury-templar.near/widget/config/css.jsx b/instances/treasury-templar.near/widget/config/css.jsx
index 75e8c00f..42f7499e 100644
--- a/instances/treasury-templar.near/widget/config/css.jsx
+++ b/instances/treasury-templar.near/widget/config/css.jsx
@@ -1,70 +1,5 @@
const Theme = styled.div`
--theme-color: #8942d9;
- --theme-bg-color: #f4f4f4;
- --text-color: white;
- --link-inactive-color: white;
- --link-active-color: white;
- --border-color: rgba(226, 230, 236, 1);
- --light-grey-color: rgba(185, 185, 185, 1);
- --dark-grey-color: rgba(103, 103, 103, 1);
-
- a {
- text-decoration: none;
- color: var(--link-inactive-color) !important;
- &.active {
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
-
- &:hover {
- text-decoration: none;
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
- }
-
- button {
- &.primary {
- background: var(--theme-color);
- color: white;
- border: none !important;
- padding-block: 0.7rem !important;
- }
- }
-
- .text-light-grey {
- color: var(--light-grey-color);
- }
-
- .text-muted {
- color: var(--dark-grey-color);
- }
-
- .text-md {
- font-size: 15px;
- }
-
- .primary-text-color {
- color: var(--theme-color);
- }
-
- .btn-outline-plain {
- padding-block: 8px;
- padding-inline: 10px;
- border-radius: 0.375rem;
- border: 1.5px solid #e2e6ec;
- background-color: white;
- color: black;
-
- &:hover {
- background-color: white;
- color: black;
- }
- }
-
- .fill-accent {
- fill: #fe6f39;
- }
`;
return { Theme };
diff --git a/instances/treasury-testing-infinex.near/widget/app.jsx b/instances/treasury-testing-infinex.near/widget/app.jsx
index 2f617073..4859aafb 100644
--- a/instances/treasury-testing-infinex.near/widget/app.jsx
+++ b/instances/treasury-testing-infinex.near/widget/app.jsx
@@ -11,6 +11,7 @@ const { AppLayout } = VM.require(
) || { AppLayout: () => <>> };
const instance = "${REPL_INSTANCE}";
+const treasuryDaoID = "${REPL_TREASURY}";
const { Theme } = VM.require(`${instance}/widget/config.css`) || {
Theme: () => <>>,
@@ -95,7 +96,12 @@ function Page() {
return (
-
+
diff --git a/instances/treasury-testing-infinex.near/widget/config/css.jsx b/instances/treasury-testing-infinex.near/widget/config/css.jsx
index 77e83bdb..f7a131f3 100644
--- a/instances/treasury-testing-infinex.near/widget/config/css.jsx
+++ b/instances/treasury-testing-infinex.near/widget/config/css.jsx
@@ -1,70 +1,5 @@
const Theme = styled.div`
- --theme-color: rgb(21 22 25);
- --theme-bg-color: #f4f4f4;
- --text-color: white;
- --link-inactive-color: white;
- --link-active-color: white;
- --border-color: rgba(226, 230, 236, 1);
- --light-grey-color: rgba(185, 185, 185, 1);
- --dark-grey-color: rgba(103, 103, 103, 1);
-
- a {
- text-decoration: none;
- color: var(--link-inactive-color) !important;
- &.active {
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
-
- &:hover {
- text-decoration: none;
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
- }
-
- button {
- &.primary {
- background: var(--theme-color);
- color: white;
- border: none !important;
- padding-block: 0.7rem !important;
- }
- }
-
- .text-light-grey {
- color: var(--light-grey-color);
- }
-
- .text-muted {
- color: var(--dark-grey-color);
- }
-
- .text-md {
- font-size: 15px;
- }
-
- .primary-text-color {
- color: var(--theme-color);
- }
-
- .btn-outline-plain {
- padding-block: 8px;
- padding-inline: 10px;
- border-radius: 0.375rem;
- border: 1.5px solid #e2e6ec;
- background-color: white;
- color: black;
-
- &:hover {
- background-color: white;
- color: black;
- }
- }
-
- .fill-accent {
- fill: #fe6f39;
- }
+ --theme-color: #f76218;
`;
return { Theme };
diff --git a/instances/treasury-testing.near/widget/app.jsx b/instances/treasury-testing.near/widget/app.jsx
index a9233012..d2fcc990 100644
--- a/instances/treasury-testing.near/widget/app.jsx
+++ b/instances/treasury-testing.near/widget/app.jsx
@@ -11,6 +11,7 @@ const { AppLayout } = VM.require(
) || { AppLayout: () => <>> };
const instance = "${REPL_INSTANCE}";
+const treasuryDaoID = "${REPL_TREASURY}";
const { Theme } = VM.require(`${instance}/widget/config.css`) || {
Theme: () => <>>,
@@ -84,7 +85,12 @@ function Page() {
return (
-
+
diff --git a/instances/treasury-testing.near/widget/config/css.jsx b/instances/treasury-testing.near/widget/config/css.jsx
index 5446ba50..aae9f279 100644
--- a/instances/treasury-testing.near/widget/config/css.jsx
+++ b/instances/treasury-testing.near/widget/config/css.jsx
@@ -1,66 +1,5 @@
const Theme = styled.div`
--theme-color: #05a36e;
- --theme-bg-color: #f4f4f4;
- --text-color: white;
- --link-inactive-color: white;
- --link-active-color: white;
- --border-color: rgba(226, 230, 236, 1);
- --light-grey-color: rgba(185, 185, 185, 1);
- --dark-grey-color: rgba(103, 103, 103, 1);
-
- a {
- text-decoration: none;
- color: var(--link-inactive-color) !important;
- &.active {
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
-
- &:hover {
- text-decoration: none;
- color: var(--link-active-color) !important;
- font-weight: 700 !important;
- }
- }
-
- button {
- &.primary {
- background: var(--theme-color);
- color: var(--text-color);
- border: none !important;
- padding-block: 0.7rem !important;
- }
- }
-
- .text-light-grey {
- color: var(--light-grey-color);
- }
-
- .text-muted {
- color: var(--dark-grey-color);
- }
-
- .text-md {
- font-size: 15px;
- }
-
- .primary-text-color {
- color: var(--theme-color);
- }
-
- .btn-outline-plain {
- padding-block: 8px;
- padding-inline: 10px;
- border-radius: 0.375rem;
- border: 1.5px solid #e2e6ec;
- background-color: white;
- color: black;
-
- &:hover {
- background-color: white;
- color: black;
- }
- }
`;
return { Theme };
diff --git a/instances/treasury-testing.near/widget/config/data.jsx b/instances/treasury-testing.near/widget/config/data.jsx
index ce1fcc33..186bc703 100644
--- a/instances/treasury-testing.near/widget/config/data.jsx
+++ b/instances/treasury-testing.near/widget/config/data.jsx
@@ -28,7 +28,6 @@ return {
showKYC: true,
showReferenceProposal: true,
isTesting: true,
-
logo: (
{
await page.unrouteAll({ behavior: "ignoreErrors" });
});
-test.describe("admin connected", function () {
+test.describe("User is not logged in", function () {
+ test("should not see 'Create Request' action", async ({
+ page,
+ instanceAccount,
+ }) => {
+ await page.goto(`/${instanceAccount}/widget/app?page=payments`);
+ await expect(page.getByText("Pending Requests")).toBeVisible();
+ await expect(
+ page.getByRole("button", {
+ name: "Create Request",
+ })
+ ).toBeHidden();
+ });
+});
+
+test.describe("User is logged in", function () {
+ const signedUser = "theori.near";
test.use({
contextOptions: {
permissions: ["clipboard-read", "clipboard-write"],
@@ -128,6 +146,55 @@ test.describe("admin connected", function () {
storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
});
+ test("low account balance should show warning modal, and allow action ", async ({
+ page,
+ instanceAccount,
+ }) => {
+ test.setTimeout(60_000);
+ await mockNearBalances({
+ page,
+ accountId: signedUser,
+ balance: BigInt(0.6 * 10 ** 24).toString(),
+ storage: 8,
+ });
+ await page.goto(`/${instanceAccount}/widget/app?page=payments`);
+ await expect(
+ page.getByText(
+ "Please add more NEAR to your account soon to avoid any issues completing actions on your treasury"
+ )
+ ).toBeVisible();
+ });
+
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ instanceAccount,
+ }) => {
+ test.setTimeout(60_000);
+ await updateDaoPolicyMembers({ page });
+ await mockNearBalances({
+ page,
+ accountId: signedUser,
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ await page.goto(`/${instanceAccount}/widget/app?page=payments`);
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page
+ .getByRole("button", {
+ name: "Create Request",
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
test("different amount values should not throw any error", async ({
page,
instanceAccount,
@@ -594,6 +661,7 @@ test.describe("admin with function access keys", function () {
expectedTransactionModalObject
);
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
let isTransactionCompleted = false;
let retryCountAfterComplete = 0;
let newProposalId;
diff --git a/playwright-tests/tests/payments/vote-on-request.spec.js b/playwright-tests/tests/payments/vote-on-request.spec.js
index 22bbf5d5..77a649ff 100644
--- a/playwright-tests/tests/payments/vote-on-request.spec.js
+++ b/playwright-tests/tests/payments/vote-on-request.spec.js
@@ -2,13 +2,18 @@ import { expect } from "@playwright/test";
import { test } from "../../util/test.js";
import { mockTransactionSubmitRPCResponses } from "../../util/transaction";
-import { mockRpcRequest, updateDaoPolicyMembers } from "../../util/rpcmock";
+import {
+ mockNearBalances,
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+} from "../../util/rpcmock";
import { setDontAskAgainCacheValues } from "../../util/cache";
import { mockPikespeakFTTokensResponse } from "../../util/pikespeak.js";
import {
CurrentTimestampInNanoseconds,
TransferProposalData,
} from "../../util/inventory.js";
+import { InsufficientBalance } from "../../util/lib.js";
async function mockWithFTBalance({ page, daoAccount, isSufficient }) {
await page.route(
@@ -153,7 +158,41 @@ test.describe("don't ask again", function () {
storageState:
"playwright-tests/storage-states/wallet-connected-admin-with-accesskey.json",
});
- test("should throw insufficient balance error", async ({
+
+ test("should throw insufficient signed in account balance error", async ({
+ page,
+ instanceAccount,
+ daoAccount,
+ }) => {
+ test.setTimeout(60_000);
+ await updateDaoPolicyMembers({ page });
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ await voteOnProposal({
+ page,
+ daoAccount,
+ instanceAccount,
+ voteStatus: "Approved",
+ vote: "Approve",
+ });
+ const approveButton = page
+ .getByRole("button", {
+ name: "Approve",
+ })
+ .first();
+ await expect(approveButton).toBeEnabled({ timeout: 30_000 });
+ await approveButton.click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+ test("should throw insufficient dao account balance error", async ({
page,
instanceAccount,
daoAccount,
@@ -211,6 +250,7 @@ test.describe("don't ask again", function () {
await expect(approveButton).toBeEnabled({ timeout: 30_000 });
await approveButton.click();
await page.getByRole("button", { name: "Confirm" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
await expect(approveButton).toBeDisabled();
const transaction_toast = page.getByText(
@@ -266,6 +306,7 @@ test.describe("don't ask again", function () {
await expect(rejectButton).toBeEnabled({ timeout: 10000 });
await rejectButton.click();
await page.getByRole("button", { name: "Confirm" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
await expect(rejectButton).toBeDisabled();
const transaction_toast = page.getByText(
@@ -321,6 +362,8 @@ test.describe("don't ask again", function () {
page.getByText("Do you really want to delete this request?")
).toBeVisible();
await page.getByRole("button", { name: "Confirm" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+
await expect(deleteButton).toBeDisabled();
const transaction_toast = page.getByText(
diff --git a/playwright-tests/tests/settings/assets/invalid.png b/playwright-tests/tests/settings/assets/invalid.png
new file mode 100644
index 0000000000000000000000000000000000000000..7b9ab52662bbafe070f97a9c5046ef5243f7d292
GIT binary patch
literal 2235
zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4Yzkn25lDE4H!+#K5uy^@npa^@3
zr>`sfZ6;}ccD5(o8*3RDIAT3r978JRyuD_~$e_S;U_)d5`3FpWYFt%iXU|A7D5U=9
zV_>*v%)lU!!oZ+#f`P$d0wY6%0y6`HBhaQM9tH*$NuVi4qY6gDVKhCA=8MttVYHka
htqw-3MM(2N?@SHL|4OI50l>BggQu&X%Q~loCIFhXCdmK*
literal 0
HcmV?d00001
diff --git a/playwright-tests/tests/settings/assets/valid.jpg b/playwright-tests/tests/settings/assets/valid.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dec00ae5a1a580a37a2c6bd151ab0b5c9133eba4
GIT binary patch
literal 26372
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
literal 0
HcmV?d00001
diff --git a/playwright-tests/tests/settings/create-member-request.spec.js b/playwright-tests/tests/settings/create-member-request.spec.js
index a97de507..e2256b87 100644
--- a/playwright-tests/tests/settings/create-member-request.spec.js
+++ b/playwright-tests/tests/settings/create-member-request.spec.js
@@ -5,9 +5,13 @@ import {
getTransactionModalObject,
mockTransactionSubmitRPCResponses,
} from "../../util/transaction.js";
-import { mockRpcRequest, updateDaoPolicyMembers } from "../../util/rpcmock.js";
+import {
+ mockNearBalances,
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+} from "../../util/rpcmock.js";
import { mockInventory } from "../../util/inventory.js";
-import { encodeToMarkdown } from "../../util/lib.js";
+import { InsufficientBalance, encodeToMarkdown } from "../../util/lib.js";
const lastProposalId = 3;
@@ -32,7 +36,9 @@ async function updateLastProposalId(page) {
async function navigateToMembersPage({ page, instanceAccount }) {
await page.goto(`/${instanceAccount}/widget/app?page=settings`);
+ await page.waitForTimeout(5_000);
await page.getByText("Members").click();
+ await expect(page.getByText("All Members")).toBeVisible({ timeout: 10_000 });
}
async function openAddMemberForm({ page }) {
@@ -52,23 +58,59 @@ test.afterEach(async ({ page }, testInfo) => {
await page.unrouteAll({ behavior: "ignoreErrors" });
});
-test.describe("admin connected", function () {
+test.describe("User is not logged in", function () {
+ test.beforeEach(async ({ page, instanceAccount }) => {
+ await navigateToMembersPage({ page, instanceAccount });
+ });
+
+ test("should not be able to add member or see actions", async ({ page }) => {
+ await expect(
+ page.getByRole("button", {
+ name: "New Member",
+ })
+ ).toBeHidden();
+ await expect(page.getByText("Actions", { exact: true })).toBeHidden();
+ });
+});
+
+test.describe("User is logged in", function () {
test.use({
storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
});
- test.beforeEach(async ({ page, daoAccount, instanceAccount }) => {
+ test.beforeEach(async ({ page, daoAccount, instanceAccount }, testInfo) => {
await mockInventory({ page, account: daoAccount });
await updateDaoPolicyMembers({ page });
await updateLastProposalId(page);
+ if (testInfo.title.includes("insufficient account balance")) {
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ }
await navigateToMembersPage({ page, instanceAccount });
});
- test("should show members of the DAO", async ({
+ test("insufficient account balance should show warning modal, disallow action ", async ({
page,
- instanceAccount,
- daoAccount,
}) => {
+ test.setTimeout(60_000);
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page.getByRole("button", { name: " New Member" }).click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
+ test("should show members of the DAO", async ({ page }) => {
test.setTimeout(60_000);
await expect(page.getByText("Megha", { exact: true })).toBeVisible();
});
@@ -148,7 +190,7 @@ test.describe("admin connected", function () {
const submitBtn = await page.locator("button", { hasText: "Submit" });
await submitBtn.scrollIntoViewIfNeeded({ timeout: 10_000 });
await submitBtn.click();
-
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
const description = {
title: "Update policy - Members Permissions",
summary: `theori.near requested to add "${account}" to "${permission}".`,
@@ -425,6 +467,7 @@ test.describe("admin connected", function () {
await expect(submitBtn).toBeAttached({ timeout: 10_000 });
await submitBtn.scrollIntoViewIfNeeded({ timeout: 10_000 });
await submitBtn.click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
const description = {
title: "Update policy - Members Permissions",
summary: `theori.near requested to remove "${account}" from "${permission}".`,
@@ -568,6 +611,8 @@ test.describe("admin connected", function () {
page.getByRole("heading", { name: "Are you sure?" })
).toBeVisible();
await page.getByRole("button", { name: "Remove" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+
const description = {
title: "Update policy - Members Permissions",
summary: `theori.near requested to requested to revoke all permissions of "megha19.near".`,
diff --git a/playwright-tests/tests/settings/create-threshold-request.spec.js b/playwright-tests/tests/settings/create-threshold-request.spec.js
index 56130a1b..8a00f76c 100644
--- a/playwright-tests/tests/settings/create-threshold-request.spec.js
+++ b/playwright-tests/tests/settings/create-threshold-request.spec.js
@@ -4,10 +4,11 @@ import { test } from "../../util/test.js";
import { getTransactionModalObject } from "../../util/transaction.js";
import {
getMockedPolicy,
+ mockNearBalances,
mockRpcRequest,
updateDaoPolicyMembers,
} from "../../util/rpcmock.js";
-import { encodeToMarkdown } from "../../util/lib.js";
+import { InsufficientBalance, encodeToMarkdown } from "../../util/lib.js";
const lastProposalId = 3;
@@ -37,7 +38,8 @@ async function checkVotingDropdownChange({ page }) {
)
).toBeVisible();
await expect(page.getByText("Enter Percentage")).toBeVisible();
- await page.getByRole("list").getByText("Number of votes").click();
+ await page.getByTestId("dropdown-btn").click();
+ await page.getByText("Number of votes").click();
await expect(
page.getByText(
"A fixed number of votes is required for a decision to pass."
@@ -51,11 +53,19 @@ test.afterEach(async ({ page }, testInfo) => {
await page.unrouteAll({ behavior: "ignoreErrors" });
});
-test.describe("without login", function () {
+async function navigateToThresholdPage({ page, instanceAccount }) {
+ await page.goto(`/${instanceAccount}/widget/app?page=settings`);
+ await page.waitForTimeout(5_000);
+ await page.getByText("Voting Threshold").click();
+ await expect(page.getByText("Permission Groups")).toBeVisible({
+ timeout: 10_000,
+ });
+}
+
+test.describe("User is not logged in", function () {
test.beforeEach(async ({ page, instanceAccount }) => {
await updateDaoPolicyMembers({ page });
- await page.goto(`/${instanceAccount}/widget/app?page=settings`);
- await page.getByText("Voting Thresholds").click({ timeout: 20_000 });
+ await navigateToThresholdPage({ page, instanceAccount });
});
test("should show members of different roles", async ({ page }) => {
@@ -87,36 +97,63 @@ test.describe("without login", function () {
timeout: 20_000,
});
await expect(page.getByTestId("dropdown-btn")).toBeDisabled();
- await expect(page.getByText("Submit")).toBeHidden();
+ await expect(page.getByText("Submit Request")).toBeHidden();
await expect(page.getByTestId("threshold-input")).toBeDisabled();
});
});
async function fillInput(page, value) {
- const submitBtn = page.getByText("Submit");
+ const submitBtn = page.getByText("Submit Request");
const thresholdInput = page.getByTestId("threshold-input");
await thresholdInput.type(value);
expect(submitBtn).toBeVisible();
}
-test.describe("admin connected", function () {
+test.describe("User is logged in", function () {
test.use({
storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
});
- test.beforeEach(async ({ page, instanceAccount }) => {
+ test.beforeEach(async ({ page, instanceAccount }, testInfo) => {
await updateLastProposalId(page);
await updateDaoPolicyMembers({ page });
- await page.goto(`/${instanceAccount}/widget/app?page=settings`);
- await page.getByText("Voting Thresholds").click({ timeout: 20_000 });
- await expect(page.getByText("Submit")).toBeVisible({
- timeout: 20_000,
- });
- await page.getByTestId("dropdown-btn").click();
+ if (testInfo.title.includes("insufficient account balance")) {
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ }
+ await navigateToThresholdPage({ page, instanceAccount });
+ });
+
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ }) => {
+ test.setTimeout(60_000);
+
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page.getByTestId("threshold-input").fill("1");
+ await page
+ .getByText("Submit Request", {
+ exact: true,
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
});
test("should allow only valid input for threshold", async ({ page }) => {
test.setTimeout(60_000);
+ await page.getByTestId("dropdown-btn").click();
await page.getByRole("list").getByText("Number of votes").click();
await fillInput(page, "20097292");
await fillInput(page, "10.323");
@@ -134,7 +171,8 @@ test.describe("admin connected", function () {
page,
}) => {
test.setTimeout(150_000);
- const submitBtn = page.getByText("Submit");
+ const submitBtn = page.getByText("Submit Request");
+ await page.getByTestId("dropdown-btn").click();
await page.getByRole("list").getByText("Number of votes").click();
const thresholdInput = page.getByTestId("threshold-input");
await thresholdInput.fill("20");
@@ -148,6 +186,7 @@ test.describe("admin connected", function () {
)
).toBeVisible();
await page.getByRole("button", { name: "Confirm" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
const updatedPolicy = {
weight_kind: "RoleWeight",
quorum: "0",
@@ -172,7 +211,8 @@ test.describe("admin connected", function () {
test("should be able to update policy by percentage", async ({ page }) => {
test.setTimeout(150_000);
- const submitBtn = page.getByText("Submit");
+ const submitBtn = page.getByText("Submit Request");
+ await page.getByTestId("dropdown-btn").click();
await page.getByRole("list").getByText("Percentage of members").click();
const thresholdInput = page.getByTestId("threshold-input");
await thresholdInput.fill("101");
@@ -189,6 +229,8 @@ test.describe("admin connected", function () {
)
).toBeVisible();
await page.getByRole("button", { name: "Confirm" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+
const updatedPolicy = {
weight_kind: "RoleWeight",
quorum: "0",
diff --git a/playwright-tests/tests/settings/theme.spec.js b/playwright-tests/tests/settings/theme.spec.js
new file mode 100644
index 00000000..622e9592
--- /dev/null
+++ b/playwright-tests/tests/settings/theme.spec.js
@@ -0,0 +1,202 @@
+import { expect } from "@playwright/test";
+import { test } from "../../util/test.js";
+import { getTransactionModalObject } from "../../util/transaction.js";
+import {
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+ mockNearBalances,
+} from "../../util/rpcmock.js";
+import path from "path";
+import { fileURLToPath } from "url";
+import { InsufficientBalance } from "../../util/lib.js";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+function toBase64(json) {
+ return Buffer.from(JSON.stringify(json)).toString("base64");
+}
+
+test.afterEach(async ({ page }, testInfo) => {
+ console.log(`Finished ${testInfo.title} with status ${testInfo.status}`);
+ await page.unrouteAll({ behavior: "ignoreErrors" });
+});
+
+const metadata = {
+ flagLogo:
+ "https://ipfs.near.social/ipfs/bafkreiboarigt5w26y5jyxyl4au7r2dl76o5lrm2jqjgqpooakck5xsojq",
+ displayName: "testing-astradao",
+ primaryColor: "#2f5483",
+ theme: "dark",
+};
+
+const config = {
+ name: "testing-astradao",
+ metadata: toBase64(metadata),
+};
+
+async function updateDaoConfig({ page }) {
+ await mockRpcRequest({
+ page,
+ filterParams: {
+ method_name: "get_config",
+ },
+ modifyOriginalResultFunction: () => {
+ return config;
+ },
+ });
+}
+
+async function navigateToThemePage({ page, instanceAccount }) {
+ await page.goto(`/${instanceAccount}/widget/app?page=settings`);
+ await updateDaoPolicyMembers({ page });
+ await updateDaoConfig({ page });
+ await page.waitForTimeout(5_000);
+ await page.getByText("Theme & Logo", { exact: true }).click();
+ await expect(page.getByText("Theme & Logo").nth(1)).toBeVisible();
+}
+
+test.describe("User is not logged in", function () {
+ test("should not be able to change config", async ({
+ page,
+ instanceAccount,
+ }) => {
+ test.setTimeout(60_000);
+ await navigateToThemePage({ page, instanceAccount });
+ await expect(page.locator("input[type='color']")).toBeDisabled();
+ await expect(
+ page.getByRole("button", { name: "Submit Request" })
+ ).toBeDisabled({ timeout: 20_000 });
+ });
+});
+
+test.describe("User is logged in", function () {
+ test.use({
+ storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
+ });
+
+ test.beforeEach(async ({ page, instanceAccount }, testInfo) => {
+ if (testInfo.title.includes("insufficient account balance")) {
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ }
+ await navigateToThemePage({ page, instanceAccount });
+ });
+
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ }) => {
+ test.setTimeout(60_000);
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page
+ .getByText("Submit Request", {
+ exact: true,
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
+ test("should be able to upload image, should show error with incorrect width and allow correct one", async ({
+ page,
+ }) => {
+ test.setTimeout(150_000);
+ await expect(
+ page.frameLocator("iframe").getByRole("button", { name: "Upload Logo" })
+ ).toBeVisible();
+ await page.route("https://ipfs.near.social/add", async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({ cid: "simple_cid_1" }),
+ });
+ });
+
+ const logoInput = await page
+ .frameLocator("iframe")
+ .locator("input[type=file]");
+ const submitBtn = await page.getByRole("button", {
+ name: "Submit Request",
+ });
+ // invalid image
+ await logoInput.setInputFiles(path.join(__dirname, "./assets/invalid.png"));
+ await expect(
+ page.getByText(
+ "Invalid logo. Please upload a PNG, JPG, or SVG file for your logo that is exactly 256x256 px"
+ )
+ ).toBeVisible();
+ await expect(submitBtn).toBeDisabled(submitBtn);
+ await logoInput.setInputFiles(path.join(__dirname, "./assets/valid.jpg"));
+ await submitBtn.click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+
+ await expect(await getTransactionModalObject(page)).toEqual({
+ proposal: {
+ description: "* Title: Update Config - Theme & logo",
+ kind: {
+ ChangeConfig: {
+ config: {
+ ...config,
+ metadata: toBase64({
+ ...metadata,
+ flagLogo: "https://ipfs.near.social/ipfs/simple_cid_1",
+ }),
+ },
+ },
+ },
+ },
+ });
+ });
+
+ test("should be able to change color and theme", async ({ page }) => {
+ test.setTimeout(150_000);
+ const newColor = "#0000";
+ await page.getByRole("textbox").nth(1).fill(newColor);
+ await page.getByTestId("dropdown-btn").click();
+ await page.getByText("Light").click();
+ await page.getByRole("button", { name: "Submit Request" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+ await expect(await getTransactionModalObject(page)).toEqual({
+ proposal: {
+ description: "* Title: Update Config - Theme & logo",
+ kind: {
+ ChangeConfig: {
+ config: {
+ ...config,
+ metadata: toBase64({
+ ...metadata,
+ primaryColor: newColor,
+ theme: "light",
+ }),
+ },
+ },
+ },
+ },
+ });
+ });
+
+ test("should display a transaction error toast when the transaction confirmation modal is canceled", async ({
+ page,
+ }) => {
+ test.setTimeout(150_000);
+ await page.getByRole("button", { name: "Submit Request" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+ await page.getByRole("button", { name: "Close" }).nth(1).click();
+ await expect(
+ page.getByText(
+ "Something went wrong. Please try resubmitting the request"
+ )
+ ).toBeVisible({ timeout: 30_000 });
+ });
+});
diff --git a/playwright-tests/tests/settings/voting-duration.spec.js b/playwright-tests/tests/settings/voting-duration.spec.js
index 23cb7994..962c4c82 100644
--- a/playwright-tests/tests/settings/voting-duration.spec.js
+++ b/playwright-tests/tests/settings/voting-duration.spec.js
@@ -2,29 +2,89 @@ import { expect } from "@playwright/test";
import { test } from "../../util/test.js";
import { getTransactionModalObject } from "../../util/transaction.js";
import { SandboxRPC } from "../../util/sandboxrpc.js";
-import { mockRpcRequest } from "../../util/rpcmock.js";
-import { encodeToMarkdown } from "../../util/lib.js";
+import {
+ mockNearBalances,
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+} from "../../util/rpcmock.js";
+import { InsufficientBalance, encodeToMarkdown } from "../../util/lib.js";
test.afterEach(async ({ page }, testInfo) => {
console.log(`Finished ${testInfo.title} with status ${testInfo.status}`);
await page.unrouteAll({ behavior: "ignoreErrors" });
});
-test.describe("admin connected", function () {
+async function navigateToVotingDurationPage({ page, instanceAccount }) {
+ await page.goto(`/${instanceAccount}/widget/app?page=settings`);
+ await page.waitForTimeout(5_000);
+ await page.getByText("Voting Duration").click();
+ await expect(
+ page.getByText("Set the number of days a vote is active.")
+ ).toBeVisible({
+ timeout: 10_000,
+ });
+}
+
+test.describe("User is not logged in", function () {
+ test("should not be able to change voting duration", async ({
+ page,
+ instanceAccount,
+ }) => {
+ await navigateToVotingDurationPage({ page, instanceAccount });
+ await expect(
+ page.getByPlaceholder("Enter voting duration days")
+ ).toBeDisabled();
+
+ await expect(
+ page.getByRole("button", { name: "Submit Request" })
+ ).toBeDisabled();
+ });
+});
+
+test.describe("User is logged in", function () {
test.use({
storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
});
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ instanceAccount,
+ }) => {
+ test.setTimeout(60_000);
+ await updateDaoPolicyMembers({ page });
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ await navigateToVotingDurationPage({ page, instanceAccount });
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page.getByPlaceholder("Enter voting duration days").fill("2");
+ await page
+ .getByText("Submit Request", {
+ exact: true,
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
test("should set voting duration", async ({
page,
instanceAccount,
daoAccount,
}) => {
test.setTimeout(150_000);
- await page.goto(`/${instanceAccount}/widget/app?page=settings`);
- await page.getByText("Voting Duration").first().click();
-
- await page.waitForTimeout(500);
+ await updateDaoPolicyMembers({ page });
+ await navigateToVotingDurationPage({ page, instanceAccount });
const currentDurationDays = await page
.getByPlaceholder("Enter voting duration days")
.inputValue();
@@ -36,12 +96,6 @@ test.describe("admin connected", function () {
await page.waitForTimeout(500);
await page.locator("button", { hasText: "Submit" }).click();
- if (daoAccount === "testing-astradao.sputnik-dao.near") {
- await page
- .locator(".modalfooter button", { hasText: "Yes, proceed" })
- .click();
- }
-
const description = {
title: "Update policy - Voting Duration",
summary: `theori.near requested to change voting duration from ${currentDurationDays} to ${newDurationDays}.`,
@@ -73,10 +127,8 @@ test.describe("admin connected", function () {
daoAccount,
}) => {
test.setTimeout(150_000);
- await page.goto(`/${instanceAccount}/widget/app?page=settings`);
- await page.getByText("Voting Duration").first().click();
-
- await page.waitForTimeout(500);
+ await updateDaoPolicyMembers({ page });
+ await navigateToVotingDurationPage({ page, instanceAccount });
const currentDurationDays = await page
.getByPlaceholder("Enter voting duration days")
.inputValue();
@@ -111,10 +163,9 @@ test.describe("admin connected", function () {
receiver_id: "webassemblymusic.near",
daoName,
});
- await page.goto(`/${instanceAccount}/widget/app?page=settings`);
- await page.getByText("Voting Duration").first().click();
+ await updateDaoPolicyMembers({ page });
+ await navigateToVotingDurationPage({ page, instanceAccount });
- await page.waitForTimeout(500);
const currentDurationDays = await page
.getByPlaceholder("Enter voting duration days")
.inputValue();
@@ -137,8 +188,8 @@ test.describe("admin connected", function () {
const wallet = await selector.wallet();
return new Promise((resolve) => {
- wallet.signAndSendTransactions = async (transactions) => {
- resolve(transactions.transactions[0]);
+ wallet.signAndSendTransaction = async (transaction) => {
+ resolve(transaction);
return await new Promise(
(transactionSentPromiseResolve) =>
(window.transactionSentPromiseResolve =
@@ -162,7 +213,7 @@ test.describe("admin connected", function () {
await page.evaluate((transactionResult) => {
window.transactionSentPromiseResolve(transactionResult);
}, transactionResult);
- await expect(page.locator(".toast-header")).toBeVisible();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
await expect(
page.getByText("Voting duration change request submitted")
).toBeVisible();
@@ -222,8 +273,8 @@ test.describe("admin connected", function () {
}
},
});
- await page.goto(`/${instanceAccount}/widget/app?page=settings`);
- await page.getByText("Voting Duration").first().click();
+ await updateDaoPolicyMembers({ page });
+ await navigateToVotingDurationPage({ page, instanceAccount });
await page.waitForTimeout(500);
const currentDurationDays = await page
diff --git a/playwright-tests/tests/stake-delegation/stake-delegation.spec.js b/playwright-tests/tests/stake-delegation/stake-delegation.spec.js
index cb7f43cd..e11730d0 100644
--- a/playwright-tests/tests/stake-delegation/stake-delegation.spec.js
+++ b/playwright-tests/tests/stake-delegation/stake-delegation.spec.js
@@ -6,7 +6,11 @@ import {
mockTransactionSubmitRPCResponses,
} from "../../util/transaction";
import { utils } from "near-api-js";
-import { mockRpcRequest, updateDaoPolicyMembers } from "../../util/rpcmock.js";
+import {
+ mockNearBalances,
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+} from "../../util/rpcmock.js";
import {
CurrentTimestampInNanoseconds,
OldJsonProposalData,
@@ -16,6 +20,7 @@ import {
} from "../../util/inventory.js";
import { setDontAskAgainCacheValues } from "../../util/cache.js";
import Big from "big.js";
+import { InsufficientBalance } from "../../util/lib.js";
test.afterEach(async ({ page }, testInfo) => {
console.log(`Finished ${testInfo.title} with status ${testInfo.status}`);
@@ -160,27 +165,6 @@ async function mockStakedPoolBalances({ page }) {
});
}
-async function mockNearBalances({ page, daoAccount, balance }) {
- await page.route(
- `https://api.fastnear.com/v1/account/${daoAccount}/full`,
- async (route) => {
- const json = {
- account_id: daoAccount,
- nfts: [],
- pools: [],
- state: {
- balance: balance,
- locked: "0",
- storage_bytes: 677278,
- },
- tokens: [],
- };
-
- await route.fulfill({ json });
- }
- );
-}
-
export async function mockStakedPools({
daoAccount,
page,
@@ -412,7 +396,7 @@ async function voteOnProposal({
}
test.describe("Have valid staked requests and sufficient token balance", function () {
- test.beforeEach(async ({ page, instanceAccount, daoAccount }) => {
+ test.beforeEach(async ({ page, instanceAccount, daoAccount }, testInfo) => {
const instanceConfig = await getInstanceConfig({ page, instanceAccount });
if (
!instanceConfig.navbarLinks.find(
@@ -422,20 +406,37 @@ test.describe("Have valid staked requests and sufficient token balance", functio
console.log("no stake delegation page configured for instance");
return test.skip();
}
- await mockStakeProposals({ page });
+ if (
+ testInfo.title.includes("Should successfully parse old JSON description")
+ ) {
+ await mockOldJSONStakeProposals({ page });
+ } else {
+ await mockStakeProposals({ page });
+ }
await updateDaoPolicyMembers({ page });
await mockStakedPools({ page, daoAccount });
- await mockNearBalances({
- page,
- daoAccount,
- balance: sufficientAvailableBalance,
- });
+ if (testInfo.title.includes("insufficient account balance")) {
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ } else {
+ await mockNearBalances({
+ page,
+ accountId: daoAccount,
+ balance: sufficientAvailableBalance,
+ storage: 2323,
+ });
+ }
await mockStakedPoolBalances({ page });
await page.goto(`/${instanceAccount}/widget/app?page=stake-delegation`);
await expect(
await page.locator("div").filter({ hasText: /^Stake Delegation$/ })
).toBeVisible();
+ await page.waitForTimeout(5_000);
});
test.describe("User not logged in", function () {
@@ -452,8 +453,6 @@ test.describe("Have valid staked requests and sufficient token balance", functio
test("Should successfully parse old JSON description", async ({ page }) => {
test.setTimeout(80_000);
- await mockOldJSONStakeProposals({ page });
-
await expect(
page.getByRole("cell", { name: "this is notes", exact: true })
).toBeVisible({ timeout: 40_000 });
@@ -480,6 +479,28 @@ test.describe("Have valid staked requests and sufficient token balance", functio
});
});
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ }) => {
+ test.setTimeout(60_000);
+
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page
+ .getByText("Create Request", {
+ exact: true,
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
test("Should create stake delegation request, should throw error when invalid data is provided", async ({
page,
}) => {
@@ -488,7 +509,7 @@ test.describe("Have valid staked requests and sufficient token balance", functio
exact: true,
});
await createRequestButton.click();
- await page.getByText("Stake", { exact: true }).click();
+ await page.locator(".option").first().click();
await page.waitForTimeout(10_000);
await fillValidatorAccount({
@@ -505,6 +526,7 @@ test.describe("Have valid staked requests and sufficient token balance", functio
.first()
.inputValue();
await page.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
await expect(await getTransactionModalObject(page)).toEqual({
proposal: {
description: "* Proposal Action: stake",
@@ -540,6 +562,8 @@ test.describe("Have valid staked requests and sufficient token balance", functio
errorText: "The amount exceeds the balance you have staked.",
});
await page.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+
await expect(await getTransactionModalObject(page)).toEqual({
proposal: {
description: "* Proposal Action: unstake",
@@ -655,7 +679,8 @@ test.describe("Withdraw request", function () {
});
const submitBtn = page.getByRole("button", { name: "Submit" });
await expect(submitBtn).toBeEnabled();
- await submitBtn.click();
+ await submitBtn.dblclick();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
await expect(await getTransactionModalObject(page)).toEqual({
proposal: {
description: `* Proposal Action: withdraw`,
@@ -701,9 +726,6 @@ test.describe("Withdraw request", function () {
await expect(page.getByText(stakedPoolAccount)).toBeVisible({
timeout: 10_000,
});
- const submitBtn = page.getByRole("button", { name: "Submit" });
- await expect(submitBtn).toBeEnabled();
- await submitBtn.click();
await expect(
page.getByText(
"By submitting, you request to withdraw all available funds. A separate withdrawal request will be created for each validator"
@@ -711,6 +733,10 @@ test.describe("Withdraw request", function () {
).toBeVisible({
timeout: 10_000,
});
+ const submitBtn = page.getByRole("button", { name: "Submit" });
+ await expect(submitBtn).toBeEnabled();
+ await submitBtn.dblclick();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
// proposals for both the pools
await expect(await getTransactionModalObject(page)).toEqual({
proposal: {
@@ -837,9 +863,11 @@ test.describe("Lockup staking", function () {
await updateDaoPolicyMembers({ page });
await mockNearBalances({
page,
- daoAccount,
+ accountId: daoAccount,
balance: sufficientAvailableBalance,
+ storage: 2323,
});
+
await mockLockupNearBalances({
page,
balance: sufficientAvailableBalance,
@@ -888,6 +916,8 @@ test.describe("Lockup staking", function () {
errorText: "Your account doesn't have sufficient balance.",
});
await page.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+
await expect(await getTransactionModalObject(page)).toEqual({
proposal: {
description:
@@ -1002,6 +1032,8 @@ test.describe("Lockup staking", function () {
errorText: "Your account doesn't have sufficient balance.",
});
await page.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+
await expect(await getTransactionModalObject(page)).toEqual({
proposal: {
description: "* Proposal Action: stake",
@@ -1049,6 +1081,8 @@ test.describe("Lockup staking", function () {
errorText: "The amount exceeds the balance you have staked.",
});
await page.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("Processing your request ...")).toBeVisible();
+
await expect(await getTransactionModalObject(page)).toEqual({
proposal: {
description: "* Proposal Action: unstake",
@@ -1306,9 +1340,11 @@ test.describe("Insufficient balance ", function () {
await mockStakedPoolBalances({ page });
await mockNearBalances({
page,
- daoAccount,
+ accountId: daoAccount,
balance: inSufficientAvailableBalance,
+ storage: 2323,
});
+
await page.goto(`/${instanceAccount}/widget/app?page=stake-delegation`);
await expect(
await page.locator("div").filter({ hasText: /^Stake Delegation$/ })
diff --git a/playwright-tests/util/lib.js b/playwright-tests/util/lib.js
index 35b81046..e6f7134a 100644
--- a/playwright-tests/util/lib.js
+++ b/playwright-tests/util/lib.js
@@ -20,3 +20,5 @@ export const encodeToMarkdown = (data) => {
})
.join(" ");
};
+
+export const InsufficientBalance = BigInt(0.05 * 10 ** 24).toString();
diff --git a/playwright-tests/util/rpcmock.js b/playwright-tests/util/rpcmock.js
index 72b74c13..1ea4781a 100644
--- a/playwright-tests/util/rpcmock.js
+++ b/playwright-tests/util/rpcmock.js
@@ -190,3 +190,24 @@ export async function updateDaoPolicyMembers({ page, isMultiVote = false }) {
},
});
}
+
+export async function mockNearBalances({ page, accountId, balance, storage }) {
+ await page.route(
+ `https://api.fastnear.com/v1/account/${accountId}/full`,
+ async (route) => {
+ const json = {
+ account_id: accountId,
+ nfts: [],
+ pools: [],
+ state: {
+ balance: balance,
+ locked: "0",
+ storage: storage,
+ },
+ tokens: [],
+ };
+
+ await route.fulfill({ json });
+ }
+ );
+}
diff --git a/playwright-tests/util/sandboxrpc.js b/playwright-tests/util/sandboxrpc.js
index 9e22f652..f97bad2b 100644
--- a/playwright-tests/util/sandboxrpc.js
+++ b/playwright-tests/util/sandboxrpc.js
@@ -6,7 +6,8 @@ import { KeyPairEd25519 } from "near-api-js/lib/utils/key_pair.js";
import { getLocalWidgetSource } from "./bos-workspace.js";
export const SPUTNIK_DAO_CONTRACT_ID = "sputnik-dao.near";
-export const PROPOSAL_BOND = "100000000000000000000000";
+// we don't have proposal bond for any instance (in this repo)
+export const PROPOSAL_BOND = "0";
export class SandboxRPC {
async init() {
@@ -237,7 +238,7 @@ export class SandboxRPC {
quorum: "0",
threshold: [1, 2],
},
- proposal_bond: "100000000000000000000000",
+ proposal_bond: PROPOSAL_BOND,
proposal_period: "604800000000000",
bounty_bond: "100000000000000000000000",
bounty_forgiveness_period: "604800000000000",