Ensure correct URL handling for product page translations

This commit is contained in:
Emil Gulamov 2024-09-07 21:51:07 +04:00
parent a5fd59b1cd
commit 020679f26c
18 changed files with 664 additions and 73 deletions

View file

@ -1,6 +1,5 @@
import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
import vercelStatic from "@astrojs/vercel/static";
import sitemap from "@astrojs/sitemap";
import compressor from "astro-compressor";
import starlight from "@astrojs/starlight";
@ -123,5 +122,4 @@ export default defineConfig({
clientPrerender: true,
directRenderScript: true,
},
adapter: vercelStatic(),
});

53
package-lock.json generated
View file

@ -12,7 +12,6 @@
"@astrojs/starlight": "^0.26.2",
"@astrojs/starlight-tailwind": "^2.0.3",
"@astrojs/tailwind": "^5.1.0",
"@astrojs/vercel": "^7.8.0",
"@preline/accordion": "^2.4.1",
"@preline/collapse": "^2.4.1",
"@preline/dropdown": "^2.4.1",
@ -278,23 +277,6 @@
"node": "^18.17.1 || ^20.3.0 || >=21.0.0"
}
},
"node_modules/@astrojs/vercel": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-7.8.0.tgz",
"integrity": "sha512-cpY14PPrKhAsYZp/tfRSIfKdiPctvvPfOH8z3USLJEAJ5lLfToUHEGTJzNfGqCBtd61QftypIxIlTHGuFY30UQ==",
"dependencies": {
"@astrojs/internal-helpers": "0.4.1",
"@vercel/analytics": "^1.3.1",
"@vercel/edge": "^1.1.2",
"@vercel/nft": "^0.27.3",
"esbuild": "^0.21.5",
"fast-glob": "^3.3.2",
"web-vitals": "^3.5.2"
},
"peerDependencies": {
"astro": "^4.2.0"
}
},
"node_modules/@astrojs/yaml2ts": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.1.tgz",
@ -2078,31 +2060,6 @@
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
},
"node_modules/@vercel/analytics": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.3.1.tgz",
"integrity": "sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==",
"dependencies": {
"server-only": "^0.0.1"
},
"peerDependencies": {
"next": ">= 13",
"react": "^18 || ^19"
},
"peerDependenciesMeta": {
"next": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/@vercel/edge": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@vercel/edge/-/edge-1.1.2.tgz",
"integrity": "sha512-wt5SnhsMahWX8U9ZZhFUQoiXhMn/CUxA5xeMdZX1cwyOL1ZbDR3rNI8HRT9RSU73nDxeF6jlnqJyp/0Jy0VM2A=="
},
"node_modules/@vercel/nft": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.27.3.tgz",
@ -7527,11 +7484,6 @@
"node": ">=10"
}
},
"node_modules/server-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@ -8776,11 +8728,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/web-vitals": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz",
"integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg=="
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View file

@ -15,7 +15,6 @@
"@astrojs/starlight": "^0.26.2",
"@astrojs/starlight-tailwind": "^2.0.3",
"@astrojs/tailwind": "^5.1.0",
"@astrojs/vercel": "^7.8.0",
"@preline/accordion": "^2.4.1",
"@preline/collapse": "^2.4.1",
"@preline/dropdown": "^2.4.1",

View file

