Add insights category and related articles section to blog

Code has been updated to include insights as a new category of blog posts. Each blog post now also includes a related articles section at the bottom. The new feature enriches the content offering for users and potentially increases user engagement. The code has also been cleaned up for readability and consistency, improving maintainability long term.
This commit is contained in:
Emil Gulamov 2024-02-18 00:55:32 +04:00
parent b9ad4143a5
commit ac22de1301
5 changed files with 210 additions and 197 deletions

View file

@ -12,18 +12,26 @@ interface Props {
}
---
<a class="group relative block rounded-xl outline-none ring-zinc-500 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none transition duration-500"
href={`/blog/${blogEntry.slug}/`} data-astro-prefetch>
<div class="flex-shrink-0 relative rounded-xl overflow-hidden w-full h-[350px] before:absolute before:inset-x-0 before:size-full before:bg-gradient-to-t before:from-neutral-900/[.7] before:z-[1]">
<Image class="size-full absolute top-0 start-0 object-cover group-hover:scale-110 transition duration-500"
src={blogEntry.data.cardImage} alt={blogEntry.data.cardImageAlt} draggable={"false"} format={"avif"}/>
<a
class="group relative block rounded-xl outline-none ring-zinc-500 transition duration-500 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none"
href={`/blog/${blogEntry.slug}/`}
data-astro-prefetch
>
<div
class="relative h-[350px] w-full flex-shrink-0 overflow-hidden rounded-xl before:absolute before:inset-x-0 before:z-[1] before:size-full before:bg-gradient-to-t before:from-neutral-900/[.7]"
>
<Image
class="absolute start-0 top-0 size-full object-cover transition duration-500 group-hover:scale-110"
src={blogEntry.data.cardImage}
alt={blogEntry.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
</div>
<div class="absolute top-0 inset-x-0 z-10">
<div class="p-4 flex flex-col h-full sm:p-6">
<div class="absolute inset-x-0 top-0 z-10">
<div class="flex h-full flex-col p-4 sm:p-6">
<div class="flex items-center">
<AvatarBlog blogEntry={blogEntry} />
<div class="ms-2.5 sm:ms-4">
@ -32,19 +40,20 @@ interface Props {
</h4>
<p class="text-xs text-neutral-50/[.8]">
{formatDate(blogEntry.data.pubDate)}
</p>
</div>
</div>
</div>
</div>
<div class="absolute bottom-0 inset-x-0 z-10">
<div class="flex flex-col h-full p-4 sm:p-6">
<h3 class="text-lg sm:text-3xl font-bold text-neutral-50 group-hover:text-neutral-50/[.8]">
<div class="absolute inset-x-0 bottom-0 z-10">
<div class="flex h-full flex-col p-4 sm:p-6">
<h3
class="text-balance text-lg font-bold text-neutral-50 group-hover:text-neutral-50/[.8] sm:text-3xl"
>
{blogEntry.data.title}
</h3>
<p class="mt-2 text-neutral-50/[.8]">
<p class="mt-2 text-pretty text-neutral-50/[.8]">
{blogEntry.data.description}
</p>
</div>

View file

@ -1,8 +1,6 @@
---
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
import AvatarBlogLarge from "../avatars/AvatarBlogLarge.astro";
import PrimaryCTA from "../buttons/PrimaryCTA.astro";
@ -13,27 +11,37 @@ interface Props {
blogEntry: CollectionEntry<"blog">;
}
---
<div class="grid sm:grid-cols-2 sm:items-center gap-8">
<div class="grid gap-8 sm:grid-cols-2 sm:items-center">
<div class="sm:order-2">
<div class="relative pt-[50%] sm:pt-[100%] rounded-lg">
<Image class="size-full absolute top-0 start-0 object-cover rounded-xl" src={blogEntry.data.cardImage} alt={blogEntry.data.cardImageAlt} draggable={"false"} format={"avif"}/>
<div class="relative rounded-lg pt-[50%] sm:pt-[100%]">
<Image
class="absolute start-0 top-0 size-full rounded-xl object-cover"
src={blogEntry.data.cardImage}
alt={blogEntry.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
</div>
</div>
<div class="sm:order-1">
<h2 class="text-2xl font-bold md:text-3xl lg:text-4xl lg:leading-tight xl:text-5xl xl:leading-tight text-neutral-800 dark:text-neutral-200 tracking-tight">
<a class="hover:text-[#fa5a15] dark:text-neutral-300 dark:hover:text-neutral-50 transition duration-300 focus-visible:ring outline-none dark:ring-zinc-200 ring-zinc-500 dark:focus:outline-none " href={`/blog/${blogEntry.slug}/`}>
<h2
class="text-balance text-2xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:text-3xl lg:text-4xl lg:leading-tight xl:text-5xl xl:leading-tight"
>
<a
class="outline-none ring-zinc-500 transition duration-300 hover:text-[#fa5a15] focus-visible:ring dark:text-neutral-300 dark:ring-zinc-200 dark:hover:text-neutral-50 dark:focus:outline-none"
href={`/blog/${blogEntry.slug}/`}
>
{blogEntry.data.description}
</a>
</h2>
<div class="mt-6 sm:mt-10 flex items-center">
<div class="mt-6 flex items-center sm:mt-10">
<AvatarBlogLarge blogEntry={blogEntry} />
<div class="ms-3 sm:ms-4">
<p class="sm:mb-1 font-bold text-neutral-800 dark:text-neutral-200">
<p class="font-bold text-neutral-800 dark:text-neutral-200 sm:mb-1">
{blogEntry.data.author}
</p>
<p class="text-xs text-neutral-500">
@ -43,8 +51,11 @@ interface Props {
</div>
<div class="mt-5">
<PrimaryCTA url={`/blog/${blogEntry.slug}/`} title="Read More" data-astro-prefetch/>
<PrimaryCTA
url={`/blog/${blogEntry.slug}/`}
title="Read More"
data-astro-prefetch
/>
</div>
</div>
</div>

View file

@ -70,7 +70,19 @@ const blogCollection = defineCollection({
}),
});
const insightsCollection = defineCollection({
type: "content",
schema: ({ image }) => z.object ({
title: z.string(),
description: z.string(),
contents: z.array(z.string()),
cardImage: image(),
cardImageAlt: z.string(),
}),
});
export const collections = {
'products': productsCollection,
'blog': blogCollection,
'insights': insightsCollection,
};

View file

@ -1,12 +1,12 @@
---
// Import section components
import MainLayout from "../../layouts/MainLayout.astro";
import ProductTabBtn from "../../components/ui/buttons/ProductTabBtn.astro";
import PrimaryCTA from "../../components/ui/buttons/PrimaryCTA.astro";
import AvatarBlogLarge from "../../components/ui/avatars/AvatarBlogLarge.astro";
import CardRelated from "../../components/ui/cards/CardRelated.astro";
import { Image } from "astro:assets";
import { capitalize, formatDate } from "../../utils";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
export async function getStaticPaths() {
const blogPosts = await getCollection("blog");
@ -17,6 +17,11 @@ export async function getStaticPaths() {
}
const { post } = Astro.props;
const blogPosts: CollectionEntry<"blog">[] = await getCollection("blog");
const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
(blogEntry) => blogEntry.slug !== post.slug,
);
---
<MainLayout
@ -34,11 +39,11 @@ const { post } = Astro.props;
<div
class="hs-tooltip inline-block [--placement:bottom] [--trigger:hover]"
>
<span class="font-bold text-neutral-700 dark:text-neutral-300">
<span
class="font-bold text-neutral-700 dark:text-neutral-300"
>
{post.data.author}
</span>
</div>
<ul class="text-xs text-neutral-500">
<li
@ -58,15 +63,17 @@ const { post } = Astro.props;
</div>
</div>
<h2 class="mb-3 text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-3xl">
<h2
class="mb-3 text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-3xl"
>
{post.data.title}
</h2>
<div class="space-y-5 md:space-y-8 mb-5 md:mb-8">
<div class="mb-5 space-y-5 md:mb-8 md:space-y-8">
{
post.data.contents.map((content: string, index: any) => (
index === 1 ?
<p class="text-lg text-neutral-700 dark:text-neutral-300">
post.data.contents.map((content: string, index: any) =>
index === 1 ? (
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
<Image
@ -75,10 +82,13 @@ const { post } = Astro.props;
alt={post.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/> : <p class="text-lg text-neutral-700 dark:text-neutral-300">
/>
) : (
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
))
),
)
}
</div>
@ -93,4 +103,18 @@ const { post } = Astro.props;
</div>
</div>
</div>
<div class="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
<div class="mb-10 max-w-2xl">
<h2
class="text-balance text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-4xl md:leading-tight"
>
Related articles
</h2>
</div>
<div class="grid grid-cols-2 gap-6">
{relatedPosts.map((entry) => <CardRelated blogEntry={entry} />)}
</div>
</div>
</MainLayout>

View file

@ -3,13 +3,19 @@
import MainLayout from "../../layouts/MainLayout.astro";
import CardBlog from "../../components/ui/cards/CardBlog.astro";
import CardBlogRecent from "../../components/ui/cards/CardBlogRecent.astro";
import CardInsight from "../../components/ui/cards/CardInsight.astro";
import { Image } from "astro:assets";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
const blogPosts: CollectionEntry<"blog">[] = (
await getCollection("blog")
).sort((a: CollectionEntry<"blog">, b: CollectionEntry<"blog">) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
const blogPosts: CollectionEntry<"blog">[] = (await getCollection("blog")).sort(
(a: CollectionEntry<"blog">, b: CollectionEntry<"blog">) =>
b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const insightPosts: CollectionEntry<"insights">[] =
await getCollection("insights");
const mostRecentPost: CollectionEntry<"blog"> = blogPosts[0];
const otherPosts: CollectionEntry<"blog">[] = blogPosts.slice(1);
@ -25,109 +31,60 @@ const secondSubTitle: string =
"Stay up-to-date with the latest trends and developments in the construction industry with insights from ScrewFast's team of industry experts. ";
---
<MainLayout title="Blog | ScrewFast" meta="ScrewFast offers top-tier hardware tools and expert construction services to meet all your project needs. Start exploring and contact our sales team for superior quality and reliability.">
<div class="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8 py-16 space-y-8 2xl:max-w-full">
<div class="max-w-3xl text-center mx-auto">
<h1 class="block font-bold tracking-tight text-neutral-800 dark:text-neutral-200 text-4xl md:text-5xl lg:text-6xl text-balance">
<MainLayout
title="Blog | ScrewFast"
meta="ScrewFast offers top-tier hardware tools and expert construction services to meet all your project needs. Start exploring and contact our sales team for superior quality and reliability."
>
<div
class="mx-auto max-w-[85rem] space-y-8 px-4 pt-16 sm:px-6 lg:px-8 2xl:max-w-full"
>
<div class="mx-auto max-w-3xl text-center">
<h1
class="block text-balance text-4xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:text-5xl lg:text-6xl"
>
{title}
</h1>
<p class="text-lg text-pretty text-neutral-600 mt-4 dark:text-neutral-400">{subTitle}</p>
</div>
</div>
<div class="max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto 2xl:max-w-full">
<div class="grid lg:grid-cols-2 gap-6">
{otherPosts.map(blogEntry => (
<CardBlog blogEntry={blogEntry} />
))}
<p
class="mt-4 text-pretty text-lg text-neutral-600 dark:text-neutral-400"
>
{subTitle}
</p>
</div>
</div>
<!-- Card Blog -->
<div class="max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto 2xl:max-w-full">
<div
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div class="grid gap-6 lg:grid-cols-2">
{otherPosts.map((blogEntry) => <CardBlog blogEntry={blogEntry} />)}
</div>
</div>
<div
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<CardBlogRecent blogEntry={mostRecentPost} />
</div>
<!-- Card Blog -->
<div class="max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto 2xl:max-w-full">
<!-- Title -->
<div class="max-w-2xl mx-auto text-center mb-10 lg:mb-14">
<h2 class="text-2xl font-bold md:text-4xl md:leading-tight dark:text-white">{secondTitle}</h2>
<p class="mt-1 text-gray-600 dark:text-gray-400">{secondSubTitle}</p>
</div>
<!-- End Title -->
<!-- Grid -->
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Card -->
<a class="group dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600" href="#">
<div class="relative pt-[50%] sm:pt-[70%] rounded-xl overflow-hidden">
<img class="size-full absolute top-0 start-0 object-cover group-hover:scale-105 transition-transform duration-500 ease-in-out rounded-xl" src="https://images.unsplash.com/photo-1586232702178-f044c5f4d4b7?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1035&q=80" alt="Image Description">
<span class="absolute top-0 end-0 rounded-se-xl rounded-es-xl text-xs font-medium bg-gray-800 text-white py-1.5 px-3 dark:bg-gray-900">
Sponsored
</span>
<div
class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
<h2
class="text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-4xl md:leading-tight"
>
{secondTitle}
</h2>
<p class="mt-1 text-neutral-600 dark:text-neutral-400 text-pretty">{secondSubTitle}</p>
</div>
<div class="mt-7">
<h3 class="text-xl font-bold text-gray-800 group-hover:text-gray-600 dark:text-gray-200">
Studio by Preline
</h3>
<p class="mt-3 text-gray-800 dark:text-gray-200">
Produce professional, reliable streams easily leveraging Preline's innovative broadcast studio
</p>
<p class="mt-5 inline-flex items-center gap-x-1 text-blue-600 decoration-2 group-hover:underline font-medium">
Read more
<svg class="flex-shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
</p>
</div>
</a>
<!-- End Card -->
<!-- Card -->
<a class="group dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600" href="#">
<div class="relative pt-[50%] sm:pt-[70%] rounded-xl overflow-hidden">
<img class="size-full absolute top-0 start-0 object-cover group-hover:scale-105 transition-transform duration-500 ease-in-out rounded-xl" src="https://images.unsplash.com/photo-1542125387-c71274d94f0a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80" alt="Image Description">
</div>
<div class="mt-7">
<h3 class="text-xl font-bold text-gray-800 group-hover:text-gray-600 dark:text-gray-200">
Onsite
</h3>
<p class="mt-3 text-gray-800 dark:text-gray-200">
Optimize your in-person experience with best-in-class capabilities like badge printing and lead retrieval
</p>
<p class="mt-5 inline-flex items-center gap-x-1 text-blue-600 decoration-2 group-hover:underline font-medium">
Read more
<svg class="flex-shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
</p>
</div>
</a>
<!-- End Card -->
<!-- Card -->
<a class="group relative flex flex-col w-full min-h-60 bg-center bg-cover rounded-xl hover:shadow-lg transition bg-[url('https://images.unsplash.com/photo-1634017839464-5c339ebe3cb4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3000&q=80')] dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600" href="#">
<div class="flex-auto p-4 md:p-6">
<h3 class="text-xl text-white/[.9] group-hover:text-white"><span class="font-bold">Preline</span> Press publishes books about economic and technological advancement.</h3>
</div>
<div class="pt-0 p-4 md:p-6">
<div class="inline-flex items-center gap-2 text-sm font-medium text-white group-hover:text-white/[.7]">
Visit the site
<svg class="flex-shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{
insightPosts.map((insightEntry) => (
<CardInsight insightEntry={insightEntry} />
))
}
</div>
</div>
</a>
<!-- End Card -->
</div>
<!-- End Grid -->
</div>
<!-- End Card Blog -->
</MainLayout>