Ported sanity studio to Next js app

This commit is contained in:
Henrik Larsson
2023-08-14 12:06:46 +02:00
parent de85d266bd
commit d32baa7782
67 changed files with 3755 additions and 46 deletions

View File

@@ -0,0 +1,50 @@
import {defineField} from 'sanity'
import { validateImage } from '../../utils/validation'
export default defineField({
name: 'banner',
type: 'object',
title: 'Banner',
description: 'Normally used in the top of a page to display current page information.',
fields: [
{
name: 'title',
type: 'string',
title: 'Title',
description: 'What do you want to convey?',
validation: Rule => [
Rule.required(),
Rule.max(50).warning('Shorter titles are usually better.')
]
},
{
name: 'text',
type: 'text',
title: 'Text',
rows: 5,
description: 'Small text below title.'
},
{
name: 'image',
type: 'mainImage',
title: 'Image',
validation: (Rule) => validateImage(Rule, true)
},
],
preview: {
select: {
title: 'title',
image: 'image',
text: 'text'
},
prepare(selection) {
const {title, image, text} = selection
return {
title: `${title}`,
subtitle: `Banner`,
media: image
}
},
},
})

View File

@@ -0,0 +1,132 @@
import {defineField} from 'sanity'
import {CommentIcon} from '@sanity/icons'
export default defineField({
name: 'blurbSection',
type: 'object',
title: 'Blurb section',
icon: CommentIcon,
fieldsets: [
{
name: 'layoutSettings',
title: 'Layout settings',
},
],
fields: [
{
name: 'disabled',
type: 'boolean',
title: 'Disabled?',
description: 'Set to true to disable this section.',
initialValue: 'false',
validation: (Rule) => Rule.required(),
},
{
name: 'title',
type: 'string',
title: 'Title',
description: 'Text displayed above blurbs.',
validation: (Rule) => Rule.required(),
},
{
name: 'mobileLayout',
type: 'string',
title: 'Mobile layout',
initialValue: 'stacked',
fieldset: 'layoutSettings',
description: 'Display blurbs stacked on top of each other or in a slider.',
validation: (Rule) => Rule.required(),
options: {
list: [
{
title: 'Vertical (1 column)',
value: 'vertical',
},
{
title: 'Horizontal (1 row with scroll)',
value: 'horizontal',
},
],
layout: 'radio',
direction: 'horizontal',
},
},
{
name: 'desktopLayout',
type: 'string',
title: 'Desktop layout',
initialValue: '3-column',
fieldset: 'layoutSettings',
description: 'Display blurbs in a 2- 3- or 4-column layout.',
validation: (Rule) => Rule.required(),
options: {
list: [
{
title: '2 columns',
value: '2-column',
},
{
title: '3 columns',
value: '3-column',
},
{
title: '4 columns',
value: '4-column',
},
],
layout: 'radio',
direction: 'horizontal',
},
},
{
name: 'imageFormat',
type: 'string',
title: 'Blurb image format',
initialValue: 'square',
description: 'Choose format to display blurb images in.',
validation: (Rule) => Rule.required(),
fieldset: 'layoutSettings',
options: {
list: [
{title: 'Square (1:1)', value: 'square'},
{title: 'Portrait (3:4)', value: 'portrait'},
{title: 'Landscape (16:9)', value: 'landscape'},
],
layout: 'radio',
},
},
{
name: 'blurbs',
type: 'array',
title: 'Blurbs',
description: 'Create blurbs or refer to existing blurbs.',
of: [
{
type: 'reference',
to: [
{
type: 'blurb',
},
],
},
],
validation: (Rule) => Rule.required(),
},
],
preview: {
select: {
title: 'title',
disabled: 'disabled',
},
prepare(selection) {
const {title, disabled} = selection
return {
title: `${title}`,
subtitle: `Blurb section ${disabled ? '(⚠️ Disabled)' : ''}`,
media: CommentIcon,
}
},
},
})

View File

@@ -0,0 +1,70 @@
import {defineField} from 'sanity'
import {FilterIcon} from '@sanity/icons'
export default defineField({
name: 'filteredProductList',
type: 'object',
title: 'Filtered product list',
icon: FilterIcon,
fields: [
{
name: 'disabled',
type: 'boolean',
title: 'Disabled?',
description: 'Set to true to disable this section.',
initialValue: 'false',
validation: (Rule) => Rule.required(),
},
{
name: 'title',
type: 'string',
title: 'Title',
description: 'Text displayed above product list.'
},
{
name: 'productCategories',
type: 'array',
title: 'Product categories',
description: 'Select category/categories to display products from.',
of: [
{
type: 'reference',
to: [{
type: 'category',
}],
options: {
disableNew: true,
},
},
],
validation: (Rule) => Rule.required(),
},
{
name: 'itemsToShow',
type: 'number',
title: 'Number of products',
initialValue: 4,
description: 'Amount of products to be displayed.',
validation: (Rule) => Rule.required(),
options: {
list: [4, 8, 12],
layout: 'radio',
},
},
],
preview: {
select: {
title: 'title',
disabled: 'disabled'
},
prepare(selection) {
const {title, disabled} = selection
return {
title: `${title}`,
subtitle: `Filtered product list ${disabled ? '(⚠️ Disabled)' : ''}`,
media: FilterIcon
}
},
},
})

