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:
parent
69530ff398
commit
04e8089294
18 changed files with 256 additions and 142 deletions
|
@ -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 LoginModal from "./ui/forms/LoginModal.astro";
|
||||||
import RegisterModal from "./ui/forms/RegisterModal.astro";
|
import RegisterModal from "./ui/forms/RegisterModal.astro";
|
||||||
import RecoverModal from "./ui/forms/RecoverModal.astro";
|
import RecoverModal from "./ui/forms/RecoverModal.astro";
|
||||||
import LoginBtn from "./ui/buttons/LoginBtn.astro";
|
import LoginBtn from "./ui/buttons/LoginBtn.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- Login Button -->
|
||||||
<LoginBtn />
|
<LoginBtn />
|
||||||
|
<!-- Login Modal -->
|
||||||
<LoginModal />
|
<LoginModal />
|
||||||
|
<!-- Register Modal -->
|
||||||
<RegisterModal />
|
<RegisterModal />
|
||||||
|
<!-- Password Recovery Modal -->
|
||||||
<RecoverModal />
|
<RecoverModal />
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
---
|
---
|
||||||
|
// Destructure the properties from Astro.props
|
||||||
const { src, alt } = Astro.props;
|
const { src, alt } = Astro.props;
|
||||||
|
|
||||||
|
// Define TypeScript interface for the properties
|
||||||
interface Props {
|
interface Props {
|
||||||
src: string;
|
src: string;
|
||||||
alt: string;
|
alt: string;
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
---
|
---
|
||||||
|
// Import necessary components
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
// Import necessary types
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
|
||||||
|
// Extract blogEntry property from Astro.props
|
||||||
const { blogEntry } = Astro.props;
|
const { blogEntry } = Astro.props;
|
||||||
|
// Define Props interface
|
||||||
interface Props {
|
interface Props {
|
||||||
blogEntry: CollectionEntry<"blog">;
|
blogEntry: CollectionEntry<"blog">;
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<Image
|
<Image
|
||||||
class="size-[46px] rounded-full border-2 border-neutral-50"
|
class="size-[46px] rounded-full border-2 border-neutral-50"
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
---
|
---
|
||||||
|
// Import necessary components
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
// Import necessary types
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
// Extract blogEntry property from Astro.props
|
||||||
const { blogEntry } = Astro.props;
|
const { blogEntry } = Astro.props;
|
||||||
|
// Define Props interface
|
||||||
interface Props {
|
interface Props {
|
||||||
blogEntry: CollectionEntry<"blog">;
|
blogEntry: CollectionEntry<"blog">;
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<Image class="size-10 sm:h-14 sm:w-14 rounded-full" src={blogEntry.data.authorImage}
|
<Image
|
||||||
alt={blogEntry.data.authorImageAlt}
|
class="size-10 rounded-full sm:h-14 sm:w-14"
|
||||||
draggable={"false"} format={"avif"}>
|
src={blogEntry.data.authorImage}
|
||||||
</div>
|
alt={blogEntry.data.authorImageAlt}
|
||||||
|
draggable={"false"}
|
||||||
|
format={"avif"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
|
// Define props from Astro
|
||||||
const { id, collapseId, heading, content, first } = Astro.props;
|
const { id, collapseId, heading, content, first } = Astro.props;
|
||||||
|
// Define TypeScript interface for props
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
collapseId: string;
|
collapseId: string;
|
||||||
|
@ -8,45 +9,58 @@ interface Props {
|
||||||
content: string;
|
content: string;
|
||||||
first?: boolean;
|
first?: boolean;
|
||||||
}
|
}
|
||||||
|
// Define class names for the accordion and its content
|
||||||
const ACCORDION_CLASS_DEFAULT = "hs-accordion pb-3 active";
|
const ACCORDION_CLASS_DEFAULT = "hs-accordion pb-3 active";
|
||||||
const ACCORDION_CLASS_COLLAPSED = "hs-accordion pt-6 pb-3";
|
const ACCORDION_CLASS_COLLAPSED = "hs-accordion pt-6 pb-3";
|
||||||
const ACCORDION_CONTENT_CLASS =
|
const ACCORDION_CONTENT_CLASS =
|
||||||
"hs-accordion-content w-full overflow-hidden transition-[height] duration-300";
|
"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) {
|
function getAccordionClass(first: boolean = false) {
|
||||||
return first ? ACCORDION_CLASS_DEFAULT : ACCORDION_CLASS_COLLAPSED;
|
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}>
|
<div class={getAccordionClass(first)} id={id}>
|
||||||
|
<!-- The accordion button, which toggles the expanded/collapsed state -->
|
||||||
<button
|
<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"
|
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}
|
aria-controls={collapseId}
|
||||||
>
|
>
|
||||||
{heading}
|
{heading}
|
||||||
|
<!-- SVG Icon that is shown when the accordion is NOT active -->
|
||||||
<svg
|
<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"
|
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
|
<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"
|
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>
|
</button>
|
||||||
|
<!-- The collapsible content of the accordion -->
|
||||||
<div
|
<div
|
||||||
aria-labelledby={id}
|
aria-labelledby={id}
|
||||||
class={`${first ? ACCORDION_CONTENT_CLASS : "hidden " + ACCORDION_CONTENT_CLASS}`}
|
class={`${first ? ACCORDION_CONTENT_CLASS : "hidden " + ACCORDION_CONTENT_CLASS}`}
|
||||||
id={collapseId}
|
id={collapseId}
|
||||||
>
|
>
|
||||||
|
<!-- The content paragraph -->
|
||||||
<p class="text-pretty text-neutral-600 dark:text-neutral-400">
|
<p class="text-pretty text-neutral-600 dark:text-neutral-400">
|
||||||
{content}
|
{content}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
---
|
---
|
||||||
|
// Destructure the properties from Astro.props
|
||||||
const { title } = Astro.props;
|
const { title } = Astro.props;
|
||||||
|
|
||||||
|
// Define TypeScript interface for the properties
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
// 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 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 borderClasses = "border border-transparent";
|
||||||
const bgColorClasses = "bg-yellow-400 dark:focus:outline-none";
|
const bgColorClasses = "bg-yellow-400 dark:focus:outline-none";
|
||||||
const hoverClasses = "hover:bg-yellow-500";
|
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 disabledClasses = "disabled:pointer-events-none disabled:opacity-50";
|
||||||
const ringClasses = "ring-zinc-500 dark:ring-zinc-200";
|
const ringClasses = "ring-zinc-500 dark:ring-zinc-200";
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- Styled submit button with dynamic title -->
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${fontSizeClasses} ${disabledClasses} ${ringClasses}`}
|
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${fontSizeClasses} ${disabledClasses} ${ringClasses}`}
|
||||||
>{title}</button>
|
>{title}</button
|
||||||
|
>
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
---
|
---
|
||||||
|
// Destructure the properties from Astro.props
|
||||||
const { title, id, noArrow } = Astro.props;
|
const { title, id, noArrow } = Astro.props;
|
||||||
|
// Define TypeScript interface for the properties
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
noArrow?: boolean;
|
noArrow?: boolean;
|
||||||
}
|
}
|
||||||
|
// 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 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 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 disableClasses = "disabled:pointer-events-none disabled:opacity-50";
|
||||||
const fontSizeClasses = "2xl:text-base";
|
const fontSizeClasses = "2xl:text-base";
|
||||||
const ringClasses = "dark:ring-zinc-200";
|
const ringClasses = "dark:ring-zinc-200";
|
||||||
|
// SVG for an arrow icon
|
||||||
const arrowSVG = `<svg
|
const arrowSVG = `<svg
|
||||||
class="h-4 w-4 flex-shrink-0 transition duration-300 group-hover:translate-x-1"
|
class="h-4 w-4 flex-shrink-0 transition duration-300 group-hover:translate-x-1"
|
||||||
width="24"
|
width="24"
|
||||||
|
@ -28,12 +32,13 @@ const arrowSVG = `<svg
|
||||||
</svg>`;
|
</svg>`;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- Button with dynamic title, id, and optional arrow -->
|
||||||
<button
|
<button
|
||||||
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses}`}
|
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses}`}
|
||||||
id={id}
|
id={id}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
{noArrow ? null :
|
<!-- About Fragment: https://docs.astro.build/en/basics/astro-syntax/#fragments -->
|
||||||
<Fragment set:html={arrowSVG} />
|
<!-- Display the arrow based on the 'noArrow' property -->
|
||||||
}
|
{noArrow ? null : <Fragment set:html={arrowSVG} />}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,22 +1,29 @@
|
||||||
---
|
---
|
||||||
|
// Import necessary components and utilities
|
||||||
import AvatarBlog from "../avatars/AvatarBlog.astro";
|
import AvatarBlog from "../avatars/AvatarBlog.astro";
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
|
||||||
import { formatDate } from "../../../utils";
|
import { formatDate } from "../../../utils";
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
|
||||||
|
// Define data sources from Astro props
|
||||||
const { blogEntry } = Astro.props;
|
const { blogEntry } = Astro.props;
|
||||||
|
|
||||||
|
// Define TypeScript props interface
|
||||||
interface Props {
|
interface Props {
|
||||||
blogEntry: CollectionEntry<"blog">;
|
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
|
<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"
|
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}/`}
|
href={`/blog/${blogEntry.slug}/`}
|
||||||
data-astro-prefetch
|
data-astro-prefetch
|
||||||
>
|
>
|
||||||
|
<!-- The container for the blog post's cover image. Uses astro:assets' Image for image source -->
|
||||||
<div
|
<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]"
|
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"}
|
format={"avif"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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="absolute inset-x-0 top-0 z-10">
|
||||||
<div class="flex h-full flex-col p-4 sm:p-6">
|
<div class="flex h-full flex-col p-4 sm:p-6">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
@ -45,7 +52,7 @@ interface Props {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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="absolute inset-x-0 bottom-0 z-10">
|
||||||
<div class="flex h-full flex-col p-4 sm:p-6">
|
<div class="flex h-full flex-col p-4 sm:p-6">
|
||||||
<h3
|
<h3
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
---
|
---
|
||||||
|
// Import all required components and utilities
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
import AvatarBlogLarge from "../avatars/AvatarBlogLarge.astro";
|
import AvatarBlogLarge from "../avatars/AvatarBlogLarge.astro";
|
||||||
import PrimaryCTA from "../buttons/PrimaryCTA.astro";
|
import PrimaryCTA from "../buttons/PrimaryCTA.astro";
|
||||||
|
// Define data source from Astro.js incoming props
|
||||||
const { blogEntry } = Astro.props;
|
const { blogEntry } = Astro.props;
|
||||||
|
// Define TypeScript props interface
|
||||||
interface Props {
|
interface Props {
|
||||||
blogEntry: CollectionEntry<"blog">;
|
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">
|
<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="sm:order-2">
|
||||||
<div class="relative rounded-lg pt-[50%] sm:pt-[100%]">
|
<div class="relative rounded-lg pt-[50%] sm:pt-[100%]">
|
||||||
<Image
|
<Image
|
||||||
|
@ -24,8 +26,9 @@ interface Props {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Container for the blog post's heading, author avatar, author's role, and read more button -->
|
||||||
<div class="sm:order-1">
|
<div class="sm:order-1">
|
||||||
|
<!-- Blog title which is also a hyperlink to the blog detail page -->
|
||||||
<h2
|
<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"
|
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}
|
{blogEntry.data.description}
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
<!-- Container for the author's avatar and metadata -->
|
||||||
<div class="mt-6 flex items-center sm:mt-10">
|
<div class="mt-6 flex items-center sm:mt-10">
|
||||||
<AvatarBlogLarge blogEntry={blogEntry} />
|
<AvatarBlogLarge blogEntry={blogEntry} />
|
||||||
|
|
||||||
|
@ -49,7 +52,7 @@ interface Props {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Read More button which is a link to the blog post detailed page -->
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<PrimaryCTA
|
<PrimaryCTA
|
||||||
url={`/blog/${blogEntry.slug}/`}
|
url={`/blog/${blogEntry.slug}/`}
|
||||||
|
|
|
@ -1,33 +1,59 @@
|
||||||
---
|
---
|
||||||
|
// Import necessary modules and utilities
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
// Define data source from Astro.js incoming props
|
||||||
const { insightEntry } = Astro.props;
|
const { insightEntry } = Astro.props;
|
||||||
|
// Define TypeScript props interface
|
||||||
interface Props {
|
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}/`}>
|
<!-- The anchor tag is the root container for the "Insight" card. It links to the insight detail page. -->
|
||||||
<div class="relative pt-[50%] sm:pt-[70%] rounded-xl overflow-hidden">
|
<a
|
||||||
<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}
|
class="group outline-none ring-zinc-500 transition duration-300 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none"
|
||||||
alt={insightEntry.data.cardImageAlt}
|
href={`/insights/${insightEntry.slug}/`}
|
||||||
draggable={"false"}
|
>
|
||||||
format={"avif"} />
|
<!-- This is the container for the insight's cover image. -->
|
||||||
</div>
|
<div class="relative overflow-hidden rounded-xl pt-[50%] sm:pt-[70%]">
|
||||||
|
<Image
|
||||||
<div class="mt-7">
|
class="absolute start-0 top-0 size-full rounded-xl object-cover transition duration-500 ease-in-out group-hover:scale-105"
|
||||||
<h3 class="text-xl font-bold text-neutral-800 group-hover:text-neutral-600 dark:text-neutral-200 dark:group-hover:text-neutral-400">
|
src={insightEntry.data.cardImage}
|
||||||
{insightEntry.data.title}
|
alt={insightEntry.data.cardImageAlt}
|
||||||
</h3>
|
draggable={"false"}
|
||||||
<p class="mt-3 text-neutral-600 dark:text-neutral-400">
|
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}
|
{insightEntry.data.description}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-5 inline-flex items-center gap-x-1 text-[#fa5a15] dark:text-[#fb713b] decoration-2 group-hover:underline font-medium">
|
<!-- The "Read More" hyperlink going to the full insight. With an arrow icon -->
|
||||||
Read more
|
<p
|
||||||
<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>
|
class="mt-5 inline-flex items-center gap-x-1 font-medium text-[#fa5a15] decoration-2 group-hover:underline dark:text-[#fb713b]"
|
||||||
</p>
|
>
|
||||||
</div>
|
Read more
|
||||||
</a>
|
<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>
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
---
|
---
|
||||||
|
// Import necessary modules and utilities
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
|
||||||
import { formatDate } from "../../../utils";
|
import { formatDate } from "../../../utils";
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
// Define data source from Astro.js incoming props
|
||||||
const { blogEntry } = Astro.props;
|
const { blogEntry } = Astro.props;
|
||||||
|
// Define TypeScript props interface
|
||||||
interface Props {
|
interface Props {
|
||||||
blogEntry: CollectionEntry<"blog">;
|
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
|
<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"
|
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="#"
|
href="#"
|
||||||
>
|
>
|
||||||
|
<!-- The blog post's cover image. It uses astro:assets' Image for loading the image optimally -->
|
||||||
<div>
|
<div>
|
||||||
<Image
|
<Image
|
||||||
class="aspect-video rounded-xl"
|
class="aspect-video rounded-xl"
|
||||||
|
@ -23,11 +26,13 @@ interface Props {
|
||||||
draggable={"false"}
|
draggable={"false"}
|
||||||
format={"avif"}
|
format={"avif"}
|
||||||
/>
|
/>
|
||||||
|
<!-- The title of the blog post -->
|
||||||
<h3
|
<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"
|
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}
|
{blogEntry.data.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
<!-- The formatted publication date of the blog post -->
|
||||||
<p class="mt-2 text-sm text-neutral-600 dark:text-neutral-400">
|
<p class="mt-2 text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
{formatDate(blogEntry.data.pubDate)}
|
{formatDate(blogEntry.data.pubDate)}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,27 +1,30 @@
|
||||||
---
|
---
|
||||||
|
// Import necessary modules and utilities
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
// Define data source from Astro.js incoming props
|
||||||
const { product } = Astro.props;
|
const { product } = Astro.props;
|
||||||
|
// Define TypeScript props interface
|
||||||
interface Props {
|
interface Props {
|
||||||
product: CollectionEntry<"products">;
|
product: CollectionEntry<"products">;
|
||||||
}
|
}
|
||||||
|
// Define classes to be used with the Image component
|
||||||
const imageClass =
|
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";
|
"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 = `
|
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">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
|
||||||
</svg>`;
|
</svg>`;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- A clickable card that leads to the details of the product-->
|
||||||
<a
|
<a
|
||||||
href={"/products/" + product.slug}
|
href={"/products/" + product.slug}
|
||||||
data-astro-prefetch
|
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"
|
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
|
<Image
|
||||||
src={product.data.main.imgCard}
|
src={product.data.main.imgCard}
|
||||||
alt={product.data.main.imgAlt}
|
alt={product.data.main.imgAlt}
|
||||||
|
@ -29,12 +32,12 @@ const AnchorSVG = `
|
||||||
class={imageClass}
|
class={imageClass}
|
||||||
format={"avif"}
|
format={"avif"}
|
||||||
/>
|
/>
|
||||||
|
<!-- An overlay gradient that sits on top of the product image-->
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-neutral-800 via-transparent to-transparent opacity-50"
|
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-neutral-800 via-transparent to-transparent opacity-50"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- The product's subtitle and the anchor SVG icon-->
|
||||||
<span
|
<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"
|
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} />
|
>{product.data.main.subTitle} <Fragment set:html={AnchorSVG} />
|
||||||
|
|
|
@ -1,27 +1,30 @@
|
||||||
---
|
---
|
||||||
|
// Import necessary modules and utilities
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
// Define data source from Astro.js incoming props
|
||||||
const { product } = Astro.props;
|
const { product } = Astro.props;
|
||||||
|
// Define TypeScript props interface
|
||||||
interface Props {
|
interface Props {
|
||||||
product: CollectionEntry<"products">;
|
product: CollectionEntry<"products">;
|
||||||
}
|
}
|
||||||
|
// Define classes to be used with the Image component
|
||||||
const imageClass =
|
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";
|
"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 = `
|
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">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
|
||||||
</svg>`;
|
</svg>`;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- The anchor tag is the main container for the product card. When clicked, this leads to the details of the product. -->
|
||||||
<a
|
<a
|
||||||
href={"/products/" + product.slug}
|
href={"/products/" + product.slug}
|
||||||
data-astro-prefetch
|
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"
|
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
|
<Image
|
||||||
src={product.data.main.imgCard}
|
src={product.data.main.imgCard}
|
||||||
alt={product.data.main.imgAlt}
|
alt={product.data.main.imgAlt}
|
||||||
|
@ -29,12 +32,12 @@ const AnchorSVG = `
|
||||||
class={imageClass}
|
class={imageClass}
|
||||||
format={"avif"}
|
format={"avif"}
|
||||||
/>
|
/>
|
||||||
|
<!-- This container includes a gradient overlay over the product's image -->
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-neutral-800 via-transparent to-transparent opacity-50"
|
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-neutral-800 via-transparent to-transparent opacity-50"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- This container includes product's subtitle and an SVG icon-->
|
||||||
<span
|
<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"
|
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
|
>{product.data.main.subTitle} <Fragment set:html={AnchorSVG} /></span
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
// Import section components
|
// Import section components
|
||||||
import MainLayout from "../layouts/MainLayout.astro";
|
import MainLayout from "../layouts/MainLayout.astro";
|
||||||
import Btn404 from "../components/ui/buttons/Btn404.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
|
<MainLayout
|
||||||
|
@ -14,25 +21,26 @@ import Btn404 from "../components/ui/buttons/Btn404.astro";
|
||||||
<h1
|
<h1
|
||||||
class="text-dark mb-4 text-7xl font-extrabold text-yellow-500 dark:text-yellow-400 lg:text-9xl"
|
class="text-dark mb-4 text-7xl font-extrabold text-yellow-500 dark:text-yellow-400 lg:text-9xl"
|
||||||
>
|
>
|
||||||
404
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
<p
|
||||||
class="mb-4 text-balance text-3xl font-bold tracking-tight text-neutral-700 dark:text-neutral-300 md:text-4xl"
|
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>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
class="mb-4 text-pretty text-lg text-neutral-600 dark:text-neutral-400"
|
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
|
{content}
|
||||||
your masterpiece.
|
|
||||||
</p>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
<!--JavaScript code that adds click event to the Button, resulting in going back to the previous page in history-->
|
||||||
<script>
|
<script>
|
||||||
document.getElementById("go-back")?.addEventListener("click", () => {
|
document.getElementById("go-back")?.addEventListener("click", () => {
|
||||||
history.back();
|
history.back();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
// Import section components
|
// Import necessary components and utilities
|
||||||
import MainLayout from "../../layouts/MainLayout.astro";
|
import MainLayout from "../../layouts/MainLayout.astro";
|
||||||
import AvatarBlogLarge from "../../components/ui/avatars/AvatarBlogLarge.astro";
|
import AvatarBlogLarge from "../../components/ui/avatars/AvatarBlogLarge.astro";
|
||||||
import CardRelated from "../../components/ui/cards/CardRelated.astro";
|
import CardRelated from "../../components/ui/cards/CardRelated.astro";
|
||||||
|
@ -8,6 +8,7 @@ import { capitalize, formatDate } from "../../utils";
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import type { CollectionEntry } 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() {
|
export async function getStaticPaths() {
|
||||||
const blogPosts = await getCollection("blog");
|
const blogPosts = await getCollection("blog");
|
||||||
return blogPosts.map((post) => ({
|
return blogPosts.map((post) => ({
|
||||||
|
@ -15,11 +16,16 @@ export async function getStaticPaths() {
|
||||||
props: { post },
|
props: { post },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
// Get the current post's data
|
||||||
const { post } = Astro.props;
|
const { post } = Astro.props;
|
||||||
|
|
||||||
|
// Get all blog posts
|
||||||
const blogPosts: CollectionEntry<"blog">[] = await getCollection("blog");
|
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,
|
(blogEntry) => blogEntry.slug !== post.slug,
|
||||||
);
|
);
|
||||||
---
|
---
|
||||||
|
@ -39,6 +45,7 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
|
||||||
<div
|
<div
|
||||||
class="hs-tooltip inline-block [--placement:bottom] [--trigger:hover]"
|
class="hs-tooltip inline-block [--placement:bottom] [--trigger:hover]"
|
||||||
>
|
>
|
||||||
|
<!--Post metadata and author info-->
|
||||||
<span
|
<span
|
||||||
class="font-bold text-neutral-700 dark:text-neutral-300"
|
class="font-bold text-neutral-700 dark:text-neutral-300"
|
||||||
>
|
>
|
||||||
|
@ -62,17 +69,18 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!--Blog post title-->
|
||||||
<h2
|
<h2
|
||||||
class="mb-3 text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-3xl"
|
class="mb-3 text-2xl font-bold text-neutral-800 dark:text-neutral-200 md:text-3xl"
|
||||||
>
|
>
|
||||||
{post.data.title}
|
{post.data.title}
|
||||||
</h2>
|
</h2>
|
||||||
|
<!--Blog post contents-->
|
||||||
<div class="mb-5 space-y-5 md:mb-8 md:space-y-8">
|
<div class="mb-5 space-y-5 md:mb-8 md:space-y-8">
|
||||||
{
|
{
|
||||||
post.data.contents.map((content: string, index: any) =>
|
post.data.contents.map((content: string, index: any) =>
|
||||||
index === 1 ? (
|
index === 1 ? (
|
||||||
|
<>
|
||||||
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
|
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
|
||||||
{content}
|
{content}
|
||||||
</p>
|
</p>
|
||||||
|
@ -83,6 +91,7 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
|
||||||
draggable={"false"}
|
draggable={"false"}
|
||||||
format={"avif"}
|
format={"avif"}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
|
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
|
||||||
{content}
|
{content}
|
||||||
|
@ -91,7 +100,7 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<!--Blog post tags-->
|
||||||
<div class="space-x-2">
|
<div class="space-x-2">
|
||||||
{
|
{
|
||||||
post.data.tags?.map((tag: string) => (
|
post.data.tags?.map((tag: string) => (
|
||||||
|
@ -103,7 +112,7 @@ const relatedPosts: CollectionEntry<"blog"> = blogPosts.filter(
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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="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">
|
<div class="mb-10 max-w-2xl">
|
||||||
<h2
|
<h2
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
---
|
---
|
||||||
// Import section components
|
// Import section components
|
||||||
import MainLayout from "../../layouts/MainLayout.astro";
|
import MainLayout from "../../layouts/MainLayout.astro";
|
||||||
import AvatarBlogLarge from "../../components/ui/avatars/AvatarBlogLarge.astro";
|
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
import { capitalize, formatDate } from "../../utils";
|
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
|
|
||||||
|
// Use `getStaticPaths` to generate static routes for generated pages on build
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const insightPosts = await getCollection("insights");
|
const insightPosts = await getCollection("insights");
|
||||||
return insightPosts.map((post) => ({
|
return insightPosts.map((post) => ({
|
||||||
|
@ -14,46 +12,46 @@ export async function getStaticPaths() {
|
||||||
props: { post },
|
props: { post },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
// Get the props for this page that define a specific insight post
|
||||||
const { post } = Astro.props;
|
const { post } = Astro.props;
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainLayout
|
<MainLayout
|
||||||
title={post.data.title + " | ScrewFast"}
|
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."
|
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="mx-auto max-w-screen-xl px-4 md:px-8">
|
||||||
<div class="grid gap-8 md:grid-cols-2 lg:gap-12">
|
<div class="grid gap-8 md:grid-cols-2 lg:gap-12">
|
||||||
<div>
|
<div>
|
||||||
<div class="h-64 overflow-hidden rounded-lg shadow-lg md:h-auto">
|
<div class="h-64 overflow-hidden rounded-lg shadow-lg md:h-auto">
|
||||||
<Image
|
<Image
|
||||||
class="h-full w-full object-cover object-center"
|
class="h-full w-full object-cover object-center"
|
||||||
src={post.data.cardImage}
|
src={post.data.cardImage}
|
||||||
alt={post.data.cardImageAlt}
|
alt={post.data.cardImageAlt}
|
||||||
draggable={"false"}
|
draggable={"false"}
|
||||||
format={"avif"}
|
format={"avif"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="md:pt-8">
|
<div class="md:pt-8">
|
||||||
|
<h1
|
||||||
<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>
|
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"
|
||||||
<div class="space-y-8">
|
>
|
||||||
{
|
{post.data.title}
|
||||||
post.data.contents.map((content) =>
|
</h1>
|
||||||
|
<div class="space-y-8">
|
||||||
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
|
{
|
||||||
{content}
|
post.data.contents.map((content) => (
|
||||||
</p>
|
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
|
||||||
)
|
{content}
|
||||||
}
|
</p>
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
import MainLayout from "../../layouts/MainLayout.astro";
|
import MainLayout from "../../layouts/MainLayout.astro";
|
||||||
import ProductTabBtn from "../../components/ui/buttons/ProductTabBtn.astro";
|
import ProductTabBtn from "../../components/ui/buttons/ProductTabBtn.astro";
|
||||||
import PrimaryCTA from "../../components/ui/buttons/PrimaryCTA.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 {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
gsap: any;
|
gsap: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
import { Image } from "astro:assets";
|
// This gets the static paths for all the unique products
|
||||||
|
|
||||||
import { getCollection } from "astro:content";
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const productEntries = await getCollection("products");
|
const productEntries = await getCollection("products");
|
||||||
return productEntries.map((product) => ({
|
return productEntries.map((product) => ({
|
||||||
|
@ -24,7 +24,10 @@ export async function getStaticPaths() {
|
||||||
const { product } = Astro.props;
|
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 id="overlay" class="fixed inset-0 bg-neutral-200 dark:bg-neutral-800">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -111,10 +114,10 @@ const { product } = Astro.props;
|
||||||
product.data.descriptionList.map((list) => (
|
product.data.descriptionList.map((list) => (
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div>
|
<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}
|
{list.title}
|
||||||
</h3>
|
</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}
|
{list.subTitle}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -200,11 +203,23 @@ const { product } = Astro.props;
|
||||||
window.addEventListener("load", (event) => {
|
window.addEventListener("load", (event) => {
|
||||||
if (window.gsap) {
|
if (window.gsap) {
|
||||||
const gsap = window.gsap;
|
const gsap = window.gsap;
|
||||||
gsap.set("#fadeText", { autoAlpha: 0, y: 50 });
|
gsap.set("#fadeText", {
|
||||||
gsap.set("#fadeInUp", { autoAlpha: 0, y: 50 });
|
autoAlpha: 0,
|
||||||
gsap.set("#fadeInMoveRight", { autoAlpha: 0, x: 300 });
|
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", {
|
timeline.to("#fadeText", {
|
||||||
duration: 1.5,
|
duration: 1.5,
|
||||||
|
|
Loading…
Add table
Reference in a new issue