refactor(web): shared ConfirmDialog, FormControls, and Modal a11y
Extract reusable confirm/modal shells and bordered form controls, adopt them across list pages and editors, and improve icon-button labels plus KvPage keyboard rows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { errorMessage } from "../api/client.js";
|
||||
import { useOwners, useUpsertSecret } from "../api/hooks.js";
|
||||
import { FormInput, FormSelect } from "./FormControls.jsx";
|
||||
import { Modal } from "./Modal.jsx";
|
||||
|
||||
/**
|
||||
* Add / replace an encrypted secret.
|
||||
@@ -43,85 +45,78 @@ export function SecretEditorModal({
|
||||
const title = mode === "add" ? "New secret" : `Replace ${form.owner}/${form.name}`;
|
||||
|
||||
return (
|
||||
<dialog className="modal modal-open">
|
||||
<div className="modal-box max-w-md">
|
||||
<h3 className="font-bold">{title}</h3>
|
||||
<form className="mt-3 space-y-2" onSubmit={onSubmit}>
|
||||
{mode === "add" ? (
|
||||
<>
|
||||
<label className="form-control w-full">
|
||||
<span className="label py-0 text-sm">Owner</span>
|
||||
{owners.length > 0 ? (
|
||||
<select
|
||||
className="select w-full"
|
||||
value={form.owner}
|
||||
onChange={(e) => setForm({ ...form, owner: e.target.value })}
|
||||
required
|
||||
disabled={lockOwner}
|
||||
>
|
||||
{owners.map((o) => (
|
||||
<option key={o} value={o}>
|
||||
{o}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
className="input w-full"
|
||||
value={form.owner}
|
||||
onChange={(e) => setForm({ ...form, owner: e.target.value })}
|
||||
required
|
||||
disabled={lockOwner}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
<label className="form-control w-full">
|
||||
<span className="label py-0 text-sm">Name</span>
|
||||
<input
|
||||
className="input w-full font-mono"
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
<Modal open onClose={onClose} boxClassName="max-w-md" aria-label={title}>
|
||||
<h3 className="font-bold">{title}</h3>
|
||||
<form className="mt-3 space-y-2" onSubmit={onSubmit}>
|
||||
{mode === "add" ? (
|
||||
<>
|
||||
<label className="form-control w-full">
|
||||
<span className="label py-0 text-sm">Owner</span>
|
||||
{owners.length > 0 ? (
|
||||
<FormSelect
|
||||
className="w-full"
|
||||
value={form.owner}
|
||||
onChange={(e) => setForm({ ...form, owner: e.target.value })}
|
||||
required
|
||||
pattern="[A-Za-z0-9._-]+"
|
||||
title="Letters, numbers, dots, underscores, hyphens"
|
||||
disabled={lockOwner}
|
||||
>
|
||||
{owners.map((o) => (
|
||||
<option key={o} value={o}>
|
||||
{o}
|
||||
</option>
|
||||
))}
|
||||
</FormSelect>
|
||||
) : (
|
||||
<FormInput
|
||||
className="w-full"
|
||||
value={form.owner}
|
||||
onChange={(e) => setForm({ ...form, owner: e.target.value })}
|
||||
required
|
||||
disabled={lockOwner}
|
||||
/>
|
||||
</label>
|
||||
</>
|
||||
) : null}
|
||||
<label className="form-control w-full">
|
||||
<span className="label py-0 text-sm">Value</span>
|
||||
<input
|
||||
type="password"
|
||||
className="input w-full"
|
||||
value={form.value}
|
||||
onChange={(e) => setForm({ ...form, value: e.target.value })}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</label>
|
||||
<p className="text-xs opacity-60">
|
||||
Values are encrypted at rest and never shown again after save. Values shorter than 8
|
||||
characters are not redacted from logs.
|
||||
</p>
|
||||
{upsert.isError ? (
|
||||
<p className="text-error text-sm">{errorMessage(upsert.error)}</p>
|
||||
) : null}
|
||||
<div className="modal-action">
|
||||
<button type="button" className="btn btn-ghost" onClick={onClose}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-primary" disabled={upsert.isPending}>
|
||||
{upsert.isPending ? <span className="loading loading-spinner loading-xs" /> : null}
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog" className="modal-backdrop">
|
||||
<button type="button" onClick={onClose}>
|
||||
close
|
||||
</button>
|
||||
)}
|
||||
</label>
|
||||
<label className="form-control w-full">
|
||||
<span className="label py-0 text-sm">Name</span>
|
||||
<FormInput
|
||||
className="w-full font-mono"
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
required
|
||||
pattern="[A-Za-z0-9._-]+"
|
||||
title="Letters, numbers, dots, underscores, hyphens"
|
||||
/>
|
||||
</label>
|
||||
</>
|
||||
) : null}
|
||||
<label className="form-control w-full">
|
||||
<span className="label py-0 text-sm">Value</span>
|
||||
<FormInput
|
||||
type="password"
|
||||
className="w-full"
|
||||
value={form.value}
|
||||
onChange={(e) => setForm({ ...form, value: e.target.value })}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</label>
|
||||
<p className="text-xs opacity-60">
|
||||
Values are encrypted at rest and never shown again after save. Values shorter than 8
|
||||
characters are not redacted from logs.
|
||||
</p>
|
||||
{upsert.isError ? (
|
||||
<p className="text-error text-sm">{errorMessage(upsert.error)}</p>
|
||||
) : null}
|
||||
<div className="modal-action">
|
||||
<button type="button" className="btn btn-ghost" onClick={onClose}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-primary" disabled={upsert.isPending}>
|
||||
{upsert.isPending ? <span className="loading loading-spinner loading-xs" /> : null}
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user