Shop with different kinds of products

I used the shop sample from the docs as a starting point, but it assumes that you only sell 1 category of products, being T-Shirts. What if someone sells glasses and outerwear?

How can I make the panel display multiple sections with categorized products?

I have a blueprint for the shop page and a shop template, but thats no good, because then I have that additional page in the path in the panel. If I rename it to home it breaks.

I decided to make my site.yml with tabs, put the shop there and set 'home' => 'shop' in the config.

title: Site
unlisted: true
image: icon

tabs:
  shop:
    label: Shop
    sections:
      drafts:
        extends: sections/products
        headline: Unpublished Products
        status: draft

      listed:
        extends: sections/products
        headline: Published Products
        status: listed
        layout: cards

  impressum:
    label: Impressum
    icon: cog
    fields:
      impressum:
        label: Impressum
        type: textarea

My extended blueprint (sections/products.yml) )thing is just like in the shop sample from the docs:

type: pages
headline: Products
parent: site.find("shop")
template: product-page
info: "{{ page.productDescription }} | € {{ page.productPrice }} "
image:
  src: page.image
  ratio: 1/1
  back: pattern

my intuition would like to do is something like this, but that doesn’t work:

    sections:
      drafts:
        extends: sections/kleidung
        headline: Unpublished Products
        status: draft

      listed:
        extends: sections/kleidung
        headline: Published Kleidung
        status: listed
        layout: cards

    sections:
      drafts:
        extends: sections/brillen
        headline: Unpublished Brillen
        status: draft

      listed:
        extends: sections/brillen
        headline: Published Brillen
        status: listed
        layout: cards

Or anything other to get:

Kleidung

product “t-shirt” | product “Blue Shirt” | product “Warm Coat”

Brillen

product “brown glasses” | product “thin glasses”

…and so forth.

There is really many attempts to do that… at least in a general matter.

