ModelPageSection

apps/www/src/sections/ModelPageSection.astro · 172 lines · group Models (product catalog)

ModelPageSection — a whole product page in one tag: the picture and the title with a description, then a certification tab with paragraphs and certificate scans, a technical-specification block, an FAQ list, and the sub-blocks placed under the description. Use for: the page of one model of a certified device — a control unit, an instrument — where the certificate and the spec sheet are the content. An author block kept from one of the fleet's sites, kept verbatim: it is compact and nothing else fits inside it. Not for: a general product page (compose <HeroSplit>, <FeatureList>, <Downloads>, <Faq> instead). Children: the sub-block tags, in any order, rendered under the description in the right column: <AttachmentsSection> (files), <ErrorCodesSection> (a link line), <HighlightsSection> (icon cards), <DistributorsSection> (contact cards). Plain markdown is rendered there too. Pictures: `modelImgName` is the product shot (cropped to fill a rounded box); `certificateImgNames` and `techSpecImgNames` are scans shown in a two-column grid.

In the fleet

Used on 1 site built with this engine.

Samples 1

sample
product-01.jpg

Station N3 — for several fields

The station that carries leaf wetness, wind and an expansion port, and still runs a full season on one battery. Mounts on a standard post with a single bolt.

Attachments

Communication protocol

Error codes

For the full list of error codes and what they mean, click here

Highlights

Cloud or your own server

The same software, either in our cloud or inside your network.

A week of buffer

The gateway keeps reporting when the uplink is away.

Field-swappable

Sensors change by hand, with no tools and no service visit.

Certification

Certificate of conformity

Every station carries a label with the certificate that applies to it. An example is shown here; the full list is published by the certification body.
For the full list of certificates click here
document-01.pngdocument-02.png
The technical specification lists the sensors, the ranges and the accuracy of each channel, together with the radio bands the station is approved for.
document-03.png
MDX of this sample
import BoltIcon from 'astro-heroicons/outline/Bolt.astro';
import SignalIcon from 'astro-heroicons/outline/Signal.astro';
import CloudIcon from 'astro-heroicons/outline/Cloud.astro';

<ModelPageSection
  modelTitle="Station N3 — for several fields"
  modelDescription="The station that carries leaf wetness, wind and an expansion port, and still runs a full season on one battery. Mounts on a standard post with a single bolt."
  modelImgName="product-01.jpg"
  certificatesText={['For the full list of certificates click', 'here']}
  certificatesUrl="https://example.com/"
  licenseSectionTitle="Certification"
  licenseTitles={['Certificate of conformity']}
  licenseContent={[
    'Every station carries a label with the certificate that applies to it. An example is shown here; the full list is published by the certification body.',
  ]}
  certificateImgNames={["document-01.png", "document-02.png"]}
  techSpecContent={[
    'The technical specification lists the sensors, the ranges and the accuracy of each channel, together with the radio bands the station is approved for.',
  ]}
  techSpecImgNames={["document-03.png"]}
>
  <AttachmentsSection title="Attachments">
    <AttachmentItem fileName="Certificate" file="/downloads/certificate.pdf" downloadText="Download" />
    <AttachmentItem fileName="Technical specification" file="/downloads/spec.pdf" downloadText="Download" />
  </AttachmentsSection>
  <AttachmentsSection title="Communication protocol">
    <AttachmentItem fileName="Radio protocol" file="/downloads/protocol.pdf" downloadText="Download" />
  </AttachmentsSection>

  <ErrorCodesSection
    title="Error codes"
    clickText={['For the full list of error codes and what they mean, click ', 'here']}
    linkPath="/api/#error-codes"
  />

  <HighlightsSection title="Highlights">
    <HighlightItem name="Cloud or your own server" description="The same software, either in our cloud or inside your network." IconImg={CloudIcon} />
    <HighlightItem name="A week of buffer" description="The gateway keeps reporting when the uplink is away." IconImg={SignalIcon} />
    <HighlightItem name="Field-swappable" description="Sensors change by hand, with no tools and no service visit." IconImg={BoltIcon} />
  </HighlightsSection>

  <DistributorsSection title="Distributors">
    <DistributorItem title="Almara Agri" email="hello@almara.example" phone="+46 8 123 45 67" imageName="logo-02.png" />
    <DistributorItem title="Kestrel Supply" email="sales@kestrel.example" phone="+47 22 12 34 56" imageName="logo-03.png" />
  </DistributorsSection>
