Cleanup: ConfirmModal primitive, drop dead PageStub, full-width editor (#107)
Three tidy-ups from the audit's deferred list: - Extracted a ConfirmModal primitive (message + Cancel/Confirm, owning the busy lock + inline error) and folded the five hand-rolled confirm dialogs onto it: DeleteGarden, LeaveGarden, DeletePlant, DeleteSeedLot, ClearBed. Each is now just its message + mutation + labels. Bonus: ClearBed now shows a failure inline instead of swallowing it. (CopyGarden stays on Modal — it has a name field, not a plain confirm.) - Removed components/PageStub.tsx — dead scaffolding, imported nowhere. - The garden editor was squeezed into the max-w-5xl reading measure the other pages use; the canvas routes (editor + public garden) now go edge-to-edge on desktop, and the top bar matches so the brand aligns with the editor's left edge. Mobile was already full-width, so it's unchanged. Verified live: the Delete-garden confirm renders/cancels; the desktop editor now uses the full viewport width. tsc + vitest + build green. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
@@ -1,8 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { Modal } from '@/components/ui/Modal'
|
||||
import { Alert } from '@/components/ui/Alert'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { errorMessage } from '@/lib/api'
|
||||
import { ConfirmModal } from '@/components/ui/ConfirmModal'
|
||||
import { useMe } from '@/lib/auth'
|
||||
import type { Garden } from '@/lib/gardens'
|
||||
import { useRemoveShare } from '@/lib/shares'
|
||||
@@ -12,36 +8,22 @@ import { useRemoveShare } from '@/lib/shares'
|
||||
export function LeaveGardenModal({ garden, onClose }: { garden: Garden; onClose: () => void }) {
|
||||
const me = useMe()
|
||||
const remove = useRemoveShare(garden.id)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
async function onConfirm() {
|
||||
if (!me.data) return
|
||||
setError(null)
|
||||
try {
|
||||
await remove.mutateAsync(me.data.id)
|
||||
onClose()
|
||||
} catch (err) {
|
||||
setError(errorMessage(err, 'Could not leave the garden.'))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal title="Leave garden" onClose={onClose} busy={remove.isPending}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-muted">
|
||||
Leave <span className="font-medium text-fg">{garden.name}</span>? You'll lose access until the owner
|
||||
shares it with you again.
|
||||
</p>
|
||||
{error && <Alert>{error}</Alert>}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="ghost" onClick={onClose} disabled={remove.isPending}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="button" variant="danger" onClick={onConfirm} disabled={remove.isPending || !me.data}>
|
||||
{remove.isPending ? 'Leaving…' : 'Leave'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<ConfirmModal
|
||||
title="Leave garden"
|
||||
confirmLabel="Leave"
|
||||
busyLabel="Leaving…"
|
||||
confirmDisabled={!me.data}
|
||||
errorFallback="Could not leave the garden."
|
||||
onConfirm={async () => {
|
||||
if (me.data) await remove.mutateAsync(me.data.id)
|
||||
}}
|
||||
onClose={onClose}
|
||||
>
|
||||
<p className="text-sm text-muted">
|
||||
Leave <span className="font-medium text-fg">{garden.name}</span>? You'll lose access until the owner
|
||||
shares it with you again.
|
||||
</p>
|
||||
</ConfirmModal>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user