my attempt varies between how products are usually functioning (e.g. if the cart logic is very different per product type… in this case, i would’ve had:

Templates:

  • shop
    – category (basically just lists of children)
    ---- product-type1 template
    ---- product-type1 template
    ---- product-type1 template
    – category 2
    ---- product-type2 template
    ---- product-type2 template
    ---- product-type2 template

it would obviously also be possible to mix it between category and type…

so each product type has it’s own template and logic (the logic could be done via page->template()
e.g.

  • digital download
  • physical product

(for example it showing different infos, and after the order provide a digital download instead of delivery)

Usually to provide mixed collections of products, I would make another template e.g. “special deals”, where it has a “pages” field query, so i would select all pages which i would want to list in that additional frontend page.

As for the Panel…

all product-blueprints use the same fields, so for the basic information (e.g. price, tax, description) i am reusing field groups over and over again… and per-type specific info is added to each type…

e.g. Basic Info Blueprint:

type: group
fields:
  produktHeadline:
    label: Produkt-Daten
    type: headline
  price:
    label: Price
    type: number
    step: .01
    width: 1/2
    translate: false
    help: 
      de: Verkaufspreis (wird bei Gold automatisch neugerechnet)
  prevprice:
    label: Prev Price (UVP, before Sale)
    type: number
    step: .01
    width: 1/2
    translate: false
    help: 
      de: UVP 
  taxrate:
    label: Taxrate
    type: number
    step: .01
    width: 1/2
    translate: false
    help:
      de: Steuersatz
  stock:
    label: Stock
    type: number
    width: 1/2
    min: 1
    required: yes
    translate: false
    help:
      de: Maximale Menge, die in den Warenkorb genommen werden darf
  gallery:
    label: Gallery
    type: files
    query: page.images
  intro: 
    label: Intro
    type: textarea
  text: 
    label: Text
    type: textarea
    size: medium
    help:
      de: Produktbeschreibung / Text
  properties: fields/product-properties
  relatedproducts:
    label:
      en: Related Products
      de: Passende Produkte
    type: pages
    query: site.index.filterBy("template", "in", ["shop-product", "shop-product-digital"])

Usually I have a few levels down structure, also for URI Breadcrumb reasions…

e.g. Shop/Menswear/Shirts/Shirt-A)
template: Shop/Category/ProductsList/ProductType

Product Category

title: Shop Category 

tabs:
  content:
    icon: file
    label: Content
    columns:
      left:
        width: 2/3
        sections:
          content:
            type: fields
            fields:
              text:
                type: textarea
                size: large
          pages: 
            type: pages
            template: shop-products

Products List

title: Products

tabs:
  content:
    icon: file
    label: Content
    columns:
      left:
        width: 2/3
        sections:
          content:
            type: fields
            fields:
              text:
                type: textarea
                size: large
          pages: 
              type: pages
              template: 
                - shop-product
                - shop-product-digital

Product Feature (e.g. special offer) which could be placed anywhere (as the listing would just provide
uri e.g. shop/angebote

title: Products Featured

tabs:
  content:
    icon: file
    label: Content
    columns:
      left:
        width: 2/3
        sections:
          content:
            type: fields
            fields:
              text:
                type: textarea
                size: large
              pages: 
                label: Produkte
                type: pages
                query: site.index.filterBy("template", "in", ["shop-product", "shop-product-digital"])

There are multiple ways to structure such a shop, but if you want your categories displayed in the Panel, the easiest way would be to set up your categories as subpages of the shop

shop/
  - clothing/
     - product-1/
     - product-2/
  - glasses/
     - product-3/
     - product-4/
  - whatever/
     - product-4/

Screenshot 2023-04-07 at 04.08.03

Then create a basic products.yml section in /site/blueprints/sections (without parent)

type: pages
headline: Products
info: "{{ page.productDescription }} | € {{ page.productPrice }} "
image:
  src: page.image
  ratio: 1/1
  back: pattern

And your site.yml would then look something like this. We set a different parent for each section (and note that each section needs a unique name!)

title: Site

sections:
  clothing:
    extends: sections/products
    label: Clothing
    parent: site.find('shop/clothing')
  glasses:
    extends: sections/products
    label: Glasses
    parent: site.find('shop/glasses')

Of course, if you want different sections for drafts and published pages, you would have to give them other keys:

title: Site

sections:
  clothingPublished:
    extends: sections/products
    label: Clothing published
    parent: site.find('shop/clothing')
    status: published
  clothingDrafts:
    extends: sections/products
    label: Clothing drafts
    parent: site.find('shop/clothing')
    status: draft
  glasses:
    extends: sections/products
    label: Glasses
    parent: site.find('shop/glasses')

Thats it!

I actually thought that the section keys like “drafts”, “listed” etc. were Kirby keywords that can not be different than that… :woozy_face:

And I didn’t know that $subpage is a thing, hence the content of “shop” didn’t show up on the shop page, because I only tried what I knew, being $page and $site.

I am not quite there yet and will probably screw more stuff up and will come back here soon.

Finaly I have a working solution. Here is what it looks like.

Content

  • 0_Home
  • 1_kleidung
  • 2_brillen
  • 3_ kram
  • plus the others like “impressum” etc.

Blueprints/ Pages

  • home.yml
  • Kleidung.yml (overview page with all clothes/ Kleidung)
  • kleidung-page.yml (product page for clothes that need option with sizes for them)
  • yml same for glasses and stuff (aka Kram).

I kicked out sections and their extends because I don’t like the concept of those extends. They confuse me and I don’t see the benefit to have extra folders and files and their names just to save 5 lines of yml code.

I need different blueprints and templates for the products because e.g. in kleidung-page I need to bring in the snipcart buy button with the custom option for sizes that appear later in the cart. In brillen-page I don’t want that sizes field in the cart, but maybe different options. Same with Kram (aka stuff).

Templates

  • home.php
  • kleidung.php
  • kleidung-page.php
  • same for glasses and stuff (Kram).

Snippets/ cart

  • cart-init.php
  • cart-summary.php

Snippets/ products

  • kleidung-add-to-cart.php (with sizes option for cart)
  • brillen-add-to-cart (maybe with different option for cart)
  • kram-add-to-cart (maybe with different option for cart)

I haven’t set the homepage to site anymore, but on home.php I pull in the headlines and links to the overview pages of the products (Kleidung, Brillen, Kram). Plus I’m gonna make some cover fotos or some kind of stuff that comes in from the home.yml


The whole purpose of that shop is for me to try to be able make one and experiment with it and have something to show to customers that maybe would want me to make them one. I guess one calls it a prototype :wink:

Here is the blueprint for an overview/ category page e.g. for clothes:

title: kleidung
type: pages
files: false

sections:
  pages:
    label: Seiten
    status: all
    layout: cards
    create: kleidung-page
    info: "{{ page.productDescription }} | € {{ page.productPrice }} "
    # image:
    #   ratio: 1/1
    #   cover: true


Here is the blueprint for product page e.g. for clothes.

title: Vorlage für Kleidung
type: pages
# num: zero
#template: kleidung-page

status:
  draft: true
  listed: true

sections:
  productdata:
    type: fields
    fields:
      # productTotal:
      #   label: Summe der Produkte
      #   type: text
      productName:
        label: Name
        type: text
        required: true
      productID:
        label: Product ID
        type: text
        required: true
      productPrice:
        label: Preis
        type: number
        min: 0
        step: 0.01
        after: €
        required: true
      productDescription:
        label: Beschreibung
        type: text
        required: true
      productImage:
        label: Image
        type: files
        max: 1
        required: true
      customOptions:
        label: Die Optionen
        type: checkboxes
        options:
          - S
          - M
          - L
          - XL
          - XXL
      praise:
        label: Praises
        type: structure
        fields:
          icon:
            label: Image
            type: files
            max: 1
          praisetext:
            label: text
            type: text

Out commented “num: zero” is because I forgot what it does. Have to look it up. Out commented “template: kleidung-page” doesn’t do anything, but I am not so sure, cause its late;) Out commented “productTotal” don’t know what its use would be.


This is the template for a product e.g. clothes (“kleidung”):

<!-- Dies ist das Template "kleidung-page" -->
<?php snippet('header') ?>

<main>
  <h2><?= $page->productName() ?></h2>
  <div class="single-product-wrapper">
    <figure class="single-product-figure">
      <?= $page->ProductImage()->toFile()?>
      <div class="single-product-data product-id-box">
        <dt class="product-id-label"><?= $page->blueprint()->field('productID')['label'] ?>:&nbsp;</dt>
        <dd class="product-id"><?= $page->ProductID()?></dd>
      </div>
    </figure>
    <article class="product-details-container">
      <dl class="single-product-data-list">
        <div class="poduct-description single-product-data">
          <dd class="poduct-description-heading"><?= $page->ProductDescription()?></dd>
        </div>
        <div class="single-product-data">
          <!-- <dt class="label-product-price"><?= $page->blueprint()->field('productPrice')['label'] ?>:&nbsp;</dt>  -->
          <dd class="product-price">€&nbsp;<?= number_format ( $page->ProductPrice()->toFloat(), 2 , null, ',' ) ?></dd>
        </div>
        <div class="single-product-data">
          <dt class="label-product-sizes"><?= $page->customLabel() ?>&nbsp;</dt>
      </div>
   <?php if ($page->customOptions()->isNotEmpty()):?>
     <label for="sizes">Größe</label>
     <div>
       <select name="sizes" id="sizes" class="size-picker">
         <?php foreach ($page->customOptions()->split() as $size): ?>
         <option><?= $size ?></option>
         <?php endforeach ?>
       </select>
     </div>
    <?php endif ?>
      <?= snippet('products/kleidung-add-to-cart', ['class' => 'add-to-cart-btn']) ?>
    </dl>
    <?= snippet('cart/checkout-summary') ?>
    </article>
</div>

<section class="product-praise-section">
  <?php foreach ($page->praise()->toStructure() as $praises): ?>
    <dl class="praisebox">
      <dt><?= $praises->icon()->toFile()?></dt>
      <dd> <?= $praises->praisetext() ?></dd>
    </dl>
  <?php endforeach ?>
</section>
</main>

<?php snippet('footer') ?>

See the snippet there? This brings in a Snipcart buy button.


Here is the snippet with the snipcart buy button with those custom options fields:

<button class="snipcart-add-item <?= $class ?>"
  data-item-id="<?= $page->ProductID() ?>"
  data-item-price="<?= $page->ProductPrice() ?>"
  data-item-url="<?= Url::path($page->url(), true, false); ?>"
  data-item-description="<?= $page->ProductDescription() ?>"
  data-item-image="<?= Url::path($page->ProductImage()->toFile()->url(), true, false); ?>"
  data-item-name="<?= $page->ProductName() ?>"
  data-item-custom1-name="Gewählte Größe:"
  data-item-custom1-options="<?= implode(' | ', $page->customOptions()->split(',')) ?>"
> 
  In den Warenkorb
</button>


This is the template for an overview/ category page e.g. “kleidung.php”:

<?php snippet('header') ?>
	
<main>
<section class="shop-product-grid">
	<?php foreach ($page->children()->listed() as $product): ?>
		<div class="product-box shop-product-box">
			<h2 class="product-title shop-product-title"><?= $product->title() ?></h2>
			<a class="product-link" href="<?= $product->url() ?>">
			<figure class="product-figure shop-product-figure">
				<?= $product->image() ?>
				</figure>
				</a>
				<figcaption class="product-details shop-product-details">
					<p class="product-description"><?= $product->productDescription() ?></p>
					<p class="product-description product-price"> €&nbsp;<?= number_format ($product->productPrice()->toFloat(), 2 , null, ',' ) ?></p>
				</figcaption>
		</div>
	<?php endforeach ?>
</section>
</main>

<?php snippet('footer') ?>

Oh, and this would be the home.php. The part that comes in from home.yml is not finished yet, I maybe will go for blocks and other stuff. I probably could work with a snippet here, but I don’t see the benefit for this use case, because I would only use that stuff that 1 time.

I put those comments into the html to reduce confusion when I stare at my code.

<!-- Dies ist das Template für Homepage-->

<?php snippet('header') ?>
<main>
	<section>
		<div>
			<?=$page->homeText()->kt()?>
		</div>
	</section>
	<section class="shop-product-grid">
		<?php foreach ($site->find('kleidung', 'brillen', 'kram')  as $subpage): ?>
			<div class="product-box shop-product-box">
				<a class="product-link" href="<?= $subpage->url() ?>">
				<h2 class="product-title shop-product-title"><?= $subpage->title() ?></h2>
				</a>  
			<figure class="single-product-figuree">
					<?= $subpage->image()?>
			</figure>
				<a class="product-link" href="<?= $subpage->url() ?>">Zur Abteilung&raquo;</a>
			</div>
		<?php endforeach ?>
	</section>
</main>
<?php snippet('footer') ?>


O course I use the shop sample in the docs for most of it, trying not to screw it up too bad. Also there is an awesome shop demo at Try | Kirby CMS which is for download on GitHub. That thing is brillant and very beautiful designed but was too advanced for me, so I could just silently admire it. :wink:

You can safely remove the lines type and files, because a page blueprint doesn’t have a type or files property.

The num property defined the numbering scheme (default number, date, alphabetical), Page blueprint | Kirby CMS

Template prop doesn’t exist at this level.

Thank you!