</ModelPageSection>

Props

NameTypeRequiredDefaultDescription
modelTitlestringyesthe model name, the page <h1>
modelDescriptionstringyesthe paragraph under the name
modelImgNamestringyesfile name in images/, the product shot
faqsFAQ[][]questions and answers rendered as a plain list at the bottom
licenseSectionTitlestringyesthe label of the certification tab
licenseTitlesstring[]yes[][heading of the certificate block, heading of the technical-specification block]
licenseContentstring[]yes[]paragraphs of the certificate block
certificatesTextstring[]yes[text before the link, text of the link] of the "full list of certificates" line
certificatesUrlstringyestarget of that link
certificateImgNamesstring[]yes[]file names in images/, certificate scans
techSpecContentstring[]yes[]paragraphs of the technical-specification block
techSpecImgNamesstring[]yes[]file names in images/, specification scans

Source apps/www/src/sections/ModelPageSection.astro @ gitt.one

---
/**
 * ModelPageSection — a whole product page in one tag: the picture and the
 *   title with a description, then a certification tab with paragraphs and
 *   certificate scans, a technical-specification block, an FAQ list, and the
 *   sub-blocks placed under the description.
 *
 * Use for: the page of one model of a certified device — a control unit, an
 *   instrument — where the certificate and the spec sheet are the content. An
 *   author block kept from one of the fleet's sites, kept verbatim: it is
 *   compact and nothing else fits inside it.
 * Not for: a general product page (compose <HeroSplit>, <FeatureList>,
 *   <Downloads>, <Faq> instead).
 * Children: the sub-block tags, in any order, rendered under the description
 *   in the right column: <AttachmentsSection> (files), <ErrorCodesSection> (a
 *   link line), <HighlightsSection> (icon cards), <DistributorsSection>
 *   (contact cards). Plain markdown is rendered there too.
 * Pictures: `modelImgName` is the product shot (cropped to fill a rounded
 *   box); `certificateImgNames` and `techSpecImgNames` are scans shown in a
 *   two-column grid.
 */
import { Image } from 'astro:assets';
import { resolveImage } from '../lib/images';

interface FAQ {
  question: string;
  answer: string;
}
interface Props {
  /** the model name, the page <h1> */
  modelTitle: string;
  /** the paragraph under the name */
  modelDescription: string;
  /** file name in images/, the product shot */
  modelImgName: string;
  /** questions and answers rendered as a plain list at the bottom */
  faqs?: FAQ[];
  /** the label of the certification tab */
  licenseSectionTitle: string;
  /** [heading of the certificate block, heading of the technical-specification block] */
  licenseTitles: string[];
  /** paragraphs of the certificate block */
  licenseContent: string[];
  /** [text before the link, text of the link] of the "full list of certificates" line */
  certificatesText: string[];
  /** target of that link */
  certificatesUrl: string;
  /** file names in images/, certificate scans */
  certificateImgNames: string[];
  /** paragraphs of the technical-specification block */
  techSpecContent: string[];
  /** file names in images/, specification scans */
  techSpecImgNames: string[];
}
const {
  modelTitle,
  modelDescription,
  modelImgName,
  faqs = [],
  licenseSectionTitle,
  licenseTitles = [],
  licenseContent = [],
  certificatesText,
  certificatesUrl,
  certificateImgNames = [],
  techSpecContent = [],
  techSpecImgNames = [],
} = Astro.props;