View File

@@ -0,0 +1,125 @@
import {defineField} from 'sanity'
import {StarIcon} from '@sanity/icons'
import { validateImage } from '../../utils/validation'
export default defineField({
name: 'hero',
type: 'object',
title: 'Hero',
icon: StarIcon,
fieldsets: [
{
name: 'settings',
title: 'Hero settings',
description: 'Hero layout and semantic settings.',
options: {
collapsed: true,
collapsible: true,
},
}
],
fields: [
{
name: 'disabled',
type: 'boolean',
title: 'Disabled?',
description: 'Set to true to disable this section.',
initialValue: false,
validation: (Rule) => Rule.required(),
},
{
name: 'variant',
type: 'string',
title: 'Hero variant',
initialValue: 'fullScreen',
description: 'Choose display for larger screens: Full or half screen height.',
validation: Rule => Rule.required(),
fieldset: 'settings',
options: {
list: [
{title: 'Full screen', value: 'fullScreen'},
{title: '50% height', value: 'halfScreen'},
],
layout: 'radio',
}
},
{
name: 'headingLevel',
type: 'string',
title: 'Heading level',
initialValue: 'h1',
fieldset: 'settings',
description: 'Set appropriate heading level depending on the current document structure.',
options: {
list: [
{title: 'H1', value: 'h1'},
{title: 'H2', value: 'h2'},
],
layout: 'radio',
},
},
{
name: 'label',
type: 'string',
title: 'Label',
description: 'Small text displayed above title.'
},
{
name: 'title',
type: 'string',
title: 'Title',
description: 'What you want to convey.',
validation: Rule => [
Rule.required(),
Rule.max(50).warning('Shorter titles are usually better.')
]
},
{
name: 'text',
type: 'text',
title: 'Text',
rows: 5,
description: 'Short text displayed below title.',
validation: Rule => [
Rule.max(100).warning('Strive to be short, precise and on point.')
]
},
{
name: 'image',
type: 'mainImage',
title: 'Image',
validation: Rule => validateImage(Rule, true),
options: {
hotspot: true,
collapsed: false,
collapsible: true,
},
},
{
name: 'link',
type: 'linkInternal',
title: 'Link',
description: 'Link to internal page.',
options: {
collapsed: true,
collapsible: true,
},
},
],
preview: {
select: {
title: 'title',
image: 'image',
disabled: 'disabled'
},
prepare(selection) {
const {title, image, disabled} = selection
return {
title: `${title}`,
media: image?.asset ? image : StarIcon,
subtitle: `Hero ${disabled ? '(⚠️ Disabled)' : ''}`,
}
},
},
})

View File

@@ -0,0 +1,54 @@
import {EarthGlobeIcon} from '@sanity/icons'
import {defineField} from 'sanity'
export default defineField({
title: 'External Link',
name: 'linkExternal',
type: 'object',
icon: EarthGlobeIcon,
description: 'Link to content on external site.',
fields: [
// Title
defineField({
title: 'Title',
name: 'title',
type: 'string',
description: 'Descriptive text for the content on this link.'
}),
// URL
defineField({
name: 'url',
title: 'URL',
type: 'url',
description: 'Link to websites, e-mail address or phone number.',
validation: (Rule) => Rule.required().uri({scheme: ['http', 'https', 'mailto', 'tel']})
}),
// Open in a new window?
defineField({
title: 'Open in a new window?',
name: 'newWindow',
type: 'boolean',
description: 'If set to true, opens the link in a new window.',
initialValue: false,
})
],
preview: {
select: {
title: 'title',
url: 'url',
},
prepare(selection) {
const {title, url} = selection
let subtitle = []
if (url) {
subtitle.push(`${url}`)
}
return {
title,
subtitle: subtitle.join(' '),
}
},
},
})

View File

