Pwa Documentation - V4
Pwa Documentation - V4
→ Credentials
PC :
Username : pcq75
Password : tatva123
Database :
Username : root
Password : tatva123
7. Open this .env file and change/add the MAGENTO_BACKEND_URL to your local magento instance.
8. Generate SSL certificate because PWA features requires an HTTPS Secure Domain. From the root
directory of PWA (/pwa-studio) run below command:
bash deployVeniaSampleData.sh
bin/magento setup:upgrade
bin/magento indexer:reindex
bin/magento cache:flush
12. Run server, Use any of the following commands from the project root directory to start the server:
Note : if you find 500 error than its permission issue for that run command : sudo chmod -R 777
/var/DirectoryName
Note : You might get the issues that product’s images are not loading on PWA.
To fix this issue, create a virtual host.
</Directory>
</VirtualHost>
Here we will create a theme name “etatvasoft” for that follow the below steps.
1. Copy venia-concept folder and create new theme folder. like example etatvasoft-concept
theme path etatvasoftpwa/pwa-studio/packages.
1. I consider that you have to create a Banner Module in Magento Admin. In the same module you
have to create a “Resolver”.
A resolver performs GraphQL request processing. In general, it is responsible for constructing a query,
fetching data and performing any calculations, then transforming the fetched and calculated data into
a GraphQL array format.
Ex : etatvasoftpwa/app/code/Etatvasoft/Banner/Model/Resolver/HomePageBanner.php
HomePageBanner.php
<?php
declare(strict_types=1);
namespace Etatvasoft\Banner\Model\Resolver;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
Ex : magentoroot/app/code/Etatvasoft/Banner/etc/di/schema.graphqls
schema.graphqls
type Query
{
homePageBanner: [BannerDetails] @resolver(class:
"Etatvasoft\\Banner\\Model\\Resolver\\HomePageBanner") @doc(description: "BannerDetails")
}
type BannerDetails
{
banner_id: String @doc(description: "ID of Banner")
banner_title: String @doc(description: "Title of Banner")
banner_image: String @doc(description: "Image url of Banner")
}
→ Here we have created schema for querying list of Banner images, title and media path of magneto.
4. Create “getHomePageBanner.graphql” file. GraphQL is a syntax that describes how to ask for data,
and is generally used to load data from a server to a client.
Ex : magentoroot/pwa-studio/packages/venia-ui/lib/queries/ getHomePageBanner.graphql
getHomePageBanner.graphql
{
homePageBanner {
banner_id
banner_title
banner_image
}
}
→ Here you can see the graphql result which display the list of banners. You run this by installing
graphql extension in your pc. Also set the end point as your “baseurl/graphql”.
cms.css
/* custom*/
:global(p) {
font-size: 15px;
line-height: 19px;
letter-spacing: normal;
}
:global(p:not(:last-child)) {
margin-bottom: 12px;;
}
/* Home Content Section Starts */
:global(.Home-Custom .richContent-root-2JD) {
padding: 30px 0;
}
/* Home Content Section Ends */
:global(.Home-Custom) {
position: relative;
}
:global(.banner-inner img) {
display: block;
height: 500px;
}
:global(.carousel .control-dots .dot) {
height: 10px;
width: 10px;
margin: 0 6px;
}
:global(.carousel .control-dots .dot:focus) {
outline: none;
}
:global(.thumbs-wrapper.axis-vertical) {
display: none;
}
:global(.h2-title) {
/* color: #053b7d; */
color: #111111;
text-align: center;
padding: 0;
font-size: 36px;
letter-spacing: 0.8px;
margin-bottom: 35px;
text-shadow: none;
font-weight: 700 !important;
text-transform: capitalize !important;
}
:global(.h2-title span) {
display: inline-block;
}
:global(button) {
outline: none;
}
:global(a:hover),
:global(a:focus) {
text-decoration: none;
}
→ Homepage will have the above slider if, you have followed all the above mentioned steps.
→ Adding Featured Product Section In Homepage
1. Let us consider that you have already developed a featured Product Module in magento backend.
And will use custom attribute “featured_product”. Now we will create a custom module “Featured
Product” and in that will override the magento default graphql and schema of products and will make
changes so that we can get the featured products in product list array.
2. We will create a Resolver which returns the custom attribute “featured_product” product values for
the products.
Ex. etatvasoftpwa/app/code/Etatvasoft/FeaturedProduct/Model/Resolver/FeaturedProduct.php
FeaturedProduct.php
<?php
declare(strict_types=1);
namespace Etatvasoft\FeaturedProduct\Model\Resolver\Product;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Catalog\Model\Product;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
schema.graphqls
input ProductFilterInput
{
featured_product: FilterTypeInput @doc(description: "Product is featured or not")
@resolver(class:
"Etatvasoft\\FeaturedProduct\\Model\\Resolver\\Product\\FeaturedProduct")
}
interface ProductInterface
{
featured_product: String @doc(description: "Product is featured or not")
@resolver(class:
"Etatvasoft\\FeaturedProduct\\Model\\Resolver\\Product\\FeaturedProduct")
}
4. Now its the time to move to react part. Create “getFeaturedProducts.graphql” file.
Ex : magentoroot/pwa-studio/packages/venia-ui/lib/queries/ getFeaturedProducts.graphql
getFeaturedProducts.graphql
query FeaturedProducts {
products (
filter: { featured_product: { like: "1" } }
pageSize: 100
currentPage: 1
sort: { name: ASC }
)
{
items{
name
id
url_key
price {
regularPrice{
amount {
currency
value
}
}
}
sku
featured_product
small_image {
url
}
}
total_count
}
}
→ Here you can see the graphql result which display list of Featured Products. Here we have added
Filter “featured_product” attribute which assures the the product to be rendered are featured one.
5. Now we will create featured product component in react. Create Index.js in which will export
FeaturedProducts.js.
Ex : magentoroot/pwa-studio/packages/venia-ui/lib/components/FeaturedProducts index.js
Index.js
export { default } from './FeaturedProducts';
Ex : magentoroot/pwa-studio/packages/venia-ui/lib/components/FeaturedProducts
FeaturedProducts.js
FeaturedProducts.js
import React from 'react';
import classify from '../../classify';
import { fullPageLoadingIndicator } from '../../components/LoadingIndicator';
import defaultClasses1 from './featuredProducts.css';
import FeaturedProductsTile from './FeaturedProductsTile';
import FeaturedProductsListQuery from '../../queries/getFeaturedProducts.graphql';
import { useQuery } from '@apollo/react-hooks';
import { mergeClasses } from '../../classify';
if (data.products.items.length < 1) {
product = (
<div className={classes.noResults}>
{data.products.items.length} products found..!!
</div>
);
}
else{
product= (
<div className="featured-section-div row">
{data.products.items.map((item,index) => (
<FeaturedProductsTile key={index} item={item} />
))}
</div>
);
}
return (
<div className="featured-section">
<div className="container">
{header}
{product}
</div>
</div>
);
};
export default classify(defaultClasses1)(FeaturedProducts);
Ex : magentoroot/pwa-studio/packages/venia-
ui/lib/components/FeaturedProducts/FeaturedProductsTile.js
FeaturedProductsTile.js
import React, { Component } from 'react';
import {string, shape } from 'prop-types';
import classify from '../../classify';
import { Link, resourceUrl } from '../../drivers';
import defaultClasses from './FeaturedProductsTile.css';
import "react-responsive-carousel/lib/styles/carousel.min.css";
get imagePath() {
render() {
console.log(this.props.item);
const { imagePath, props } = this;
const { classes, item } = props;
// interpolation doesn't work inside `url()` for legacy reasons
// so a custom property should wrap its value in `url()`
const imageUrl = imagePath ? `url(${imagePath})` : 'none';
const style = { '--venia-image': imageUrl };
8. Create featuredProducts.css
Ex : magentoroot/pwa-studio/packages/venia-
ui/lib/components/FeaturedProducts/featuredProducts.css
featuredProducts.css
.root {
padding: 1rem;
}
.header {
margin-bottom: 2rem;
text-align: center;
}
.title {
text-transform: uppercase;
}
.content {
display: grid;
grid-gap: 3rem 1rem;
grid-template-columns: repeat(auto-fit, 6rem);
justify-content: center;
}
.fetchError {
background-color: rgb(var(--venia-warning-light));
color: rgb(var(--venia-warning-dark));
}
.fetchingData {
color: rgb(--venia-grey);
}
.noResults {
composes: fetchingData;
}
:global(.hide_features) {
display: none !important;
}
/* starts here */
:global(.featured-section) {
display: block;
padding: 50px 0 40px;
background: #f4f4f4;
}
:global(.featured-section-div .featured-block) {
text-align: center;
padding: 15px;
margin-bottom: 15px;
transition: all .25s;
}
:global(img.featured-block-img) {
width: auto;
display: inline-block;
max-width: 100%;
}
:global(i.featured-block-image) {
display: block;
box-shadow: none;
margin-bottom: 15px;
}
:global(.featured-block i.featured-block-image img) {
max-height: 250px;
transition: all .25s;
}
:global(.featured-block:hover) {
background-color: #ffffff;
box-shadow: 0px 3px 15.75px -3.75px rgba(0,0,0,0.5);
}
:global(.item-price) {
display: block;
font-size: 14px;
letter-spacing: 0.2px;
line-height: 18px;
color: #00686c;
}
:global(.featured-block > span:not(:last-child)) {
margin-bottom: 8px;
}
:global(.featured-block:nth-last-child(1)),
:global(.featured-block:nth-last-child(2)),
:global(.featured-block:nth-last-child(3)),
:global(.featured-block:nth-last-child(4)) {
margin-bottom: 0;
}
:global(.item-name) {
display: block;
font-size: 18px;
color: #000;
letter-spacing: 0.2px;
line-height: 22px;
font-weight: 600;
transition: all .25s;
}
:global(.item-name:hover) {
color: #043b7d;
}
9. Create FeaturedProductsTile.css
Ex : magentoroot/pwa-studio/packages/venia-
ui/lib/components/FeaturedProducts/FeaturedProductsTile.css
FeaturedProductsTile.css
.root {
display: block;
line-height: 1rem;
text-align: center;
width: 6rem;
}
.imageWrapper {
background-image: var(--venia-image);
background-position: 50% 50%;
background-size: cover;
border-radius: 50%;
box-shadow: 0 0 0 1px rgb(var(--venia-border));
display: block;
height: 5rem;
margin: 0 auto 1rem auto;
width: 5rem;
}
.image {
height: 100%;
opacity: 0;
width: 100%;
}
.name {
display: block;
}
.hide_features {
display: none !important;
}
10. In cms.js we have to update out new component which is featured product section.
di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider">
<arguments>
<argument name="extendedConfigData" xsi:type="array">
<item name="pwa_logo" xsi:type="string">pwaconfiguration/general/pwa_logo</item>
<item name="pwa_title"
xsi:type="string">pwaconfiguration/general/pwa_website_title</item>
<item name="pwa_website_copyright"
xsi:type="string">pwaconfiguration/general/pwa_website_copyright</item>
<item name="pwa_website_email"
xsi:type="string">pwaconfiguration/general/pwa_website_email</item>
<item name="pwa_website_phone"
xsi:type="string">pwaconfiguration/general/pwa_website_phone</item>
<item name="pwa_website_address"
xsi:type="string">pwaconfiguration/general/pwa_website_address</item>
</argument>
</arguments>
</type>
</config>
→ As you can in the image how the configuration data is requested and the respected result is
displayed in graphql.
3. Now its the time to move to react part. Open the “getStoreConfigData.graphql” file and add the
configuration fields.
Ex : magentoroot/pwa-studio/packages/venia-ui/lib/queries/ getStoreConfigData.graphql
getStoreConfigData.graphql
query storeConfigData {
storeConfig {
id
copyright
pwa_logo
pwa_title
base_media_url
pwa_website_copyright
pwa_website_address
pwa_website_email
pwa_website_phone
}
}
4. Now will use this “pwa_logo” for the website. Go to the following path - etatvasoftpwa/pwa-
studio/packages/venia-ui/lib/components/Logo/logo.js
Replace the code with the below code.
import React from 'react';
import PropTypes from 'prop-types';
import { mergeClasses } from '../../classify';
import Image from '../Image';
import logo from './logo.svg';
import { useQuery } from '@apollo/react-hooks';
import storeConfigDataQuery from '../../queries/getStoreConfigData.graphql';
1. Let us assume that we have already created a block “Home First Block” with identifier - “home-first-
block” in Magento Admin with below contents.
“<div class="container">
<h2 class="h2-title">Welcome to Progressive Web Apps</h2>
<p>A Progressive Web App (PWA) is a web app that uses modern web capabilities to deliver an app-
like experience to users. These apps meet certain requirements (see below), are deployed to servers,
accessible through URLs, and indexed by search engines. This can work in conjunction with
Cordova to provide a multiple deploy targets for all your users. You can deploy your app as a PWA as
well as Native app and take advantage of both channels.</p>
<p>Ionic allows you to ship your app to not only the app store, but also deploy to the mobile web as a
PWA. A Progressive Web App (PWA) is a web app that uses modern web capabilities to deliver
an app-like experience to users. These apps meet certain requirements (see below), are deployed to
servers, accessible through URLs, and indexed by search engines. This can work in conjunction
with Cordova to provide a multiple deploy targets for all your users. You can deploy your app as a PWA
as well as Native app and take advantage of both channels.</p>
</div>”
2. Now we can get this block content in our site Homepage. For that we have to place
“<CmsBlock identifiers={2} />” in “Home-Custom“ section inside cms.js file. Here identifier
is the id of the block which we want to access from magneto admin.
Add “import CmsBlock from '../../components/CmsBlock';” in the file in import section.
1. Now will add categories list to the Homepage. We have CategoryList component in pwa. Go to the
following path - etatvasoftpwa/pwa-studio/packages/venia-
ui/lib/components/CategoryList/categoryList.js. Replace the code with the below code.
categoryList.js
import React from 'react';
import { string, number, shape } from 'prop-types';
import { mergeClasses } from '../../classify';
import { fullPageLoadingIndicator } from '../LoadingIndicator';
import defaultClasses from './categoryList.css';
import CategoryTile from './categoryTile';
import categoryListQuery from '../../queries/getCategoryList.graphql';
import { useCategoryList } from '@magento/peregrine/lib/talons/CategoryList/useCategoryLi
st';
// map Magento 2.3.1 schema changes to Venia 2.0.0 proptype shape to maintain backwards
compatibility
const mapCategory = categoryItem => {
const { items } = categoryItem.productImagePreview;
return {
...categoryItem,
productImagePreview: {
items: items.map(item => {
const { small_image } = item;
return {
...item,
small_image:
typeof small_image === 'object'
? small_image.url
: small_image
};
})
}
};
};
const CategoryList = props => {
const { id } = props;
const talonProps = useCategoryList({
query: categoryListQuery,
id
});
const { childCategories, error, loading } = talonProps;
const classes = mergeClasses(defaultClasses, props.classes);
const header =(
<h2 className='h2-title'>
<span>Our Categories</span>
</h2>
);
let child;
if (error) {
child = (
<div className={classes.fetchError}>
Data Fetch Error: <pre>{error.message}</pre>
</div>
);
}
if (loading || !childCategories) {
child = fullPageLoadingIndicator;
} else if (childCategories.length === 0) {
child = (
<div className={classes.noResults}>No child categories found.</div>
);
} else {
child = (
<div className="category-section-div row">
{childCategories.map(item => (
<CategoryTile item={mapCategory(item)} key={item.url_key} />
))}
</div>
);
}
return (
<div className="category-section">
<div className="container">
{header}
{child}
</div>
</div>
);
};
CategoryList.propTypes = {
id: number,
title: string,
classes: shape({
root: string,
header: string,
content: string
})
};
export default CategoryList;
2. Go to the following path - etatvasoftpwa/pwa-studio/packages/venia-
ui/lib/components/CategoryList/categoryTile.js. Replace the code with the below code.
categoryTile.js
import React, { Component } from 'react';
import { arrayOf, string, shape } from 'prop-types';
import classify from '../../classify';
import { Link, resourceUrl } from '@magento/venia-drivers';
import defaultClasses from './categoryTile.css';
:global(.category-section) {
display: block;
padding: 50px 0 40px;
}
:global(.category-section-div .category-block) {
padding: 0 15px;
margin-bottom: 35px;
}
:global(.category-block > i) {
width: auto;
height: auto;
overflow: hidden;
max-height: 150px;
}
:global(.category-block > i img) {
max-width: 100%;
height: 100%;
width: 100%;
opacity: 1;
object-fit: cover;
transition: all .30s ease;
}
:global(.category-block:hover > i img) {
transform: scale(1.06);
transition: all .25s;
}
:global(.category-name) {
display: block;
font-size: 18px;
letter-spacing: 0.2px;
line-height: 22px;
font-weight: 600;
transition: all .25s;
text-align: center;
}
:global(.category-name:hover) {
color: #043b7d;
}
:global(.category-block:nth-last-child(1)),
:global(.category-block:nth-last-child(2)),
:global(.category-block:nth-last-child(3)),
:global(.category-block:nth-last-child(4)) {
margin-bottom: 0;
}
@media (max-width: 991px) {
:global(.category-block) {
flex:0 0 33%; max-width: 33%;
}
:global(.category-block > i) {
max-height: 120px;
}
}
@media (max-width: 767px) {
:global(.category-block) {
flex:0 0 50%; max-width: 50%;
}
:global(.category-section) {
padding: 35px 0;
}
:global(.category-section-div .category-block) {
margin-bottom: 25px;
}
:global(.category-section-div .category-block:last-child) {
margin-bottom: 0;
}
}
@media (max-width: 480px) {
:global(.category-section-div .category-block) {
flex:0 0 100%; max-width: 100%;
}
:global(.category-block > i) {
max-height: 153px;
}
}
4. Go to the following path - etatvasoftpwa/pwa-studio/packages/venia-
ui/lib/components/CategoryList/ categoryTile.css. Add the following css to file categoryTile.css.
categoryTile.css
.root {
display: block;
line-height: 1rem;
text-align: center;
width: 6rem;
}
.imageContainer {
margin: 0 0.5rem 1rem 0.5rem;
}
.image {
border-radius: 0;
box-shadow: 0 0 0 1px rgb(var(--venia-border));
display: block;
height: 5rem;
object-fit: cover;
}
.imageWrapper {
display: block;
margin-bottom: 10px;
}
.category-name {
text-align: center;
}
.image_empty {
composes: image;
}
.name {
display: block;
}
@media (max-width: 480px) {
:global(.category-name) { font-size: 15px; line-height: 19px; }
}
5. Now we can get this category list in our site Homepage. For that we have to place
“<CategoryList id={2} />” in “Home-Custom“ section inside cms.js file. Here id is category id
that the root category whose subcategory we want to access from magneto admin.
Add “importmport CategoryList from '../../components/CategoryList'” in the file in import
section.
→ Get Footer details From Magento admin and set it in frontend footer section.
1. Now will create footer for our site.We already have Footer component in pwa. Go to the following
path - etatvasoftpwa/pwa-studio/packages/venia-ui/lib/components/Footer/footer.js. Replace the
code with the below code.
footer.js
import React,{useEffect} from 'react';
import { shape, string } from 'prop-types';
import { useQuery } from '@apollo/react-hooks';
import { mergeClasses } from '../../classify';
import defaultClasses from './footer.css';
import GET_STORE_CONFIG_DATA from '../../queries/getStoreConfigData.graphql';
import FooterCategories from './footerCategories.js';
import { fullPageLoadingIndicator } from '../LoadingIndicator';
if (error) {
return <div className={classes.fetchError}>
Data Fetch Error: <pre>{error.message}</pre>
</div>;
}
if (loading) {
return fullPageLoadingIndicator;
}
let copyright = null;
let address = null;
let phone = null;
let email = null;
if (data && data.storeConfig) {
copyright = data.storeConfig.pwa_website_copyright;
address = 'Address : '+data.storeConfig.pwa_website_address;
phone = 'Phone No. : '+data.storeConfig.pwa_website_phone;
email = 'Email : '+data.storeConfig.pwa_website_email;
}
return (
<footer className="footer">
<div className="footer-top">
<div className="container">
<div className="row">
<div className="col-md-3">
<h2 className={classes.tileTitle}>
<span>Quick Links</span>
</h2>
<p className={classes.tileBody}>
<span>
<a href="/">Home</a>
</span>
</p>
<p className={classes.tileBody}>
<span>
<a href="about">About Us</a>
</span>
</p>
<p className={classes.tileBody}>
<span>
<a href="">Terms & Conditions</a>
</span>
</p>
<p className={classes.tileBody}>
<span>
<a href="">Privacy Policy</a>
</span>
</p>
</div>
<div className="col-md-3">
<h2 className={classes.tileTitle}>
<span>Categories</span>
</h2>
<FooterCategories />
</div>
<div className="col-md-6">
<h2 className={classes.tileTitle}>
<span>Contact Us</span>
</h2>
<p className={classes.tileBody}>
<span>
{address}
</span>
</p>
<p className={classes.tileBody}>
<span>
<a href="tel:{phone}">{phone}</a>
</span>
</p>
<p className={classes.tileBody}>
<span>
<a href="mailto:{email}">{email}</a>
</span>
</p>
</div>
</div>
</div>
</div>
<div className="footer-copyright">
<div className="container">
<p className={classes.copyright}>{copyright}</p>
</div>
</div>
</footer>
);
};
Footer.propTypes = {
classes: shape({
copyright: string,
root: string,
tile: string,
tileBody: string,
tileTitle: string
})
};
2. The second section is the main categories list section here we have i mported
“FooterCategories” from the file footerCategories.js. Go to the following path -
etatvasoftpwa/pwa-studio/packages/venia-ui/lib/components/Footer and create the
footerCategories.js file with the below below.
footerCategories.js
import React, { Component } from 'react';
import { useQuery } from '@apollo/react-hooks';
import { fullPageLoadingIndicator } from '../LoadingIndicator';
import categoryListQuery from '../../queries/getCategoryList.graphql';
const categoryUrlSuffix = '.html';
3. The third section will be the contact detail section. Which we can fetch from magneto admin from
store configuration which are we have already mentioned above in “Custom Configurations” section.
→ Create static Page in Pwa and display data fetached from the Magento Block.
Here we will create About Us page in Pwa.
1. Now will create About Us component for our site. Go to the following path - etatvasoftpwa/pwa-
studio/packages/venia-ui/lib/RootComponents/ and create “About” folder. Inside that we will create
about.js file and index.js file as shown below.
about.js
import React, {Component} from 'react';
import './about.css';
import CmsBlock from '../../components/CmsBlock';
import { Title } from '../../components/Head';
render() {
const titleContent = 'About Us - Tatvasoft Pwa';
return (
<>
<Title>{titleContent}</Title>
<div className="about-us">
<CmsBlock identifiers={1} />
</div>
</>
);
}
}
index.js
export { default } from './about';