Add Saved Projects Functionality

How to implement the saved projects page for Shopify.

Create the Pixfizz Products API page

Add a new template under the theme code edit page. Name it page.pixfizz-product-api.liquid

Copy the below code to this page

{% layout none %}
{
{%- comment %}
  On sites with many non-pixfizz products, it makes sense from a performance perspective
  to use a dedicated collections for pixfizz products, to be able to iterate only through them
  instead of through all live products with `collections.all`.
{%- endcomment %}
{% paginate collections.all.products by 50 %}
  {% for product in collections.all.products %}
    "{{ product.id }}": {
      "handle": {{ product.handle | json }},
      "title": {{ product.title | json }},
      "integration_type": {{ product.metafields.pixfizz.integration_type | json }},
      "pixfizz_sku_map": {
        {%- for variant in product.variants -%}
          "{{ variant.id }}": {{ variant.metafields.pixfizz.product_sku | default: product.metafields.pixfizz.product_sku | json }}
        {% unless forloop.last %},{% endunless %}{%- endfor -%}
      },
      "pixfizz_addons_map": {
        {%- for variant in product.variants -%}
          "{{ variant.id }}": {
            "page_addon": {{ variant.metafields.pixfizz.page_addon_product.value.id | default: product.metafields.pixfizz.page_addon_product.value.id | json }},
            "option_addons": {
              {%- for addon in product.metafields.pixfizz.option_addon_products.value -%}
                {{ addon.metafields.pixfizz.option_type_code | json }}: {
                  {%- for addon_variant in addon.variants -%}
                    {{ addon_variant.metafields.pixfizz.option_value_code | json }}: {{ addon_variant.id | json }}
                  {% capture variant_addons %}
                  {%- for addon in variant.metafields.pixfizz.option_addon_products.value -%}
                    {%- for addon_variant in addon.variants -%}
                      {{ addon_variant.metafields.pixfizz.option_value_code | json }}: {{ addon_variant.id | json }}
                    {% unless forloop.last %},{% endunless %}{%- endfor -%}
                  {%- endfor -%}
                  {% endcapture %}
                  {% assign variant_addons_stripped = variant_addons | strip %}
                  {% unless forloop.last and variant_addons_stripped == blank %},{% endunless %}{%- endfor -%}
                  {{ variant_addons }}
                }{% unless forloop.last %},{% endunless %}
              {%- endfor -%}
            }
          }{% unless forloop.last %},{% endunless %}
        {%- endfor -%}
      }
    }{% unless forloop.last %},{% endunless %}
  {% endfor %}
{% endpaginate %}
}

Create a Snippet

Create a snippet named pixfizz-saved-projects.liquid snippet in Shopify. Add the following code. The snippet was created to work with the Dawn theme and might have to be adapted to look good on other themes!

<div style="margin-top:2em;">
  <div>
    <h2>Saved Projects</h2>

    <table role="table" class="order-history">
      <caption class="visually-hidden">
        Saved Projects
      </caption>
      <thead role="rowgroup">
        <tr role="row">
          <th scope="col" role="columnheader">Preview</th>
          <th scope="col" role="columnheader">Updated</th>
          <th scope="col" role="columnheader">Name</th>
          <th scope="col" role="columnheader">Product</th>
        </tr>
      </thead>
      <tbody role="rowgroup">
        <template id="project-row">
          <tr role="row" class="project-item">
            <td role="cell" class="project-preview">
              <img src="" alt="" style="height:80px; display:block; cursor:pointer;" />
            </td>
            <td role="cell" class="project-updated">
            </td>
            <td role="cell" class="project-name">
            </td>
            <td role="cell" class="project-product">
            </td>
          </tr>
        </template>
      </tbody>
    </table>

    <div id="projects-loading" style="margin:1em 0; text-align:center;">
      <progress />
    </div>

    <div id="projects-load-more" style="margin:1em 0; text-align:center;" hidden>
      <a href="#">
        Load more&hellip;
      </a>
    </div>
  </div>
</div>

