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…
</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