Files
headquarter/apps/web/src/hooks/use-ssh-keys.ts
T
Developer 96ce3f4c53 refactor: extract SshKeysPage components
- Extract use-ssh-keys hook for state management
- Extract SSHKeyCreateForm and SSHKeyList components
- Slim SshKeysPage from 277 to ~80 lines

Quality gates: tsc --noEmit passes, npm run build passes
2026-06-05 19:27:38 +00:00

109 lines
3.4 KiB
TypeScript

import { useState } from "react";
import { createSSHKey, deleteSSHKey, listSSHKeys, signPayload, verifySignature, type SSHKey } from "../api/ssh-keys";
import { useAsyncData } from "./use-async-data";
export const useSSHKeys = () => {
const { data: keys, status, reload: loadKeys } = useAsyncData<SSHKey[]>(listSSHKeys, []);
const [newKeyName, setNewKeyName] = useState("");
const [generating, setGenerating] = useState(false);
const [signPayloads, setSignPayloads] = useState<Record<string, string>>({});
const [signatures, setSignatures] = useState<Record<string, string>>({});
const [signing, setSigning] = useState<Record<string, boolean>>({});
const [verifyPayloads, setVerifyPayloads] = useState<Record<string, string>>({});
const [verifySignatures, setVerifySignatures] = useState<Record<string, string>>({});
const [verifyResults, setVerifyResults] = useState<Record<string, boolean | null>>({});
const [verifying, setVerifying] = useState<Record<string, boolean>>({});
const [mutationError, setMutationError] = useState<string | null>(null);
const safeKeys = keys ?? [];
async function handleGenerate(e: React.FormEvent) {
e.preventDefault();
if (!newKeyName.trim()) return;
try {
setGenerating(true);
await createSSHKey({ name: newKeyName.trim() });
setNewKeyName("");
await loadKeys();
} catch {
setMutationError("Failed to generate SSH key");
} finally {
setGenerating(false);
}
}
async function handleDelete(keyId: string) {
if (!confirm("Are you sure you want to delete this SSH key?")) return;
try {
await deleteSSHKey(keyId);
await loadKeys();
} catch {
setMutationError("Failed to delete SSH key");
}
}
function copyToClipboard(text: string) {
navigator.clipboard.writeText(text);
}
async function handleSign(keyId: string) {
const payload = signPayloads[keyId];
if (!payload?.trim()) return;
try {
setSigning((prev) => ({ ...prev, [keyId]: true }));
const result = await signPayload(keyId, { payload: payload.trim() });
setSignatures((prev) => ({ ...prev, [keyId]: result.signature }));
setMutationError(null);
} catch {
setMutationError("Failed to sign payload");
} finally {
setSigning((prev) => ({ ...prev, [keyId]: false }));
}
}
async function handleVerify(keyId: string) {
const payload = verifyPayloads[keyId];
const signature = verifySignatures[keyId];
if (!payload?.trim() || !signature?.trim()) return;
try {
setVerifying((prev) => ({ ...prev, [keyId]: true }));
const result = await verifySignature(keyId, {
payload: payload.trim(),
signature: signature.trim(),
});
setVerifyResults((prev) => ({ ...prev, [keyId]: result.valid }));
setMutationError(null);
} catch {
setMutationError("Failed to verify signature");
} finally {
setVerifying((prev) => ({ ...prev, [keyId]: false }));
}
}
return {
keys: safeKeys,
status,
loadKeys,
newKeyName,
setNewKeyName,
generating,
mutationError,
setMutationError,
signPayloads,
setSignPayloads,
signatures,
signing,
verifyPayloads,
setVerifyPayloads,
verifySignatures,
setVerifySignatures,
verifyResults,
verifying,
handleGenerate,
handleDelete,
copyToClipboard,
handleSign,
handleVerify,
};
};