├── 978-1-4842-9611-0.jpg ├── Ch5_Code_Nextjs_eCommerce └── Ch5_Code_Nextjs_eCommerce │ ├── alternative method - sass │ ├── index.scss │ ├── next.config.js │ └── about.scss │ ├── applying styles │ ├── images │ │ ├── perfect.jpg │ │ ├── frontimage.jpg │ │ └── newsletter.jpg │ └── globals.css │ └── alternative method - css modules │ ├── Footer │ ├── Footer.module.css │ └── Footer.jsx │ └── Layout.jsx ├── Ch11_Code_Nextjs_eCommerce └── Ch11_Code_Nextjs_eCommerce │ ├── translating into dutch │ └── nl-NL │ │ ├── common.json │ │ ├── about.json │ │ ├── emptycart.json │ │ ├── privacy.json │ │ ├── test.json │ │ ├── delivery.json │ │ ├── terms.json │ │ ├── shop.json │ │ ├── demobanner.json │ │ ├── navbar.json │ │ ├── canceled.json │ │ ├── minicart.json │ │ ├── info.json │ │ ├── cart.json │ │ ├── newsletter.json │ │ ├── success.json │ │ ├── slug.json │ │ ├── footer.json │ │ ├── perfect.json │ │ └── home.json │ ├── translating into english │ ├── en │ │ ├── common.json │ │ ├── about.json │ │ ├── privacy.json │ │ ├── test.json │ │ ├── delivery.json │ │ ├── emptycart.json │ │ ├── terms.json │ │ ├── shop.json │ │ ├── demobanner.json │ │ ├── navbar.json │ │ ├── canceled.json │ │ ├── minicart.json │ │ ├── cart.json │ │ ├── info.json │ │ ├── newsletter.json │ │ ├── slug.json │ │ ├── success.json │ │ ├── footer.json │ │ └── perfect.json │ └── home.json │ ├── changes for adding language support │ ├── public │ │ └── locales │ │ │ ├── en │ │ │ ├── common.json │ │ │ └── test.json │ │ │ └── nl-NL │ │ │ ├── common.json │ │ │ └── test.json │ └── snippets for adding language support.txt │ ├── guidance - converting components │ └── snippets for converting pages.txt │ ├── updating a component file │ └── snippets for updating a component file.txt │ ├── updating a page file │ └── snippets for updating a page file.txt │ ├── guidance - converting pages │ └── snippets for converting pages.txt │ ├── changes for adding locale support │ └── snippets for adding locale support.txt │ └── adapting slug.js │ └── Adapting slug.js.txt ├── Ch2_Code_Nextjs_eCommerce └── Ch2_Code_Nextjs_eCommerce │ ├── components │ ├── index.js │ ├── DemoBanner.jsx │ ├── Newsletter.jsx │ ├── PerfectBanner.jsx │ ├── NavBar.jsx │ ├── Footer.jsx │ ├── Layout.jsx │ └── PaymentIcons.jsx │ ├── pages │ ├── privacy.js │ ├── delivery.js │ ├── terms.js │ ├── about.js │ ├── _app.js │ ├── contact.js │ └── index.js │ └── images │ ├── perfect.jpg │ ├── frontimage.jpg │ └── newsletter.jpg ├── Ch8_Code_Nextjs_eCommerce └── Ch8_Code_Nextjs_eCommerce │ ├── adding next-seo.txt │ ├── overriding default settings.txt │ ├── next-seo.config file.txt │ └── adding dynamic names.txt ├── Ch3_Code_Nextjs_eCommerce └── Ch3_Code_Nextjs_eCommerce │ ├── Product listing.xlsx │ ├── data fields for Sanity.xlsx │ ├── adding products │ ├── caramel-apple.png │ ├── quince-cobnut.png │ ├── spiced-pumpkin.png │ ├── sticky-toffee.png │ ├── chocolate-orange.jpg │ ├── cranberry-clementine.png │ ├── pear-vanilla-cinnamon.png │ └── ingredients.txt │ ├── configuring Sanity │ ├── client.js │ └── product.js │ ├── building shop │ └── shop.js │ └── creating individual page │ ├── Info.jsx │ ├── Product.jsx │ └── product │ └── [slug].js ├── Ch9_Code_Nextjs_eCommerce └── Ch9_Code_Nextjs_eCommerce │ ├── e2e testing results.png │ ├── script entries.txt │ ├── cypress │ └── e2e │ │ ├── creditCardImages.cy.js │ │ ├── app.cy.js │ │ ├── starRating.cy.js │ │ ├── component │ │ ├── demobanner.cy.js │ │ ├── info.cy.js │ │ └── product.cy.js │ │ ├── addingProducts.cy.js │ │ └── removingProducts.cy.js │ └── tsconfig.json ├── Ch7_Code_Nextjs_eCommerce └── Ch7_Code_Nextjs_eCommerce │ ├── correcting errors │ ├── code for Info.jsx.txt │ ├── favicon │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-384x384.png │ │ └── safari-pinned-tab.svg │ ├── browserconfig.xml │ └── site.webmanifest │ ├── updated add to cart button │ ├── markup for button.txt │ └── styles for button.txt │ ├── updated star review │ ├── styles.css.txt │ └── StarRating.jsx │ ├── cancel page │ └── canceled.js │ └── flipping product images │ └── flipping product images.txt ├── Ch12_Code_Nextjs_eCommerce └── Ch12_Code_Nextjs_eCommerce │ ├── adding account page │ ├── code changes for NavBar.jsx.txt │ ├── account.js │ ├── styles for global.css.txt │ └── DisplayProfile.jsx │ ├── installing next-auth │ └── src │ │ └── pages │ │ └── api │ │ └── auth │ │ └── [...nextauth].js │ └── adding login options │ ├── styles for globals.css.txt │ └── DemoBanner.jsx ├── Ch4_Code_Nextjs_eCommerce └── Ch4_Code_Nextjs_eCommerce │ ├── adding stripe │ ├── pages │ │ └── api │ │ │ ├── hello.js │ │ │ └── stripe.js │ ├── lib │ │ ├── getStripe.js │ │ ├── client.js │ │ └── utils.js │ ├── success.js │ └── Cart.jsx │ ├── refactoring the cart │ ├── Cart │ │ └── EmptyCart.jsx │ ├── MiniCart.jsx │ └── Cart.jsx │ ├── adding context │ ├── NavBar.jsx │ ├── _app.js │ ├── Footer.jsx │ └── StateContext.js │ ├── constructing the cart │ ├── NavBar.jsx │ └── Cart.jsx │ ├── adding a minicart │ └── MiniCart.jsx │ └── finishing the buttons │ └── [slug].js ├── Ch1_Code_Nextjs_eCommerce └── Ch1_Code_Nextjs_eCommerce │ ├── devDependencies for site.txt │ └── dependencies for site.txt ├── Ch10_Code_Nextjs_eCommerce └── Ch10_Code_Nextjs_eCommerce │ └── cypress tests │ └── cypress tests pipeline file.txt ├── README.md ├── Contributing.md ├── Ch6_Code_Nextjs_eCommerce └── Ch6_Code_Nextjs_eCommerce │ ├── mobile.css │ ├── tablet.css │ └── portable.css └── LICENSE.txt /978-1-4842-9611-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/978-1-4842-9611-0.jpg -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/alternative method - sass/index.scss: -------------------------------------------------------------------------------- 1 | @use "about"; /* About Us page */ 2 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "greeting": "Hello world!" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "greeting": "Hello world!" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/about.json: -------------------------------------------------------------------------------- 1 | { 2 | "about": "Over Macaron Magic" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/about.json: -------------------------------------------------------------------------------- 1 | { 2 | "about": "About Macaron Magic" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/emptycart.json: -------------------------------------------------------------------------------- 1 | { 2 | "empty": "Je winkeltas is leeg" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/privacy.json: -------------------------------------------------------------------------------- 1 | { 2 | "privacy": "Dit is de privacypagina" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Home", 3 | "about": "About" 4 | } 5 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/privacy.json: -------------------------------------------------------------------------------- 1 | { 2 | "privacy": "This is the privacy page" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Home", 3 | "about": "About" 4 | } 5 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/delivery.json: -------------------------------------------------------------------------------- 1 | { 2 | "delivery": "Dit is de leveringspagina" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/delivery.json: -------------------------------------------------------------------------------- 1 | { 2 | "delivery": "This is the delivery page" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/emptycart.json: -------------------------------------------------------------------------------- 1 | { 2 | "empty": "Your shopping bag is empty" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/terms.json: -------------------------------------------------------------------------------- 1 | { 2 | "terms": "Dit is de algemene voorwaarden pagina" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/terms.json: -------------------------------------------------------------------------------- 1 | { 2 | "terms": "This is the terms and conditions page" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/changes for adding language support/public/locales/en/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "greeting": "Hello world!" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/shop.json: -------------------------------------------------------------------------------- 1 | { 2 | "shop": "Shop", 3 | "browse": "Browse for products" 4 | } 5 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/changes for adding language support/public/locales/nl-NL/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "greeting": "Hallo Wereld!" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/shop.json: -------------------------------------------------------------------------------- 1 | { 2 | "shop": "Winkel", 3 | "browse": "Blader naar producten" 4 | } 5 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Layout } from "./Layout"; 2 | export { default as NavBar } from "./NavBar"; 3 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/changes for adding language support/public/locales/en/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Home", 3 | "about": "About" 4 | } 5 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/changes for adding language support/public/locales/nl-NL/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Thuis", 3 | "about": "Over" 4 | } 5 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/demobanner.json: -------------------------------------------------------------------------------- 1 | { 2 | "demo": "This is a demo store - no orders will be accepted or delivered" 3 | } 4 | -------------------------------------------------------------------------------- /Ch8_Code_Nextjs_eCommerce/Ch8_Code_Nextjs_eCommerce/adding next-seo.txt: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/demobanner.json: -------------------------------------------------------------------------------- 1 | { 2 | "demo": "Dit is een demo-winkel - er worden geen bestellingen geaccepteerd of geleverd" 3 | } 4 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/navbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Home", 3 | "about": "About", 4 | "shop": "Shop", 5 | "contact": "Contact" 6 | } 7 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/pages/privacy.js: -------------------------------------------------------------------------------- 1 | const Privacy = () => ( 2 |
3 |

This is the privacy page

4 |
5 | ); 6 | 7 | export default Privacy; 8 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/navbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Thuis", 3 | "about": "Over", 4 | "shop": "Winkel", 5 | "contact": "Contact" 6 | } 7 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/pages/delivery.js: -------------------------------------------------------------------------------- 1 | const Delivery = () => ( 2 |
3 |

This is the delivery page

4 |
5 | ); 6 | 7 | export default Delivery; 8 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/images/perfect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/images/perfect.jpg -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/pages/terms.js: -------------------------------------------------------------------------------- 1 | const Terms = () => ( 2 |
3 |

This is the terms and conditions page

4 |
5 | ); 6 | 7 | export default Terms; 8 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/canceled.json: -------------------------------------------------------------------------------- 1 | { 2 | "canceled": "Your order is canceled - you have not been charged", 3 | "continue": "Continue Shopping" 4 | } 5 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/images/frontimage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/images/frontimage.jpg -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/images/newsletter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/images/newsletter.jpg -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/Product listing.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/Product listing.xlsx -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/e2e testing results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/e2e testing results.png -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/script entries.txt: -------------------------------------------------------------------------------- 1 | "cypress": "cypress open", 2 | "cypress:headless": "cypress run --browser chrome --headless", 3 | "cypress:component": "cypress run --component" -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/data fields for Sanity.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/data fields for Sanity.xlsx -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/minicart.json: -------------------------------------------------------------------------------- 1 | { 2 | "your-cart": "Your cart contains", 3 | "items": "items", 4 | "go-to-shop": "Go to Shop", 5 | "pay": "Pay with Stripe" 6 | } 7 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/code for Info.jsx.txt: -------------------------------------------------------------------------------- 1 | import dynamic from "next/dynamic"; 2 | const Tabs = dynamic( 3 | import("react-tabs").then((mod) => mod.Tabs), 4 | { ssr: false } 5 | ); 6 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/canceled.json: -------------------------------------------------------------------------------- 1 | { 2 | "canceled": "YUw bestelling is geannuleerd - er zijn geen kosten in rekening gebracht", 3 | "continue": "Doorgaan met winkelen" 4 | } 5 | -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/caramel-apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/caramel-apple.png -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/quince-cobnut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/quince-cobnut.png -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/spiced-pumpkin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/spiced-pumpkin.png -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/sticky-toffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/sticky-toffee.png -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/applying styles/images/perfect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/applying styles/images/perfect.jpg -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/chocolate-orange.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/chocolate-orange.jpg -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/applying styles/images/frontimage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/applying styles/images/frontimage.jpg -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/applying styles/images/newsletter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/applying styles/images/newsletter.jpg -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/favicon.ico -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/minicart.json: -------------------------------------------------------------------------------- 1 | { 2 | "your-cart": "Uw winkelwagen bevat", 3 | "items": "artikelen", 4 | "go-to-shop": "Ga naar winkel", 5 | "pay": "Betaal met Stripe" 6 | } 7 | -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/cranberry-clementine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/cranberry-clementine.png -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/pear-vanilla-cinnamon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/pear-vanilla-cinnamon.png -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/cart.json: -------------------------------------------------------------------------------- 1 | { 2 | "your-cart": "Your cart", 3 | "items": "items", 4 | "continue": "Continue Shopping", 5 | "subtotal": "Subtotal: ", 6 | "pay": "Pay with Stripe" 7 | } 8 | -------------------------------------------------------------------------------- /Ch12_Code_Nextjs_eCommerce/Ch12_Code_Nextjs_eCommerce/adding account page/code changes for NavBar.jsx.txt: -------------------------------------------------------------------------------- 1 | import { useSession } from "next-auth/react"; 2 | 3 | const { data: session } = useSession(); 4 | 5 | {session && Account} 6 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding stripe/pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "ingredients": "Ingrediënten", 3 | "description": "Beschrijving", 4 | "additional": "Extra informatie", 5 | "weight": "Gewicht:", 6 | "delivery": "Levering:" 7 | } 8 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/Practical-Next.js-for-E-Commerce/HEAD/Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/android-chrome-384x384.png -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/cart.json: -------------------------------------------------------------------------------- 1 | { 2 | "your-cart": "Jouw winkelwagen", 3 | "items": "artikelen", 4 | "continue": "Doorgaan met winkelen", 5 | "subtotal": "Subtotaal: ", 6 | "pay": "Betaal met Stripe" 7 | } 8 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "ingredients": "Ingredients", 3 | "description": "Description", 4 | "additional": "Additional information", 5 | "weight": "Weight:", 6 | "delivery": "Delivery:" 7 | } 8 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/newsletter.json: -------------------------------------------------------------------------------- 1 | { 2 | "join": "Meedoen", 3 | "newsletter": { 4 | "firstname": "Voornaam", 5 | "lastname": "Achternaam", 6 | "email": "E-mailadres", 7 | "submit": "Indienen" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/newsletter.json: -------------------------------------------------------------------------------- 1 | { 2 | "join": "Join", 3 | "newsletter": { 4 | "firstname": "First name", 5 | "lastname": "Last name", 6 | "email": "Email address", 7 | "submit": "Submit" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/alternative method - sass/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | sassOptions: { 5 | includePaths: ["styles"], 6 | }, 7 | }; 8 | 9 | module.exports = nextConfig; 10 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/updated add to cart button/markup for button.txt: -------------------------------------------------------------------------------- 1 | {/* NEW BUTTON */} 2 | 11 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/slug.json: -------------------------------------------------------------------------------- 1 | { 2 | "Details": "Details:", 3 | "perbox": "per box of 12", 4 | "quantity": "Quantity:", 5 | "add": { 6 | "tocart": "Add to cart", 7 | "tobag": "Add to My Bag" 8 | }, 9 | "buynow": "Buy Now", 10 | "alsolike": "You may also like" 11 | } 12 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/success.json: -------------------------------------------------------------------------------- 1 | { 2 | "thankyou": "Thank you for your order!", 3 | "email-msg": "Check your email inbox for the receipt.", 4 | "questions": "If you have any questions, please email", 5 | "questions-email": "hello@macaron-magic.com", 6 | "continue": "Continue Shopping" 7 | } 8 | -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/cypress/e2e/creditCardImages.cy.js: -------------------------------------------------------------------------------- 1 | describe("Counting Credit Card Images", () => { 2 | it("should navigate to the about page", () => { 3 | cy.visit("http://localhost:3000/"); 4 | 5 | // verify that we have 9 list items 6 | cy.get("ul.payment-icons > li").should("have.length", 9); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/success.json: -------------------------------------------------------------------------------- 1 | { 2 | "thankyou": "Bedankt voor je bestelling!", 3 | "email-msg": "Controleer uw e-mailinbox voor de ontvangst.", 4 | "questions": "Als u vragen heeft, kunt u mailen", 5 | "questions-email": "hello@macaron-magic.com", 6 | "continue": "Doorgaan met winkelen" 7 | } 8 | -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/alternative method - sass/about.scss: -------------------------------------------------------------------------------- 1 | .about-us { 2 | width: 70%; 3 | margin: 0 auto; 4 | 5 | & p:nth-child(1) { 6 | margin: 40px 0 0 0; 7 | font-family: "Oswald", sans-serif; 8 | font-size: 42px; 9 | } 10 | 11 | & > p:nth-child(2) { 12 | margin: 20px 0 20px 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Ch12_Code_Nextjs_eCommerce/Ch12_Code_Nextjs_eCommerce/adding account page/account.js: -------------------------------------------------------------------------------- 1 | import { DisplayProfile } from "src/components"; 2 | 3 | const Account = () => { 4 | return ( 5 | <> 6 |
7 |

Your Account

8 |
9 | 10 | 11 | ); 12 | }; 13 | 14 | export default Account; 15 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/slug.json: -------------------------------------------------------------------------------- 1 | { 2 | "Details": "Details:", 3 | "perbox": "per doos van 12", 4 | "quantity": "Hoeveelheid:", 5 | "add": { 6 | "tocart": "Voeg toe aan winkelkar", 7 | "tobag": "Voeg toe aan mijn tas" 8 | }, 9 | "buynow": "Koop nu", 10 | "alsolike": "Dit vind je misschien ook leuk" 11 | } 12 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "footer": { 3 | "delivery": "Delivery", 4 | "privacy": "Privacy", 5 | "terms": "Terms and Conditions of Sale", 6 | "contact": "Contact Us", 7 | "email": "Contact: hello@macaron-magic.com", 8 | "copyright": "2022 Macaron Magic All rights reserved" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/components/DemoBanner.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const DemoBanner = () => { 4 | return ( 5 |
6 | 7 | This is a demo store - no orders will be accepted or delivered 8 | 9 |
10 | ); 11 | }; 12 | 13 | export default DemoBanner; 14 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/guidance - converting components/snippets for converting pages.txt: -------------------------------------------------------------------------------- 1 | import { useTranslation } from "next-i18next"; 2 | 3 | 4 | 5 | const { t } = useTranslation("demobanner"); 6 | 7 | 8 | 9 | const Home = () => { 10 | const { t } = useTranslation("home"); 11 | 12 | return ( 13 | <> 14 | ... 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "footer": { 3 | "delivery": "Levering", 4 | "privacy": "Privacy", 5 | "terms": "Verkoopvoorwaarden", 6 | "contact": "Neem contact met ons op", 7 | "email": "Contact: hello@macaron-magic.com", 8 | "copyright": "2022 Macaron Magic Alle rechten voorbehouden" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding stripe/lib/getStripe.js: -------------------------------------------------------------------------------- 1 | import { loadStripe } from "@stripe/stripe-js"; 2 | 3 | let stripePromise; 4 | 5 | const getStripe = () => { 6 | if (!stripePromise) { 7 | stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY); 8 | } 9 | 10 | return stripePromise; 11 | }; 12 | 13 | export default getStripe; 14 | -------------------------------------------------------------------------------- /Ch8_Code_Nextjs_eCommerce/Ch8_Code_Nextjs_eCommerce/overriding default settings.txt: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /Ch1_Code_Nextjs_eCommerce/Ch1_Code_Nextjs_eCommerce/devDependencies for site.txt: -------------------------------------------------------------------------------- 1 | "devDependencies": { 2 | "eslint": "^8.10.0", 3 | "eslint-config-airbnb": "^19.0.4", 4 | "eslint-config-next": "^12.1.0", 5 | "eslint-plugin-import": "^2.25.4", 6 | "eslint-plugin-jsx-a11y": "^6.5.1", 7 | "eslint-plugin-react": "^7.29.3", 8 | "eslint-plugin-react-hooks": "^4.3.0", 9 | "sass": "^1.57.0" 10 | } 11 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/refactoring the cart/Cart/EmptyCart.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { AiOutlineShopping } from "react-icons/ai"; 3 | 4 | const EmptyCart = ({ children }) => { 5 | return ( 6 |
7 | 8 |

Your shopping bag is empty

9 | {children} 10 |
11 | ); 12 | }; 13 | 14 | export default EmptyCart; 15 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/en/perfect.json: -------------------------------------------------------------------------------- 1 | { 2 | "perfect": { 3 | "for": "PERFECT FOR", 4 | "special": "SPECIAL OCCASIONS", 5 | "text": "Share the love and give every guest a little explosion of sweetness with our show stopping macaron towers. Perfect for weddings, anniversaries and parties. You could even add a touch of luxury to party bags and wedding favors with these perfect bite sized treats." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Ch12_Code_Nextjs_eCommerce/Ch12_Code_Nextjs_eCommerce/installing next-auth/src/pages/api/auth/[...nextauth].js: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | import GitHubProvider from "next-auth/providers/github"; 3 | 4 | const options = { 5 | providers: [ 6 | GitHubProvider({ 7 | clientId: process.env.GITHUB_ID, 8 | clientSecret: process.env.GITHUB_SECRET, 9 | }), 10 | ], 11 | }; 12 | 13 | export default (req, res) => NextAuth(req, res, options); 14 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/perfect.json: -------------------------------------------------------------------------------- 1 | { 2 | "perfect": { 3 | "for": "PERFECT VOOR", 4 | "special": "SPECIALE GELEGENHEDEN", 5 | "text": "Deel de liefde en geef elke gast een kleine explosie van zoetheid met onze spectaculaire macarontorens. Perfect voor bruiloften, jubilea en feesten. Je kunt zelfs een vleugje luxe toevoegen aan feesttassen en huwelijksgunsten met deze perfecte hapklare traktaties." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/updating a component file/snippets for updating a component file.txt: -------------------------------------------------------------------------------- 1 | import { useTranslation } from "next-i18next"; 2 | 3 | 4 | 5 | const DemoBanner = () => { 6 | const { t } = useTranslation("demobanner") 7 | 8 | 9 | 10 | {t("demo")} 11 | 12 | 13 | 14 | 15 | return { 16 | props: { 17 | ...(await serverSideTranslations(locale, [ 18 | "home", 19 | "demobanner", 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Ch1_Code_Nextjs_eCommerce/Ch1_Code_Nextjs_eCommerce/dependencies for site.txt: -------------------------------------------------------------------------------- 1 | "dependencies": { 2 | "@sanity/client": "^3.4.1", 3 | "@sanity/image-url": "^1.0.1", 4 | "@stripe/stripe-js": "^1.25.0", 5 | "canvas-confetti": "^1.5.1", 6 | "next": "12.1.0", 7 | "next-sanity-image": "^3.2.1", 8 | "react": "17.0.2", 9 | "react-dom": "17.0.2", 10 | "react-hot-toast": "^2.2.0", 11 | "react-icons": "^4.7.1", 12 | "react-tabs": "^4.2.1", 13 | "stripe": "^8.209.0" 14 | }, 15 | -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/adding products/ingredients.txt: -------------------------------------------------------------------------------- 1 | Ground Almonds (contains nuts) , Icing Sugar, Free Range Egg Whites (contains Eggs), Sugar, Milk Chocolate, (Sugar, Cocoa Butter, High Fat Milk Powder, Cocoa Mass, Whole Milk Powder, Skimmed Milk Powder, Lactose (Milk), Emulsifier: Lecithins (Soya); Vanilla Extract), Double Cream (contains Milk), Orange Extract, Colour E110 may have an adverse effect on activity and attention in children Please note: product may contain allergens - if in doubt, please ask. -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/favicon/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/favicon/android-chrome-384x384.png", 12 | "sizes": "384x384", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/configuring Sanity/client.js: -------------------------------------------------------------------------------- 1 | import sanityClient from "@sanity/client"; 2 | import imageUrlBuilder from "@sanity/image-url"; 3 | 4 | export const client = sanityClient({ 5 | projectId: 6 | dataset: , 7 | apiVersion: "2022-11-27", 8 | useCdn: true, 9 | token: process.env.NEXT_PUBLIC_SANITY_TOKEN, 10 | }); 11 | 12 | const builder = imageUrlBuilder(client); 13 | 14 | export const urlFor = (source) => builder.image(source); 15 | -------------------------------------------------------------------------------- /Ch10_Code_Nextjs_eCommerce/Ch10_Code_Nextjs_eCommerce/cypress tests/cypress tests pipeline file.txt: -------------------------------------------------------------------------------- 1 | name: Cypress Tests 2 | 3 | on: push 4 | 5 | jobs: 6 | cypress-run: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v3 11 | # Install NPM dependencies, cache them correctly 12 | # and run all Cypress tests 13 | - name: Cypress run 14 | uses: cypress-io/github-action@v5 15 | with: 16 | build: npm run build 17 | start: npm start 18 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding stripe/lib/client.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@sanity/client"; 2 | import imageUrlBuilder from "@sanity/image-url"; 3 | 4 | export const client = createClient({ 5 | projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID, 6 | dataset: "production", 7 | apiVersion: "2022-11-27", 8 | useCdn: true, 9 | token: process.env.NEXT_PUBLIC_SANITY_TOKEN, 10 | }); 11 | 12 | const builder = imageUrlBuilder(client); 13 | 14 | export const urlFor = (source) => builder.image(source); 15 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/updated star review/styles.css.txt: -------------------------------------------------------------------------------- 1 | /* UPDATED STAR COUNT */ 2 | .star-rating button { 3 | background-color: transparent; 4 | border: none; 5 | outline: none; 6 | cursor: pointer; 7 | } 8 | 9 | .star { 10 | width: 16px; 11 | height: 16px; 12 | display: flex; 13 | } 14 | 15 | .on { 16 | color: #f02d34; 17 | fill: #f02d34; 18 | } 19 | 20 | .off { 21 | color: #d3d3d3; 22 | fill: #ffffff; 23 | } 24 | 25 | .on > path { 26 | color: #f02d34; 27 | fill: #f02d34; 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apress Source Code 2 | 3 | This repository accompanies [*Practical Next.js for E-Commerce*](https://link.springer.com/book/10.1007/978-1-4842-9612-7) by Alex Libby (Apress, 2023). 4 | 5 | [comment]: #cover 6 | ![Cover image](978-1-4842-9611-0.jpg) 7 | 8 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 9 | 10 | ## Releases 11 | 12 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 13 | 14 | ## Contributions 15 | 16 | See the file Contributing.md for more information on how you can contribute to this repository. -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/cypress/e2e/app.cy.js: -------------------------------------------------------------------------------- 1 | describe("Navigation", () => { 2 | it("should navigate to the about page", () => { 3 | // Start from the index page 4 | cy.visit("http://localhost:3000/"); 5 | 6 | // Find a link with an href attribute containing "about" and click it 7 | cy.get('a[href*="about"]').click(); 8 | 9 | // The new url should include "/about" 10 | cy.url().should("include", "/about"); 11 | 12 | // The new page should contain a div with "About Macaron Magic" 13 | cy.get("div.about-us").contains("About Macaron Magic"); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /Ch12_Code_Nextjs_eCommerce/Ch12_Code_Nextjs_eCommerce/adding login options/styles for globals.css.txt: -------------------------------------------------------------------------------- 1 | /* CHANGES FOR AUTHENTICATION */ 2 | .demo-banner-container { 3 | display: flex; 4 | justify-content: space-evenly; 5 | } 6 | 7 | .demo-banner-container > span:nth-child(2) { 8 | display: flex; 9 | } 10 | 11 | .demo-banner-container > span:nth-child(2) > img { 12 | width: 20px; 13 | height: 20px; 14 | margin-right: 5px; 15 | } 16 | 17 | .demo-banner-container > span:nth-child(2) > p { 18 | margin-right: 15px; 19 | } 20 | 21 | .demo-banner-container > span:nth-child(2) > a { 22 | border-left: 2px solid #ffffff; 23 | padding-left: 15px; 24 | } 25 | -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/cypress/e2e/starRating.cy.js: -------------------------------------------------------------------------------- 1 | describe("Star Rating", () => { 2 | it("should navigate to a product page", () => { 3 | cy.visit("http://localhost:3000/product/chocolate-orange"); 4 | 5 | // confirm that we have number of counts of ratings to right of stars 6 | cy.get("div.reviews > p").contains("(20)"); 7 | 8 | // div should contain 5 stars 9 | cy.get("div.star-rating > button").should("have.length", 5); 10 | 11 | // click on star 3 from left 12 | cy.get("div.star-rating > button").eq(2).click(); 13 | 14 | // should have 3 red stars, 2 grey 15 | cy.get("div.star-rating > button").eq(2).should("have.class", "on"); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Apress Source Code 2 | 3 | Copyright for Apress source code belongs to the author. However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author and other readers. 4 | 5 | ## How to Contribute 6 | 7 | 1. Make sure you have a GitHub account. 8 | 2. Fork the repository for the relevant book. 9 | 3. Create a new branch on which to make your change, e.g. 10 | `git checkout -b my_code_contribution` 11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. 12 | 5. Submit a pull request. 13 | 14 | Thank you for your contribution! -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/cypress/e2e/component/demobanner.cy.js: -------------------------------------------------------------------------------- 1 | import DemoBanner from "../../src/components/DemoBanner.jsx"; 2 | 3 | const mockText = 4 | "This is a demo store - no orders will be accepted or delivered"; 5 | 6 | describe("", () => { 7 | it("should render and display expected content", () => { 8 | // Mount the DemoBanner component 9 | cy.mount(); 10 | 11 | // The component should contain an element of "footer" 12 | cy.get("div.demo-banner-container").should("be.visible"); 13 | 14 | // Validate that the correct text is displayed in the banner 15 | cy.get("div.demo-banner-container").should("have.text", mockText); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/updating a page file/snippets for updating a page file.txt: -------------------------------------------------------------------------------- 1 | import { useTranslation } from "next-i18next"; 2 | import { serverSideTranslations } from "next-i18next/serverSideTranslations"; 3 | 4 | 5 | const Home = () => { 6 | const { t } = useTranslation("home"); 7 | 8 | return ( 9 | 10 | 11 | 12 | ...(await serverSideTranslations(locale, ["common", "test", "home"])), 13 | 14 | 15 | 16 | 17 | 18 | {t("shop-now")} 19 | 20 | 21 | 22 | 23 |
24 |

{t("welcome.para1")}

25 |

{t("welcome.para2")}

26 |

{t("welcome.para3")}

27 |
28 | 29 | -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "es5", 6 | "dom" 7 | ], 8 | "types": [ 9 | "cypress", 10 | "node" 11 | ], 12 | "baseUrl": "./", 13 | "allowJs": true, 14 | "skipLibCheck": true, 15 | "strict": false, 16 | "forceConsistentCasingInFileNames": true, 17 | "noEmit": true, 18 | "incremental": true, 19 | "esModuleInterop": true, 20 | "module": "esnext", 21 | "moduleResolution": "node", 22 | "resolveJsonModule": true, 23 | "isolatedModules": true, 24 | "jsx": "preserve" 25 | }, 26 | "include": [ 27 | "**/*.ts", 28 | "**/*.js" 29 | ], 30 | "exclude": [ 31 | "node_modules" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into dutch/nl-NL/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "luxury": "Luxe Macarons met de hand gemaakt", 3 | "shop-now": "Winkel nu", 4 | "welcome": { 5 | "para1": "Welkom bij Macaron Magic - de thuisbasis van heerlijk smakende, luxe macarons, met de hand gemaakt hier in onze werkplaats in het Peak District.", 6 | "para2": "We hebben zorgvuldig een selectie van smaken uitgekozen voor uw plezier, klaar om van te genieten - stel u eens voor... dat u in elke smaak bijt, waar het praktisch smelt in uw mond... jammie!", 7 | "para3": "Blader om te beginnen naar onze winkel waar u het volledige beschikbare assortiment ziet - we zullen er in de loop van de tijd meer aan toevoegen. Als u vragen heeft, laat het ons dan weten - onze contactgegevens staan onderaan deze pagina." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch8_Code_Nextjs_eCommerce/Ch8_Code_Nextjs_eCommerce/next-seo.config file.txt: -------------------------------------------------------------------------------- 1 | export default { 2 | openGraph: { 3 | type: "website", 4 | locale: "en_IE", 5 | url: "https://www.url.ie/", 6 | siteName: "SiteName", 7 | }, 8 | twitter: { 9 | handle: "@handle", 10 | site: "@site", 11 | cardType: "summary_large_image", 12 | }, 13 | }; 14 | 15 | 16 | 17 | 32 | -------------------------------------------------------------------------------- /Ch12_Code_Nextjs_eCommerce/Ch12_Code_Nextjs_eCommerce/adding account page/styles for global.css.txt: -------------------------------------------------------------------------------- 1 | /* CHANGES FOR YOUR ACCOUNT */ 2 | .your-account { 3 | width: 70%; 4 | margin: 0 auto; 5 | text-align: center; 6 | } 7 | 8 | .your-account p { 9 | margin: 40px 0 0 0; 10 | font-family: "Oswald", sans-serif; 11 | font-size: 42px; 12 | } 13 | 14 | .display-profile { 15 | width: 60%; 16 | margin: 20px auto 50px auto; 17 | text-align: center; 18 | } 19 | 20 | .display-profile div:nth-child(1) { 21 | display: flex; 22 | } 23 | 24 | .profile-image { 25 | margin-right: 20px; 26 | } 27 | 28 | .profile-image img { 29 | width: 100px; 30 | height: 100px; 31 | } 32 | 33 | .profile-details { 34 | text-align: left; 35 | border-left: 1px solid #dcdcdc; 36 | display: block; 37 | padding-left: 15px; 38 | min-height: 200px; 39 | } -------------------------------------------------------------------------------- /Ch6_Code_Nextjs_eCommerce/Ch6_Code_Nextjs_eCommerce/mobile.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 414px) { 2 | html { 3 | display: flex; 4 | width: 100%; 5 | } 6 | 7 | footer > div:nth-child(1) { 8 | padding: 30px; 9 | } 10 | 11 | .product-detail-container { 12 | margin: 60px; 13 | } 14 | 15 | .cart-wrapper { 16 | top: 190px; 17 | width: 100%; 18 | min-height: 2000px; 19 | } 20 | 21 | @keyframes movemobile { 22 | 0% { 23 | transform: translateY(-40%); 24 | } 25 | 100% { 26 | transform: translateY(0%); 27 | } 28 | } 29 | 30 | .cart-container { 31 | min-height: 500px; 32 | width: 100%; 33 | animation-name: movemobile; 34 | animation-duration: 2s; 35 | animation-iteration-count: 1; 36 | animation-fill-mode: forwards; 37 | display: flex; 38 | flex-direction: column; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/components/Newsletter.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import newsletter from "../../public/images/newsletter.jpg"; 4 | 5 | const Newsletter = () => ( 6 |
7 |
8 | Join our Newsletter 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | newsletter 17 | 18 |
19 | ); 20 | 21 | export default Newsletter; 22 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/components/PerfectBanner.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import perfect from "../../public/images/perfect.jpg"; 4 | 5 | const PerfectBanner = () => ( 6 |
7 |
8 | perfect occasion 9 |
10 |
11 |

Perfect for

12 |

special occasions

13 |

14 | Share the love and give every guest a little explosion of sweetness with 15 | our show stopping macaron towers. Perfect for weddings, anniversaries 16 | and parties. You could even add a touch of luxury to party bags and 17 | wedding favors with these perfect bite sized treats. 18 |

19 |
20 |
21 | ); 22 | 23 | export default PerfectBanner; 24 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/guidance - converting pages/snippets for converting pages.txt: -------------------------------------------------------------------------------- 1 | import { useTranslation } from "next-i18next"; 2 | import { serverSideTranslations } from "next-i18next/serverSideTranslations"; 3 | 4 | 5 | const { t } = useTranslation("home"); 6 | 7 | 8 | 9 | const Home = () => { 10 | const { t } = useTranslation("home"); 11 | 12 | return ( 13 | <> 14 | ... 15 | 16 | ); 17 | }; 18 | 19 | 20 | 21 | export async function getServerSideProps({ locale }) { 22 | return { 23 | props: { 24 | ...(await serverSideTranslations(locale, [ 25 | "home", 26 | "demobanner", 27 | "navbar", 28 | "perfect", 29 | "newsletter", 30 | "footer", 31 | "minicart", 32 | "emptycart", 33 | ])), 34 | // Will be passed to the page component as props 35 | }, 36 | }; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/cancel page/canceled.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import Link from "next/link"; 3 | 4 | import { useStateContext } from "../context/StateContext"; 5 | 6 | const Canceled = () => { 7 | const { setCartItems, setTotalPrice, setTotalQuantities } = useStateContext(); 8 | 9 | useEffect(() => { 10 | localStorage.clear(); 11 | setCartItems([]); 12 | setTotalPrice(0); 13 | setTotalQuantities(0); 14 | }, []); 15 | 16 | return ( 17 |
18 |
19 |

Your order is canceled - you have not been charged.

20 | 21 | 24 | 25 |
26 |
27 | ); 28 | }; 29 | 30 | export default Canceled; 31 | -------------------------------------------------------------------------------- /Ch6_Code_Nextjs_eCommerce/Ch6_Code_Nextjs_eCommerce/tablet.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 414px) and (max-width: 820px) { 2 | body { 3 | width: 100%; 4 | display: flex; 5 | } 6 | 7 | .product-detail-container { 8 | margin: 0; 9 | padding: 40px; 10 | } 11 | 12 | footer > div:nth-child(1) { 13 | padding: 30px; 14 | } 15 | 16 | .cart-wrapper { 17 | top: 190px; 18 | width: 100%; 19 | min-height: 2000px; 20 | } 21 | 22 | @keyframes movemobile { 23 | 0% { 24 | transform: translateY(-40%); 25 | } 26 | 100% { 27 | transform: translateY(0%); 28 | } 29 | } 30 | 31 | .cart-container { 32 | height: 0; 33 | min-height: 500px; 34 | width: 100%; 35 | animation-name: movemobile; 36 | animation-duration: 2s; 37 | animation-iteration-count: 1; 38 | animation-fill-mode: forwards; 39 | display: flex; 40 | flex-direction: column; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/building shop/shop.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { client } from "../../lib/client"; 3 | 4 | import { Product } from "../components"; 5 | 6 | const Shop = ({ products }) => ( 7 |
8 |
9 |

Shop

10 |

Browse for products

11 |
12 |
13 | {products?.map((product) => ( 14 | 15 | ))} 16 |
17 |
18 | ); 19 | 20 | export const getServerSideProps = async ({ req, res }) => { 21 | const query = '*[_type == "product"]'; 22 | const products = await client.fetch(query); 23 | 24 | res.setHeader( 25 | "Cache-Control", 26 | "public, s-maxage=10, stale-while-revalidate=59" 27 | ); 28 | 29 | return { 30 | props: { products }, 31 | }; 32 | }; 33 | 34 | export default Shop; 35 | -------------------------------------------------------------------------------- /Ch8_Code_Nextjs_eCommerce/Ch8_Code_Nextjs_eCommerce/adding dynamic names.txt: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { NextSeo } from "next-seo"; 3 | 4 | 5 | function toTitleCase(str) { 6 | return str.replace(/\w\S*/g, function (txt) { 7 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 8 | }); 9 | } 10 | 11 | 12 | const Product = ({ product: { image, name, slug, price } }) => { 13 | const { asPath } = useRouter(); 14 | 15 | let seoProductSlug = asPath.split("/")[2]; 16 | let seoProductName = ""; 17 | 18 | if (seoProductSlug != null) { 19 | seoProductName = seoProductSlug.replace("-", " "); 20 | 21 | if (seoProductSlug === slug.current) { 22 | seoProductName = toTitleCase(seoProductSlug.replace("-", " ")); 23 | } 24 | } 25 | 26 | 27 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/creating individual page/Info.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; 3 | 4 | const Info = ({ ingredients, weight, delivery }) => ( 5 | 6 | 7 | Description 8 | Additional Information 9 | 10 | 11 | 12 |

Ingredients

13 |

{ingredients}

14 |
15 | 16 |

Additional Information

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 |
Weight:{weight}
Delivery: 26 |

{delivery}

27 |
31 |
32 |
33 | ); 34 | 35 | export default Info; 36 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/updated star review/StarRating.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | const StarRating = () => { 4 | const [rating, setRating] = useState(0); 5 | const [hover, setHover] = useState(0); 6 | 7 | return ( 8 |
9 | {[...Array(5)].map((star, index) => { 10 | index += 1; 11 | return ( 12 | 26 | ); 27 | })} 28 |
29 | ); 30 | }; 31 | 32 | export default StarRating; 33 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/changes for adding locale support/snippets for adding locale support.txt: -------------------------------------------------------------------------------- 1 | i18n: { 2 | defaultLocale: "en", 3 | locales: ["en", "nl-NL"], 4 | }, 5 | 6 | 7 | import { useRouter } from "next/router"; 8 | 9 | 10 |

Hello world: {locale}

11 | 12 | 13 | const handleLocaleChange = (event) => { 14 | const value = event.target.value; 15 | 16 | router.push(router.route, router.asPath, { 17 | locale: value, 18 | }); 19 | }; 20 | 21 | 22 | 23 | Home 24 | 25 | 26 | 30 | About 31 | 32 | 33 | 37 | 38 | -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/cypress/e2e/component/info.cy.js: -------------------------------------------------------------------------------- 1 | import Info from "../../src/components/Info.jsx"; 2 | 3 | const mockTextFirstTab = "Description"; 4 | const mockTextSecondTab = "Additional Information"; 5 | 6 | describe("", () => { 7 | it("should render and display expected content", () => { 8 | // Mount the Info component 9 | cy.mount(); 10 | 11 | // The component should contain an element of "footer" 12 | cy.get("div.react-tabs").should("be.visible"); 13 | 14 | // The component should contain 2 tabs 15 | cy.get("ul[role='tablist'] > li").should("have.length", 2); 16 | 17 | // Validate that four links with the expected URLs are present 18 | cy.get("ul[role='tablist'] > li:nth-child(1)").should( 19 | "have.text", 20 | mockTextFirstTab 21 | ); 22 | 23 | // Validate that four links with the expected URLs are present 24 | cy.get("ul[role='tablist'] > li:nth-child(2)").should( 25 | "have.text", 26 | mockTextSecondTab 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /Ch6_Code_Nextjs_eCommerce/Ch6_Code_Nextjs_eCommerce/portable.css: -------------------------------------------------------------------------------- 1 | @keyframes movemobile { 2 | 0% { 3 | transform: translateY(-40%); 4 | } 5 | 100% { 6 | transform: translateY(0%); 7 | } 8 | } 9 | 10 | @media (max-width: 820px) { 11 | html { 12 | display: flex; 13 | width: 100%; 14 | } 15 | 16 | footer > div:nth-child(1) { 17 | padding: 30px; 18 | } 19 | 20 | .cart-wrapper { 21 | top: 190px; 22 | width: 100%; 23 | min-height: 2000px; 24 | } 25 | 26 | .cart-container { 27 | min-height: 500px; 28 | width: 100%; 29 | animation-name: movemobile; 30 | animation-duration: 2s; 31 | animation-iteration-count: 1; 32 | animation-fill-mode: forwards; 33 | display: flex; 34 | flex-direction: column; 35 | } 36 | } 37 | 38 | @media (max-width: 414px) { 39 | .product-detail-container { 40 | margin: 60px; 41 | } 42 | } 43 | 44 | @media (min-width: 414px) and (max-width: 820px) { 45 | .product-detail-container { 46 | margin: 0; 47 | padding: 40px; 48 | } 49 | 50 | .cart-container { 51 | height: 0; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/pages/about.js: -------------------------------------------------------------------------------- 1 | const About = () => ( 2 |
3 |

About Macaron Magic

4 | 5 |

6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vulputate 7 | justo ac tellus egestas dictum. Aenean vestibulum diam eu risus cursus 8 | mollis. Nulla dapibus ante in felis vulputate mattis. Mauris sed lacus 9 | eget sapien rutrum interdum. Proin a semper magna. Aliquam eget purus 10 | fringilla, rutrum augue at, porta sem. 11 |

12 | 13 |

14 | Aenean aliquam lectus tellus, sed venenatis nulla pretium ac. Vivamus 15 | cursus purus quam, eu placerat est rutrum quis. Nunc sit amet urna sed 16 | libero auctor imperdiet nec consequat sem. Quisque mauris est, fermentum 17 | in tristique quis, tincidunt a odio. Fusce porttitor eu est interdum 18 | consectetur. Ut aliquam semper dui, vulputate hendrerit sapien interdum 19 | sit amet. Nam vitae nulla et lorem ornare auctor. Nulla facilisi. Nam sed 20 | sollicitudin leo. 21 |

22 |
23 | ); 24 | 25 | export default About; 26 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/components/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { AiOutlineShopping } from "react-icons/ai"; 4 | import { Cart } from "./"; 5 | 6 | import { useStateContext } from "../../context/StateContext"; 7 | 8 | const NavBar = () => { 9 | const { showCart, setShowCart, totalQuantities } = useStateContext(); 10 | 11 | return ( 12 |
13 |
14 | Mangez Macaron 15 | 16 |
17 | Home 18 | About 19 | Shop 20 | Contact 21 | 29 | {showCart && } 30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default NavBar; 37 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding context/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { AiOutlineShopping } from "react-icons/ai"; 4 | import { Cart } from "./"; 5 | 6 | import { useStateContext } from "../../context/StateContext"; 7 | 8 | const NavBar = () => { 9 | const { showCart, setShowCart, totalQuantities } = useStateContext(); 10 | 11 | return ( 12 |
13 |
14 | Mangez Macaron 15 | 16 |
17 | Home 18 | About 19 | Shop 20 | Contact 21 | 29 | {showCart && } 30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default NavBar; 37 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/constructing the cart/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { AiOutlineShopping } from "react-icons/ai"; 4 | import { Cart } from "./"; 5 | 6 | import { useStateContext } from "../../context/StateContext"; 7 | 8 | const NavBar = () => { 9 | const { showCart, setShowCart, totalQuantities } = useStateContext(); 10 | 11 | return ( 12 |
13 |
14 | Mangez Macaron 15 | 16 |
17 | Home 18 | About 19 | Shop 20 | Contact 21 | 29 | {showCart && } 30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default NavBar; 37 | -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/alternative method - css modules/Footer/Footer.module.css: -------------------------------------------------------------------------------- 1 | .footerContainer { 2 | color: #caa34d; 3 | background-color: #000000; 4 | text-align: center; 5 | margin-top: 20px; 6 | padding: 30px 10px; 7 | font-weight: 700; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | gap: 10px; 12 | justify-content: center; 13 | border-bottom: 1px solid #c7c7c7; 14 | } 15 | 16 | .footerContainer .footerContent { 17 | display: flex; 18 | margin-left: auto; 19 | margin-right: auto; 20 | width: 800px; 21 | } 22 | 23 | .footerContainer .footerContent div { 24 | flex-direction: column; 25 | display: flex; 26 | text-align: left; 27 | min-width: 250px; 28 | margin-right: 20px; 29 | } 30 | 31 | .footerContainer .footerContent div:last-child { 32 | margin-right: 0; 33 | } 34 | 35 | .footerContainer .mini-cart-bottom { 36 | width: 100%; 37 | } 38 | 39 | .footerContainer .icons { 40 | font-size: 30px; 41 | display: flex; 42 | gap: 10px; 43 | } 44 | 45 | .footerContainer .iconContainer { 46 | width: 800px; 47 | display: flex; 48 | justify-content: space-between; 49 | } 50 | 51 | .copyright { 52 | margin: 10px; 53 | } 54 | -------------------------------------------------------------------------------- /Ch12_Code_Nextjs_eCommerce/Ch12_Code_Nextjs_eCommerce/adding account page/DisplayProfile.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSession, signIn } from "next-auth/react"; 3 | 4 | function DisplayProfile() { 5 | const { data: session } = useSession(); 6 | 7 | const handleSignin = (e) => { 8 | e.preventDefault(); 9 | signIn(); 10 | }; 11 | 12 | return ( 13 | <> 14 |
15 | {session && ( 16 | <> 17 |
18 |
19 | 20 |
21 |
22 |

{session.user.name}

23 |

Email: {session.user.email}

24 |
25 |
26 | 27 | )} 28 | 29 | {!session && ( 30 | <> 31 | 32 | Sorry - this page is restricted to members only. Please log in to 33 | view your details. 34 | 35 | 36 | )} 37 |
38 | 39 | ); 40 | } 41 | 42 | export default DisplayProfile; 43 | -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/cypress/e2e/addingProducts.cy.js: -------------------------------------------------------------------------------- 1 | describe("Adding Products", () => { 2 | it("should navigate to the about page", () => { 3 | // Start from the index page 4 | cy.visit("http://localhost:3000/"); 5 | 6 | // Click on the Shop link 7 | cy.get("div.navbar > a:nth-child(3)").click(true); 8 | 9 | // The new url should include "/shop" 10 | cy.url().should("include", "/shop"); 11 | 12 | // Click on a product - Spiced Pumpkin 13 | cy.get('a[href*="spiced-pumpkin"]').click(); 14 | 15 | // Increase product quantity by 2 16 | cy.get("span.plus").click(); 17 | 18 | // Click on Add to Cart 19 | cy.get("button.add-to-cart").click(); 20 | 21 | // Verify that cart icon shows 2 22 | cy.get("span.cart-item-qty").contains(2); 23 | 24 | // Open cart - verify details 25 | cy.get("span.cart-item-qty").click(); 26 | cy.get("span.cart-num-items").contains("2 items"); 27 | 28 | // Verify mini cart shows relevant details 29 | cy.get("span.cart-num-items").contains("(2 items)"); 30 | cy.get("span.item-desc > span").contains("Spiced Pumpkin"); 31 | cy.get("span.totals").contains("$18.50"); 32 | cy.get("div.total > h3").contains("$37.00"); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding stripe/lib/utils.js: -------------------------------------------------------------------------------- 1 | import confetti from "canvas-confetti"; 2 | 3 | export const runFireworks = () => { 4 | var duration = 5 * 1000; 5 | var animationEnd = Date.now() + duration; 6 | var defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 }; 7 | 8 | function randomInRange(min, max) { 9 | return Math.random() * (max - min) + min; 10 | } 11 | 12 | var interval = setInterval(function () { 13 | var timeLeft = animationEnd - Date.now(); 14 | 15 | if (timeLeft <= 0) { 16 | return clearInterval(interval); 17 | } 18 | 19 | var particleCount = 50 * (timeLeft / duration); 20 | // since particles fall down, start a bit higher than random 21 | confetti( 22 | Object.assign({}, defaults, { 23 | particleCount, 24 | origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 }, 25 | }) 26 | ); 27 | confetti( 28 | Object.assign({}, defaults, { 29 | particleCount, 30 | origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 }, 31 | }) 32 | ); 33 | }, 250); 34 | }; 35 | 36 | export const eUSLocale = (x) => { 37 | return x.toLocaleString("en-US", { 38 | maximumFractionDigits: 2, 39 | minimumFractionDigits: 2, 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /Ch12_Code_Nextjs_eCommerce/Ch12_Code_Nextjs_eCommerce/adding login options/DemoBanner.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSession, signIn, signOut } from "next-auth/react"; 3 | 4 | const DemoBanner = () => { 5 | const { data: session } = useSession(); 6 | 7 | const handleSignin = (e) => { 8 | e.preventDefault(); 9 | signIn(); 10 | }; 11 | const handleSignout = (e) => { 12 | e.preventDefault(); 13 | signOut(); 14 | }; 15 | 16 | return ( 17 |
18 | 19 | This is a demo store - no orders will be accepted or delivered 20 | 21 | 22 | 23 | {session && ( 24 | <> 25 | 26 |

Welcome, {session.user.name ?? session.user.email}

27 | 28 | Sign out 29 | 30 | 31 | )} 32 | 33 | {!session && ( 34 | <> 35 |

Welcome

36 | 37 | Sign in 38 | 39 | 40 | )} 41 |
42 |
43 | ); 44 | }; 45 | 46 | export default DemoBanner; 47 | -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/configuring Sanity/product.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'product', 3 | title: 'Product', 4 | type: 'document', 5 | fields: [ 6 | { 7 | name: 'image', 8 | title: 'Image', 9 | type: 'array', 10 | of: [{type: 'image'}], 11 | options: { 12 | hotspot: true, 13 | }, 14 | }, 15 | { 16 | name: 'name', 17 | title: 'Name', 18 | type: 'string', 19 | }, 20 | { 21 | name: 'slug', 22 | title: 'Slug', 23 | type: 'slug', 24 | options: { 25 | source: 'name', 26 | maxLength: 90, 27 | }, 28 | }, 29 | { 30 | name: 'price', 31 | title: 'Price', 32 | type: 'number', 33 | }, 34 | { 35 | name: 'details', 36 | title: 'Details', 37 | type: 'string', 38 | }, 39 | { 40 | name: 'sku', 41 | title: 'SKU', 42 | type: 'string', 43 | }, 44 | { 45 | name: 'ingredients', 46 | title: 'Ingredients', 47 | type: 'string', 48 | }, 49 | { 50 | name: 'weight', 51 | title: 'Weight', 52 | type: 'string', 53 | }, 54 | { 55 | name: 'delivery', 56 | title: 'Delivery', 57 | type: 'string', 58 | }, 59 | ], 60 | } 61 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/pages/_app.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Toaster } from "react-hot-toast"; 3 | import { Layout } from "../components"; 4 | import { DefaultSeo } from "next-seo"; 5 | 6 | import "../styles/globals.css"; 7 | import "../styles/index.scss"; /* main styles */ 8 | // import "../styles/overrides/mobile.css"; /* styles for cell phones */ 9 | // import "../styles/overrides/tablet.css"; /* styles for cell phones */ 10 | import "../styles/overrides/portable.css"; 11 | 12 | import { StateContext } from "../../context/StateContext"; 13 | 14 | function MyApp({ Component, pageProps }) { 15 | return ( 16 | <> 17 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | 42 | export default MyApp; 43 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding context/_app.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Toaster } from "react-hot-toast"; 3 | import { Layout } from "../components"; 4 | import { DefaultSeo } from "next-seo"; 5 | 6 | import "../styles/globals.css"; 7 | import "../styles/index.scss"; /* main styles */ 8 | // import "../styles/overrides/mobile.css"; /* styles for cell phones */ 9 | // import "../styles/overrides/tablet.css"; /* styles for cell phones */ 10 | import "../styles/overrides/portable.css"; 11 | 12 | import { StateContext } from "../../context/StateContext"; 13 | 14 | function MyApp({ Component, pageProps }) { 15 | return ( 16 | <> 17 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | 42 | export default MyApp; 43 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import PaymentIcons from "../PaymentIcons"; 4 | import MiniCart from "../MiniCart"; 5 | 6 | import { useStateContext } from "../../../context/StateContext"; 7 | 8 | import { AiFillInstagram, AiOutlineTwitter } from "react-icons/ai"; 9 | 10 | import styles from "./Footer.module.css"; 11 | 12 | const Footer = () => { 13 | const { showCart } = useStateContext(); 14 | 15 | return ( 16 | <> 17 |
18 |
19 |
20 | Delivery 21 | Privacy 22 | Terms and Conditions of Sale 23 | Contact Us 24 |
25 |
Contact: hello@macaronmagic.com
26 | 27 | 28 |
29 |
30 | 31 |
32 | 33 | 34 |
35 |
36 |
37 |

2022 Macaron Magic All rights reserved

38 | 39 | ); 40 | }; 41 | 42 | export default Footer; 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Freeware License, some rights reserved 2 | 3 | Copyright (c) 2023 Alex Libby 4 | 5 | Permission is hereby granted, free of charge, to anyone obtaining a copy 6 | of this software and associated documentation files (the "Software"), 7 | to work with the Software within the limits of freeware distribution and fair use. 8 | This includes the rights to use, copy, and modify the Software for personal use. 9 | Users are also allowed and encouraged to submit corrections and modifications 10 | to the Software for the benefit of other users. 11 | 12 | It is not allowed to reuse, modify, or redistribute the Software for 13 | commercial use in any way, or for a user’s educational materials such as books 14 | or blog articles without prior permission from the copyright holder. 15 | 16 | The above copyright notice and this permission notice need to be included 17 | in all copies or substantial portions of the software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | 28 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding context/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import PaymentIcons from "../PaymentIcons"; 4 | import MiniCart from "../MiniCart"; 5 | 6 | import { useStateContext } from "../../../context/StateContext"; 7 | 8 | import { AiFillInstagram, AiOutlineTwitter } from "react-icons/ai"; 9 | 10 | import styles from "./Footer.module.css"; 11 | 12 | const Footer = () => { 13 | // const { showCart } = useStateContext(); 14 | 15 | return ( 16 | <> 17 |
18 |
19 |
20 | Delivery 21 | Privacy 22 | Terms and Conditions of Sale 23 | Contact Us 24 |
25 |
Contact: hello@macaronmagic.com
26 | 27 | 28 |
29 |
30 | 31 |
32 | 33 | 34 |
35 |
36 |
37 |

2022 Macaron Magic All rights reserved

38 | 39 | ); 40 | }; 41 | 42 | export default Footer; 43 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding stripe/success.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Link from "next/link"; 3 | import { BsBagCheckFill } from "react-icons/bs"; 4 | 5 | import { useStateContext } from "../context/StateContext"; 6 | import { runFireworks } from "../lib/utils"; 7 | 8 | const Success = () => { 9 | const { setItems, setTotalPrice, setTotalQuantities } = useStateContext(); 10 | 11 | useEffect(() => { 12 | localStorage.clear(); 13 | setCartItems([]); 14 | setTotalPrice(0); 15 | setTotalQuantities(0); 16 | runFireworks(); 17 | }, []); 18 | 19 | return ( 20 |
21 |
22 |

23 | 24 |

25 |

Thank you for your order!

26 |

Check your email inbox for the receipt.

27 |

28 | If you have any questions, please email 29 | 30 | hello@macaronmagic.com 31 | 32 |

33 | 34 | 37 | 38 |
39 |
40 | ); 41 | }; 42 | 43 | export default Success; 44 | -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/alternative method - css modules/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import PaymentIcons from "../PaymentIcons"; 4 | import MiniCart from "../MiniCart"; 5 | 6 | import { useStateContext } from "../../../context/StateContext"; 7 | 8 | import { AiFillInstagram, AiOutlineTwitter } from "react-icons/ai"; 9 | 10 | import styles from "./Footer.module.css"; 11 | 12 | const Footer = () => { 13 | // const { showCart } = useStateContext(); 14 | 15 | return ( 16 | <> 17 |
18 |
19 |
20 | Delivery 21 | Privacy 22 | Terms and Conditions of Sale 23 | Contact Us 24 |
25 |
Contact: hello@macaronmagic.com
26 | 27 | 28 |
29 |
30 | 31 |
32 | 33 | 34 |
35 |
36 |
37 |

2022 Macaron Magic All rights reserved

38 | 39 | ); 40 | }; 41 | 42 | export default Footer; 43 | -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/cypress/e2e/removingProducts.cy.js: -------------------------------------------------------------------------------- 1 | describe("Adding Products", () => { 2 | it("should navigate to the about page", () => { 3 | // Start from the index page 4 | cy.visit("http://localhost:3000/"); 5 | 6 | // Click on the Shop link 7 | cy.get("div.navbar > a:nth-child(3)").click(true); 8 | 9 | // The new url should include "/shop" 10 | cy.url().should("include", "/shop"); 11 | 12 | // Click on a product - Spiced Pumpkin 13 | cy.get('a[href*="spiced-pumpkin"]').click(); 14 | 15 | // Increase product quantity by 2 16 | cy.get("span.plus").click(); 17 | 18 | // Click on Add to Cart 19 | cy.get("button.add-to-cart").click(); 20 | 21 | // Verify that cart icon shows 2 22 | cy.get("span.cart-item-qty").contains(2); 23 | 24 | // Open cart to remove item 25 | cy.get("span.cart-item-qty").click(); 26 | 27 | // Remove one item from basket 28 | cy.get("span.minus").eq(0).click(); 29 | 30 | // Close Cart 31 | cy.get("div.cart-wrapper").click(); 32 | 33 | // Verify that selected item shows 1 34 | cy.get("span.cart-num-items").contains("1 items"); 35 | 36 | // Verify mini cart shows relevant details 37 | cy.get("span.cart-num-items").contains("(1 items)"); 38 | cy.get("span.item-desc > span").contains("Spiced Pumpkin"); 39 | cy.get("span.totals").contains("$18.50"); 40 | cy.get("div.total > h3").contains("$18.50"); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/pages/contact.js: -------------------------------------------------------------------------------- 1 | const Contact = () => ( 2 |
3 |

Contact Us

4 |

5 | If you have an enquiry about any of our products, we'd love to hear from 6 | you. 7 |

8 |
9 |

Fields marked with a * are required

10 | 11 |
12 | 19 | * 20 |
21 | 22 | 23 |
24 | 31 | * 32 |
33 | 34 | 35 |
36 | 42 |
43 | 46 |
47 |
48 | ); 49 | 50 | export default Contact; 51 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | // import { client } from "../lib/client"; 4 | import PerfectBanner from "../components/PerfectBanner"; 5 | import Newsletter from "../components/Newsletter"; 6 | 7 | const Home = () => ( 8 |
9 |
10 |
11 | Luxury macarons made by hand 12 | 13 | 14 | Shop Now 15 | 16 | 17 |
18 |
19 | 20 |
21 |

22 | Welcome to Macaron Magic - the home of great-tasting, luxurious 23 | macarons, made by hand here in our workshop in the Peak District. 24 |

25 |

26 | We have carefully chosen a select range of flavors for your delight, 27 | ready for you to enjoy - just imagine...biting into each one, where it 28 | practically melts in your mouth...yum! 29 |

30 |

31 | To start, browse over to our shop where you will see the full range 32 | available - we'll be adding more over time. If you have any questions, 33 | please do let us know - our contact details are at the bottom of this 34 | page. 35 |

36 |
37 | 38 | 39 | 40 |
41 | ); 42 | 43 | /* ADD SERVERSIDE PROPS HERE */ 44 | 45 | export default Home; 46 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/flipping product images/flipping product images.txt: -------------------------------------------------------------------------------- 1 | /* FLIP IMAGES */ 2 | .fliptile { 3 | color: #fff; 4 | position: relative; 5 | overflow: hidden; 6 | margin: 10px; 7 | min-width: 220px; 8 | max-width: 310px; 9 | width: 100%; 10 | color: #000000; 11 | text-align: left; 12 | font-size: 16px; 13 | perspective: 50em; 14 | } 15 | 16 | .fliptile * { 17 | box-sizing: padding-box; 18 | transition: all 0.2s ease-out; 19 | } 20 | 21 | .fliptile img { 22 | max-width: 100%; 23 | vertical-align: top; 24 | } 25 | 26 | .fliptile figcaption { 27 | top: 20px; 28 | left: 20px; 29 | right: 20px; 30 | bottom: 20px; 31 | padding: 20px; 32 | position: absolute; 33 | opacity: 0; 34 | z-index: 1; 35 | transform: translateY(40px); 36 | } 37 | 38 | .fliptile h2 { 39 | margin: 0 0 5px; 40 | } 41 | 42 | .fliptile h2 { 43 | font-weight: 600; 44 | } 45 | 46 | .fliptile:after { 47 | background-color: rgb(0, 0, 0, 0.1); 48 | position: absolute; 49 | content: ""; 50 | display: block; 51 | top: 20px; 52 | left: 20px; 53 | right: 20px; 54 | bottom: 20px; 55 | transition: all 0.4s ease-in-out; 56 | transform: rotateX(-90deg); 57 | transform-origin: 50% 50%; 58 | opacity: 0; 59 | } 60 | 61 | .fliptile:hover figcaption, 62 | .fliptile.hover figcaption { 63 | transform: translateY(0%); 64 | opacity: 1; 65 | transition-delay: 0.2s; 66 | } 67 | 68 | .fliptile:hover:after, 69 | .fliptile.hover:after { 70 | transform: rotateX(0); 71 | opacity: 0.9; 72 | } 73 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/components/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Head from "next/head"; 3 | 4 | import NavBar from "./NavBar"; 5 | import DemoBanner from "./DemoBanner"; 6 | import Footer from "./Footer"; 7 | 8 | const Layout = ({ children }) => { 9 | return ( 10 | <> 11 | 12 | Macaron Magic | great tasting home-made macarons 13 | 18 | 24 | 30 | 31 | 36 | 37 | 38 | 39 |
40 | 41 | 42 |
43 |
44 |
{children}
45 |
46 |
47 |
48 |
49 | 50 | ); 51 | }; 52 | 53 | export default Layout; 54 | -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/alternative method - css modules/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Head from "next/head"; 3 | 4 | import NavBar from "./NavBar"; 5 | import DemoBanner from "./DemoBanner"; 6 | import Footer from "./Footer/Footer"; 7 | 8 | const Layout = ({ children }) => { 9 | return ( 10 | <> 11 | 12 | Macaron Magic | great tasting home-made macarons 13 | 18 | 24 | 30 | 31 | 36 | 37 | 38 | 39 |
40 | 41 | 42 |
43 |
44 |
{children}
45 |
46 |
47 |
48 |
49 | 50 | ); 51 | }; 52 | 53 | export default Layout; 54 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/adapting slug.js/Adapting slug.js.txt: -------------------------------------------------------------------------------- 1 | import { useTranslation } from "next-i18next"; 2 | import { serverSideTranslations } from "next-i18next/serverSideTranslations"; 3 | 4 | 5 | const { t } = useTranslation("demobanner"); 6 | 7 | 8 | 9 | const Home = () => { 10 | const { t } = useTranslation("home"); 11 | 12 | return ( 13 | <> 14 | ... 15 | 16 | ); 17 | }; 18 | 19 | 20 | 21 | export async function getServerSideProps({ locale, params: { slug } }) { 22 | // getStaticPaths() 23 | const query = `*[_type == "product"] { 24 | slug { 25 | current 26 | } 27 | } 28 | `; 29 | 30 | const productPaths = await client.fetch(query); 31 | 32 | const paths = productPaths.map((product) => ({ 33 | params: { 34 | slug: product.slug.current, 35 | }, 36 | })); 37 | 38 | // getStaticProps() 39 | const query2 = `*[_type == "product" && slug.current == '${slug}'][0]`; 40 | const productsQuery = '*[_type == "product"]'; 41 | 42 | const product = await client.fetch(query2); 43 | const products = await client.fetch(productsQuery); 44 | 45 | return { 46 | props: { 47 | paths, 48 | fallback: "blocking", 49 | products, 50 | product, 51 | ...(await serverSideTranslations(locale, [ 52 | "demobanner", 53 | "navbar", 54 | "cart", 55 | "footer", 56 | "slug", 57 | "minicart", 58 | "emptycart", 59 | "shop", 60 | "product", 61 | "info", 62 | ])), 63 | // Will be passed to the page component as props 64 | }, 65 | }; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/translating into english/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Home", 3 | "about": "About", 4 | "shop": "Shop", 5 | "contact": "Contact", 6 | "demo": "This is a demo store - no orders will be accepted or delivered", 7 | "luxury": "Luxury Macarons made by hand", 8 | "shop-now": "Shop now", 9 | "welcome": { 10 | "para1": "Welcome to Macaron Magic - the home of great-tasting, luxurious macarons, made by hand here in our workshop in the Peak District.", 11 | "para2": "We have carefully chosen a select range of flavors for your delight, ready for you to enjoy - just imagine...biting into each one, where it practically melts in your mouth...yum!", 12 | "para3": "To start, browse over to our shop where you will see the full range available - we'll be adding more over time. If you have any questions, please do let us know - our contact details are at the bottom of this page." 13 | }, 14 | "perfect": { 15 | "for": "PERFECT FOR", 16 | "special": "SPECIAL OCCASIONS", 17 | "text": "Share the love and give every guest a little explosion of sweetness with our show stopping macaron towers. Perfect for weddings, anniversaries and parties. You could even add a touch of luxury to party bags and wedding favors with these perfect bite sized treats." 18 | }, 19 | "newsletter": { 20 | "firstname": "First name", 21 | "lastname": "Last name", 22 | "email": "Email address" 23 | }, 24 | "footer": { 25 | "delivery": "Delivery", 26 | "privacy": "Privacy", 27 | "terms": "Terms and Conditions of Sale", 28 | "contact": "Contact Us", 29 | "email": "Contact: hello@macaron-magic.com", 30 | "empty": "Your shopping bag is empty", 31 | "gotoshop": "Go to Shop" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding stripe/pages/api/stripe.js: -------------------------------------------------------------------------------- 1 | import Stripe from "stripe"; 2 | 3 | const stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY); 4 | 5 | export default async function handler(req, res) { 6 | if (req.method === "POST") { 7 | try { 8 | const params = { 9 | submit_type: "pay", 10 | mode: "payment", 11 | payment_method_types: ["card"], 12 | billing_address_collection: "auto", 13 | shipping_options: [ 14 | { 15 | shipping_rate: "shr_1MAwLmIG1cktkZ0RP4YPwGil", 16 | }, 17 | ], 18 | line_items: req.body.map((item) => { 19 | const img = item.image[0].asset._ref; 20 | const newImage = img 21 | .replace("image-", "https://cdn.sanity.io/images/g6xvtbtr/") 22 | .replace("-webp", ".webp"); 23 | 24 | return { 25 | price_data: { 26 | currency: "gbp", 27 | product_data: { 28 | name: item.name, 29 | images: [newImage], 30 | }, 31 | unit_amount: item.price * 100, 32 | }, 33 | adjustable_quantity: { 34 | enabled: true, 35 | minimum: 1, 36 | }, 37 | quantity: item.quantity, 38 | }; 39 | }), 40 | success_url: `${req.headers.origin}/success`, 41 | cancel_url: `${req.headers.origin}/canceled`, 42 | }; 43 | 44 | // Create Checkout Sessions from body params. 45 | const session = await stripe.checkout.sessions.create(params); 46 | 47 | res.status(200).json(session); 48 | } catch (err) { 49 | res.status(err.statusCode || 500).json(err.message); 50 | } 51 | } else { 52 | res.setHeader("Allow", "POST"); 53 | res.status(405).end("Method Not Allowed"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Ch9_Code_Nextjs_eCommerce/Ch9_Code_Nextjs_eCommerce/cypress/e2e/component/product.cy.js: -------------------------------------------------------------------------------- 1 | import Product from "../../src/components/Product.jsx"; 2 | 3 | const mockProduct = { 4 | _type: "product", 5 | delivery: 6 | "We carefully package our macarons and use Royal Mail to post them to you under first class postage. We only deliver to the UK.", 7 | details: 8 | "Enjoy the taste of our indulgent Chocolate Orange Macarons. Bursting with 100% natural flavours, our perfectly proportioned treats are handcrafted in our kitchen and beautifully packaged for your enjoyment.", 9 | image: [ 10 | { 11 | _key: "ba6786ee0e81", 12 | _type: "image", 13 | asset: { 14 | _ref: "image-812a8575cab31a81ea8352e913d173c9244151b7-456x456-jpg", 15 | }, 16 | }, 17 | ], 18 | ingredients: 19 | "Ground Almonds (contains nuts) , Icing Sugar, Free Range Egg Whites (contains Eggs), Sugar, Milk Chocolate, (Sugar, Cocoa Butter, High Fat Milk Powder, Cocoa Mass, Whole Milk Powder, Skimmed Milk Powder, Lactose (Milk), Emulsifier: Lecithins (Soya); Vanilla Extract), Double Cream (contains Milk), Orange Extract, Colour E110 may have an adverse effect on activity and attention in children Please note: product may contain allergens - if in doubt, please ask.", 20 | name: "Chocolate Orange", 21 | price: 18.5, 22 | sku: "MACM001", 23 | slug: { 24 | current: "chocolate-orange", 25 | }, 26 | weight: "335g", 27 | }; 28 | 29 | describe("", () => { 30 | it("should render and display expected content", () => { 31 | // Mount the DemoBanner component 32 | cy.mount(); 33 | 34 | // The component should contain an element of "footer" 35 | cy.get("div").should("be.visible"); 36 | 37 | // Validate that the correct text is displayed in the Product card 38 | cy.get("p.product-name:nth-child(2)").should("have.text", mockProduct.name); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /Ch11_Code_Nextjs_eCommerce/Ch11_Code_Nextjs_eCommerce/changes for adding language support/snippets for adding language support.txt: -------------------------------------------------------------------------------- 1 | "peerDependencies": { 2 | "i18next": "^22.4.15", 3 | "react-i18next": "^12.2.0" 4 | }, 5 | 6 | 7 | module.exports = { 8 | i18n: { 9 | defaultLocale: "en", 10 | locales: ["en", "nl-NL"], 11 | }, 12 | react: { useSuspense: false }, 13 | }; 14 | 15 | 16 | 17 | const { i18n } = require("./next-i18next.config"); 18 | 19 | 20 | 21 | reactStrictMode: true, 22 | sassOptions: { 23 | includePaths: ["styles"], 24 | }, 25 | i18n, <-- ADD THIS LINE 26 | }; 27 | 28 | 29 | 30 | 31 | import { appWithTranslation } from "next-i18next"; 32 | import nextI18NextConfig from "../../next-i18next.config"; 33 | 34 | 35 | 36 | export default appWithTranslation(MyApp, nextI18NextConfig); 37 | 38 | 39 | 40 | import { serverSideTranslations } from "next-i18next/serverSideTranslations"; 41 | 42 | 43 | 44 | 45 | export async function getStaticProps({ locale }) { 46 | return { 47 | props: { 48 | ...(await serverSideTranslations(locale, ["common", "test"])), 49 | // Will be passed to the page component as props 50 | }, 51 | }; 52 | } 53 | 54 | 55 | import { useTranslation } from "next-i18next"; 56 | import { serverSideTranslations } from "next-i18next/serverSideTranslations"; 57 | 58 | 59 | /* Completed version of the test component code */ 60 | const About = () => { 61 | const { t } = useTranslation("test"); 62 | 63 | return ( 64 |
65 |

About Macaron Magic

66 | 67 |

{t("about")}

68 |

69 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris 70 | (...shortened for brevity) 71 |

72 |
73 | ); 74 | }; 75 | 76 | 77 | 78 | export async function getServerSideProps({ locale }) { 79 | return { 80 | props: { 81 | ...(await serverSideTranslations(locale, ["test"])), 82 | // Will be passed to the page component as props 83 | }, 84 | }; 85 | } 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/creating individual page/Product.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | 4 | import { urlFor } from "../../lib/client"; 5 | import { ArticleJsonLd, NextSeo } from "next-seo"; 6 | 7 | const Product = ({ 8 | product: { image, name, slug, price, description, sku }, 9 | }) => { 10 | return ( 11 | <> 12 | 22 | 23 | 40 | 41 |
42 | 43 |
44 |
45 | 51 |
52 |

{name}

53 |
54 |
55 |

{name}

56 |

57 | $ 58 | {price.toLocaleString("en-US", { 59 | maximumFractionDigits: 2, 60 | minimumFractionDigits: 2, 61 | })} 62 |

63 |
64 | 65 |
66 | 67 | ); 68 | }; 69 | 70 | export default Product; 71 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding a minicart/MiniCart.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { AiOutlineShopping } from "react-icons/ai"; 3 | import toast from "react-hot-toast"; 4 | import { useStateContext } from "../../context/StateContext"; 5 | import { urlFor } from "../../lib/client"; 6 | import getStripe from "../../lib/getStripe"; 7 | import { eUSLocale } from "../../lib/utils"; 8 | import EmptyCart from "./Cart/EmptyCart"; 9 | import Link from "next/link"; 10 | 11 | const MiniCart = () => { 12 | const { totalPrice, totalQuantities, cartItems } = useStateContext(); 13 | 14 | const handleCheckout = async () => { 15 | const stripe = await getStripe(); 16 | 17 | const response = await fetch("/api/stripe", { 18 | method: "POST", 19 | headers: { 20 | "Content-Type": "application/json", 21 | }, 22 | body: JSON.stringify(cartItems), 23 | }); 24 | 25 | if (response.statusCode === 500) return; 26 | 27 | const data = await response.json(); 28 | 29 | toast.loading("Redirecting..."); 30 | 31 | stripe.redirectToCheckout({ sessionId: data.id }); 32 | }; 33 | 34 | return ( 35 |
36 | 37 | Your Cart contains {totalQuantities} item 38 | {totalQuantities > 1 || totalQuantities === 0 ? "s" : ""} 39 | 40 | 41 | {cartItems.length < 1 && ( 42 | 43 | 44 | 47 | 48 | 49 | )} 50 | 51 |
52 | {cartItems.length >= 1 && 53 | cartItems.map((item) => ( 54 |
55 | 56 | 57 | 58 | 59 | {item.name} 60 | 61 | {item.quantity} 62 | x 63 | ${eUSLocale(item.price)} 64 | 65 | 66 |
67 | ))} 68 |
69 | {cartItems.length >= 1 && ( 70 |
71 |
72 |

${eUSLocale(totalPrice)}

73 |
74 |
75 | 78 |
79 |
80 | )} 81 |
82 | ); 83 | }; 84 | 85 | export default MiniCart; 86 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/refactoring the cart/MiniCart.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { AiOutlineShopping } from "react-icons/ai"; 3 | import toast from "react-hot-toast"; 4 | import { useStateContext } from "../../context/StateContext"; 5 | import { urlFor } from "../../lib/client"; 6 | import getStripe from "../../lib/getStripe"; 7 | import { eUSLocale } from "../../lib/utils"; 8 | import EmptyCart from "./Cart/EmptyCart"; 9 | import Link from "next/link"; 10 | 11 | const MiniCart = () => { 12 | const { totalPrice, totalQuantities, cartItems } = useStateContext(); 13 | 14 | const handleCheckout = async () => { 15 | const stripe = await getStripe(); 16 | 17 | const response = await fetch("/api/stripe", { 18 | method: "POST", 19 | headers: { 20 | "Content-Type": "application/json", 21 | }, 22 | body: JSON.stringify(cartItems), 23 | }); 24 | 25 | if (response.statusCode === 500) return; 26 | 27 | const data = await response.json(); 28 | 29 | toast.loading("Redirecting..."); 30 | 31 | stripe.redirectToCheckout({ sessionId: data.id }); 32 | }; 33 | 34 | return ( 35 |
36 | 37 | Your Cart contains {totalQuantities} item 38 | {totalQuantities > 1 || totalQuantities === 0 ? "s" : ""} 39 | 40 | 41 | {cartItems.length < 1 && ( 42 | 43 | 44 | 47 | 48 | 49 | )} 50 | 51 |
52 | {cartItems.length >= 1 && 53 | cartItems.map((item) => ( 54 |
55 | 56 | 57 | 58 | 59 | {item.name} 60 | 61 | {item.quantity} 62 | x 63 | ${eUSLocale(item.price)} 64 | 65 | 66 |
67 | ))} 68 |
69 | {cartItems.length >= 1 && ( 70 |
71 |
72 |

${eUSLocale(totalPrice)}

73 |
74 |
75 | 78 |
79 |
80 | )} 81 |
82 | ); 83 | }; 84 | 85 | export default MiniCart; 86 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/updated add to cart button/styles for button.txt: -------------------------------------------------------------------------------- 1 | /* UPDATED ADD TO CART BUTTON */ 2 | .buttons { 3 | margin-top: 40px; 4 | } 5 | .buttons .add-to-cart, 6 | .buttons .buy-now { 7 | margin-top: 0; 8 | } 9 | 10 | @keyframes shift-left { 11 | 0% { 12 | transform: translateX(0); 13 | } 14 | 100% { 15 | transform: translateX(-40px); 16 | } 17 | } 18 | 19 | @keyframes shift-left-circle { 20 | 0% { 21 | transform: translateX(0); 22 | } 23 | 50% { 24 | transform: translateX(-40px); 25 | } 26 | 100% { 27 | transform: translateX(-40px); 28 | } 29 | } 30 | 31 | @keyframes shift-left-mask { 32 | 0% { 33 | height: 7px; 34 | transform: translateX(0) rotate(0); 35 | } 36 | 50% { 37 | transform: translateX(0) rotate(180deg); 38 | } 39 | 100% { 40 | transform: translateX(-40px) rotate(180deg); 41 | } 42 | } 43 | 44 | .btn-cart { 45 | display: block; 46 | width: 200px; 47 | border: none; 48 | margin: 0 auto; 49 | background: none; 50 | background-color: #ffffff; 51 | font-weight: 500; 52 | color: white; 53 | font-size: 14px; 54 | position: relative; 55 | cursor: pointer; 56 | height: 45px; 57 | border: 1px solid #f02d34; 58 | font-size: 18px; 59 | } 60 | 61 | .btn-cart:before { 62 | content: ""; 63 | display: block; 64 | width: 12px; 65 | height: 12px; 66 | position: absolute; 67 | border: 2px solid #f02d34; 68 | transform: translateX(0); 69 | left: 94px; 70 | border-radius: 50%; 71 | top: 5px; 72 | box-sizing: border-box; 73 | } 74 | 75 | .btn-cart:after { 76 | content: ""; 77 | position: absolute; 78 | top: 0; 79 | left: 0; 80 | width: 100%; 81 | height: 100%; 82 | background: #f02d34; 83 | transition: all 400ms cubic-bezier(0.175, 0.885, 0.32, 1.275); 84 | } 85 | 86 | .btn-cart:focus { 87 | outline: none; 88 | } 89 | 90 | .btn-cart:focus:before { 91 | animation: shift-left-circle 800ms forwards; 92 | animation-delay: 1200ms; 93 | } 94 | 95 | .btn-cart:focus:after { 96 | width: 20px; 97 | height: 20px; 98 | top: 12px; 99 | left: 90px; 100 | animation: shift-left 400ms forwards; 101 | animation-delay: 1200ms; 102 | transition-delay: 400ms; 103 | } 104 | 105 | .btn-cart:focus > span:before { 106 | animation: shift-left-mask 800ms forwards; 107 | animation-delay: 800ms; 108 | height: 7px; 109 | } 110 | 111 | .btn-cart:focus > span:after { 112 | transform: translate(-30%, 0); 113 | transition-delay: 1600ms; 114 | opacity: 1; 115 | } 116 | 117 | .btn-cart:focus > span span { 118 | opacity: 0; 119 | transform: translateY(20px); 120 | } 121 | 122 | .btn-cart > span { 123 | position: relative; 124 | display: block; 125 | } 126 | 127 | .btn-cart > span:before { 128 | content: ""; 129 | display: block; 130 | position: absolute; 131 | width: 12px; 132 | height: 20px; 133 | background: white; 134 | top: 5px; 135 | left: 94px; 136 | animation-timing-function: linear; 137 | transform: translateX(0) rotate(0deg); 138 | transform-origin: center bottom; 139 | } 140 | 141 | .btn-cart > span:after { 142 | content: "Added"; 143 | color: green; 144 | position: absolute; 145 | z-index: 3; 146 | left: 50%; 147 | opacity: 0; 148 | transition: all 400ms cubic-bezier(0.175, 0.885, 0.32, 1.275); 149 | transform: translate(-30%, 20px); 150 | transition-delay: 0; 151 | } 152 | 153 | .btn-cart span span { 154 | display: inline-block; 155 | position: relative; 156 | z-index: 2; 157 | transition: all 400ms cubic-bezier(0.175, 0.885, 0.32, 1.275); 158 | transform: translateY(0px); 159 | } -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding context/StateContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState, useEffect } from "react"; 2 | import { toast } from "react-hot-toast"; 3 | 4 | const Context = createContext(); 5 | 6 | export const StateContext = ({ children }) => { 7 | const [showCart, setShowCart] = useState(false); 8 | const [cartItems, setCartItems] = useState([]); 9 | const [totalPrice, setTotalPrice] = useState(0); 10 | const [totalQuantities, setTotalQuantities] = useState(0); 11 | const [qty, setQty] = useState(1); 12 | 13 | let foundProduct; 14 | let index; 15 | 16 | const onAdd = (product, quantity) => { 17 | const checkProductInCart = cartItems.find( 18 | (item) => item._id === product._id 19 | ); 20 | 21 | setTotalPrice( 22 | (prevTotalPrice) => prevTotalPrice + product.price * quantity 23 | ); 24 | setTotalQuantities((prevTotalQuantities) => prevTotalQuantities + quantity); 25 | 26 | if (checkProductInCart) { 27 | const updatedCartItems = cartItems.map((cartProduct) => { 28 | if (cartProduct._id === product._id) 29 | return { 30 | ...cartProduct, 31 | quantity: cartProduct.quantity + quantity, 32 | }; 33 | }); 34 | 35 | setCartItems(updatedCartItems); 36 | } else { 37 | product.quantity = quantity; 38 | 39 | setCartItems([...cartItems, { ...product }]); 40 | } 41 | 42 | toast.success(`${qty} ${product.name} added to the cart.`); 43 | }; 44 | 45 | const onRemove = (product) => { 46 | foundProduct = cartItems.find((item) => item._id === product._id); 47 | const newCartItems = cartItems.filter((item) => item._id !== product._id); 48 | 49 | setTotalPrice( 50 | (prevTotalPrice) => 51 | prevTotalPrice - foundProduct.price * foundProduct.quantity 52 | ); 53 | setTotalQuantities( 54 | (prevTotalQuantities) => prevTotalQuantities - foundProduct.quantity 55 | ); 56 | setCartItems(newCartItems); 57 | }; 58 | 59 | const toggleCartItemQuantity = (id, value) => { 60 | foundProduct = cartItems.find((item) => item._id === id); 61 | index = cartItems.findIndex((product) => product._id === id); 62 | const newCartItems = cartItems.filter((item) => item._id !== id); 63 | 64 | if (value === "inc") { 65 | setCartItems([ 66 | ...newCartItems, 67 | { ...foundProduct, quantity: foundProduct.quantity + 1 }, 68 | ]); 69 | setTotalPrice((prevTotalPrice) => prevTotalPrice + foundProduct.price); 70 | setTotalQuantities((prevTotalQuantities) => prevTotalQuantities + 1); 71 | } else if (value === "dec") { 72 | if (foundProduct.quantity > 1) { 73 | setCartItems([ 74 | ...newCartItems, 75 | { ...foundProduct, quantity: foundProduct.quantity - 1 }, 76 | ]); 77 | setTotalPrice((prevTotalPrice) => prevTotalPrice - foundProduct.price); 78 | setTotalQuantities((prevTotalQuantities) => prevTotalQuantities - 1); 79 | } 80 | } 81 | }; 82 | 83 | const incQty = () => { 84 | setQty((prevQty) => prevQty + 1); 85 | }; 86 | 87 | const decQty = () => { 88 | setQty((prevQty) => { 89 | if (prevQty - 1 < 1) return 1; 90 | 91 | return prevQty - 1; 92 | }); 93 | }; 94 | 95 | return ( 96 | 114 | {children} 115 | 116 | ); 117 | }; 118 | 119 | export const useStateContext = () => useContext(Context); 120 | -------------------------------------------------------------------------------- /Ch3_Code_Nextjs_eCommerce/Ch3_Code_Nextjs_eCommerce/creating individual page/product/[slug].js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | AiOutlineMinus, 4 | AiOutlinePlus, 5 | AiFillStar, 6 | AiOutlineStar, 7 | } from "react-icons/ai"; 8 | import { Info } from "../../components"; 9 | 10 | import { client, urlFor } from "../../lib/client"; 11 | import { Product } from "../../components"; 12 | 13 | const ProductDetails = ({ product, products }) => { 14 | const { image, name, details, price, sku, ingredients, weight, delivery } = 15 | product; 16 | const [index, setIndex] = useState(0); 17 | 18 | return ( 19 |
20 |
21 |
22 |
23 | 27 |
28 |
29 | {image?.map((item, i) => ( 30 | setIndex(i)} 37 | /> 38 | ))} 39 |
40 |
41 | 42 |
43 |

{name}

44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 |
52 |

(20)

53 |
54 |

Details:

55 |

{details}

56 |

57 | $ 58 | {price.toLocaleString("en-US", { 59 | maximumFractionDigits: 2, 60 | minimumFractionDigits: 2, 61 | })} 62 |

63 | per box of 12 64 |
65 |

Quantity:

66 |

67 | 68 | 69 | 70 | 1 71 | 72 | 73 | 74 |

75 |
76 |
SKU: {sku}
77 |
78 |
79 | 80 | 81 | 82 |
83 |

You may also like

84 |
85 |
86 | {products.map((item) => ( 87 | 88 | ))} 89 |
90 |
91 |
92 |
93 | ); 94 | }; 95 | 96 | export const getStaticPaths = async () => { 97 | const query = `*[_type == "product"] { 98 | slug { 99 | current 100 | } 101 | } 102 | `; 103 | 104 | const products = await client.fetch(query); 105 | 106 | const paths = products.map((product) => ({ 107 | params: { 108 | slug: product.slug.current, 109 | }, 110 | })); 111 | 112 | return { 113 | paths, 114 | fallback: "blocking", 115 | }; 116 | }; 117 | 118 | export const getStaticProps = async ({ params: { slug } }) => { 119 | const query = `*[_type == "product" && slug.current == '${slug}'][0]`; 120 | const productsQuery = '*[_type == "product"]'; 121 | 122 | const product = await client.fetch(query); 123 | const products = await client.fetch(productsQuery); 124 | 125 | return { 126 | props: { products, product }, 127 | }; 128 | }; 129 | 130 | export default ProductDetails; 131 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/adding stripe/Cart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import Link from "next/link"; 3 | import { 4 | AiOutlineMinus, 5 | AiOutlinePlus, 6 | AiOutlineLeft, 7 | AiOutlineShopping, 8 | } from "react-icons/ai"; 9 | import { TiDeleteOutline } from "react-icons/ti"; 10 | import { useStateContext } from "../../context/StateContext"; 11 | import { urlFor } from "../../lib/client"; 12 | import getStripe from "../../lib/getStripe"; 13 | import toast from "react-hot-toast"; 14 | import { eUSLocale } from "../../lib/utils"; 15 | import EmptyCart from "./Cart/EmptyCart"; 16 | 17 | const Cart = () => { 18 | const cartRef = useRef(); 19 | const { 20 | totalPrice, 21 | totalQuantities, 22 | cartItems, 23 | setShowCart, 24 | toggleCartItemQuantity, 25 | onRemove, 26 | } = useStateContext(); 27 | 28 | const handleCheckout = async () => { 29 | const stripe = await getStripe(); 30 | 31 | const response = await fetch("/api/stripe", { 32 | method: "POST", 33 | headers: { 34 | "Content-Type": "application/json", 35 | }, 36 | body: JSON.stringify(cartItems), 37 | }); 38 | 39 | if (response.statusCode === 500) return; 40 | 41 | const data = await response.json(); 42 | 43 | toast.loading("Redirecting..."); 44 | 45 | stripe.redirectToCheckout({ sessionId: data.id }); 46 | }; 47 | 48 | return ( 49 |
50 |
51 | 60 | 61 | {cartItems.length < 1 && ( 62 | 63 | 64 | 71 | 72 | 73 | )} 74 | 75 |
76 | {cartItems.length >= 1 && 77 | cartItems.map((item) => ( 78 |
79 | 86 | 90 |
91 |
92 | {item.name} 93 | 94 | {item.quantity} @ ${eUSLocale(item.price)} 95 | 96 |
97 |

98 | toggleCartItemQuantity(item._id, "dec")} 101 | > 102 | 103 | 104 | toggleCartItemQuantity(item._id, "inc")} 107 | > 108 | 109 | 110 |

111 |
112 |
113 | ))} 114 |
115 | {cartItems.length >= 1 && ( 116 |
117 |
118 |

Subtotal:

119 | ${eUSLocale(totalPrice)} 120 |
121 |
122 | 125 |
126 |
127 | )} 128 |
129 |
130 | ); 131 | }; 132 | 133 | export default Cart; 134 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/constructing the cart/Cart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import Link from "next/link"; 3 | import { 4 | AiOutlineMinus, 5 | AiOutlinePlus, 6 | AiOutlineLeft, 7 | AiOutlineShopping, 8 | } from "react-icons/ai"; 9 | import { TiDeleteOutline } from "react-icons/ti"; 10 | import { useStateContext } from "../../context/StateContext"; 11 | import { urlFor } from "../../lib/client"; 12 | import getStripe from "../../lib/getStripe"; 13 | import toast from "react-hot-toast"; 14 | import { eUSLocale } from "../../lib/utils"; 15 | import EmptyCart from "./Cart/EmptyCart"; 16 | 17 | const Cart = () => { 18 | const cartRef = useRef(); 19 | const { 20 | totalPrice, 21 | totalQuantities, 22 | cartItems, 23 | setShowCart, 24 | toggleCartItemQuantity, 25 | onRemove, 26 | } = useStateContext(); 27 | 28 | const handleCheckout = async () => { 29 | const stripe = await getStripe(); 30 | 31 | const response = await fetch("/api/stripe", { 32 | method: "POST", 33 | headers: { 34 | "Content-Type": "application/json", 35 | }, 36 | body: JSON.stringify(cartItems), 37 | }); 38 | 39 | if (response.statusCode === 500) return; 40 | 41 | const data = await response.json(); 42 | 43 | toast.loading("Redirecting..."); 44 | 45 | stripe.redirectToCheckout({ sessionId: data.id }); 46 | }; 47 | 48 | return ( 49 |
50 |
51 | 60 | 61 | {cartItems.length < 1 && ( 62 | 63 | 64 | 71 | 72 | 73 | )} 74 | 75 |
76 | {cartItems.length >= 1 && 77 | cartItems.map((item) => ( 78 |
79 | 86 | 90 |
91 |
92 | {item.name} 93 | 94 | {item.quantity} @ ${eUSLocale(item.price)} 95 | 96 |
97 |

98 | toggleCartItemQuantity(item._id, "dec")} 101 | > 102 | 103 | 104 | toggleCartItemQuantity(item._id, "inc")} 107 | > 108 | 109 | 110 |

111 |
112 |
113 | ))} 114 |
115 | {cartItems.length >= 1 && ( 116 |
117 |
118 |

Subtotal:

119 | ${eUSLocale(totalPrice)} 120 |
121 |
122 | 125 |
126 |
127 | )} 128 |
129 |
130 | ); 131 | }; 132 | 133 | export default Cart; 134 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/refactoring the cart/Cart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import Link from "next/link"; 3 | import { 4 | AiOutlineMinus, 5 | AiOutlinePlus, 6 | AiOutlineLeft, 7 | AiOutlineShopping, 8 | } from "react-icons/ai"; 9 | import { TiDeleteOutline } from "react-icons/ti"; 10 | import { useStateContext } from "../../context/StateContext"; 11 | import { urlFor } from "../../lib/client"; 12 | import getStripe from "../../lib/getStripe"; 13 | import toast from "react-hot-toast"; 14 | import { eUSLocale } from "../../lib/utils"; 15 | import EmptyCart from "./Cart/EmptyCart"; 16 | 17 | const Cart = () => { 18 | const cartRef = useRef(); 19 | const { 20 | totalPrice, 21 | totalQuantities, 22 | cartItems, 23 | setShowCart, 24 | toggleCartItemQuantity, 25 | onRemove, 26 | } = useStateContext(); 27 | 28 | const handleCheckout = async () => { 29 | const stripe = await getStripe(); 30 | 31 | const response = await fetch("/api/stripe", { 32 | method: "POST", 33 | headers: { 34 | "Content-Type": "application/json", 35 | }, 36 | body: JSON.stringify(cartItems), 37 | }); 38 | 39 | if (response.statusCode === 500) return; 40 | 41 | const data = await response.json(); 42 | 43 | toast.loading("Redirecting..."); 44 | 45 | stripe.redirectToCheckout({ sessionId: data.id }); 46 | }; 47 | 48 | return ( 49 |
50 |
51 | 60 | 61 | {cartItems.length < 1 && ( 62 | 63 | 64 | 71 | 72 | 73 | )} 74 | 75 |
76 | {cartItems.length >= 1 && 77 | cartItems.map((item) => ( 78 |
79 | 86 | 90 |
91 |
92 | {item.name} 93 | 94 | {item.quantity} @ ${eUSLocale(item.price)} 95 | 96 |
97 |

98 | toggleCartItemQuantity(item._id, "dec")} 101 | > 102 | 103 | 104 | toggleCartItemQuantity(item._id, "inc")} 107 | > 108 | 109 | 110 |

111 |
112 |
113 | ))} 114 |
115 | {cartItems.length >= 1 && ( 116 |
117 |
118 |

Subtotal:

119 | ${eUSLocale(totalPrice)} 120 |
121 |
122 | 125 |
126 |
127 | )} 128 |
129 |
130 | ); 131 | }; 132 | 133 | export default Cart; 134 | -------------------------------------------------------------------------------- /Ch4_Code_Nextjs_eCommerce/Ch4_Code_Nextjs_eCommerce/finishing the buttons/[slug].js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { AiOutlineMinus, AiOutlinePlus } from "react-icons/ai"; 3 | 4 | import { client, urlFor } from "../../../lib/client"; 5 | import { Product } from "../../components"; 6 | import { useStateContext } from "../../../context/StateContext"; 7 | import { Info } from "../../components"; 8 | import { StarRating } from "../../components"; 9 | 10 | const ProductDetails = ({ product, products }) => { 11 | const { image, name, details, price, sku, ingredients, weight, delivery } = 12 | product; 13 | const [index, setIndex] = useState(0); 14 | const { decQty, incQty, qty, onAdd, setShowCart } = useStateContext(); 15 | 16 | const handleBuyNow = () => { 17 | onAdd(product, qty); 18 | setShowCart(true); 19 | }; 20 | 21 | return ( 22 |
23 |
24 |
25 |
26 | 30 |
31 |
32 | {image?.map((item, i) => ( 33 | setIndex(i)} 40 | /> 41 | ))} 42 |
43 |
44 | 45 |
46 |

{name}

47 |
48 | 49 |

(20)

50 |
51 |

Details:

52 |

{details}

53 |

54 | $ 55 | {price.toLocaleString("en-US", { 56 | maximumFractionDigits: 2, 57 | minimumFractionDigits: 2, 58 | })} 59 |

60 | per box of 12 61 |
62 |

Quantity:

63 |

64 | 65 | 66 | 67 | {qty} 68 | 69 | 70 | 71 |

72 |
73 |
SKU: {sku}
74 |
75 | 82 | {/* NEW BUTTON */} 83 | 92 | 93 | 96 |
97 |
98 |
99 | 100 | 101 | 102 |
103 |

You may also like

104 |
105 |
106 | {products.map((item) => ( 107 | 108 | ))} 109 |
110 |
111 |
112 |
113 | ); 114 | }; 115 | 116 | export const getStaticPaths = async () => { 117 | const query = `*[_type == "product"] { 118 | slug { 119 | current 120 | } 121 | } 122 | `; 123 | 124 | const products = await client.fetch(query); 125 | 126 | const paths = products.map((product) => ({ 127 | params: { 128 | slug: product.slug.current, 129 | }, 130 | })); 131 | 132 | return { 133 | paths, 134 | fallback: "blocking", 135 | }; 136 | }; 137 | 138 | export const getStaticProps = async ({ params: { slug } }) => { 139 | const query = `*[_type == "product" && slug.current == '${slug}'][0]`; 140 | const productsQuery = '*[_type == "product"]'; 141 | 142 | const product = await client.fetch(query); 143 | const products = await client.fetch(productsQuery); 144 | 145 | return { 146 | props: { products, product }, 147 | }; 148 | }; 149 | 150 | export default ProductDetails; 151 | -------------------------------------------------------------------------------- /Ch7_Code_Nextjs_eCommerce/Ch7_Code_Nextjs_eCommerce/correcting errors/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 17 | 19 | 21 | 22 | 24 | 25 | 27 | 29 | 32 | 37 | 39 | 41 | 44 | 46 | 47 | 49 | 51 | 54 | 56 | 58 | 60 | 62 | 64 | 65 | 67 | 69 | 71 | 73 | 74 | 82 | 84 | 86 | 88 | 90 | 92 | 93 | 95 | 96 | 98 | 100 | 101 | 102 | 104 | 106 | 107 | 108 | 109 | 111 | 113 | 115 | 118 | 120 | 121 | 122 | 127 | 134 | 136 | 140 | 145 | 148 | 150 | 152 | 154 | 156 | 158 | 160 | 161 | 169 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /Ch2_Code_Nextjs_eCommerce/Ch2_Code_Nextjs_eCommerce/components/PaymentIcons.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const PaymentIcons = () => { 4 | return ( 5 |
6 | Payment methods 7 |
    8 |
  • 9 | 18 | American Express 19 | 20 | 25 | 29 | 33 | 34 | 35 |
  • 36 |
  • 37 | 50 | Apple Pay 51 | 55 | 59 | 60 | 61 | 65 | 69 | 70 | 71 | 75 | 79 | 83 | 84 | 85 | 86 |
  • 87 |
  • 88 | 97 | Diners Club 98 | 102 | 106 | 110 | 111 |
  • 112 |
  • 113 | 123 | Discover 124 | 129 | 133 | 137 | 141 | 146 | 150 | 154 | 158 | 159 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 |
  • 190 |
  • 191 | 200 | Google Pay 201 | 206 | 210 | 214 | 218 | 222 | 226 | 230 | 231 |
  • 232 |
  • 233 | 242 | Maestro 243 | 247 | 251 | 252 | 253 | 257 | 258 |
  • 259 |
  • 260 | 269 | Mastercard 270 | 274 | 278 | 279 | 280 | 284 | 285 |
  • 286 |
  • 287 | 296 | Shop Pay 297 | 302 | 306 | 310 | 311 |
  • 312 |
  • 313 | 322 | Visa 323 | 327 | 331 | 335 | 336 |
  • 337 |
338 |
339 | ); 340 | }; 341 | 342 | export default PaymentIcons; 343 | -------------------------------------------------------------------------------- /Ch5_Code_Nextjs_eCommerce/Ch5_Code_Nextjs_eCommerce/applying styles/globals.css: -------------------------------------------------------------------------------- 1 | /* GLOBALS ----------------------------------------- */ 2 | @import url("https://fonts.googleapis.com/css2?family=Oswald&display=swap"); 3 | 4 | html, 5 | body, 6 | * { 7 | padding: 0; 8 | margin: 0; 9 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 10 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 11 | box-sizing: border-box; 12 | } 13 | ::-webkit-scrollbar { 14 | width: 0px; 15 | } 16 | 17 | a { 18 | color: inherit; 19 | text-decoration: none; 20 | } 21 | 22 | .main-container { 23 | margin: auto; 24 | width: 100%; 25 | } 26 | 27 | .navbar { 28 | border-top: 1px solid red; 29 | padding-top: 3px; 30 | } 31 | 32 | .navbar a { 33 | font-size: 20px; 34 | margin-right: 20px; 35 | } 36 | 37 | .demo-banner-container { 38 | background-color: #000000; 39 | color: white; 40 | text-align: center; 41 | padding: 10px 0; 42 | } 43 | 44 | .btn { 45 | width: 100%; 46 | max-width: 400px; 47 | padding: 10px 12px; 48 | border-radius: 15px; 49 | border: none; 50 | font-size: 20px; 51 | margin-top: 10px; 52 | margin-top: 40px; 53 | 54 | background-color: #f02d34; 55 | color: #fff; 56 | cursor: pointer; 57 | transform: scale(1, 1); 58 | transition: transform 0.5s ease; 59 | } 60 | .btn:hover { 61 | transform: scale(1.1, 1.1); 62 | } 63 | 64 | /* Product Page and Cart */ 65 | .quantity-desc { 66 | border: 1px solid gray; 67 | padding: 6px 0; 68 | display: flex; 69 | align-items: baseline; 70 | } 71 | 72 | .quantity-desc span { 73 | font-size: 16px; 74 | padding: 6px 12px; 75 | cursor: pointer; 76 | } 77 | 78 | .quantity-desc .minus { 79 | color: #f02d34; 80 | } 81 | 82 | .quantity-desc .num { 83 | border-left: 1px solid gray; 84 | border-right: 1px solid gray; 85 | font-size: 20px; 86 | } 87 | 88 | .quantity-desc .plus { 89 | color: rgb(49, 168, 49); 90 | } 91 | 92 | /* HOMEPAGE ---------------------------------------- */ 93 | 94 | /* Main homepage logo */ 95 | .frontlogo { 96 | background-position: center top; 97 | background-image: url("../../public/images/frontimage.jpg"); 98 | width: 100%; 99 | height: 675px; 100 | } 101 | 102 | /* "Luxury Macarons made by hand" banner on front page */ 103 | .banner { 104 | float: right; 105 | margin: 100px 350px; 106 | font-size: 36px; 107 | display: flex; 108 | flex-direction: column; 109 | text-align: center; 110 | } 111 | 112 | .banner > span:nth-child(2) { 113 | width: 200px; 114 | background-color: #cbb790; 115 | margin: 30px auto; 116 | padding: 10px; 117 | } 118 | 119 | .tagline { 120 | font-weight: 600; 121 | font-family: "Oswald", sans-serif; 122 | text-transform: uppercase; 123 | font-size: 60px; 124 | line-height: 1.1em; 125 | color: #ffffff; 126 | width: 500px; 127 | } 128 | 129 | /* Newsletter component - home page */ 130 | .newsletter { 131 | background-color: #cbb790; 132 | max-width: 60%; 133 | margin: 0 auto; 134 | display: flex; 135 | } 136 | 137 | @keyframes fadeIn { 138 | 0% { 139 | opacity: 0; 140 | } 141 | 100% { 142 | opacity: 1; 143 | } 144 | } 145 | 146 | .newsletter-image { 147 | width: 593px; 148 | padding: 0px 20px; 149 | position: relative; 150 | top: -35px; 151 | text-align: center; 152 | animation: fadeIn 4s; 153 | } 154 | 155 | /* Main intro on home page */ 156 | .intro { 157 | background-color: #cbb790; 158 | width: 50%; 159 | margin: 50px auto; 160 | padding: 20px; 161 | font-size: 18px; 162 | } 163 | 164 | .intro p { 165 | padding-bottom: 10px; 166 | } 167 | 168 | /* Perfect message component */ 169 | .perfect-occasions { 170 | display: flex; 171 | margin: 100px 10%; 172 | } 173 | 174 | .perfect-message { 175 | background-color: #959e92ff; 176 | width: 593px; 177 | height: 250px; 178 | padding: 50px 40px; 179 | position: relative; 180 | left: -30px; 181 | top: 20px; 182 | text-align: center; 183 | } 184 | 185 | .perfect-message > p:nth-child(1) { 186 | font-weight: 300; 187 | font-size: 21px; 188 | letter-spacing: 1px; 189 | line-height: 23px; 190 | padding-bottom: 5px; 191 | text-transform: uppercase; 192 | } 193 | 194 | .perfect-message > p:nth-child(2) { 195 | font-size: 42px; 196 | font-weight: 500; 197 | line-height: 24px; 198 | text-transform: uppercase; 199 | } 200 | 201 | .perfect-message > p:nth-child(3) { 202 | padding-top: 30px; 203 | } 204 | 205 | /* FOOTER --------------------------------------- */ 206 | /* Now in Footer.module.css */ 207 | 208 | /* Payment Icons */ 209 | .visually-hidden { 210 | border: 0px; 211 | clip: rect(0px, 0px, 0px, 0px); 212 | height: 1px; 213 | width: 1px; 214 | margin: -1px; 215 | padding: 0px; 216 | overflow: hidden; 217 | white-space: nowrap; 218 | position: absolute !important; 219 | } 220 | 221 | .payment-icons { 222 | display: flex; 223 | list-style: none; 224 | } 225 | 226 | .payment-icons .icon { 227 | width: 38px; 228 | height: 24px; 229 | fill: inherit; 230 | display: inline-block; 231 | vertical-align: middle; 232 | } 233 | 234 | /* NAVBAR --------------------------------------- */ 235 | .navbar-container { 236 | display: flex; 237 | justify-content: space-between; 238 | margin: 24px 18px; 239 | position: relative; 240 | } 241 | .navbar-container > div { 242 | display: flex; 243 | flex-direction: column; 244 | margin: 0 auto; 245 | text-align: center; 246 | } 247 | 248 | .navbar-container > div > a { 249 | margin-right: -0.1rem; 250 | letter-spacing: 0.2rem; 251 | opacity: 1; 252 | font-size: 48px; 253 | } 254 | 255 | .cart-icon { 256 | font-size: 25px; 257 | color: gray; 258 | cursor: pointer; 259 | position: relative; 260 | transition: transform 0.4s ease; 261 | border: none; 262 | background-color: transparent; 263 | vertical-align: middle; 264 | } 265 | .cart-icon:hover { 266 | transform: scale(1.1, 1.1); 267 | } 268 | .cart-item-qty { 269 | position: absolute; 270 | right: -8px; 271 | font-size: 12px; 272 | color: #eee; 273 | background-color: #f02d34; 274 | width: 18px; 275 | height: 18px; 276 | border-radius: 50%; 277 | text-align: center; 278 | font-weight: 600; 279 | } 280 | 281 | /* EMAIL SIGN-UP -------------------------------- */ 282 | .email-signup { 283 | display: flex; 284 | flex-direction: column; 285 | width: 640px; 286 | padding: 64px 50px; 287 | } 288 | 289 | .email-signup > span { 290 | font-size: 42px; 291 | text-transform: uppercase; 292 | font-family: "Oswald", sans-serif; 293 | margin-bottom: 25px; 294 | text-align: center; 295 | } 296 | 297 | .email-signup > input { 298 | padding: 14px 4%; 299 | border-radius: 3px; 300 | border: 0; 301 | margin-bottom: 16px; 302 | } 303 | 304 | .email-signup > button { 305 | padding: 0.8rem 1rem; 306 | font-size: 18px; 307 | } 308 | 309 | /* ABOUT ---------------------------------------- */ 310 | /* .about-us { 311 | width: 70%; 312 | margin: 0 auto; 313 | } 314 | 315 | .about-us p:nth-child(1) { 316 | margin: 40px 0 0 0; 317 | font-family: "Oswald", sans-serif; 318 | font-size: 42px; 319 | } 320 | 321 | .about-us > p:nth-child(2) { 322 | margin: 20px 0 20px 0; 323 | } */ 324 | 325 | /* SHOP ---------------------------------------- */ 326 | .products-heading { 327 | text-align: center; 328 | margin: 40px 0px; 329 | color: #324d67; 330 | } 331 | .products-heading h2 { 332 | font-size: 40px; 333 | font-weight: 800; 334 | } 335 | .products-heading p { 336 | font-size: 16px; 337 | font-weight: 200; 338 | } 339 | 340 | /* MAY LIKE COMPONENT -------------------------- */ 341 | .maylike-products-wrapper { 342 | margin: 60px auto 0 auto; 343 | width: 70%; 344 | } 345 | 346 | .maylike-products-wrapper h2 { 347 | margin: 30px 0 20px 20px; 348 | color: #324d67; 349 | 350 | font-size: 28px; 351 | } 352 | 353 | .maylike-products-container { 354 | display: flex; 355 | justify-content: center; 356 | gap: 15px; 357 | } 358 | 359 | /* CONTACT --------------------------------- */ 360 | .contact-us { 361 | width: 70%; 362 | margin: 0 auto; 363 | text-align: center; 364 | } 365 | 366 | .contact-us > p:nth-child(1) { 367 | margin: 40px 0 0 0; 368 | font-family: "Oswald", sans-serif; 369 | font-size: 42px; 370 | } 371 | 372 | .contact-us > p:nth-child(2) { 373 | margin: 20px 0 20px 0; 374 | } 375 | 376 | .contact-us > form > p { 377 | margin: 0 0 40px 0; 378 | } 379 | 380 | .contact-us-form { 381 | display: flex; 382 | flex-direction: column; 383 | width: 500px; 384 | margin: 0 auto; 385 | } 386 | 387 | .contact-us-form label { 388 | display: flex; 389 | } 390 | 391 | .contact-field { 392 | margin-bottom: 20px; 393 | display: flex; 394 | } 395 | 396 | .contact-field input, 397 | .contact-field textarea { 398 | width: 100%; 399 | } 400 | 401 | .contact-field input, 402 | .contact-field textarea { 403 | padding: 7px 15px; 404 | } 405 | 406 | .contact-field span { 407 | padding: 0 3px; 408 | color: #ff0000; 409 | font-weight: bold; 410 | } 411 | 412 | .contact-submit { 413 | padding: 15px 0; 414 | } 415 | 416 | /* MARQUEE COMPONENT ------------------------------- */ 417 | .marquee-text { 418 | font-size: 29px; 419 | font-weight: 600; 420 | margin: 60px 0px; 421 | color: #f02d34; 422 | } 423 | .marquee { 424 | position: relative; 425 | height: 400px; 426 | width: 100%; 427 | overflow-x: hidden; 428 | } 429 | 430 | .track { 431 | position: absolute; 432 | white-space: nowrap; 433 | will-change: transform; 434 | animation: marquee 15s linear infinite; 435 | width: 180%; 436 | } 437 | .track:hover { 438 | animation-play-state: paused; 439 | } 440 | @keyframes marquee { 441 | from { 442 | transform: translateX(0); 443 | } 444 | to { 445 | transform: translateX(-50%); 446 | } 447 | } 448 | 449 | /* PRODUCT PAGE ------------------------------------- */ 450 | .products-container { 451 | display: flex; 452 | flex-wrap: wrap; 453 | justify-content: center; 454 | gap: 15px; 455 | margin-top: 20px; 456 | width: 100%; 457 | } 458 | .product-card { 459 | cursor: pointer; 460 | transform: scale(1, 1); 461 | transition: transform 0.5s ease; 462 | color: #324d67; 463 | } 464 | 465 | .product-card:hover { 466 | transform: scale(1.1, 1.1); 467 | } 468 | 469 | .product-image { 470 | border-radius: 15px; 471 | background-color: #ebebeb; 472 | transform: scale(1, 1); 473 | transition: transform 0.5s ease; 474 | } 475 | 476 | .product-name { 477 | font-weight: 500; 478 | } 479 | .product-price { 480 | font-weight: 800; 481 | margin-top: 6px; 482 | color: black; 483 | } 484 | 485 | .product-detail-container { 486 | display: flex; 487 | grid-gap: 40px; 488 | gap: 40px; 489 | margin: 60px auto 0 auto; 490 | color: #324d67; 491 | width: 70%; 492 | } 493 | 494 | .product-detail-image { 495 | border-radius: 15px; 496 | background-color: #ebebeb; 497 | 498 | width: 400px; 499 | height: 400px; 500 | cursor: pointer; 501 | transition: 0.3s ease-in-out; 502 | } 503 | .product-detail-image:hover { 504 | background-color: #f02d34; 505 | } 506 | .small-images-container { 507 | display: flex; 508 | gap: 10px; 509 | margin-top: 20px; 510 | } 511 | .small-image { 512 | border: 1px solid #aaaaaa; 513 | border-radius: 4px; 514 | background-color: #ebebeb; 515 | width: 70px; 516 | height: 70px; 517 | cursor: pointer; 518 | } 519 | 520 | .selected-image { 521 | background-color: #f02d34; 522 | } 523 | 524 | .reviews { 525 | color: #f02d34; 526 | margin-top: 10px; 527 | display: flex; 528 | gap: 5px; 529 | align-items: center; 530 | } 531 | 532 | .product-detail-desc h4 { 533 | margin-top: 10px; 534 | } 535 | .product-detail-desc p { 536 | margin-top: 10px; 537 | } 538 | .reviews p { 539 | color: #324d67; 540 | margin-top: 0px; 541 | } 542 | .product-detail-desc .price { 543 | font-weight: 700; 544 | font-size: 26px; 545 | margin-top: 30px; 546 | color: #f02d34; 547 | } 548 | 549 | .product-detail-desc .quantity { 550 | display: flex; 551 | gap: 20px; 552 | margin-top: 10px; 553 | align-items: center; 554 | } 555 | .product-detail-desc .buttons { 556 | display: flex; 557 | gap: 30px; 558 | } 559 | .buttons .add-to-cart { 560 | padding: 10px 20px; 561 | border: 1px solid #f02d34; 562 | margin-top: 40px; 563 | font-size: 18px; 564 | font-weight: 500; 565 | background-color: white; 566 | color: #f02d34; 567 | cursor: pointer; 568 | width: 200px; 569 | transform: scale(1, 1); 570 | transition: transform 0.5s ease; 571 | } 572 | .buttons .add-to-cart:hover { 573 | transform: scale(1.1, 1.1); 574 | } 575 | .buttons .buy-now { 576 | width: 200px; 577 | padding: 10px 20px; 578 | background-color: #f02d34; 579 | color: white; 580 | border: none; 581 | margin-top: 40px; 582 | font-size: 18px; 583 | font-weight: 500; 584 | cursor: pointer; 585 | transform: scale(1, 1); 586 | transition: transform 0.5s ease; 587 | } 588 | .buttons .buy-now:hover { 589 | transform: scale(1.1, 1.1); 590 | } 591 | 592 | .sku { 593 | color: #65809a; 594 | font-size: 18px; 595 | } 596 | 597 | /* SUCCESS-CANCEL COMPONENT ------------------- */ 598 | .success-wrapper, 599 | .canceled-wrapper { 600 | background-color: white; 601 | min-height: 60vh; 602 | } 603 | .success, 604 | .canceled { 605 | width: 1000px; 606 | margin: auto; 607 | margin-top: 20px; 608 | background-color: #dcdcdc; 609 | padding: 50px; 610 | border-radius: 15px; 611 | display: flex; 612 | justify-content: center; 613 | align-items: center; 614 | flex-direction: column; 615 | } 616 | 617 | .success .icon { 618 | color: green; 619 | font-size: 40px; 620 | } 621 | .success h2 { 622 | text-transform: capitalize; 623 | margin-top: 15px 0px; 624 | font-weight: 900; 625 | font-size: 40px; 626 | color: #324d67; 627 | } 628 | .success .email-msg { 629 | font-size: 16px; 630 | font-weight: 600; 631 | text-align: center; 632 | } 633 | 634 | .canceled { 635 | cursor: pointer; 636 | } 637 | 638 | .canceled p { 639 | font-size: 20px; 640 | font-weight: 600; 641 | } 642 | .success .description { 643 | font-size: 16px; 644 | font-weight: 600; 645 | text-align: center; 646 | margin: 10px; 647 | margin-top: 30px; 648 | } 649 | .success .description .email { 650 | margin-left: 5px; 651 | color: #f02d34; 652 | } 653 | 654 | /* CART COMPONENT ---------------------------- */ 655 | /* @keyframes move { 656 | 0% { 657 | transform: translateX(100%); 658 | } 659 | 100% { 660 | transform: translateX(0%); 661 | } 662 | } */ 663 | 664 | /* .cart-container { 665 | height: 100vh; 666 | width: 600px; 667 | background-color: white; 668 | float: right; 669 | padding: 40px 10px; 670 | animation-name: move; 671 | animation-duration: 2s; 672 | animation-iteration-count: 1; 673 | animation-fill-mode: backwards; 674 | } */ 675 | 676 | @keyframes move { 677 | 0% { 678 | transform: translateX(100%); 679 | } 680 | 100% { 681 | transform: translateX(0%); 682 | } 683 | } 684 | 685 | .cart-container { 686 | height: 100vh; 687 | width: 600px; 688 | background-color: white; 689 | float: right; 690 | padding: 40px 10px; 691 | animation-name: move; 692 | animation-duration: 2s; 693 | animation-iteration-count: 1; 694 | animation-fill-mode: backwards; 695 | border: 1px solid #000000; 696 | } 697 | 698 | .cart-wrapper { 699 | width: 100vw; 700 | background: rgba(0, 0, 0, 0.5); 701 | position: fixed; 702 | right: 0; 703 | top: 0; 704 | z-index: 100; 705 | /* will-change: transform; */ 706 | transition: all 1s ease-in-out; 707 | } 708 | 709 | /* .cart-wrapper { 710 | width: 100vw; 711 | background: rgba(0, 0, 0, 0.5); 712 | position: fixed; 713 | right: 0; 714 | top: 0; 715 | z-index: 100; 716 | /* will-change: transform; */ 717 | /* transition: all 1s ease-in-out; 718 | } */ 719 | 720 | .mini-cart-container { 721 | align-items: center; 722 | } 723 | 724 | .mini-cart-container > div.product-container { 725 | margin: 0; 726 | padding: 0; 727 | max-width: 320px; 728 | } 729 | 730 | .mini-cart-container > div.product-container > div { 731 | flex-direction: row; 732 | } 733 | 734 | div.mini-cart-container > div.product-container > div { 735 | padding: 5px; 736 | } 737 | 738 | /* Mini cart and Cart page */ 739 | span.item-desc { 740 | display: flex; 741 | flex-direction: column; 742 | } 743 | 744 | /* Mini cart */ 745 | .multiply { 746 | padding: 0 3px; 747 | } 748 | 749 | .icon-container { 750 | margin-top: 20px; 751 | border-top: 1px solid #caa34d; 752 | padding: 5px 20px 0 20px; 753 | } 754 | 755 | .cart-heading { 756 | display: flex; 757 | align-items: center; 758 | font-size: 18px; 759 | font-weight: 500; 760 | cursor: pointer; 761 | gap: 2px; 762 | margin-left: 10px; 763 | border: none; 764 | background-color: transparent; 765 | } 766 | 767 | .cart-heading .heading { 768 | margin-left: 10px; 769 | } 770 | 771 | .cart-num-items { 772 | margin-left: 10px; 773 | color: #f02d34; 774 | } 775 | 776 | .empty-cart { 777 | margin: 20px 0 20px 0; 778 | display: flex; 779 | align-items: center; 780 | display: flex; 781 | flex-direction: column; 782 | margin-right: 0; 783 | } 784 | 785 | .empty-cart h3 { 786 | font-weight: 600; 787 | font-size: 20px; 788 | } 789 | 790 | .empty-cart svg { 791 | width: 36px; 792 | height: auto; 793 | margin: 0 auto; 794 | } 795 | 796 | .product-container { 797 | overflow: auto; 798 | max-height: 70vh; 799 | padding: 20px 10px; 800 | } 801 | 802 | .product { 803 | display: flex; 804 | align-items: center; 805 | grid-gap: 30px; 806 | gap: 10px; 807 | padding: 20px 20px 0 0; 808 | } 809 | 810 | .product .cart-product-image { 811 | height: 75px; 812 | background-color: #ebebeb; 813 | } 814 | 815 | .product .mini-cart-image { 816 | width: 50px; 817 | height: auto; 818 | } 819 | 820 | .item-desc { 821 | display: flex; 822 | justify-content: space-between; 823 | width: 350px; 824 | color: #324d67; 825 | } 826 | 827 | .item-desc div { 828 | display: flex; 829 | flex-direction: column; 830 | } 831 | 832 | .item-desc .bottom { 833 | margin-top: 20px; 834 | } 835 | 836 | .total { 837 | display: flex; 838 | justify-content: space-between; 839 | } 840 | 841 | .total h3 { 842 | font-size: 22px; 843 | } 844 | 845 | .remove-item { 846 | font-size: 24px; 847 | color: #f02d34; 848 | cursor: pointer; 849 | background: transparent; 850 | border: none; 851 | } 852 | 853 | .cart-bottom { 854 | bottom: 12px; 855 | right: 5px; 856 | width: 100%; 857 | padding: 30px 65px; 858 | } 859 | 860 | .btn-container { 861 | width: 400px; 862 | margin: auto; 863 | } 864 | 865 | .mini-cart-container { 866 | align-items: center; 867 | } 868 | 869 | .mini-cart-container > div.product-container { 870 | margin: 0; 871 | padding: 0; 872 | max-width: 320px; 873 | } 874 | 875 | .mini-cart-container > div.product-container > div { 876 | flex-direction: row; 877 | } 878 | 879 | div.mini-cart-container > div.product-container > div { 880 | padding: 5px; 881 | } 882 | 883 | /* Mini cart and Cart page */ 884 | span.item-desc { 885 | display: flex; 886 | flex-direction: column; 887 | } 888 | 889 | /* Mini cart */ 890 | .multiply { 891 | padding: 0 3px; 892 | } 893 | 894 | .icon-container { 895 | margin-top: 20px; 896 | border-top: 1px solid #caa34d; 897 | padding: 5px 20px 0 20px; 898 | } 899 | 900 | .cart-heading { 901 | display: flex; 902 | align-items: center; 903 | font-size: 18px; 904 | font-weight: 500; 905 | cursor: pointer; 906 | gap: 2px; 907 | margin-left: 10px; 908 | border: none; 909 | background-color: transparent; 910 | } 911 | 912 | .cart-heading .heading { 913 | margin-left: 10px; 914 | } 915 | 916 | .cart-num-items { 917 | margin-left: 10px; 918 | color: #f02d34; 919 | } 920 | 921 | .empty-cart { 922 | margin: 20px 0 20px 0; 923 | display: flex; 924 | align-items: center; 925 | display: flex; 926 | flex-direction: column; 927 | margin-right: 0; 928 | } 929 | 930 | .empty-cart h3 { 931 | font-weight: 600; 932 | font-size: 20px; 933 | } 934 | 935 | .empty-cart svg { 936 | width: 36px; 937 | height: auto; 938 | margin: 0 auto; 939 | } 940 | 941 | .product-container { 942 | overflow: auto; 943 | max-height: 70vh; 944 | padding: 20px 10px; 945 | } 946 | 947 | .product { 948 | display: flex; 949 | align-items: center; 950 | grid-gap: 30px; 951 | gap: 10px; 952 | padding: 20px 20px 0 0; 953 | } 954 | 955 | .product .cart-product-image { 956 | height: 75px; 957 | background-color: #ebebeb; 958 | } 959 | 960 | .product .mini-cart-image { 961 | width: 50px; 962 | height: auto; 963 | } 964 | 965 | .item-desc { 966 | display: flex; 967 | justify-content: space-between; 968 | width: 350px; 969 | color: #324d67; 970 | } 971 | 972 | .item-desc div { 973 | display: flex; 974 | flex-direction: column; 975 | } 976 | 977 | .item-desc .bottom { 978 | margin-top: 20px; 979 | } 980 | 981 | .total { 982 | display: flex; 983 | justify-content: space-between; 984 | } 985 | 986 | .total h3 { 987 | font-size: 22px; 988 | } 989 | 990 | .remove-item { 991 | font-size: 24px; 992 | color: #f02d34; 993 | cursor: pointer; 994 | background: transparent; 995 | border: none; 996 | } 997 | 998 | .cart-bottom { 999 | bottom: 12px; 1000 | right: 5px; 1001 | width: 100%; 1002 | padding: 30px 65px; 1003 | } 1004 | 1005 | .btn-container { 1006 | width: 400px; 1007 | margin: auto; 1008 | } 1009 | 1010 | /* TABS COMPONENT ------------------------------ */ 1011 | .react-tabs { 1012 | -webkit-tap-highlight-color: transparent; 1013 | width: 70%; 1014 | margin: 0 auto; 1015 | border: 1px solid #aaa; 1016 | margin-top: 40px; 1017 | } 1018 | 1019 | .react-tabs__tab-list { 1020 | border-bottom: 1px solid #aaa; 1021 | padding: 0; 1022 | background-color: #f4f4f4; 1023 | } 1024 | 1025 | .react-tabs__tab { 1026 | display: inline-block; 1027 | border: 1px solid transparent; 1028 | border-bottom: none; 1029 | bottom: -1px; 1030 | position: relative; 1031 | list-style: none; 1032 | padding: 6px 12px; 1033 | cursor: pointer; 1034 | } 1035 | 1036 | .react-tabs__tab--selected { 1037 | background: #ffffff; 1038 | border-right-color: #aaaaaa; 1039 | color: #000000; 1040 | } 1041 | 1042 | .react-tabs__tab--disabled { 1043 | color: #6d6d6d; 1044 | cursor: default; 1045 | } 1046 | 1047 | .react-tabs__tab:focus { 1048 | outline: none; 1049 | } 1050 | 1051 | .react-tabs__tab:focus:after { 1052 | content: ""; 1053 | position: absolute; 1054 | height: 5px; 1055 | left: -4px; 1056 | right: -4px; 1057 | bottom: -5px; 1058 | background: #ffffff; 1059 | } 1060 | 1061 | .react-tabs__tab-panel { 1062 | display: none; 1063 | } 1064 | 1065 | .react-tabs__tab-panel--selected { 1066 | display: block; 1067 | padding: 10px; 1068 | } 1069 | 1070 | .react-tabs__tab-panel--selected h2 { 1071 | margin-bottom: 20px; 1072 | font-family: "Oswald", sans-serif; 1073 | text-transform: uppercase; 1074 | } 1075 | 1076 | .react-tabs__tab-panel--selected span { 1077 | line-height: 2; 1078 | display: inline-flex; 1079 | width: 120px; 1080 | } 1081 | 1082 | .react-tabs__tab--selected:last-child { 1083 | border-left: 1px solid #aaa; 1084 | } 1085 | 1086 | table.additional-info > tbody > tr > th { 1087 | padding: 13px 57px 13px 9px; 1088 | max-width: 100%; 1089 | } 1090 | 1091 | /* UPDATED ADD TO CART BUTTON */ 1092 | .buttons { 1093 | margin-top: 40px; 1094 | } 1095 | .buttons .add-to-cart, 1096 | .buttons .buy-now { 1097 | margin-top: 0; 1098 | } 1099 | 1100 | @keyframes shift-left { 1101 | 0% { 1102 | transform: translateX(0); 1103 | } 1104 | 100% { 1105 | transform: translateX(-40px); 1106 | } 1107 | } 1108 | 1109 | @keyframes shift-left-circle { 1110 | 0% { 1111 | transform: translateX(0); 1112 | } 1113 | 50% { 1114 | transform: translateX(-40px); 1115 | } 1116 | 100% { 1117 | transform: translateX(-40px); 1118 | } 1119 | } 1120 | 1121 | @keyframes shift-left-mask { 1122 | 0% { 1123 | height: 7px; 1124 | transform: translateX(0) rotate(0); 1125 | } 1126 | 50% { 1127 | transform: translateX(0) rotate(180deg); 1128 | } 1129 | 100% { 1130 | transform: translateX(-40px) rotate(180deg); 1131 | } 1132 | } 1133 | 1134 | .btn-cart { 1135 | display: block; 1136 | width: 200px; 1137 | border: none; 1138 | margin: 0 auto; 1139 | background: none; 1140 | background-color: #ffffff; 1141 | font-weight: 500; 1142 | color: white; 1143 | font-size: 14px; 1144 | position: relative; 1145 | cursor: pointer; 1146 | height: 45px; 1147 | border: 1px solid #f02d34; 1148 | font-size: 18px; 1149 | } 1150 | 1151 | .btn-cart:before { 1152 | content: ""; 1153 | display: block; 1154 | width: 12px; 1155 | height: 12px; 1156 | position: absolute; 1157 | border: 2px solid #f02d34; 1158 | transform: translateX(0); 1159 | left: 94px; 1160 | border-radius: 50%; 1161 | top: 5px; 1162 | box-sizing: border-box; 1163 | } 1164 | 1165 | .btn-cart:after { 1166 | content: ""; 1167 | position: absolute; 1168 | top: 0; 1169 | left: 0; 1170 | width: 100%; 1171 | height: 100%; 1172 | background: #f02d34; 1173 | transition: all 400ms cubic-bezier(0.175, 0.885, 0.32, 1.275); 1174 | } 1175 | 1176 | .btn-cart:focus { 1177 | outline: none; 1178 | } 1179 | 1180 | .btn-cart:focus:before { 1181 | animation: shift-left-circle 800ms forwards; 1182 | animation-delay: 1200ms; 1183 | } 1184 | 1185 | .btn-cart:focus:after { 1186 | width: 20px; 1187 | height: 20px; 1188 | top: 12px; 1189 | left: 90px; 1190 | animation: shift-left 400ms forwards; 1191 | animation-delay: 1200ms; 1192 | transition-delay: 400ms; 1193 | } 1194 | 1195 | .btn-cart:focus > span:before { 1196 | animation: shift-left-mask 800ms forwards; 1197 | animation-delay: 800ms; 1198 | height: 7px; 1199 | } 1200 | 1201 | .btn-cart:focus > span:after { 1202 | transform: translate(-30%, 0); 1203 | transition-delay: 1600ms; 1204 | opacity: 1; 1205 | } 1206 | 1207 | .btn-cart:focus > span span { 1208 | opacity: 0; 1209 | transform: translateY(20px); 1210 | } 1211 | 1212 | .btn-cart > span { 1213 | position: relative; 1214 | display: block; 1215 | } 1216 | 1217 | .btn-cart > span:before { 1218 | content: ""; 1219 | display: block; 1220 | position: absolute; 1221 | width: 12px; 1222 | height: 20px; 1223 | background: white; 1224 | top: 5px; 1225 | left: 94px; 1226 | animation-timing-function: linear; 1227 | transform: translateX(0) rotate(0deg); 1228 | transform-origin: center bottom; 1229 | } 1230 | 1231 | .btn-cart > span:after { 1232 | content: "Added"; 1233 | color: green; 1234 | position: absolute; 1235 | z-index: 3; 1236 | left: 50%; 1237 | opacity: 0; 1238 | transition: all 400ms cubic-bezier(0.175, 0.885, 0.32, 1.275); 1239 | transform: translate(-30%, 20px); 1240 | transition-delay: 0; 1241 | } 1242 | 1243 | .btn-cart span span { 1244 | display: inline-block; 1245 | position: relative; 1246 | z-index: 2; 1247 | transition: all 400ms cubic-bezier(0.175, 0.885, 0.32, 1.275); 1248 | transform: translateY(0px); 1249 | } 1250 | 1251 | /* FLIP IMAGES */ 1252 | .fliptile { 1253 | color: #fff; 1254 | position: relative; 1255 | overflow: hidden; 1256 | margin: 10px; 1257 | min-width: 220px; 1258 | max-width: 310px; 1259 | width: 100%; 1260 | color: #000000; 1261 | text-align: left; 1262 | font-size: 16px; 1263 | perspective: 50em; 1264 | } 1265 | .fliptile * { 1266 | box-sizing: padding-box; 1267 | transition: all 0.2s ease-out; 1268 | } 1269 | 1270 | .fliptile img { 1271 | max-width: 100%; 1272 | vertical-align: top; 1273 | } 1274 | 1275 | .fliptile figcaption { 1276 | top: 20px; 1277 | left: 20px; 1278 | right: 20px; 1279 | bottom: 20px; 1280 | padding: 20px; 1281 | position: absolute; 1282 | opacity: 0; 1283 | z-index: 1; 1284 | transform: translateY(40px); 1285 | } 1286 | 1287 | .fliptile h2 { 1288 | margin: 0 0 5px; 1289 | } 1290 | 1291 | .fliptile h2 { 1292 | font-weight: 600; 1293 | } 1294 | 1295 | .fliptile:after { 1296 | background-color: rgb(0, 0, 0, 0.1); 1297 | position: absolute; 1298 | content: ""; 1299 | display: block; 1300 | top: 20px; 1301 | left: 20px; 1302 | right: 20px; 1303 | bottom: 20px; 1304 | transition: all 0.4s ease-in-out; 1305 | transform: rotateX(-90deg); 1306 | transform-origin: 50% 50%; 1307 | opacity: 0; 1308 | } 1309 | 1310 | .fliptile:hover figcaption, 1311 | .fliptile.hover figcaption { 1312 | transform: translateY(0%); 1313 | opacity: 1; 1314 | transition-delay: 0.2s; 1315 | } 1316 | 1317 | .fliptile:hover:after, 1318 | .fliptile.hover:after { 1319 | transform: rotateX(0); 1320 | opacity: 0.9; 1321 | } 1322 | 1323 | /* UPDATED STAR COUNT */ 1324 | .star-rating button { 1325 | background-color: transparent; 1326 | border: none; 1327 | outline: none; 1328 | cursor: pointer; 1329 | } 1330 | 1331 | .star { 1332 | width: 16px; 1333 | height: 16px; 1334 | display: flex; 1335 | } 1336 | 1337 | .on { 1338 | color: #f02d34; 1339 | fill: #f02d34; 1340 | } 1341 | 1342 | .off { 1343 | color: #d3d3d3; 1344 | fill: #ffffff; 1345 | } 1346 | 1347 | .on > path { 1348 | color: #f02d34; 1349 | fill: #f02d34; 1350 | } 1351 | --------------------------------------------------------------------------------