diff --git a/components/pages/home/HomePage.tsx b/components/pages/home/HomePage.tsx index 410683f..1afa9c1 100644 --- a/components/pages/home/HomePage.tsx +++ b/components/pages/home/HomePage.tsx @@ -48,7 +48,7 @@ export function HomePage({ data, encodeDataAttribute }: HomePageProps) { )} {showcaseProjects && showcaseProjects.length > 0 && ( -
+
{showcaseProjects.map((project, key) => { const href = resolveHref(project._type, project.slug) if (!href) { diff --git a/components/pages/home/PostListItem.tsx b/components/pages/home/PostListItem.tsx index 4e83ba4..acbfa17 100644 --- a/components/pages/home/PostListItem.tsx +++ b/components/pages/home/PostListItem.tsx @@ -2,6 +2,7 @@ import type { PortableTextBlock } from '@portabletext/types' import clsx from 'clsx' import { CustomPortableText } from '@/components/shared/CustomPortableText' +import ImageBox from '@/components/shared/ImageBox' import type { ShowcasePost } from '@/types' interface PostProps { @@ -11,26 +12,51 @@ interface PostProps { export function PostListItem(props: PostProps) { const { post, odd } = props + const formattedDate = new Date(post.publishedAt as string).toLocaleDateString('en-US') + const daysAgo = Math.floor((new Date().getTime() - new Date(post.publishedAt as string).getTime()) / (1000 * 3600 * 24)) return (
-

{post.title} {post.publishedAt?.toString()}

+
+ +
- +

{`${formattedDate} (${daysAgo} days ago)`}

+ +

{post.title}

+ +
+ {post.categories?.map((tag, key) => ( +
+ #{tag} +
+ ))} +
+
+ +
) } diff --git a/components/pages/home/ProjectListItem.tsx b/components/pages/home/ProjectListItem.tsx index e3869f7..6843a98 100644 --- a/components/pages/home/ProjectListItem.tsx +++ b/components/pages/home/ProjectListItem.tsx @@ -37,7 +37,7 @@ export function ProjectListItem(props: ProjectProps) {
diff --git a/sanity.config.ts b/sanity.config.ts index 6a31fe8..004c311 100644 --- a/sanity.config.ts +++ b/sanity.config.ts @@ -13,6 +13,7 @@ import { locate } from '@/sanity/plugins/locate' import { pageStructure, singletonPlugin } from '@/sanity/plugins/settings' import page from '@/sanity/schemas/documents/page' import post from '@/sanity/schemas/documents/post' +import member from '@/sanity/schemas/documents/member' import project from '@/sanity/schemas/documents/project' import duration from '@/sanity/schemas/objects/duration' import milestone from '@/sanity/schemas/objects/milestone' @@ -38,11 +39,12 @@ export default defineConfig({ home, settings, // Documents - duration, page, project, post, + member, // Objects + duration, milestone, timeline, socialLink, diff --git a/sanity/lib/queries.ts b/sanity/lib/queries.ts index 88ae51b..c91c703 100644 --- a/sanity/lib/queries.ts +++ b/sanity/lib/queries.ts @@ -15,8 +15,11 @@ export const homePageQuery = groq` showcasePosts[]->{ _type, body, + overview, + coverImage, "slug": slug.current, publishedAt, + categories, title, }, title, diff --git a/sanity/plugins/locate.ts b/sanity/plugins/locate.ts index e7971f9..54e22b6 100644 --- a/sanity/plugins/locate.ts +++ b/sanity/plugins/locate.ts @@ -17,6 +17,7 @@ export const locate: DocumentLocationResolver = (params, context) => { if ( params.type === 'home' || params.type === 'page' || + params.type === 'post' || params.type === 'project' ) { const doc$ = context.documentStore.listenQuery( @@ -87,6 +88,22 @@ export const locate: DocumentLocationResolver = (params, context) => { ? 'This document is used on all pages as it is in the top menu' : undefined, } satisfies DocumentLocationsState + case 'post': + return { + locations: docs + ?.map((doc) => { + const href = resolveHref(doc._type, doc?.slug?.current) + return { + title: doc?.title || 'Untitled', + href: href!, + } + }) + .filter((doc) => doc.href !== undefined), + tone: isReferencedBySettings ? 'caution' : undefined, + message: isReferencedBySettings + ? 'This document is used on all pages as it is in the top menu' + : undefined, + } satisfies DocumentLocationsState default: return { message: 'Unable to map document type to locations', diff --git a/sanity/schemas/documents/member.ts b/sanity/schemas/documents/member.ts new file mode 100644 index 0000000..75fd473 --- /dev/null +++ b/sanity/schemas/documents/member.ts @@ -0,0 +1,95 @@ +import { RocketIcon } from '@sanity/icons' +import { defineArrayMember, defineField } from 'sanity' + +export default { + name: 'member', + title: 'Member', + type: 'document', + icon: RocketIcon, + fields: [ + defineField({ + name: 'title', + description: 'This field is the name of the team member.', + title: 'Title', + type: 'string', + validation: (rule) => rule.required(), + }), + defineField({ + name: 'slug', + title: 'Slug', + type: 'slug', + options: { + source: 'title', + maxLength: 96, + isUnique: (value, context) => context.defaultIsUnique(value, context), + }, + validation: (rule) => rule.required(), + }), + defineField({ + name: 'shortName', + description: 'This field is the displayed short name of the team member.', + title: 'Displayed name', + type: 'string', + validation: (rule) => rule.required(), + }), + defineField({ + name: 'role', + description: 'This field is the role of the team member.', + title: 'Role', + type: 'string', + validation: (rule) => rule.required(), + }), + defineField({ + name: 'overview', + description: + 'Used both for the description tag for SEO, and team member header.', + title: 'Overview', + type: 'array', + of: [ + // Paragraphs + defineArrayMember({ + lists: [], + marks: { + annotations: [], + decorators: [ + { + title: 'Italic', + value: 'em', + }, + { + title: 'Strong', + value: 'strong', + }, + ], + }, + styles: [], + type: 'block', + }), + ], + validation: (rule) => rule.max(155).required(), + }), + defineField({ + name: 'coverImage', + title: 'Cover Image', + description: + 'This image will be used as the image for the team member.', + type: 'image', + options: { + hotspot: true, + }, + validation: (rule) => rule.required(), + }), + ], + preview: { + select: { + title: 'title', + role: 'role', + }, + prepare({ title, role }) { + return { + subtitle: `Role: ${role}`, + title, + } + }, + }, +} diff --git a/sanity/schemas/documents/post.ts b/sanity/schemas/documents/post.ts index 2439a71..ef1bd02 100644 --- a/sanity/schemas/documents/post.ts +++ b/sanity/schemas/documents/post.ts @@ -8,17 +8,60 @@ export default defineType({ icon: ComposeIcon, fields: [ defineField({ - type: 'string', name: 'title', + description: 'This field is the title of your post.', title: 'Title', + type: 'string', validation: (rule) => rule.required(), }), defineField({ - type: 'slug', name: 'slug', title: 'Slug', + type: 'slug', options: { source: 'title', + maxLength: 96, + isUnique: (value, context) => context.defaultIsUnique(value, context), + }, + validation: (rule) => rule.required(), + }), + defineField({ + name: 'overview', + description: + 'Used both for the description tag for SEO, and project subheader.', + title: 'Overview', + type: 'array', + of: [ + // Paragraphs + defineArrayMember({ + lists: [], + marks: { + annotations: [], + decorators: [ + { + title: 'Italic', + value: 'em', + }, + { + title: 'Strong', + value: 'strong', + }, + ], + }, + styles: [], + type: 'block', + }), + ], + validation: (rule) => rule.max(155).required(), + }), + defineField({ + name: 'coverImage', + title: 'Cover Image', + description: + 'This image will be used as the cover image for the project. If you choose to add it to the show case projects, this is the image displayed in the list within the homepage.', + type: 'image', + options: { + hotspot: true, }, validation: (rule) => rule.required(), }), @@ -28,6 +71,15 @@ export default defineType({ title: 'Published at', validation: (rule) => rule.required(), }), + defineField({ + name: 'categories', + title: 'Categories', + type: 'array', + of: [{ type: 'string' }], + options: { + layout: 'tags', + }, + }), defineField({ type: 'array', name: 'body', @@ -57,10 +109,6 @@ export default defineType({ styles: [], }), // Custom blocks - defineArrayMember({ - name: 'timeline', - type: 'timeline', - }), defineField({ type: 'image', icon: ImageIcon, diff --git a/sanity/schemas/documents/project.ts b/sanity/schemas/documents/project.ts index f72108a..0ac8518 100644 --- a/sanity/schemas/documents/project.ts +++ b/sanity/schemas/documents/project.ts @@ -1,11 +1,11 @@ -import { DocumentIcon, ImageIcon } from '@sanity/icons' +import { PresentationIcon, ImageIcon } from '@sanity/icons' import { defineArrayMember, defineField, defineType } from 'sanity' export default defineType({ name: 'project', title: 'Project', type: 'document', - icon: DocumentIcon, + icon: PresentationIcon, // Uncomment below to have edits publish automatically as you type // liveEdit: true, fields: [ @@ -153,4 +153,16 @@ export default defineType({ ], }), ], + preview: { + select: { + title: 'title', + client: 'client', + }, + prepare({ title, client }) { + return { + subtitle: `Project for ${client}`, + title, + } + }, + }, }) diff --git a/types/index.ts b/types/index.ts index 7e220f4..a3b27c4 100644 --- a/types/index.ts +++ b/types/index.ts @@ -43,9 +43,12 @@ export interface ShowcaseProject { export interface ShowcasePost { _type: string + coverImage?: Image + overview?: PortableTextBlock[] body?: PortableTextBlock[] slug?: string publishedAt?: string + categories?: string[] title?: string }