Added search pages to sanity and storefront

This commit is contained in:
Henrik Larsson 2023-08-15 10:44:10 +02:00
parent 2e926ae209
commit 8dff87e62f
15 changed files with 220 additions and 85 deletions

View File

@ -1,5 +1,7 @@
import CategoryPage from '@/components/pages/category-page'; import CategoryPage from '@/components/pages/category-page';
import ProductPage from '@/components/pages/product-page'; import ProductPage from '@/components/pages/product-page';
import SearchPage from '@/components/pages/search-page';
import SearchPagePreview from '@/components/pages/search-page-preview';
import SinglePage from '@/components/pages/single-page'; import SinglePage from '@/components/pages/single-page';
import SinglePagePreview from '@/components/pages/single-page-preview'; import SinglePagePreview from '@/components/pages/single-page-preview';
import PreviewProvider from '@/components/preview-provider'; import PreviewProvider from '@/components/preview-provider';
@ -55,6 +57,8 @@ export default async function Page({ params }: PageParams) {
pageData = await getCachedClient()(query, queryParams); pageData = await getCachedClient()(query, queryParams);
} else if (docType === 'category') { } else if (docType === 'category') {
pageData = await getCachedClient()(query, queryParams); pageData = await getCachedClient()(query, queryParams);
} else if (docType === 'search') {
pageData = await getCachedClient()(query, queryParams);
} else { } else {
return; return;
} }
@ -65,6 +69,7 @@ export default async function Page({ params }: PageParams) {
return ( return (
<PreviewProvider token={preview.token}> <PreviewProvider token={preview.token}>
{docType === 'page' && <SinglePagePreview initialData={pageData} params={queryParams} />} {docType === 'page' && <SinglePagePreview initialData={pageData} params={queryParams} />}
{docType === 'search' && <SearchPagePreview initialData={pageData} params={queryParams} />}
</PreviewProvider> </PreviewProvider>
); );
} }
@ -74,6 +79,7 @@ export default async function Page({ params }: PageParams) {
{docType === 'page' && <SinglePage data={pageData} />} {docType === 'page' && <SinglePage data={pageData} />}
{docType === 'product' && <ProductPage data={pageData} />} {docType === 'product' && <ProductPage data={pageData} />}
{docType === 'category' && <CategoryPage data={pageData} />} {docType === 'category' && <CategoryPage data={pageData} />}
{docType === 'search' && <SearchPage data={pageData} />}
</> </>
); );
} }

View File

