├── .babelrc
├── .gitignore
├── .prettierignore
├── .vscode
└── settings.json
├── README.md
├── components
├── About
│ ├── Contacts
│ │ ├── ContactButtons.tsx
│ │ └── ContactMap.tsx
│ ├── FAQ
│ │ ├── FaqQuestions.tsx
│ │ └── faqSections.tsx
│ └── HowItWorks
│ │ ├── HowItWorksOffline.tsx
│ │ └── HowItWorksOnline.tsx
├── Account
│ ├── AccountSections.tsx
│ ├── Addresses
│ │ ├── AddressListItem.tsx
│ │ ├── AddressesList.tsx
│ │ └── AddressesPage.tsx
│ └── Orders
│ │ ├── OrderDetails.tsx
│ │ ├── OrderSingleItem.tsx
│ │ ├── OrdersList.tsx
│ │ └── OrdersPage.tsx
├── Cart
│ ├── AddToCart.tsx
│ ├── Cart.tsx
│ ├── CartButton.tsx
│ ├── CartItem.tsx
│ ├── CartPageItems.tsx
│ ├── CartPayment.tsx
│ ├── RemoveFromCart.tsx
│ └── UpdateCartQuantity.tsx
├── Checkout
│ ├── AddressForm.tsx
│ ├── AddressListItem.tsx
│ ├── AddressListItems.tsx
│ ├── Checkout.tsx
│ ├── Customer
│ │ ├── AddressModal.js
│ │ ├── CheckoutSpedition.tsx
│ │ └── CustomerCheckout.tsx
│ ├── Guest
│ │ ├── CheckoutGuestSuggestLogin.tsx
│ │ ├── GuestCheckout.tsx
│ │ └── GuestWarning.tsx
│ ├── TotalSummary.tsx
│ └── index.ts
├── Home
│ ├── FeaturedProducts.tsx
│ ├── FrontCategory.tsx
│ └── FrontPageCategories.tsx
├── Items
│ ├── ItemsList.tsx
│ ├── ProductCard.tsx
│ └── RelatedItems.tsx
├── Shop
│ ├── ShopHeader.tsx
│ ├── ShopItems.tsx
│ ├── SingleProduct.tsx
│ ├── SingleProductImages.tsx
│ ├── SingleProductInventory.tsx
│ └── SingleProductNote.tsx
├── Signin
│ ├── LoginButton.tsx
│ ├── LoginFormWrapper.tsx
│ ├── PasswordForgetForm.tsx
│ ├── SignOutButton.tsx
│ ├── SigninForm.tsx
│ └── SignupForm.tsx
├── Structure
│ ├── Footer
│ │ ├── Footer.tsx
│ │ ├── FooterContainer.tsx
│ │ └── FooterMobile
│ │ │ ├── FooterMobile.tsx
│ │ │ ├── FooterMobileDrawLinks.tsx
│ │ │ ├── FooterMobileDrawLinksStyle.ts
│ │ │ ├── FooterMobileIcons.tsx
│ │ │ └── FooterMobileIconsStyle.ts
│ ├── Header
│ │ ├── HeaderLinks.tsx
│ │ ├── HeaderLinksStyle.ts
│ │ └── HeaderMobileLinks.tsx
│ ├── Layouts
│ │ ├── CheckoutLayout.tsx
│ │ ├── NoContainerLayout.tsx
│ │ └── PageLayout.tsx
│ ├── Meta.tsx
│ ├── SectionBlock.tsx
│ ├── Sections.tsx
│ └── Title.tsx
└── utils
│ ├── BouncingLoader.tsx
│ ├── Components
│ ├── Button
│ │ ├── Button.tsx
│ │ └── ButtonStyle.tsx
│ ├── Card
│ │ ├── Card.tsx
│ │ ├── CardBody.tsx
│ │ ├── CardFooter.tsx
│ │ └── CardHeader.tsx
│ ├── Carousel
│ │ ├── Carousel.tsx
│ │ └── CarouselStyles.tsx
│ ├── CustomInput.tsx
│ ├── Dropdown
│ │ ├── Dropdown.tsx
│ │ └── DropdownStyle.tsx
│ ├── ErrorMessage.tsx
│ ├── Header
│ │ ├── Header.tsx
│ │ └── HeaderStyles.tsx
│ ├── Parallax.tsx
│ └── TooltipStyle.tsx
│ ├── GlobalStyles
│ ├── Container.ts
│ ├── FormWrapper.tsx
│ ├── GlobalCss.ts
│ └── PageTitle.tsx
│ ├── ImageBordered.tsx
│ ├── NumericInput.tsx
│ ├── Toast
│ └── Toast.js
│ ├── index.ts
│ ├── lib
│ ├── calcTotalPrice.ts
│ ├── formatDate.ts
│ ├── formatMoney.ts
│ ├── formatOrderStatus.ts
│ ├── numberUtility.ts
│ └── stringUtils.ts
│ └── styles.ts
├── constants
├── categories.ts
└── routes.ts
├── frontend-structure
├── address
│ ├── hooks
│ │ ├── useAddresses.ts
│ │ └── usePopulateCheckoutAddress.ts
│ ├── mutations
│ │ └── MUTATION_CHECKOUT_ADDRESS.ts
│ └── queries
│ │ └── QUERY_GET_ADDRESSES.ts
├── checkout
│ ├── hooks
│ │ ├── useAddToCart.ts
│ │ ├── useCart.ts
│ │ ├── useCheckoutUrl.ts
│ │ ├── useRemoveFromCart.ts
│ │ └── useUpdateItem.ts
│ ├── mutations
│ │ ├── MUTATION_ADD_TO_CART.ts
│ │ ├── MUTATION_REMOVE_FROM_CART.ts
│ │ └── MUTATION_UPDATE_CART_ITEM.ts
│ └── queries
│ │ ├── QUERY_GET_CART.ts
│ │ └── QUERY_GET_CHECKOUT_URL.ts
├── order
│ ├── hooks
│ │ ├── useOrder.ts
│ │ └── useOrders.ts
│ └── queries
│ │ ├── QUERY_GET_ORDER.ts
│ │ └── QUERY_GET_ORDERS.ts
├── product
│ ├── hooks
│ │ ├── useProductAvailability.ts
│ │ └── useProducts.ts
│ └── queries
│ │ ├── QUERY_GET_PRODUCTS.ts
│ │ └── QUERY_GET_PRODUCT_AVAILABILITY.ts
└── user
│ ├── hooks
│ ├── useCustomer.ts
│ ├── useLogin.ts
│ ├── useLogout.ts
│ └── useSignup.ts
│ ├── mutations
│ ├── MUTATION_SIGNIN_EMAIL.ts
│ ├── MUTATION_SIGNOUT.ts
│ └── MUTATION_SIGNUP_EMAIL.ts
│ └── queries
│ └── QUERY_GET_CUSTOMER.ts
├── generated
├── nexus-typegen.ts
├── schema.graphql
└── shopify.model.ts
├── graphql-shopify
├── address
│ ├── functions
│ │ └── manage-addresses.ts
│ ├── mutations
│ │ └── MUTATION_POPULATE_CHECKOUT_ADDRESS.ts
│ └── queries
│ │ └── QUERY_GET_ADDRESSES.ts
├── category
│ ├── functions
│ │ ├── categories-from-prisma.ts
│ │ ├── index.ts
│ │ └── manage-collection.ts
│ └── queries
│ │ ├── QUERY_GET_ALL_COLLECTIONS_NAMES.ts
│ │ ├── QUERY_GET_COLLECTION.ts
│ │ └── QUERY_GET_COLLECTIONS.ts
├── checkout
│ ├── checkout-interface.ts
│ ├── mutations
│ │ ├── MUTATION_CHECKOUT_CREATE.ts
│ │ ├── MUTATION_CHECKOUT_ITEM_ADD.ts
│ │ ├── MUTATION_CHECKOUT_ITEM_REMOVE.ts
│ │ └── MUTATION_CHECKOUT_ITEM_UPDATE.ts
│ ├── queries
│ │ └── QUERY_GET_CHECKOUT.ts
│ └── utils
│ │ ├── checkout-create.ts
│ │ ├── checkout-to-cart.ts
│ │ └── normalize.ts
├── common
│ ├── functions
│ │ ├── get-checkout-id.ts
│ │ └── get-checkout-url.ts
│ └── utils
│ │ └── normalize.ts
├── context.ts
├── order
│ ├── QUERY_GET_ORDER_DETAILS.ts
│ ├── functions
│ │ └── manage-order.ts
│ └── queries
│ │ └── QUERY_GET_USER_ORDERS.ts
├── product
│ ├── functions
│ │ ├── index.ts
│ │ └── manage-products.ts
│ ├── product-interface.ts
│ ├── queries
│ │ ├── QUERY_GET_ALL_PRODUCT_NAMES.ts
│ │ ├── QUERY_GET_PRODUCT.ts
│ │ ├── QUERY_GET_PRODUCTS.ts
│ │ └── QUERY_GET_PRODUCT_AVAILABILITY.ts
│ └── utils
│ │ └── normalize.ts
├── schema.ts
├── types
│ ├── Address.ts
│ ├── Category.ts
│ ├── Checkout.ts
│ ├── Common.ts
│ ├── Order.ts
│ ├── Product.ts
│ ├── User.ts
│ └── index.ts
└── user
│ ├── mutations
│ ├── MUTATION_USER_ASSOCIATE_CHECKOUT.ts
│ ├── MUTATION_USER_CREATE.ts
│ ├── MUTATION_USER_CREATE_ACCESSTOKEN.ts
│ ├── MUTATION_USER_DELETE_ACCESSTOKEN.ts
│ └── MUTATION_USER_DISASSOCIATE_CHECKOUT.ts
│ └── queries
│ └── QUERY_USER_GET_CUSTOMER.ts
├── lib
└── auth
│ ├── auth-cookies.ts
│ └── auth-session.ts
├── media
└── frontpage.png
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── [category].tsx
├── [category]
│ └── [slug].tsx
├── _app.tsx
├── _document.tsx
├── _error.js
├── about
│ ├── contacts.tsx
│ ├── faq.tsx
│ └── howitworks.tsx
├── account.tsx
├── api
│ └── graphql.ts
├── cart.tsx
├── checkout.tsx
├── index.tsx
├── login.tsx
└── thank-you.js
├── prisma
└── schema.prisma
├── public
├── favicon.ico
├── favicon.png
├── fonts
│ └── DoodlesOfFun.ttf
├── images
│ ├── car.png
│ ├── consegna-gratuita.png
│ ├── fantasy-land.jpg
│ ├── lmn-icon.png
│ ├── macchina-da-cucito.svg
│ ├── macchina-per-cucito.png
│ ├── mastercard-logo.png
│ ├── package.png
│ ├── paypal-logo.png
│ ├── profile-bg.jpg
│ ├── sarta.png
│ ├── sartoria.png
│ ├── shop.png
│ └── visa-logo.png
└── nprogress.css
├── shopify-codegen.yml
└── tsconfig.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": [
4 | [
5 | "styled-components",
6 | { "ssr": true, "displayName": true, "preprocess": false }
7 | ]
8 | ]
9 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .cache
2 | .next
3 | package.json
4 | package-lock.json
5 | public
6 | node_modules
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.colorCustomizations": {
3 | "titleBar.activeBackground": "#66ff66",
4 | "titleBar.inactiveBackground": "#66ff66"
5 | },
6 | "explorer.autoReveal": false,
7 | "todo-tree.customHighlight": {
8 | "TODO": {
9 | "icon": "check",
10 | "type": "line",
11 | "iconColour": "yellow",
12 | "foreground": "red",
13 | "background": "yellow"
14 | },
15 | "FIXME": {
16 | "icon": "beaker",
17 | "iconColour": "red",
18 | "foreground": "white",
19 | "background": "red"
20 | }
21 | },
22 | "todo-tree.highlights.customHighlight": {
23 | "TODO": {
24 | "icon": "check",
25 | "type": "line",
26 | "iconColour": "yellow",
27 | "foreground": "red",
28 | "background": "yellow"
29 | },
30 | "FIXME": {
31 | "icon": "beaker",
32 | "iconColour": "red",
33 | "foreground": "white",
34 | "background": "red"
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | A simple demo e-commerce, built with NextJS and Shopify: https://nextjs-shopify-omega.vercel.app/
3 |
4 |
5 | 
6 |
7 | ## Features
8 |
9 | - NextJS
10 | - Typescript
11 | - Next API
12 | - Shopify
13 | - [Nexus](https://nexusjs.org/)
14 | - Graphql
15 | - Styled Components
16 |
17 | ### If this project helped you, please consider giving it a star! It would mean a lot to me :)
18 |
19 | ## 🚀 Quick start
20 |
21 | 1. **Clone the repository**
22 |
23 | Use git to clone the repository.
24 |
25 | ```sh
26 | # clone the project using Git
27 | git clone https://github.com/escapemanuele/nextjs-shopify your-project-name
28 | ```
29 |
30 | 2. **Install the packages**
31 |
32 | Navigate into your new site’s directory and install the required packages.
33 |
34 | ```sh
35 | cd your-project-name
36 | npm install
37 | ```
38 |
39 | 3. **Required Environmental Variables**
40 |
41 | COOKIE_CRYPT=a-complex-enough-crypt-key
42 | SHOPIFY_GRAPHQL_ENDPOINT=https://your-shop-name.myshopify.com/api/latest-graphql-version/graphql.json
43 | STOREFRONT_ACCESS_TOKEN=...(retrieve it from shopify)
44 | LOCAL_API_GRAPHQL_ENDPOINT=your-url/api/graphql (localhost:PORT/api/graphql in env.development)
45 |
46 | 4. **Start developing.**
47 |
48 | Finally you are ready to see your project. Start the engine!
49 |
50 | ```sh
51 | npm run start
52 | ```
53 |
54 | 5. **Open the source code and start editing!**
55 |
56 | Your site is now running at `http://localhost:8006`!
57 |
58 | Feel free to change everything you want!
59 |
--------------------------------------------------------------------------------
/components/About/Contacts/ContactButtons.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button, styles } from "../../utils";
3 | import styled from "styled-components";
4 |
5 | const ContactsButtons = () => {
6 | return (
7 |
8 | {/*
9 | CALL ME
10 |
14 | */}
15 |
16 | Find me on Linkedin
17 |
25 |
26 |
27 | Find me on Github
28 |
36 |
37 |
38 | );
39 | };
40 |
41 | const ContactsButtonsWrapper = styled.div`
42 | display: flex;
43 | flex-direction: column;
44 |
45 | margin-top: 2rem;
46 |
47 | @media (min-width: ${styles.size.tablet}) {
48 | flex-direction: row;
49 | justify-content: space-evenly;
50 | }
51 | `;
52 |
53 | const ContactsItem = styled.div`
54 | display: flex;
55 | justify-content: space-around;
56 | align-items: center;
57 |
58 | font-size: 1.3rem;
59 | font-weight: 600;
60 | text-align: center;
61 |
62 | div {
63 | flex: 1;
64 | }
65 | a {
66 | flex: 1;
67 | }
68 |
69 | .socialIcons {
70 | font-size: 20px !important;
71 | margin-right: 1rem;
72 | position: relative;
73 | }
74 |
75 | @media (min-width: ${styles.size.tablet}) {
76 | flex-direction: column;
77 | }
78 | `;
79 |
80 | export default ContactsButtons;
81 |
--------------------------------------------------------------------------------
/components/About/Contacts/ContactMap.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { styles } from "../../utils";
4 |
5 | const ContactMap = () => {
6 | return (
7 |
8 |
9 |
Si riceve su appuntamento!
10 |
11 |
12 | Viale Abruzzi, 430, 47521 Cesena FC
13 |
14 |
15 |
16 | 351 559 5121
17 |
18 |
19 |
20 |
28 |
29 |
30 | );
31 | };
32 |
33 | const MapWrapper = styled.div`
34 | display: flex;
35 | flex-direction: column-reverse;
36 |
37 | margin-bottom: 2rem;
38 | margin-top: 2rem;
39 | padding: 1 rem;
40 |
41 | .info_content {
42 | font-size: 1.4rem;
43 | text-align: center;
44 |
45 | display: flex;
46 | flex-direction: column;
47 |
48 | div {
49 | font-weight: 400;
50 | margin-top: 1rem;
51 | }
52 |
53 | .socialIcons {
54 | font-size: 1.8rem !important;
55 | margin-right: 1rem;
56 | position: relative;
57 | }
58 | }
59 |
60 | @media (min-width: ${styles.size.tablet}) {
61 | display: grid;
62 | grid-template-columns: 1fr 1fr;
63 | justify-items: stretch;
64 | align-items: center;
65 | }
66 | `;
67 |
68 | export default ContactMap;
69 |
--------------------------------------------------------------------------------
/components/About/FAQ/FaqQuestions.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import styled from "styled-components";
4 | import { styles } from "../../utils";
5 |
6 | const FaqQuestions = ({ question }) => {
7 | if (!question)
8 | return Le risposte alle tue domande arriveranno presto!
;
9 | return (
10 |
11 | {question.title}
12 |
13 | {question.questions.map(q => (
14 | -
15 |
{q.question}
16 | {q.answer}
17 |
18 | ))}
19 |
20 |
21 | );
22 | };
23 |
24 | const FaqWrapper = styled.div`
25 | margin-top: 3rem;
26 |
27 | h2 {
28 | font-size: 2rem;
29 | }
30 |
31 | ul {
32 | list-style: none;
33 |
34 | li {
35 | border-bottom: 1px solid ${styles.colors.lightGrey};
36 | padding: 1rem;
37 |
38 | div:first-child {
39 | font-size: 1.4rem;
40 | font-weight: 700;
41 | margin-bottom: 1rem;
42 | }
43 |
44 | div:nth-child(2) {
45 | font-weight: 300;
46 | }
47 | }
48 | }
49 | `;
50 |
51 | FaqQuestions.propTypes = {
52 | question: PropTypes.shape({
53 | type: PropTypes.string.isRequired,
54 | title: PropTypes.string.isRequired,
55 | questions: PropTypes.arrayOf(
56 | PropTypes.shape({
57 | question: PropTypes.string.isRequired,
58 | answer: PropTypes.string.isRequired
59 | })
60 | ).isRequired
61 | }).isRequired
62 | };
63 |
64 | export default FaqQuestions;
65 |
--------------------------------------------------------------------------------
/components/About/FAQ/faqSections.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FaTruck, FaMapMarkedAlt } from "react-icons/fa";
3 |
4 | export default [
5 | {
6 | icon: null,
7 | iconClass: "fas fa-cut",
8 | title: "Repairs",
9 | link: "Repairs",
10 | },
11 | {
12 | icon: ,
13 | title: "Shipping",
14 | link: "Shipping",
15 | },
16 | {
17 | icon: ,
18 | title: "Payments",
19 | link: "Payment",
20 | },
21 | ];
22 |
23 | export const QUESTIONS = [
24 | {
25 | type: "Repairs",
26 | title: "Questions about things",
27 | questions: [
28 | {
29 | question: "How much does shipping cost?",
30 | answer:
31 | "Shipping is free in the United States, $30 flat rate per order elsewhere. For deliveries outside of the US, additional import duties may be applied upon receipt of your order.",
32 | },
33 | {
34 | question: "Why ...?",
35 | answer: "Because I eat a lot of carrots",
36 | },
37 | ],
38 | },
39 | {
40 | type: "Shipping",
41 | title: "Questions about shipping",
42 | questions: [
43 | {
44 | question: "How much does shipping cost?",
45 | answer:
46 | "Shipping is free in the United States, $30 flat rate per order elsewhere. For deliveries outside of the US, additional import duties may be applied upon receipt of your order.",
47 | },
48 | {
49 | question: "Why ...?",
50 | answer: "Because I eat a lot of carrots",
51 | },
52 | ],
53 | },
54 | {
55 | type: "Payment",
56 | title: "Questions about payments",
57 | questions: [
58 | {
59 | question: "What currency are your prices listed in?",
60 | answer: "All of our prices are in Euro (EUR).",
61 | },
62 | {
63 | question: "Why ...?",
64 | answer: "Because I eat a lot of carrots",
65 | },
66 | ],
67 | },
68 | ];
69 |
--------------------------------------------------------------------------------
/components/Account/AccountSections.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FaTruck, FaMapMarkedAlt } from "react-icons/fa";
3 | import OrdersPage from "./Orders/OrdersPage";
4 | import AddressesPage from "./Addresses/AddressesPage";
5 |
6 | export default [
7 | {
8 | icon: ,
9 | title: "Orders",
10 | description: "Show past orders",
11 | link: "orders",
12 | component: ,
13 | },
14 | {
15 | icon: ,
16 | title: "Shipping addresses",
17 | description: "Edit your shipping addresses",
18 | link: "addresses",
19 | component: ,
20 | },
21 | ];
22 |
--------------------------------------------------------------------------------
/components/Account/Addresses/AddressListItem.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { styles } from "../../utils";
4 |
5 | const AddressListItem = ({ address }) => {
6 | const {
7 | id,
8 | firstName,
9 | lastName,
10 | address1,
11 | address2,
12 | city,
13 | country,
14 | province,
15 | zip,
16 | active,
17 | } = address;
18 |
19 | return (
20 |
21 |
22 |
23 | {firstName} {lastName}
24 |
25 |
{address1}
26 |
27 | {zip} {city} {province} {country}
28 |
29 |
30 | {/*
31 | {active ? (
32 |
33 | ) : (
34 |
41 | )}
42 |
50 |
*/}
51 |
52 | );
53 | };
54 |
55 | const AddressItemWrapper = styled.li`
56 | display: flex;
57 | flex-direction: column;
58 | align-items: center;
59 |
60 | border: 1px solid ${styles.colors.lightGrey};
61 | margin: 0 auto;
62 | padding: 1rem;
63 | width: 90%;
64 |
65 | .address_info {
66 | flex-grow: 3;
67 |
68 | display: flex;
69 | flex-direction: column;
70 |
71 | cursor: pointer;
72 | margin-left: 1rem;
73 | margin-bottom: 1rem;
74 |
75 | .first_line {
76 | font-weight: 600;
77 | }
78 | }
79 |
80 | .address_buttons {
81 | flex-grow: 2;
82 | }
83 |
84 | @media (min-width: ${styles.size.tablet}) {
85 | flex-direction: row;
86 |
87 | width: 60%;
88 | }
89 | `;
90 |
91 | export default AddressListItem;
92 |
--------------------------------------------------------------------------------
/components/Account/Addresses/AddressesList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useQuery } from "react-apollo";
3 |
4 | import styled from "styled-components";
5 | import useAddresses from "../../../frontend-structure/address/hooks/useAddresses";
6 | import BouncingLoader from "../../utils/BouncingLoader";
7 | import AddressListItem from "./AddressListItem";
8 |
9 | const AddressesList = () => {
10 | const [data, loading] = useAddresses();
11 |
12 | if (loading) return ;
13 |
14 | if (!data || !data.addresses)
15 | return Errore nel recupero degli indirizzi
;
16 |
17 | return (
18 |
19 |
20 | {data.addresses.map((address) => (
21 |
22 | ))}
23 |
24 |
25 | );
26 | };
27 |
28 | const AddressesListWrapper = styled.ul`
29 | padding: 0;
30 | `;
31 |
32 | AddressesList.propTypes = {};
33 |
34 | export default AddressesList;
35 |
--------------------------------------------------------------------------------
/components/Account/Addresses/AddressesPage.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Head from "next/head";
3 |
4 | import { PageTitle } from "../../utils";
5 | import AddressesList from "./AddressesList";
6 |
7 | const AddressesPage = () => (
8 | <>
9 |
10 | Addresses | Shopify Store
11 |
12 | My addresses
13 |
14 | >
15 | );
16 |
17 | export default AddressesPage;
18 |
--------------------------------------------------------------------------------
/components/Account/Orders/OrderSingleItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import PropTypes from "prop-types";
3 | import styled from "styled-components";
4 | import { Hidden } from "@material-ui/core";
5 | import ArrowRight from "@material-ui/icons/ArrowRight";
6 | import {
7 | Button,
8 | formatMoney,
9 | formatOrderStatus,
10 | formatDate,
11 | styles,
12 | } from "../../utils";
13 | import OrderDetails from "./OrderDetails";
14 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
15 |
16 | interface SingleOrderProp {
17 | order: NexusGenObjects["Order"];
18 | }
19 |
20 | const OrderSingleItem = ({ order }: SingleOrderProp) => {
21 | const [showDetails, setShowDetails] = useState(false);
22 |
23 | const { id, processedAt, amount, fulfillment } = order;
24 |
25 | return (
26 | <>
27 |
28 |
29 | {formatDate(processedAt)}
30 | {formatMoney(amount)}
31 | {formatOrderStatus(fulfillment)}
32 |
39 |
40 |
41 |
42 | setShowDetails(!showDetails)}>
43 |
44 |
{formatDate(processedAt)}
45 |
{formatMoney(amount)}
46 |
47 | {formatOrderStatus(fulfillment)}
48 |
49 |
50 |
51 | {showDetails && }
52 | >
53 | );
54 | };
55 |
56 | const OrderItemWrapper = styled.li`
57 | display: grid;
58 | grid-template-columns: repeat(3, 1fr) 80px;
59 | justify-items: center;
60 | align-items: center;
61 | `;
62 |
63 | const OrderItemMobileWrapper = styled.li`
64 | border-bottom: 1px solid ${styles.colors.lightGrey};
65 | ${styles.transDefault};
66 |
67 | display: flex;
68 | justify-content: space-around;
69 | align-items: center;
70 |
71 | svg {
72 | font-size: 2rem;
73 | ${styles.transDefault};
74 | }
75 |
76 | svg.arrowDown {
77 | transform: rotate(90deg);
78 | }
79 | `;
80 |
81 | export default OrderSingleItem;
82 |
--------------------------------------------------------------------------------
/components/Account/Orders/OrdersList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import OrderSingleItem from "./OrderSingleItem";
4 | import { styles } from "../../utils";
5 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
6 |
7 | interface ListOrderProps {
8 | orders: Array;
9 | }
10 |
11 | const OrdersList: React.FC = ({ orders = [] }) => {
12 | return (
13 |
14 | {orders && orders.length > 0 ? (
15 |
16 |
17 | Data
18 | Totale
19 | Stato Ordine
20 |
21 |
22 | {orders.map((order) => (
23 |
24 | ))}
25 |
26 | ) : (
27 |
Nessun ordine presente
28 | )}
29 |
30 | );
31 | };
32 |
33 | const OrdersListWrapper = styled.ul`
34 | list-style: none;
35 | padding-left: 0;
36 |
37 | .orderlist_header {
38 | display: none;
39 | }
40 |
41 | @media (min-width: ${styles.size.tablet}) {
42 | .orderlist_header {
43 | display: grid;
44 | grid-template-columns: repeat(3, 1fr) 80px;
45 | justify-items: center;
46 |
47 | text-transform: uppercase;
48 | font-weight: 600;
49 | font-size: 1.5rem;
50 | }
51 | }
52 | `;
53 |
54 | export default OrdersList;
55 |
--------------------------------------------------------------------------------
/components/Account/Orders/OrdersPage.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Head from "next/head";
3 | import { useQuery } from "react-apollo";
4 | import OrdersList from "./OrdersList";
5 | import { BouncingLoader, PageTitle } from "../../utils";
6 | import useOrders from "../../../frontend-structure/order/hooks/useOrders";
7 |
8 | const OrdersPage = () => {
9 | const [data, loading] = useOrders();
10 |
11 | return (
12 | <>
13 |
14 | Orders | Shopify Store
15 |
16 | My Orders
17 | {!data || loading ? (
18 |
19 | ) : (
20 |
21 | )}
22 | >
23 | );
24 | };
25 |
26 | export default OrdersPage;
27 |
--------------------------------------------------------------------------------
/components/Cart/AddToCart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 | import Favorite from "@material-ui/icons/Favorite";
4 | import { Button, NumericInput, styles } from "../utils";
5 | import { successToast, errorToast } from "../utils/Toast/Toast";
6 | import useAddToCart from "../../frontend-structure/checkout/hooks/useAddToCart";
7 |
8 | const AddToCart = ({ id, note, maxQuantity }) => {
9 | const [quantity, setQuantity] = useState(1);
10 |
11 | const [addToCart, loading] = useAddToCart();
12 |
13 | const handleAddToCart = async () => {
14 | addToCart({
15 | variables: {
16 | variantId: id,
17 | quantity: quantity,
18 | note: note,
19 | },
20 | })
21 | .then(({ data }) => {
22 | const res = data.addToCart;
23 | if (res.success) {
24 | successToast("Aggiunto al carrello!");
25 | } else {
26 | errorToast("Errore nell'aggiunta al carrello");
27 | }
28 | })
29 | .catch((err) => {
30 | errorToast("Errore nell'aggiunta al carrello");
31 | });
32 | };
33 |
34 | return (
35 |
36 |
44 |
54 |
55 | );
56 | };
57 |
58 | AddToCart.defaultProps = {
59 | note: null,
60 | maxQuantity: null,
61 | };
62 |
63 | const AddToCartWrapper = styled.div`
64 | display: flex;
65 | flex-direction: column;
66 | justify-content: center;
67 |
68 | @media (min-width: ${styles.size.laptop}) {
69 | flex-direction: row;
70 | .numeric {
71 | flex: 1;
72 | }
73 |
74 | .addButton {
75 | flex: 2;
76 | margin: 0 0 0 5px;
77 | }
78 | }
79 | `;
80 |
81 | export default AddToCart;
82 |
--------------------------------------------------------------------------------
/components/Cart/CartButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useQuery } from "react-apollo";
3 | import styled from "styled-components";
4 | import ShoppingCartIcon from "@material-ui/icons/ShoppingCart";
5 |
6 | import Link from "next/link";
7 | import { CART } from "../../constants/routes";
8 | import QUERY_GET_CART from "../../frontend-structure/checkout/queries/QUERY_GET_CART";
9 | import useCart from "../../frontend-structure/checkout/hooks/useCart";
10 |
11 | const CartButton = (props) => {
12 | const [data, loading] = useCart();
13 | // const { data, loading } = useQuery(QUERY_GET_CART);
14 |
15 | const getCartItems = (cartItems) =>
16 | cartItems.reduce((sum, cartItem) => sum + cartItem.quantity, 0);
17 |
18 | return (
19 |
20 |
21 |
22 | {data?.cart?.cartItems && (
23 | {getCartItems(data.cart.cartItems)}
24 | )}
25 |
26 |
27 | );
28 | };
29 |
30 | const Dot = styled.div`
31 | background: red;
32 | color: white;
33 | border-radius: 50%;
34 | min-width: 1rem;
35 | font-weight: 100;
36 | font-feature-settings: "tnum";
37 | font-variant-numeric: tabular-nums;
38 | `;
39 |
40 | export default CartButton;
41 |
--------------------------------------------------------------------------------
/components/Cart/CartItem.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import PropTypes from "prop-types";
4 | import Link from "next/link";
5 | import RemoveFromCart from "./RemoveFromCart";
6 | import { styles, formatMoney } from "../utils";
7 | import { ITEM } from "../../constants/routes";
8 |
9 | const CartItem = ({ cartItem }) => {
10 | if (!cartItem) {
11 | return Questo elemento è stato rimosso!
;
12 | }
13 |
14 | return (
15 |
16 | {/*
17 |
18 |
19 |
20 | */}
21 |
22 |
{cartItem.title}
23 |
24 | {/* {formatMoney(item.price * cartItem.quantity)} */}
25 | {" - "}
26 |
27 | {cartItem.quantity} ×
28 | {formatMoney(cartItem.price)}
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | CartItem.propTypes = {
38 | cartItem: PropTypes.shape({
39 | id: PropTypes.string.isRequired,
40 | item: PropTypes.shape({
41 | id: PropTypes.string.isRequired,
42 | title: PropTypes.string.isRequired,
43 | description: PropTypes.string,
44 | shortDescription: PropTypes.string,
45 | image: PropTypes.string.isRequired,
46 | price: PropTypes.number.isRequired,
47 | }).isRequired,
48 | quantity: PropTypes.number.isRequired,
49 | }).isRequired,
50 | };
51 |
52 | const CartItemWrapper = styled.li`
53 | padding: 1rem;
54 | border-bottom: 1px solid ${styles.colors.mainGrey};
55 | display: grid;
56 | align-items: center;
57 | grid-template-columns: auto 1fr auto;
58 | img {
59 | margin-right: 10px;
60 | width: 4rem;
61 | }
62 | h3,
63 | p {
64 | margin: 0;
65 | }
66 | `;
67 |
68 | export default CartItem;
69 |
--------------------------------------------------------------------------------
/components/Cart/CartPayment.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled, { css } from "styled-components";
3 | import { styles, Button, calcTotalPrice, formatMoney } from "../utils";
4 | import { CHECKOUT } from "../../constants/routes";
5 | import { NexusGenObjects } from "../../generated/nexus-typegen";
6 | import { string } from "prop-types";
7 |
8 | interface CartPaymentProp {
9 | cartItems: Array;
10 | }
11 |
12 | const CartPayment = ({ cartItems }: CartPaymentProp) => (
13 |
14 | Total
15 |
19 |
20 |
21 | {cartItems.length && (
22 |
25 | )}
26 |
27 | );
28 |
29 | interface TotalLineProp {
30 | title: string;
31 | price: string;
32 | titleBold?: boolean;
33 | priceSuccess?: boolean;
34 | }
35 |
36 | const TotalLine = ({
37 | title,
38 | price,
39 | titleBold = false,
40 | priceSuccess = false,
41 | }: TotalLineProp) => (
42 |
43 | {title}
44 | {price}
45 |
46 | );
47 |
48 | const CartPaymentWrapper = styled.div`
49 | width: 100%;
50 |
51 | h3 {
52 | font-size: 1.5rem;
53 | }
54 |
55 | a {
56 | display: block;
57 | margin: 0 auto;
58 | }
59 |
60 | @media (min-width: ${styles.size.laptop}) {
61 | border: 1px solid ${styles.colors.lightGrey};
62 | border-radius: 5px;
63 | ${styles.boxColors.primaryBoxShadow};
64 | padding: 0.5rem;
65 | order: 1;
66 | }
67 | `;
68 |
69 | const TotalLineWrapper = styled.div<{
70 | titleBold: boolean;
71 | priceSuccess: boolean;
72 | }>`
73 | display: flex;
74 | justify-content: space-between;
75 |
76 | .line-title {
77 | ${(props) =>
78 | props.titleBold &&
79 | css`
80 | font-weight: bold;
81 | `}
82 | }
83 |
84 | .line-price {
85 | ${(props) =>
86 | props.priceSuccess &&
87 | css`
88 | color: ${styles.colors.successColor};
89 | `}
90 | }
91 | `;
92 |
93 | export default CartPayment;
94 |
--------------------------------------------------------------------------------
/components/Cart/RemoveFromCart.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { successToast, errorToast } from "../utils/Toast/Toast";
3 | import { Button } from "../utils";
4 | import useRemoveFromCart from "../../frontend-structure/checkout/hooks/useRemoveFromCart";
5 |
6 | const RemoveFromCart: React.FC<{ cartItemId: string }> = ({ cartItemId }) => {
7 | const [removeFromCart, loading] = useRemoveFromCart();
8 |
9 | const handleRemove = async () => {
10 | removeFromCart({
11 | variables: {
12 | itemId: cartItemId,
13 | },
14 | }).then(({ data }) => {
15 | const res = data.removeFromCart;
16 | if (res.success) {
17 | successToast("Elemento rimosso con successo!");
18 | } else {
19 | errorToast("Errore nella rimozione dal carrello");
20 | }
21 | });
22 | //TODO: Check for optimisticResponse
23 | // await removeFromCart({
24 | // variables: { id },
25 | // optimisticResponse: {
26 | // __typename: "Mutation",
27 | // removeFromCart: {
28 | // __typename: "CartItem",
29 | // id,
30 | // },
31 | // },
32 | // update: (proxy, payload) => {
33 | // const cache = proxy.readQuery({ query: CURRENT_USER_QUERY });
34 | // const cartItemId = payload.data.removeFromCart.id;
35 | // cache.me.cart = cache.me.cart.filter(
36 | // (cartItem) => cartItem.id !== cartItemId
37 | // );
38 | // proxy.writeQuery({ query: CURRENT_USER_QUERY, data: cache });
39 | // },
40 | // })
41 | // .then(() => {
42 | // successToast("Elemento rimosso con successo!");
43 | // })
44 | // .catch(() => errorToast("Errore nella rimozione dal carrello"));
45 | };
46 |
47 | return (
48 |
56 | );
57 | };
58 |
59 | export default RemoveFromCart;
60 |
--------------------------------------------------------------------------------
/components/Cart/UpdateCartQuantity.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from "react";
2 | import styled from "styled-components";
3 | import { styles } from "../utils";
4 | import useUpdateItem from "../../frontend-structure/checkout/hooks/useUpdateItem";
5 | import { errorToast, successToast } from "../utils/Toast/Toast";
6 | import { NexusGenObjects } from "../../generated/nexus-typegen";
7 |
8 | interface UpdateQuantityProp {
9 | cartItemId: string;
10 | quantity: number;
11 | maxQuantity: number;
12 | }
13 |
14 | const UpdateCartQuantity: React.FC = ({
15 | cartItemId,
16 | quantity,
17 | maxQuantity,
18 | }) => {
19 | const [updateCartQuantity, loading] = useUpdateItem();
20 |
21 | const onChangeQuantity = async (e) => {
22 | let qnt = parseInt(e.target.value, 10);
23 |
24 | if (maxQuantity && qnt > maxQuantity) {
25 | qnt = maxQuantity;
26 | }
27 |
28 | await updateCartQuantity({
29 | variables: {
30 | itemId: cartItemId,
31 | quantity: qnt,
32 | },
33 | }).then(({ data }) => {
34 | const res: NexusGenObjects["FieldResponse"] = data.updateCartQuantity;
35 | if (res.success) {
36 | successToast(res.message);
37 | } else {
38 | errorToast(res.message);
39 | }
40 | });
41 | };
42 |
43 | const displayQuantities = useMemo(() => {
44 | const quantities = [];
45 | let totalQuantity = 30;
46 | if (maxQuantity) {
47 | totalQuantity = Math.min(totalQuantity, maxQuantity);
48 | }
49 |
50 | for (let i = 0; i <= totalQuantity; i++) {
51 | quantities.push(
52 |
55 | );
56 | }
57 | return quantities;
58 | }, [maxQuantity]);
59 |
60 | return (
61 |
66 | {displayQuantities}
67 |
68 | );
69 | };
70 |
71 | const SelectWrapper = styled.select`
72 | font-size: 1.2rem;
73 | font-weight: 700;
74 | line-height: 1.3;
75 | border: 1px solid #aaa;
76 | box-shadow: 0 1px 0 1px rgba(0, 0, 0, 0.04);
77 | padding: 0.2em 1.2em 0.3em 0.6em;
78 |
79 | @media (min-width: ${styles.size.tablet}) {
80 | padding: 0.6em 1.4em 0.5em 0.8em;
81 | }
82 | `;
83 |
84 | export default UpdateCartQuantity;
85 | export { SelectWrapper };
86 |
--------------------------------------------------------------------------------
/components/Checkout/AddressListItems.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import styled from "styled-components";
3 |
4 | import AddressListItem from "./AddressListItem";
5 | import { CheckoutContext } from "./Checkout";
6 | import useAddresses from "../../frontend-structure/address/hooks/useAddresses";
7 | import usePopulateCheckoutAddress from "../../frontend-structure/address/hooks/usePopulateCheckoutAddress";
8 | import { BouncingLoader } from "../utils";
9 |
10 | const AddressListItems = () => {
11 | const context = useContext(CheckoutContext);
12 | const { nextPage, addressChosen } = context;
13 |
14 | const [data, loading] = useAddresses();
15 | const [populateCheckout, loadingPopulate] = usePopulateCheckoutAddress();
16 | // const [removeAddress, { loading: loadingRemove }] = useMutation(
17 | // MUTATION_REMOVE_ADDRESS,
18 | // {
19 | // refetchQueries: [
20 | // { query: QUERY_GET_ADDRESSES, variables: { where: { active: true } } },
21 | // ],
22 | // }
23 | // );
24 |
25 | // Set the first item of the list to be the default address
26 | // useEffect(() => {
27 | // if (data && data.addresses && data.addresses.length > 0 && !addressChosen) {
28 | // setAddressChosen(data.addresses[0].id);
29 | // }
30 | // }, [addressChosen, data]);
31 |
32 | const handleStepDone = async () => {
33 | if (addressChosen) {
34 | const { id, ...rest } = addressChosen;
35 | await populateCheckout({
36 | variables: {
37 | shippingAddress: rest,
38 | },
39 | });
40 | }
41 | nextPage();
42 | };
43 |
44 | // const handleRemoveAddress = async (id) => {
45 | // await removeAddress({ variables: { id } });
46 | // };
47 |
48 | if (loading) return ;
49 | if (!data || !data.addresses) return ;
50 |
51 | return (
52 |
53 | {data.addresses.map((address) => (
54 |
61 | ))}
62 |
63 | );
64 | };
65 |
66 | const EmptyOrWaitingAdddressList = () => (
67 |
68 | Ancora non hai un indirizzo!
69 |
70 | );
71 |
72 | const AddressListWrapper = styled.ul`
73 | list-style: none;
74 | padding: 0;
75 | `;
76 |
77 | const EmptyAddressListWrapper = styled.div`
78 | font-size: 1.2rem;
79 | font-weight: 600;
80 | margin: 2rem 0 2rem 0;
81 | text-align: center;
82 | `;
83 |
84 | export default AddressListItems;
85 |
--------------------------------------------------------------------------------
/components/Checkout/Checkout.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useMemo, useEffect } from "react";
2 | import { CustomerCheckout, GuestCheckout } from ".";
3 | import useCheckoutUrl from "../../frontend-structure/checkout/hooks/useCheckoutUrl";
4 | import useCustomer from "../../frontend-structure/user/hooks/useCustomer";
5 |
6 | export const CheckoutContext = React.createContext(null);
7 |
8 | const Checkout = () => {
9 | const [hasUser] = useCustomer();
10 | const [data, loading] = useCheckoutUrl();
11 |
12 | useEffect(() => {
13 | if (data?.checkoutUrl) {
14 | setCheckoutUrl(data.checkoutUrl);
15 | }
16 | }, [data]);
17 |
18 | const [addressChosen, setAddressChosen] = useState(null);
19 | const [checkoutUrl, setCheckoutUrl] = useState(null);
20 |
21 | const [step, setStep] = useState(1);
22 | const nextPage = () => {
23 | setStep((prevState) => prevState + 1);
24 | };
25 | const prevPage = () => {
26 | setStep((prevState) => prevState - 1);
27 | };
28 |
29 | const getContext = useMemo(
30 | () => ({
31 | step,
32 | nextPage,
33 | prevPage,
34 | addressChosen,
35 | setAddressChosen,
36 | checkoutUrl,
37 | }),
38 | [step, addressChosen]
39 | );
40 |
41 | return (
42 |
43 |
44 | {hasUser ? : }
45 |
46 |
47 | );
48 | };
49 |
50 | export default Checkout;
51 |
--------------------------------------------------------------------------------
/components/Checkout/Customer/CheckoutSpedition.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import AddressModal from "./AddressModal";
3 | import { Button, PageTitle, styles } from "../../utils";
4 | import AddressListItems from "../AddressListItems";
5 | import styled from "styled-components";
6 | import { CheckoutContext } from "../Checkout";
7 |
8 | const CheckoutSpedition = () => {
9 | // const [newAddressModal, setNewAddressModal] = useState(false);
10 | const context = useContext(CheckoutContext);
11 | const { nextPage } = context;
12 |
13 | return (
14 |
15 | Select your address
16 |
17 | {/*
*/}
25 |
33 |
34 |
35 | {/* */}
39 |
40 | );
41 | };
42 |
43 | const CheckoutSpeditionWrapper = styled.div`
44 | @media (min-width: ${styles.size.tablet}) {
45 | .addressList {
46 | margin: 0 auto;
47 | width: 60%;
48 | }
49 | }
50 | `;
51 |
52 | export default CheckoutSpedition;
53 |
--------------------------------------------------------------------------------
/components/Checkout/Customer/CustomerCheckout.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect } from "react";
2 | import CheckoutSpedition from "./CheckoutSpedition";
3 | import { CheckoutContext } from "../Checkout";
4 | import { useRouter } from "next/router";
5 |
6 | const CustomerCheckout = () => {
7 | const { step, checkoutUrl } = useContext(CheckoutContext);
8 |
9 | const router = useRouter();
10 |
11 | useEffect(() => {
12 | if (step === 2) {
13 | router.push(checkoutUrl);
14 | }
15 | }, [step]);
16 |
17 | return (
18 |
19 | {step === 1 && }
20 | {/* {step === 2 && } */}
21 | {/* {step === 3 && } */}
22 |
23 | );
24 | };
25 |
26 | export default CustomerCheckout;
27 |
--------------------------------------------------------------------------------
/components/Checkout/Guest/GuestCheckout.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect } from "react";
2 | import { CheckoutContext } from "../Checkout";
3 | import CheckoutGuestSuggestLogin from "./CheckoutGuestSuggestLogin";
4 | import { useRouter } from "next/router";
5 |
6 | const GuestCheckout = () => {
7 | const { step, checkoutUrl } = useContext(CheckoutContext);
8 | const router = useRouter();
9 |
10 | useEffect(() => {
11 | if (step === 2) {
12 | router.push(checkoutUrl);
13 | }
14 | }, [step]);
15 |
16 | return (
17 |
18 | {step === 1 && }
19 | {/* {step === 2 && }
20 | {step === 3 && } */}
21 |
22 | );
23 | };
24 |
25 | export default GuestCheckout;
26 |
--------------------------------------------------------------------------------
/components/Checkout/Guest/GuestWarning.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Link from "next/link";
4 | import { styles } from "../../utils";
5 | import { LOGIN } from "../../../constants/routes";
6 |
7 | const GuestWarning = () => (
8 |
9 | You are checking out as a guest, so you will not be able to track your
10 | orders!
11 |
12 |
13 | );
14 |
15 | const GuestWarningWrapper = styled.div`
16 | background-color: ${styles.colors.warningColor};
17 | border-radius: 10px;
18 | margin-bottom: 2rem;
19 | padding: 1rem;
20 | text-align: center;
21 |
22 | a {
23 | font-weight: bold;
24 | }
25 | `;
26 |
27 | export default GuestWarning;
28 |
--------------------------------------------------------------------------------
/components/Checkout/TotalSummary.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import styled, { css } from "styled-components";
4 | import { styles, formatMoney, calcTotalPrice } from "../utils";
5 |
6 | const TotalSummary = ({ me, children }) => {
7 | const { cart } = me;
8 | const totalPrice = calcTotalPrice(cart);
9 |
10 | return (
11 |
12 | Ordine
13 |
14 |
15 |
16 |
22 | {children}
23 |
24 | );
25 | };
26 |
27 | TotalSummary.propTypes = {
28 | me: PropTypes.object.isRequired,
29 | children: PropTypes.arrayOf(PropTypes.object).isRequired,
30 | };
31 |
32 | const TotalLine = ({ title, price, titleBold, priceSuccess }) => (
33 |
34 | {title}
35 | {price}
36 |
37 | );
38 |
39 | const CartPaymentWrapper = styled.div`
40 | width: 100%;
41 | border: 1px solid ${styles.colors.darkGrey};
42 | padding: 1rem;
43 | box-shadow: ${styles.shadows.lightShadow};
44 |
45 | h3 {
46 | font-size: 1.5rem;
47 | }
48 |
49 | @media (min-width: ${styles.size.laptop}) {
50 | padding: 0.5rem;
51 | order: 1;
52 | }
53 | `;
54 |
55 | TotalLine.propTypes = {
56 | title: PropTypes.string.isRequired,
57 | price: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
58 | titleBold: PropTypes.bool,
59 | priceSuccess: PropTypes.bool,
60 | };
61 |
62 | TotalLine.defaultProps = {
63 | titleBold: false,
64 | priceSuccess: false,
65 | };
66 |
67 | const TotalLineWrapper = styled.div<{
68 | titleBold: boolean;
69 | priceSuccess: boolean;
70 | }>`
71 | display: flex;
72 | justify-content: space-between;
73 |
74 | .line-title {
75 | ${(props) =>
76 | props.titleBold &&
77 | css`
78 | font-weight: bold;
79 | `}
80 | }
81 |
82 | .line-price {
83 | ${(props) =>
84 | props.priceSuccess &&
85 | css`
86 | color: ${styles.colors.successColor};
87 | `}
88 | }
89 | `;
90 |
91 | export default TotalSummary;
92 |
--------------------------------------------------------------------------------
/components/Checkout/index.ts:
--------------------------------------------------------------------------------
1 | import CustomerCheckout from "./Customer/CustomerCheckout";
2 | import GuestCheckout from "./Guest/GuestCheckout";
3 | import Checkout from "./Checkout";
4 |
5 | export { Checkout, CustomerCheckout, GuestCheckout };
6 |
--------------------------------------------------------------------------------
/components/Home/FeaturedProducts.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Title from "../Structure/Title";
3 | import ItemsList from "../Items/ItemsList";
4 | import { NexusGenObjects } from "../../generated/nexus-typegen";
5 |
6 | interface FeaturedProductsProp {
7 | products: Array;
8 | }
9 |
10 | const FeaturedProducts: React.FC = ({ products }) => {
11 | return (
12 |
13 |
14 | {products && }
15 |
16 | );
17 | };
18 |
19 | export default FeaturedProducts;
20 |
--------------------------------------------------------------------------------
/components/Home/FrontCategory.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 | import styled from "styled-components";
4 | import Link from "next/link";
5 | import { styles } from "../utils";
6 | import { SHOPCATEGORY } from "../../constants/routes";
7 | import { NexusGenObjects } from "../../generated/nexus-typegen";
8 |
9 | interface FrontCategoryProp {
10 | category: NexusGenObjects["Category"];
11 | }
12 |
13 | const FrontCategory: React.FC = ({ category }) => {
14 | return (
15 |
16 |
17 |
18 | {category.image && (
19 |
25 | )}
26 |
{category.title}
27 |
{category.description}
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | // // FrontCategory.propTypes = {
35 | // // category: PropTypes.shape({
36 | // // id: PropTypes.string.isRequired,
37 | // // name: PropTypes.string.isRequired,
38 | // // // image: PropTypes.string.isRequired,
39 | // // // title: PropTypes.string,
40 | // // // description: PropTypes.string,
41 | // // // categoryCode: PropTypes.string.isRequired
42 | // // }).isRequired
43 | // // };
44 |
45 | const FrontCategoryWrapper = styled.li`
46 | box-shadow: ${styles.shadows.lightShadow};
47 | ${styles.transDefault};
48 | cursor: pointer;
49 |
50 | display: grid;
51 |
52 | &:hover {
53 | box-shadow: ${styles.shadows.darkShadow};
54 | }
55 |
56 | .imageContainer {
57 | position: relative;
58 | overflow: hidden;
59 | ${styles.transDefault};
60 | background: ${styles.colors.mainBlack};
61 |
62 | .img {
63 | width: 100%;
64 | height: 100%;
65 | ${styles.transDefault};
66 | }
67 |
68 | .title {
69 | position: absolute;
70 | top: 50%;
71 | left: 50%;
72 | transform: translate(-50%, -50%);
73 | color: ${styles.colors.mainWhite};
74 | font-size: 2rem;
75 | font-weight: 600;
76 | ${styles.transDefault};
77 | text-shadow: 3px 3px 1px gray;
78 | }
79 |
80 | &:hover .img {
81 | opacity: 0.3;
82 | }
83 |
84 | &:hover .description {
85 | opacity: 1;
86 | }
87 |
88 | &:hover .title {
89 | opacity: 0;
90 | }
91 |
92 | .description {
93 | position: absolute;
94 | top: 50%;
95 | left: 50%;
96 | transform: translate(-50%, -50%);
97 | opacity: 0;
98 | text-align: center;
99 | text-transform: uppercase;
100 | color: ${styles.colors.mainWhite};
101 | padding: 0.5rem 0.7rem;
102 | display: inline-block;
103 | ${styles.transDefault};
104 | }
105 | }
106 | `;
107 |
108 | export default FrontCategory;
109 |
--------------------------------------------------------------------------------
/components/Home/FrontPageCategories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import BouncingLoader from "../utils/BouncingLoader";
4 | import FrontCategory from "./FrontCategory";
5 | import { NexusGenObjects } from "../../generated/nexus-typegen";
6 |
7 | interface FrontPageCategoriesProp {
8 | categories: Array;
9 | }
10 |
11 | const FrontPageCategories: React.FC = ({
12 | categories,
13 | }) => {
14 | return (
15 |
16 | {categories.map((category) => (
17 |
18 | ))}
19 |
20 | );
21 | };
22 |
23 | const FrontCategoriesWrapper = styled.ul`
24 | list-style: none;
25 | display: grid;
26 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
27 | grid-auto-rows: 250px;
28 | grid-gap: 1.2rem;
29 |
30 | padding: 0;
31 | `;
32 |
33 | export default FrontPageCategories;
34 |
--------------------------------------------------------------------------------
/components/Items/ItemsList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import ProductCard from "./ProductCard";
4 | import { NexusGenObjects } from "../../generated/nexus-typegen";
5 |
6 | interface ItemsListProp {
7 | products: Array;
8 | }
9 |
10 | const ItemsList: React.FC = ({ products = [] }) => (
11 |
12 | {products.map((product) => (
13 |
14 | ))}
15 |
16 | );
17 |
18 | const ItemsListWrapper = styled.div`
19 | display: flex;
20 | flex-wrap: wrap;
21 | justify-content: space-evenly;
22 |
23 | margin-bottom: 1.4rem;
24 | `;
25 |
26 | export default ItemsList;
27 |
--------------------------------------------------------------------------------
/components/Items/RelatedItems.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import PropTypes from "prop-types";
4 | import ItemsList from "./ItemsList";
5 | import Title from "../Structure/Title";
6 |
7 | const RelatedItems = ({ related }) => (
8 |
9 | {related.length > 0 && (
10 | <>
11 |
12 |
13 | >
14 | )}
15 |
16 | );
17 |
18 | RelatedItems.propTypes = {
19 | related: PropTypes.arrayOf(PropTypes.object).isRequired,
20 | };
21 |
22 | const RelatedWrapper = styled.div`
23 | margin-top: 3rem;
24 | `;
25 |
26 | export default RelatedItems;
27 |
--------------------------------------------------------------------------------
/components/Shop/ShopHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { styles, ImageBordered } from "../utils";
4 | import { NexusGenObjects } from "../../generated/nexus-typegen";
5 |
6 | interface ShopHeaderProp {
7 | category: NexusGenObjects["Category"];
8 | }
9 |
10 | const ShopHeader: React.FC = ({ category }) => (
11 |
12 |
13 |
{category.title}
14 |
{category.description}
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
22 | const ShopHeaderWrapper = styled.div`
23 | padding: 1.5rem;
24 |
25 | .description {
26 | text-align: center;
27 | h1 {
28 | font-size: 2.5rem;
29 | animation: ${styles.animations.slideIn} 1.1s both;
30 | }
31 | }
32 |
33 | .image {
34 | display: none;
35 | }
36 |
37 | @media (min-width: ${styles.size.tablet}) {
38 | display: grid;
39 | grid-template-columns: 1fr 1fr;
40 | grid-template-rows: auto;
41 | align-items: center;
42 | word-break: break-all;
43 |
44 | .image {
45 | display: block;
46 | }
47 | }
48 | `;
49 |
50 | export default ShopHeader;
51 |
--------------------------------------------------------------------------------
/components/Shop/ShopItems.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import ItemsList from "../Items/ItemsList";
4 | import { NexusGenObjects } from "../../generated/nexus-typegen";
5 |
6 | interface ShopItemsProp {
7 | products: Array;
8 | }
9 |
10 | const ShopItems: React.FC = ({ products }) => (
11 |
12 |
13 |
14 | );
15 |
16 | const ShopItemsWrapper = styled.div`
17 | padding-top: 3rem;
18 | `;
19 |
20 | export default ShopItems;
21 |
--------------------------------------------------------------------------------
/components/Shop/SingleProductImages.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { Carousel } from "../utils";
4 | import { NexusGenObjects } from "../../generated/nexus-typegen";
5 |
6 | interface SingleProductType {
7 | image: NexusGenObjects["ProductImage"];
8 | additionalImages: Array;
9 | }
10 |
11 | const SingleProductImages: React.FC = ({
12 | image,
13 | additionalImages = [],
14 | }) => {
15 | return (
16 |
17 |
18 |
19 |

20 |
21 | {additionalImages &&
22 | additionalImages.map((img) => (
23 |
24 |

25 |
26 | ))}
27 |
28 |
29 | );
30 | };
31 |
32 | const ProductImagesWrapper = styled.div`
33 | margin: 0 auto;
34 | width: 80%;
35 | `;
36 |
37 | export default SingleProductImages;
38 |
--------------------------------------------------------------------------------
/components/Shop/SingleProductInventory.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled, { css } from "styled-components";
3 | import PropTypes from "prop-types";
4 | import { styles } from "../utils";
5 |
6 | interface SingleProductInventoryType {
7 | availableForSale: boolean;
8 | quantity: number;
9 | }
10 |
11 | const SingleProductInventory: React.FC = ({
12 | availableForSale,
13 | quantity,
14 | }) => {
15 | if (!availableForSale) {
16 | return Not Available!;
17 | } else if (quantity > 0) {
18 | const remaining = quantity === 1 ? "remaining" : "remaining";
19 |
20 | if (quantity < 5) {
21 | return (
22 |
23 | Only {quantity} {remaining}
24 |
25 | );
26 | }
27 | }
28 |
29 | return <>>;
30 | };
31 |
32 | const InventoryStyle = css`
33 | ${styles.defaultBox};
34 | font-size: 1.2rem;
35 | font-weight: 700;
36 | margin: 0 auto;
37 | margin-bottom: 0.8rem;
38 | text-align: center;
39 | width: 40%;
40 | `;
41 |
42 | const InventoryLowWrapper = styled.div`
43 | color: ${styles.colors.warningColor};
44 | ${InventoryStyle};
45 | `;
46 |
47 | const InventoryWrapper = styled.div`
48 | ${InventoryStyle};
49 | `;
50 |
51 | export default SingleProductInventory;
52 |
--------------------------------------------------------------------------------
/components/Shop/SingleProductNote.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import styled from "styled-components";
3 | import PropTypes from "prop-types";
4 | import TextField from "@material-ui/core/TextField";
5 | import { Button, styles } from "../utils";
6 |
7 | const SingleProductNote = ({ note, setNote }) => {
8 | const [showText, setShowText] = useState(false);
9 |
10 | const handleChange = (e) => {
11 | if (e && e.target) {
12 | setNote(e.target.value);
13 | }
14 | };
15 |
16 | return (
17 |
18 | {showText ? (
19 | handleChange(e)}
27 | value={note}
28 | variant="outlined"
29 | />
30 | ) : (
31 |
32 | )}
33 |
34 | );
35 | };
36 |
37 | SingleProductNote.propTypes = {
38 | note: PropTypes.string,
39 | setNote: PropTypes.func.isRequired,
40 | };
41 |
42 | SingleProductNote.defaultProps = {
43 | note: "",
44 | };
45 |
46 | const NoteWrapper = styled.div`
47 | margin-top: 2rem;
48 | ${styles.transDefault};
49 | `;
50 |
51 | export default SingleProductNote;
52 |
--------------------------------------------------------------------------------
/components/Signin/LoginButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Button } from "../utils";
4 | import { LOGIN } from "../../constants/routes";
5 |
6 | interface LoginButtonProp {
7 | color?: string;
8 | className?: string;
9 | }
10 |
11 | const LoginButton: React.FC = (props) => {
12 | const { color, ...rest } = props;
13 |
14 | return (
15 |
18 | );
19 | };
20 |
21 | LoginButton.propTypes = {
22 | color: PropTypes.string.isRequired,
23 | };
24 |
25 | export default LoginButton;
26 |
--------------------------------------------------------------------------------
/components/Signin/LoginFormWrapper.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { styles } from "../utils";
3 |
4 | const LoginFormWrapper = styled.form`
5 | margin: 0;
6 |
7 | .socialLine {
8 | margin-top: 1rem;
9 | padding: 0;
10 | text-align: center;
11 | }
12 |
13 | .divider {
14 | margin-top: 1rem;
15 | margin-bottom: 0;
16 | text-align: center;
17 | }
18 |
19 | .errorText {
20 | background: ${styles.colors.dangerColor};
21 | border-radius: 0.2rem;
22 | font-weight: 600;
23 | margin: 5px auto 5px auto;
24 | padding: 1rem;
25 | text-align: center;
26 | width: 80%;
27 | }
28 |
29 | .cardFooterPasswordForget {
30 | color: #595959;
31 | cursor: pointer;
32 | text-decoration: underline;
33 | }
34 | `;
35 |
36 | export default LoginFormWrapper;
37 |
--------------------------------------------------------------------------------
/components/Signin/PasswordForgetForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | // @material-ui/icons
3 | import Email from "@material-ui/icons/Email";
4 | import InputAdornment from "@material-ui/core/InputAdornment";
5 |
6 | import { useForm, ErrorMessage } from "react-hook-form";
7 |
8 | import {
9 | Button,
10 | CardHeader,
11 | CardBody,
12 | CardFooter,
13 | CustomInput,
14 | } from "../utils";
15 |
16 | import LoginFormWrapper from "./LoginFormWrapper";
17 |
18 | const PasswordForgetForm = ({ handleFormChange }) => {
19 | const [loading, setLoading] = useState(false);
20 |
21 | const { register, handleSubmit, errors, reset } = useForm({
22 | mode: "onBlur",
23 | reValidateMode: "onBlur",
24 | });
25 |
26 | const [authError, setAuthError] = useState("");
27 |
28 | const onSubmit = async (data) => {
29 | const { email } = data;
30 | setLoading(true);
31 |
32 | // await firebase
33 | // .doPasswordReset(email)
34 | // .then(() => {
35 | // reset();
36 | // setLoading(false);
37 | // })
38 | // .catch((error) => {
39 | // const errorMessage = translateErrorCodes(error.code);
40 | // setAuthError(errorMessage);
41 | // setLoading(false);
42 | // });
43 | };
44 |
45 | return (
46 |
47 |
48 | Reimposta la password
49 |
50 | {authError && {authError}
}
51 |
52 |
64 |
65 |
66 | ),
67 | }}
68 | />
69 |
70 |
71 |
72 |
81 |
82 |
83 |
84 |
85 | Oppure{" "}
86 | handleFormChange("login")}
89 | >
90 | Accedi
91 |
92 |
93 |
94 |
95 | );
96 | };
97 |
98 | export default PasswordForgetForm;
99 |
--------------------------------------------------------------------------------
/components/Signin/SignOutButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { successToast, errorToast } from "../utils/Toast/Toast";
3 | import { Button } from "../utils";
4 | import useLogout from "../../frontend-structure/user/hooks/useLogout";
5 |
6 | interface SignoutButtonType {
7 | className?: string;
8 | }
9 |
10 | const SignOutButton: React.FC = (props) => {
11 | const [signout, loading] = useLogout();
12 |
13 | const handleSignout = async () => {
14 | signout()
15 | .then(({ data }) => {
16 | if (data) {
17 | if (data.signout?.success) {
18 | successToast(data.signout.message);
19 | } else {
20 | errorToast(data.signout.message);
21 | }
22 | }
23 | })
24 | .catch((err) => {
25 | console.log("Errore nel logout", err);
26 | errorToast("Errore nel logout, riprova tra poco!");
27 | });
28 | };
29 |
30 | return (
31 |
34 | // SCOLLEGATI
35 | );
36 | };
37 |
38 | export default SignOutButton;
39 |
--------------------------------------------------------------------------------
/components/Structure/Footer/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Link from "next/link";
3 | import styled from "styled-components";
4 | import { styles, Container } from "../../utils";
5 |
6 | const Footer = () => (
7 |
8 |
9 |
10 |

11 |
12 |
13 |
14 | );
15 |
16 | const FooterWrapper = styled.footer`
17 | background-color: ${styles.colors.primaryColor};
18 | border-radius: 3px;
19 | color: ${styles.colors.mainWhite};
20 | margin-top: 3rem;
21 |
22 | .footer-container {
23 | ${Container};
24 | }
25 | `;
26 |
27 | export default Footer;
28 |
--------------------------------------------------------------------------------
/components/Structure/Footer/FooterContainer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { styles } from "../../utils";
4 | import Footer from "./Footer";
5 | import FooterMobile from "./FooterMobile/FooterMobile";
6 |
7 | const FooterContainer = () => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 |
18 | const FooterContainerWrapper = styled.div`
19 | .default-footer {
20 | display: none;
21 | }
22 |
23 | .mobile-footer {
24 | display: block;
25 | }
26 |
27 | @media (min-width: ${styles.size.tablet}) {
28 | .default-footer {
29 | display: block;
30 | }
31 |
32 | .mobile-footer {
33 | display: none;
34 | }
35 | }
36 | `;
37 |
38 | export default FooterContainer;
39 |
--------------------------------------------------------------------------------
/components/Structure/Footer/FooterMobile/FooterMobile.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 |
4 | import Drawer from "@material-ui/core/Drawer";
5 | import { styles } from "../../../utils";
6 | import FooterMobileIcons from "./FooterMobileIcons";
7 | import FooterMobileDrawLinks from "./FooterMobileDrawLinks";
8 |
9 | const FooterMobile = () => {
10 | const [mobileOpen, setMobileOpen] = useState(false);
11 |
12 | const handleDrawerToggle = () => {
13 | setMobileOpen(!mobileOpen);
14 | };
15 |
16 | return (
17 |
18 |
19 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | const StyledDrawer = styled(Drawer)`
32 | .MuiDrawer-paper {
33 | bottom: 0;
34 | flex: 1 0 auto;
35 | display: flex;
36 | outline: 0;
37 | z-index: 1200;
38 | padding-bottom: 10vh;
39 | position: fixed;
40 | overflow-y: auto;
41 | flex-direction: column;
42 | }
43 | `;
44 |
45 | const FooterMobileWrapper = styled.footer`
46 | background-color: ${styles.colors.primaryColor};
47 | border-radius: 3px;
48 | color: ${styles.colors.mainWhite};
49 |
50 | position: fixed;
51 | bottom: 0;
52 |
53 | z-index: 1200;
54 |
55 | width: 100%;
56 |
57 | .drawerPaper {
58 | border: none;
59 | bottom: 0;
60 | transition-property: top, bottom, width;
61 | transition-duration: 0.2s, 0.2s, 0.35s;
62 | transition-timing-function: linear, linear, ease;
63 | width: ${styles.drawerWidth}px;
64 | ${styles.boxShadow};
65 | position: fixed;
66 | display: block;
67 | top: 0;
68 | height: 80vh;
69 | right: 0;
70 | left: auto;
71 | visibility: visible;
72 | overflow-y: visible;
73 | border-top: none;
74 | text-align: left;
75 | padding-right: 0px;
76 | padding-left: 0;
77 | ${styles.transBezier};
78 | }
79 | `;
80 |
81 | export default FooterMobile;
82 |
--------------------------------------------------------------------------------
/components/Structure/Footer/FooterMobile/FooterMobileDrawLinksStyle.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from "styled-components";
2 | import { styles } from "../../../utils";
3 |
4 | export const ListWrapper = styled.ul`
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: flex-end;
8 |
9 | color: inherit;
10 | font-size: 1rem;
11 | height: 100%;
12 | list-style: none;
13 | margin: 0;
14 | padding-bottom: 0;
15 | padding-left: 0;
16 | padding-top: 0;
17 |
18 | @media (min-width: ${styles.size.tablet}) {
19 | flex-direction: row;
20 | align-items: center;
21 | }
22 | `;
23 |
24 | export const ListItemWrapper = styled.li< { active?: boolean } >`
25 | /* position: relative; */
26 | /* text-align: center; */
27 | /* width: auto; */
28 | margin-bottom: 1rem;
29 |
30 | ${props =>
31 | props.active &&
32 | css`
33 | background-color: ${styles.colors.mainBlack};
34 | border-radius: 3px;
35 | color: ${styles.colors.mainWhite};
36 | `}
37 |
38 | .navLink {
39 | color: inherit;
40 | display: inline-flex;
41 | font-size: 12px;
42 | font-weight: 400;
43 | line-height: 20px;
44 | margin: 0;
45 | padding: 0.5rem;
46 | text-decoration: none;
47 | /* text-transform: uppercase; */
48 |
49 | &:hover,
50 | &:focus {
51 | color: inherit;
52 | /* background: rgba(200, 200, 200, 0.2); */
53 | }
54 | @media (max-width: ${styles.size.mobileL}) {
55 | margin-bottom: 8px;
56 | /* margin-left: 15px; */
57 | /* text-align: left; */
58 | /* width: calc(100% - 30px); */
59 | & > span:first-child {
60 | justify-content: flex-start;
61 | }
62 | }
63 | }
64 |
65 | .socialIcons {
66 | font-size: 20px !important;
67 | margin-right: 4px;
68 | position: relative;
69 | }
70 |
71 | .dropdownLink {
72 | color: inherit;
73 | display: block;
74 | padding: 10px 20px;
75 | text-decoration: none;
76 | }
77 | `;
78 |
--------------------------------------------------------------------------------
/components/Structure/Footer/FooterMobile/FooterMobileIcons.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Link from "next/link";
3 | import { useRouter } from "next/router";
4 | import AccountCircle from "@material-ui/icons/AccountCircle";
5 | import Home from "@material-ui/icons/Home";
6 | import Menu from "@material-ui/icons/Menu";
7 | import PropTypes from "prop-types";
8 |
9 | import { IconButton } from "@material-ui/core";
10 | import CartButton from "../../../Cart/CartButton";
11 | import { ListWrapper, ListItemWrapper } from "./FooterMobileIconsStyle";
12 | import * as ROUTES from "../../../../constants/routes";
13 | import useCustomer from "../../../../frontend-structure/user/hooks/useCustomer";
14 |
15 | const FooterMobileIcons = ({ handleDrawerToggle }) => {
16 | const router = useRouter();
17 | const [_, hasUser] = useCustomer();
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | {hasUser ? (
33 |
34 |
35 |
36 |
37 |
38 | ) : (
39 |
40 |
41 |
42 |
43 |
44 | )}
45 |
46 |
47 |
52 |
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | FooterMobileIcons.propTypes = {
60 | handleDrawerToggle: PropTypes.func.isRequired,
61 | };
62 |
63 | export default FooterMobileIcons;
64 |
--------------------------------------------------------------------------------
/components/Structure/Footer/FooterMobile/FooterMobileIconsStyle.ts:
--------------------------------------------------------------------------------
1 | import { bool } from "prop-types";
2 | import styled, { css } from "styled-components";
3 | import { styles } from "../../../utils";
4 |
5 | export const ListWrapper = styled.ul`
6 | display: flex;
7 | flex-direction: row;
8 | align-items: center;
9 | justify-content: space-evenly;
10 |
11 | color: inherit;
12 | font-size: 1rem;
13 | list-style: none;
14 | margin: 0;
15 | padding-bottom: 0;
16 | padding-left: 0;
17 | padding-top: 0;
18 | `;
19 |
20 | export const ListItemWrapper =
21 | styled.li <
22 | { active?: boolean } >
23 | `
24 | flex-grow: 1;
25 |
26 | a {
27 | color: inherit;
28 | }
29 |
30 | color: inherit;
31 | position: relative;
32 | text-align: center;
33 |
34 | ${(props) =>
35 | props.active &&
36 | css`
37 | background-color: ${styles.colors.mainWhite};
38 | color: ${styles.colors.mainBlack};
39 |
40 | a {
41 | animation: ${styles.animations.pulse} 1s 1;
42 | }
43 | `}
44 |
45 | .navLink {
46 | /* border-radius: 3px; */
47 | /* color: inherit; */
48 | display: inline-flex;
49 | margin: 0;
50 | padding: 0.9375rem;
51 | text-decoration: none;
52 |
53 | &:hover,
54 | &:focus {
55 | color: inherit;
56 | background: rgba(200, 200, 200, 0.2);
57 | }
58 | @media (max-width: ${styles.size.mobileL}) {
59 | & > span:first-child {
60 | justify-content: flex-start;
61 | }
62 | }
63 | }
64 |
65 | .dropdownLink {
66 | color: inherit;
67 | display: block;
68 | padding: 10px 20px;
69 | text-decoration: none;
70 | }
71 | `;
72 |
--------------------------------------------------------------------------------
/components/Structure/Header/HeaderLinksStyle.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from "styled-components";
2 | import { styles } from "../../utils";
3 |
4 | export const ListWrapper = styled.ul`
5 | display: flex;
6 | flex-direction: row;
7 |
8 | color: inherit;
9 | font-size: 1rem;
10 | list-style: none;
11 | margin: 0;
12 | padding-bottom: 0;
13 | padding-left: 0;
14 | padding-top: 0;
15 |
16 | @media (min-width: ${styles.size.tablet}) {
17 | align-items: center;
18 | }
19 | `;
20 |
21 | export const ListItemWrapper =
22 | styled.li <
23 | { active?: boolean } >
24 | `
25 | color: inherit;
26 | display: block;
27 | float: left;
28 | margin: 0;
29 | padding: 0;
30 | position: relative;
31 | text-align: center;
32 | width: auto;
33 |
34 | ${(props) =>
35 | props.active &&
36 | css`
37 | background-color: ${styles.colors.mainWhite};
38 | border-radius: 3px;
39 | border: 1px solid;
40 | color: ${styles.colors.mainBlack};
41 | `}
42 |
43 |
44 | @media (max-width: ${styles.size.mobileL}) {
45 | width: 100%;
46 |
47 | /* &:after {
48 | background-color: "#e5e5e5";
49 | content: '""';
50 | display: block;
51 | height: 1px;
52 | margin-left: 15px;
53 | width: calc(100% - 30px);
54 | } */
55 | }
56 |
57 | .navLink {
58 | border-radius: 3px;
59 | color: inherit;
60 | display: inline-flex;
61 | font-size: 12px;
62 | font-weight: 400;
63 | line-height: 20px;
64 | margin: 0;
65 | padding: 0.9375rem;
66 | text-decoration: none;
67 | text-transform: uppercase;
68 |
69 | &:hover,
70 | &:focus {
71 | color: inherit;
72 | background: rgba(200, 200, 200, 0.2);
73 | }
74 | @media (max-width: ${styles.size.mobileL}) {
75 | /* margin-bottom: 8px; */
76 | /* margin-left: 15px; */
77 | text-align: left;
78 | /* width: calc(100% - 30px); */
79 | & > span:first-child {
80 | justify-content: flex-start;
81 | }
82 | }
83 | }
84 |
85 | .socialIcons {
86 | font-size: 20px !important;
87 | margin-right: 4px;
88 | position: relative;
89 | }
90 |
91 | .dropdownLink {
92 | color: inherit;
93 | display: block;
94 | padding: 10px 20px;
95 | text-decoration: none;
96 | }
97 | `;
98 |
--------------------------------------------------------------------------------
/components/Structure/Header/HeaderMobileLinks.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRouter } from "next/router";
3 | import Link from "next/link";
4 |
5 | import { ListWrapper, ListItemWrapper } from "./HeaderLinksStyle";
6 | import * as ROUTES from "../../../constants/routes";
7 | import { FPS, GRAPH, RTS } from "../../../constants/categories";
8 |
9 | function HeaderMobileLinks() {
10 | const router = useRouter();
11 |
12 | const isActiveLink = (slug) => router.asPath.indexOf(slug) !== -1;
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | {/* */}
20 | {FPS.CODE}
21 |
22 |
23 |
24 |
25 |
26 |
27 | {/* */}
28 | {GRAPH.CODE}
29 |
30 |
31 |
32 |
33 |
34 |
35 | {/* */}
36 | {RTS.CODE}
37 |
38 |
39 |
40 |
41 | );
42 | }
43 |
44 | export default HeaderMobileLinks;
45 |
--------------------------------------------------------------------------------
/components/Structure/Layouts/CheckoutLayout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import {
4 | styles,
5 | MainRaisedWrapper,
6 | ContainerWrapper,
7 | Parallax,
8 | Header,
9 | } from "../../utils";
10 |
11 | import Meta from "../Meta";
12 |
13 | const CheckoutLayout = (props) => (
14 |
15 |
16 |
17 |
18 |
19 |
20 | {props.children}
21 |
22 |
23 |
24 | );
25 |
26 | const StyledPage = styled.div`
27 | background: white;
28 | box-sizing: border-box;
29 | color: ${styles.colors.mainBlack};
30 |
31 | *,
32 | *:before,
33 | *:after {
34 | box-sizing: inherit;
35 | }
36 |
37 | body {
38 | padding: 0;
39 | margin: 100px;
40 | line-height: 2;
41 | }
42 | `;
43 |
44 | export default CheckoutLayout;
45 |
--------------------------------------------------------------------------------
/components/Structure/Layouts/NoContainerLayout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Header } from "../../utils";
3 |
4 | import HeaderLinks from "../Header/HeaderLinks";
5 | import HeaderMobileLinks from "../Header/HeaderMobileLinks";
6 |
7 | import Meta from "../Meta";
8 | import FooterContainer from "../Footer/FooterContainer";
9 |
10 | const NoContainerLayout = (props) => (
11 | <>
12 | }
15 | mobileLinks={}
16 | fixed
17 | color="overylayBlack"
18 | changeColorOnScroll={{
19 | height: 200,
20 | color: "white",
21 | }}
22 | changeBrandOnScroll={{
23 | height: 200,
24 | }}
25 | />
26 |
27 | {/* */}
28 | {props.children}
29 |
30 | >
31 | );
32 |
33 | export default NoContainerLayout;
34 |
--------------------------------------------------------------------------------
/components/Structure/Layouts/PageLayout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import {
4 | styles,
5 | MainRaisedWrapper,
6 | ContainerWrapper,
7 | Header,
8 | Parallax,
9 | } from "../../utils";
10 | import HeaderLinks from "../Header/HeaderLinks";
11 | import HeaderMobileLinks from "../Header/HeaderMobileLinks";
12 | import Meta from "../Meta";
13 | import FooterContainer from "../Footer/FooterContainer";
14 |
15 | const PageLayout = (props) => (
16 |
17 | }
20 | mobileLinks={}
21 | fixed
22 | color="overylayBlack"
23 | changeColorOnScroll={{
24 | height: 200,
25 | color: "white",
26 | }}
27 | changeBrandOnScroll={{
28 | height: 160,
29 | }}
30 | />
31 |
32 |
33 |
34 |
35 | {props.children}
36 |
37 |
38 |
39 |
40 | );
41 |
42 | const StyledPage = styled.div`
43 | background: white;
44 | box-sizing: border-box;
45 | color: ${styles.colors.mainBlack};
46 |
47 | *,
48 | *:before,
49 | *:after {
50 | box-sizing: inherit;
51 | }
52 |
53 | body {
54 | padding: 0;
55 | margin: 100px;
56 | line-height: 2;
57 | }
58 | `;
59 |
60 | export default PageLayout;
61 |
--------------------------------------------------------------------------------
/components/Structure/Meta.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Head from "next/head";
3 |
4 | const Meta = () => (
5 |
6 |
7 |
8 |
9 |
10 |
14 | @escapemanuele Shopify Store
15 |
16 | );
17 |
18 | export default Meta;
19 |
--------------------------------------------------------------------------------
/components/Structure/SectionBlock.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled, { css } from "styled-components";
3 | import { styles } from "../utils";
4 |
5 | const SectionBlock = ({ icon, iconClass, title, description, active }) => (
6 |
7 |
8 | {icon ? <>{icon}> : }
9 |
10 |
11 |
{title}
12 |
{description}
13 |
14 |
15 | );
16 |
17 | const BlockWrapper = styled.article<{ active: boolean }>`
18 | border: 1px solid ${styles.colors.mainGrey};
19 | border-radius: 3px;
20 | cursor: pointer;
21 | text-align: center;
22 | padding: 1rem;
23 |
24 | ${(props) =>
25 | props.active &&
26 | css`
27 | background-color: ${styles.colors.lightGrey};
28 |
29 | .section_icon svg {
30 | animation: ${styles.animations.pulse} 1s 1;
31 | }
32 | `}
33 |
34 | &:hover {
35 | background: ${styles.colors.lightGrey};
36 | }
37 |
38 | .section_icon {
39 | font-size: 3rem;
40 | font-weight: 200;
41 | color: var(--primaryColor);
42 | }
43 |
44 | .section_info {
45 | align-self: start;
46 |
47 | h3 {
48 | font-size: 1rem;
49 | font-weight: 500;
50 | margin: 0.2rem;
51 | }
52 |
53 | p {
54 | display: none;
55 | }
56 | }
57 |
58 | @media (min-width: ${styles.size.tablet}) {
59 | display: grid;
60 | grid-template-columns: auto 3fr;
61 | align-items: center;
62 |
63 | .section_info {
64 | h3 {
65 | font-size: 1.6rem;
66 | }
67 |
68 | p {
69 | display: block;
70 | }
71 | }
72 | }
73 | `;
74 |
75 | export default SectionBlock;
76 |
--------------------------------------------------------------------------------
/components/Structure/Sections.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import PropTypes, { object } from "prop-types";
4 | import SectionBlock from "./SectionBlock";
5 |
6 | const AccountSections = ({ sections, setSection, section }) => (
7 |
8 |
9 | {sections.map((sec) => {
10 | const { title, icon, iconClass, description, link } = sec;
11 |
12 | const active = link === section;
13 | return (
14 | - setSection(link)}>
15 |
22 |
23 | );
24 | })}
25 |
26 |
27 | );
28 |
29 | const AccountSectionsWrapper = styled.section`
30 | ul {
31 | display: grid;
32 | grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
33 | /* justify-content: center; */
34 | grid-gap: 1.5rem;
35 |
36 | list-style: none;
37 | padding-left: 0;
38 | }
39 | `;
40 |
41 | export default AccountSections;
42 |
--------------------------------------------------------------------------------
/components/Structure/Title.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import PropTypes from "prop-types";
4 | import { styles } from "../utils";
5 |
6 | const Title = ({ title, subtitle, className }) => (
7 |
8 |
9 | {title}
10 | {subtitle}
11 |
12 |
13 | );
14 |
15 | Title.propTypes = {
16 | title: PropTypes.string.isRequired,
17 | subtitle: PropTypes.string.isRequired,
18 | className: PropTypes.string
19 | };
20 |
21 | Title.defaultProps = {
22 | className: ""
23 | };
24 |
25 | export default styled(Title)`
26 | text-transform: uppercase;
27 | font-size: 2rem;
28 | margin-bottom: 2rem;
29 |
30 | h4 {
31 | text-align: center;
32 | letter-spacing: 7px;
33 | color: ${styles.colors.successColor};
34 | padding-top: 1rem;
35 | }
36 |
37 | .title {
38 | color: ${styles.colors.mainBlack};
39 | margin-bottom: 0.5rem;
40 | }
41 | span {
42 | display: block;
43 | }
44 |
45 | @media (min-width: ${styles.size.tablet}) {
46 | span {
47 | display: inline;
48 | margin: 0 0.35rem;
49 | }
50 |
51 | .title {
52 | margin-bottom: 0;
53 | }
54 | }
55 | `;
56 |
--------------------------------------------------------------------------------
/components/utils/BouncingLoader.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const BouncingLoader = () => (
5 |
6 |
7 |
8 |
9 |
10 | );
11 |
12 | const LoaderWrapper = styled.div`
13 | @keyframes bouncing-loader {
14 | to {
15 | opacity: 0.1;
16 | transform: translate3d(0, -1rem, 0);
17 | }
18 | }
19 |
20 | display: flex;
21 | justify-content: center;
22 |
23 | div {
24 | width: 1rem;
25 | height: 1rem;
26 | margin: 3rem 0.2rem;
27 | background: #8385aa;
28 | border-radius: 50%;
29 | animation: bouncing-loader 0.6s infinite alternate;
30 | }
31 | div:nth-child(2) {
32 | animation-delay: 0.2s;
33 | }
34 | div:nth-child(3) {
35 | animation-delay: 0.4s;
36 | }
37 | `;
38 |
39 | export default BouncingLoader;
40 |
--------------------------------------------------------------------------------
/components/utils/Components/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | // nodejs library to set properties for components
3 | import PropTypes from "prop-types";
4 |
5 | import Link from "next/link";
6 | import { ButtonWrapper, ButtonAWrapper } from "./ButtonStyle";
7 |
8 | const RegularButton = (props) => {
9 | const { children, className, href, internal, type, ...rest } = props;
10 |
11 | return type ? (
12 |
13 | {children}
14 |
15 | ) : internal ? (
16 |
17 |
18 | {children}
19 |
20 |
21 | ) : href ? (
22 |
23 | {children}
24 |
25 | ) : (
26 |
27 | {children}
28 |
29 | );
30 | };
31 |
32 | RegularButton.propTypes = {
33 | color: PropTypes.oneOf([
34 | "primary",
35 | "info",
36 | "success",
37 | "warning",
38 | "danger",
39 | "rose",
40 | "white",
41 | "facebook",
42 | "twitter",
43 | "google",
44 | "instagram",
45 | "github",
46 | "whatsapp",
47 | "transparent",
48 | "transparentBorded",
49 | ]),
50 | autoFocus: PropTypes.bool,
51 | href: PropTypes.string,
52 | size: PropTypes.oneOf(["sm", "lg", "xl"]),
53 | simple: PropTypes.bool,
54 | fullWidth: PropTypes.bool,
55 | disabled: PropTypes.bool,
56 | round: PropTypes.bool,
57 | title: PropTypes.string,
58 | link: PropTypes.bool,
59 | justIcon: PropTypes.bool,
60 | children: PropTypes.node,
61 | className: PropTypes.string,
62 | internal: PropTypes.bool,
63 | target: PropTypes.string,
64 | type: PropTypes.string,
65 | width: PropTypes.string,
66 | widthMobile: PropTypes.string,
67 | onClick: PropTypes.func,
68 | };
69 |
70 | RegularButton.defaultProps = {
71 | color: "primary",
72 | href: "",
73 | size: "lg",
74 | simple: false,
75 | fullWidth: false,
76 | width: null,
77 | widthMobile: null,
78 | disabled: false,
79 | justIcon: false,
80 | children: null,
81 | className: "",
82 | link: false,
83 | internal: false,
84 | };
85 |
86 | export default RegularButton;
87 |
--------------------------------------------------------------------------------
/components/utils/Components/Card/Card.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import classNames from "classnames";
3 | import PropTypes from "prop-types";
4 | import styled, { css } from "styled-components";
5 |
6 | export default function Card(props) {
7 | const { className, children, plain, carousel, ...rest } = props;
8 | const cardClasses = classNames({
9 | cardPlain: plain,
10 | cardCarousel: carousel,
11 | [className]: className !== undefined,
12 | });
13 |
14 | return (
15 |
16 | {children}
17 |
18 | );
19 | }
20 |
21 | Card.propTypes = {
22 | className: PropTypes.string,
23 | plain: PropTypes.bool,
24 | carousel: PropTypes.bool,
25 | cardHidden: PropTypes.bool,
26 | children: PropTypes.node,
27 | };
28 |
29 | const CardWrapper = styled.div<{ cardHidden?: boolean }>`
30 | background: #fff;
31 | border: 0;
32 | border-radius: 6px;
33 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2),
34 | 0 1px 5px 0 rgba(0, 0, 0, 0.12);
35 | color: rgba(0, 0, 0, 0.87);
36 | display: flex;
37 | flex-direction: column;
38 | font-size: 0.875rem;
39 | margin-bottom: 30px;
40 | margin-top: 30px;
41 | min-width: 0;
42 | position: relative;
43 | transition: all 300ms linear;
44 | width: 100%;
45 | word-wrap: break-word;
46 |
47 | .cardPlain {
48 | background: transparent;
49 | box-shadow: none;
50 | }
51 |
52 | .cardCarousel {
53 | overflow: hidden;
54 | }
55 |
56 | ${(props) =>
57 | props.cardHidden &&
58 | css`
59 | transform: translate3d(0, -60px, 0);
60 | opacity: 0;
61 | `}
62 | `;
63 |
--------------------------------------------------------------------------------
/components/utils/Components/Card/CardBody.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import styled from "styled-components";
4 | import { styles } from "../../index";
5 |
6 | // core components
7 |
8 | export default function CardBody(props) {
9 | const { children, ...rest } = props;
10 |
11 | return {children};
12 | }
13 |
14 | const CardBodyWrapper = styled.div`
15 | padding: 0.5rem 1rem;
16 | flex: 1 1 auto;
17 |
18 | @media (min-width: ${styles.size.tablet}) {
19 | padding: 0.9375rem 1.875rem;
20 | }
21 | `;
22 | CardBody.propTypes = {
23 | className: PropTypes.string,
24 | children: PropTypes.node
25 | };
26 |
--------------------------------------------------------------------------------
/components/utils/Components/Card/CardFooter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | // nodejs library that concatenates classes
3 | import classNames from "classnames";
4 | // nodejs library to set properties for components
5 | import PropTypes from "prop-types";
6 | import styled from "styled-components";
7 |
8 | import { styles } from "../../index";
9 |
10 | export default function CardFooter(props) {
11 | const { children, ...rest } = props;
12 |
13 | return {children};
14 | }
15 |
16 | const CardFooterWrapper = styled.div`
17 | display: flex;
18 | align-items: center;
19 |
20 | background-color: transparent;
21 | border: 0;
22 | border-radius: 6px;
23 | justify-content: center !important;
24 | padding: 0.5rem 1.875rem;
25 | padding-top: 0rem;
26 |
27 | .cardFooterLink {
28 | cursor: pointer;
29 | ${styles.transFunction("all", "0.4s", "ease-in")};
30 |
31 | &:hover {
32 | color: #000;
33 | font-size: 1.2rem;
34 | font-weight: bold;
35 | }
36 | }
37 |
38 | @media (min-width: ${styles.size.tablet}) {
39 | padding: 0.9375rem 1.875rem;
40 | }
41 | `;
42 |
43 | CardFooter.propTypes = {
44 | className: PropTypes.string,
45 | children: PropTypes.node
46 | };
47 |
--------------------------------------------------------------------------------
/components/utils/Components/Carousel/Carousel.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "react-responsive-carousel/lib/styles/carousel.min.css";
3 | import { Carousel } from "react-responsive-carousel";
4 | import styled from "styled-components";
5 | import {
6 | customIndicators,
7 | customArrowPrev,
8 | customArrowNext,
9 | } from "./CarouselStyles";
10 |
11 | interface CarouselType {
12 | infiniteLoop?: boolean;
13 | white?: any;
14 | background?: any;
15 | children?: any;
16 | }
17 |
18 | const CarouselComponent = ({
19 | infiniteLoop = false,
20 | white,
21 | background,
22 | children,
23 | }: CarouselType) => {
24 | let additionalProps: AdditionalPropsType = {};
25 |
26 | if (white) {
27 | additionalProps.renderIndicator = customIndicators;
28 | additionalProps.renderArrowPrev = customArrowPrev;
29 | additionalProps.renderArrowNext = customArrowNext;
30 | }
31 |
32 | return (
33 |
40 | {children}
41 |
42 | );
43 | };
44 |
45 | interface AdditionalPropsType {
46 | renderIndicator?: any;
47 | renderArrowPrev?: any;
48 | renderArrowNext?: any;
49 | }
50 |
51 | const StyledCarousel = styled(Carousel)<{ background: string }>`
52 | .slide {
53 | background: ${(props) => props.background || "#fff"};
54 | }
55 | `;
56 |
57 | export default CarouselComponent;
58 |
--------------------------------------------------------------------------------
/components/utils/Components/Carousel/CarouselStyles.tsx:
--------------------------------------------------------------------------------
1 | import styled, { css } from "styled-components";
2 | import { styles } from "../..";
3 |
4 | export const customIndicators = (onClickHandler, isSelected, index, label) => {
5 | if (isSelected) {
6 | return (
7 |
12 | );
13 | }
14 | return (
15 |
25 | );
26 | };
27 |
28 | const IndicatorLiWrapper = styled.li<{ selected?: boolean }>`
29 | background: ${styles.colors.mainWhite};
30 | ${({ selected }) =>
31 | selected &&
32 | `
33 | background: ${styles.colors.mainBlack};
34 | `}
35 | border: 1px solid ${styles.colors.primaryColor};
36 | border-radius: 50%;
37 | box-shadow: 1px 1px 2px rgba(0,0,0,0.9);
38 | cursor: pointer;
39 | display: inline-block;
40 | height: 8px;
41 | margin: 0 8px;
42 | transition: opacity .25s ease-in;
43 | width: 8px;
44 | `;
45 |
46 | export const customArrowPrev = (onClickHandler, hasPrev, label) =>
47 | hasPrev && (
48 |
54 | );
55 |
56 | export const customArrowNext = (onClickHandler, hasNext, label) =>
57 | hasNext && (
58 |
64 | );
65 |
66 | const ArrowPrevWrapper = styled.button<{ left?: boolean; right?: boolean }>`
67 | background: none;
68 | border: 0;
69 | bottom: 0;
70 | color: ${styles.colors.primaryColor};
71 | cursor: pointer;
72 | font-size: 26px;
73 | ${({ left }) =>
74 | left &&
75 | css`
76 | left: 0;
77 | `}
78 | ${({ right }) =>
79 | right &&
80 | css`
81 | right: 0;
82 | `}
83 | margin-top: 0;
84 | opacity: 0.4;
85 | padding: 5px;
86 | position: absolute;
87 | top: 0;
88 | transition: all 0.25s ease-in;
89 | z-index: 2;
90 |
91 | &:before {
92 | margin: 0 5px;
93 | display: inline-block;
94 | ${({ right }) =>
95 | right &&
96 | css`
97 | border-left: 8px solid ${styles.colors.primaryColor};
98 | `}
99 | ${({ left }) =>
100 | left &&
101 | css`
102 | border-right: 8px solid ${styles.colors.primaryColor};
103 | `}
104 | border-top: 8px solid transparent;
105 | border-bottom: 8px solid transparent;
106 | content: "";
107 | }
108 |
109 | &:hover {
110 | background: rgba(0, 0, 0, 0.2);
111 | opacity: 1;
112 | }
113 | `;
114 |
115 | // const arrowStyles = {
116 | // position: 'absolute',
117 | // zIndex: 2,
118 | // top: 'calc(50% - 15px)',
119 | // width: 30,
120 | // height: 30,
121 | // };
122 |
--------------------------------------------------------------------------------
/components/utils/Components/ErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { styles } from "..";
4 |
5 | const ErrorMessage = ({ message, children }) => (
6 |
7 | {message}
8 | {children}
9 |
10 | );
11 |
12 | const ErrorWrapper = styled.div`
13 | background-color: ${styles.colors.dangerColor};
14 | font-weight: bold;
15 | margin-top: 1rem;
16 | padding: 1rem;
17 | text-align: center;
18 | `;
19 | export default ErrorMessage;
20 |
--------------------------------------------------------------------------------
/components/utils/Components/Parallax.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import PropTypes from "prop-types";
3 | import styled, { css } from "styled-components";
4 | import { styles } from "..";
5 |
6 | export default function Parallax(props) {
7 | let windowScrollTop;
8 | // if (window.innerWidth >= 768) {
9 | // windowScrollTop = window.pageYOffset / 3;
10 | // } else {
11 | // windowScrollTop = 0;
12 | // }
13 | const [transform, setTransform] = useState("translate3d(0,0px,0)");
14 | useEffect(() => {
15 | if (window.innerWidth >= 768) {
16 | window.addEventListener("scroll", resetTransform);
17 | }
18 | return function cleanup() {
19 | if (window.innerWidth >= 768) {
20 | window.removeEventListener("scroll", resetTransform);
21 | }
22 | };
23 | });
24 |
25 | const resetTransform = () => {
26 | const windowScrollTop = window.pageYOffset / 3;
27 | setTransform(`translate3d(0,${windowScrollTop}px,0)`);
28 | };
29 |
30 | const { children, style, ...rest } = props;
31 |
32 | return (
33 |
39 | {children}
40 |
41 | );
42 | }
43 |
44 | const ParallaxWrapper = styled.div<{
45 | image?: string;
46 | xsmall?: boolean;
47 | small?: boolean;
48 | medium?: boolean;
49 | responsive?: boolean;
50 | }>`
51 | display: flex;
52 | align-items: center;
53 | justify-content: center;
54 |
55 | background-image: url(${(props) => props.image});
56 | background-position: center top;
57 | background-size: cover;
58 | border: 0;
59 | height: 90vh;
60 | margin: 0;
61 | max-height: 1000px;
62 | overflow: hidden;
63 | padding: 0;
64 | position: relative;
65 |
66 | ${(props) =>
67 | props.xsmall &&
68 | css`
69 | height: 30vh;
70 | `}
71 |
72 | ${(props) =>
73 | props.small &&
74 | css`
75 | height: 50vh;
76 | `}
77 |
78 | ${(props) =>
79 | props.medium &&
80 | css`
81 | height: 65vh;
82 | `}
83 |
84 | ${(props) =>
85 | props.responsive &&
86 | css`
87 | @media (max-width: ${styles.size.tablet}) {
88 | min-height: 660px;
89 | }
90 | `}
91 | `;
92 |
93 | Parallax.propTypes = {
94 | className: PropTypes.string,
95 | children: PropTypes.node,
96 | style: PropTypes.string,
97 | image: PropTypes.string,
98 | medium: PropTypes.bool,
99 | small: PropTypes.bool,
100 | xsmall: PropTypes.bool,
101 | // this will add a min-height of 660px on small screens
102 | responsive: PropTypes.bool,
103 | };
104 |
--------------------------------------------------------------------------------
/components/utils/Components/TooltipStyle.tsx:
--------------------------------------------------------------------------------
1 | import { css } from "styled-components";
2 |
3 | export const ToolTipStyle = {
4 | /*
5 |
6 | padding: "10px 15px",
7 | minWidth: "130px",
8 | color: "#555555",
9 | lineHeight: "1.7em",
10 | background: "#FFFFFF",
11 | border: "none",
12 | borderRadius: "3px",
13 | boxShadow:
14 | "0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2)",
15 | maxWidth: "200px",
16 | textAlign: "center",
17 | fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif',
18 | fontSize: "0.875em",
19 | fontStyle: "normal",
20 | fontWeight: "400",
21 | textShadow: "none",
22 | textTransform: "none",
23 | letterSpacing: "normal",
24 | wordBreak: "normal",
25 | wordSpacing: "normal",
26 | wordWrap: "normal",
27 | whiteSpace: "normal",
28 | lineBreak: "auto"
29 |
30 | */
31 | };
32 |
--------------------------------------------------------------------------------
/components/utils/GlobalStyles/Container.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from "styled-components";
2 | import { styles } from "..";
3 |
4 | export const Container = css<{}>`
5 | padding: 0.5rem 1rem;
6 | margin: 0 auto;
7 | width: 100%;
8 |
9 | @media (min-width: ${styles.size.mobileL}) {
10 | max-width: ${425 - 30}px;
11 | }
12 |
13 | @media (min-width: ${styles.size.tablet}) {
14 | max-width: ${768 - 40}px;
15 | padding: 1rem 2rem;
16 | }
17 |
18 | @media (min-width: ${styles.size.laptop}) {
19 | max-width: ${1024 - 50}px;
20 | }
21 |
22 | @media (min-width: ${styles.size.laptopL}) {
23 | max-width: ${1440 - 60}px;
24 | }
25 | `;
26 |
27 | export const ContainerWrapper = styled.div`
28 | ${Container};
29 | `;
30 |
31 | export const LoginContainerWrapper = styled.div`
32 | ${Container};
33 | color: #fff;
34 | padding-bottom: 200px;
35 | padding-top: 15vh !important;
36 | position: relative;
37 | z-index: 2;
38 | width: auto;
39 |
40 | @media (min-width: ${styles.size.tablet}) {
41 | padding-top: 20vh !important;
42 | }
43 | `;
44 | export const MainRaisedWrapper = styled.div`
45 | background: #ffffff;
46 | padding-bottom: 2.5rem;
47 | position: relative;
48 | z-index: 3;
49 | min-height: 90vh;
50 |
51 | margin: -60px 30px 0px;
52 | border-radius: 6px;
53 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14),
54 | 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.2);
55 | @media (max-width: ${styles.size.tablet}) {
56 | margin: -30px 30px 0px;
57 |
58 | margin-left: 10px;
59 | margin-right: 10px;
60 | }
61 | `;
62 |
--------------------------------------------------------------------------------
/components/utils/GlobalStyles/FormWrapper.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { styles } from "..";
3 |
4 | const FormWrapper = styled.form`
5 | margin: 0;
6 |
7 | .socialLine {
8 | margin-top: 1rem;
9 | padding: 0;
10 | text-align: center;
11 | }
12 |
13 | .divider {
14 | margin-top: 2rem;
15 | margin-bottom: 0;
16 | text-align: center;
17 | }
18 |
19 | .errorText {
20 | background: ${styles.colors.dangerColor};
21 | border-radius: 0.2rem;
22 | font-weight: 600;
23 | margin: 5px auto 5px auto;
24 | padding: 1rem;
25 | text-align: center;
26 | width: 80%;
27 | }
28 |
29 | .cardFooterPasswordForget {
30 | color: #595959;
31 | cursor: pointer;
32 | text-decoration: underline;
33 | }
34 | `;
35 |
36 | export default FormWrapper;
37 |
--------------------------------------------------------------------------------
/components/utils/GlobalStyles/GlobalCss.ts:
--------------------------------------------------------------------------------
1 | import { css } from "styled-components";
2 |
3 | export const DefaultFont = css`
4 | font-family: "Roboto", "Helvetica", "Arial", sans-serif;
5 | font-weight: 300;
6 | line-height: 1.5em;
7 | `;
8 |
--------------------------------------------------------------------------------
/components/utils/GlobalStyles/PageTitle.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { styles } from "..";
3 |
4 | export const PageTitle = styled.h1`
5 | font-size: 2rem;
6 | padding-top: 1rem;
7 |
8 | @media (min-width: ${styles.size.tablet}) {
9 | font-size: 3rem;
10 | padding-top: 2rem;
11 | }
12 | `;
13 |
14 | export const PageSubtitle = styled.h3`
15 | font-weight: 400;
16 | `;
17 |
--------------------------------------------------------------------------------
/components/utils/ImageBordered.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { styles } from ".";
4 |
5 | export const ImageBordered = ({ image, alt = "Immagine" }) => (
6 |
7 |
8 |

9 |
10 |
11 | );
12 |
13 | const ImageBorderedWrapper = styled.article`
14 | position: relative;
15 | display: block;
16 | box-shadow: ${styles.shadows.lightShadow};
17 | margin: 3rem 0;
18 |
19 | img {
20 | width: 100%;
21 | display: block;
22 | box-shadow: ${styles.shadows.lightShadow};
23 | /* object-fit: cover; */
24 | }
25 |
26 | @media screen and (min-width: ${styles.size.laptop}) {
27 | margin: 0;
28 | img {
29 | max-height: 500px;
30 | }
31 |
32 | .imgContainer::before {
33 | max-height: 500px;
34 | content: "";
35 | position: absolute;
36 | width: 100%;
37 | height: 100%;
38 | border: 3px solid ${styles.colors.mainBlack};
39 | box-sizing: border-box;
40 | top: -16px;
41 | left: -16px;
42 | z-index: -1;
43 | ${styles.transDefault};
44 | }
45 | }
46 | `;
47 |
--------------------------------------------------------------------------------
/components/utils/NumericInput.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import styled from "styled-components";
4 | import { styles } from "./index";
5 |
6 | const NumericInput = ({ value, changeState, minValue, maxValue }) => {
7 | const subtract = () => {
8 | changeState(prevState =>
9 | minValue ? Math.max(prevState - 1, minValue) : prevState - 1
10 | );
11 | };
12 |
13 | const add = () => {
14 | changeState(prevState =>
15 | maxValue ? Math.min(prevState + 1, maxValue) : prevState + 1
16 | );
17 | };
18 |
19 | const changeValue = e => {
20 | let value = 1;
21 | if (e.target.value) {
22 | value = parseInt(e.target.value);
23 | }
24 | if (minValue && value < minValue) {
25 | value = minValue;
26 | } else if (maxValue && value > maxValue) {
27 | value = maxValue;
28 | }
29 | changeState(value);
30 | };
31 |
32 | return (
33 |
34 | -
35 |
36 | +
37 |
38 | );
39 | };
40 |
41 | const NumericWrapper = styled.div`
42 | display: grid;
43 | grid-template-columns: 1fr 2fr 1fr;
44 | grid-auto-rows: auto;
45 |
46 | /* Disable the arrows in the input element */
47 | input[type="number"]::-webkit-inner-spin-button,
48 | input[type="number"]::-webkit-outer-spin-button {
49 | -webkit-appearance: none;
50 | -moz-appearance: none;
51 | appearance: none;
52 | margin: 0;
53 | }
54 |
55 | a {
56 | background: ${styles.colors.mainBlack};
57 | color: ${styles.colors.mainWhite};
58 | cursor: pointer;
59 | padding: 1rem;
60 | text-align: center;
61 | ${styles.transDefault};
62 |
63 | &:hover {
64 | transform: scale(1.1);
65 | }
66 | }
67 |
68 | input[type="number"] {
69 | font-weight: 600;
70 | text-align: center;
71 | }
72 | `;
73 |
74 | NumericInput.propTypes = {
75 | value: PropTypes.number.isRequired,
76 | minValue: PropTypes.number,
77 | maxValue: PropTypes.number,
78 | changeState: PropTypes.func.isRequired
79 | };
80 |
81 | export default NumericInput;
82 |
--------------------------------------------------------------------------------
/components/utils/Toast/Toast.js:
--------------------------------------------------------------------------------
1 | import { styles } from "../index";
2 |
3 | export const successToast = async (
4 | title,
5 | afterFunc = null,
6 | duration = 1600
7 | ) => {
8 | const { default: Swal } = await import("sweetalert2");
9 |
10 | Swal.fire({
11 | toast: true,
12 | background: styles.colors.primaryColor,
13 | position: "bottom",
14 | icon: "success",
15 | title,
16 | showConfirmButton: false,
17 | timer: duration,
18 | timerProgressBar: true,
19 | onOpen: toast => {
20 | toast.addEventListener("mouseenter", Swal.stopTimer);
21 | toast.addEventListener("mouseleave", Swal.resumeTimer);
22 | },
23 | onClose: () => {
24 | if (afterFunc) {
25 | afterFunc();
26 | }
27 | }
28 | });
29 | };
30 |
31 | export const errorToast = async (title, afterFunc = null, duration = 1800) => {
32 | const { default: Swal } = await import("sweetalert2");
33 |
34 | Swal.fire({
35 | toast: true,
36 | background: styles.colors.primaryColor,
37 | position: "bottom",
38 | icon: "error",
39 | title,
40 | showConfirmButton: true,
41 | timer: duration,
42 | timerProgressBar: true,
43 | onOpen: toast => {
44 | toast.addEventListener("mouseenter", Swal.stopTimer);
45 | toast.addEventListener("mouseleave", Swal.resumeTimer);
46 | },
47 | onClose: () => {
48 | if (afterFunc) {
49 | afterFunc();
50 | }
51 | }
52 | });
53 | };
54 |
--------------------------------------------------------------------------------
/components/utils/index.ts:
--------------------------------------------------------------------------------
1 | import * as styles from "./styles";
2 | import BouncingLoader from "./BouncingLoader";
3 | import NumericInput from "./NumericInput";
4 | import { ImageBordered } from "./ImageBordered";
5 | import {
6 | Container,
7 | ContainerWrapper,
8 | LoginContainerWrapper,
9 | MainRaisedWrapper
10 | } from "./GlobalStyles/Container";
11 | import { DefaultFont } from "./GlobalStyles/GlobalCss";
12 | import FormWrapper from "./GlobalStyles/FormWrapper";
13 | import Button from "./Components/Button/Button";
14 | import { ButtonWrapperStyle } from "./Components/Button/ButtonStyle";
15 | import Parallax from "./Components/Parallax";
16 | import Dropdown from "./Components/Dropdown/Dropdown";
17 | import CustomInput from "./Components/CustomInput";
18 | import ErrorMessage from "./Components/ErrorMessage";
19 | import Header from "./Components/Header/Header";
20 | import Card from "./Components/Card/Card";
21 | import CardHeader from "./Components/Card/CardHeader";
22 | import CardBody from "./Components/Card/CardBody";
23 | import CardFooter from "./Components/Card/CardFooter";
24 | import Carousel from "./Components/Carousel/Carousel";
25 | import { PageTitle, PageSubtitle } from "./GlobalStyles/PageTitle";
26 |
27 | import calcTotalPrice, {
28 | calcTotalItems,
29 | calcItemTotalPrice
30 | } from "./lib/calcTotalPrice";
31 | import formatMoney from "./lib/formatMoney";
32 | import formatOrderStatus from "./lib/formatOrderStatus";
33 | import formatDate from "./lib/formatDate";
34 | import { addZeroIfNeeded } from "./lib/numberUtility";
35 |
36 | export {
37 | BouncingLoader,
38 | Button,
39 | ButtonWrapperStyle,
40 | Card,
41 | CardHeader,
42 | CardBody,
43 | CardFooter,
44 | Container,
45 | ContainerWrapper,
46 | DefaultFont,
47 | FormWrapper,
48 | Dropdown,
49 | CustomInput,
50 | Header,
51 | ImageBordered,
52 | LoginContainerWrapper,
53 | MainRaisedWrapper,
54 | NumericInput,
55 | ErrorMessage,
56 | Parallax,
57 | Carousel,
58 | styles,
59 | calcTotalPrice,
60 | calcTotalItems,
61 | calcItemTotalPrice,
62 | addZeroIfNeeded,
63 | formatMoney,
64 | formatOrderStatus,
65 | formatDate,
66 | PageTitle,
67 | PageSubtitle
68 | };
69 |
--------------------------------------------------------------------------------
/components/utils/lib/calcTotalPrice.ts:
--------------------------------------------------------------------------------
1 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
2 |
3 | export default function calcTotalPrice(cartItems: Array) {
4 | const total = cartItems.reduce((tally, cartItem) => {
5 | if (!cartItem) return tally;
6 | return tally + cartItem.quantity * cartItem.price;
7 | }, 0);
8 |
9 | return total;
10 | }
11 |
12 | export const calcItemTotalPrice = (cartItem: NexusGenObjects["CartItem"]) =>
13 | cartItem.quantity * cartItem.price;
14 |
15 | export const calcTotalItems = (cartItems: Array): string => {
16 | const totalItems = cartItems.reduce((tally, cartItem) => {
17 | if (!cartItem) return tally;
18 | return tally + cartItem.quantity;
19 | }, 0);
20 |
21 | if (totalItems === 1) {
22 | return `${totalItems} articolo`;
23 | }
24 | return `${totalItems} articoli`;
25 | };
26 |
--------------------------------------------------------------------------------
/components/utils/lib/formatDate.ts:
--------------------------------------------------------------------------------
1 | export default function formatDate(date) {
2 | return new Date(date).toLocaleDateString("it-IT", {
3 | day: "numeric",
4 | month: "short",
5 | year: "numeric"
6 | });
7 | }
8 |
--------------------------------------------------------------------------------
/components/utils/lib/formatMoney.ts:
--------------------------------------------------------------------------------
1 | export default function formatMoney(amount) {
2 | const options = {
3 | style: "currency",
4 | currency: "EUR",
5 | minimumFractionDigits: 2
6 | };
7 | // if its a whole, dollar amount, leave off the .00
8 | if (amount % 100 === 0) options.minimumFractionDigits = 0;
9 | const formatter = new Intl.NumberFormat("it-IT", options);
10 | return formatter.format(amount);
11 | }
12 |
--------------------------------------------------------------------------------
/components/utils/lib/formatOrderStatus.ts:
--------------------------------------------------------------------------------
1 | export default function formatOrderStatus(orderStatus) {
2 | switch (orderStatus) {
3 | case "IN_PROGRESS":
4 | return "In gestione";
5 | case "UNFULFILLED":
6 | return "In attesa";
7 | case "FULFILLED":
8 | return "Gestito";
9 | case "PARTIALLY_FULFILLED":
10 | return "Parzialmente gestito";
11 | case "SHIPPED":
12 | return "Spedito";
13 | case "ARRIVED":
14 | return "Consegnato";
15 | default:
16 | return "In gestione";
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/components/utils/lib/numberUtility.ts:
--------------------------------------------------------------------------------
1 | export const addZeroIfNeeded = (num, size) => {
2 | const s = `000000000${num}`;
3 | return s.substr(s.length - size);
4 | };
5 |
--------------------------------------------------------------------------------
/components/utils/lib/stringUtils.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | export const truncateString = (str, n) => {
6 | return str.length > n ? str.substr(0, n - 1) + "..." : str;
7 | }
--------------------------------------------------------------------------------
/constants/categories.ts:
--------------------------------------------------------------------------------
1 | export const FPS = {
2 | CODE: "FPS",
3 | TITLE: "FIRST PERSON SHOOTER",
4 | SLUG: "/first-person-shooter",
5 | ICONCLASS: "fas fa-cut"
6 | };
7 | export const RTS = {
8 | CODE: "RTS",
9 | TITLE: "REAL TIME STRATEGY",
10 | SLUG: "/real-time-strategy",
11 | ICONCLASS: "fas fa-cogs"
12 | };
13 |
14 | export const GRAPH = {
15 | CODE: "GRP",
16 | TITLE: "GRAPHIC ADVENTURES",
17 | SLUG: "/graphic-adventures",
18 | ICONCLASS: "fas fa-shopping-bag"
19 | };
20 |
--------------------------------------------------------------------------------
/constants/routes.ts:
--------------------------------------------------------------------------------
1 | export const HOME = "/";
2 | export const LOGIN = "/login";
3 | export const LOGON = "/login?step=logon";
4 | export const ACCOUNT = "/account";
5 | export const CART = "/cart";
6 | export const CHECKOUT = "/checkout";
7 | export const CATEGORIES = "/categories";
8 | export const THANKYOU = "/thank-you";
9 | export const ORDERS = "/account/orders";
10 |
11 | export const SHOPCATEGORY = "/[category]";
12 | export const ITEM = "/[category]/[slug]";
13 |
14 | export const FAQ = "/about/faq";
15 | export const HOWITWORKS = "/about/howitworks";
16 | export const CONTACTS = "/about/contacts";
17 |
--------------------------------------------------------------------------------
/frontend-structure/address/hooks/useAddresses.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@apollo/react-hooks";
2 | import QUERY_GET_ADDRESSES, { QueryGetAddressType } from "../queries/QUERY_GET_ADDRESSES";
3 |
4 |
5 | const useAddresses = (): [QueryGetAddressType, boolean] => {
6 | const { data, loading } = useQuery(QUERY_GET_ADDRESSES);
7 |
8 | return [data, loading]
9 | }
10 |
11 | export default useAddresses
--------------------------------------------------------------------------------
/frontend-structure/address/hooks/usePopulateCheckoutAddress.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "@apollo/react-hooks"
2 | import MUTATION_CHECKOUT_ADDRESS from "../mutations/MUTATION_CHECKOUT_ADDRESS"
3 |
4 |
5 | const usePopulateCheckoutAddress = (): [(_) => Promise, boolean] => {
6 |
7 | const [populateCheckout, { loading }] = useMutation(MUTATION_CHECKOUT_ADDRESS)
8 |
9 | return [populateCheckout, loading]
10 | }
11 |
12 | export default usePopulateCheckoutAddress
--------------------------------------------------------------------------------
/frontend-structure/address/mutations/MUTATION_CHECKOUT_ADDRESS.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag"
2 |
3 |
4 |
5 | const MUTATION_CHECKOUT_ADDRESS = gql`
6 | mutation MUTATION_CHECKOUT_ADDRESS($shippingAddress: ShippingAddressInput) {
7 | populateCheckout(shippingAddress: $shippingAddress) {
8 | success
9 | message
10 | data
11 | }
12 | }
13 | `
14 |
15 | export default MUTATION_CHECKOUT_ADDRESS
--------------------------------------------------------------------------------
/frontend-structure/address/queries/QUERY_GET_ADDRESSES.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
3 |
4 | const QUERY_GET_ADDRESSES = gql`
5 | query QUERY_GET_ADDRESSES {
6 | addresses {
7 | id
8 | address1
9 | address2
10 | city
11 | country
12 | province
13 | zip
14 | firstName
15 | lastName
16 | active
17 | }
18 | }
19 | `;
20 |
21 | export interface QueryGetAddressType {
22 | addresses: Array
23 | }
24 |
25 | export default QUERY_GET_ADDRESSES
--------------------------------------------------------------------------------
/frontend-structure/checkout/hooks/useAddToCart.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-apollo";
2 | import MUTATION_ADD_TO_CART from "../mutations/MUTATION_ADD_TO_CART";
3 | import QUERY_GET_CART from "../queries/QUERY_GET_CART";
4 |
5 | const useAddToCart = (): [any, boolean] => {
6 | const [addToCart, { loading }] = useMutation(MUTATION_ADD_TO_CART, {
7 | refetchQueries: [{ query: QUERY_GET_CART }],
8 | });
9 |
10 | return [addToCart, loading]
11 | }
12 |
13 | export default useAddToCart
--------------------------------------------------------------------------------
/frontend-structure/checkout/hooks/useCart.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "react-apollo";
2 | import QUERY_GET_CART, { CartQueryData } from "../queries/QUERY_GET_CART";
3 |
4 |
5 | const useCart = (): [CartQueryData, boolean] => {
6 | const { data, loading } = useQuery(QUERY_GET_CART);
7 |
8 | return [data, loading]
9 | }
10 |
11 | export default useCart
--------------------------------------------------------------------------------
/frontend-structure/checkout/hooks/useCheckoutUrl.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@apollo/react-hooks"
2 | import QUERY_GET_CHECKOUT_URL from "../queries/QUERY_GET_CHECKOUT_URL"
3 |
4 |
5 | const useCheckoutUrl = (): [any, boolean] => {
6 | const { data, loading } = useQuery(QUERY_GET_CHECKOUT_URL)
7 |
8 | return [data, loading]
9 | }
10 |
11 |
12 | export default useCheckoutUrl
--------------------------------------------------------------------------------
/frontend-structure/checkout/hooks/useRemoveFromCart.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-apollo";
2 | import MUTATION_REMOVE_FROM_CART from "../mutations/MUTATION_REMOVE_FROM_CART";
3 | import QUERY_GET_CART from "../queries/QUERY_GET_CART";
4 |
5 |
6 | const useRemoveFromCart = (): [any, boolean] => {
7 | const [removeFromCart, { loading }] = useMutation(MUTATION_REMOVE_FROM_CART, {
8 | refetchQueries: [{ query: QUERY_GET_CART }],
9 | });
10 |
11 | return [removeFromCart, loading]
12 | }
13 |
14 | export default useRemoveFromCart
--------------------------------------------------------------------------------
/frontend-structure/checkout/hooks/useUpdateItem.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-apollo";
2 | import MUTATION_UPDATE_QUANTITY from "../mutations/MUTATION_UPDATE_CART_ITEM";
3 | import QUERY_GET_CART from "../queries/QUERY_GET_CART";
4 |
5 | const useUpdateItem = (): [any, boolean] => {
6 | const [updateCartQuantity, { loading }] = useMutation(
7 | MUTATION_UPDATE_QUANTITY, {
8 | refetchQueries: [{ query: QUERY_GET_CART }],
9 | });
10 |
11 |
12 | return [updateCartQuantity, loading]
13 | }
14 |
15 | export default useUpdateItem
--------------------------------------------------------------------------------
/frontend-structure/checkout/mutations/MUTATION_ADD_TO_CART.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag"
2 |
3 |
4 |
5 | const MUTATION_ADD_TO_CART = gql`
6 | mutation MUTATION_ADD_TO_CART($variantId: String!, $quantity: Int!, $note: String) {
7 | addToCart(variantId: $variantId, quantity: $quantity, note: $note) {
8 | success
9 | message
10 | data
11 | }
12 | }
13 | `
14 |
15 | export default MUTATION_ADD_TO_CART
--------------------------------------------------------------------------------
/frontend-structure/checkout/mutations/MUTATION_REMOVE_FROM_CART.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag"
2 |
3 | const MUTATION_REMOVE_FROM_CART = gql`
4 | mutation MUTATION_REMOVE_FROM_CART($itemId: String!) {
5 | removeFromCart(itemId: $itemId) {
6 | success
7 | message
8 | data
9 | }
10 | }
11 | `
12 |
13 | export default MUTATION_REMOVE_FROM_CART
--------------------------------------------------------------------------------
/frontend-structure/checkout/mutations/MUTATION_UPDATE_CART_ITEM.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | const MUTATION_UPDATE_QUANTITY = gql`
4 | mutation MUTATION_UPDATE_QUANTITY($itemId: String!, $quantity: Int!) {
5 | updateCartQuantity(itemId: $itemId, quantity: $quantity) {
6 | success
7 | message
8 | data
9 | }
10 | }
11 | `;
12 |
13 | export default MUTATION_UPDATE_QUANTITY
--------------------------------------------------------------------------------
/frontend-structure/checkout/queries/QUERY_GET_CART.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag"
2 | import { NexusGenObjects } from "../../../generated/nexus-typegen"
3 |
4 |
5 |
6 | const QUERY_GET_CART = gql`
7 | query QUERY_GET_CART {
8 | cart {
9 | id
10 | cartItems {
11 | id
12 | title
13 | description
14 | handle
15 | categoryHandle
16 | quantityAvailable
17 | quantity
18 | image
19 | price
20 | note
21 | }
22 | }
23 | }
24 | `
25 |
26 | export interface CartQueryData {
27 | cart: NexusGenObjects["Cart"]
28 | }
29 |
30 | export default QUERY_GET_CART
--------------------------------------------------------------------------------
/frontend-structure/checkout/queries/QUERY_GET_CHECKOUT_URL.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 |
4 |
5 |
6 | const QUERY_GET_CHECKOUT_URL = gql`
7 | query QUERY_GET_CHECKOUT_URL {
8 | checkoutUrl
9 | }
10 | `
11 |
12 |
13 | export default QUERY_GET_CHECKOUT_URL
--------------------------------------------------------------------------------
/frontend-structure/order/hooks/useOrder.ts:
--------------------------------------------------------------------------------
1 | import { useLazyQuery, useQuery } from "@apollo/react-hooks"
2 | import QUERY_GET_ORDER, { QueryGetOrderType } from "../queries/QUERY_GET_ORDER"
3 |
4 |
5 | const useOrder = (orderId): [QueryGetOrderType, boolean] => {
6 | const { data, loading } = useQuery(QUERY_GET_ORDER, { variables: { orderId: orderId } })
7 |
8 | return [data, loading]
9 | }
10 |
11 | export default useOrder
--------------------------------------------------------------------------------
/frontend-structure/order/hooks/useOrders.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@apollo/react-hooks";
2 | import { OrdersQueryData, QUERY_GET_ORDERS } from "../queries/QUERY_GET_ORDERS";
3 |
4 |
5 | const useOrders = (): [OrdersQueryData, boolean] => {
6 | const { data, loading } = useQuery(QUERY_GET_ORDERS);
7 |
8 | return [data, loading]
9 | }
10 |
11 | export default useOrders
--------------------------------------------------------------------------------
/frontend-structure/order/queries/QUERY_GET_ORDER.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
3 |
4 | const QUERY_GET_ORDER = gql`
5 | query QUERY_GET_ORDER($orderId: String!) {
6 | order(orderId: $orderId) {
7 | id
8 | orderItems {
9 | quantity
10 | title
11 | image
12 | total
13 | productHandle
14 | categoryHandle
15 | }
16 | }
17 | }
18 | `;
19 |
20 | export interface QueryGetOrderType {
21 | order: NexusGenObjects["Order"]
22 | }
23 |
24 | export default QUERY_GET_ORDER
--------------------------------------------------------------------------------
/frontend-structure/order/queries/QUERY_GET_ORDERS.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
3 |
4 |
5 | export const QUERY_GET_ORDERS = gql`
6 | query QUERY_GET_ORDERS {
7 | getUserOrders {
8 | id
9 | processedAt
10 | amount
11 | fulfillment
12 | }
13 | }
14 | `;
15 |
16 | export interface OrdersQueryData {
17 | getUserOrders: Array
18 | }
--------------------------------------------------------------------------------
/frontend-structure/product/hooks/useProductAvailability.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@apollo/react-hooks"
2 | import { NexusGenObjects } from "../../../generated/nexus-typegen"
3 | import QUERY_GET_PRODUCT_AVAILABILITY, { GetProductAvailabilityType } from "../queries/QUERY_GET_PRODUCT_AVAILABILITY"
4 |
5 |
6 | const useProductAvailability = (handle: string): NexusGenObjects["ProductAvailability"] => {
7 | const { data, loading } = useQuery(QUERY_GET_PRODUCT_AVAILABILITY, { variables: { handle: handle } })
8 |
9 | if (data) {
10 | return data.productAvailability
11 | }
12 | return null
13 | }
14 |
15 | export default useProductAvailability
--------------------------------------------------------------------------------
/frontend-structure/product/hooks/useProducts.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@apollo/react-hooks"
2 | import QUERY_GET_PRODUCTS, { ProductsQueryData } from "../queries/QUERY_GET_PRODUCTS"
3 |
4 |
5 | const useProducts = (): [ProductsQueryData, boolean] => {
6 | const { data, loading } = useQuery(QUERY_GET_PRODUCTS)
7 |
8 | return [data, loading]
9 | }
10 |
11 | export default useProducts
--------------------------------------------------------------------------------
/frontend-structure/product/queries/QUERY_GET_PRODUCTS.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag"
2 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
3 |
4 |
5 | const QUERY_GET_PRODUCTS = gql`
6 | query QUERY_GET_PRODUCTS {
7 | products {
8 | id
9 | title
10 | image
11 | price
12 | handle
13 | description
14 | category {
15 | id
16 | title
17 | handle
18 | }
19 | }
20 | }
21 | `;
22 |
23 | export interface ProductsQueryData {
24 | products: Array
25 | }
26 |
27 | export default QUERY_GET_PRODUCTS
--------------------------------------------------------------------------------
/frontend-structure/product/queries/QUERY_GET_PRODUCT_AVAILABILITY.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
3 |
4 |
5 | const QUERY_GET_PRODUCT_AVAILABILITY = gql`
6 | query QUERY_GET_PRODUCT_AVAILABILITY($handle: String!) {
7 | productAvailability(handle: $handle) {
8 | id
9 | availableForSale
10 | totalInventory
11 | }
12 | }
13 | `
14 |
15 | export interface GetProductAvailabilityType {
16 | productAvailability: NexusGenObjects["ProductAvailability"]
17 | }
18 | export default QUERY_GET_PRODUCT_AVAILABILITY
--------------------------------------------------------------------------------
/frontend-structure/user/hooks/useCustomer.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react"
2 | import Router from "next/router"
3 | import { useQuery } from "react-apollo";
4 | import CURRENT_USER_QUERY from "../queries/QUERY_GET_CUSTOMER"
5 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
6 |
7 |
8 | const useCustomer = ({
9 | redirectTo = null,
10 | redirectIfFound = false
11 | } = {}): [NexusGenObjects["User"], boolean] => {
12 |
13 | const { data, loading } = useQuery(CURRENT_USER_QUERY)
14 | const user = data?.me
15 | const finished = Boolean(data)
16 | const hasUser = Boolean(user)
17 |
18 | useEffect(() => {
19 | if (!redirectTo || !finished) return
20 | if ((redirectTo && !redirectIfFound && !hasUser) || (redirectIfFound && hasUser)) {
21 | Router.push(redirectTo)
22 | }
23 | }, [redirectIfFound, redirectTo, finished, hasUser])
24 |
25 | return [user, hasUser]
26 | }
27 |
28 | export default useCustomer
--------------------------------------------------------------------------------
/frontend-structure/user/hooks/useLogin.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-apollo";
2 | import MUTATION_SIGNIN_EMAIL from "../mutations/MUTATION_SIGNIN_EMAIL";
3 | import QUERY_GET_CUSTOMER from "../queries/QUERY_GET_CUSTOMER";
4 |
5 |
6 | const useLogin = (): [any, boolean] => {
7 | const [signinEmail, { loading }] = useMutation(MUTATION_SIGNIN_EMAIL, {
8 | refetchQueries: [{ query: QUERY_GET_CUSTOMER }]
9 | });
10 |
11 | return [signinEmail, loading]
12 | }
13 |
14 | export default useLogin
--------------------------------------------------------------------------------
/frontend-structure/user/hooks/useLogout.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-apollo";
2 | import MUTATION_SIGNOUT from "../mutations/MUTATION_SIGNOUT";
3 | import QUERY_GET_CUSTOMER from "../queries/QUERY_GET_CUSTOMER";
4 |
5 |
6 | const useLogout = (): [any, boolean] => {
7 | const [signout, { loading }] = useMutation(MUTATION_SIGNOUT, {
8 | refetchQueries: [{ query: QUERY_GET_CUSTOMER }],
9 | });
10 |
11 | return [signout, loading]
12 | }
13 |
14 | export default useLogout
--------------------------------------------------------------------------------
/frontend-structure/user/hooks/useSignup.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-apollo";
2 | import MUTATION_SIGNUP_EMAIL from "../mutations/MUTATION_SIGNUP_EMAIL";
3 | import QUERY_GET_CUSTOMER from "../queries/QUERY_GET_CUSTOMER";
4 |
5 | const useSignup = (): [any, boolean] => {
6 | const [signupCustomer, { loading }] = useMutation(MUTATION_SIGNUP_EMAIL, {
7 | refetchQueries: [{ query: QUERY_GET_CUSTOMER }]
8 | });
9 |
10 | return [signupCustomer, loading]
11 | }
12 |
13 | export default useSignup
--------------------------------------------------------------------------------
/frontend-structure/user/mutations/MUTATION_SIGNIN_EMAIL.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 |
4 | const MUTATION_SIGNIN_EMAIL = gql`
5 | mutation MUTATION_SIGNIN_EMAIL($email: String!, $password: String!) {
6 | signinEmail(email: $email, password: $password) {
7 | success
8 | message
9 | data
10 | }
11 | }
12 | `;
13 |
14 | export default MUTATION_SIGNIN_EMAIL
--------------------------------------------------------------------------------
/frontend-structure/user/mutations/MUTATION_SIGNOUT.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | const MUTATION_SIGNOUT = gql`
4 | mutation MUTATION_SIGNOUT {
5 | signout {
6 | success
7 | message
8 | data
9 | }
10 | }
11 | `;
12 |
13 | export default MUTATION_SIGNOUT
--------------------------------------------------------------------------------
/frontend-structure/user/mutations/MUTATION_SIGNUP_EMAIL.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 |
4 | const MUTATION_SIGNUP_CUSTOMER = gql`
5 | mutation MUTATION_SIGNUP_CUSTOMER($email: String!, $password: String!) {
6 | signupCustomer(email: $email, password: $password) {
7 | success
8 | message
9 | data
10 | }
11 | }
12 | `;
13 |
14 | export default MUTATION_SIGNUP_CUSTOMER
--------------------------------------------------------------------------------
/frontend-structure/user/queries/QUERY_GET_CUSTOMER.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | const CURRENT_USER_QUERY = gql`
4 | query CURRENT_USER_QUERY {
5 | me {
6 | id
7 | firstName
8 | lastName
9 | email
10 | displayName
11 | }
12 | }
13 | `;
14 |
15 | export default CURRENT_USER_QUERY
--------------------------------------------------------------------------------
/generated/schema.graphql:
--------------------------------------------------------------------------------
1 | ### This file was generated by Nexus Schema
2 | ### Do not make changes to this file directly
3 |
4 |
5 | type Address {
6 | active: Boolean
7 | address1: String
8 | address2: String
9 | city: String
10 | country: String
11 | firstName: String
12 | id: String
13 | lastName: String
14 | province: String
15 | zip: String
16 | }
17 |
18 | type Cart {
19 | cartItems: [CartItem]
20 | id: String
21 | }
22 |
23 | type CartItem {
24 | categoryHandle: String
25 | description: String
26 | handle: String
27 | id: String
28 | image: String
29 | note: String
30 | price: Float
31 | quantity: Int
32 | quantityAvailable: Int
33 | title: String
34 | }
35 |
36 | type Category {
37 | description: String
38 | handle: String
39 | id: String
40 | image: String
41 | products: [Product]
42 | title: String
43 | }
44 |
45 | type FieldResponse {
46 | data: String
47 | message: String
48 | success: Boolean
49 | }
50 |
51 | type Mutation {
52 | addToCart(note: String, quantity: Int!, variantId: String!): FieldResponse
53 | populateCheckout(shippingAddress: ShippingAddressInput): FieldResponse
54 | removeFromCart(itemId: String!): FieldResponse
55 | signinEmail(email: String!, password: String!): FieldResponse
56 | signout: FieldResponse
57 | signupCustomer(email: String!, firstName: String, password: String!): FieldResponse
58 | updateCartQuantity(itemId: String!, quantity: Int!): FieldResponse
59 | }
60 |
61 | type Order {
62 | amount: Float
63 | fulfillment: String
64 | id: String
65 | orderItems: [OrderItem]
66 | processedAt: String
67 | }
68 |
69 | type OrderItem {
70 | categoryHandle: String
71 | image: String
72 | productHandle: String
73 | quantity: Int
74 | title: String
75 | total: Float
76 | }
77 |
78 | type Product {
79 | additionalImages: [ProductImage]
80 | category: Category
81 | categoryHandle: String
82 | description: String
83 | handle: String
84 | id: String
85 | image: ProductImage
86 | price: Float
87 | title: String
88 | }
89 |
90 | type ProductAvailability {
91 | availableForSale: Boolean
92 | id: String
93 | totalInventory: Int
94 | }
95 |
96 | type ProductImage {
97 | alt: String
98 | url: String
99 | }
100 |
101 | type Query {
102 | addresses: [Address]!
103 | cart: Cart
104 | checkoutUrl: String
105 | frontCategories: [Category]!
106 | getUserOrders: [Order]
107 | me: User
108 | order(orderId: String!): Order
109 | productAvailability(handle: String!): ProductAvailability
110 | products: [Product]!
111 | }
112 |
113 | input ShippingAddressInput {
114 | address1: String!
115 | address2: String
116 | city: String
117 | country: String
118 | firstName: String
119 | lastName: String
120 | province: String
121 | zip: String
122 | }
123 |
124 | type User {
125 | displayName: String
126 | email: String
127 | firstName: String
128 | id: String
129 | lastName: String
130 | }
131 |
--------------------------------------------------------------------------------
/graphql-shopify/address/functions/manage-addresses.ts:
--------------------------------------------------------------------------------
1 | import { NexusGenObjects } from "../../../generated/nexus-typegen"
2 | import { MailingAddress, MailingAddressConnection, MailingAddressEdge } from "../../../generated/shopify.model"
3 |
4 |
5 |
6 | export const manageAddresses = async (addressesConnection: MailingAddressConnection): Promise> => {
7 | return await Promise.all(addressesConnection?.edges?.map(async ({ node }: MailingAddressEdge) => await manageAddress(node))) ?? []
8 | }
9 |
10 | export const manageAddress = async (addressData: MailingAddress): Promise => {
11 |
12 | const address: NexusGenObjects["Address"] = {
13 | id: addressData.id,
14 | address1: addressData.address1,
15 | address2: addressData.address2,
16 | city: addressData.city,
17 | country: addressData.country,
18 | province: addressData.province,
19 | zip: addressData.zip,
20 | firstName: addressData.firstName,
21 | lastName: addressData.lastName,
22 | active: false
23 | }
24 |
25 | return address
26 | }
--------------------------------------------------------------------------------
/graphql-shopify/address/mutations/MUTATION_POPULATE_CHECKOUT_ADDRESS.ts:
--------------------------------------------------------------------------------
1 | import { CheckoutShippingAddressUpdateV2Payload } from '../../../generated/shopify.model'
2 |
3 | const mutationPopulateCheckoutAddress = /* GraphQL */ `
4 | mutation mutationPopulateCheckoutAddress($shippingAddress: MailingAddressInput!, $checkoutId: ID!) {
5 | checkoutShippingAddressUpdateV2(shippingAddress: $shippingAddress, checkoutId: $checkoutId) {
6 | checkoutUserErrors {
7 | code
8 | field
9 | message
10 | }
11 | checkout {
12 | id
13 | shippingAddress {
14 | firstName
15 | lastName
16 | address1
17 | province
18 | country
19 | zip
20 | }
21 | }
22 | }
23 | }
24 | `
25 |
26 | export interface PopulateCheckoutAddressType {
27 | checkoutShippingAddressUpdateV2: CheckoutShippingAddressUpdateV2Payload
28 | }
29 |
30 | export default mutationPopulateCheckoutAddress
--------------------------------------------------------------------------------
/graphql-shopify/address/queries/QUERY_GET_ADDRESSES.ts:
--------------------------------------------------------------------------------
1 | import { Customer } from "../../../generated/shopify.model";
2 |
3 |
4 |
5 | export const QUERY_GET_ADDRESSES = /* GraphQL */ `
6 | query QUERY_GET_ADDRESSES($customerAccessToken: String!) {
7 | customer(customerAccessToken: $customerAccessToken) {
8 | defaultAddress {
9 | id
10 | address1
11 | address2
12 | city
13 | country
14 | province
15 | zip
16 | firstName
17 | lastName
18 | }
19 | addresses (first:250) {
20 | edges{
21 | node {
22 | id
23 | address1
24 | address2
25 | city
26 | country
27 | province
28 | zip
29 | firstName
30 | lastName
31 | }
32 | }
33 | }
34 | }
35 | }
36 | `;
37 |
38 | export interface QueryGetAddressType {
39 | customer: Customer
40 | }
--------------------------------------------------------------------------------
/graphql-shopify/category/functions/categories-from-prisma.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NexusGenObjects } from "../../../generated/nexus-typegen";
3 | import { Collection } from "../../../generated/shopify.model";
4 |
5 |
6 | const categoriesFromPrisma = (prismaCategories: Array, shopifyCategories: Collection[]): Array => {
7 |
8 | return prismaCategories.map(cat => {
9 | const foundCat = shopifyCategories.find(x => x.id === cat.id);
10 | if (foundCat) {
11 | const image = foundCat.image?.originalSrc
12 | return {
13 | id: foundCat.id,
14 | title: foundCat.title,
15 | description: foundCat.description,
16 | handle: foundCat.handle,
17 | image: image
18 | }
19 |
20 | }
21 | })
22 | }
23 |
24 | export default categoriesFromPrisma
--------------------------------------------------------------------------------
/graphql-shopify/category/functions/index.ts:
--------------------------------------------------------------------------------
1 | import categoriesFromPrisma from './categories-from-prisma'
2 | import { manageCollection } from './manage-collection'
3 |
4 | export {
5 | categoriesFromPrisma,
6 | manageCollection
7 | }
--------------------------------------------------------------------------------
/graphql-shopify/category/functions/manage-collection.ts:
--------------------------------------------------------------------------------
1 | import { NexusGenObjects } from "../../../generated/nexus-typegen"
2 | import { Collection, CollectionConnection, ProductConnection } from "../../../generated/shopify.model"
3 | import { manageProduct, manageProducts } from "../../product/functions";
4 |
5 |
6 | export const manageCollection = async (collection: Collection): Promise => {
7 | const category: NexusGenObjects["Category"] = {
8 | id: collection.id,
9 | handle: collection.handle,
10 | title: collection.title,
11 | image: collection.image?.originalSrc ?? null,
12 | description: collection.description,
13 | };
14 |
15 | if (collection?.products?.edges.length) {
16 | category.products = await manageProducts(collection?.products)
17 | }
18 | return category
19 | }
20 |
21 | export const manageCollectionConnection = async (collectionConn: CollectionConnection): Promise> => {
22 | return await Promise.all(collectionConn?.edges?.map(async ({ node }) => await manageCollection(node)));
23 | }
--------------------------------------------------------------------------------
/graphql-shopify/category/queries/QUERY_GET_ALL_COLLECTIONS_NAMES.ts:
--------------------------------------------------------------------------------
1 | import { CollectionConnection } from "../../../generated/shopify.model"
2 |
3 |
4 | const QUERY_GET_ALL_COLLECTIONS_NAMES = /* GraphQL */ `
5 | query QUERY_GET_ALL_COLLECTIONS_NAMES {
6 | collections (first:250) {
7 | edges {
8 | node {
9 | id
10 | handle
11 | }
12 | }
13 | }
14 | }
15 | `
16 |
17 | export interface QueryAllCollectionNamesType {
18 | collections: CollectionConnection
19 | }
20 |
21 |
22 | export default QUERY_GET_ALL_COLLECTIONS_NAMES
--------------------------------------------------------------------------------
/graphql-shopify/category/queries/QUERY_GET_COLLECTION.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from "../../../generated/shopify.model"
2 |
3 | const queryGetCollection = /* GraphQL */ `
4 | query queryGetCollection($slug: String!) {
5 | collectionByHandle(handle: $slug) {
6 | id
7 | title
8 | description
9 | handle
10 | image {
11 | originalSrc
12 | altText
13 | }
14 | products (first: 250) {
15 | edges {
16 | node {
17 | id
18 | title
19 | handle
20 | description
21 | collections (first: 10){
22 | edges {
23 | node {
24 | id
25 | title
26 | handle
27 | }
28 | }
29 | }
30 | priceRange {
31 | maxVariantPrice {
32 | amount
33 | currencyCode
34 | }
35 | minVariantPrice {
36 | amount
37 | currencyCode
38 | }
39 | }
40 | images(first: 250) {
41 | pageInfo {
42 | hasNextPage
43 | hasPreviousPage
44 | }
45 | edges {
46 | node {
47 | originalSrc
48 | altText
49 | width
50 | height
51 | }
52 | }
53 | }
54 | }
55 | }
56 | }
57 | }
58 | }
59 | `
60 |
61 | export interface QueryGetCollectionProp {
62 | collectionByHandle: Collection
63 | }
64 |
65 | export default queryGetCollection
66 |
--------------------------------------------------------------------------------
/graphql-shopify/category/queries/QUERY_GET_COLLECTIONS.ts:
--------------------------------------------------------------------------------
1 | import { CollectionConnection } from "../../../generated/shopify.model"
2 |
3 | const queryGetCollections = /* GraphQL */ `
4 | query queryGetCollections($first: Int!) {
5 | collections(first: $first) {
6 | edges {
7 | node {
8 | id
9 | title
10 | handle
11 | description
12 | image {
13 | originalSrc
14 | }
15 | }
16 | }
17 | }
18 | }
19 | `
20 |
21 | export interface QueryGetCollectionsType {
22 | collections: CollectionConnection
23 | }
24 |
25 |
26 | export default queryGetCollections
27 |
--------------------------------------------------------------------------------
/graphql-shopify/checkout/mutations/MUTATION_CHECKOUT_CREATE.ts:
--------------------------------------------------------------------------------
1 | import { checkoutDetailsFragment } from '../queries/QUERY_GET_CHECKOUT'
2 |
3 | const mutationCheckoutCreate = /* GraphQL */ `
4 | mutation {
5 | checkoutCreate(input: {}) {
6 | userErrors {
7 | message
8 | field
9 | }
10 | checkout {
11 | ${checkoutDetailsFragment}
12 | }
13 | }
14 | }
15 | `
16 | export default mutationCheckoutCreate
17 |
--------------------------------------------------------------------------------
/graphql-shopify/checkout/mutations/MUTATION_CHECKOUT_ITEM_ADD.ts:
--------------------------------------------------------------------------------
1 | import { checkoutDetailsFragment } from '../queries/QUERY_GET_CHECKOUT'
2 |
3 | const mutationCheckoutItemAdd = /* GraphQL */ `
4 | mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemInput!]!) {
5 | checkoutLineItemsAdd(checkoutId: $checkoutId, lineItems: $lineItems) {
6 | userErrors {
7 | message
8 | field
9 | }
10 | checkout {
11 | ${checkoutDetailsFragment}
12 | }
13 | }
14 | }
15 | `
16 | export default mutationCheckoutItemAdd
17 |
--------------------------------------------------------------------------------
/graphql-shopify/checkout/mutations/MUTATION_CHECKOUT_ITEM_REMOVE.ts:
--------------------------------------------------------------------------------
1 | import { checkoutDetailsFragment } from '../queries/QUERY_GET_CHECKOUT'
2 |
3 | const mutationCheckoutItemRemove = /* GraphQL */ `
4 | mutation($checkoutId: ID!, $lineItemIds: [ID!]!) {
5 | checkoutLineItemsRemove(
6 | checkoutId: $checkoutId
7 | lineItemIds: $lineItemIds
8 | ) {
9 | userErrors {
10 | message
11 | field
12 | }
13 | checkout {
14 | ${checkoutDetailsFragment}
15 | }
16 | }
17 | }
18 | `
19 | export default mutationCheckoutItemRemove
20 |
--------------------------------------------------------------------------------
/graphql-shopify/checkout/mutations/MUTATION_CHECKOUT_ITEM_UPDATE.ts:
--------------------------------------------------------------------------------
1 | import { checkoutDetailsFragment } from '../queries/QUERY_GET_CHECKOUT'
2 |
3 | const mutationCheckoutItemUpdate = /* GraphQL */ `
4 | mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemUpdateInput!]!) {
5 | checkoutLineItemsUpdate(checkoutId: $checkoutId, lineItems: $lineItems) {
6 | userErrors {
7 | message
8 | field
9 | }
10 | checkout {
11 | ${checkoutDetailsFragment}
12 | }
13 | }
14 | }
15 | `
16 | export default mutationCheckoutItemUpdate
17 |
--------------------------------------------------------------------------------
/graphql-shopify/checkout/queries/QUERY_GET_CHECKOUT.ts:
--------------------------------------------------------------------------------
1 |
2 | export const checkoutDetailsFragment = `
3 | id
4 | webUrl
5 | subtotalPriceV2{
6 | amount
7 | currencyCode
8 | }
9 | totalTaxV2 {
10 | amount
11 | currencyCode
12 | }
13 | totalPriceV2 {
14 | amount
15 | currencyCode
16 | }
17 | completedAt
18 | createdAt
19 | taxesIncluded
20 | lineItems(first: 250) {
21 | pageInfo {
22 | hasNextPage
23 | hasPreviousPage
24 | }
25 | edges {
26 | node {
27 | id
28 | title
29 | customAttributes {
30 | key
31 | value
32 | }
33 | variant {
34 | id
35 | sku
36 | title
37 | availableForSale
38 | quantityAvailable
39 | image {
40 | originalSrc
41 | altText
42 | width
43 | height
44 | }
45 | priceV2{
46 | amount
47 | currencyCode
48 | }
49 | compareAtPriceV2{
50 | amount
51 | currencyCode
52 | }
53 | product {
54 | id
55 | handle
56 | description
57 | collections(first:1) {
58 | edges {
59 | node {
60 | id
61 | title
62 | handle
63 | }
64 | }
65 | }
66 | }
67 | }
68 | quantity
69 | }
70 | }
71 | }
72 | `
73 |
74 | const getCheckoutQuery = /* GraphQL */ `
75 | query($checkoutId: ID!) {
76 | node(id: $checkoutId) {
77 | ... on Checkout {
78 | ${checkoutDetailsFragment}
79 | }
80 | }
81 | }
82 | `
83 |
84 | export default getCheckoutQuery
85 |
--------------------------------------------------------------------------------
/graphql-shopify/checkout/utils/checkout-create.ts:
--------------------------------------------------------------------------------
1 | import { Context } from '../../context';
2 | import MUTATION_CHECKOUT_CREATE from '../mutations/MUTATION_CHECKOUT_CREATE';
3 | import { Checkout } from '../../../generated/shopify.model'
4 | import { setUserSession } from '../../../lib/auth/auth-session';
5 |
6 |
7 |
8 | export const checkoutCreate = async (ctx: Context): Promise => {
9 | const data = await ctx.shopifyGraphql.request(MUTATION_CHECKOUT_CREATE)
10 |
11 | const checkout: Checkout = data.checkoutCreate?.checkout
12 | const checkoutId = checkout?.id
13 |
14 | if (checkoutId) {
15 | await setUserSession(ctx, {
16 | checkoutId: checkout.id,
17 | checkoutUrl: checkout.webUrl
18 | })
19 | }
20 |
21 | return checkout
22 | }
23 |
--------------------------------------------------------------------------------
/graphql-shopify/checkout/utils/checkout-to-cart.ts:
--------------------------------------------------------------------------------
1 | import { Checkout, Maybe } from "../../../generated/shopify.model"
2 | import { CartCheckout } from "../checkout-interface"
3 | import { normalizeCart } from "./normalize"
4 |
5 |
6 | const transfromCheckoutToCart = (checkout?: Maybe): CartCheckout => {
7 | if (!checkout) {
8 | //TODO: Throw new error
9 | return;
10 | }
11 |
12 | return normalizeCart(checkout)
13 | }
14 |
15 | export default transfromCheckoutToCart
--------------------------------------------------------------------------------
/graphql-shopify/checkout/utils/normalize.ts:
--------------------------------------------------------------------------------
1 | import { CartCheckout, LineItem } from "../checkout-interface";
2 | import { Checkout, CheckoutLineItemEdge, CollectionConnection, Collection } from '../../../generated/shopify.model'
3 |
4 |
5 | export function normalizeCart(checkout: Checkout): CartCheckout {
6 | return {
7 | id: checkout.id,
8 | customerId: '',
9 | email: '',
10 | createdAt: checkout.createdAt,
11 | currency: {
12 | code: checkout.totalPriceV2?.currencyCode,
13 | },
14 | taxesIncluded: checkout.taxesIncluded,
15 | lineItems: checkout.lineItems?.edges.map(normalizeLineItem),
16 | lineItemsSubtotalPrice: +checkout.subtotalPriceV2?.amount,
17 | subtotalPrice: +checkout.subtotalPriceV2?.amount,
18 | totalPrice: checkout.totalPriceV2?.amount,
19 | discounts: [],
20 | }
21 | }
22 |
23 | export function normalizeLineItem({
24 | node: { id, title, variant, quantity, customAttributes },
25 | }: CheckoutLineItemEdge): LineItem {
26 |
27 | const collection: Collection = variant?.product?.collections?.edges.length ? variant.product.collections.edges[0].node : null
28 | const lineItem: LineItem = {
29 | id,
30 | variantId: String(variant?.id),
31 | productId: String(variant?.id),
32 | name: `${title}`,
33 | quantity,
34 | handle: variant?.product?.handle,
35 | categoryHandle: collection?.handle,
36 | variant: {
37 | id: String(variant?.id),
38 | sku: variant?.sku ?? '',
39 | name: variant?.title!,
40 | description: variant?.product?.description,
41 | image: {
42 | url: variant?.image?.originalSrc,
43 | },
44 | requiresShipping: variant?.requiresShipping ?? false,
45 | price: variant?.priceV2?.amount,
46 | listPrice: variant?.compareAtPriceV2?.amount,
47 | quantityAvailable: variant.quantityAvailable,
48 | availableForSale: variant.availableForSale
49 | },
50 | discounts: [],
51 | options:
52 | // By default Shopify adds a default variant with default names, we're removing it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095
53 | variant?.title == 'Default Title'
54 | ? []
55 | : [
56 | {
57 | value: variant?.title,
58 | },
59 | ],
60 | }
61 |
62 | if (customAttributes?.length) {
63 | customAttributes.forEach(ca => {
64 | switch (ca.key) {
65 | case "note":
66 | lineItem.note = ca.value
67 | break;
68 | }
69 | })
70 | }
71 |
72 | return lineItem
73 | }
--------------------------------------------------------------------------------
/graphql-shopify/common/functions/get-checkout-id.ts:
--------------------------------------------------------------------------------
1 | import { getUserSession } from "../../../lib/auth/auth-session";
2 | import { Context } from "../../context";
3 |
4 | const getCheckoutId = async (ctx: Context): Promise => {
5 | const session = await getUserSession(ctx.req)
6 | if (session?.checkoutId) {
7 | // TODO: Manage cookie expiration date
8 | // if (session.expiresAt < Date.now()) {
9 | // return null;
10 | // }
11 | return session.checkoutId
12 | }
13 |
14 | return null;
15 | }
16 |
17 | export default getCheckoutId
--------------------------------------------------------------------------------
/graphql-shopify/common/functions/get-checkout-url.ts:
--------------------------------------------------------------------------------
1 | import { getUserSession } from "../../../lib/auth/auth-session";
2 | import { Context } from "../../context";
3 |
4 |
5 | const getCheckoutUrl = async (ctx: Context): Promise => {
6 | const session = await getUserSession(ctx.req)
7 | if (session?.checkoutUrl) {
8 | // TODO: Manage cookie expiration date
9 | // if (session.expiresAt < Date.now()) {
10 | // return null;
11 | // }
12 | return session.checkoutUrl
13 | }
14 |
15 | return null;
16 | }
17 |
18 | export default getCheckoutUrl
--------------------------------------------------------------------------------
/graphql-shopify/common/utils/normalize.ts:
--------------------------------------------------------------------------------
1 | import { MoneyV2 } from "../../../generated/shopify.model"
2 |
3 |
4 |
5 | export const money = ({ amount, currencyCode }: MoneyV2) => {
6 | return {
7 | value: +amount,
8 | currencyCode,
9 | }
10 | }
--------------------------------------------------------------------------------
/graphql-shopify/context.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLClient } from 'graphql-request'
2 |
3 |
4 | export const createShopifyGraphql = () => {
5 | return new GraphQLClient(
6 | process.env.SHOPIFY_GRAPHQL_ENDPOINT, {
7 | headers: {
8 | "X-Shopify-Storefront-Access-Token": process.env.STOREFRONT_ACCESS_TOKEN,
9 | },
10 | }
11 | )
12 | }
13 |
14 | export interface Context {
15 | req: any;
16 | res: any;
17 | shopifyGraphql: GraphQLClient
18 | }
19 |
20 | export function createContext(ctx): Context {
21 | return {
22 | req: ctx.req,
23 | res: ctx.res,
24 | shopifyGraphql: createShopifyGraphql()
25 | };
26 | }
--------------------------------------------------------------------------------
/graphql-shopify/order/QUERY_GET_ORDER_DETAILS.ts:
--------------------------------------------------------------------------------
1 | import { Order } from "../../generated/shopify.model"
2 |
3 |
4 | const getOrderDetails = /* GraphQL */ `
5 | query($orderId: ID!) {
6 | node(id: $orderId) {
7 | ... on Order {
8 | id
9 | fulfillmentStatus
10 | financialStatus
11 | lineItems(first:250) {
12 | edges {
13 | node {
14 | title
15 | quantity
16 | discountedTotalPrice {
17 | amount
18 | currencyCode
19 | }
20 | variant {
21 | image {
22 | originalSrc
23 | }
24 | product {
25 | handle
26 | collections (first:250) {
27 | edges {
28 | node {
29 | handle
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 | `
42 |
43 | export interface GetOrderDetailsType {
44 | node: Order
45 | }
46 |
47 | export default getOrderDetails
--------------------------------------------------------------------------------
/graphql-shopify/order/functions/manage-order.ts:
--------------------------------------------------------------------------------
1 | import { NexusGenObjectNames, NexusGenObjects } from "../../../generated/nexus-typegen";
2 | import { Order, OrderLineItem, OrderLineItemConnection, OrderLineItemEdge } from "../../../generated/shopify.model";
3 | import { normalizeProductCollections } from "../../product/utils/normalize";
4 |
5 |
6 | const manageOrder = async (orderShopify: Order): Promise => {
7 |
8 | orderShopify.lineItems
9 |
10 | const order: NexusGenObjects["Order"] = {
11 | id: orderShopify.id
12 | }
13 |
14 | order.orderItems = await manageOrderLineItems(orderShopify.lineItems)
15 |
16 | return order
17 | }
18 |
19 | const manageOrderLineItems = async (orderItemsConnection: OrderLineItemConnection): Promise> => {
20 | return await Promise.all(orderItemsConnection?.edges?.map(async ({ node }: OrderLineItemEdge) => await manageOrderLineItem(node))) ?? []
21 | }
22 |
23 | const manageOrderLineItem = async (orderLineItem: OrderLineItem): Promise => {
24 |
25 | const orderItem: NexusGenObjects["OrderItem"] = {
26 | title: orderLineItem.title,
27 | quantity: orderLineItem.quantity,
28 | total: orderLineItem.discountedTotalPrice?.amount,
29 | image: orderLineItem.variant?.image?.originalSrc
30 | }
31 |
32 | if (orderLineItem.variant?.product?.collections) {
33 | const collectionsNormalized = normalizeProductCollections(orderLineItem.variant?.product.collections)
34 | if (collectionsNormalized && collectionsNormalized.length) {
35 | orderItem.productHandle = orderLineItem.variant?.product?.handle
36 | orderItem.categoryHandle = collectionsNormalized[0].handle
37 | }
38 | }
39 |
40 |
41 | return orderItem
42 | }
43 |
44 |
45 |
46 | export default manageOrder
--------------------------------------------------------------------------------
/graphql-shopify/order/queries/QUERY_GET_USER_ORDERS.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export const QUERY_GET_USER_ORDERS = /* GraphQL */ `
4 | query QUERY_GET_USER_ORDERS($customerAccessToken: String!) {
5 | customer(customerAccessToken: $customerAccessToken) {
6 | orders(first: 250) {
7 | edges {
8 | node {
9 | id
10 | totalPriceV2 {
11 | amount
12 | currencyCode
13 | }
14 | processedAt
15 | fulfillmentStatus
16 | }
17 | }
18 | }
19 | }
20 | }
21 | `;
--------------------------------------------------------------------------------
/graphql-shopify/product/functions/index.ts:
--------------------------------------------------------------------------------
1 | import manageProducts, { manageProduct } from './manage-products'
2 |
3 |
4 | export {
5 | manageProducts,
6 | manageProduct
7 | }
--------------------------------------------------------------------------------
/graphql-shopify/product/functions/manage-products.ts:
--------------------------------------------------------------------------------
1 | import { NexusGenObjects } from "../../../generated/nexus-typegen"
2 | import { Product, ProductConnection, ProductEdge } from "../../../generated/shopify.model"
3 | import { ProductImage, ProductNormalized } from "../product-interface"
4 | import { normalizeProduct } from "../utils/normalize"
5 |
6 |
7 | const manageProducts = async (productConnection: ProductConnection): Promise> => {
8 | const products: Array = await Promise.all(productConnection?.edges?.map(async ({ node }: ProductEdge) => await manageProduct(node))) ?? []
9 | return products
10 | }
11 |
12 | export const manageProduct = async (productData: Product): Promise => {
13 | const pn: ProductNormalized = normalizeProduct(productData)
14 | const [firstImage, ...additionalImgs] = pn.images
15 | const image = manageProductImage(firstImage)
16 |
17 | const collection = pn.collections ? pn.collections[0] : null
18 |
19 | let additionalImages: Array = []
20 | if (pn.images.length > 1) {
21 | additionalImages = additionalImgs.map(img => manageProductImage(img))
22 | }
23 |
24 | const id: string = (pn.variants && pn.variants.length) ? pn.variants[0].id : pn.id
25 |
26 | const product: NexusGenObjects["Product"] = {
27 | id,
28 | price: pn.price?.value,
29 | handle: pn.handle,
30 | image,
31 | additionalImages,
32 | title: pn.name,
33 | description: pn.description,
34 | }
35 |
36 | if (collection) {
37 | product.categoryHandle = collection.handle
38 | product.category = {
39 | id: collection.id,
40 | title: collection.title,
41 | handle: collection.handle
42 | };
43 | }
44 |
45 | return product
46 | }
47 |
48 | const manageProductImage = (normalizedProductImage: ProductImage): NexusGenObjects["ProductImage"] => {
49 | return {
50 | url: normalizedProductImage.url,
51 | alt: normalizedProductImage.alt ? normalizedProductImage.alt : null
52 | }
53 | }
54 |
55 |
56 | export default manageProducts
--------------------------------------------------------------------------------
/graphql-shopify/product/product-interface.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | interface Entity {
4 | id: string
5 | [prop: string]: any
6 | }
7 |
8 | export interface ProductNormalized extends Entity {
9 | name: string
10 | description: string
11 | slug?: string
12 | handle?: string
13 | collections: ProductCollection[]
14 | images: ProductImage[]
15 | variants: ProductVariant2[]
16 | price: ProductPrice
17 | options: ProductOption[]
18 | sku?: string
19 | }
20 |
21 | interface ProductCollection {
22 | id: string
23 | title: string
24 | handle: string
25 | }
26 |
27 | interface ProductOption extends Entity {
28 | displayName: string
29 | values: ProductOptionValues[]
30 | }
31 |
32 | interface ProductOptionValues {
33 | label: string
34 | hexColors?: string[]
35 | }
36 |
37 | export interface ProductImage {
38 | url: string
39 | alt?: string
40 | }
41 |
42 | interface ProductVariant2 {
43 | id: string
44 | options: ProductOption[]
45 | }
46 |
47 | interface ProductPrice {
48 | value: number
49 | currencyCode: 'USD' | 'ARS' | string | undefined
50 | retailPrice?: number
51 | salePrice?: number
52 | listPrice?: number
53 | extendedSalePrice?: number
54 | extendedListPrice?: number
55 | }
--------------------------------------------------------------------------------
/graphql-shopify/product/queries/QUERY_GET_ALL_PRODUCT_NAMES.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | const QUERY_GET_ALL_PRODUCT_NAMES = /* GraphQL */ `
4 | query {
5 | products(first: 250) {
6 | edges {
7 | node {
8 | handle
9 | collections(first: 1) {
10 | edges {
11 | node {
12 | handle
13 | }
14 | }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | `;
21 |
22 | export default QUERY_GET_ALL_PRODUCT_NAMES
--------------------------------------------------------------------------------
/graphql-shopify/product/queries/QUERY_GET_PRODUCT.ts:
--------------------------------------------------------------------------------
1 | import { Product } from "../../../generated/shopify.model"
2 |
3 | const queryGetProduct = /* GraphQL */ `
4 | query queryGetProduct($slug: String!) {
5 | productByHandle(handle: $slug) {
6 | id
7 | handle
8 | title
9 | productType
10 | vendor
11 | description
12 | descriptionHtml
13 | options {
14 | id
15 | name
16 | values
17 | }
18 | priceRange {
19 | maxVariantPrice {
20 | amount
21 | currencyCode
22 | }
23 | minVariantPrice {
24 | amount
25 | currencyCode
26 | }
27 | }
28 | variants(first: 250) {
29 | pageInfo {
30 | hasNextPage
31 | hasPreviousPage
32 | }
33 | edges {
34 | node {
35 | id
36 | title
37 | sku
38 | selectedOptions {
39 | name
40 | value
41 | }
42 | priceV2 {
43 | amount
44 | currencyCode
45 | }
46 | compareAtPriceV2 {
47 | amount
48 | currencyCode
49 | }
50 | }
51 | }
52 | }
53 | images(first: 250) {
54 | pageInfo {
55 | hasNextPage
56 | hasPreviousPage
57 | }
58 | edges {
59 | node {
60 | originalSrc
61 | altText
62 | width
63 | height
64 | }
65 | }
66 | }
67 | }
68 | }
69 | `
70 |
71 | export interface QueryGetProduct {
72 | productByHandle: Product
73 | }
74 |
75 | export default queryGetProduct
76 |
--------------------------------------------------------------------------------
/graphql-shopify/product/queries/QUERY_GET_PRODUCTS.ts:
--------------------------------------------------------------------------------
1 | import { ProductConnection } from "../../../generated/shopify.model"
2 |
3 | export const productConnection = `
4 | pageInfo {
5 | hasNextPage
6 | hasPreviousPage
7 | }
8 | edges {
9 | node {
10 | id
11 | title
12 | vendor
13 | handle
14 | description
15 | collections(first:1) {
16 | edges {
17 | node {
18 | id
19 | title
20 | handle
21 | }
22 | }
23 | }
24 | priceRange {
25 | minVariantPrice {
26 | amount
27 | currencyCode
28 | }
29 | }
30 | images(first: 1) {
31 | pageInfo {
32 | hasNextPage
33 | hasPreviousPage
34 | }
35 | edges {
36 | node {
37 | originalSrc
38 | altText
39 | width
40 | height
41 | }
42 | }
43 | }
44 | }
45 | }
46 | `
47 |
48 | export const productsFragment = `
49 | products(
50 | first: $first
51 | sortKey: $sortKey
52 | reverse: $reverse
53 | query: $query
54 | ) {
55 | ${productConnection}
56 | }
57 | `
58 |
59 | const getAllProductsQuery = /* GraphQL */ `
60 | query getAllProducts(
61 | $first: Int = 250
62 | $query: String = ""
63 | $sortKey: ProductSortKeys = RELEVANCE
64 | $reverse: Boolean = false
65 | ) {
66 | ${productsFragment}
67 | }
68 | `
69 |
70 |
71 | export interface GetAllProductsQueryType {
72 | products: ProductConnection
73 | }
74 | export default getAllProductsQuery
75 |
--------------------------------------------------------------------------------
/graphql-shopify/product/queries/QUERY_GET_PRODUCT_AVAILABILITY.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | const QUERY_GET_PRODUCT_AVAILABILITY = /* GraphQL */ `
4 | query QUERY_GET_PRODUCT_AVAILABILITY($handle: String!) {
5 | productByHandle(handle: $handle) {
6 | id
7 | availableForSale
8 | totalInventory
9 | }
10 | }
11 | `
12 |
13 |
14 | export interface GetProductAvailabilityType {
15 | productByHandle: {
16 | id: string,
17 | availableForSale: boolean,
18 | totalInventory: number
19 | }
20 | }
21 |
22 | export default QUERY_GET_PRODUCT_AVAILABILITY
--------------------------------------------------------------------------------
/graphql-shopify/product/utils/normalize.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Product,
3 | ImageConnection,
4 | ProductVariantConnection,
5 | ProductOption,
6 | SelectedOption,
7 | CollectionConnection
8 | } from "../../../generated/shopify.model"
9 | import { money } from "../../common/utils/normalize"
10 | import { ProductNormalized } from "../product-interface"
11 |
12 |
13 | export function normalizeProduct(productNode: Product): ProductNormalized {
14 | const {
15 | id,
16 | title: name,
17 | vendor,
18 | collections,
19 | images,
20 | variants,
21 | description,
22 | handle,
23 | priceRange,
24 | options,
25 | ...rest
26 | } = productNode
27 |
28 | const product = {
29 | id,
30 | name,
31 | vendor,
32 | description,
33 | handle: handle,
34 | slug: handle?.replace(/^\/+|\/+$/g, ''),
35 | collections: collections ? normalizeProductCollections(collections) : [],
36 | price: priceRange ? money(priceRange?.minVariantPrice) : null,
37 | images: images ? normalizeProductImages(images) : [],
38 | variants: variants ? normalizeProductVariants(variants) : [],
39 | options: options ? options.map((o) => normalizeProductOption(o)) : [],
40 | ...rest,
41 | }
42 |
43 | return product
44 | }
45 |
46 | export const normalizeProductCollections = ({ edges }: CollectionConnection) => {
47 | return edges?.map(
48 | ({
49 | node: { id, title, handle },
50 | }) => ({
51 | id,
52 | title,
53 | handle
54 | })
55 | )
56 | }
57 |
58 | const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
59 | return edges?.map(
60 | ({
61 | node: { id, selectedOptions, sku, title, priceV2, compareAtPriceV2 },
62 | }) => ({
63 | id,
64 | name: title,
65 | sku: sku ?? id,
66 | price: +priceV2.amount,
67 | listPrice: +compareAtPriceV2?.amount,
68 | requiresShipping: true,
69 | options: selectedOptions.map(({ name, value }: SelectedOption) =>
70 | normalizeProductOption({
71 | id,
72 | name,
73 | values: [value],
74 | })
75 | ),
76 | })
77 | )
78 | }
79 |
80 | const normalizeProductOption = ({
81 | id,
82 | name: displayName,
83 | values,
84 | }: ProductOption) => {
85 | return {
86 | __typename: 'MultipleChoiceOption',
87 | id,
88 | displayName,
89 | values: values.map((value) => {
90 | let output: any = {
91 | label: value,
92 | }
93 | if (displayName === 'Color') {
94 | output = {
95 | ...output,
96 | hexColors: [value],
97 | }
98 | }
99 | return output
100 | }),
101 | }
102 | }
103 |
104 |
105 | const normalizeProductImages = ({ edges }: ImageConnection) =>
106 | edges?.map(({ node: { originalSrc: url, ...rest } }) => ({
107 | url,
108 | ...rest,
109 | }))
--------------------------------------------------------------------------------
/graphql-shopify/schema.ts:
--------------------------------------------------------------------------------
1 | import { makeSchema } from 'nexus'
2 | import path from 'path';
3 | import * as types from './types'
4 |
5 | export const schema = makeSchema({
6 | types,
7 | // plugins: [nexusPrisma({ experimentalCRUD: true })],
8 | outputs: {
9 | typegen: path.join(process.cwd(), 'generated', 'nexus-typegen.ts'),
10 | schema: path.join(process.cwd(), 'generated', 'schema.graphql')
11 | },
12 | contextType: {
13 | module: path.join(process.cwd(), 'graphql-shopify', 'context.ts'),
14 | export: 'Context'
15 | },
16 | sourceTypes: {
17 | modules: [
18 | {
19 | module: '@prisma/client',
20 | alias: "prisma",
21 | }
22 | ],
23 |
24 | },
25 | })
--------------------------------------------------------------------------------
/graphql-shopify/types/Category.ts:
--------------------------------------------------------------------------------
1 | import { extendType, objectType } from "nexus";
2 | import { NexusGenObjects } from "../../generated/nexus-typegen";
3 | import { manageCollectionConnection } from "../category/functions/manage-collection";
4 | import QUERY_GET_COLLECTIONS, { QueryGetCollectionsType } from "../category/queries/QUERY_GET_COLLECTIONS";
5 |
6 | export const Category = objectType({
7 | name: 'Category',
8 | definition(t) {
9 | t.string('id');
10 | t.string('title');
11 | t.string('description');
12 | t.string('image');
13 | t.string('handle');
14 | t.list.field('products', {
15 | type: 'Product'
16 | })
17 | }
18 | })
19 |
20 | export const CategoryQuery = extendType({
21 | type: 'Query',
22 | definition: t => {
23 | t.nonNull.list.field('frontCategories', {
24 | type: 'Category',
25 | async resolve(parent, args, ctx) {
26 |
27 | let frontCategories: Array = []
28 | const collectionsData = await ctx.shopifyGraphql.request(QUERY_GET_COLLECTIONS)
29 | frontCategories = await manageCollectionConnection(collectionsData.collections)
30 |
31 | return frontCategories
32 | }
33 | })
34 | }
35 | })
--------------------------------------------------------------------------------
/graphql-shopify/types/Common.ts:
--------------------------------------------------------------------------------
1 | import { objectType } from "nexus"
2 |
3 |
4 |
5 |
6 | export const FieldResponse = objectType({
7 | name: 'FieldResponse',
8 | definition(t) {
9 | t.boolean('success')
10 | t.string('message')
11 | t.string('data')
12 | },
13 | })
--------------------------------------------------------------------------------
/graphql-shopify/types/Order.ts:
--------------------------------------------------------------------------------
1 | import { extendType, nonNull, objectType, stringArg } from "nexus"
2 | import { NexusGenObjects } from "../../generated/nexus-typegen"
3 | import { OrderEdge } from "../../generated/shopify.model"
4 | import { getUserSession, Session } from "../../lib/auth/auth-session"
5 | import manageOrder from "../order/functions/manage-order"
6 | import { QUERY_GET_USER_ORDERS } from "../order/queries/QUERY_GET_USER_ORDERS"
7 | import QUERY_GET_ORDER_DETAILS, { GetOrderDetailsType } from "../order/QUERY_GET_ORDER_DETAILS"
8 |
9 |
10 |
11 | export const Order = objectType({
12 | name: 'Order',
13 | definition(t) {
14 | t.string('id')
15 | t.string('processedAt')
16 | t.string('fulfillment')
17 | t.float('amount')
18 | t.list.field('orderItems', {
19 | type: 'OrderItem'
20 | })
21 | }
22 | })
23 |
24 | export const OrderItem = objectType({
25 | name: 'OrderItem',
26 | definition(t) {
27 | t.string('title')
28 | t.int('quantity')
29 | t.string('image')
30 | t.float('total')
31 | t.string('productHandle')
32 | t.string('categoryHandle')
33 | }
34 | })
35 |
36 |
37 | export const OrderQuery = extendType({
38 | type: 'Query',
39 | definition: t => {
40 | t.list.field('getUserOrders', {
41 | type: 'Order',
42 | async resolve(_, args, ctx) {
43 | try {
44 | const session: Session = await getUserSession(ctx.req)
45 | const { customer } = await ctx.shopifyGraphql.request(QUERY_GET_USER_ORDERS, { customerAccessToken: session.accessToken })
46 |
47 | const ordersShopify: OrderEdge[] = customer.orders.edges
48 |
49 | const orders: Array = ordersShopify.map(({ node }) => {
50 | const order: NexusGenObjects["Order"] = {
51 | id: node.id,
52 | processedAt: node.processedAt,
53 | amount: node.totalPriceV2?.amount,
54 | fulfillment: node.fulfillmentStatus
55 | }
56 | return order
57 | });
58 |
59 | return orders
60 |
61 | } catch (error) {
62 | console.log("Error getting user orders", error)
63 | }
64 | }
65 | })
66 | t.field('order', {
67 | type: 'Order',
68 | args: { orderId: nonNull(stringArg()) },
69 | async resolve(_, { orderId }, ctx) {
70 | const orderDetailsData = await ctx.shopifyGraphql.request(QUERY_GET_ORDER_DETAILS, { orderId: orderId })
71 | return await manageOrder(orderDetailsData.node)
72 | }
73 | })
74 | }
75 | })
--------------------------------------------------------------------------------
/graphql-shopify/types/Product.ts:
--------------------------------------------------------------------------------
1 | import { extendType, nonNull, objectType, stringArg } from "nexus";
2 | import { Product as ProductShopify, Collection, ProductEdge } from "../../generated/shopify.model"
3 | import { NexusGenObjects } from "../../generated/nexus-typegen"
4 | import QUERY_GET_PRODUCTS from "../product/queries/QUERY_GET_PRODUCTS";
5 | import { normalizeProduct } from "../product/utils/normalize";
6 | import { ProductNormalized } from "../product/product-interface";
7 | // import { Product as ShopifyProduct } from '../../generated/shopify.model'
8 | import { manageProducts } from '../product/functions/'
9 | import QUERY_GET_PRODUCT_AVAILABILITY, { GetProductAvailabilityType } from "../product/queries/QUERY_GET_PRODUCT_AVAILABILITY";
10 |
11 |
12 | export const ProductQuery = extendType({
13 | type: 'Query',
14 | definition: t => {
15 | t.nonNull.list.field('products', {
16 | type: 'Product',
17 | async resolve(parent, args, ctx) {
18 |
19 | const productData = await ctx.shopifyGraphql.request(QUERY_GET_PRODUCTS, { query: "tag:featured" })
20 | const products = await manageProducts(productData)
21 | return products
22 | }
23 | })
24 | t.field('productAvailability', {
25 | type: 'ProductAvailability',
26 | args: { handle: nonNull(stringArg()) },
27 | async resolve(parent, { handle }, ctx) {
28 | const productData = await ctx.shopifyGraphql.request(QUERY_GET_PRODUCT_AVAILABILITY, { handle: handle })
29 |
30 | return productData.productByHandle
31 | }
32 | })
33 | }
34 | })
35 |
36 | export const Product = objectType({
37 | name: 'Product',
38 | definition(t) {
39 | t.string('id');
40 | t.string('title');
41 | t.field('image', {
42 | type: 'ProductImage'
43 | });
44 | t.list.field('additionalImages', {
45 | type: 'ProductImage'
46 | })
47 | t.string('handle');
48 | t.string('description');
49 | t.float('price');
50 | t.field('category', {
51 | type: 'Category',
52 | });
53 | t.string('categoryHandle');
54 | }
55 | })
56 |
57 | export const ProductImage = objectType({
58 | name: 'ProductImage',
59 | definition(t) {
60 | t.string('url');
61 | t.string('alt')
62 | }
63 | })
64 |
65 | export const ProductAvailability = objectType({
66 | name: 'ProductAvailability',
67 | definition(t) {
68 | t.string('id')
69 | t.boolean('availableForSale')
70 | t.int('totalInventory');
71 | }
72 | })
--------------------------------------------------------------------------------
/graphql-shopify/types/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './Category'
3 | export * from './User'
4 | export * from './Product'
5 | export * from './Common'
6 | export * from './Order'
7 | export * from './Checkout'
8 | export * from './Address'
--------------------------------------------------------------------------------
/graphql-shopify/user/mutations/MUTATION_USER_ASSOCIATE_CHECKOUT.ts:
--------------------------------------------------------------------------------
1 | import { CheckoutCustomerAssociateV2Payload } from "../../../generated/shopify.model"
2 |
3 |
4 | const mutationUserAssociateCheckout = /* GraphQL */ `
5 | mutation mutationUserAssociateCheckout($checkoutId: ID!, $customerAccessToken: String!) {
6 | checkoutCustomerAssociateV2(checkoutId: $checkoutId, customerAccessToken: $customerAccessToken) {
7 | checkout {
8 | id
9 | }
10 | checkoutUserErrors {
11 | code
12 | field
13 | message
14 | }
15 | customer {
16 | id
17 | }
18 | }
19 | }
20 | `
21 |
22 | export interface CheckoutCustomerAssociateType {
23 | checkoutCustomerAssociateV2: CheckoutCustomerAssociateV2Payload
24 | }
25 |
26 | export default mutationUserAssociateCheckout
--------------------------------------------------------------------------------
/graphql-shopify/user/mutations/MUTATION_USER_CREATE.ts:
--------------------------------------------------------------------------------
1 | const mutationCreateUser = /* GraphQL */ `
2 | mutation customerCreate($input: CustomerCreateInput!) {
3 | customerCreate(input: $input) {
4 | customerUserErrors {
5 | code
6 | field
7 | message
8 | }
9 | customer {
10 | id
11 | }
12 | }
13 | }
14 | `
15 | export default mutationCreateUser
16 |
--------------------------------------------------------------------------------
/graphql-shopify/user/mutations/MUTATION_USER_CREATE_ACCESSTOKEN.ts:
--------------------------------------------------------------------------------
1 | const mutationUserCreateAccessToken = /* GraphQL */ `
2 | mutation customerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) {
3 | customerAccessTokenCreate(input: $input) {
4 | customerAccessToken {
5 | accessToken
6 | expiresAt
7 | }
8 | customerUserErrors {
9 | code
10 | field
11 | message
12 | }
13 | }
14 | }
15 | `
16 | export default mutationUserCreateAccessToken
17 |
--------------------------------------------------------------------------------
/graphql-shopify/user/mutations/MUTATION_USER_DELETE_ACCESSTOKEN.ts:
--------------------------------------------------------------------------------
1 | const mutationUserDeleteAccessToken = /* GraphQL */ `
2 | mutation customerAccessTokenDelete($customerAccessToken: String!) {
3 | customerAccessTokenDelete(customerAccessToken: $customerAccessToken) {
4 | deletedAccessToken
5 | deletedCustomerAccessTokenId
6 | userErrors {
7 | field
8 | message
9 | }
10 | }
11 | }
12 | `
13 |
14 | export default mutationUserDeleteAccessToken
15 |
--------------------------------------------------------------------------------
/graphql-shopify/user/mutations/MUTATION_USER_DISASSOCIATE_CHECKOUT.ts:
--------------------------------------------------------------------------------
1 | import { CheckoutCustomerDisassociateV2Payload } from "../../../generated/shopify.model"
2 |
3 |
4 | const mutationUserDisssociateCheckout = /* GraphQL */ `
5 | mutation mutationUserDisssociateCheckout($checkoutId: ID!) {
6 | checkoutCustomerDisassociateV2(checkoutId: $checkoutId) {
7 | checkout {
8 | id
9 | }
10 | checkoutUserErrors {
11 | code
12 | field
13 | message
14 | }
15 | }
16 | }
17 | `
18 |
19 | export interface CheckoutCustomerDisassociateType {
20 | checkoutCustomerAssociateV2: CheckoutCustomerDisassociateV2Payload
21 | }
22 |
23 | export default mutationUserDisssociateCheckout
--------------------------------------------------------------------------------
/graphql-shopify/user/queries/QUERY_USER_GET_CUSTOMER.ts:
--------------------------------------------------------------------------------
1 | export const queryUserGetCustomer = /* GraphQL */ `
2 | query getCustomer($customerAccessToken: String!) {
3 | customer(customerAccessToken: $customerAccessToken) {
4 | id
5 | firstName
6 | lastName
7 | displayName
8 | email
9 | phone
10 | tags
11 | acceptsMarketing
12 | createdAt
13 | }
14 | }
15 | `
16 | export default queryUserGetCustomer
17 |
--------------------------------------------------------------------------------
/lib/auth/auth-cookies.ts:
--------------------------------------------------------------------------------
1 | import { serialize, parse } from "cookie";
2 |
3 | const TOKEN_NAME = "lmn_token";
4 |
5 | export const MAX_AGE = 60 * 60 * 24 * 30; // 30 days
6 |
7 | export function setTokenCookie(res, token) {
8 |
9 | const expirationDate = new Date()
10 | expirationDate.setDate(expirationDate.getDate() + 30)
11 |
12 | const cookie = serialize(TOKEN_NAME, token, {
13 | maxAge: MAX_AGE,
14 | expires: expirationDate,
15 | httpOnly: true,
16 | sameSite: "lax",
17 | path: "/",
18 | secure: process.env.NODE_ENV === "production",
19 | });
20 |
21 | res.setHeader("Set-Cookie", cookie);
22 | }
23 |
24 | export function removeTokenCookie(res) {
25 | const cookie = serialize(TOKEN_NAME, "", {
26 | maxAge: -1,
27 | path: "/",
28 | });
29 |
30 | res.setHeader("Set-Cookie", cookie);
31 | }
32 |
33 | export function parseCookies(req) {
34 | // For API Routes we don't need to parse the cookies.
35 | if (req.cookies) return req.cookies;
36 |
37 | // For pages we do need to parse the cookies.
38 | const cookie = req.headers?.cookie;
39 | return parse(cookie || "");
40 | }
41 |
42 | export function getTokenCookie(req) {
43 | const cookies = parseCookies(req);
44 | return cookies[TOKEN_NAME];
45 | }
46 |
--------------------------------------------------------------------------------
/lib/auth/auth-session.ts:
--------------------------------------------------------------------------------
1 | import Iron from '@hapi/iron'
2 | import { getTokenCookie, MAX_AGE, setTokenCookie } from './auth-cookies'
3 |
4 | const COOKIE_CRYPT = process.env.COOKIE_CRYPT
5 |
6 | export async function setUserSession(ctx, session) {
7 |
8 | const savedSession = await getUserSession(ctx.req)
9 | const obj: Session = { ...savedSession, ...session }
10 |
11 | const token = await Iron.seal(obj, COOKIE_CRYPT, Iron.defaults)
12 |
13 | setTokenCookie(ctx.res, token)
14 | }
15 |
16 | export async function getUserSession(req): Promise {
17 | const token = getTokenCookie(req)
18 | if (!token) return
19 |
20 | const session: Session = await Iron.unseal(token, COOKIE_CRYPT, Iron.defaults)
21 |
22 | // if (Date.now() > session.expiresAt) {
23 | // //throw new Error('Sessione scaduta')
24 | // return null
25 | // }
26 |
27 | return session
28 | }
29 |
30 | export type Session = {
31 | accessToken: string
32 | checkoutId: string
33 | checkoutUrl: string
34 | }
--------------------------------------------------------------------------------
/media/frontpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/media/frontpage.png
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 | const withPlugins = require("next-compose-plugins");
3 | const withImages = require("next-images");
4 | const path = require("path");
5 |
6 | require("dotenv").config();
7 |
8 | module.exports = withPlugins([withImages], {
9 | webpack: (config, options) => {
10 | config.resolve.modules.push(path.resolve("./"));
11 | const env = Object.keys(process.env).reduce((acc, curr) => {
12 | acc[`process.env.${curr}`] = JSON.stringify(process.env[curr]);
13 | return acc;
14 | }, {});
15 |
16 | config.plugins.push(new webpack.DefinePlugin(env));
17 |
18 | return config;
19 | },
20 | images: {
21 | domains: ["cdn.shopify.com"],
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/pages/[category].tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Head from "next/head";
3 | import { createShopifyGraphql } from "../graphql-shopify/context";
4 | import Error from "./_error";
5 | import ShopHeader from "../components/Shop/ShopHeader";
6 | import ShopItems from "../components/Shop/ShopItems";
7 | import { GetStaticPaths, InferGetStaticPropsType } from "next";
8 | import { manageCollection } from "../graphql-shopify/category/functions";
9 | import QUERY_GET_COLLECTION, {
10 | QueryGetCollectionProp,
11 | } from "../graphql-shopify/category/queries/QUERY_GET_COLLECTION";
12 | import QUERY_GET_ALL_COLLECTIONS_NAMES, {
13 | QueryAllCollectionNamesType,
14 | } from "../graphql-shopify/category/queries/QUERY_GET_ALL_COLLECTIONS_NAMES";
15 |
16 | const Shop = ({ category }: InferGetStaticPropsType) => {
17 | return (
18 | <>
19 |
20 | {category.title} | La Mosca Nera
21 |
22 |
23 |
24 | >
25 | );
26 | };
27 |
28 | export const getStaticPaths: GetStaticPaths = async () => {
29 | const shopifyGraphql = createShopifyGraphql();
30 | const collectionsData = await shopifyGraphql.request<
31 | QueryAllCollectionNamesType
32 | >(QUERY_GET_ALL_COLLECTIONS_NAMES);
33 |
34 | const paths = collectionsData.collections.edges.map(
35 | ({ node }) => `/${node.handle}`
36 | );
37 |
38 | return { paths, fallback: false };
39 | };
40 |
41 | export async function getStaticProps({ params }) {
42 | const graphqlClient = createShopifyGraphql();
43 | const collectionData = await graphqlClient.request(
44 | QUERY_GET_COLLECTION,
45 | {
46 | slug: params.category,
47 | }
48 | );
49 | const category = await manageCollection(collectionData.collectionByHandle);
50 |
51 | return { props: { category } };
52 | }
53 |
54 | export default Shop;
55 |
--------------------------------------------------------------------------------
/pages/[category]/[slug].tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SingleProduct from "../../components/Shop/SingleProduct";
3 | import { BouncingLoader } from "../../components/utils";
4 | import { NextSeo } from "next-seo";
5 | import Error from "../_error";
6 | import { createShopifyGraphql } from "../../graphql-shopify/context";
7 | import { NexusGenObjects } from "../../generated/nexus-typegen";
8 | import QUERY_GET_PRODUCT, {
9 | QueryGetProduct,
10 | } from "../../graphql-shopify/product/queries/QUERY_GET_PRODUCT";
11 | import { manageProduct } from "../../graphql-shopify/product/functions";
12 | import { InferGetStaticPropsType } from "next";
13 | import QUERY_GET_ALL_PRODUCT_NAMES from "../../graphql-shopify/product/queries/QUERY_GET_ALL_PRODUCT_NAMES";
14 |
15 | const Item = ({ product }: InferGetStaticPropsType) => {
16 | if (!product) return ;
17 |
18 | return (
19 | <>
20 |
37 |
38 | >
39 | );
40 | };
41 |
42 | export const getStaticPaths = async () => {
43 | const shopifyGraphql = createShopifyGraphql();
44 |
45 | const productData = await shopifyGraphql.request(QUERY_GET_ALL_PRODUCT_NAMES);
46 |
47 | const paths = productData.products.edges
48 | .filter(({ node }) => node.collections?.edges?.length > 0)
49 | .map(({ node }) => {
50 | const collectionHandle = node.collections.edges[0].node.handle;
51 | return `/${collectionHandle}/${node.handle}`;
52 | });
53 |
54 | return { paths, fallback: false };
55 | };
56 |
57 | export async function getStaticProps({ params }) {
58 | const graphqlClient = createShopifyGraphql();
59 | const productData = await graphqlClient.request(
60 | QUERY_GET_PRODUCT,
61 | {
62 | slug: params.slug,
63 | }
64 | );
65 | const product: NexusGenObjects["Product"] = await manageProduct(
66 | productData.productByHandle
67 | );
68 |
69 | return { props: { product } };
70 | }
71 |
72 | export default Item;
73 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import type { AppProps } from 'next/app'
3 | import Head from "next/head";
4 | import PageLayout from '../components/Structure/Layouts/PageLayout';
5 | import { styles, DefaultFont } from "../components/utils";
6 | import { createGlobalStyle } from 'styled-components';
7 | import { ApolloProvider } from '@apollo/react-common';
8 | import ApolloClient from "apollo-boost";
9 |
10 | const apollo = new ApolloClient({
11 | uri: process.env.LOCAL_API_GRAPHQL_ENDPOINT,
12 | request: (operation) => {
13 | operation.setContext({
14 | fetchOptions: {
15 | credentials: "include",
16 | },
17 | });
18 | },
19 | });
20 |
21 | export default function MyApp({ Component, pageProps }: AppProps) {
22 | const Layout = (Component as any).Layout || PageLayout;
23 |
24 | return (
25 | <>
26 |
27 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | >
39 | )
40 | }
41 |
42 | const GlobalStyles = createGlobalStyle`
43 | html {
44 | box-sizing: border-box;
45 | }
46 |
47 | *, *:before, *:after{
48 | box-sizing: inherit;
49 | }
50 |
51 | body {
52 | ${DefaultFont};
53 | padding: 0;
54 | margin: 0;
55 | font-size: 1rem;
56 |
57 | ${styles.ScrollbarCss};
58 |
59 | @media (max-width: ${styles.size.mobileL}) {
60 | .swal2-container {
61 | width: 100%;
62 | }
63 |
64 | .swal2-popup {
65 | margin-bottom: 60px;
66 | }
67 | }
68 | }
69 |
70 | @font-face {
71 | font-family: 'LaMoscaNera';
72 | src: url('/fonts/DoodlesOfFun.ttf');
73 | }
74 |
75 | a {
76 | text-decoration: none;
77 | color: ${styles.colors.mainBlack};
78 | }
79 | `;
80 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Document, { Html, Head, Main, NextScript } from "next/document";
3 | import { ServerStyleSheet } from "styled-components";
4 | class MyDocument extends Document {
5 | static async getInitialProps(ctx) {
6 | const sheet = new ServerStyleSheet();
7 | const originalRenderPage = ctx.renderPage;
8 |
9 | try {
10 | ctx.renderPage = () =>
11 | originalRenderPage({
12 | enhanceApp: (App) => (props) =>
13 | sheet.collectStyles(),
14 | });
15 |
16 | const initialProps = await Document.getInitialProps(ctx);
17 | return {
18 | ...initialProps,
19 | styles: (
20 | <>
21 | {initialProps.styles}
22 | {sheet.getStyleElement()}
23 | >
24 | ),
25 | };
26 | } finally {
27 | sheet.seal();
28 | }
29 | }
30 |
31 | render() {
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | );
42 | }
43 | }
44 |
45 | export default MyDocument;
46 |
--------------------------------------------------------------------------------
/pages/_error.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | // import FrontPageCategories from "../components/Home/FrontPageCategories";
3 | import FeaturedProducts from "../components/Home/FeaturedProducts";
4 |
5 | function Error({ statusCode }) {
6 | let content = "";
7 |
8 | if (statusCode === 500) {
9 | content = (
10 | C'è stato un errore imprevisto! Ci dispiace per il disagio!
11 | );
12 | } else {
13 | content = Purtroppo la pagina non è stata trovata!
;
14 | }
15 | return (
16 | <>
17 | {content}
18 | Cosa cercavi?
19 | {/* */}
20 |
21 | >
22 | );
23 | }
24 |
25 | Error.getInitialProps = ({ res, err }) => {
26 | const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
27 | return { statusCode };
28 | };
29 |
30 | export default Error;
31 |
--------------------------------------------------------------------------------
/pages/about/contacts.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Head from "next/head";
3 | import { PageTitle, PageSubtitle } from "../../components/utils";
4 | import ContactsButtons from "../../components/About/Contacts/ContactButtons";
5 | import ContactMap from "../../components/About/Contacts/ContactMap";
6 |
7 | const Contacts = () => {
8 | return (
9 | <>
10 |
11 | Contacts | @escapemanuele shop
12 |
13 | Contact me
14 | {/*
15 | Sentiti libero di contattarmi con il mezzo che più preferisci, senza
16 | assolutamente nessun impegno!
17 | */}
18 |
19 | {/* */}
20 | >
21 | );
22 | };
23 |
24 | export async function getStaticProps(context) {
25 | return {
26 | props: {}, // will be passed to the page component as props
27 | };
28 | }
29 |
30 | export default Contacts;
31 |
--------------------------------------------------------------------------------
/pages/about/faq.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Head from "next/head";
3 | import { PageTitle } from "../../components/utils";
4 | import Sections from "../../components/Structure/Sections";
5 | import FAQSECTIONS, { QUESTIONS } from "../../components/About/FAQ/faqSections";
6 | import FaqQuestions from "../../components/About/FAQ/FaqQuestions";
7 |
8 | const Faq = () => {
9 | const [faqSection, setFaqSection] = useState("Spedition");
10 | const [question, setQuestion] = useState(null);
11 |
12 | useEffect(() => {
13 | const quest = QUESTIONS.find((x) => x.type === faqSection);
14 | setQuestion(quest);
15 | }, [faqSection]);
16 |
17 | return (
18 | <>
19 |
20 | FAQ | @escapemanuele store
21 |
22 | FAQ
23 |
28 |
29 | {question && }
30 | >
31 | );
32 | };
33 |
34 | export async function getStaticProps(context) {
35 | return {
36 | props: {}, // will be passed to the page component as props
37 | };
38 | }
39 |
40 | export default Faq;
41 |
--------------------------------------------------------------------------------
/pages/about/howitworks.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Head from "next/head";
3 | import HowItWorksOnline from "../../components/About/HowItWorks/HowItWorksOnline";
4 | import HowItWorksOffline from "../../components/About/HowItWorks/HowItWorksOffline";
5 | import styled from "styled-components";
6 |
7 | const HowItWorks = () => {
8 | return (
9 | <>
10 |
11 | How it works | @escapemanuele Store
12 |
13 | Explanation...
14 |
15 |
16 | {/* */}
17 | >
18 | );
19 | };
20 |
21 | export async function getStaticProps(context) {
22 | return {
23 | props: {}, // will be passed to the page component as props
24 | };
25 | }
26 |
27 | const IntroductionWrapper = styled.div`
28 | font-size: 1.3rem;
29 | `;
30 |
31 | export default HowItWorks;
32 |
--------------------------------------------------------------------------------
/pages/account.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Head from "next/head";
3 |
4 | import Sections from "../components/Structure/Sections";
5 | import ACCOUNTSECTIONS from "../components/Account/AccountSections";
6 | import { BouncingLoader, PageTitle } from "../components/utils";
7 | import useCustomer from "../frontend-structure/user/hooks/useCustomer";
8 |
9 | const AccountPage = () => {
10 | const user = useCustomer({ redirectTo: "/login" });
11 |
12 | const [accountSection, setAccountSection] = useState("orders");
13 | const [AccountComponent, setAccountComponent] = useState(null);
14 |
15 | useEffect(() => {
16 | const section = ACCOUNTSECTIONS.find((x) => x.link === accountSection);
17 | setAccountComponent(section.component);
18 | }, [accountSection]);
19 |
20 | return (
21 | <>
22 |
23 | Account | @escapemanuele shop
24 |
25 | My account
26 | {user && (
27 | <>
28 |
33 |
34 | {AccountComponent != null ? AccountComponent : }
35 | >
36 | )}
37 | >
38 | );
39 | };
40 |
41 | export default AccountPage;
42 |
--------------------------------------------------------------------------------
/pages/api/graphql.ts:
--------------------------------------------------------------------------------
1 | import { ApolloServer } from "apollo-server-micro"
2 | import { schema } from '../../graphql-shopify/schema';
3 | import { createContext } from '../../graphql-shopify/context';
4 |
5 | const server = new ApolloServer({
6 | context: createContext,
7 | schema,
8 | tracing: process.env.NODE_ENV === 'development'
9 | })
10 |
11 | export const config = {
12 | api: {
13 | bodyParser: false
14 | }
15 | }
16 |
17 | export default server.createHandler({ path: "/api/graphql" });
--------------------------------------------------------------------------------
/pages/cart.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Head from "next/head";
4 |
5 | import CartPageItems from "../components/Cart/CartPageItems";
6 | import { styles, calcTotalItems, BouncingLoader } from "../components/utils";
7 | import CartPayment from "../components/Cart/CartPayment";
8 | import { PageTitle } from "../components/utils";
9 | import useCart from "../frontend-structure/checkout/hooks/useCart";
10 |
11 | const CartPage: React.FC = () => {
12 | const [data, loading] = useCart();
13 |
14 | return (
15 | <>
16 |
17 | Cart | @escapemanuele shop
18 |
19 | {(() => {
20 | if (!data) {
21 | return ;
22 | }
23 |
24 | if (
25 | !data.cart ||
26 | !data.cart.cartItems ||
27 | data.cart.cartItems.length === 0
28 | ) {
29 | return ;
30 | }
31 |
32 | return (
33 | <>
34 |
35 | Cart (
36 | {data?.cart?.cartItems
37 | ? calcTotalItems(data?.cart.cartItems)
38 | : "0 items"}
39 | )
40 |
41 |
42 | {data.cart.cartItems && (
43 | <>
44 |
45 |
46 | >
47 | )}
48 |
49 | >
50 | );
51 | })()}
52 | >
53 | );
54 | };
55 |
56 | const EmptyCart = () => Il tuo carrello è vuoto!;
57 |
58 | const EmptyWrapper = styled.div`
59 | border: 1px solid ${styles.colors.lightGrey};
60 | ${styles.boxColors.primaryBoxShadow};
61 | font-weight: 700;
62 | padding: 2rem;
63 | text-align: center;
64 |
65 | @media (min-width: ${styles.size.tablet}) {
66 | margin-top: 3rem;
67 | }
68 | `;
69 |
70 | const CartPageWrapper = styled.div`
71 | display: grid;
72 | grid-template-columns: 1fr;
73 |
74 | @media (min-width: ${styles.size.laptop}) {
75 | grid-template-columns: 3fr 1fr;
76 | }
77 | `;
78 |
79 | export default CartPage;
80 |
--------------------------------------------------------------------------------
/pages/checkout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Head from "next/head";
3 | import CheckoutLayout from "../components/Structure/Layouts/CheckoutLayout";
4 | import { Checkout } from "../components/Checkout";
5 |
6 | const CheckoutPage = () => (
7 | <>
8 |
9 | Pagamento | La Mosca Nera
10 |
11 |
12 | >
13 | );
14 |
15 | CheckoutPage.layout = CheckoutLayout;
16 |
17 | export default CheckoutPage;
18 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Parallax,
4 | ContainerWrapper,
5 | MainRaisedWrapper,
6 | } from "../components/utils";
7 | import FeaturedProducts from "../components/Home/FeaturedProducts";
8 | import FrontPageCategories from "../components/Home/FrontPageCategories";
9 | import NoContainerLayout from "../components/Structure/Layouts/NoContainerLayout";
10 | import { manageProducts } from "../graphql-shopify/product/functions";
11 | import { createShopifyGraphql } from "../graphql-shopify/context";
12 | import QUERY_GET_PRODUCTS, {
13 | GetAllProductsQueryType,
14 | } from "../graphql-shopify/product/queries/QUERY_GET_PRODUCTS";
15 | import { InferGetStaticPropsType } from "next";
16 | import QUERY_GET_COLLECTIONS, {
17 | QueryGetCollectionsType,
18 | } from "../graphql-shopify/category/queries/QUERY_GET_COLLECTIONS";
19 | import { categoriesFromPrisma } from "../graphql-shopify/category/functions";
20 | import { Collection } from "../generated/shopify.model";
21 | import { NexusGenObjects } from "../generated/nexus-typegen";
22 | import { manageCollectionConnection } from "../graphql-shopify/category/functions/manage-collection";
23 |
24 | const Home = ({
25 | products,
26 | categories,
27 | }: InferGetStaticPropsType) => {
28 | return (
29 | <>
30 |
31 |
32 |
33 |
34 |
35 | {categories && categories.length && (
36 |
37 | )}
38 | {products && }
39 |
40 |
41 | >
42 | );
43 | };
44 |
45 | export async function getStaticProps() {
46 | const graphqlClient = createShopifyGraphql();
47 |
48 | let featuredProducts: Array = [];
49 | const productsData = await graphqlClient.request(
50 | QUERY_GET_PRODUCTS,
51 | {
52 | query: "tag:featured",
53 | }
54 | );
55 | featuredProducts = await manageProducts(productsData.products);
56 |
57 | const collectionsData = await graphqlClient.request(
58 | QUERY_GET_COLLECTIONS,
59 | { first: 250 }
60 | );
61 |
62 | let frontCategories: Array = await manageCollectionConnection(
63 | collectionsData.collections
64 | );
65 |
66 | return {
67 | props: {
68 | products: featuredProducts,
69 | categories: frontCategories,
70 | },
71 | };
72 | }
73 |
74 | Home.Layout = NoContainerLayout;
75 |
76 | export default Home;
77 |
--------------------------------------------------------------------------------
/pages/thank-you.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Head from "next/head";
3 | import { PageTitle } from "../components/utils";
4 |
5 | const ThankYouPage = () => {
6 | return (
7 | <>
8 |
9 | Grazie! | La Mosca Nera
10 |
11 | Grazie!
12 | >
13 | );
14 | };
15 |
16 | export default ThankYouPage;
17 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | // datasource db {
5 | // provider = "postgresql"
6 | // url = env("DATABASE_URL")
7 | // }
8 |
9 | // generator client {
10 | // provider = "prisma-client-js"
11 | // }
12 |
13 |
14 | // model User {
15 | // id String @default(cuid()) @id
16 | // }
17 |
18 | // model Category {
19 | // id String @default(cuid()) @id
20 | // front Boolean @default(false)
21 | // // parentCategory Category @relation(name: "CategoryParent")
22 | // // subCategories: [Category!]! @relation(name: "CategoryChildren")
23 | // // items Item[] @relation(references: [id])
24 | // }
25 |
26 | // model Product {
27 | // id String @default(cuid()) @id
28 | // title String
29 | // image String
30 | // }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/favicon.png
--------------------------------------------------------------------------------
/public/fonts/DoodlesOfFun.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/fonts/DoodlesOfFun.ttf
--------------------------------------------------------------------------------
/public/images/car.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/car.png
--------------------------------------------------------------------------------
/public/images/consegna-gratuita.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/consegna-gratuita.png
--------------------------------------------------------------------------------
/public/images/fantasy-land.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/fantasy-land.jpg
--------------------------------------------------------------------------------
/public/images/lmn-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/lmn-icon.png
--------------------------------------------------------------------------------
/public/images/macchina-per-cucito.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/macchina-per-cucito.png
--------------------------------------------------------------------------------
/public/images/mastercard-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/mastercard-logo.png
--------------------------------------------------------------------------------
/public/images/package.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/package.png
--------------------------------------------------------------------------------
/public/images/paypal-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/paypal-logo.png
--------------------------------------------------------------------------------
/public/images/profile-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/profile-bg.jpg
--------------------------------------------------------------------------------
/public/images/sarta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/sarta.png
--------------------------------------------------------------------------------
/public/images/sartoria.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/sartoria.png
--------------------------------------------------------------------------------
/public/images/shop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/shop.png
--------------------------------------------------------------------------------
/public/images/visa-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SkyCaptainess/Shopify-nextjs/568b3ac0e31ef43907dcedc1f52889f8b62bd46b/public/images/visa-logo.png
--------------------------------------------------------------------------------
/public/nprogress.css:
--------------------------------------------------------------------------------
1 | #nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translate(0px,-4px);-ms-transform:rotate(3deg) translate(0px,-4px);transform:rotate(3deg) translate(0px,-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:solid 2px transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner 400ms linear infinite;animation:nprogress-spinner 400ms linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .spinner,.nprogress-custom-parent #nprogress .bar{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
--------------------------------------------------------------------------------
/shopify-codegen.yml:
--------------------------------------------------------------------------------
1 | {
2 | "overwrite": true,
3 | "generates": {
4 | "generated/shopify.model.ts": {
5 | "schema": [
6 | {
7 | ${SHOPIFY_GRAPHQL_ENDPOINT}: {
8 | "headers": {
9 | "X-Shopify-Storefront-Access-Token": ${STOREFRONT_ACCESS_TOKEN}
10 | }
11 | }
12 | }
13 | ],
14 | "plugins": ["typescript", "typescript-operations"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext", "ES6"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": false,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "CommonJS",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "rootDir": ".",
17 | "typeRoots": ["node_modules/@types", "types"],
18 | "plugins": [{ "name": "nexus/typescript-language-service" }]
19 | },
20 | "include": [
21 | "next-env.d.ts",
22 | "**/*.ts",
23 | "**/*.tsx",
24 | "types.d.ts"
25 | ],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------