/** * Cmd-K "Pets…" page — browse the petdex gallery, adopt/switch, toggle off. * * A thin view over the `pet-gallery` store: it subscribes to the shared atoms * and calls the store's actions. The store owns fetching, caching, the thumb * cache, and optimistic mutations, so reopening this page is instant and a * toggle never re-pulls the network gallery. */ import { useStore } from '@nanostores/react' import { useEffect, useMemo } from 'react' import { StatusRow } from '@/app/command-palette/status-row' import { HUD_ITEM, HUD_TEXT } from '@/app/floating-hud' import { useGatewayRequest } from '@/app/gateway/hooks/use-gateway-request' import { PetThumb } from '@/components/pet/pet-thumb' import { useI18n } from '@/i18n' import { triggerHaptic } from '@/lib/haptics' import { Check, Egg, Loader2, PawPrint } from '@/lib/icons' import { cn } from '@/lib/utils' import { $petBusy, $petGallery, $petGalleryError, $petGalleryStatus, adoptPet, loadPetGallery, loadPetThumb, rankedGalleryPets, setPetEnabled } from '@/store/pet-gallery' interface PetPalettePageProps { search: string /** Navigate to the "generate a pet" page (rendered as a header action). */ onGenerate?: () => void } export function PetPalettePage({ search, onGenerate }: PetPalettePageProps) { const { t } = useI18n() const copy = t.commandCenter.pets const { requestGateway } = useGatewayRequest() const gallery = useStore($petGallery) const status = useStore($petGalleryStatus) const error = useStore($petGalleryError) const busy = useStore($petBusy) useEffect(() => { void loadPetGallery(requestGateway) }, [requestGateway]) const enabled = gallery?.enabled ?? false const active = gallery?.active ?? '' const shown = useMemo(() => rankedGalleryPets(gallery, search).slice(0, 50), [gallery, search]) const adopt = (slug: string) => { void adoptPet(requestGateway, slug, copy.adoptFailed).then(ok => ok && triggerHaptic('crisp')) } if (status === 'loading' && !gallery) { return } text={copy.loading} /> } if (status === 'stale') { return } if (!gallery?.pets.length && error) { return } const mutating = Boolean(busy) return (
{onGenerate && ( )} {error &&

{error}

} {shown.length === 0 ? ( ) : ( shown.map(pet => { const isActive = enabled && pet.slug === active const isBusy = busy === pet.slug return ( ) }) )}
) } /** * Single on/off toggle, rendered inline on the palette's search row (see * `CommandInput`'s `right` slot). The paw lights up when pets are on. Reads the * same shared gallery atoms, so it stays in sync with the list below. */ export function PetInlineToggle() { const { t } = useI18n() const copy = t.commandCenter.pets const { requestGateway } = useGatewayRequest() const gallery = useStore($petGallery) const busy = useStore($petBusy) if (!gallery) { return null } const enabled = gallery.enabled const toggle = () => { void setPetEnabled(requestGateway, !enabled, { noneAvailable: copy.noneAvailable, fallback: copy.toggleFailed(!enabled) }).then(ok => ok && triggerHaptic('crisp')) } return ( ) }