@@ -0,0 +1,56 @@
import {LinkIcon} from '@sanity/icons'
import {defineField} from 'sanity'
import {PAGE_REFERENCES} from '../../constants'
export default defineField({
title: 'Internal Link',
name: 'linkInternal',
type: 'object',
description: 'Link to content on this site.',
icon: LinkIcon,
fields: [
// Title
defineField({
title: 'Title',
name: 'title',
type: 'string',
description: 'If empty, displays the current reference title.'
}),
// Reference
defineField({
name: 'reference',
type: 'reference',
title: 'Content reference',
description: 'Link to already created, internal content.',
weak: true,
to: PAGE_REFERENCES,
}),
],
preview: {
select: {
reference: 'reference',
referenceTitle: 'reference.title',
referenceType: 'reference._type',
title: 'title',
},
prepare(selection) {
const {
reference,
referenceTitle,
title,
} = selection
let subtitle = []
if (reference) {
subtitle.push([`${referenceTitle || reference?._id}`])
} else {
subtitle.push('(Nonexistent document reference)')
}
return {
title,
subtitle: subtitle.join(' '),
}
},
},
})

View File

@@ -0,0 +1,26 @@
import { defineField, defineType } from "sanity"
export default defineType({
name: 'mainImage',
type: 'image',
title: 'Main image',
description: 'Select or upload image. Edit by using the `menu` icon. Modify by using the `crop` icon.',
options: {
hotspot: true,
metadata: [
'blurhash', // Default: included
'lqip', // Default: included
'palette', // Default: included
'exif', // Default: not included
'location', // Default: not included
],
},
fields: [
defineField({
name: 'alt',
type: 'string',
title: 'Alternative text',
description: 'Note: Important for SEO and accessibility.',
}),
],
})

View File

@@ -0,0 +1,34 @@
import {MenuIcon} from '@sanity/icons'
import {defineType, defineField} from 'sanity'
export default defineType({
name: 'menu',
title: 'Menu',
type: 'object',
icon: MenuIcon,
groups: [],
fields: [
// Links
defineField({
name: 'links',
title: 'Links',
type: 'array',
of: [
{type: 'linkInternal'},
{type: 'linkExternal'},
],
})
],
preview: {
select: {
title: 'title',
},
prepare(selection) {
const {title} = selection;
return {
title: `${title}`,
}
},
},
})

View File

@@ -0,0 +1,10 @@
import PlaceholderStringInput from '../../components/inputs/PlaceholderString'
export default {
name: 'placeholderString',
title: 'Title',
type: 'string',
components: {
input: PlaceholderStringInput,
},
}

View File

@@ -0,0 +1,25 @@
import {defineField} from 'sanity'
export default defineField({
name: 'productOption',
title: 'Product option',
type: 'object',
fields: [
defineField({
name: 'label',
title: 'Label',
type: 'string',
validation: Rule => Rule.required(),
description: 'Product option label.'
}),
defineField({
name: 'hexColors',
title: 'Color hex code',
type: 'color',
description: 'Hex color code for product option.',
options: {
disableAlpha: true
}
})
],
})

View File

@@ -0,0 +1,32 @@
import {defineField} from 'sanity'
export default defineField({
name: 'productOptions',
title: 'Product options',
type: 'object',
fields: [
defineField({
name: 'id',
title: 'ID (string)',
type: 'string',
validation: Rule => Rule.required(),
description: 'Unique product option ID.'
}),
defineField({
name: 'displayName',
title: 'Display name',
type: 'string',
description: 'Name displayed for this collection of product options.',
validation: Rule => Rule.required(),
}),
defineField({
name: 'values',
title: 'Values',
type: 'array',
description: 'What kind of values are available?',
of: [{type: 'productOption'}],
options: {},
validation: Rule => Rule.required(),
}),
],
})

View File

@@ -0,0 +1,11 @@
import {defineField} from 'sanity'
import ProxyStringInput from '../../components/inputs/ProxyString'
export default defineField({
name: 'proxyString',
title: 'Title',
type: 'string',
components: {
input: ProxyStringInput,
},
})

View File

@@ -0,0 +1,54 @@
import {defineField} from 'sanity'
import {BlockElementIcon} from '@sanity/icons'
export default defineField({
name: 'reusableSection',
type: 'object',
title: 'Reusable section',
icon: BlockElementIcon,
fields: [
{
name: 'disabled',
type: 'boolean',
title: 'Disabled?',
description: 'Set to true to disable this section.',
initialValue: 'false',
validation: (Rule) => Rule.required(),
},
{
name: 'title',
type: 'string',
title: 'Title',
},
{
name: 'section',
type: 'object',
title: 'Section',
description: 'Reference to an existing section (only 1 allowed).',
fields: [
{
title: 'Existing section',
name: 'existingSection',
type: 'reference',
to: [{type: 'section'}],
},
],
},
],
preview: {
select: {
title: 'title',
disabled: 'disabled',
},
prepare(selection) {
const {title, disabled} = selection
return {
title: `${title}`,
subtitle: `Reusable section ${disabled ? '(⚠️ Disabled)' : ''}`,
media: BlockElementIcon,
}
},
},
})

