import { useState, type FormEvent } from 'react' import { Alert } from '@/components/ui/Alert' import { Button, IconButton } from '@/components/ui/Button' import { Dialog } from '@/components/ui/Dialog' import { Toggle } from '@/components/ui/Toggle' import { errorMessage } from '@/lib/api' import type { Garden } from '@/lib/gardens' import { useAddShare, useDisableShareLink, useEnableShareLink, useRemoveShare, useShareLink, useShares, useUpdateShareRole, type ShareRole, } from '@/lib/shares' /** * Owner-only: invite an existing account by email (new invites start as * viewers — tap the role chip to flip to editor), remove a share, and manage the * public read-only link. v1 has no invitation emails; an unknown address gets a * friendly "no account with that email". */ export function ShareDialog({ garden, onClose }: { garden: Garden; onClose: () => void }) { const shares = useShares(garden.id) const add = useAddShare(garden.id) const updateRole = useUpdateShareRole(garden.id) const remove = useRemoveShare(garden.id) const [email, setEmail] = useState('') const [error, setError] = useState(null) const busy = add.isPending || updateRole.isPending || remove.isPending async function onInvite(e: FormEvent) { e.preventDefault() setError(null) const addr = email.trim() if (!addr) return try { await add.mutateAsync({ email: addr, role: 'viewer' }) setEmail('') } catch (err) { setError(errorMessage(err, 'Could not share the garden.')) } } const onMutationError = (fallback: string) => (err: unknown) => setError(errorMessage(err, fallback)) return (
setEmail(e.target.value)} />
{shares.isError && Could not load who this garden is shared with.} {shares.isSuccess && shares.data.length === 0 && (

Not shared with anyone yet — invites go to existing accounts.

)} {shares.data?.map((sh) => (
{sh.email} { setError(null) remove.mutate(sh.userId, { onError: onMutationError('Could not remove that person.') }) }} />
))}
{error && {error}}
) } /** The public read-only link: a toggle, the link itself (tap to copy), and a * way to issue a fresh one that invalidates the old. */ function PublicLinkSection({ gardenId }: { gardenId: number }) { const link = useShareLink(gardenId) const enable = useEnableShareLink(gardenId) const disable = useDisableShareLink(gardenId) const [copied, setCopied] = useState(false) const [error, setError] = useState(null) const token = link.data?.enabled ? link.data.token : undefined const url = token ? `${window.location.origin}/g/${token}` : '' const busy = link.isPending || enable.isPending || disable.isPending const run = (p: Promise, fallback: string) => { setError(null) p.catch((err) => setError(errorMessage(err, fallback))) } async function copy() { if (!url) return try { await navigator.clipboard.writeText(url) setCopied(true) window.setTimeout(() => setCopied(false), 1500) } catch { // Clipboard may be unavailable (non-secure context); the text is selectable. } } return ( <>
Read-only link anyone with it can look, no account needed on ? run(enable.mutateAsync({}), 'Could not create the link.') : run(disable.mutateAsync(), 'Could not turn off the link.') } />
{link.isError && Could not load the public link.} {error && {error}} {url && (
run(enable.mutateAsync({ rotate: true }), 'Could not regenerate the link.')} />
)} ) }