Refactor several component files for improved maintainability

This commit refactors multiple component files for better understandability and maintainability. Detailed comments are added for enhanced readability. The data source is defined using Astro.js to simplify the process of acquiring data for the components. Unnecessary files like 'preline.js' from the vendor scripts directory are deleted as part of clean-up.
This commit is contained in:
Emil Gulamov 2024-02-18 07:39:17 +04:00
parent 69530ff398
commit 04e8089294
18 changed files with 256 additions and 142 deletions

View file

@ -1,15 +1,16 @@
---
// Import the necessary dependencies from individual component files
// Import the necessary components from their respective component files
import LoginModal from "./ui/forms/LoginModal.astro";
import RegisterModal from "./ui/forms/RegisterModal.astro";
import RecoverModal from "./ui/forms/RecoverModal.astro";
import LoginBtn from "./ui/buttons/LoginBtn.astro";
---
<!-- Login Button -->
<LoginBtn />
<!-- Login Modal -->
<LoginModal />
<!-- Register Modal -->
<RegisterModal />
<!-- Password Recovery Modal -->
<RecoverModal />

View file

@ -1,6 +1,8 @@
---
// Destructure the properties from Astro.props
const { src, alt } = Astro.props;
// Define TypeScript interface for the properties
interface Props {
src: string;
alt: string;

View file

@ -1,14 +1,17 @@
---
// Import necessary components
import { Image } from "astro:assets";
// Import necessary types
import type { CollectionEntry } from "astro:content";
// Extract blogEntry property from Astro.props
const { blogEntry } = Astro.props;
// Define Props interface
interface Props {
blogEntry: CollectionEntry<"blog">;
}
---
<div class="flex-shrink-0">
<Image
class="size-[46px] rounded-full border-2 border-neutral-50"

View file

@ -1,16 +1,22 @@
---
// Import necessary components
import { Image } from "astro:assets";
// Import necessary types
import type { CollectionEntry } from "astro:content";
// Extract blogEntry property from Astro.props
const { blogEntry } = Astro.props;
// Define Props interface
interface Props {
blogEntry: CollectionEntry<"blog">;
}
---
<div class="flex-shrink-0">
<Image class="size-10 sm:h-14 sm:w-14 rounded-full" src={blogEntry.data.authorImage}
alt={blogEntry.data.authorImageAlt}
draggable={"false"} format={"avif"}>
</div>
<Image
class="size-10 rounded-full sm:h-14 sm:w-14"
src={blogEntry.data.authorImage}
alt={blogEntry.data.authorImageAlt}
draggable={"false"}
format={"avif"}
/>
</div>

View file

@ -1,6 +1,7 @@
---
// Define props from Astro
const { id, collapseId, heading, content, first } = Astro.props;
// Define TypeScript interface for props
interface Props {
id: string;
collapseId: string;
@ -8,45 +9,58 @@ interface Props {
content: string;
first?: boolean;
}
// Define class names for the accordion and its content
const ACCORDION_CLASS_DEFAULT = "hs-accordion pb-3 active";
const ACCORDION_CLASS_COLLAPSED = "hs-accordion pt-6 pb-3";
const ACCORDION_CONTENT_CLASS =
"hs-accordion-content w-full overflow-hidden transition-[height] duration-300";
// Helper function to return the correct class for the accordion
function getAccordionClass(first: boolean = false) {
return first ? ACCORDION_CLASS_DEFAULT : ACCORDION_CLASS_COLLAPSED;
}
const SVG_PARAMS = {
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
};
---
<!-- The main container for the accordion item -->
<div class={getAccordionClass(first)} id={id}>
<!-- The accordion button, which toggles the expanded/collapsed state -->
<button
class="hs-accordion-toggle group inline-flex w-full items-center justify-between gap-x-3 text-balance rounded-lg pb-3 text-start font-bold text-neutral-800 outline-none ring-zinc-500 transition hover:text-neutral-500 focus-visible:ring dark:text-neutral-200 dark:ring-zinc-200 dark:hover:text-neutral-400 dark:focus:outline-none md:text-lg"
aria-controls={collapseId}
>
{heading}
<!-- SVG Icon that is shown when the accordion is NOT active -->
<svg
class="block h-5 w-5 flex-shrink-0 text-neutral-600 group-hover:text-neutral-500 hs-accordion-active:hidden dark:text-neutral-400"
{...SVG_PARAMS}><path d="m6 9 6 6 6-6"></path></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="m6 9 6 6 6-6"></path></svg
>
<!-- SVG Icon that is shown when the accordion is active -->
<svg
class="hidden h-5 w-5 flex-shrink-0 text-neutral-600 group-hover:text-neutral-500 hs-accordion-active:block dark:text-neutral-400"
{...SVG_PARAMS}><path d="m18 15-6-6-6 6"></path></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="m18 15-6-6-6 6"></path></svg
>
</button>
<!-- The collapsible content of the accordion -->
<div
aria-labelledby={id}
class={`${first ? ACCORDION_CONTENT_CLASS : "hidden " + ACCORDION_CONTENT_CLASS}`}
id={collapseId}
>
<!-- The content paragraph -->
<p class="text-pretty text-neutral-600 dark:text-neutral-400">
{content}
</p>

View file

@ -1,11 +1,14 @@
---
// Destructure the properties from Astro.props
const { title } = Astro.props;
// Define TypeScript interface for the properties
interface Props {
title: string;
}
const baseClasses = "inline-flex w-full items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-sm font-bold text-neutral-700 focus-visible:ring outline-none transition duration-300";
// Define CSS classes for styling the button
const baseClasses =
"inline-flex w-full items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-sm font-bold text-neutral-700 focus-visible:ring outline-none transition duration-300";
const borderClasses = "border border-transparent";
const bgColorClasses = "bg-yellow-400 dark:focus:outline-none";
const hoverClasses = "hover:bg-yellow-500";
@ -13,7 +16,10 @@ const fontSizeClasses = "2xl:text-base";
const disabledClasses = "disabled:pointer-events-none disabled:opacity-50";
const ringClasses = "ring-zinc-500 dark:ring-zinc-200";
---
<!-- Styled submit button with dynamic title -->
<button
type="submit"
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${fontSizeClasses} ${disabledClasses} ${ringClasses}`}
>{title}</button>
type="submit"
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${fontSizeClasses} ${disabledClasses} ${ringClasses}`}
>{title}</button
>

View file

@ -1,18 +1,22 @@
---
// Destructure the properties from Astro.props
const { title, id, noArrow } = Astro.props;
// Define TypeScript interface for the properties
interface Props {
title?: string;
id?: string;
noArrow?: boolean;
title?: string;
id?: string;
noArrow?: boolean;
}
const baseClasses = "group inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-sm font-bold text-neutral-50 ring-zinc-500 transition duration-300 focus-visible:ring outline-none";
// Define CSS classes for styling the button
const baseClasses =
"group inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-sm font-bold text-neutral-50 ring-zinc-500 transition duration-300 focus-visible:ring outline-none";
const borderClasses = "border border-transparent";
const bgColorClasses = "bg-[#fa5a15] hover:bg-[#e14d0b] active:bg-[#e14d0b] dark:focus:outline-none";
const bgColorClasses =
"bg-[#fa5a15] hover:bg-[#e14d0b] active:bg-[#e14d0b] dark:focus:outline-none";
const disableClasses = "disabled:pointer-events-none disabled:opacity-50";
const fontSizeClasses = "2xl:text-base";
const ringClasses = "dark:ring-zinc-200";
// SVG for an arrow icon
const arrowSVG = `<svg
class="h-4 w-4 flex-shrink-0 transition duration-300 group-hover:translate-x-1"
width="24"
@ -28,12 +32,13 @@ const arrowSVG = `<svg
</svg>`;
---
<!-- Button with dynamic title, id, and optional arrow -->
<button
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses}`}
id={id}
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses}`}
id={id}
>
{title}
{noArrow ? null :
<Fragment set:html={arrowSVG} />
}
</button>
{title}
<!-- About Fragment: https://docs.astro.build/en/basics/astro-syntax/#fragments -->
<!-- Display the arrow based on the 'noArrow' property -->
{noArrow ? null : <Fragment set:html={arrowSVG} />}
</button>

View file

@ -1,22 +1,29 @@
---
// Import necessary components and utilities
import AvatarBlog from "../avatars/AvatarBlog.astro";
import { Image } from "astro:assets";
import { formatDate } from "../../../utils";
import type { CollectionEntry } from "astro:content";
// Define data sources from Astro props
const { blogEntry } = Astro.props;
// Define TypeScript props interface
interface Props {
blogEntry: CollectionEntry<"blog">;
}
---
<!-- The following anchor tag is the main container for the card.
It's a link to the blog post detailed page.
The data-astro-prefetch is a Astro.js specific Dynamic HTML feature,
which automatically prefetches the linked page to speed up navigation. -->
<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
>
<!-- The container for the blog post's cover image. Uses astro:assets' Image for image source -->
<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]"
>
@ -28,7 +35,7 @@ interface Props {
format={"avif"}
/>
</div>
<!-- The container for the blog author's avatar and associated metadata (author name and publication date) -->
<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">
@ -45,7 +52,7 @@ interface Props {
</div>
</div>
</div>
<!-- The container for the blog post's title and description -->
<div class="absolute inset-x-0 bottom-0 z-10">
<div class="flex h-full flex-col p-4 sm:p-6">
<h3

View file

@ -1,18 +1,20 @@
---
// Import all required components and utilities
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
import AvatarBlogLarge from "../avatars/AvatarBlogLarge.astro";
import PrimaryCTA from "../buttons/PrimaryCTA.astro";
// Define data source from Astro.js incoming props
const { blogEntry } = Astro.props;
// Define TypeScript props interface
interface Props {
blogEntry: CollectionEntry<"blog">;
}
---
<!-- Root container, which is divided into 2 grid column layout for larger screens -->
<div class="grid gap-8 sm:grid-cols-2 sm:items-center">
<!-- Container for the blog post's cover image -->
<div class="sm:order-2">
<div class="relative rounded-lg pt-[50%] sm:pt-[100%]">
<Image
@ -24,8 +26,9 @@ interface Props {
/>
</div>
</div>
<!-- Container for the blog post's heading, author avatar, author's role, and read more button -->
<div class="sm:order-1">
<!-- Blog title which is also a hyperlink to the blog detail page -->
<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"
>
@ -36,7 +39,7 @@ interface Props {
{blogEntry.data.description}
</a>
</h2>
<!-- Container for the author's avatar and metadata -->
<div class="mt-6 flex items-center sm:mt-10">
<AvatarBlogLarge blogEntry={blogEntry} />
@ -49,7 +52,7 @@ interface Props {
</p>
</div>
</div>
<!-- Read More button which is a link to the blog post detailed page -->
<div class="mt-5">
<PrimaryCTA
url={`/blog/${blogEntry.slug}/`}

View file

@ -1,33 +1,59 @@
---
// Import necessary modules and utilities
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
// Define data source from Astro.js incoming props
const { insightEntry } = Astro.props;
// Define TypeScript props interface
interface Props {
insightEntry: CollectionEntry<"insights">;
insightEntry: CollectionEntry<"insights">;
}
---
<a class="group outline-none ring-zinc-500 transition duration-300 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none" href={`/insights/${insightEntry.slug}/`}>
<div class="relative pt-[50%] sm:pt-[70%] rounded-xl overflow-hidden">
<Image class="size-full absolute top-0 start-0 object-cover group-hover:scale-105 transition duration-500 ease-in-out rounded-xl" src={insightEntry.data.cardImage}
alt={insightEntry.data.cardImageAlt}
draggable={"false"}
format={"avif"} />
</div>
<div class="mt-7">
<h3 class="text-xl font-bold text-neutral-800 group-hover:text-neutral-600 dark:text-neutral-200 dark:group-hover:text-neutral-400">
{insightEntry.data.title}
</h3>
<p class="mt-3 text-neutral-600 dark:text-neutral-400">
<!-- The anchor tag is the root container for the "Insight" card. It links to the insight detail page. -->
<a
class="group outline-none ring-zinc-500 transition duration-300 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none"
href={`/insights/${insightEntry.slug}/`}
>
<!-- This is the container for the insight's cover image. -->
<div class="relative overflow-hidden rounded-xl pt-[50%] sm:pt-[70%]">
<Image
class="absolute start-0 top-0 size-full rounded-xl object-cover transition duration-500 ease-in-out group-hover:scale-105"
src={insightEntry.data.cardImage}
alt={insightEntry.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
</div>
<!-- This is the container for the insight's title and description. -->
<div class="mt-7">
<!-- The title of the insight -->
<h3
class="text-xl font-bold text-neutral-800 group-hover:text-neutral-600 dark:text-neutral-200 dark:group-hover:text-neutral-400"
>
{insightEntry.data.title}
</h3>
<!-- The description of the insight -->
<p class="mt-3 text-neutral-600 dark:text-neutral-400">
{insightEntry.data.description}
</p>
<p class="mt-5 inline-flex items-center gap-x-1 text-[#fa5a15] dark:text-[#fb713b] 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>
</p>
<!-- The "Read More" hyperlink going to the full insight. With an arrow icon -->
<p
class="mt-5 inline-flex items-center gap-x-1 font-medium text-[#fa5a15] decoration-2 group-hover:underline dark:text-[#fb713b]"
>
Read more
<svg
class="size-4 flex-shrink-0"
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"></path></svg
>
</p>
</div>
</a>

View file

@ -1,20 +1,23 @@
---
// Import necessary modules and utilities
import { Image } from "astro:assets";
import { formatDate } from "../../../utils";
import type { CollectionEntry } from "astro:content";
// Define data source from Astro.js incoming props
const { blogEntry } = Astro.props;
// Define TypeScript props interface
interface Props {
blogEntry: CollectionEntry<"blog">;
}
---
<!-- The anchor tag is the main container for the blog preview card. Currently, it links to "#".
You might want to change it to the corresponding blog post's URL. -->
<a
class="group block rounded-xl outline-none ring-zinc-500 transition duration-300 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none"
href="#"
>
<!-- The blog post's cover image. It uses astro:assets' Image for loading the image optimally -->
<div>
<Image
class="aspect-video rounded-xl"
@ -23,11 +26,13 @@ interface Props {
draggable={"false"}
format={"avif"}
/>
<!-- The title of the blog post -->
<h3
class="mt-2 text-balance text-lg font-medium text-neutral-800 group-hover:text-[#fa5a15] dark:text-neutral-300 dark:group-hover:text-neutral-50"
>
{blogEntry.data.title}
</h3>
<!-- The formatted publication date of the blog post -->
<p class="mt-2 text-sm text-neutral-600 dark:text-neutral-400">
{formatDate(blogEntry.data.pubDate)}
</p>

View file

@ -1,27 +1,30 @@
---
// Import necessary modules and utilities
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
// Define data source from Astro.js incoming props
const { product } = Astro.props;
// Define TypeScript props interface
interface Props {
product: CollectionEntry<"products">;
}
// Define classes to be used with the Image component
const imageClass =
"absolute inset-0 h-full w-full object-cover object-center transition duration-[600ms] ease-[cubic-bezier(0.45,0,0.55,1)] group-hover:scale-110";
// AnchorSVG - an SVG icon to be added next to the product's subtitle
const AnchorSVG = `
<svg fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" class="ml-0.5 w-3 h-3 md:w-4 md:h-4 inline pb-0.5">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
</svg>`;
---
<!-- A clickable card that leads to the details of the product-->
<a
href={"/products/" + product.slug}
data-astro-prefetch
class="group relative flex h-48 items-end overflow-hidden rounded-xl shadow-lg outline-none ring-zinc-500 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none md:h-80"
>
<!-- The product's main image -->
<Image
src={product.data.main.imgCard}
alt={product.data.main.imgAlt}
@ -29,12 +32,12 @@ const AnchorSVG = `
class={imageClass}
format={"avif"}
/>
<!-- An overlay gradient that sits on top of the product image-->
<div
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-neutral-800 via-transparent to-transparent opacity-50"
>
</div>
<!-- The product's subtitle and the anchor SVG icon-->
<span
class="relative mb-3 ml-4 inline-block text-sm font-bold text-neutral-50 transition duration-[600ms] ease-[cubic-bezier(0.45,0,0.55,1)] group-hover:scale-110 md:ml-5 md:text-lg"
>{product.data.main.subTitle} <Fragment set:html={AnchorSVG} />

