Skip to content

Instantly share code, notes, and snippets.

@JamesSingleton
Created June 24, 2025 19:37
Show Gist options
  • Save JamesSingleton/65cb4a4b8997289c6f7b851a8c1a2b8e to your computer and use it in GitHub Desktop.
Save JamesSingleton/65cb4a4b8997289c6f7b851a8c1a2b8e to your computer and use it in GitHub Desktop.
import { defineArrayMember, defineField, defineType } from 'sanity'
import { isUnique } from '../../utils/slug'
export const conference = defineType({
name: 'conference',
title: 'Conference',
type: 'document',
fields: [
defineField({
title: 'Name',
name: 'name',
type: 'string',
description: 'The name of the conference.',
validation: (rule) => rule.required(),
}),
defineField({
title: 'Short Name',
name: 'shortName',
type: 'string',
description: 'The short name of the conference.',
}),
defineField({
title: 'Abbreviation',
name: 'abbreviation',
type: 'string',
description: 'The abbreviation of the conference (e.g., "SEC", "ACC").',
}),
defineField({
title: 'Slug',
name: 'slug',
type: 'slug',
options: {
source: 'name',
maxLength: 96,
slugify: (input) =>
input
.toLowerCase()
.trim()
.replace(/\s+/g, '-')
.replace(/[^\w\-]+/g, '')
.replace(/\-\-+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, ''),
isUnique,
},
validation: (rule) => rule.required(),
}),
defineField({
title: 'Logo',
name: 'logo',
type: 'image',
options: {
hotspot: true,
metadata: ['blurhash', 'lqip'],
},
description: 'Please provide a logo for the conference.',
fields: [
defineField({
name: 'alt',
title: 'Alt Text',
type: 'string',
description: 'Just a brief description of the image.',
}),
],
}),
defineField({
title: 'Division',
name: 'division',
type: 'reference',
to: { type: 'division' },
validation: (rule) => rule.required(),
}),
defineField({
title: 'Sports',
name: 'sports',
description: 'The sports that this conference sponsors.',
// validation: (rule) => rule.required(),
type: 'array',
of: [
defineArrayMember({
type: 'reference',
to: { type: 'sport' },
options: {
disableNew: true,
},
}),
],
}),
defineField({
title: 'Sport Subgrouping Affiliations',
name: 'sportSubdivisionAffiliations',
type: 'array',
description:
'For each sport this conference participates in, select the relevant subgrouping (e.g., for Football, select FBS or FCS; for Basketball, select Power 5 or Mid-Major).',
of: [
defineArrayMember({
type: 'object',
name: 'sportSubgroupingAffiliation',
fields: [
defineField({
name: 'sport',
title: 'Sport',
type: 'reference',
description:
'Select the sport and then the subgrouping that applies to this conference.',
to: [{ type: 'sport' }],
validation: (rule) => rule.required(),
options: {
disableNew: true,
filter: ({ document }) => {
const conferenceSportsRefs = Array.isArray(document?.sports)
? document.sports
: []
const conferenceSportIds = conferenceSportsRefs.map((s) => s._ref)
if (conferenceSportIds.length === 0) {
return { filter: 'false' }
}
return {
filter: '_id in $conferenceSportIds',
params: { conferenceSportIds: conferenceSportIds },
}
},
},
}),
defineField({
name: 'subgrouping',
title: 'Subgrouping',
type: 'reference',
to: [{ type: 'sportSubgrouping' }],
validation: (rule) => rule.required(),
description: 'Select the subgrouping that applies to this sport for this conference.',
options: {
disableNew: true,
filter: ({ parent }) => {
const sportRef =
parent && !Array.isArray(parent) && typeof parent === 'object'
? (parent as { sport?: { _ref?: string } }).sport?._ref
: undefined
if (!sportRef) {
return { filter: 'false' }
}
return {
filter: '$sportId in applicableSports[]._ref',
params: { sportId: sportRef },
}
},
},
}),
],
preview: {
select: {
sportTitle: 'sport.title',
subgroupingName: 'subgrouping.name',
subgroupingShortName: 'subgrouping.shortName',
},
prepare: ({ sportTitle, subgroupingName, subgroupingShortName }) => ({
title: sportTitle,
subtitle: subgroupingShortName || subgroupingName,
}),
},
}),
],
validation: (rule) =>
rule.unique().min(1).error('At least one sport subgrouping affiliation is required.'),
}),
],
preview: {
select: {
title: 'name',
subtitle: 'division.title',
media: 'logo',
},
prepare: ({ title, subtitle, media }) => ({
title: title,
subtitle: subtitle,
media,
}),
},
})
import { defineField, defineType } from 'sanity'
import { TextInputWithLimits } from '../../components/text-input-with-limits'
import { isUnique } from '../../utils/slug'
export const division = defineType({
name: 'division',
title: 'Division',
type: 'document',
fields: [
defineField({
title: 'Name',
name: 'name',
type: 'string',
description: 'The name of the division.',
validation: (rule) => rule.required(),
}),
defineField({
title: 'Title',
name: 'title',
type: 'string',
description: 'The title of the division.',
validation: (rule) => rule.required(),
}),
defineField({
title: 'Heading',
name: 'heading',
type: 'string',
description: 'The heading displayed on the page for the division.',
validation: (rule) => rule.required(),
}),
defineField({
title: 'Long Name',
name: 'longName',
type: 'string',
description:
'The long name of the division. For example, "Football Championship Subdivision".',
validation: (rule) => rule.required(),
}),
defineField({
title: 'Slug',
name: 'slug',
type: 'slug',
options: {
source: 'title',
maxLength: 96,
slugify: (input) =>
input
.toLowerCase()
.trim()
.replace(/\s+/g, '-')
.replace(/[^\w\-]+/g, '')
.replace(/\-\-+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, ''),
isUnique,
},
validation: (rule) => rule.required(),
}),
defineField({
title: 'Description',
name: 'description',
type: 'text',
description:
'This will be used for article snippets in social media and Google searches. Ideally between 110 and 160 characters.',
components: {
input: TextInputWithLimits,
},
validation: (rule) => [
rule
.min(140)
.warning(
'The meta description should be at least 140 characters for optimal SEO visibility in search results',
),
rule
.max(160)
.warning(
'The meta description should not exceed 160 characters as it will be truncated in search results',
),
],
}),
defineField({
title: 'Logo',
name: 'logo',
type: 'image',
options: {
hotspot: true,
metadata: ['blurhash', 'lqip'],
},
description: 'Please provide a logo for the division.',
fields: [
defineField({
name: 'alt',
title: 'Alt Text',
type: 'string',
description: 'Just a brief description of the image.',
validation: (rule) => rule.error('You have to fill out the alt text.').required(),
}),
],
}),
],
})
// he final groq query I came up with
async function fetchNavigationData() {
return await sanityFetch({
query: `*[_type == "sport" && count(*[_type == "post" && references(^._id)]) > 0] | order(title asc) {
_id,
"name": title,
"slug": slug.current,
"groupings": select(
slug.current == "football" => [
// FBS Subgrouping
*[_type == "sportSubgrouping" && shortName == "FBS" && count(*[_type == "conference" && references(^._id) && count(*[_type == "post" && references(^._id)]) > 0]) > 0][0]{
_id,
"name": coalesce(shortName, name),
"slug": slug.current,
"type": "subgrouping",
"conferences": *[_type == "conference" && references(^._id) && ^.^._id in sports[]._ref && count(*[_type == "post" && references(^._id)]) > 0] | order(name asc) {
_id,
name,
"slug": slug.current,
shortName
}
},
// FCS Subgrouping
*[_type == "sportSubgrouping" && shortName == "FCS" && count(*[_type == "conference" && references(^._id) && count(*[_type == "post" && references(^._id)]) > 0]) > 0][0]{
_id,
"name": coalesce(shortName, name),
"slug": slug.current,
"type": "subgrouping",
"conferences": *[_type == "conference" && references(^._id) && ^.^._id in sports[]._ref && count(*[_type == "post" && references(^._id)]) > 0] | order(name asc) {
_id,
name,
"slug": slug.current,
shortName
}
},
// Division II
*[_type == "division" && title == "Division II" && count(*[_type == "conference" && references(^._id) && count(*[_type == "post" && references(^._id)]) > 0]) > 0][0]{
_id,
"name": name,
"slug": slug.current,
"type": "division",
"conferences": *[_type == "conference" && references(^._id) && ^.^._id in sports[]._ref && count(*[_type == "post" && references(^._id)]) > 0] | order(name asc) {
_id,
name,
"slug": slug.current,
shortName
}
},
// Division III
*[_type == "division" && title == "Division III" && count(*[_type == "conference" && references(^._id) && count(*[_type == "post" && references(^._id)]) > 0]) > 0][0]{
_id,
"name": name,
"slug": slug.current,
"type": "division",
"conferences": *[_type == "conference" && references(^._id) && ^.^._id in sports[]._ref && count(*[_type == "post" && references(^._id)]) > 0] | order(name asc) {
_id,
name,
"slug": slug.current,
shortName
}
}
],
true => (
// Generic subgroupings
*[_type == "sportSubgrouping" && ^._id in applicableSports[]._ref] | order(name asc) {
_id,
"name": coalesce(shortName, name),
"slug": slug.current,
"type": "subgrouping",
"conferences": *[_type == "conference" && count(sportSubdivisionAffiliations[subgrouping._ref == ^.^._id && sport._ref == ^.^.^._id]) > 0 && count(*[_type == "post" && references(^._id) && sport._ref == ^.^.^._id]) > 0] | order(name asc) {
_id,
name,
shortName,
"slug": slug.current
}
} +
// Generic divisions (excluding specific football and basketball divisions)
*[_type == "division"
&& !(title == "FBS" || title == "FCS")
&& !(
(title == "Division I")
&& (
^.slug.current == "mens-basketball" || ^.slug.current == "womens-basketball"
)
)
] | order(name asc) {
_id,
"name": title,
"slug": slug.current,
"type": "division",
"conferences": *[_type == "conference" && division._ref == ^.^._id && count(*[_type == "post" && references(^._id) && sport->slug.current == ^.^.slug.current]) > 0] | order(name asc) {
_id,
name,
shortName,
"slug": slug.current
}
}
)[defined(conferences) && count(conferences) > 0]
)
}`,
})
}
import { defineArrayMember, defineField, defineType } from 'sanity'
import { PathnameFieldComponent } from '../../components/slug-field-component'
import { StringInputWithLimits } from '../../components/string-input-with-limits'
import { TextInputWithLimits } from '../../components/text-input-with-limits'
import { GROUP, GROUPS } from '../../utils/constant'
import { ogFields } from '../../utils/og-fields'
import { seoFields } from '../../utils/seo-fields'
import { createSlug, isUnique } from '../../utils/slug'
const DIVISION_1_ID = '329c4f4f-bb7c-459e-872d-eb1a57deb196' // Assuming this is the ID for Division 1
export const post = defineType({
name: 'post',
title: 'Post',
type: 'document',
groups: GROUPS,
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
description:
'Make it as enticing as possible to convert users in social feeds and Google searches. Ideally between 15 and 70 characters.',
group: GROUP.MAIN_CONTENT,
validation: (rule) => rule.required(),
components: {
input: StringInputWithLimits,
},
}),
defineField({
name: 'slug',
type: 'slug',
title: 'URL',
group: GROUP.MAIN_CONTENT,
// components: {
// field: PathnameFieldComponent,
// },
options: {
source: 'title',
// slugify: createSlug,
isUnique,
},
validation: (rule) => rule.required(),
}),
defineField({
name: 'author',
title: 'Author',
type: 'reference',
to: { type: 'author' },
options: {
filter: 'archived != true',
disableNew: true,
},
group: GROUP.MAIN_CONTENT,
hidden: true,
}),
defineField({
name: 'authors',
title: 'Authors',
type: 'array',
description:
'If only you wrote the article, select yourself. Otherwise, select the authors that contributed to the article.',
of: [
defineArrayMember({
type: 'reference',
to: [
{
type: 'author',
options: {
disableNew: true,
},
},
],
options: {
disableNew: true,
},
}),
],
validation: (rule) => [rule.required(), rule.unique()],
group: GROUP.MAIN_CONTENT,
}),
defineField({
name: 'publishedAt',
type: 'datetime',
title: 'Published At',
group: GROUP.MAIN_CONTENT,
validation: (rule) => rule.required(),
}),
defineField({
name: 'mainImage',
title: 'Image',
type: 'image',
description:
'Please provide an image for the article. Use something like https://squoosh.app or https://tinypng.com to compress the image first.',
group: GROUP.MAIN_CONTENT,
options: {
hotspot: true,
},
validation: (rule) => rule.required(),
fields: [
defineField({
name: 'caption',
title: 'Caption',
type: 'string',
description: 'Just a brief description of the image.',
validation: (rule) => rule.required(),
}),
defineField({
name: 'attribution',
type: 'string',
title: 'Attribution',
description: 'Who took the photo or where did you get the photo?',
validation: (rule) => rule.required(),
}),
],
}),
defineField({
title: 'Sport',
name: 'sport',
description: 'What sport is this article about?',
type: 'reference',
to: [{ type: 'sport' }],
group: GROUP.MAIN_CONTENT,
options: {
disableNew: true,
},
}),
defineField({
title: 'Division',
name: 'division',
description:
"What's the primary division this article is about? If it's FCS, FBS, Mid-Major, or Power 5, select Division I.",
type: 'reference',
to: [{ type: 'division' }],
group: GROUP.MAIN_CONTENT,
}),
defineField({
name: 'sportSubgrouping',
title: 'Sport Subgrouping',
type: 'reference',
to: [{ type: 'sportSubgrouping' }],
description:
'Select a subgrouping related to the chosen sport (e.g., "FBS" for "Football", "Mid-Major" for "Basketball").',
group: GROUP.MAIN_CONTENT,
// @ts-expect-error `_ref` actually does exist on the document
hidden: ({ document }) => !document?.sport || document?.division?._ref !== DIVISION_1_ID,
options: {
disableNew: true,
filter: ({ document }) => {
// @ts-expect-error `_ref` actually does exist on the document
if (!document.sport?._ref || document.division?._ref !== DIVISION_1_ID) {
return {
filter: `_id == null`,
}
}
return {
filter: 'references($sportId)',
// @ts-expect-error `_ref` actually does exist on the document
params: { sportId: document.sport._ref },
}
},
},
}),
defineField({
title: 'Conferences',
name: 'conferences',
description: 'What conferences does this article mention?',
type: 'array',
of: [
defineArrayMember({
type: 'reference',
to: [{ type: 'conference' }],
}),
],
hidden: ({ document }) => !document?.division,
group: GROUP.MAIN_CONTENT,
}),
defineField({
title: 'Teams Mentioned',
name: 'teams',
description: 'What teams does this article mention?',
type: 'array',
of: [
defineArrayMember({
type: 'reference',
to: [{ type: 'school' }],
}),
],
group: GROUP.MAIN_CONTENT,
}),
defineField({
title: 'Tags',
name: 'tags',
description:
'Add tags to help with the "Related Articles" section on a post. Especially if you are not adding a conference or division.',
type: 'array',
of: [
defineArrayMember({
type: 'reference',
to: [{ type: 'tag' }],
}),
],
group: GROUP.MAIN_CONTENT,
}),
defineField({
title: 'Is this a featured article?',
description: 'Only select Featured if it has been discussed with everyone on the team.',
name: 'featuredArticle',
type: 'boolean',
initialValue: false,
group: GROUP.MAIN_CONTENT,
}),
defineField({
name: 'excerpt',
title: 'Article Excerpt',
type: 'text',
description:
'This will be used for article snippets in social media and Google searches. Ideally between 140 and 160 characters.',
group: GROUP.MAIN_CONTENT,
validation: (rule) => [
rule.required(),
rule
.min(140)
.warning(
'Excerpt should be at least 140 characters for optimal SEO visibility in search results.',
),
rule
.max(160)
.warning(
'Excerpt should not exceed 160 characters as it will be truncated in search results.',
),
],
components: {
input: TextInputWithLimits,
},
}),
defineField({
name: 'body',
title: 'Body',
type: 'blockContent',
group: GROUP.MAIN_CONTENT,
validation: (rule) => rule.required(),
}),
...seoFields,
...ogFields,
],
preview: {
select: {
title: 'title',
media: 'mainImage',
date: 'publishedAt',
},
prepare: ({ title, media, date }) => ({
title,
media,
// add subtitle to preview but only if it's defined
subtitle: date
? `on ${
date &&
new Date(date).toLocaleString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}`
: 'Missing publish date',
}),
},
})
import { defineArrayMember, defineField, defineType } from 'sanity'
export const sportSubgrouping = defineType({
name: 'sportSubgrouping',
title: 'Sport Subgrouping',
type: 'document',
description:
"A subgrouping of a sport, such as 'Football Bowl Subdivision', 'Football Championship Subdivision', 'Mid-Major', or 'Power 5'.",
fields: [
defineField({
name: 'name',
title: 'Name',
type: 'string',
description:
'The full name of the subgrouping (e.g., "Football Bowl Subdivision", "Power 5 Conference").',
validation: (rule) => rule.required(),
}),
defineField({
name: 'shortName',
title: 'Short Name',
type: 'string',
description: 'A shorter version of the name (e.g., "FBS", "Power 5").',
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'shortName',
},
}),
defineField({
name: 'applicableSports',
title: 'Applicable Sports',
type: 'array',
of: [
defineArrayMember({
type: 'reference',
to: [{ type: 'sport' }],
options: {
disableNew: true,
},
}),
],
description:
'The sports this subgrouping applies to (e.g., select both "Men\'s Basketball" and "Women\'s Basketball" for "Power 5").',
validation: (rule) =>
rule.required().min(1).error('At least one applicable sport is required.'),
}),
],
preview: {
select: {
title: 'name',
subtitle: 'applicableSport.title',
},
prepare: ({ title, subtitle }) => ({
title,
subtitle,
}),
},
})
import { defineField, defineType } from 'sanity'
import { GROUPS } from '../../utils/constant'
import { isUnique } from '../../utils/slug'
export const sport = defineType({
name: 'sport',
title: 'Sport',
type: 'document',
groups: GROUPS,
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
validation: (rule) => rule.required(),
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
isUnique,
},
validation: (rule) => rule.required(),
}),
],
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment