First pass at full implementation

This commit is contained in:
Jon Staab
2026-06-08 15:16:09 -07:00
parent 97cfbd3c22
commit 5545558b0c
11 changed files with 934 additions and 88 deletions
+139
View File
@@ -0,0 +1,139 @@
import { createSignal, Show } from "solid-js"
import type { ResharingSession } from "../../models"
export function ProposeResharing(props: { quorumPubkey: string; onClose: () => void }) {
const [newMembersText, setNewMembersText] = createSignal("")
const [newThreshold, setNewThreshold] = createSignal(2)
const [error, setError] = createSignal("")
function handleSubmit(e: Event) {
e.preventDefault()
setError("")
const lines = newMembersText()
.split("\n")
.map(l => l.trim())
.filter(l => l.length > 0)
if (lines.length === 0) {
setError("Enter at least one member pubkey.")
return
}
const t = newThreshold()
if (t < 1 || t > lines.length) {
setError("Threshold must be between 1 and the number of new members.")
return
}
console.log("ProposeResharing", {
quorumPubkey: props.quorumPubkey,
newMembers: lines,
newThreshold: t,
})
props.onClose()
}
return (
<div class="space-y-4">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Propose Key Rotation</h2>
<p class="text-sm italic text-gray-500 dark:text-neutral-400">
Current members will contribute their shares to bootstrap the new quorum.
</p>
<form onSubmit={handleSubmit} class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-1">
New Members (one pubkey per line)
</label>
<textarea
rows={5}
value={newMembersText()}
onInput={e => setNewMembersText(e.currentTarget.value)}
class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-neutral-600 rounded-lg bg-white dark:bg-neutral-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-1">
New Threshold
</label>
<input
type="number"
min={1}
value={newThreshold()}
onInput={e => setNewThreshold(Number(e.currentTarget.value))}
class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-neutral-600 rounded-lg bg-white dark:bg-neutral-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<Show when={error()}>
<p class="text-sm text-red-600 dark:text-red-400">{error()}</p>
</Show>
<div class="flex gap-2 justify-end">
<button
type="button"
onClick={props.onClose}
class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-neutral-600 text-gray-700 dark:text-neutral-300 hover:bg-gray-50 dark:hover:bg-neutral-700 transition-colors"
>
Cancel
</button>
<button
type="submit"
class="px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700 transition-colors"
>
Propose
</button>
</div>
</form>
</div>
)
}
export function ResharingInviteResponse(props: { session: ResharingSession; onClose: () => void }) {
const truncated = props.session.quorumPubkey.slice(0, 8) + "..." + props.session.quorumPubkey.slice(-8)
function handleParticipate() {
console.log("ResharingInviteResponse: participate", props.session.proposalId)
props.onClose()
}
function handleDecline() {
console.log("ResharingInviteResponse: decline", props.session.proposalId)
props.onClose()
}
return (
<div class="space-y-4">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Resharing Proposal</h2>
<div class="space-y-2 text-sm text-gray-700 dark:text-neutral-300">
<div>
<span class="font-medium">Quorum: </span>
<span class="font-mono">{truncated}</span>
</div>
<div>
<span class="font-medium">New member count: </span>
<span>{props.session.newMembers.length}</span>
</div>
<div>
<span class="font-medium">New threshold: </span>
<span>{props.session.newThreshold}</span>
</div>
</div>
<div class="flex gap-2 justify-end">
<button
type="button"
onClick={handleDecline}
class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-neutral-600 text-gray-700 dark:text-neutral-300 hover:bg-gray-50 dark:hover:bg-neutral-700 transition-colors"
>
Decline
</button>
<button
type="button"
onClick={handleParticipate}
class="px-4 py-2 text-sm font-medium rounded-lg bg-green-600 text-white hover:bg-green-700 transition-colors"
>
Participate
</button>
</div>
</div>
)
}