Sharing UI: invite by email, roles, read-only viewer mode (#17)
Build image / build-and-push (push) Successful in 8s
Build image / build-and-push (push) Successful in 8s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #36.
This commit is contained in:
@@ -25,6 +25,7 @@ export function PlopInspector({
|
||||
unit,
|
||||
onChangePlant,
|
||||
onClose,
|
||||
readOnly = false,
|
||||
}: {
|
||||
plop: EditorPlanting
|
||||
plant?: Plant
|
||||
@@ -32,6 +33,7 @@ export function PlopInspector({
|
||||
unit: UnitPref
|
||||
onChangePlant: () => void
|
||||
onClose: () => void
|
||||
readOnly?: boolean
|
||||
}) {
|
||||
const update = useUpdatePlanting(gardenId)
|
||||
const remove = useRemovePlanting(gardenId)
|
||||
@@ -56,8 +58,10 @@ export function PlopInspector({
|
||||
setPlanted(p.plantedAt ?? '')
|
||||
}, [p.version, p.radiusCm, p.count, p.label, p.plantedAt, unit])
|
||||
|
||||
const patch = (fields: Omit<Parameters<typeof update.mutate>[0], 'id' | 'version'>) =>
|
||||
const patch = (fields: Omit<Parameters<typeof update.mutate>[0], 'id' | 'version'>) => {
|
||||
if (readOnly) return // a blur mustn't fire a mutation if the role changed mid-edit
|
||||
update.mutate({ id: plop.id, version: plop.version, ...fields })
|
||||
}
|
||||
|
||||
const u = spacingUnitLabel(unit)
|
||||
const derived = plant ? computeDerivedCount(p.radiusCm, plant.spacingCm) : p.derivedCount
|
||||
@@ -104,6 +108,10 @@ export function PlopInspector({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{readOnly && (
|
||||
<p className="rounded-md bg-border/40 px-2 py-1 text-xs text-muted">View only — you can't edit this garden.</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border p-2">
|
||||
{plant ? (
|
||||
<PlantIcon color={plant.color} icon={plant.icon} className="h-9 w-9 rounded-md text-xl" />
|
||||
@@ -111,66 +119,72 @@ export function PlopInspector({
|
||||
<span className="grid h-9 w-9 place-items-center rounded-md bg-border/50 text-muted">?</span>
|
||||
)}
|
||||
<span className="min-w-0 flex-1 truncate text-sm font-medium text-fg">{plant?.name ?? 'Unknown plant'}</span>
|
||||
<Button variant="ghost" className="px-2 py-1 text-xs" onClick={onChangePlant}>
|
||||
Change
|
||||
{!readOnly && (
|
||||
<Button variant="ghost" className="px-2 py-1 text-xs" onClick={onChangePlant}>
|
||||
Change
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<fieldset disabled={readOnly} className="flex min-w-0 flex-col gap-3 border-0 p-0">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<TextField
|
||||
label={`Radius (${u})`}
|
||||
name="radius"
|
||||
type="number"
|
||||
inputMode="decimal"
|
||||
step="any"
|
||||
min="0"
|
||||
value={radius}
|
||||
onChange={(e) => setRadius(e.target.value)}
|
||||
onBlur={commitRadius}
|
||||
/>
|
||||
<TextField
|
||||
label="Count"
|
||||
name="count"
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
step="1"
|
||||
min="1"
|
||||
placeholder={`${derived} (auto)`}
|
||||
value={count}
|
||||
onChange={(e) => setCount(e.target.value)}
|
||||
onBlur={commitCount}
|
||||
hint={p.count != null ? 'Override — clear for auto' : 'Auto from area ÷ spacing²'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
label="Label (optional)"
|
||||
name="label"
|
||||
value={label}
|
||||
onChange={(e) => setLabel(e.target.value)}
|
||||
onBlur={() => label !== (p.label ?? '') && patch({ label: label.trim() || null })}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Planted"
|
||||
name="planted"
|
||||
type="date"
|
||||
value={planted}
|
||||
onChange={(e) => setPlanted(e.target.value)}
|
||||
onBlur={commitPlanted}
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
{!readOnly && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-red-600 dark:text-red-400"
|
||||
disabled={remove.isPending}
|
||||
onClick={() => {
|
||||
onClose()
|
||||
remove.mutate({ id: plop.id, version: plop.version })
|
||||
}}
|
||||
>
|
||||
Remove plant
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<TextField
|
||||
label={`Radius (${u})`}
|
||||
name="radius"
|
||||
type="number"
|
||||
inputMode="decimal"
|
||||
step="any"
|
||||
min="0"
|
||||
value={radius}
|
||||
onChange={(e) => setRadius(e.target.value)}
|
||||
onBlur={commitRadius}
|
||||
/>
|
||||
<TextField
|
||||
label="Count"
|
||||
name="count"
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
step="1"
|
||||
min="1"
|
||||
placeholder={`${derived} (auto)`}
|
||||
value={count}
|
||||
onChange={(e) => setCount(e.target.value)}
|
||||
onBlur={commitCount}
|
||||
hint={p.count != null ? 'Override — clear for auto' : 'Auto from area ÷ spacing²'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
label="Label (optional)"
|
||||
name="label"
|
||||
value={label}
|
||||
onChange={(e) => setLabel(e.target.value)}
|
||||
onBlur={() => label !== (p.label ?? '') && patch({ label: label.trim() || null })}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Planted"
|
||||
name="planted"
|
||||
type="date"
|
||||
value={planted}
|
||||
onChange={(e) => setPlanted(e.target.value)}
|
||||
onBlur={commitPlanted}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-red-600 dark:text-red-400"
|
||||
disabled={remove.isPending}
|
||||
onClick={() => {
|
||||
onClose()
|
||||
remove.mutate({ id: plop.id, version: plop.version })
|
||||
}}
|
||||
>
|
||||
Remove plant
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user