Copy a garden (deep duplicate) from the gardens list (#46)
Build image / build-and-push (push) Successful in 16s
Build image / build-and-push (push) Successful in 16s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #46.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { Alert } from '@/components/ui/Alert'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { Modal } from '@/components/ui/Modal'
|
||||
import { TextField } from '@/components/ui/TextField'
|
||||
import { toast } from '@/components/ui/toast'
|
||||
import { errorMessage } from '@/lib/api'
|
||||
import { defaultCopyName, useCopyGarden, type Garden } from '@/lib/gardens'
|
||||
|
||||
/**
|
||||
* Duplicate a garden under a new name. The name is prefilled with the server's
|
||||
* own default so what you see is what you get; the beds and everything currently
|
||||
* planted in them come along, while the source's share link and shares do not.
|
||||
* On success we land in the copy — the point of copying is to start editing it.
|
||||
*/
|
||||
export function CopyGardenModal({ garden, onClose }: { garden: Garden; onClose: () => void }) {
|
||||
const copy = useCopyGarden()
|
||||
const navigate = useNavigate()
|
||||
const [name, setName] = useState(() => defaultCopyName(garden.name))
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
async function onSubmit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
setError(null)
|
||||
try {
|
||||
const created = await copy.mutateAsync({ id: garden.id, name: name.trim() })
|
||||
toast.info(`Copied to “${created.name}”.`)
|
||||
onClose()
|
||||
navigate({ to: '/gardens/$gardenId', params: { gardenId: String(created.id) } })
|
||||
} catch (err) {
|
||||
setError(errorMessage(err, 'Could not copy the garden.'))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal title="Copy garden" onClose={onClose} busy={copy.isPending}>
|
||||
<form onSubmit={onSubmit} className="flex flex-col gap-4">
|
||||
<p className="text-sm text-muted">
|
||||
Make a copy of <span className="font-medium text-fg">{garden.name}</span> with its beds and
|
||||
everything planted in them. The copy is private — the original's share link and people you've
|
||||
shared it with aren't carried over.
|
||||
</p>
|
||||
<TextField
|
||||
label="Name"
|
||||
name="name"
|
||||
required
|
||||
autoFocus
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
{error && <Alert>{error}</Alert>}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="ghost" onClick={onClose} disabled={copy.isPending}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={copy.isPending || name.trim() === ''}>
|
||||
{copy.isPending ? 'Copying…' : 'Copy garden'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -5,14 +5,16 @@ import { cardActionClass, cardDangerClass } from '@/components/ui/cardActions'
|
||||
|
||||
/**
|
||||
* One garden as a card: the body links into the editor. The footer differs by
|
||||
* role — the owner gets Share / Edit / Delete; a recipient sees a "shared · role"
|
||||
* badge and a Leave action (garden metadata edit + sharing are owner-only).
|
||||
* role — the owner gets Share / Copy / Edit / Delete; a recipient sees a
|
||||
* "shared · role" badge and a Leave action (garden metadata edit, sharing and
|
||||
* copying are owner-only).
|
||||
* Ownership is the authoritative ownerId==me check, not the my_role hint.
|
||||
*/
|
||||
export function GardenCard({
|
||||
garden,
|
||||
currentUserId,
|
||||
onShare,
|
||||
onCopy,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onLeave,
|
||||
@@ -20,6 +22,7 @@ export function GardenCard({
|
||||
garden: Garden
|
||||
currentUserId?: number
|
||||
onShare: () => void
|
||||
onCopy: () => void
|
||||
onEdit: () => void
|
||||
onDelete: () => void
|
||||
onLeave: () => void
|
||||
@@ -51,6 +54,9 @@ export function GardenCard({
|
||||
<button type="button" onClick={onShare} className={cardActionClass}>
|
||||
Share
|
||||
</button>
|
||||
<button type="button" onClick={onCopy} className={cardActionClass}>
|
||||
Copy
|
||||
</button>
|
||||
<button type="button" onClick={onEdit} className={cardActionClass}>
|
||||
Edit
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user