@ -74,6 +74,26 @@ import Icon from "./icons/Icon.astro";
// Disable the selection of the same language
if (lang === url.pathname.split("/")[1]) return;
// If on product detail page, adjust the slug
if (url.pathname.includes("/products/")) {
let productSlug = pathParts[pathParts.length - 1]; // Get the last part of the URL which is the slug
if (lang === "fr") {
// If switching to French, ensure the slug has "-fr"
if (!productSlug.endsWith("-fr")) {
productSlug += "-fr";
}
} else {
// If switching to English, remove "-fr" from the slug
if (productSlug.endsWith("-fr")) {
productSlug = productSlug.replace("-fr", "");
}
}
// Update the last part of the path with the adjusted slug
pathParts[pathParts.length - 1] = productSlug;
}
if (
url.pathname.includes("/post") ||
url.pathname.includes("/insight")

View file

@ -4,7 +4,12 @@ import { Image } from "astro:assets";
import Icon from "@components/ui/icons/Icon.astro";
import type { CollectionEntry } from "astro:content";
const { product } = Astro.props;
const { productEntry, productLocale = "" } = Astro.props;
interface Props {
productEntry: CollectionEntry<"products">;
productLocale?: string;
}
interface Props {
product: CollectionEntry<"products">;
@ -16,14 +21,14 @@ const imageClass =
<!-- A clickable card that leads to the details of the product-->
<a
href={"/products/" + product.slug}
href={productLocale !== "" ? `/${productLocale}/products/${productEntry.slug}/` : `/products/${productEntry.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}
src={productEntry.data.main.imgCard}
alt={productEntry.data.main.imgAlt}
draggable={"false"}
class={imageClass}
format={"avif"}
@ -36,6 +41,6 @@ const imageClass =
<!-- 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.description} <Icon name="openInNew" />
>{productEntry.data.description} <Icon name="openInNew" />
</span>
</a>

View file

@ -4,7 +4,12 @@ import { Image } from "astro:assets";
import Icon from "@components/ui/icons/Icon.astro";
import type { CollectionEntry } from "astro:content";
const { product } = Astro.props;
const { productEntry, productLocale = "" } = Astro.props;
interface Props {
productEntry: CollectionEntry<"products">;
productLocale?: string;
}
interface Props {
product: CollectionEntry<"products">;
@ -16,14 +21,14 @@ const imageClass =
<!-- 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}
href={productLocale !== "" ? `/${productLocale}/products/${productEntry.slug}/` : `/products/${productEntry.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}
src={productEntry.data.main.imgCard}
alt={productEntry.data.main.imgAlt}
draggable={"false"}
class={imageClass}
format={"avif"}
@ -36,6 +41,6 @@ const imageClass =
<!-- 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.description} <Icon name="openInNew" /></span
>{productEntry.data.description} <Icon name="openInNew" /></span
>
</a>

View file

@ -0,0 +1,55 @@
---
title: "SF-AB A765"
description: "Ensemble de Vis Assorties"
main:
id: 2
content: |
Découvrez l'ensemble de vis assorties SF-AB A765 la solution ultime pour vos besoins de fixation. Ce set complet comprend une grande variété de vis soigneusement sélectionnées pour aborder divers projets avec facilité et précision.
imgCard: "@/images/product-image-2.avif"
imgMain: "@/images/product-image-main-2.avif"
imgAlt: "Boîtes factices de l'ensemble de vis assorties"
tabs:
- id: "tabs-with-card-item-1"
dataTab: "#tabs-with-card-1"
title: "Description"
- id: "tabs-with-card-item-2"
dataTab: "#tabs-with-card-2"
title: "Spécifications"
- id: "tabs-with-card-item-3"
dataTab: "#tabs-with-card-3"
title: "Plans"
longDescription:
title: "Solutions Polyvalentes de Fixation"
subTitle: |
L'ensemble de vis assorties SF-AB A765 offre une polyvalence et une commodité inégalées, ce qui en fait le choix idéal pour les amateurs de bricolage et les professionnels. Avec une sélection complète de vis, vous aurez toujours le bon élément de fixation pour chaque tâche.
btnTitle: "Contactez les ventes pour en savoir plus"
btnURL: "#"
descriptionList:
- title: "Grande Variété"
subTitle: "Comprend une gamme diversifiée de types et de tailles de vis pour s'adapter à diverses applications et matériaux."
- title: "Facilité d'Utilisation"
subTitle: "Chaque vis est conçue pour une installation facile, garantissant une fixation sans tracas à chaque fois."
- title: "Commodité"
subTitle: "Élimine le besoin de multiples déplacements au magasin de bricolage, économisant temps et effort sur vos projets."
specificationsLeft:
- title: "Matériau"
subTitle: "Fabriqué à partir de matériaux de haute qualité tels que l'acier inoxydable, assurant durabilité et résistance à la corrosion."
- title: "Assortiment"
subTitle: "Contient un assortiment généreux de vis, incluant des vis à bois, des vis mécaniques, et des vis pour tôle."
- title: "Quantité"
subTitle: "Chaque set comprend une quantité suffisante de vis pour gérer une large gamme de projets et de tâches."
- title: "Tailles"
subTitle: "Disponible en différentes tailles pour s'adapter aux exigences de divers projets, assurant compatibilité et polyvalence."
tableData:
- feature: ["Spécification", "Valeur"]
description:
- ["Longueur (mm)", "Divers"]
- ["Poids (g)", "N/A"]
- ["Matériau", "Acier Inoxydable"]
- ["Finition", "Assortie"]
- ["Contenu du Pack", "Diverses vis dans un ensemble"]
blueprints:
first: "@/images/blueprint-1.avif"
second: "@/images/blueprint-2.avif"
slug: a765-fr
---

View file

@ -0,0 +1,56 @@
---
title: "SF-BN B203"
description: "Ensemble de boulons à vis et écrous"
main:
id: 3
content: |
Découvrez le SF-BN B203 votre compagnon fiable pour des fixations de qualité professionnelle. Cet ensemble complet comprend une sélection polyvalente de boulons à vis et d'écrous, méticuleusement conçus pour offrir la tenue la plus solide pour vos projets de construction et d'assemblage.
imgCard: "@/images/product-image-3.avif"
imgMain: "@/images/product-image-main-3.avif"
imgAlt: "Maquettes de boîtes d'un ensemble de boulons à vis et écrous"
tabs:
- id: "tabs-with-card-item-1"
dataTab: "#tabs-with-card-1"
title: "Description"
- id: "tabs-with-card-item-2"
dataTab: "#tabs-with-card-2"
title: "Spécifications"
- id: "tabs-with-card-item-3"
dataTab: "#tabs-with-card-3"
title: "Plans"
longDescription:
title: "La force rencontre la précision"
subTitle: |
L'ensemble de boulons à vis et écrous SF-BN B203 offre une durabilité robuste et une précision pour les professionnels de la construction, garantissant des performances fiables dans chaque application, de l'encadrement de maisons à l'assemblage de machines.
btnTitle: "Contactez le service commercial pour en savoir plus"
btnURL: "#"
descriptionList:
- title: "Résistance à la corrosion"
subTitle: "Le revêtement en zinc offre non seulement un aspect poli, mais protège également contre la corrosion, garantissant une longue durée de vie."
- title: "Sécurité améliorée"
subTitle: "Un ajustement sécurisé se traduit par des structures plus sûres avec un risque réduit de défaillance des composants."
- title: "Praticité"
subTitle: "Cet ensemble tout-en-un signifie que vous avez la bonne taille sous la main, réduisant les retards de projet et les allers-retours supplémentaires au magasin de bricolage."
specificationsLeft:
- title: "Composition du matériau"
subTitle: "Fabriqué à partir d'acier de haute qualité, offrant résistance et fiabilité pour des applications exigeantes."
- title: "Finition de surface"
subTitle: "Protégé par un revêtement de zinc pour offrir une résistance accrue à la corrosion et une longévité."
- title: "Quantité par ensemble"
subTitle: "L'ensemble comprend une sélection équilibrée de 25 boulons à vis et 25 écrous assortis."
- title: "Assortiment de tailles"
subTitle: "Comprend une gamme complète de tailles pour répondre à diverses exigences de projet, garantissant compatibilité et polyvalence."
specificationsRight:
- title: "Détails du filetage"
subTitle: "Conçu avec des filets coupés avec précision pour un ajustement sécurisé et une installation facile."
- title: "Propriétés mécaniques"
subTitle: "Chaque boulon et écrou est conçu pour répondre à des indices de charge spécifiques ou à des normes de résistance, adaptés aux applications structurelles."
- title: "Normes et certifications"
subTitle: "Conforme aux normes et certifications industrielles pertinentes, garantissant une qualité et une sécurité constantes."
- title: "Applications adaptées"
subTitle: "Idéal pour un large éventail d'utilisations, des environnements de construction aux assemblages mécaniques qui nécessitent des joints solides et sécurisés."
blueprints:
first: "@/images/blueprint-1.avif"
second: "@/images/blueprint-2.avif"
slug: b203-fr
---

View file

@ -0,0 +1,56 @@
---
title: "SF-FN F303"
description: "Boulons hexagonaux"
main:
id: 4
content: |
Découvrez les boulons hexagonaux SF-FN F303 le choix parfait pour les applications de fixation lourdes. Conçus avec précision et durabilité, ces boulons hexagonaux offrent la force et la fiabilité dont vous avez besoin pour vos projets les plus exigeants.
imgCard: "@/images/product-image-4.avif"
imgMain: "@/images/product-image-main-4.avif"
imgAlt: "Maquettes de boîtes de boulons hexagonaux"
tabs:
- id: "tabs-with-card-item-1"
dataTab: "#tabs-with-card-1"
title: "Description"
- id: "tabs-with-card-item-2"
dataTab: "#tabs-with-card-2"
title: "Spécifications"
- id: "tabs-with-card-item-3"
dataTab: "#tabs-with-card-3"
title: "Plans"
longDescription:
title: "Solutions de fixation robustes"
subTitle: |
Les boulons hexagonaux SF-FN F303 sont conçus pour relever les défis de fixation les plus difficiles avec aisance. Que vous travailliez sur des projets de construction ou des machines lourdes, ces boulons hexagonaux offrent la force et la fiabilité nécessaires.
btnTitle: "Contactez le service commercial pour en savoir plus"
btnURL: "#"
descriptionList:
- title: "Solidité et durabilité"
subTitle: "Fabriqués à partir de matériaux de haute qualité, ces boulons hexagonaux sont conçus pour supporter des charges lourdes et des conditions difficiles."
- title: "Ingénierie de précision"
subTitle: "Conçus avec des filets coupés avec précision et des spécifications exactes, garantissant un ajustement serré et sécurisé à chaque fois."
- title: "Polyvalence"
subTitle: "Adaptés à une large gamme d'applications, de la construction aux machines, offrant des solutions de fixation polyvalentes."
specificationsLeft:
- title: "Matériau"
subTitle: "Fabriqués en acier ou en alliage de qualité supérieure, offrant une résistance exceptionnelle et une résistance à la corrosion."
- title: "Conception du filetage"
subTitle: "Des filets coupés avec précision assurent une adhérence optimale et une fiabilité même dans des environnements à haute contrainte."
- title: "Quantité"
subTitle: "Chaque ensemble comprend une quantité suffisante de boulons hexagonaux pour divers projets et applications."
- title: "Tailles"
subTitle: "Disponible dans une gamme de tailles pour répondre aux différentes exigences des projets, garantissant polyvalence et compatibilité."
specificationsRight:
- title: "Finition"
subTitle: "Fini avec un revêtement protecteur pour améliorer la résistance à la corrosion et prolonger la durée de vie."
- title: "Capacité de charge"
subTitle: "Conçus pour répondre ou dépasser les normes industrielles en matière de capacité de charge, garantissant des performances fiables sous des charges lourdes."
- title: "Certifications"
subTitle: "Conformes aux normes et certifications industrielles pertinentes, garantissant qualité et fiabilité."
- title: "Applications"
subTitle: "Idéal pour une utilisation dans la construction, les machines, l'automobile et d'autres applications lourdes nécessitant une fixation solide et fiable."
blueprints:
first: "@/images/blueprint-1.avif"
second: "@/images/blueprint-2.avif"
slug: f303-fr
---

View file

@ -0,0 +1,56 @@
---
title: "SF-TB T845"
description: "Vis à machine"
main:
id: 1
content: |
Découvrez le SF-TB T845 votre solution de prédilection pour la fixation de précision dans les machines et équipements. Cet ensemble complet de vis à machine est méticuleusement conçu pour répondre aux exigences strictes des applications industrielles, garantissant une fixation sécurisée et fiable.
imgCard: "@/images/product-image-1.avif"
imgMain: "@/images/product-image-main-1.avif"
imgAlt: "Maquettes de boîtes de vis à machine"
tabs:
- id: "tabs-with-card-item-1"
dataTab: "#tabs-with-card-1"
title: "Description"
- id: "tabs-with-card-item-2"
dataTab: "#tabs-with-card-2"
title: "Spécifications"
- id: "tabs-with-card-item-3"
dataTab: "#tabs-with-card-3"
title: "Plans"
longDescription:
title: "Solutions de fixation de précision"
subTitle: |
Les vis à machine SF-TB T845 offrent une précision et une fiabilité inégalées pour les applications industrielles, garantissant un fonctionnement fluide et une longue durée de vie pour vos machines et équipements.
btnTitle: "Contactez le service commercial pour en savoir plus"
btnURL: "#"
descriptionList:
- title: "Durabilité"
subTitle: "Fabriquées à partir de matériaux de haute qualité, ces vis à machine sont conçues pour résister aux rigueurs des environnements industriels."
- title: "Ingénierie de précision"
subTitle: "Conçues avec des filets coupés avec précision et des spécifications exactes, garantissant un ajustement serré et sécurisé pour chaque application."
- title: "Polyvalence"
subTitle: "Adaptées à une large gamme de machines et d'équipements, offrant des solutions de fixation polyvalentes pour divers besoins industriels."
specificationsLeft:
- title: "Composition du matériau"
subTitle: "Construites en acier ou alliage de qualité supérieure pour une résistance et une durabilité exceptionnelles."
- title: "Finition de surface"
subTitle: "Fini avec un revêtement protecteur pour améliorer la résistance à la corrosion et prolonger la durée de vie."
- title: "Quantité par ensemble"
subTitle: "Chaque ensemble contient un assortiment complet de vis à machine pour répondre aux divers besoins industriels."
- title: "Gamme de tailles"
subTitle: "Disponible en différentes tailles et longueurs pour s'adapter aux spécifications des machines et équipements."
specificationsRight:
- title: "Spécifications du filetage"
subTitle: "Des filets conçus avec précision assurent une adhérence optimale et une fiabilité, même dans les environnements à haute vibration."
- title: "Capacité de charge"
subTitle: "Conçues pour répondre ou dépasser les normes industrielles en matière de capacité de charge, garantissant une opération sûre et fiable."
- title: "Certifications"
subTitle: "Conformes aux normes et certifications industrielles pertinentes, garantissant qualité et fiabilité."
- title: "Applications"
subTitle: "Idéal pour une utilisation dans une large gamme de machines industrielles, équipements et assemblages nécessitant une fixation précise et sécurisée."
blueprints:
first: "@/images/blueprint-1.avif"
second: "@/images/blueprint-2.avif"
slug: t845-fr
---

View file

@ -0,0 +1,387 @@
---
// 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 { Image } from "astro:assets";
import { getCollection } from "astro:content";
import { SITE } from "@data/constants";
// Global declaration for gsap animation library
declare global {
interface Window {
gsap: any;
}
}
// This gets the static paths for all the unique products
export async function getStaticPaths() {
const productEntries = await getCollection("products", ({ id }) => {
return id.startsWith("fr/");
});
return productEntries.map((product) => ({
params: { slug: product.slug },
props: { product },
}));
}
const { product } = Astro.props;
const pageTitle: string = `${product.data.title} | ${SITE.title}`;
---
<MainLayout title={pageTitle}>
<div id="overlay" class="fixed inset-0 bg-neutral-200 dark:bg-neutral-800">
</div>
<section
class="mx-auto flex max-w-[85rem] flex-col px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div>
<p
id="fadeText"
class="mb-8 max-w-prose text-pretty font-light text-neutral-700 dark:text-neutral-300 sm:text-xl"
>
{product.data.main.content}
</p>
</div>
<div
class="flex flex-col items-center justify-between space-y-4 sm:flex-row sm:space-y-0"
>
<div id="fadeInUp">
<h1
class="block text-4xl font-bold tracking-tighter text-neutral-800 dark:text-neutral-200 sm:text-5xl md:text-6xl lg:text-7xl"
>
{product.data.title}
</h1>
<p class="text-lg text-neutral-600 dark:text-neutral-400">
{product.data.description}
</p>
</div>
<div>
<Image
id="fadeInMoveRight"
src={product.data.main.imgMain}
class="w-[600px]"
alt={product.data.main.imgAlt}
format={"avif"}
loading={"eager"}
/>
</div>
</div>
</section>
<div class="mx-auto max-w-[85rem] px-4 pt-10 sm:px-6 lg:px-8 lg:pt-14">
<nav
class="mx-auto grid max-w-6xl gap-y-px sm:flex sm:gap-x-4 sm:gap-y-0"
aria-label="Tabs"
role="tablist"
>
{
product.data.tabs.map((tab, index) => (
<ProductTabBtn
title={tab.title}
id={tab.id}
dataTab={tab.dataTab}
first={index === 0}
/>
))
}
</nav>
<div class="mt-12 md:mt-16">
<div id="tabs-with-card-1" role="tabpanel">
<div class="mx-auto max-w-[85rem] px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14">
<div class="grid gap-12 md:grid-cols-2">
<div class="lg:w-3/4">
<h2
class="text-balance text-3xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:leading-tight lg:text-4xl"
>
{product.data.longDescription.title}
</h2>
<p
class="mt-3 text-pretty text-neutral-600 dark:text-neutral-400"
>
{product.data.longDescription.subTitle}
</p>
<p class="mt-5">
<PrimaryCTA
title={product.data.longDescription.btnTitle}
url={product.data.longDescription.btnURL}
/>
</p>
</div>
<div class="space-y-6 lg:space-y-10">
{
product.data.descriptionList.map((list) => (
<div class="flex">
<div>
<h3 class="text-base font-bold text-neutral-800 dark:text-neutral-200 sm:text-lg">
{list.title}
</h3>
<p class="mt-1 text-neutral-600 dark:text-neutral-400">
{list.subTitle}
</p>
</div>
</div>
))
}
</div>
</div>
</div>
</div>
<div id="tabs-with-card-2" class="hidden" role="tabpanel">
<div class="mx-auto max-w-[85rem] px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14">
<div class="grid w-full grid-cols-1 gap-x-16 md:grid-cols-2">
<div class="max-w-md space-y-6">
{
product.data.specificationsLeft.map((spec) => (
<div>
<h3 class="block font-bold text-neutral-800 dark:text-neutral-200">
{spec.title}
</h3>
<p class="text-neutral-600 dark:text-neutral-400">
{spec.subTitle}
</p>
</div>
))
}
</div>
{
product.data.specificationsRight ? (
<div class="mt-6 max-w-md space-y-6 md:ml-auto md:mt-0">
{product.data.specificationsRight?.map((spec) => (
<div>
<h3 class="block font-bold text-neutral-800 dark:text-neutral-200">
{spec.title}
</h3>
<p class="text-neutral-600 dark:text-neutral-400">
{spec.subTitle}
</p>
</div>
))}
</div>
) : product.data.tableData ? (
<div class="mt-6 space-y-6 md:ml-auto md:mt-0">
<div class="flex flex-col">
<div class="-m-1.5 overflow-x-auto">
<div class="inline-block min-w-full p-1.5 align-middle">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-neutral-300 dark:divide-neutral-700">
<thead>
<tr>
{product.data.tableData?.[0].feature?.map(
(header) => (
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-neutral-500 dark:text-neutral-500"
>
{header}
</th>
)
)}
</tr>
</thead>
<tbody class="divide-y divide-neutral-300 dark:divide-neutral-700">
{product.data.tableData?.map((row) =>
// Wrap each row's content in a separate <tr> element
row.description.map((rowData) => (
<tr>
{/* Iterate through each cell value in the row's description array */}
{rowData.map((cellValue) => (
// Render each cell value in its own <td> element
<td class="whitespace-nowrap px-6 py-4 text-sm font-medium text-neutral-600 dark:text-neutral-400">
{cellValue}
</td>
))}
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
) : null
}
</div>
</div>
</div>
</div>
</div>
<div id="tabs-with-card-3" class="hidden" role="tabpanel">
<div class="mx-auto mb-20 flex w-full md:mb-28 2xl:w-4/5">
<div
class="relative left-12 top-12 z-10 overflow-hidden rounded-xl shadow-lg md:left-12 md:top-16 md:-ml-12 lg:ml-0"
>
{
product.data.blueprints.first && (
<Image
src={product.data.blueprints.first}
class="h-full w-full object-cover object-center"
alt="Blueprint Illustration"
format={"avif"}
/>
)
}
</div>
<div class="relative right-12 overflow-hidden rounded-xl shadow-xl">
{
product.data.blueprints.second && (
<Image
src={product.data.blueprints.second}
class="h-full w-full object-cover object-center"
alt="Blueprint Illustration"
format={"avif"}
/>
)
}
</div>
</div>
</div>
</MainLayout>
<script>
import { gsap } from "gsap";
type AnimationSettings = {
autoAlpha?: number;
y?: number;
x?: number;
willChange?: string;
};
function setElementAnimationDefaults(
id: string,
settings: AnimationSettings
) {
gsap.set(id, settings);
}
setElementAnimationDefaults("#fadeText", {
autoAlpha: 0,
y: 50,
willChange: "transform, opacity",
});
setElementAnimationDefaults("#fadeInUp", {
autoAlpha: 0,
y: 50,
willChange: "transform, opacity",
});
setElementAnimationDefaults("#fadeInMoveRight", {
autoAlpha: 0,
x: 300,
willChange: "transform, opacity",
});
let timeline = gsap.timeline({ defaults: { overwrite: "auto" } });
timeline.to("#fadeText", {
duration: 1.5,
autoAlpha: 1,
y: 0,
delay: 1,
ease: "power2.out",
});
timeline.to(
"#fadeInUp",
{ duration: 1.5, autoAlpha: 1, y: 0, ease: "power2.out" },
"-=1.2"
);
timeline.to(
"#fadeInMoveRight",
{ duration: 1.5, autoAlpha: 1, x: 0, ease: "power2.inOut" },
"-=1.4"
);
timeline.to("#overlay", { duration: 1, autoAlpha: 0, delay: 0.2 });
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
function setButtonInactive(btn: any, activeButton: any) {
if (btn !== activeButton) {
btn.classList.remove(
"active",
"bg-neutral-100",
"hover:border-transparent",
"dark:bg-white/[.05]"
);
const tabId = btn.getAttribute("data-target");
if (tabId) {
const contentElement = document.querySelector(tabId);
if (contentElement) {
contentElement.classList.add("hidden");
}
}
changeHeadingStyle(
btn,
["text-neutral-800", "dark:text-neutral-200"],
["text-orange-400", "dark:text-orange-300"]
);
}
}
function activateButton(button: any) {
button.classList.add(
"active",
"bg-neutral-100",
",hover:border-transparent",
"dark:bg-white/[.05]"
);
const tabId = button.getAttribute("data-target");
if (tabId) {
const contentElementToShow = document.querySelector(tabId);
if (contentElementToShow) {
contentElementToShow.classList.remove("hidden");
}
}
changeHeadingStyle(
button,
["text-orange-400", "dark:text-orange-300"],
["text-neutral-800", "dark:text-neutral-200"]
);
}
function changeHeadingStyle(
button: any,
addClasses: any,
removeClasses: any
) {
let heading = button.querySelector("span");
if (heading) {
heading.classList.remove(...removeClasses);
heading.classList.add(...addClasses);
}
}
const tabButtons = document.querySelectorAll("[data-target]");
if (tabButtons.length > 0) {
changeHeadingStyle(
tabButtons[0],
["text-orange-400", "dark:text-orange-300"],
[]
);
}
tabButtons.forEach((button) => {
button.addEventListener("click", () => {
tabButtons.forEach((btn) => setButtonInactive(btn, button));
activateButton(button);
});
});
});
</script>

View file

@ -13,7 +13,9 @@ import type { CollectionEntry } from "astro:content";
// Fetching all the product related content and sorting it by main.id
const product: CollectionEntry<"products">[] = (
await getCollection("products")
await getCollection("products", ({ id }) => {
return id.startsWith("fr/");
})
).sort(
(a: CollectionEntry<"products">, b: CollectionEntry<"products">) =>
a.data.main.id - b.data.main.id
@ -110,9 +112,9 @@ const testimonials = [
product.map((product, index) => {
const position = index % 4;
if (position === 0 || position === 3) {
return <CardSmall product={product} />;
return <CardSmall productEntry={product} productLocale="fr" />;
} else {
return <CardWide product={product} />;
return <CardWide productEntry={product} productLocale="fr" />;
}
})
}

View file

@ -13,9 +13,12 @@ declare global {
gsap: any;
}
}
// This gets the static paths for all the unique products
export async function getStaticPaths() {
const productEntries = await getCollection("products");
const productEntries = await getCollection("products", ({ id }) => {
return id.startsWith("en/");
});
return productEntries.map((product) => ({
params: { slug: product.slug },
props: { product },

View file

@ -14,7 +14,9 @@ import { SITE } from "@data/constants";
// Fetching all the product related content and sorting it by main.id
const product: CollectionEntry<"products">[] = (
await getCollection("products")
await getCollection("products", ({ id }) => {
return id.startsWith("en/");
})
).sort(
(a: CollectionEntry<"products">, b: CollectionEntry<"products">) =>
a.data.main.id - b.data.main.id,
@ -107,9 +109,9 @@ const pageTitle: string = `Products | ${SITE.title}`;
product.map((product, index) => {
const position = index % 4;
if (position === 0 || position === 3) {
return <CardSmall product={product} />;
return <CardSmall productEntry={product} />;
} else {
return <CardWide product={product} />;
return <CardWide productEntry={product} />;
}
})
}