Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added low and insufficient balance UI #200

Merged
merged 7 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion instances/treasury-devdao.near/widget/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ function Page() {

return (
<Theme>
<AppLayout page={page} instance={instance} treasuryDaoID={treasuryDaoID}>
<AppLayout
page={page}
instance={instance}
treasuryDaoID={treasuryDaoID}
accountId={context.accountId}
>
<Page />
</AppLayout>
</Theme>
Expand Down
88 changes: 88 additions & 0 deletions instances/treasury-devdao.near/widget/components/BalanceBanner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const { getNearBalances } = VM.require(
"${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common"
);

const Container = styled.div`
.low-balance-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;

h5 {
color: var(--other-warning) !important;
}
}

.insufficient-balance-warning {
background-color: rgba(217, 92, 74, 0.08);
color: var(--other-red);
padding-inline: 0.8rem;
padding-block: 0.5rem;
font-weight: 500;
font-size: 13px;

h5 {
color: var(--other-red) !important;
}
}
`;

function formatNearAmount(amount) {
return parseFloat(
Big(amount ?? "0")
.div(Big(10).pow(24))
.toFixed(2)
);
}

function BalanceBanner({ accountId, treasuryDaoID }) {
if (!treasuryDaoID || typeof getNearBalances !== "function" || !accountId) {
return <></>;
}

const nearBalances = getNearBalances(accountId);

const LOW_BALANCE_LIMIT = 0.7; // 0.7N
const INSUFFICIENT_BALANCE_LIMIT = 0.1; // 0.1N

const profile = Social.getr(`${accountId}/profile`);
const name = profile.name ?? accountId;

return (
<Container>
{!nearBalances ||
!nearBalances.availableParsed ||
nearBalances.availableParsed === "0.00" ||
parseFloat(nearBalances.availableParsed) > LOW_BALANCE_LIMIT ? (
<></>
) : parseFloat(nearBalances.availableParsed) <
INSUFFICIENT_BALANCE_LIMIT ? (
<div className="insufficient-balance-warning d-flex gap-3 p-3 rounded-3 m-3">
<i class="bi bi-exclamation-octagon error-icon h4 mb-0"></i>
<div>
<h5>Insufficient Funds</h5>
Hey {name}, you don't have enough NEAR to complete actions on your
treasury. You need at least {INSUFFICIENT_BALANCE_LIMIT}N. Please
add more funds to your account and try again
</div>
</div>
) : (
<div className="low-balance-warning d-flex gap-3 p-3 rounded-3 m-3">
<i class="bi bi-exclamation-triangle warning-icon h4 mb-0"></i>
<div>
<h5>Low Balance</h5>
Hey {name}, your NEAR balance is {nearBalances.availableParsed}N,
which is getting low. The minimum balance required is{" "}
{INSUFFICIENT_BALANCE_LIMIT}N. Please add more NEAR to your account
soon to avoid any issues completing actions on your treasury.
</div>
</div>
)}
</Container>
);
}

return { BalanceBanner };
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const { getNearBalances } = VM.require(
"${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common"
);

const { Modal, ModalContent, ModalHeader, ModalFooter } = VM.require(
"${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.modal"
);

function formatNearAmount(amount) {
return parseFloat(
Big(amount ?? "0")
.div(Big(10).pow(24))
.toFixed(2)
);
}

const { ActionButton, checkForDeposit, treasuryDaoID, callbackAction } = props;

const nearBalances = getNearBalances(context.accountId);
const profile = Social.getr(`${context.accountId}/profile`);
const name = profile.name ?? context.accountId;
const daoPolicy = useCache(
() => Near.asyncView(treasuryDaoID, "get_policy"),
"get_policy",
{ subscribe: false }
);

const [showModal, setShowModal] = useState(false);
const ADDITIONAL_AMOUNT = checkForDeposit
? formatNearAmount(daoPolicy?.proposal_bond)
: 0;

const INSUFFICIENT_BALANCE_LIMIT = ADDITIONAL_AMOUNT + 0.1; // 0.1N

function checkBalance() {
if (parseFloat(nearBalances?.availableParsed) < INSUFFICIENT_BALANCE_LIMIT) {
setShowModal(true);
} else {
callbackAction();
}
}

const WarningModal = () => (
<Modal sty>
<ModalHeader>
<div className="d-flex align-items-center justify-content-between mb-2">
<div className="d-flex gap-3">
<i class="bi bi-exclamation-octagon error-icon h4 mb-0"></i>
Insufficient Funds
</div>
<i
className="bi bi-x-lg h4 mb-0 cursor-pointer"
onClick={() => setShowModal(false)}
></i>
</div>
</ModalHeader>
<ModalContent>
Hey {name}, you don't have enough NEAR to complete actions on your
treasury. You need at least {INSUFFICIENT_BALANCE_LIMIT}N{" "}
{checkForDeposit &&
", which includes the proposal bond needed to create a proposal"}
. Please add more funds to your account and try again.
</ModalContent>
</Modal>
);

return (
<>
<div onClick={checkBalance}>
<ActionButton />
</div>
{showModal && <WarningModal />}
</>
);
85 changes: 56 additions & 29 deletions instances/treasury-devdao.near/widget/components/VoteActions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,58 +229,85 @@ return (
hasVotingPermission && (
<div className="d-flex gap-2 align-items-center">
<Widget
src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}
src={`${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.InsufficientBannerModal`}
props={{
classNames: {
root: "approve-btn p-2",
},
label: "Approve",
onClick: () => {
ActionButton: () => (
<Widget
src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}
props={{
classNames: {
root: "approve-btn p-2",
},
label: "Approve",
loading: isTxnCreated && vote === actions.APPROVE,
disabled: isTxnCreated,
}}
/>
),
checkForDeposit: false,
treasuryDaoID,
callbackAction: () => {
if (isInsufficientBalance) {
setShowWarning(true);
} else {
setVote(actions.APPROVE);
setConfirmModal(true);
}
},
loading: isTxnCreated && vote === actions.APPROVE,
disabled: isTxnCreated,
}}
/>
<Widget
src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}
src={`${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.InsufficientBannerModal`}
props={{
classNames: {
root: "reject-btn p-2",
},
label: "Reject",
onClick: () => {
ActionButton: () => (
<Widget
src={`${REPL_DEVHUB}/widget/devhub.components.molecule.Button`}
props={{
classNames: {
root: "reject-btn p-2",
},
label: "Reject",
loading: isTxnCreated && vote === actions.REJECT,
disabled: isTxnCreated,
}}
/>
),
checkForDeposit: false,
treasuryDaoID,
callbackAction: () => {
setVote(actions.REJECT);
setConfirmModal(true);
},
loading: isTxnCreated && vote === actions.REJECT,
disabled: isTxnCreated,
}}
/>
</div>
)
)}
{/* currently showing delete btn only for proposal creator */}
{hasDeletePermission && proposalCreator === accountId && (
<button
className="remove-btn p-2"
onClick={() => {
setVote(actions.REMOVE);
setConfirmModal(true);
<Widget
src={`${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.InsufficientBannerModal`}
props={{
ActionButton: () => (
<button
className="remove-btn p-2"
data-testid="delete-btn"
disabled={isTxnCreated}
>
<img
style={{ height: 30 }}
src="https://ipfs.near.social/ipfs/bafkreieobqzwouuadj7eneei7aadwfel6ubhj7qishnqwrlv5ldgcwuyt4"
/>
</button>
),
checkForDeposit: false,
treasuryDaoID,
callbackAction: () => {
setVote(actions.REMOVE);
setConfirmModal(true);
},
}}
data-testid="delete-btn"
disabled={isTxnCreated}
>
<img
style={{ height: 30 }}
src="https://ipfs.near.social/ipfs/bafkreieobqzwouuadj7eneei7aadwfel6ubhj7qishnqwrlv5ldgcwuyt4"
/>
</button>
/>
)}
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
const { BalanceBanner } = VM.require(
`${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.BalanceBanner`
) || { BalanceBanner: () => <></> };

const AppHeader = ({ page, instance }) => (
<Widget
src="${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.Navbar"
Expand All @@ -8,7 +12,7 @@ const AppHeader = ({ page, instance }) => (
/>
);

function AppLayout({ page, instance, children, treasuryDaoID }) {
function AppLayout({ page, instance, children, treasuryDaoID, accountId }) {
const config = treasuryDaoID
? useCache(
() => Near.asyncView(treasuryDaoID, "get_config"),
Expand Down Expand Up @@ -379,6 +383,10 @@ function AppLayout({ page, instance, children, treasuryDaoID }) {
.warning-icon {
color: var(--other-warning) !important;
}

.error-icon {
color: var(--other-red) !important;
}
`;

return !config ? (
Expand All @@ -387,6 +395,7 @@ function AppLayout({ page, instance, children, treasuryDaoID }) {
<ParentContainer data-bs-theme={isDarkTheme ? "dark" : "light"}>
<Theme className="h-100 w-100">
<AppHeader page={page} instance={instance} />
<BalanceBanner accountId={accountId} treasuryDaoID={treasuryDaoID} />
<div className="px-3 py-2 w-100 h-100">{children}</div>
</Theme>
</ParentContainer>
Expand Down
6 changes: 2 additions & 4 deletions instances/treasury-devdao.near/widget/lib/common.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,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();
Expand Down
2 changes: 1 addition & 1 deletion instances/treasury-devdao.near/widget/lib/modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const NoButton = styled.button`

const ModalHeader = ({ children }) => (
<ModalHeaderDiv>
<h5 className="mb-0">{children}</h5>
<h5 className="mb-0 w-100">{children}</h5>
</ModalHeaderDiv>
);

Expand Down
19 changes: 13 additions & 6 deletions instances/treasury-devdao.near/widget/pages/payments/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,19 @@ const SidebarMenu = ({ currentTab }) => {
style={{ paddingBottom: "7px" }}
>
{hasCreatePermission && (
<button
className="primary py-1 px-3 rounded-2 h6 fw-bold d-flex align-items-center gap-2 mb-0"
onClick={() => setShowCreateRequest(true)}
>
<i class="bi bi-plus-lg h5 mb-0"></i>Create Request
</button>
<Widget
src={`${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.InsufficientBannerModal`}
props={{
ActionButton: () => (
<button className="primary py-1 px-3 rounded-2 h6 fw-bold d-flex align-items-center gap-2 mb-0">
<i class="bi bi-plus-lg h5 mb-0"></i>Create Request
</button>
),
checkForDeposit: true,
treasuryDaoID,
callbackAction: () => setShowCreateRequest(true),
}}
/>
)}
<Widget
src={`${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.SettingsDropdown`}
Expand Down
Loading
Loading