<script>
  const PROJECTS_PER_PAGE = 20;

  Pixfizz.Shopify.product_data_loader.load();
  
  const loaderDiv = document.querySelector('#projects-loading');
  const loadMoreDiv = document.querySelector('#projects-load-more');
  const projectRowTemplate = document.querySelector('#project-row');

  const loadProjects = (page) => {
    loaderDiv.hidden = false;
    loadMoreDiv.hidden = true;

    Pixfizz.Shopify.getSavedProjects(books => {
      books.forEach(book => {
        const product_id = book.options.shopify_product_id;
        const variant_id = book.options.shopify_variant_id;
  
        const date_formatter = new Intl.DateTimeFormat("en-us", {
          year: "numeric",
          month: "long",
          day: "numeric"
        });
        const date_parts = date_formatter.formatToParts(new Date(book.updated_at));
        const month = date_parts.find(p => p.type=== 'month').value;
        const day = date_parts.find(p => p.type === 'day').value;
        const year = date_parts.find(p => p.type === 'year').value;
        
        const tpl = projectRowTemplate.content.querySelector('tr');
        tpl.setAttribute('data-project-id', book.id);
        tpl.setAttribute('data-product-id', product_id);
        tpl.setAttribute('data-variant-id', variant_id);
        tpl.querySelector('.project-updated').innerText = `${month} ${day}, ${year}`;
        tpl.querySelector('.project-name').innerText = book.name;
  
        const node = tpl.cloneNode(true);
        projectRowTemplate.parentElement.appendChild(node);

        Pixfizz.Shopify.product_data_loader.get(product_id).then(data => {
          node.querySelector('.project-product').innerText = data ? data.title : '';
        });
        Pixfizz.Shopify.replaceImageWithProjectPreview(node.querySelector('.project-preview img'), book.id);
        
        node.querySelector('.project-preview img').addEventListener('click', evt => {
          evt.preventDefault();
          const item = evt.target.closest('.project-item');
          Pixfizz.Shopify.launchSavedProject(
            item.getAttribute('data-project-id'),
            item.getAttribute('data-product-id'),
            item.getAttribute('data-variant-id')
          );
        });
      });

      loaderDiv.hidden = true;
      if (books.length >= PROJECTS_PER_PAGE) {
        loadMoreDiv.hidden = false;
      }
    }, {page: page});
  };

  loadProjects(1);

  loadMoreDiv.addEventListener('click', evt => {
    evt.preventDefault();
    loadProjects(1 + Math.floor(document.querySelectorAll('.project-item').length / PROJECTS_PER_PAGE));
  })
</script>

Add the snippet to the Shopify account page

Locate the Shopify template/section that renders the default account page. For Dawn it’s the main-account.liquid section. Plug the snippet into the most appropriate place (see below code showing an example of Dawn theme)

{{ 'customer.css' | asset_url | stylesheet_tag }}

{%- style -%}
  .section-{{ section.id }}-padding {
    padding-top: {{ section.settings.padding_top | times: 0.75 | round: 0 }}px;
    padding-bottom: {{ section.settings.padding_bottom | times: 0.75 | round: 0 }}px;
  }

  @media screen and (min-width: 750px) {
    .section-{{ section.id }}-padding {
      padding-top: {{ section.settings.padding_top }}px;
      padding-bottom: {{ section.settings.padding_bottom }}px;
    }
  }
{%- endstyle -%}