View file

@ -1,27 +1,30 @@
---
// Import necessary modules and utilities
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
// Define data source from Astro.js incoming props
const { product } = Astro.props;
// Define TypeScript props interface
interface Props {
product: CollectionEntry<"products">;
}
// Define classes to be used with the Image component
const imageClass =
"absolute inset-0 h-full w-full object-cover object-center transition duration-[600ms] ease-[cubic-bezier(0.45,0,0.55,1)] group-hover:scale-110";
// AnchorSVG - an SVG icon to be added next to the product's subtitle
const AnchorSVG = `
<svg fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" class="ml-0.5 w-3 h-3 md:w-4 md:h-4 inline pb-0.5">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
</svg>`;
---
<!-- The anchor tag is the main container for the product card. When clicked, this leads to the details of the product. -->
<a
href={"/products/" + product.slug}
data-astro-prefetch
class="group relative flex h-48 items-end overflow-hidden rounded-lg shadow-xl outline-none ring-zinc-500 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none md:col-span-2 md:h-80"
>
<!-- The product's main image -->
<Image
src={product.data.main.imgCard}
alt={product.data.main.imgAlt}
@ -29,12 +32,12 @@ const AnchorSVG = `
class={imageClass}
format={"avif"}
/>
<!-- This container includes a gradient overlay over the product's image -->
<div
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-neutral-800 via-transparent to-transparent opacity-50"
>
</div>
<!-- This container includes product's subtitle and an SVG icon-->
<span
class="relative mb-3 ml-4 inline-block text-sm font-bold text-neutral-50 transition duration-[600ms] ease-[cubic-bezier(0.45,0,0.55,1)] group-hover:scale-110 md:ml-5 md:text-lg"
>{product.data.main.subTitle} <Fragment set:html={AnchorSVG} /></span

View file

@ -2,6 +2,13 @@
// Import section components
import MainLayout from "../layouts/MainLayout.astro";
import Btn404 from "../components/ui/buttons/Btn404.astro";
// Define variables for page content
const title: string = "404";
const subTitle: string = "Oops, this isn't the tool you were looking for!";
const content: string =
"Don't let this hiccup slow you down. Let's get you back to building your masterpiece.";
const btnTitle: string = "Go Back";
---
<MainLayout
@ -14,25 +21,26 @@ import Btn404 from "../components/ui/buttons/Btn404.astro";
<h1
class="text-dark mb-4 text-7xl font-extrabold text-yellow-500 dark:text-yellow-400 lg:text-9xl"
>
404
{title}
</h1>
<p
class="mb-4 text-balance text-3xl font-bold tracking-tight text-neutral-700 dark:text-neutral-300 md:text-4xl"
>
Oops, this isn't the tool you were looking for!
{subTitle}
</p>
<p
class="mb-4 text-pretty text-lg text-neutral-600 dark:text-neutral-400"
>
Don't let this hiccup slow you down. Let's get you back to building
your masterpiece.
{content}
</p>
<Btn404 title="Go Back" id="go-back" />
<!--Display a button that navigates user back to the previous page-->
<Btn404 title={btnTitle} id="go-back" />
</div>
</div>
</section>
</MainLayout>
<!--JavaScript code that adds click event to the Button, resulting in going back to the previous page in history-->
<script>
document.getElementById("go-back")?.addEventListener("click", () => {
history.back();

View file

@ -1,5 +1,5 @@
---
// Import section components
// Import necessary components and utilities
import MainLayout from "../../layouts/MainLayout.astro";
import AvatarBlogLarge from "../../components/ui/avatars/AvatarBlogLarge.astro";
import CardRelated from "../../components/ui/cards/CardRelated.astro";
@ -8,6 +8,7 @@ import { capitalize, formatDate } from "../../utils";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
// getStaticPaths is used to pre-render all routes based on the blog posts
export async function getStaticPaths() {
const blogPosts = await getCollection("blog");
return blogPosts.map((post) => ({
@ -15,11 +16,16 @@ export async function getStaticPaths() {
props: { post },
}));
}
// Get the current post's data
const { post } = Astro.props;
// Get all blog posts
const blogPosts: CollectionEntry<"blog">[] = await getCollection("blog");
const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
// Filter out the current post to get related posts
// Note: This is a very basic way of choosing related posts, just for the purpose of the example.
// In a production site, you might want to implement a more robust algorithm, choosing related posts based on tags, categories, dates, authors, or keywords.
const relatedPosts: CollectionEntry<"blog">[] = blogPosts.filter(
(blogEntry) => blogEntry.slug !== post.slug,
);
---
@ -39,6 +45,7 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
<div
class="hs-tooltip inline-block [--placement:bottom] [--trigger:hover]"
>
<!--Post metadata and author info-->
<span
class="font-bold text-neutral-700 dark:text-neutral-300"
>
@ -62,17 +69,18 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
</div>
</div>
</div>
<!--Blog post title-->
<h2
class="mb-3 text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-3xl"
>
{post.data.title}
</h2>
<!--Blog post contents-->
<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-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
@ -83,6 +91,7 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
draggable={"false"}
format={"avif"}
/>
</>
) : (
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
@ -91,7 +100,7 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
)
}
</div>
<!--Blog post tags-->
<div class="space-x-2">
{
post.data.tags?.map((tag: string) => (
@ -103,7 +112,7 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
</div>
</div>
</div>
<!--Related articles section-->
<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

View file

@ -1,12 +1,10 @@
---
// Import section components
import MainLayout from "../../layouts/MainLayout.astro";
import AvatarBlogLarge from "../../components/ui/avatars/AvatarBlogLarge.astro";
import { Image } from "astro:assets";
import { capitalize, formatDate } from "../../utils";
import { getCollection } from "astro:content";
// Use `getStaticPaths` to generate static routes for generated pages on build
export async function getStaticPaths() {
const insightPosts = await getCollection("insights");
return insightPosts.map((post) => ({
@ -14,46 +12,46 @@ export async function getStaticPaths() {
props: { post },
}));
}
// Get the props for this page that define a specific insight post
const { post } = Astro.props;
---
<MainLayout
title={post.data.title + " | 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="py-6 sm:py-8 lg:py-12">
<div class="py-6 sm:py-8 lg:py-12">
<div class="mx-auto max-w-screen-xl px-4 md:px-8">
<div class="grid gap-8 md:grid-cols-2 lg:gap-12">
<div>
<div class="h-64 overflow-hidden rounded-lg shadow-lg md:h-auto">
<Image
class="h-full w-full object-cover object-center"
src={post.data.cardImage}
alt={post.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
class="h-full w-full object-cover object-center"
src={post.data.cardImage}
alt={post.data.cardImageAlt}
draggable={"false"}
format={"avif"}
/>
</div>
</div>
<div class="md:pt-8">
<h1 class="mb-4 text-center text-2xl font-bold text-neutral-800 dark:text-neutral-200 text-balance sm:text-3xl md:mb-6 md:text-left">{post.data.title}</h1>
<div class="space-y-8">
{
post.data.contents.map((content) =>
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
)
}
<h1
class="mb-4 text-balance text-center text-2xl font-bold text-neutral-800 dark:text-neutral-200 sm:text-3xl md:mb-6 md:text-left"
>
{post.data.title}
</h1>
<div class="space-y-8">
{
post.data.contents.map((content) => (
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
{content}
</p>
))
}
</div>
</div>
</div>
</div>
</div>
</div>
</MainLayout>

View file

@ -3,16 +3,16 @@
import MainLayout from "../../layouts/MainLayout.astro";
import ProductTabBtn from "../../components/ui/buttons/ProductTabBtn.astro";
import PrimaryCTA from "../../components/ui/buttons/PrimaryCTA.astro";
import { Image } from "astro:assets";
import { getCollection } from "astro:content";
// Global declaration for gsap animation library
declare global {
interface Window {
gsap: any;
}
}
import { Image } from "astro:assets";
import { getCollection } from "astro:content";
// This gets the static paths for all the unique products
export async function getStaticPaths() {
const productEntries = await getCollection("products");
return productEntries.map((product) => ({
@ -24,7 +24,10 @@ export async function getStaticPaths() {
const { product } = Astro.props;
---
<MainLayout title={product.data.main.title + " | 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.">
<MainLayout
title={product.data.main.title + " | 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 id="overlay" class="fixed inset-0 bg-neutral-200 dark:bg-neutral-800">
</div>
@ -111,10 +114,10 @@ const { product } = Astro.props;
product.data.descriptionList.map((list) => (
<div class="flex">
<div>
<h3 class="text-base font-bold text-gray-800 dark:text-gray-200 sm:text-lg">
<h3 class="text-base font-bold text-neutral-800 dark:text-neutral-200 sm:text-lg">
{list.title}
</h3>
<p class="mt-1 text-gray-600 dark:text-gray-400">
<p class="mt-1 text-neutral-600 dark:text-neutral-400">
{list.subTitle}
</p>
</div>
@ -200,11 +203,23 @@ const { product } = Astro.props;
window.addEventListener("load", (event) => {
if (window.gsap) {
const gsap = window.gsap;
gsap.set("#fadeText", { autoAlpha: 0, y: 50 });
gsap.set("#fadeInUp", { autoAlpha: 0, y: 50 });
gsap.set("#fadeInMoveRight", { autoAlpha: 0, x: 300 });
gsap.set("#fadeText", {
autoAlpha: 0,
y: 50,
willChange: "transform, opacity",
});
gsap.set("#fadeInUp", {
autoAlpha: 0,
y: 50,
willChange: "transform, opacity",
});
gsap.set("#fadeInMoveRight", {
autoAlpha: 0,
x: 300,
willChange: "transform, opacity",
});
let timeline = gsap.timeline();
let timeline = gsap.timeline({ defaults: { overwrite: "auto" } });
timeline.to("#fadeText", {
duration: 1.5,