151 lines
5.2 KiB
TypeScript
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>
|
|
)
|
|
}
|