<div class="customer account section-{{ section.id }}-padding">
  <div>
    <h1 class="customer__title">{{ 'customer.account.title' | t }}</h1>
    <a href="{{ routes.account_logout_url }}">
      <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" fill="none" viewBox="0 0 18 19">
        <path fill-rule="evenodd" clip-rule="evenodd" d="M6 4.5a3 3 0 116 0 3 3 0 01-6 0zm3-4a4 4 0 100 8 4 4 0 000-8zm5.58 12.15c1.12.82 1.83 2.24 1.91 4.85H1.51c.08-2.6.79-4.03 1.9-4.85C4.66 11.75 6.5 11.5 9 11.5s4.35.26 5.58 1.15zM9 10.5c-2.5 0-4.65.24-6.17 1.35C1.27 12.98.5 14.93.5 18v.5h17V18c0-3.07-.77-5.02-2.33-6.15-1.52-1.1-3.67-1.35-6.17-1.35z" fill="currentColor">
      </svg>
      {{ 'customer.log_out' | t }}
    </a>
  </div>

  <div>
    <div>
      <h2>{{ 'customer.orders.title' | t }}</h2>

      {% paginate customer.orders by 20 %}
        {%- if customer.orders.size > 0 -%}
          <table role="table" class="order-history">
            <caption class="visually-hidden">
              {{ 'customer.orders.title' | t }}
            </caption>
            <thead role="rowgroup">
              <tr role="row">
                <th id="ColumnOrder" scope="col" role="columnheader">{{ 'customer.orders.order_number' | t }}</th>
                <th id="ColumnDate" scope="col" role="columnheader">{{ 'customer.orders.date' | t }}</th>
                <th id="ColumnPayment" scope="col" role="columnheader">{{ 'customer.orders.payment_status' | t }}</th>
                <th id="ColumnFulfillment" scope="col" role="columnheader">
                  {{ 'customer.orders.fulfillment_status' | t }}
                </th>
                <th id="ColumnTotal" scope="col" role="columnheader">{{ 'customer.orders.total' | t }}</th>
              </tr>
            </thead>
            <tbody role="rowgroup">
              {%- for order in customer.orders -%}
                <tr role="row">
                  <td
                    id="RowOrder"
                    role="cell"
                    headers="ColumnOrder"
                    data-label="{{ 'customer.orders.order_number' | t }}"
                  >
                    <a
                      href="{{ order.customer_url }}"
                      aria-label="{{ 'customer.orders.order_number_link' | t: number: order.name }}"
                    >
                      {{ order.name }}
                    </a>
                  </td>
                  <td headers="RowOrder ColumnDate" role="cell" data-label="{{ 'customer.orders.date' | t }}">
                    {{ order.created_at | time_tag: format: 'date' }}
                  </td>
                  <td
                    headers="RowOrder ColumnPayment"
                    role="cell"
                    data-label="{{ 'customer.orders.payment_status' | t }}"
                  >
                    {{ order.financial_status_label }}
                  </td>
                  <td
                    headers="RowOrder ColumnFulfillment"
                    role="cell"
                    data-label="{{ 'customer.orders.fulfillment_status' | t }}"
                  >
                    {{ order.fulfillment_status_label }}
                  </td>
                  <td headers="RowOrder ColumnTotal" role="cell" data-label="{{ 'customer.orders.total' | t }}">
                    {{ order.total_net_amount | money_with_currency }}
                  </td>
                </tr>
              {%- endfor -%}
            </tbody>
          </table>
        {%- else -%}
          <p>{{ 'customer.orders.none' | t }}</p>
        {%- endif -%}

        {%- if paginate.pages > 1 -%}
          {%- if paginate.parts.size > 0 -%}
            <nav class="pagination" role="navigation" aria-label="{{ 'general.pagination.label' | t }}">
              <ul role="list">
                {%- if paginate.previous -%}
                  <li>
                    <a href="{{ paginate.previous.url }}" aria-label="{{ 'general.pagination.previous' | t }}">
                      <svg aria-hidden="true" focusable="false" viewBox="0 0 10 6">
                        <path fill-rule="evenodd" clip-rule="evenodd" d="M9.354.646a.5.5 0 00-.708 0L5 4.293 1.354.646a.5.5 0 00-.708.708l4 4a.5.5 0 00.708 0l4-4a.5.5 0 000-.708z" fill="currentColor">
                      </svg>
                    </a>
                  </li>
                {%- endif -%}

                {%- for part in paginate.parts -%}
                  <li>
                    {%- if part.is_link -%}
                      <a href="{{ part.url }}" aria-label="{{ 'general.pagination.page' | t: number: part.title }}">
                        {{- part.title -}}
                      </a>
                    {%- else -%}
                      {%- if part.title == paginate.current_page -%}
                        <span aria-current="page" aria-label="{{ 'general.pagination.page' | t: number: part.title }}">
                          {{- part.title -}}
                        </span>
                      {%- else -%}
                        <span>{{ part.title }}</span>
                      {%- endif -%}
                    {%- endif -%}
                  </li>
                {%- endfor -%}

                {%- if paginate.next -%}
                  <li>
                    <a href="{{ paginate.next.url }}" aria-label="{{ 'general.pagination.next' | t }}">
                      <svg aria-hidden="true" focusable="false" viewBox="0 0 10 6">
                        <path fill-rule="evenodd" clip-rule="evenodd" d="M9.354.646a.5.5 0 00-.708 0L5 4.293 1.354.646a.5.5 0 00-.708.708l4 4a.5.5 0 00.708 0l4-4a.5.5 0 000-.708z" fill="currentColor">
                      </svg>
                    </a>
                  </li>
                {%- endif -%}
              </ul>
            </nav>
          {%- endif -%}
        {%- endif -%}
      {% endpaginate %}

      {% render 'pixfizz-saved-projects' %}
    </div>

    <div>
      <h2>{{ 'customer.account.details' | t }}</h2>

      {{ customer.default_address | format_address }}

      <a href="{{ routes.account_addresses_url }}">
        {{ 'customer.account.view_addresses' | t }} ({{ customer.addresses_count }})
      </a>
    </div>
  </div>
</div>

{% schema %}
{
  "name": "t:sections.main-account.name",
  "settings": [
    {
      "type": "header",
      "content": "t:sections.all.padding.section_padding_heading"
    },
    {
      "type": "range",
      "id": "padding_top",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "label": "t:sections.all.padding.padding_top",
      "default": 36
    },
    {
      "type": "range",
      "id": "padding_bottom",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "label": "t:sections.all.padding.padding_bottom",
      "default": 36
    }
  ]
}
{% endschema %}

Create a Shopify Page

Create a new Page in the Shopify CMS (under “Online Store → Pages”). Give it the title “Pixfizz Product API” and select the “pixfizz-proudct-api” theme template for it.

Enable the save button in Editor

Go to your Pixfizz account and select "Settings" - "Design Tool"

Go to editor configuration and set the “Account Page Name” setting to “../account”. This will make the save button in shopify mode show up and direct the user to Shopify’s default /account page.

NOTE: We recommend incorporating the help guide to advise customers on how they can save their projects

Last updated