Home
Softono
a

alextechnoir

Professional software vendor delivering innovative solutions on the Softono platform. Specialized in both open-source and proprietary software development.

Total Products
1

Software by alextechnoir

Next.js-Strapi-Ecommerce-store
Open Source

Next.js-Strapi-Ecommerce-store

# Next.js Strapi E-Commerce Store Built with Next.js 11.1.4 and Strapi 4.3.4. Code deployed on Vercel, HCMS uses PostgreSQL and deployed on Heroku, images stored on Cloudinary --- WARNING! Strapi is deployed on Heroku. Due to Heroku's financial decision to shut down free plans, this project will most likely crash starting with 28.11.22. But all code examples in this repo are valid! You can still study it and learn how I've done some features! I tried some alternatives and with couple of failures, unfortunately, right now I don't have time to explore for more. If you also switching from Heroku, be careful with Railway - if you create an account with one e-mail and then link a GitHub account with another (different) e-mail, they may ban you by mistake for "multiple accounts". Tech support's not answering on ban appeals, at least in my case --- ## [Dependencies](https://github.com/AlexTechNoir/Next.js-e-commerce-online-store/blob/master/package.json#L10), 3rd party [links](https://github.com/AlexTechNoir/Next.js-e-commerce-online-store/blob/master/src/pages/_document.js#L34) and [scripts](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L230) ## Features - guest shopping cart - guest checkout - no authentication (auth buttons just for show) - checkout form data is saved between page reloads - product search - slider with magnifying glass - currency change (3 available) - discounts - product reviews - Google Analytics - simple cookie banner - pagination - available item amount checks ("out-of-stock" and "less than selected") - here's the [demo video](https://vimeo.com/742672808) - PayPal checkout button (fake payments) - email with order info is sent after fake transaction (it looks like [this](https://ibb.co/TtSv5gR)) ## Code examples: - React Context - example: [1](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/context/cartContext.js), [2](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L216) - styled-components - examples: [global style](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/Layout.js#L118), [ususal style](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/Layout.js#L119), [keyframes](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/Layout.js#L142) - [React Draft WYSIWYG](https://github.com/jpuri/react-draft-wysiwyg) ([example](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L167)) with [draftjs-to-html](https://github.com/jpuri/draftjs-to-html) ([example](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L94)) and [html-to-draftjs](https://github.com/jpuri/html-to-draftjs) ([example](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L81)) - [Formik](https://github.com/formik/formik) - useFormik hook example: [1](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L38), [2](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L64); useFormik hook + Rich text (React Draft WYSIWYG) example: [1](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L20), [2](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L174), [3](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L77-L99), [4](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L127) (credits goes to [this codesanbox example](https://codesandbox.io/s/v5rfp?file=/src/TextEditor.tsx) πŸ™) - [yup](https://github.com/jquense/yup) - [example](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L70) - [React Responsive Carousel](https://github.com/leandrowd/react-responsive-carousel) - [example](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/index/FeaturedCarousel.js#L6), [with image magnifier](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/ProductSlider.js#L6) - [react-image-magnifiers](https://github.com/AdamRisberg/react-image-magnifiers) - [example](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/ProductSlider.js#L16) - [react-paginate](https://github.com/AdeleD/react-paginate) - [example](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/products/%5Bcategory%5D/%5Bpage%5D.js#L108) - [SWR](https://github.com/vercel/swr) - [example](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/search/%5Bvalue%5D.js#L15) - [PayPal Checkout Button (react-paypal-js)](https://github.com/paypal/react-paypal-js) - example: [1](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L275), [2](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L27-L37), [3](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L44) - [isomorphic-dompurify](https://github.com/kkomelin/isomorphic-dompurify) - [example](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/postReview.js#L7-L9) - [nodemailer](https://github.com/nodemailer/nodemailer) - example: [1](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L170), [2](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L193) - Cookies banner - example: [1](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/layout/CookieBanner.js), [2](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/Layout.js#L104-L110) - Google Analytics - example: [1](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L230), [2](https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/Layout.js#L113) ## How did I do... <details> <summary>Purchasing process with guest cart and checkout</summary> <br /> <p>I divided it into 3 stages:</p> <details> <summary>1. Product page</summary> <br /> <ol> <li>When user navigates to page of product he wants to purchase, we: <ul> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/product-page/%5Bid%5D.js#L12">fetch</a> product data in getServerSideProps() based on product id, passed to dynamic route</li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/product-page/%5Bid%5D.js#L149">put</a> "available" value into variable</li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/product-page/%5Bid%5D.js#L199">pass</a> it to addToCart component</li> </ul> </li> <li>In addToCart component, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L69">render</a> "options" elements inside "select" element, based on "available" value</li> <li>When user chooses amount, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L35">set</a> this number to state</li> <li>Then, when user clicks on <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L107">"Add to cart" button</a>, we: <ul> <li>pass selected amount, along with product id, to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L110">addToCart function</a></li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L38">create</a> object out of them</li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L42-L50">put</a> object in localStorage</li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L52-L53">set</a> two states to show <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L122">amount</a> and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L93">Cancel button</a></li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L54">toggle</a> cart badge state (that lives in <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L20">_app.js</a>) to show number of items in header, near cart icon: <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/layout/header/CartButton.js#L28">trigger</a> assignItemsAmount() function to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/layout/header/CartButton.js#L39">render</a> badge</li> </ul> </li> <li>If user'll change their mind, and click on <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L93">Cancel button</a>, we trigger <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L57">cancelAdding function</a>, where we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L59">filter out</a> current product based on id, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L61">re-save</a> cart list in localStorage and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/AddToCart.js#L63-L64">toggle</a> all relevant states back</li> <li>Based on localStorage data, amount in cart for each item will appear in <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/ProductListItem.js#L96">ProductListItem component</a> and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/SearchResult.js#L67">SearchResult component</a></li> </ol> </details> <br /> <details> <summary>2. Cart page</summary> <br /> <ol> <li>Then, we have cart page to understand. Before we dive into cart, I need to explain render process and launching sequence of useEffects inside of it, depending on different circumstances. So: we have cart.js page, inside of which we have CartList.js component, inside of which we have mapped CartListItem.js components. The rendering process happens top-down, meaning: cart.js page -> CartList.js component -> CartListItem.js components, but the launching sequence of useEffects in all of those files is happening from down to top, like the event bubbling in JavaScript</li> <li>However, this is true only if we have all nested components already rendered (being already there). This will not be true, if those nested components are being conditionally rendered and are not initially there, but appear in process, during, for example, function launched in useEffect in parent page</li> <li>So, if user visits cart page for the first time: the state that toggles the render of nested components is initially "false", but turnes to "true" in function in useEffect of parent page/component. The whole sequence will be: cart.js page rendered -> useEffect of cart.js page is launched and triggered state that renders CartList.js child component -> CartList.js child component is rendered -> useEffect of CartList.js component is launched and triggered state that renders CartListItem.js child components -> CartListItem.js child components are rendered -> useEffects of CartListItem.js components are launched. If we present levels of nesting in ascending numbers, in this case useEffects sequence will be: 1 - 2 - 3</li> <li>If user visits cart page not in the first time and without reloading our website even once (meaning not resetting all the states in the app), then, that state, that allows to render nested component, initially will be "true". Then, the whole sequence will be: cart.js page rendered -> CartList.js child component is rendered -> CartListItem.js child components are rendered -> useEffects of CartListItem.js components are launched -> useEffect of CartList.js component is launched -> useEffect of cart.js page is launched. In nesting levels, presented in numbers, useEffects sequence will be: 3 - 2 - 1. I'm saying it so you kept this in mind to understand code better. I will reference this points below, when needed</li> <li>Now we can dive into cart. Whooosh! 🏊 When user navigates there (first time, see step #3), we launch <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/cart.js#L13">assignProductAmountInCart() function</a>, that lives in <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L28">Custom App</a>. Since we don't have authentication in this project (thus cannot bind amount to user in CMS), we store selected amount of products in localStorage. In order to create one guest cart with all necessary values, we need to fetch products from CMS, based on ids in localStorage, and loop through this fetched array of products, adding "selectedAmount" key-value inside each of them, from localStorage array. Inside of assignProductAmountInCart() function, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L36">fetch</a> the API route, attaching ids in query</li> <li>In api/cart route, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/cart.js#L7">form</a> a string of ids for Strapi's GraphQL query, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/cart.js#L10">fetch</a> and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/cart.js#L64">send</a> data back to frontend</li> <li>Back in assignProductAmountInCart() function, we manipulate the data as we need (I decided to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L49">sort</a> in alphabetical order just because πŸ€·β€β™‚οΈ) and set <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L56">cart</a> and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L59">cart length</a> states</li> <li>The change of the latter state will <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/cart.js#L34">render</a> CartList component in cart.js page. The former will <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L148">map</a> cart items in CartList.js component</li> <li>In CartList.js component, useEffect launches initially and on "cartList" state change (in assignProductAmountInCart() function). Here we need to check if both localStorage cart list and fetched cart list are in sync, before (re-)rendering cart list. To do that we: 1.check their lengths to be the same, and at the same time 2.if all ids in localStorage are coincide with ids in potentially stale fetched cart list (first, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L127">map</a> boolean results of coincidences, then we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L136">check</a> if any of them are false). Based on these <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L138">two conditions</a>, we launch 2 following functions</li> <li>First, we estimate total price of all items in function, that <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L66">lives</a> in Custom App. To do that we need to multiply price by amount of each item, and get the sum of the results of all those multiplications. This project doesn't have authentication, so we can't bind user's selected amount to public products right away. "price" value exists in one cart, "selectedAmount" in another. So, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L75">create</a> one common cart list from those two, and in the process, if ids from both cards <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L77">are coincide</a>, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L79">put</a> "price" value in the newly created cart. Then, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L86">create</a> an array with final prices, and if there is only 1 item, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L90">set</a> its price to state or, if there are more than 1, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L95">set</a> the sum of prices</li> <li>For the higher chance of convertion we show both total prices with and without discount. So, we do the same thing to create total price with discount (and set as a separate state). We <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L26">check</a> if there are any discounts in cart, and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L99">create</a> the second total price with discount this time. The only difference in code is <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L105">the check</a> for the discount presence. Also, "areThereAnyDiscountsInCart" variable is passed through Context to CartList.js component to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L184">render prices</a> and to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L198">show the message</a> of how much money will be saved to make user feel more happy about himself πŸ˜ƒπŸ‘</li> <li>Second, we check the amount of items to trigger any errors. User may leave the cart and get back after a long time, the availability of products may change, that's why we need this check at the beginning of component's lifecycle on page load. We <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L58">launch</a> checkIfItemsAreAvailable() function in CartList.js (after estimateTotalPriceOfAllItems() in useEffect). Inside this function we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L63">pass</a> both synced cart lists into helper function - checkItemsAmount(), that returns 2 entities: <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L61">a boolean</a> - whether any of items out of stock, and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L62">an array of ids of items</a> - the selected amount of whose exceeded available amount in CMS (but they are not 0). For the first returned value we use ".some()" method to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L34">check</a> if at least one of items is out of stock. It will return boolean value for us to trigger errors. We do not need a list of out-of-stock items, a boolean is enough. But for the second returned value (selected amount exceeded available amount) we need array of ids of items, because in cart we need to highlight "select" element (e.g. make its border red) in each cart item individually, to show user, that they need to reselect amount. So in this case we need array, because user has an option not to delete item from cart, but to reselect value, in which case we toggle the state of 1 individual item. So, here we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L39">use</a> ".filter()" method on cart list from CMS, and for each of its items, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L40">run</a> a "for of" loop on cart list from localStorage. We <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L43">check</a> all conditions (if ids are coincide AND if available value > 0 AND if selected amount > available amount), if passed - the item is returned into shallow array copy created by ".filter()". And then, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L53">make</a> a new array of ids of returned items with ".map()" method</li> <li>Back in checkIfItemsAreAvailable() function we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L66">set</a> the second returned value (array) to state. This state is passed down to child component(s) (CartListItem.js) as a dependency for useEffect, that runs a function, changing border colour of select element indivilually, as mentioned above. However, if function triggers state that is a dependency of useEffect, that runs another function, we need to rememeber that this another function will run after the end of first function, that triggered that state. It'll wait for that first function to finish its execution, and then runs itself (here it's function, changing border colour). So, we set the second returned value to state, then toggle two other boolean states, if either <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L68">one of checks</a> is true. <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L217">The first state</a> shows/hides error, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L210">the second</a> disables/enables "Go to checkout" button</li> <li>Now, after checkIfItemsAreAvailable() function finished execution, as a side-effect of setting one of the states - in CartListItem.js child component(s) the useEffect <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L157">runs</a> toggleBorderColour() function. It does <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L138">a standard check</a> of the length of array of items' ids with exceeded amount. Then, if id of current item <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L140">coincides</a> with any of ids in there, it toggles border colour of select element in current item component</li> <li>Last function that launches at the start of page lifecycle (if page is loaded for the first time - see step #3) is "estimatePrice()" in cart item component(s) in <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L152">useEffect</a>. It also launches after "available" value changes, that depends on "cartList" value change as well (since the former is inside of the latter). Inside of it we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L49">look</a> for the id of current item, if found - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L54">get</a> the selected amount of that item. If amount is not exceeded available, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L62">select</a> the corresponding option in select element and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L63">set</a> total price of one item, along with border colour (that we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L275">pass</a> to item's stylesheet). If amount is <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L68">exceeded</a> - set option to "unselected" and total price to 0, along with border colour again</li> <li>We're done with initial useEffects' launches in cart, now let's see what user is able to do here. Inside of each cart item, user can edit the selected amount. When they do that, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L187">"editAmount()" function launches</a>. In it, we: <ul> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L87">get</a> the selected value</li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L89">create</a> new cart list with that edited amount and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L99">put</a> it into localStorage</li> <li>update <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L103">item price</a> and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L107">total price of all items</a> (see step #10)</li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L110">re-check</a> items availability to trigger toggleBorderColour() function again (see step #14) to toggle border colour (from red to lightgrey, in case when user edits "unselected" option)</li> </ul> </li> <li>User can delete item by pressing the corresponding button. We launch <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L231">deleteItem() function</a>, inside of which we: <ul> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L115">filter out</a> current item from localStorage</li> <li>if that was the last deleted item, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L119">remove</a> "cartList" value from localStorage, as well as "order" and "isFormSubmitted" - these last two are created during checkout process itself, we have yet to get there, don't bother with them now</li> <li>if that wasn't the last deleted item, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L126">put</a> filtered cart list in localStorage</li> <li>in any case, after delete operation, we have to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L127-L133">re-run/re-set</a> all the necessary functions/hooks again and those would be: assignProductAmountInCart() func (see step #5), setCartBadgeToggle() hook (see "Product page", step #4, substep #5) and estimateTotalPriceOfAllItems() func (see step #10)</li> </ul> </li> <li>User can clear the whole cart with one button click. We launch <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L171">clearCart() function</a>, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L77">inside</a> of which we do pretty much the same stuff as in step #17, substeps #2 and 4</li> <li>Now we can go to checkout page, by clicking "Proceed to checkout" button and launching <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L211">goToCheckout() function</a>. But before redirecting user there, we need to check if something is changed with items availability (what if user leaves website without closing tab or browser? Somebody may purchase some of those items before him, or browser may show cached data without refreshing the page, which user may not bother to do manually when he gets back. We have to do all checks on click as well). To do this we need data from cart, but I decided it would be too crazy to re-fetch the whole freaking cart with tons of key-values that we don't need 😡 Instead, we gonna fetch only ids and amounts from the custom-created API route (we're making this internal check more cheaper for traffic than it might turn out to be). So, right after clicking, we: <ul> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L88">disable</a> the button</li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L91">get</a> all item ids from localStorage cart</li> <li>attach them to query and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L94">hit</a> the API endpoint</li> <li>there, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/available.js#L9">form</a> the query string as Strapi wants it, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/available.js#L12">fetch</a> and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/available.js#L50">send</a> the response back (with only ids and available amounts)</li> <li>derived data we put into same <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L108">helper function</a> that we used in step #12 to check amounts by comparing cart lists (it'll accept cart list or data with only ids and amounts in the same parameter just fine πŸ‘Œ)</li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L111">set</a> state that may or may not toggle border colour</li> <li>if at least one check hasn't been passed - relaunch <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L115">assignProductAmountInCart() function</a> (see step #5) that will trigger all occurring errors</li> <li>if passed - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L117">redirect</a> to checkout page (we see that in this more frequent case expences are cheaper than if we'd fetch the whole cart)</li> </ul> </li> <li>If user visits cart page not for the first time without reloading our website, useEffects will launch in a reverse sequence (see step #4). It may lead to some errors (such as undefined values, because some functions run before the functions they depend on). That's why here we're checking <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/CartList.js#L138">if carts are in sync</a>, and when we estimate price for each item, we check <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/cart/cartList/CartListItem.js#L52">if current item exist or not</a></li> </ol> </details> <br /> <details> <summary>3. Checkout page</summary> <br /> <ol> <li>First thing we need to do on checkout page is to check if user is being a smart aleck and managed to visit this page without actually putting anything in his cart 😏 In the first useEffect we launch <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L188">redirect() function</a>, that <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L61">checks</a> if "itemsAmountInCart" was set and equals 0. If it is - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L62">redirect</a> back to cart, where user'll see "empty cart" message</li> <li>The checkout page has 3 child components: Form.js (form for customer's data), CartInfo.js (mini-version of cart) and PayPalCheckoutButton.js (payment button)</li> <li>Keep in mind that here we have multiple useEffects in one page, and functions in one useEffect will trigger state that is a dependency of other useEffects. The execution sequence will be: 1. the launches of all functions in all useEffects on page load, in order they were written (by "they" I mean all functions in each useEffect, and all useEffects in page), 2. the launches of functions in useEffects, that have a states in their dependencies, that were triggered by any of functions that were launched initially on page load. But further I will be explaining functions in the order that'll make more sense to understand code behaviour, okay? πŸ˜‰</li> <li>Unlike in cart page, here we don't edit cart, it's purely informational. So, some checking functions that run here are similar to functions in cart, but more lightweight</li> <li>In the second useEffect we launch same old <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L195">assignProductAmountInCart() function</a>, that will set "cartList" state, that will trigger <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L204">third useEffect</a>, where we launch 2 functions: 1. familiar to us estimateTotalPriceOfAllItems() function, that sets total prices, and 2. <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L207">setCartListInCheckout() function</a>, that <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L113">creates</a> one cart list out of 2 and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L124">sets</a> mini-version of cart list in checkout page</li> <li>Setting the state in setCartListInCheckout() function will trigger <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L211">fourth useEffect</a>, where we launch toggleErrors() function. Inside, it passes "checkoutCartList" value to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L138">checkItemsAmount() helper function</a>, that based on single passed cart list (with all fields, necessary for a check) <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L133">return</a> 2 values: boolean - are any of items out of stock, and array of items, where selected amount exceeded available. <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L140">Based on</a> returned values, checkItemsAmount() toggles error message and checkout button state. Everything is similar to functions in cart, but slighty different, because the cart list in checkout is also different</li> <li>We have several functions in useEffects left to understand, but to do it better, let's analyze <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L236">Form.js component</a> first. When user goes to checkout page, he'll see <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L68">the form</a> powered with Formik. <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L78">The default value</a> of all inputs (except radio buttons, that are slightly different to handle) is set to Formik values, that <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L39">are initialized</a> with useFormik hook</li> <li>Despite that we can handle form data solely with localStorage, I decided to use both localStorage and Formik for the sake of learning. Every time user changes input values, we run handleFormFields() function. Inside, we need to put entered value in localStorage (to save on reload), and save it in Formik values. We <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L16-L17">declare name and value</a> of current input. It it's a first time user enters value - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L22">create</a> order item in localStorage and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L24">put</a> key-value pair it it, based on declared inputs. If not - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L31-L36">handle the case of deleting data</a> from localStorage or put data in it as usual. As for Formik hook - all values saved with <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L51">.handleChange() Formik function</a>. Except radio buttons values, the example to whose I couldn't find in docs, nor in the Web, so I <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L46">set it manually</a>. When user changes delivery option, we change tax charge. We launch <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L48">estimateShippingCost() function</a> (that also initially <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L201">runs</a> in the second useEffect! ☝️), that <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L180-L184">sets</a> shipping cost state</li> <li>When user submits form, it is handled by Formik onSubmit function, where we toggle the visual state of form, by <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L51">setting</a> value in localStorage (to save it on page reload) and by <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L52">setting</a> state to "true". Also, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L55">scroll</a> to the top of form to make it look pretty ✨</li> <li>Visual view of form <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L66">depends</a> on that state, when it's true - it shows <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L196">info with Formik values</a>. By pressing <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L212">"edit" button</a>, the function of which <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/Form.js#L56-L57">sets</a> values back, user can get back to editing whenever he needs</li> <li>Speaking of changing form's visual view - let's briefly get back to second useEffect, where we're launching <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L198">setFormVisualView()</a> on page load. It checks those values in localStorage and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L155-L157">sets</a> the state of form based on them</li> <li>Setting "isFormSubmitted" state will trigger the fifth useEffect, where we launch <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L216">insertSavedDataInForm() function</a>, that is needed for saving user entered data in form on page reload. Inside of it, after checking if "order" item exists in localStorage, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L74-L80">set all Formik values</a> to the corresponding values from that item, it they exist. If they don't - set Formik values to themselves (doing that, we cover the case when form's state is "not submitted" and when some inputs weren't touched yet (hence - no values in localStorage)). It works for both form states and all that in just one line. Easy! 🀏 Except for those pesky dropdown (country) and radio buttons (delivery) - they need "special treatment" by getting and setting <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L85-L86">their</a> <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L92-L99">values</a> from DOM when form is not submitted (that's why we need "isFormSubmitted" state as a dependency in the fifth useEffect - to check its state). After we set delivery, let's not forget to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L103">launch</a> estimateShippingCost() function, because shipping cost depends on delivery</li> <li>During insertSavedDataInForm() function run we set "formik.values.country" value, which will trigger the last useEffect, specifically <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L220">setTaxAndFormBasedOnCountry() function</a> in it. Inside, we get the country input's element and, depending on its value, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L166-L168">set all necessary states</a>: tax (goes to CartInfo.js), territory type and post code regEx pattern (both go to Form.js)</li> <li>Nothing much going on in <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L245">CartInfo.js component</a> - we just <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/CartInfo.js#L9-L18">import</a> all passed values and render JSX with them. Respectively, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/CartInfo.js#L34">map</a> cart items</li> <li>Each checkout cart item should have different state based on available item amount. To cover "out of stock" case, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/cartInfo/CheckoutCartListItem.js#L17">abstract</a> DRY markup into variable and if available amount is 0 - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/cartInfo/CheckoutCartListItem.js#L39">render</a> strikethrough element with "out of stock" message, if not - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/cartInfo/CheckoutCartListItem.js#L48">render</a> usual "p" tag. To cover "selected amount exceeded available" case, we set <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/cartInfo/CheckoutCartListItem.js#L23">amount</a> and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/cartInfo/CheckoutCartListItem.js#L30">price</a> to 0, and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/cartInfo/CheckoutCartListItem.js#L60">change</a> text colour</li> <li>Our final component is PayPal Checkout button. We gonna use <a href="https://github.com/paypal/react-paypal-js">"react-paypal-js" library</a>. To initialize script we need Client ID of app, created in PayPal Developer account (Dashboard -> My Apps & Credentials). To test fake payments we also need to create 2 sandbox test accounts (Sanbdox -> Accounts) - one "Business" (merchant) and one "Personal" (customer) type. In options of PayPalScriptProvider we specify <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L276-L277">environment variables of client-id and merchant-id</a> πŸ”’ The latter we can find in Business test account info (hover over "..." button near sandbox account -> View/edit account -> Profile tab -> Account ID). Full reference to all provider options is <a href="https://developer.paypal.com/sdk/js/configuration/#link-configureandcustomizeyourintegration">here</a></li> <li>PayPalScriptProvider wraps our <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/checkout.js#L283">PayPalCheckoutButton.js component</a>, where we initialize <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L27">usePayPalScriptReducer hook</a> and put <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L30-L36">PayPal code</a> in useEffect to render button. Official example is <a href="https://paypal.github.io/react-paypal-js/?path=/docs/example-paypalbuttons--default">here</a></li> <li>To show checkout button, we import <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L44">PayPalButtons component</a>. We need button to re-initialize whenever user changes total price or currency, so we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L47">put</a> those values in "forceReRender" prop</li> <li>On checkout button click PayPal runs <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L48">createOrder() function</a>. We don't need to disable checkout button right away, because PayPal shows overlay loader. Before we run code that sets up payment, we have to check items availability one last time: <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L51">get</a> ids from localStorage, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L53">fetch</a> items amounts based on them, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L64">create</a> one common cart with all values and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L75">pass</a> it to helper function to return check results. If not passed - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L78">launch</a> assignProductAmountInCart(), that'll trigger errors. If passed - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L80">set up</a> the transaction, as it shown in <a href="https://developer.paypal.com/sdk/js/reference/#link-createorder">official example</a>. We pass <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L84-L85">currency and total price</a>, and set <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L90">"NO_SHIPPING" preference</a>, to show user the PayPal form without shipping fields (in case he'll choose credit card payment), that he already filled in our custom form</li> <li>If you want to see how the amount errors will trigger on all pages, you need to set up Strapi on your own machine to toggle amounts in CMS manually. But if you think it's not worth to set up, I made a <a href="https://vimeo.com/742672808">video</a></li> <li>After PayPal transaction is finished, it launches <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L95">onApprove() function</a>, where we do stuff for our store. We <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L97">disable</a> checkout button and write <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L99">PayPal promise</a> (like in <a href="https://developer.paypal.com/sdk/js/reference/#link-onapprove">the official example</a>), that returns data with generated order id. Now we just need to create <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L119">order object</a> to send it to CMS with all data in it, specifically: <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L120">order id</a>, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L140">time of purchase</a> and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L141">payment method</a>, that are coming from PayPal; <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L121">customer info</a>, that we store in Formik; <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L135">purchased items</a>, that we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L103">map</a> from checkout cart list; and other values that we pass to button as props. This object we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L145">put</a> in query and hit our API route - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js">api/order</a></li> <li>In there, we need to do 3 things: post order data in CMS, update amount of items and send email to customer with all information.</li> <li>Before posting order, let's <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L11">create</a> a copy of "purchasedItems" array without "available" keys - we don't need this field in "purchaseditems" Strapi's component in orders collection (but we need it for subtraction further). Then, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L20">create</a> a string of it (just like the string of object with ids before) for GraphQL query for Strapi</li> <li>We gonna do everything in async order: <ul> <li>First, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L28">post</a> the data with the corresponding query, which will depend on how you structured your collections in CMS. After posting, in .then() statement we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L116">create</a> array with items with subtracted amounts</li> <li>At the time of writing, Strapi doesn't support bulk entries creation, but this feature is a candidate (if you use Strapi and would like to see this feature, please, upvote <a href="https://feedback.strapi.io/developer-experience/p/support-bulk-entries-creationupdates-v4">this</a> πŸ™) So, for now we have to do multiple requests with <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L128">PromiseAll</a>. In query of each fetch() function we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L141">set</a> the subtracted amount of the item</li> <li>Next, in PromiseAll .then() statement, we are using nodemailer to send an email to a submitted email address. First, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L170">create</a> transporter, where we specify nesessary options, depending on email provider (I used Outlook). Api pages won't go into final code bundle, but for the extra layer of security, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L175-L176">use env vars</a> for your sender email and password πŸ”’ Also, turn off <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L180">"rejectUnauthorized" flag</a> for self-signed sertificate. Just in case of error, we'll <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L184">fire</a> the console logs (the .verify() method doesn't check if email was sent or not - that is up to email provider you chose, it verifies if Simple Mail Transfer Protocol is ready to work). Finally, let's <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L193">send</a> an email. Nodemailer code is based on the official examples: <a href="https://nodemailer.com/smtp/#3-allow-self-signed-certificates">here</a>, <a href="https://nodemailer.com/smtp/#verify-smtp-connection-configuration">here</a> and <a href="https://nodemailer.com/about/#example">here</a>. In Google Mail the email will look like <a href="https://ibb.co/TtSv5gR">this</a></li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/order.js#L258">send</a> the response with order id to the frontend</li> </ul> </li> <li>Almost finished! Back to PayPalCheckoutButton.js, in .then() statement, after everything we've done, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L158-L161">delete all data</a>: cartlist, order and isFormSubmitted items; <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L162">trigger</a> cart badge to disappear; and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/checkout/PayPalCheckoutButton.js#L164">redirect</a> user to "thank you" page with order id</li> <li>And in "thank you" page we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/thank-you.js#L39">show</a> a success message with order id. If no id was provided in query for any reason, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/thank-you.js#L19">redirect</a> user to the main page</li> </ol> </details> </details> <br /> <details> <summary>Currency change</summary> <br /> <ol> <li>User can change currency in Footer's SelectButton.js component, in <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/layout/footer/SelectButtons.js#L26">select element</a></li> <li>On select change we launch changeCurrency() function, where we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/layout/footer/SelectButtons.js#L18">put</a> the grabbed value into localStorage and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/layout/footer/SelectButtons.js#L19">launch</a> refreshCurrency() function, that lives in _app.js</li> <li>In there, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L141">set</a> "isCurrencySet" state to false to show loader where we need, <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L142">set</a> "currency" state to a value saved in localStorage and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L143">set</a> "isCurrencySet" state back to true to show currency instead of loader</li> <li>Setting "currency" state will trigger second useEffect in _app.js, that'll <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L210">launch</a> setCurrencyRates() function. Inside of it, we use <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L175">switch</a> operator to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L182-L183">set</a> "currencyRate" and "currencyCode" states based on "currency" state</li> <li>We set "currencyRate" state to value from fetched data from 3rd party currency API (I used openexchangerates.org). API has monthly limit, so in case it will be exceeded, just for the pet project to work without crashes, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L179">set</a> fallback value</li> <li>To save user's currency choice, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L206">launch</a> setCurrencyCodes() function in the first useEffect on page load. Inside it, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L150">set</a> state to show loader and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L152">check</a> if "currency" item is present in localStorage. If it isn't, meaning user hasn't selected currency option before - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L153">set</a> it to default, if it is - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L157">hit</a> custom "currencyRates" API route. In there we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/currencyRates.js#L2">fetch</a> the exchange rates from 3rd party API and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/currencyRates.js#L13">send</a> derived data in response. Back in _app.js, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L168">set</a> response into React state and, like on user click, as we described in steps #2-5 - <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/_app.js#L169">launch</a> refreshCurrency() function, that will trigger setCurrencyRates() function in the second useEffect, that'll do the magic ✨</li> <li>Also, in Footer's SelectButton.js component, on page load we visually <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/layout/footer/SelectButtons.js#L9-L13">set</a> one of select options based on item's value in localStorage</li> <li>Below, in "Info about where Context data goes" section you can see where all states, connected with currency, go in this app</li> </ol> </details> <br /> <details> <summary>Product reviews (Rich text editor + Formik)</summary> <br /> <ol> <li>On product page we have <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L127">form</a> with 2 usual HTML inputs (name and email) and React Draft WYSIWYG rich text editor</li> <li>To grab the values we need to <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L21">set</a> them to their default state with useFormik hook first. For HTML inputs we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L143">set</a> "value" attribute to Formik value. Every time when user enters name or email, we launch <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L144">"formik.handleChange" function</a> that puts them into Formik values</li> <li>For RTE it's different. We have <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L168">"editorState" prop</a>, wich will be set to React state. In this state we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L90">check</a> if formik "message" value is empty (value, that we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L77">declared</a> earlier): if it's empty (empty string === false) - set state to initialized empty RTE state, if it's not - set it to prepareDraft() custom function, that accepts Formik "message" value as a parameter</li> <li>Every time when user enters text in RTE, we launch onEditorStateChange() function, that we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L174">put</a> into RTE prop. Inside it, we: <ul> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L94">convert</a> entered text into HTML using <a href="https://github.com/jpuri/draftjs-to-html">"draftjs-to-html" library</a></li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L97">put</a> derived HTML into another function that will explicitly <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L88">set</a> it to Formik "message" value with ".setFieldValue()" method</li> <li><a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L98">trigger</a> the React state change, that will launch prepareDraft() function we mentioned in the previous step</li> </ul> </li> <li>prepareDraft() function takes Formik "message" value. Then: <ul> <li>Formik "message" value <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L81">is converted</a> into DraftJS Editor content, using <a href="https://github.com/jpuri/html-to-draftjs">"html-to-draftjs" library</a></li> <li>DraftJS Editor content <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L82">is converted</a> into <a href="https://draftjs.org/docs/api-reference-content-state">ContentState record</a>, using <a href="https://draftjs.org/docs/api-reference-content-state#createfromblockarray">createFromBlockArray() function</a></li> <li>ContentState record <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L83">is converted</a> into <a href="https://draftjs.org/docs/api-reference-editor-state">EditorState object</a>, using <a href="https://draftjs.org/docs/api-reference-editor-state#createwithcontent">createWithContent() function</a></li> <li>derived EditorState object <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L85">is returned</a> to "editorState" React state</li> </ul> </li> <li>All credits goes to <a href="https://codesandbox.io/s/v5rfp?file=/src/TextEditor.tsx">this codesandbox example</a> πŸ™</li> <li>After user submits review, we launch Formik's onSubmit function. Inside, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L24">set</a> the state that will disable both editor and submit button. Then, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L27">fetch</a> postReview API route with product id and submitted data in query</li> <li>In api/postReview, before posting, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/postReview.js#L7-L9">sanitize</a> data with <a href="https://github.com/kkomelin/isomorphic-dompurify">"isomorphic-dompurify' library</a>. Then, after we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/postReview.js#L11">send</a> the request, we must ensure that collection has one review per person. We do that by <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/api/postReview.js#L58-L63">checking</a> if submitted email is already present. If it is - Strapi will attach "errors" object and in this case we send status code 400 (bad request) with custom error message, if it isn't - send status code 200 with response data (it'd be posted review)</li> <li>Back in product page, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L31-L47">check</a> response on status code, if it's 400 - we set error message to React state, that we declared earlier, and throw err object; if it's any other 40X - we do the same, but set custom error state to false. This state will <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L186-L190">toggle</a> error message</li> <li>If no errors pop up, in the next .then() statement we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L53-L54">reset Formik form and RTE state</a>, and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L56">set</a> error to false. Reviews <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L112-L124">are rendered</a> from "reviews" state, so we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L60-L61">push response data (posted review) and refresh the state</a>. In the end, we <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L63">disable</a> editor's "read only" and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/components/productPage/Reviews.js#L66">scroll</a> to the top of review list to let user see his review</li> <li>On page reload, based on passed id from getServerSideProps() context object, we fetch data from 2 endpoints: <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/product-page/%5Bid%5D.js#L12">"product"</a> and <a href="https://github.com/AlexTechNoir/Next.js-Strapi-Ecommerce-store/blob/master/src/pages/product-page/%5Bid%5D.js#L98">"reviews"</a>. If you don't use Strapi, you're probably creating your own API, so, further info won't be of interest to you. You can stop reading now πŸ‘‹ If you do use Strapi, I have some points left to say at the end</li> <li>At the time of writing, Strapi doesn't support array field type. It would be easier to handle product reviews with it. So far, it is only a feature request (if you would like to see this feature, please, upvote <a href="https://feedback.strapi.io/developer-experience/p/allow-array-or-multi-select-field-type">this</a> πŸ™)</li> <li>We could create "repeatable component" field type, but AFAIK, in this case, for every new submitted review you have to re-replace ALL components (other reviews, that are already in CMS) in one shot (same case with "custom JSON" field type), sending them ALL to CMS along with the new one. If we have hundreds of reviews, this is sucks! For traffic and performance</li> <li>The only option I see is to create "relation" field type: one product has many reviews. Creating "reviews" collection and relating it to "products" will allow us to post one entry without harassing others. That's why, when user visits products page, we fetch data in 2 requests: product data and reviews</li> </ol> </details> ## Info about where Context data goes (from pages/_app.js): <details> <summary>Show/hide</summary> <br> `areCookiesAccepted` goes to: - components/Layout.js `setAreCookiesAccepted` goes to: - components/Layout.js - components/layout/CookieBanner.js (as props from components/Layout.js) `cartBadgeToggle` goes to: - components/layout/header/CartButton.js - components/productPage/AddToCart.js - components/cart/CartList.js - components/cart/cartList/CartListItem.js (as props from components/cart/CartList.js) - components/checkout/PayPalCheckoutButton.js `setCartBadgeToggle` goes to: - components/productPage/AddToCart.js - components/cart/CartList.js - components/cart/cartList/CartListItem.js (as props from components/cart/CartList.js) - components/checkout/PayPalCheckoutButton.js `itemsAmountInCart` goes to: - pages/cart.js - pages/checkout.js - pages/checkout/CartInfo.js (as props from pages/checkout.js) `cartList` goes to: - components/cart/CartList.js - pages/checkout.js `setCartList` goes to: - components/checkout/PayPalCheckoutButton.js `totalPriceInCart`, `totalDiscountedPriceInCart` and `areThereAnyDiscountsInCart` go to: - components/cart/CartList.js - pages/checkout.js - pages/checkout/CartInfo.js (as props from pages/checkout.js) `assignProductAmountInCart` goes to: - pages/cart.js - components/cart/CartList.js (as props from pages/cart.js) - components/cart/cartList/CartListItem.js(as props from components/cart/CartList.js) - pages/checkout.js - components/checkout/PayPalCheckoutButton.js (as props from pages/checkout.js) `estimateTotalPrice` goes to: - components/cart/CartList.js - components/cart/cartList/CartListItem.js (as props from components/cart/CartList.js) - pages/checkout.js `currency` goes to: - components/ProductListItem.js - components/SearchResult.js - components/product/productPage/ProductInfo.js - components/cart/CartList.js - components/cart/cartList/CartListItem.js (as props from components/cart/CartList.js) - pages/checkout.js - pages/checkout/CartInfo.js (as props from pages/checkout.js) - pages/checkout/cartInfo/CheckoutCartListItem.js (as props from pages/checkout/CartInfo.js) - pages/checkout/PayPalCheckoutButton.js (as props from pages/checkout.js) `currencyCode` goes to: - pages/checkout.js - pages/checkout/PayPalCheckoutButton.js (as props from pages/checkout.js) `currencyRate` goes to: - components/ProductListItem.js - components/SearchResult.js - components/product/productPage/ProductInfo.js - components/cart/CartList.js - pages/checkout.js - pages/checkout/CartInfo.js (as props from pages/checkout.js) - pages/checkout/cartInfo/CheckoutCartListItem.js (as props from pages/checkout/CartInfo.js) - pages/checkout/PayPalCheckoutButton.js (as props from pages/checkout.js) `isCurrencySet` goes to: - components/ProductListItem.js - components/SearchResult.js - components/product/productPage/ProductInfo.js - components/cart/CartList.js - pages/checkout.js - pages/checkout/CartInfo.js (as props from pages/checkout.js) `refreshCurrency` goes to: - components/layout/footer/Buttons.js `setItemsAmountInCart`, `setTotalPriceInCart`, `setTotalDiscountedPriceInCart`, `fetchedRates`, `setFetchedRates`, `setCurrency`, `setCurrencyCode`, `setCurrencyRate`, `setIsCurrencySet`, `setCurrencyCodes` and `setCurrencyCodes` stay in pages/_app.js </details> ## Notes: - the loader has been taken from [this codepen example](https://codepen.io/t_afif/pen/LYyYgQw) πŸ™ - since it's a demo, all pages have meta tag with content="noindex" attribute - html-to-draftjs library is deliberately downgraded to 1.4.0 to avoid bug (see [issue #78](https://github.com/jpuri/html-to-draftjs/issues/78))

Frontend Templates E-commerce Platforms
35 Github Stars