const modelImage = await resolveImage(modelImgName);
const certificateImages = await Promise.all(
  certificateImgNames.map(async (imgName) => ({ img: await resolveImage(imgName), imgName })),
);
const techSpecImages = await Promise.all(
  techSpecImgNames.map(async (imgName) => ({ img: await resolveImage(imgName), imgName })),
);
---

<div class="mx-auto px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl lg:px-8">
  <div class="lg:grid lg:grid-cols-7 lg:grid-rows-1 lg:gap-x-8 lg:gap-y-10 xl:gap-x-16">
    <div class="lg:col-span-4 lg:row-end-1">
      <div class="overflow-hidden rounded-lg bg-gray-100">
        <Image
          src={modelImage}
          alt={modelImgName}
          widths={[400, 840]}
          class="object-cover object-center"
        />
      </div>
    </div>

    <div class="mx-auto mt-14 max-w-2xl sm:mt-16 lg:col-span-3 lg:row-span-2 lg:row-end-2 lg:mt-0 lg:max-w-none">
      <div class="flex flex-col-reverse">
        <h1 class="text-2xl font-extrabold tracking-tight text-gray-900 sm:text-3xl">
          {modelTitle}
        </h1>
      </div>

      <p class="mt-6 text-gray-500">{modelDescription}</p>

      <slot />
    </div>

    <div class="mx-auto mt-8 w-full max-w-2xl lg:col-span-4 lg:mt-0 lg:max-w-none">
      <div class="border-b border-gray-200">
        <h2 class="border-b-2 border-primary-600 py-6 text-sm font-medium whitespace-nowrap text-primary-600 inline-block -mb-px">
          {licenseSectionTitle}
        </h2>
      </div>

      <div class="mt-4 grid gap-4 text-sm text-gray-500">
        {licenseTitles[0] && (
          <h3 class="text-sm font-medium text-gray-900">{licenseTitles[0]}</h3>
        )}
        <div>
          {licenseContent.map((paragraph, index) => (
            <div class={index > 0 ? 'mt-4' : ''}>{paragraph}</div>
          ))}
          <div class="mt-4">
            {certificatesText[0]}{' '}
            <a
              href={certificatesUrl}
              class="font-bold text-primary-600 underline hover:text-primary-500"
            >
              {certificatesText[1]}
            </a>
          </div>
        </div>
        <div class="grid sm:grid-cols-2">
          {certificateImages.map(({ img, imgName }) => (
            <Image src={img} alt={imgName} widths={[400, 840]} class="h-auto w-full" />
          ))}
        </div>
      </div>

      {
        (techSpecContent.length > 0 || techSpecImages.length > 0) && (
          <div class="mt-8 grid gap-4 text-sm text-gray-500 sm:grid-cols-2">
            {licenseTitles[1] && (
              <h3 class="text-sm font-medium text-gray-900 sm:col-span-2">{licenseTitles[1]}</h3>
            )}
            {techSpecContent.map((paragraph) => (
              <div>{paragraph}</div>
            ))}
            <div class="mx-auto grid w-60 gap-4 sm:w-full sm:grid-cols-2">
              {techSpecImages.map(({ img, imgName }) => (
                <Image src={img} alt={imgName} widths={[400, 840]} class="h-auto w-full" />
              ))}
            </div>
          </div>
        )
      }

      {
        faqs.length > 0 && (
          <div class="mt-8 text-sm text-gray-500">
            <h3 class="font-medium text-gray-900">FAQ</h3>
            {faqs.map((faq) => (
              <dl>
                <dt class="mt-8 font-medium text-gray-900">{faq.question}</dt>
                <dd class="mt-2 max-w-none text-gray-500">
                  <p>{faq.answer}</p>
                </dd>
              </dl>
            ))}
          </div>
        )
      }
    </div>
  </div>
</div>

Source links point to engine-astro@7b686388. Samples are demo content: the components are the real ones, the copy and the pictures are made up.