From 4370778b9cf572f15ab8d5b6669558bc370816e3 Mon Sep 17 00:00:00 2001 From: Brendon Heinst Date: Sun, 22 Feb 2026 23:36:01 +0100 Subject: [PATCH] Followed Sanity's best practices for a small overhaul. --- components/CountryInput.tsx | 54 + components/TrackDisplayTitleInput.tsx | 125 + components/WorkSlugInput.tsx | 144 + package-lock.json | 4131 +++++++++-------- package.json | 10 +- sanity-typegen.json | 5 + sanity.config.ts | 24 +- sanity.types.ts | 735 +++ schema.json | 4110 ++++++++++++++++ schemaTypes/{artistType.ts => artist-type.ts} | 29 +- schemaTypes/blog-options.ts | 11 + schemaTypes/blog-type.ts | 203 + schemaTypes/composer-type.ts | 94 + schemaTypes/concert-type.ts | 126 + schemaTypes/country-list.ts | 199 + schemaTypes/index.ts | 11 +- schemaTypes/release-type.ts | 563 +++ schemaTypes/releaseType.ts | 249 - schemaTypes/settings-type.ts | 43 + schemaTypes/work-type.ts | 88 + scripts/regenerate-release-slugs.ts | 50 + 21 files changed, 8929 insertions(+), 2075 deletions(-) create mode 100644 components/CountryInput.tsx create mode 100644 components/TrackDisplayTitleInput.tsx create mode 100644 components/WorkSlugInput.tsx create mode 100644 sanity-typegen.json create mode 100644 sanity.types.ts create mode 100644 schema.json rename schemaTypes/{artistType.ts => artist-type.ts} (59%) create mode 100644 schemaTypes/blog-options.ts create mode 100644 schemaTypes/blog-type.ts create mode 100644 schemaTypes/composer-type.ts create mode 100644 schemaTypes/concert-type.ts create mode 100644 schemaTypes/country-list.ts create mode 100644 schemaTypes/release-type.ts delete mode 100644 schemaTypes/releaseType.ts create mode 100644 schemaTypes/settings-type.ts create mode 100644 schemaTypes/work-type.ts create mode 100644 scripts/regenerate-release-slugs.ts diff --git a/components/CountryInput.tsx b/components/CountryInput.tsx new file mode 100644 index 0000000..b1a18f9 --- /dev/null +++ b/components/CountryInput.tsx @@ -0,0 +1,54 @@ +import React, {useCallback, useMemo, useState} from 'react' +import {Autocomplete, Card, Text} from '@sanity/ui' +import {set, unset, StringInputProps} from 'sanity' +import {countryList} from '../schemaTypes/country-list' + +const options = countryList.map((c) => ({value: c.value, payload: c})) + +export function CountryInput(props: StringInputProps) { + const {value, onChange, readOnly} = props + const [query, setQuery] = useState('') + + const filtered = useMemo(() => { + if (!query) return options + const q = query.toLowerCase() + return options.filter((o) => o.payload.title.toLowerCase().includes(q)) + }, [query]) + + const handleSelect = useCallback( + (val: string) => { + onChange(val ? set(val) : unset()) + }, + [onChange], + ) + + const renderOption = useCallback( + (option: (typeof options)[number]) => ( + + {option.payload.title} + + ), + [], + ) + + const selectedTitle = useMemo(() => { + if (!value) return undefined + return countryList.find((c) => c.value === value)?.title + }, [value]) + + return ( + setQuery(q ?? '')} + placeholder="Search for a country…" + renderOption={renderOption} + value={value || ''} + readOnly={readOnly} + openButton + filterOption={() => true} + renderValue={() => selectedTitle || value || ''} + /> + ) +} diff --git a/components/TrackDisplayTitleInput.tsx b/components/TrackDisplayTitleInput.tsx new file mode 100644 index 0000000..04d73f2 --- /dev/null +++ b/components/TrackDisplayTitleInput.tsx @@ -0,0 +1,125 @@ +import React, {useEffect, useMemo, useRef, useState} from 'react' +import {Button, Card, Flex, Stack, Text, TextInput} from '@sanity/ui' +import {set, unset, StringInputProps, useClient, useFormValue} from 'sanity' + +type WorkDoc = { + _id: string + title?: string +} + +function computeTitle(workTitle: string | undefined, movement: string | undefined) { + const wt = (workTitle || '').trim() + const mv = (movement || '').trim() + + if (wt && mv) return `${wt}: ${mv}` + if (wt) return wt + if (mv) return mv + return '' +} + +export function TrackDisplayTitleInput(props: StringInputProps) { + const {value, onChange, readOnly, elementProps} = props + + const workRef = useFormValue(['work', '_ref']) as string | undefined + const movement = useFormValue(['movement']) as string | undefined + + const client = useClient({apiVersion: '2025-01-01'}) + const [workTitle, setWorkTitle] = useState('') + + useEffect(() => { + let cancelled = false + + async function run() { + if (!workRef) { + setWorkTitle('') + return + } + + const doc = await client.fetch(`*[_type == "work" && _id == $id][0]{title}`, { + id: workRef, + }) + + if (!cancelled) setWorkTitle((doc?.title || '').trim()) + } + + run() + return () => { + cancelled = true + } + }, [client, workRef]) + + const computed = useMemo(() => computeTitle(workTitle, movement), [workTitle, movement]) + + const didAutofill = useRef(false) + useEffect(() => { + if (didAutofill.current) return + if (!workRef) return + if (value && String(value).trim()) return + if (!computed) return + if (readOnly) return + + didAutofill.current = true + onChange(set(computed)) + }, [workRef, value, computed, readOnly, onChange]) + + const hasStored = Boolean(value && String(value).trim()) + const inWorkMode = Boolean(workRef) + + if (inWorkMode) { + return ( + + + + + Display title (stored) + + + Generated once from Work/Movement and saved. It will not change automatically if the + Work changes. + + + + + + +