Files
nq/src/components/forms/ResharingForms.tsx
T
2026-06-09 10:52:57 -07:00

151 lines
5.2 KiB
TypeScript

import { createSignal, Show } from "solid-js"
import type { ResharingSession } from "../../models"
import PubkeyInput from "../PubkeyInput"
import { validateMessagingRelays } from "../../lib/relays"
import { useAutoThreshold } from "../../hooks"
export function ProposeResharing(props: { quorumPubkey: string; onClose: () => void }) {
const [newMembers, setNewMembers] = createSignal<string[]>([])
const [newThreshold, setNewThreshold] = useAutoThreshold(newMembers)
const [error, setError] = createSignal("")
const [submitting, setSubmitting] = createSignal(false)
async function handleSubmit(e: Event) {
e.preventDefault()
setError("")
const members = newMembers()
if (members.length === 0) {
setError("Add at least one member.")
return
}
const t = newThreshold()
if (t < 1 || t > members.length) {
setError("Threshold must be between 1 and the number of new members.")
return
}
setSubmitting(true)
const relayError = await validateMessagingRelays(members)
setSubmitting(false)
if (relayError) {
setError(relayError)
return
}
console.log("ProposeResharing", {
quorumPubkey: props.quorumPubkey,
newMembers: members,
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.5">
New Members
</label>
<PubkeyInput value={newMembers()} onChange={setNewMembers} />
</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}
max={newMembers().length || undefined}
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"
/>
<p class="text-xs text-gray-400 dark:text-neutral-500 mt-1">
{newMembers().length > 0
? `${newThreshold()} of ${newMembers().length} signatures required`
: "Add members first"}
</p>
</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"
disabled={submitting()}
class="px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700 transition-colors"
>
{submitting() ? "Checking…" : "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>
)
}