@ -22,9 +22,7 @@ export async function GET(request: Request) {
Location: `/${locale}`, Location: `/${locale}`,
}, },
}) })
} } else {
if (type === 'page') {
return new Response(null, { return new Response(null, {
status: 307, status: 307,
headers: { headers: {

View File

@ -0,0 +1,26 @@
'use client';
import PreviewBanner from '@/components/ui/preview-banner';
import { searchPageQuery } from '@/lib/sanity/queries';
import { useLiveQuery } from '@sanity/preview-kit';
import SearchPage from './search-page';
interface SearchPagePreviewParams {
initialData: [];
params: {
locale: string;
slug: string;
};
}
export default function SearchPagePreview({ initialData, params }: SearchPagePreviewParams) {
const [data] = useLiveQuery(initialData, searchPageQuery, params);
return (
<>
<SearchPage data={data && data} />
{/* @ts-ignore */}
<PreviewBanner title={data?.title} type={data?._type} />
</>
);
}

View File

@ -1,17 +1,16 @@
'use client';
import Search from '@/components/search/search'; import Search from '@/components/search/search';
import SearchResult from '@/components/search/search-result'; import SearchResult from '@/components/search/search-result';
import Text from '@/components/ui/text/text'; import Text from '@/components/ui/text/text';
import { useTranslations } from 'next-intl';
export default function SearchPage() { interface SearchPageParams {
const t = useTranslations('search'); data: object | any;
}
export default function SearchPage({ data }: SearchPageParams) {
return ( return (
<div className="my-8 flex w-full flex-col px-4 lg:my-12 lg:px-8 2xl:px-16"> <div className="my-8 flex w-full flex-col px-4 lg:my-12 lg:px-8 2xl:px-16">
<Text className="mb-8 lg:mb-12" variant="pageHeading"> <Text className="mb-8 lg:mb-12" variant="pageHeading">
{t('search')} {data?.title}
</Text> </Text>
<Search> <Search>

View File

@ -1,7 +1,8 @@
import { import {
categoryQuery, categoryQuery,
pageQuery, pageQuery,
productQuery productQuery,
searchPageQuery
} from '@/lib/sanity/queries'; } from '@/lib/sanity/queries';
import { groq } from 'next-sanity'; import { groq } from 'next-sanity';
@ -10,6 +11,7 @@ const getQueryFromSlug = (slugArray: string[], locale: string) => {
'product': groq`${productQuery}`, 'product': groq`${productQuery}`,
'category': groq`${categoryQuery}`, 'category': groq`${categoryQuery}`,
'page': groq`${pageQuery}`, 'page': groq`${pageQuery}`,
'search': groq`${searchPageQuery}`
} }
let docType = '' let docType = ''
@ -26,6 +28,8 @@ const getQueryFromSlug = (slugArray: string[], locale: string) => {
docType = `product` docType = `product`
} else if (slugStart === `kategori` || slugStart === `category`) { } else if (slugStart === `kategori` || slugStart === `category`) {
docType = `category` docType = `category`
} else if (slugStart === `sok` || slugStart === `search`) {
docType = `search`
} else { } else {
docType = `page` docType = `page`
} }

View File

@ -1,16 +1,17 @@
/** /**
* Desk structure overrides * Desk structure overrides
*/ */
import {ListItemBuilder, StructureResolver} from 'sanity/desk' import { ListItemBuilder, StructureResolver } from 'sanity/desk'
import blurbs from './blurb-structure'
import categories from './category-structure' import categories from './category-structure'
import home from './home-structure' import home from './home-structure'
import navigation from './navigation-structure'
import pages from './page-structure' import pages from './page-structure'
import products from './product-structure' import products from './product-structure'
import settings from './settings-structure' import search from './search-structure'
import blurbs from './blurb-structure'
import sections from './section-structure' import sections from './section-structure'
import settings from './settings-structure'
import usps from './usp-structure' import usps from './usp-structure'
import navigation from './navigation-structure'
/** /**
* Desk structure overrides * Desk structure overrides
@ -46,7 +47,8 @@ const hiddenDocTypes = (listItem: ListItemBuilder) => {
'usp', 'usp',
'navigation', 'navigation',
'footerMenu', 'footerMenu',
'utilityMenu' 'utilityMenu',
'search'
].includes(id) ].includes(id)
} }
@ -64,9 +66,9 @@ export const structure: StructureResolver = (S, context) =>
usps(S, context), usps(S, context),
sections(S, context), sections(S, context),
S.divider(), S.divider(),
navigation(S, context),
S.divider(),
settings(S, context), settings(S, context),
search(S, context),
navigation(S, context),
S.divider(), S.divider(),
...S.documentTypeListItems().filter(hiddenDocTypes), ...S.documentTypeListItems().filter(hiddenDocTypes),
S.divider(), S.divider(),

View File

@ -0,0 +1,34 @@
import { EyeOpenIcon, MasterDetailIcon } from '@sanity/icons'
import { SanityDocument } from 'sanity'
import Iframe from 'sanity-plugin-iframe-pane'
import { ListItemBuilder } from 'sanity/desk'
import defineStructure from '../utils/define-structure'
import getPreviewUrl from '../utils/get-preview-url'
export default defineStructure<ListItemBuilder>((S) =>
S.listItem()
.title('Search pages')
.schemaType('search')
.child (
S.documentList()
.title('Search pages')
.filter('_type == "search"')
.child(id =>
S.document()
.schemaType("search")
.id(id)
.views([
S.view
.form()
.icon(MasterDetailIcon),
S.view
.component(Iframe)
.icon(EyeOpenIcon)
.options({
url: (doc: SanityDocument) => getPreviewUrl(doc),
})
.title('Preview')
])
)
)
)

View File

@ -178,6 +178,19 @@ export const homePageQuery = `*[_type == "home" && language == $locale][0] {
} }
}`; }`;
export const searchPageQuery = `*[_type == "search" && language == $locale][0] {
_type,
title,
"locale": language,
"translations": *[_type == "translation.metadata" && references(^._id)].translations[].value->{
title,
"locale": language
},
seo {
${seoFields}
}
}`;
// Page query // Page query
export const pageQuery = `*[_type == "page" && slug.current == $slug && language == $locale][0] { export const pageQuery = `*[_type == "page" && slug.current == $slug && language == $locale][0] {
_type, _type,

View File

@ -1,19 +1,19 @@
import {TagIcon} from '@sanity/icons' import { TagIcon } from '@sanity/icons';
import {defineField, defineType} from 'sanity' import { defineField, defineType } from 'sanity';
import {slugWithLocalizedType} from './slugWithLocalizedType' import { languages } from '../../languages';
import {languages} from '../../languages' import { validateImage } from '../../utils/validation';
import {validateImage} from '../../utils/validation' import { slugWithLocalizedType } from '../slugWithLocalizedType';
const GROUPS = [ const GROUPS = [
{ {
name: 'editorial', name: 'editorial',
title: 'Editorial', title: 'Editorial'
}, },
{ {
name: 'seo', name: 'seo',
title: 'SEO', title: 'SEO'
}, }
] ];
export default defineType({ export default defineType({
name: 'category', name: 'category',
@ -27,7 +27,7 @@ export default defineType({
name: 'language', name: 'language',
type: 'string', type: 'string',
readOnly: true, readOnly: true,
description: 'Language of this document.', description: 'Language of this document.'
// hidden: true, // hidden: true,
}), }),
// Title // Title
@ -35,7 +35,7 @@ export default defineType({
name: 'title', name: 'title',
title: 'Title', title: 'Title',
type: 'string', type: 'string',
description: 'Category title.', description: 'Category title.'
}), }),
// Slug // Slug
slugWithLocalizedType('category', 'title'), slugWithLocalizedType('category', 'title'),
@ -60,67 +60,67 @@ export default defineType({
name: 'image', name: 'image',
type: 'mainImage', type: 'mainImage',
title: 'Image', title: 'Image',
validation: (Rule) => validateImage(Rule, true), validation: (Rule) => validateImage(Rule, true)
}), }),
defineField({ defineField({
name: 'description', name: 'description',
title: 'Description', title: 'Description',
type: 'text', type: 'text',
rows: 5, rows: 5,
description: 'Description of this category.', description: 'Description of this category.'
}), }),
defineField({ defineField({
name: 'id', name: 'id',
title: 'ID', title: 'ID',
type: 'number', type: 'number',
description: 'Unique ID.', description: 'Unique ID.'
}), }),
defineField({ defineField({
name: 'categoryId', name: 'categoryId',
title: 'Category ID', title: 'Category ID',
type: 'number', type: 'number',
description: 'Unique category ID.', description: 'Unique category ID.'
}), }),
defineField({ defineField({
name: 'parentId', name: 'parentId',
title: 'Parent ID', title: 'Parent ID',
type: 'number', type: 'number',
description: 'Unique parent category ID.', description: 'Unique parent category ID.'
}), }),
// SEO // SEO
defineField({ defineField({
name: 'seo', name: 'seo',
title: 'SEO', title: 'SEO',
type: 'seo', type: 'seo',
group: 'seo', group: 'seo'
}), })
], ],
orderings: [ orderings: [
{ {
name: 'titleAsc', name: 'titleAsc',
title: 'Title (A-Z)', title: 'Title (A-Z)',
by: [{field: 'title', direction: 'asc'}], by: [{ field: 'title', direction: 'asc' }]
}, },
{ {
name: 'titleDesc', name: 'titleDesc',
title: 'Title (Z-A)', title: 'Title (Z-A)',
by: [{field: 'title', direction: 'desc'}], by: [{ field: 'title', direction: 'desc' }]
}, }
], ],
preview: { preview: {
select: { select: {
title: 'title', title: 'title',
language: 'language', language: 'language'
}, },
prepare(selection) { prepare(selection) {
const {title, language} = selection const { title, language } = selection;
const currentLang = languages.find((lang) => lang.id === language) const currentLang = languages.find((lang) => lang.id === language);
return { return {
title, title,
subtitle: `${currentLang ? currentLang.title : ''}`, subtitle: `${currentLang ? currentLang.title : ''}`,
media: TagIcon, media: TagIcon
} };
}, }
}, }
}) });

View File

@ -1,8 +1,8 @@
import {DocumentIcon} from '@sanity/icons' import { DocumentIcon } from '@sanity/icons';
import {defineField} from 'sanity' import { defineField } from 'sanity';
import {languages} from '../../languages' import { COMPONENT_REFERENCES } from '../../constants';
import {COMPONENT_REFERENCES} from '../../constants' import { languages } from '../../languages';
import {slugWithLocalizedType} from './slugWithLocalizedType' import { slugWithLocalizedType } from '../slugWithLocalizedType';
export default defineField({ export default defineField({
name: 'page', name: 'page',
@ -12,19 +12,19 @@ export default defineField({
groups: [ groups: [
{ {
name: 'editorial', name: 'editorial',
title: 'Editorial', title: 'Editorial'
}, },
{ {
name: 'seo', name: 'seo',
title: 'SEO', title: 'SEO'
}, }
], ],
fields: [ fields: [
defineField({ defineField({
name: 'language', name: 'language',
type: 'string', type: 'string',
readOnly: true, readOnly: true,
description: 'Language of this document.', description: 'Language of this document.'
// hidden: true, // hidden: true,
}), }),
// Title // Title
@ -33,7 +33,7 @@ export default defineField({
title: 'Title', title: 'Title',
type: 'string', type: 'string',
description: 'Page title.', description: 'Page title.',
validation: (Rule) => Rule.required(), validation: (Rule) => Rule.required()
}), }),
// Slug // Slug
slugWithLocalizedType('page', 'title'), slugWithLocalizedType('page', 'title'),
@ -44,32 +44,32 @@ export default defineField({
type: 'array', type: 'array',
group: 'editorial', group: 'editorial',
description: 'Add, reorder, edit or delete page sections.', description: 'Add, reorder, edit or delete page sections.',
of: COMPONENT_REFERENCES, of: COMPONENT_REFERENCES
}), }),
// SEO // SEO
defineField({ defineField({
name: 'seo', name: 'seo',
title: 'SEO', title: 'SEO',
type: 'seo', type: 'seo',
group: 'seo', group: 'seo'
}), })
], ],
preview: { preview: {
select: { select: {
seoImage: 'seo.image', seoImage: 'seo.image',
title: 'title', title: 'title',
language: 'language', language: 'language'
}, },
prepare(selection) { prepare(selection) {
const {seoImage, title, language} = selection const { seoImage, title, language } = selection;
const currentLang = languages.find((lang) => lang.id === language) const currentLang = languages.find((lang) => lang.id === language);
return { return {
media: seoImage, media: seoImage,
title, title,
subtitle: `${currentLang ? currentLang.title : ''}`, subtitle: `${currentLang ? currentLang.title : ''}`
} };
}, }
}, }
}) });

View File

@ -1,8 +1,8 @@
import { PackageIcon } from '@sanity/icons'; import { PackageIcon } from '@sanity/icons';
import { defineField, defineType } from 'sanity'; import { defineField, defineType } from 'sanity';
import { slugWithLocalizedType } from './slugWithLocalizedType';
import { languages } from '../../languages'; import { languages } from '../../languages';
import { validateImage } from '../../utils/validation'; import { validateImage } from '../../utils/validation';
import { slugWithLocalizedType } from '../slugWithLocalizedType';
const GROUPS = [ const GROUPS = [
{ {

View File

@ -12,14 +12,14 @@
// ] // ]
// Document types // Document types
import blurb from './documents/blurb'
import category from './documents/category' import category from './documents/category'
import footerMenu from './documents/footerMenu'
import page from './documents/page' import page from './documents/page'
import product from './documents/product' import product from './documents/product'
import productVariant from './documents/productVariant' import productVariant from './documents/productVariant'
import blurb from './documents/blurb'
import section from './documents/section' import section from './documents/section'
import usp from './documents/usp' import usp from './documents/usp'
import footerMenu from './documents/footerMenu'
const documents = [ const documents = [
category, category,
@ -34,11 +34,11 @@ const documents = [
// Singleton document types // Singleton document types
import home from './singletons/home' import home from './singletons/home'
import search from './singletons/search'
import settings from './singletons/settings' import settings from './singletons/settings'
import utilityMenu from './singletons/utilityMenu' import utilityMenu from './singletons/utilityMenu'
// import navigation from './singletons/navigation'
const singletons = [home, settings, utilityMenu] const singletons = [home, settings, utilityMenu, search]
// Block content // Block content
import body from './blocks/body' import body from './blocks/body'
@ -47,17 +47,17 @@ const blocks = [body]
// Object types // Object types
import banner from './objects/banner' import banner from './objects/banner'
import linkExternal from './objects/linkExternal'
import linkInternal from './objects/linkInternal'
import hero from './objects/hero'
import seo from './objects/seo'
import mainImage from './objects/mainImage'
import slider from './objects/slider'
import blurbSection from './objects/blurbSection' import blurbSection from './objects/blurbSection'
import filteredProductList from './objects/filteredProductList' import filteredProductList from './objects/filteredProductList'
import uspSection from './objects/uspSection' import hero from './objects/hero'
import reusableSection from './objects/reusableSection' import linkExternal from './objects/linkExternal'
import linkInternal from './objects/linkInternal'
import mainImage from './objects/mainImage'
import menu from './objects/menu' import menu from './objects/menu'
import reusableSection from './objects/reusableSection'
import seo from './objects/seo'
import slider from './objects/slider'
import uspSection from './objects/uspSection'
const objects = [ const objects = [
linkExternal, linkExternal,

View File

@ -0,0 +1,52 @@
import { SearchIcon } from '@sanity/icons';
import { defineField } from 'sanity';
import { languages } from '../../languages';
import { slugWithLocalizedType } from '../slugWithLocalizedType';
export default defineField({
name: 'search',
title: 'Search',
type: 'document',
icon: SearchIcon,
fields: [
defineField({
name: 'language',
type: 'string',
readOnly: true,
description: 'Language of this document.'
// hidden: true,
}),
// Title
{
name: 'title',
title: 'Title',
type: 'string',
description: 'Page title.',
validation: (Rule) => Rule.required()
},
// Slug
slugWithLocalizedType('search', 'title'),
// SEO
defineField({
name: 'seo',
title: 'SEO',
type: 'seo'
})
],
preview: {
select: {
title: 'title',
language: 'language'
},
prepare(selection) {
const { title, language } = selection;
const currentLang = languages.find((lang) => lang.id === language);
return {
title: `${title}`,
subtitle: `${currentLang ? currentLang.title : ''}`
};
}
}
});

View File

@ -1,7 +1,7 @@
import {Rule, Slug} from 'sanity' import { Rule, Slug } from 'sanity';
import slugify from "slugify"; import slugify from "slugify";
import { i18n } from "../../languages"; import { i18n } from "../languages";
import { localizedTypes } from "../../localizedTypes"; import { localizedTypes } from "../localizedTypes";
const MAX_LENGTH = 96 const MAX_LENGTH = 96

View File

@ -13,7 +13,7 @@ const devOnlyPlugins = [visionTool()]
const singletonActions = new Set(["publish", "discardChanges", "restore"]) const singletonActions = new Set(["publish", "discardChanges", "restore"])
// Define the singleton document types // Define the singleton document types
const singletonTypes = new Set(["settings", "home", "utilityMenu", "media.tag"]) const singletonTypes = new Set(["settings", "home", "utilityMenu", "media.tag", ])
// console.log(process.env.SANITY_API_READ_TOKEN) // console.log(process.env.SANITY_API_READ_TOKEN)
@ -45,7 +45,8 @@ export default defineConfig({
'section', 'section',
'usp', 'usp',
'footerMenu', 'footerMenu',
'utilityMenu' 'utilityMenu',
'search'
], ],
// Optional // Optional
// languageField: `language`, // defauts to "language" // languageField: `language`, // defauts to "language"