View File

@@ -0,0 +1,45 @@
import {defineField} from 'sanity'
import { validateImage } from '../../utils/validation'
export default defineField({
name: 'seo',
title: 'SEO',
type: 'object',
description: 'Optimise content for search engines.',
options: {
collapsed: false,
collapsible: true,
},
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
validation: (Rule) =>
Rule.max(50).warning('Longer titles may be truncated by search engines'),
description: (
<>
A short and accurate title representative of the content on this page.<br />
If empty, displays the current document title (<i>title</i>).
</>
),
}),
defineField({
name: 'description',
title: 'Description',
type: 'text',
rows: 2,
validation: (Rule) =>
Rule.max(150).warning('Longer descriptions may be truncated by search engines'),
description: 'A brief description of the content on this page.'
}),
defineField({
name: 'image',
title: 'Image',
type: 'mainImage',
validation: Rule => validateImage(Rule, false),
description: 'A representative image of the content on this page.'
}),
],
validation: (Rule) => Rule.required(),
})

View File

@@ -0,0 +1,97 @@
import {defineField} from 'sanity'
import {PackageIcon} from '@sanity/icons'
import {TagIcon, NumberIcon} from '@sanity/icons'
export default defineField({
name: 'slider',
type: 'object',
title: 'Slider',
icon: NumberIcon,
fields: [
{
name: 'disabled',
type: 'boolean',
title: 'Disabled?',
description: 'Set to true to disable this section.',
initialValue: 'false',
validation: (Rule) => Rule.required(),
},
{
name: 'title',
type: 'string',
title: 'Title',
description: 'Title displayed above slider items.',
validation: Rule => Rule.required(),
},
{
name: 'sliderType',
type: 'string',
title: 'Slider type',
initialValue: 'products',
description: 'Select content type to display.',
validation: Rule => Rule.required(),
options: {
list: [
{title: 'Products', value: 'products'},
{title: 'Categories', value: 'categories'},
],
layout: 'radio'
}
},
{
title: 'Products',
name: 'products',
type: 'array',
description: 'Select products to display.',
of: [
{
type: 'reference',
to: [{type: 'product'}],
},
],
validation: Rule => Rule.custom((x:any, context:any) => {
if (context.parent.sliderType == 'products' && context?.parent?.products?.length < 3 || context?.parent?.products?.length > 8) {
return 'Must have between 3 and 8 items'
}
return true
}),
hidden: ({ parent }) => parent?.sliderType !== "products"
},
{
title: 'Categories',
name: 'categories',
type: 'array',
description: 'Select categories to display.',
of: [
{
type: 'reference',
to: [{type: 'category'}],
},
],
validation: Rule => Rule.custom((x:any, context:any) => {
if (context.parent.sliderType == 'categories' && context?.parent?.categories?.length < 3 || context?.parent?.categories?.length > 8) {
return 'Must have between 3 and 8 items'
}
return true
}),
hidden: ({ parent }) => parent?.sliderType !== "categories"
},
],
preview: {
select: {
title: 'title',
sliderType: 'sliderType',
disabled: 'disabled'
},
prepare(selection) {
const {title, sliderType, disabled} = selection
return {
title: `${title}`,
media: sliderType === 'products' ? PackageIcon : TagIcon,
subtitle: `${sliderType === 'products' ? 'Product' : 'Category'} slider ${disabled ? '(⚠️ Disabled)' : ''}`,
}
},
},
})

View File

@@ -0,0 +1,63 @@
import {defineField} from 'sanity'
import {StarIcon} from '@sanity/icons'
export default defineField({
name: 'uspSection',
type: 'object',
title: 'USP section',
icon: StarIcon,
fieldsets: [
{
name: 'layoutSettings',
title: 'Layout settings'
},
],
fields: [
{
name: 'disabled',
type: 'boolean',
title: 'Disabled?',
description: 'Set to true to disable this section.',
initialValue: 'false',
validation: (Rule) => Rule.required(),
},
{
name: 'title',
type: 'string',
title: 'Title',
description: 'Title will only be used internally.',
validation: (Rule) => Rule.required(),
},
{
name: 'usps',
type: 'array',
title: 'USPs',
description: 'Create USPs or refer to existing USP.',
of: [
{
type: 'reference',
to: [{
type: 'usp',
}],
},
],
validation: (Rule) => Rule.required().min(2).max(4),
},
],
preview: {
select: {
title: 'title',
disabled: 'disabled'
},
prepare(selection) {
const {title, disabled} = selection
return {
title: `${title}`,
subtitle: `USP section ${disabled ? '(⚠️ Disabled)' : ''}`,
media: StarIcon,
}
},
},
})