From 1050c781bf0ccdd9faf1f31d25ff81ebaf7cd047 Mon Sep 17 00:00:00 2001 From: Timo Clasen Date: Sat, 21 Oct 2023 21:20:33 +0200 Subject: [PATCH] Add LCD Blog Post Teaser (#28) --- .../app/(home)/NewBlogPosts/NewBlogPosts.tsx | 13 ++ apps/website/src/app/(home)/page.tsx | 4 +- apps/website/src/app/blog/[slug]/page.tsx | 89 +--------- apps/website/src/app/blog/page.tsx | 51 +----- .../LcdBlogPosts/LcdBlogPosts.tsx | 18 ++ .../src/app/lifecentereddesign/page.tsx | 2 + .../app/projects/ProjectList/ProjectList.tsx | 5 +- apps/website/src/app/projects/[slug]/page.tsx | 97 +---------- apps/website/src/app/projects/page.tsx | 54 +----- .../BlogTeaser/BlogTeaser.tsx | 74 ++------ apps/website/src/lib/queries.ts | 159 ++++++++++++++++++ 11 files changed, 228 insertions(+), 338 deletions(-) create mode 100644 apps/website/src/app/(home)/NewBlogPosts/NewBlogPosts.tsx create mode 100644 apps/website/src/app/lifecentereddesign/LcdBlogPosts/LcdBlogPosts.tsx rename apps/website/src/{app/(home) => components}/BlogTeaser/BlogTeaser.tsx (59%) diff --git a/apps/website/src/app/(home)/NewBlogPosts/NewBlogPosts.tsx b/apps/website/src/app/(home)/NewBlogPosts/NewBlogPosts.tsx new file mode 100644 index 0000000..0e8e17e --- /dev/null +++ b/apps/website/src/app/(home)/NewBlogPosts/NewBlogPosts.tsx @@ -0,0 +1,13 @@ +import { BlogTeaser } from "../../../components/BlogTeaser/BlogTeaser"; +import { getBlogPosts } from "../../../lib/queries"; + +export const NewBlogPosts = async () => { + const blogPosts = await getBlogPosts(); + return ( + + ); +}; diff --git a/apps/website/src/app/(home)/page.tsx b/apps/website/src/app/(home)/page.tsx index ddf65d1..ae37be4 100644 --- a/apps/website/src/app/(home)/page.tsx +++ b/apps/website/src/app/(home)/page.tsx @@ -1,9 +1,9 @@ import { MyClients } from "../../components/MyClients/MyClients"; import { createGenerateMetadata } from "../../lib/metadata"; -import { BlogTeaser } from "./BlogTeaser/BlogTeaser"; import { Header } from "./Header/Header"; import { HomeAccordion } from "./HomeAccordion/HomeAccordion"; import { LcdTeaser } from "./LcdTeaser/LcdTeaser"; +import { NewBlogPosts } from "./NewBlogPosts/NewBlogPosts"; export const generateMetadata = createGenerateMetadata(async () => ({ other: { @@ -17,7 +17,7 @@ const Home = async () => {
- + ); diff --git a/apps/website/src/app/blog/[slug]/page.tsx b/apps/website/src/app/blog/[slug]/page.tsx index dc69a29..6ebed2f 100644 --- a/apps/website/src/app/blog/[slug]/page.tsx +++ b/apps/website/src/app/blog/[slug]/page.tsx @@ -1,37 +1,16 @@ import { format } from "date-fns"; import { CalendarDays, Clock, Feather, User } from "lucide-react"; -import { groq } from "next-sanity"; import readingTime from "reading-time"; -import { z } from "zod"; import { ArticleHeader } from "../../../components/ArticleHeader/ArticleHeader"; import { MDXContent } from "../../../components/MDXContent/MDXContent"; import { StructuredData } from "../../../components/StructuredData/StructuredData"; import { Container } from "../../../design-system/Container/Container"; import { createGenerateMetadata, ogImage } from "../../../lib/metadata"; -import { queryContent } from "../../../lib/sanity"; +import { getBlogPost, getBlogPosts } from "../../../lib/queries"; export const generateMetadata = createGenerateMetadata(async ({ params }) => { const { slug } = params; - const { title, summary, date, image, content } = await queryContent( - groq` - *[_type == 'blogPost' && slug.current == '${slug}'][0] - { - title, - date, - summary, - 'image': image.asset->url, - content, - } - `, - z.object({ - title: z.string(), - date: z.string(), - summary: z.string().nullable(), - image: z.string(), - content: z.string(), - }), - ); - + const { title, summary, date, image, content } = await getBlogPost(slug); const stats = readingTime(content); return { @@ -50,7 +29,7 @@ export const generateMetadata = createGenerateMetadata(async ({ params }) => { url: ogImage({ overline: "Blog • Katharina Clasen", headline: title, - image, + image: image.url, readingTime: `${Math.ceil(stats.minutes)} min`, date: format(new Date(date), "LLLL dd, yyyy"), }), @@ -70,20 +49,7 @@ export const generateMetadata = createGenerateMetadata(async ({ params }) => { }); export const generateStaticParams = async () => { - const blogPosts = await queryContent( - groq` - *[_type == 'blogPost'] - { - 'slug': slug.current - } - `, - z.array( - z.object({ - slug: z.string(), - }), - ), - ); - + const blogPosts = await getBlogPosts(); return blogPosts.map((blogPost) => ({ slug: blogPost.slug, })); @@ -97,52 +63,7 @@ interface Props { const BlogPostPage = async ({ params }: Props) => { const { slug } = params; - const blogPost = await queryContent( - groq` - *[_type == 'blogPost' && slug.current == '${slug}'][0] - { - _id, - 'slug': slug.current, - title, - summary, - image{'url': asset->url, alt, border}, - author, - date, - services[]->{title}, - topics[]->{title}, - content - } - `, - z.object({ - _id: z.string(), - slug: z.string(), - title: z.string(), - summary: z.string().nullable(), - image: z.object({ - url: z.string(), - alt: z.string(), - border: z.boolean().nullable(), - }), - author: z.string(), - date: z.string(), - services: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - topics: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - content: z.string(), - }), - ); - + const blogPost = await getBlogPost(slug); const stats = readingTime(blogPost.content); return ( <> diff --git a/apps/website/src/app/blog/page.tsx b/apps/website/src/app/blog/page.tsx index 919b196..a9d99ae 100644 --- a/apps/website/src/app/blog/page.tsx +++ b/apps/website/src/app/blog/page.tsx @@ -1,14 +1,11 @@ import { format } from "date-fns"; import { CalendarDays, Clock, Feather, User } from "lucide-react"; -import { groq } from "next-sanity"; import Link from "next/link"; import readingTime from "reading-time"; -import { z } from "zod"; import { ArticlePreview } from "../../components/ArticlePreview/ArticlePreview"; import { Container } from "../../design-system/Container/Container"; import { createGenerateMetadata, ogImage } from "../../lib/metadata"; -import { getMetadata } from "../../lib/queries"; -import { queryContent } from "../../lib/sanity"; +import { getBlogPosts, getMetadata } from "../../lib/queries"; export const generateMetadata = createGenerateMetadata(async () => { const { @@ -43,51 +40,7 @@ export const generateMetadata = createGenerateMetadata(async () => { }); const BlogPage = async () => { - const blogPosts = await queryContent( - groq` - *[_type == 'blogPost'] - { - _id, - title, - 'slug': slug.current, - image{'url': asset->url, alt, border}, - author, - date, - services[]->{title}, - topics[]->{title}, - content - } | order(date desc) - `, - z.array( - z.object({ - _id: z.string(), - title: z.string(), - slug: z.string(), - image: z.object({ - url: z.string(), - alt: z.string(), - border: z.boolean().nullable(), - }), - author: z.string(), - date: z.string(), - services: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - topics: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - content: z.string(), - }), - ), - ); + const blogPosts = await getBlogPosts(); return (
diff --git a/apps/website/src/app/lifecentereddesign/LcdBlogPosts/LcdBlogPosts.tsx b/apps/website/src/app/lifecentereddesign/LcdBlogPosts/LcdBlogPosts.tsx new file mode 100644 index 0000000..d3d5595 --- /dev/null +++ b/apps/website/src/app/lifecentereddesign/LcdBlogPosts/LcdBlogPosts.tsx @@ -0,0 +1,18 @@ +import { BlogTeaser } from "../../../components/BlogTeaser/BlogTeaser"; +import { getBlogPosts } from "../../../lib/queries"; + +export const LcdBlogPosts = async () => { + const blogPosts = await getBlogPosts(); + const lcdBlogPosts = blogPosts.filter( + (blogPost) => + blogPost.topics?.some((topic) => topic.title === "Life-centered Design"), + ); + + return ( + + ); +}; diff --git a/apps/website/src/app/lifecentereddesign/page.tsx b/apps/website/src/app/lifecentereddesign/page.tsx index df41bcf..236bcc3 100644 --- a/apps/website/src/app/lifecentereddesign/page.tsx +++ b/apps/website/src/app/lifecentereddesign/page.tsx @@ -2,6 +2,7 @@ import { createGenerateMetadata, ogImage } from "../../lib/metadata"; import { getMetadata } from "../../lib/queries"; import { Header } from "./Header/Header"; import { LcdAccordion } from "./LcdAccordion/LcdAccordion"; +import { LcdBlogPosts } from "./LcdBlogPosts/LcdBlogPosts"; import { LcdJourney } from "./LcdJourney/LcdJourney"; import { LcdPrinciples } from "./LcdPrinciples/LcdPrinciples"; import { LcdThinking } from "./LcdThinking/LcdThinking"; @@ -50,6 +51,7 @@ const LcdPage = () => { + diff --git a/apps/website/src/app/projects/ProjectList/ProjectList.tsx b/apps/website/src/app/projects/ProjectList/ProjectList.tsx index 03d1ab2..0492446 100644 --- a/apps/website/src/app/projects/ProjectList/ProjectList.tsx +++ b/apps/website/src/app/projects/ProjectList/ProjectList.tsx @@ -4,10 +4,11 @@ import { ArticlePreview } from "../../../components/ArticlePreview/ArticlePrevie import { AutoAnimate } from "../../../components/AutoAnimate/AutoAnimate"; import { Heading } from "../../../design-system/Heading/Heading"; import { context } from "../../../lib/projects"; -import { Filter, Projects, Sort } from "../page"; +import { Project } from "../../../lib/queries"; +import { Filter, Sort } from "../page"; interface Props { - projects: Projects; + projects: Array; filter?: Filter; sort?: Sort; } diff --git a/apps/website/src/app/projects/[slug]/page.tsx b/apps/website/src/app/projects/[slug]/page.tsx index eaa3228..ded143c 100644 --- a/apps/website/src/app/projects/[slug]/page.tsx +++ b/apps/website/src/app/projects/[slug]/page.tsx @@ -1,36 +1,14 @@ import { CalendarDays, Contact, Feather } from "lucide-react"; -import { groq } from "next-sanity"; -import { z } from "zod"; import { ArticleHeader } from "../../../components/ArticleHeader/ArticleHeader"; import { MDXContent } from "../../../components/MDXContent/MDXContent"; import { Container } from "../../../design-system/Container/Container"; import { createGenerateMetadata, ogImage } from "../../../lib/metadata"; -import { context, contexts } from "../../../lib/projects"; -import { queryContent } from "../../../lib/sanity"; +import { context } from "../../../lib/projects"; +import { getProject, getProjects } from "../../../lib/queries"; export const generateMetadata = createGenerateMetadata(async ({ params }) => { const { slug } = params; - const project = await queryContent( - groq` - *[_type == 'project' && slug.current == '${slug}'][0] - { - title, - summary, - date, - context, - 'client': client->shortName, - 'image': image.asset->url, - } - `, - z.object({ - title: z.string(), - summary: z.string().nullable(), - date: z.string(), - context: z.enum(contexts), - client: z.string().nullable(), - image: z.string(), - }), - ); + const project = await getProject(slug); return { title: project.title, description: project.summary || "Project by Katharina Clasen", @@ -47,7 +25,7 @@ export const generateMetadata = createGenerateMetadata(async ({ params }) => { url: ogImage({ overline: "Project • Katharina Clasen", headline: project.title, - image: project.image, + image: project.image.url, client: context(project.context, project.client || ""), date: new Date(project.date).getFullYear().toString(), }), @@ -60,19 +38,7 @@ export const generateMetadata = createGenerateMetadata(async ({ params }) => { }); export const generateStaticParams = async () => { - const projects = await queryContent( - groq` - *[_type == 'project'] - { - 'slug': slug.current - }`, - z.array( - z.object({ - slug: z.string(), - }), - ), - ); - + const projects = await getProjects(); return projects.map((project) => ({ slug: project.slug, })); @@ -86,58 +52,7 @@ interface Props { const ProjectPage = async ({ params }: Props) => { const { slug } = params; - const project = await queryContent( - groq` - *[_type == 'project' && slug.current == '${slug}'][0] - { - _id, - title, - image{'url': asset->url, alt, border}, - context, - 'client': client->shortName, - date, - period, - externalLink{label, href}, - services[]->{title}, - topics[]->{title}, - content - } - `, - z.object({ - _id: z.string(), - title: z.string(), - image: z.object({ - url: z.string(), - alt: z.string(), - border: z.boolean().nullable(), - }), - context: z.enum(contexts), - client: z.string().nullable(), - date: z.string(), - period: z.string().nullable(), - externalLink: z - .object({ - label: z.string(), - href: z.string(), - }) - .nullable(), - services: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - topics: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - content: z.string(), - }), - ); + const project = await getProject(slug); return (
diff --git a/apps/website/src/app/projects/page.tsx b/apps/website/src/app/projects/page.tsx index 58ed3e0..47c56db 100644 --- a/apps/website/src/app/projects/page.tsx +++ b/apps/website/src/app/projects/page.tsx @@ -5,7 +5,7 @@ import { Container } from "../../design-system/Container/Container"; import { Heading } from "../../design-system/Heading/Heading"; import { createGenerateMetadata, ogImage } from "../../lib/metadata"; import { contexts } from "../../lib/projects"; -import { getMetadata } from "../../lib/queries"; +import { getMetadata, getProjects } from "../../lib/queries"; import { queryContent } from "../../lib/sanity"; import { ProjectFilter } from "./ProjectFilter/ProjectFilter"; import { ProjectList } from "./ProjectList/ProjectList"; @@ -44,58 +44,6 @@ export const generateMetadata = createGenerateMetadata(async () => { }; }); -export type Projects = Awaited>; - -const getProjects = async () => { - return await queryContent( - groq` - *[_type == 'project'] - { - _id, - title, - 'slug': slug.current, - image{'url': asset->url, alt, border}, - context, - 'client': client->shortName, - date, - period, - services[]->{title}, - topics[]->{title} - } | order(date desc) - `, - z.array( - z.object({ - _id: z.string(), - title: z.string(), - slug: z.string(), - image: z.object({ - url: z.string(), - alt: z.string(), - border: z.boolean().nullable(), - }), - context: z.enum(contexts), - client: z.string().nullable(), - date: z.string(), - period: z.string().nullable(), - services: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - topics: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - }), - ), - ); -}; - export type Filter = z.infer; const filterSchema = z.object({ service: z.coerce.string().optional(), diff --git a/apps/website/src/app/(home)/BlogTeaser/BlogTeaser.tsx b/apps/website/src/components/BlogTeaser/BlogTeaser.tsx similarity index 59% rename from apps/website/src/app/(home)/BlogTeaser/BlogTeaser.tsx rename to apps/website/src/components/BlogTeaser/BlogTeaser.tsx index deea96a..7c48aa1 100644 --- a/apps/website/src/app/(home)/BlogTeaser/BlogTeaser.tsx +++ b/apps/website/src/components/BlogTeaser/BlogTeaser.tsx @@ -1,72 +1,32 @@ import { format } from "date-fns"; import { ArrowRight, CalendarDays, Clock, User } from "lucide-react"; -import { groq } from "next-sanity"; import Link from "next/link"; import readingTime from "reading-time"; -import { z } from "zod"; -import { ArticlePreview } from "../../../components/ArticlePreview/ArticlePreview"; -import { Section } from "../../../components/Section/Section"; -import { Body } from "../../../design-system/Body/Body"; -import { Button } from "../../../design-system/Button/Button"; -import { Heading } from "../../../design-system/Heading/Heading"; -import { queryContent } from "../../../lib/sanity"; +import { Body } from "../../design-system/Body/Body"; +import { Button } from "../../design-system/Button/Button"; +import { Heading } from "../../design-system/Heading/Heading"; +import { BlogPost } from "../../lib/queries"; +import { ArticlePreview } from "../ArticlePreview/ArticlePreview"; +import { Section } from "../Section/Section"; -export const BlogTeaser = async () => { - const blogPosts = await queryContent( - groq` - *[_type == 'blogPost'] - { - _id, - title, - 'slug': slug.current, - image{'url': asset->url, alt, border}, - author, - date, - services[]->{title}, - topics[]->{title}, - content - } | order(date desc)[0..1] - `, - z.array( - z.object({ - _id: z.string(), - title: z.string(), - slug: z.string(), - image: z.object({ - url: z.string(), - alt: z.string(), - border: z.boolean().nullable(), - }), - author: z.string(), - date: z.string(), - services: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - topics: z - .array( - z.object({ - title: z.string(), - }), - ) - .nullable(), - content: z.string(), - }), - ), - ); +interface Props { + title: string; + description: string; + blogPosts: Array; +} + +export const BlogTeaser = async ({ title, description, blogPosts }: Props) => { + const blogPostsToDisplay = blogPosts.slice(0, 2); return (
- Blog + {title} - Irregular writing on UX Design, Life-centered Design, and more... + {description}
    - {blogPosts.map((blogPost) => { + {blogPostsToDisplay.map((blogPost) => { const stats = readingTime(blogPost.content); return (
  • diff --git a/apps/website/src/lib/queries.ts b/apps/website/src/lib/queries.ts index e462844..8f3ecd1 100644 --- a/apps/website/src/lib/queries.ts +++ b/apps/website/src/lib/queries.ts @@ -5,6 +5,7 @@ import { backgroundColorsList, colorsList } from "./colors"; import { illustrationsList } from "../components/illustrations/illustrations"; import { markdownToHtml } from "./markdown"; import { queryContent } from "./sanity"; +import { contexts } from "./projects"; export type AccordeonItems = Awaited>; @@ -229,3 +230,161 @@ export const getImage = async (id: string) => { }), ); }; + +export type BlogPost = z.infer; +const blogPostSchema = z.object({ + _id: z.string(), + slug: z.string(), + title: z.string(), + summary: z.string().nullable(), + image: z.object({ + url: z.string(), + alt: z.string(), + border: z.boolean().nullable(), + }), + author: z.string(), + date: z.string(), + services: z + .array( + z.object({ + title: z.string(), + }), + ) + .nullable(), + topics: z + .array( + z.object({ + title: z.string(), + }), + ) + .nullable(), + content: z.string(), +}); + +export const getBlogPosts = async () => { + return await queryContent( + groq` + *[_type == 'blogPost'] + { + _id, + 'slug': slug.current, + title, + summary, + image{'url': asset->url, alt, border}, + author, + date, + services[]->{title}, + topics[]->{title}, + content + } | order(date desc) + `, + z.array(blogPostSchema), + ); +}; + +export const getBlogPost = async (slug: string) => { + return await queryContent( + groq` + *[_type == 'blogPost' && slug.current == '${slug}'][0] + { + _id, + 'slug': slug.current, + title, + summary, + image{'url': asset->url, alt, border}, + author, + date, + services[]->{title}, + topics[]->{title}, + content + } + `, + blogPostSchema, + ); +}; + +export type Project = z.infer; +const projectSchema = z.object({ + _id: z.string(), + slug: z.string(), + title: z.string(), + summary: z.string().nullable(), + image: z.object({ + url: z.string(), + alt: z.string(), + border: z.boolean().nullable(), + }), + context: z.enum(contexts), + client: z.string().nullable(), + date: z.string(), + period: z.string().nullable(), + externalLink: z + .object({ + label: z.string(), + href: z.string(), + }) + .nullable(), + services: z + .array( + z.object({ + title: z.string(), + }), + ) + .nullable(), + topics: z + .array( + z.object({ + title: z.string(), + }), + ) + .nullable(), + content: z.string(), +}); + +export const getProjects = async () => { + return await queryContent( + groq` + *[_type == 'project'] + { + _id, + 'slug': slug.current, + title, + summary, + image{'url': asset->url, alt, border}, + context, + 'client': client->shortName, + date, + period, + externalLink{label, href}, + services[]->{title}, + topics[]->{title}, + content + } | order(date desc) + `, + z.array(projectSchema), + ); +}; + +export const getProject = async (slug: string) => { + return await queryContent( + groq` + *[_type == 'project' && slug.current == '${slug}'][0] + { + _id, + 'slug': slug.current, + title, + summary, + image{'url': asset->url, alt, border}, + context, + 'client': client->shortName, + date, + period, + externalLink{label, href}, + services[]->{title}, + topics[]->{title}, + content + } + `, + projectSchema, + ); +};