Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 77 additions & 12 deletions apps/web/src/components/retire-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,12 @@ export function RetireModal({ certificateId, kwh, onConfirm, onClose }: Props) {
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setSubmitting(true)
setError(null)
try {
await onConfirm(reason)
setSucceeded(true)
} catch (err) {
setError(err instanceof Error ? err.message : 'Retirement failed. Please try again.')
} finally {
setSubmitting(false)
}
Expand All @@ -70,7 +74,7 @@ export function RetireModal({ certificateId, kwh, onConfirm, onClose }: Props) {
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
onClick={(e) => e.target === e.currentTarget && onClose()}
onClick={(e) => e.target === e.currentTarget && !submitting && onClose()}
>
<div
ref={dialogRef}
Expand Down Expand Up @@ -124,17 +128,8 @@ export function RetireModal({ certificateId, kwh, onConfirm, onClose }: Props) {

<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
disabled={submitting}
className="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
>
Cancel
</button>
<button
type="submit"
disabled={submitting}
className="flex items-center gap-2 rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 disabled:cursor-not-allowed disabled:opacity-60"
className="mt-2 rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700"
>
{submitting && (
<svg
Expand All @@ -151,7 +146,77 @@ export function RetireModal({ certificateId, kwh, onConfirm, onClose }: Props) {
{submitting ? 'Retiring…' : 'Confirm retirement'}
</button>
</div>
</form>
) : (
<>
<p className="mb-4 text-sm text-gray-600 dark:text-gray-400">
You are about to permanently retire certificate{' '}
<CopyableText value={certificateId} displayValue={`${certificateId.slice(0, 8)}…`} />
{' '}({kwh.toFixed(3)} kWh). This action cannot be undone.
</p>

{error && (
<div
role="alert"
className="mb-4 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700 dark:border-red-800 dark:bg-red-900/20 dark:text-red-400"
>
{error}
</div>
)}

<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="retire-reason"
className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Retirement reason <span className="text-gray-400">(optional)</span>
</label>
<textarea
id="retire-reason"
ref={inputRef}
value={reason}
onChange={(e) => setReason(e.target.value)}
rows={3}
disabled={submitting}
placeholder="e.g. Offset Q1 2026 carbon footprint"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-yellow-400 focus:outline-none focus:ring-1 focus:ring-yellow-400 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-500"
/>
</div>

<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
disabled={submitting}
className="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
>
Cancel
</button>
<button
type="submit"
disabled={submitting}
aria-busy={submitting}
aria-describedby={error ? 'retire-error' : undefined}
className="flex items-center gap-2 rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 disabled:cursor-not-allowed disabled:opacity-60"
>
{submitting && (
<svg
className="h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
aria-hidden="true"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
)}
{submitting ? 'Retiring…' : 'Confirm retirement'}
</button>
</div>
</form>
</>
)}
</div>
</div>
)
Expand Down