├── .editorconfig ├── .env.local-exemple ├── .eslintignore ├── .eslintrc.js ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── logo.png ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README-pt.md ├── README.md ├── apollo ├── client │ ├── cache.js │ ├── index.js │ ├── mutations.js │ └── queries.js ├── resolvers.js ├── schema.js └── typeDefs.js ├── components ├── alerts │ ├── error.js │ ├── success.js │ └── warnig.js ├── asideCategories.js ├── categoriesItem.js ├── emptySection.js ├── finishOrderCart.js ├── footer.js ├── form │ ├── InputContainer.js │ ├── button.js │ ├── formContainer.js │ └── input.js ├── header │ ├── header-desktop.js │ ├── header-mobile.js │ ├── index.js │ ├── open-drawer-button.js │ └── side-drawer.js ├── headerBarProducts.js ├── loading-page.js ├── logo.js ├── page-container.js ├── page.js ├── productItem.js ├── productSection.js ├── products.js ├── productsGrid.js ├── promoCard.js ├── search-box.js └── title.js ├── db ├── connection.js ├── db.sqlite ├── migrations │ ├── 01_product.js │ ├── 02_category.js │ ├── 03_product_category.js │ └── 20200713085223_user.js ├── offlineData │ ├── categories.js │ └── products.js └── seeds │ ├── 00_users.js │ ├── 01_categories.js │ ├── 02_products.js │ └── 03_products_categories.js ├── knexfile.js ├── lib ├── auth-cookies.js ├── auth.js ├── category.js ├── form.js ├── product.js └── user.js ├── next.config.js ├── package.json ├── pages ├── _app.js ├── api │ └── graphql.js ├── cart.js ├── category │ └── [category].js ├── checkout.js ├── index.js ├── product │ └── [id].js ├── user │ ├── login.js │ ├── resetpassword.js │ ├── signout.js │ └── signup.js └── wishlist.js ├── public ├── favicon.ico ├── products │ ├── 61JnrafZ7zL._AC_SL1457_.jpg │ ├── 61UgXsi+mcL._AC_SL1500_.jpg │ ├── 61xQRmY+RRL._AC_SL1500_.jpg │ ├── 71S-XwHaGzL._AC_SL1500_.jpg │ ├── 71VqtdDUzsL._AC_SL1500_.jpg │ ├── 71nmrSRQ3cL._AC_SL1500_.jpg │ ├── 71xe2bDZ0nL._AC_UX679_.jpg │ ├── 81QpkIctqPL._AC_SL1500_.jpg │ ├── 81UgYuadkpL._AC_SL1500_.jpg │ ├── 81V7L6auixL._SL1500_.jpg │ ├── 81fstJkUlaL._AC_SL1500_.jpg │ └── 81hCytKTUTL.jpg ├── reset.css └── vercel.svg ├── utils └── toggleProductStates.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = 0 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.env.local-exemple: -------------------------------------------------------------------------------- 1 | TOKEN_SECRET='this-is-a-secret-value-with-at-least-32-characters' 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .next 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | }, 6 | extends: 'plugin:react/recommended', 7 | globals: { 8 | Atomics: 'readonly', 9 | SharedArrayBuffer: 'readonly', 10 | React: 'writable', 11 | }, 12 | parserOptions: { 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | ecmaVersion: 11, 17 | sourceType: 'module', 18 | }, 19 | plugins: ['react'], 20 | rules: { 21 | 'react/react-in-jsx-scope': 'off', 22 | 'react/prop-types': 'off', 23 | quotes: ['error', 'single'], 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [RafaelGoulartB] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RafaelGoulartB/next-ecommerce/0e749c8fcd6dfbf14b08e57c541d8299dcdd2005/.github/logo.png -------------------------------------------------------------------------------- /.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 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # /db/db.sqlite 33 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at rafagoulartb@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thanks you so much for your interest in contributing to this project! 4 | 5 | ## About our deal 6 | 7 | Hi! I'm Rafael Goulart and I'm the creator and maintainer of this project. 8 | 9 | If you encounter bugs, please **do** open an issue describing the bug and including steps to easily reproduce it. 10 | 11 | If you have an idea for an enhancement, go ahead and share it via an issue, but please don't expect a timely response. 12 | 13 | This project is MIT-licensed, and this means that you can implement and use whatever enhancements you'd like. 14 | 15 | ## Commits and Code Padronization 16 | 17 | This project follow the [Conventinal Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification, and the following [Eslint Lint Rules](https://github.com/RafaelGoulartB/Next.js-Ecommerce/blob/master/.eslintrc.js). 18 | 19 | ## Bug reports 20 | 21 | If you encounter a problem with this project, please open an issue. Be sure to include: 22 | 23 | - Package version 24 | - Node and Express versions 25 | - Brief but thorough description of the issue 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 RafaelGoulart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-pt.md: -------------------------------------------------------------------------------- 1 |

ECOMMERCE FEITO COM NEXT.JS

2 | 3 |

4 | 5 | Rafael Goulart 6 | 7 | 8 | Documentation 9 | 10 | 11 | Maintenance 12 | 13 | 14 | License: MIT 15 | 16 | GitHub Pull Requests 17 | GitHub Contributors 18 | 19 |

20 | 21 | > Esse projeto foi feito para mostrar uma experiencia completa de um ecommerce feito utilizando Next.js e Next.js Serveless functions para construir o backend, utilizando Apollo Server e Apollo Client para o GraphQL. 22 | 23 |

24 | Inglês 25 | · 26 | Português 27 |

28 | 29 |
30 | The ecommerce project. Built with ❤︎ by 31 | Rafael Goulart and 32 | 33 | contributors 34 | 35 | 36 |
37 | 38 |
39 |
40 | 41 |
42 | 43 | --- 44 | 45 | # :pushpin: Tabela de Conteúdo 46 | 47 | * [Site de Demostração](#eyes-site-de-demostração) 48 | * [Tecnologias](#computer-tecnologias) 49 | * [Funcionalidades](#rocket-funcionalidades) 50 | * [Como rodar](#construction_worker-como-rodar) 51 | * [Encontrou um bug? Ou está faltando uma feature?](#bug-problemas) 52 | * [Contribuindo](#tada-contribuindo) 53 | * [Licencia](#closed_book-licencia) 54 | 55 | ## 📥 Layout disponivel para download em: 56 |

57 | 58 | Direct Download 59 | 60 |

61 | 62 | # :eyes: Site de Demostração 63 | No site de demostração pode estar faltando algumas funcionalidades, clene e rode o projeto para uma experiencia completa. 64 | 👉 demo: https://quantum-ecommerce.now.sh/ 65 | 66 | # :computer: Tecnologias 67 | Esse projeto foi feito utilizando as seguintes tecnologias: 68 | 69 | * [Next.js](https://nextjs.org/) - Para o SSR e controle de rotas 70 | * [GraphQL](https://graphql.org/) - Para linguagem de query 71 | * [Apollo](https://www.apollographql.com/) - Para o cliente/servidor graphql 72 | * [Knex](https://knexjs.org/) - ORM 73 | * [Vercel](https://vercel.com/) - Para hostear o site 74 | 75 | # :rocket: Funcionalidades 76 | 77 | - Autenticação com Cookies Sessions. 78 | - Rest password com email 79 | - Listagem de produtos 80 | - Filtrar produtos por categoria 81 | - Ordenar listagem de produtos 82 | - Pesquisa de Produtos 83 | - Adicionar produtos a lista de desejos 84 | - Adicionar produtos ao carinho 85 | - Checkout page 86 | - Review de Produtos 87 | 88 | # :construction_worker: Como rodar 89 | ### Renomear arquivo de variaveis de ambiente 90 | Renomear `.env.local-exemple` para `.env.local` 91 | ### Instalar Dependencias 92 | ```bash 93 | yarn install 94 | ``` 95 | ### Configurar banco de dados 96 | ```bash 97 | # Criar DB usando migrations 98 | yarn knex:migrate 99 | 100 | # Rodar seeds para popular o banco de dados 101 | yarn knex:seed 102 | ``` 103 | ### Rodar Aplicação 104 | ```bash 105 | yarn dev 106 | ``` 107 | Acesse [http://localhost:3000](http://localhost:3000) para ver o resultado. 108 |
109 |
110 | Acesse [http://localhost:3000/api/graphql](http://localhost:3000/api/graphql) para ver a documentação da API. 111 | 112 | 113 | # :bug: Problemas 114 | 115 | Fique a vontade **para criar uma nova issue** com o respectivo titulo e descrição na página de issues do [Next.js Ecommerce](https://github.com/RafaelGoulartB/Next.js-Ecommerce/issues) Repositorio. Se você já encontrou a solução para o problema, **Eu amaria fazer o review do seu pull request**! 116 | 117 | # :tada: Contribuindo 118 | 119 | Confira a página de [contribuição](./CONTRIBUTING.md) para ver como começar uma discução e começar a contribuir. 120 | 121 | # :closed_book: Licencia 122 | 123 | Lançado em 2020 :closed_book: Licencia 124 | 125 | Made with love by [Rafael Goulart](https://github.com/RafaelGoulartB) 🚀. 126 | Esse projeto esta sobre [MIT license](./LICENSE). 127 | 128 | 129 | Dê uma ⭐️ se esse projeto te ajudou! 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Ecommerce made with Next.js

2 | 3 |

4 | 5 | Rafael Goulart 6 | 7 | 8 | Documentation 9 | 10 | 11 | Maintenance 12 | 13 | 14 | License: MIT 15 | 16 | GitHub Pull Requests 17 | GitHub Contributors 18 | 19 |

20 | 21 | > This project was made to show a full ecommerce plataform made with Next.js and Nextjs Serverless functions to build the backend, using Apollo Server and Apollo Client to GraphQL. 22 | 23 |

24 | English 25 | · 26 | Portuguese 27 |

28 | 29 |
30 | The ecommerce project. Built with ❤︎ by 31 | Rafael Goulart and 32 | 33 | contributors 34 | 35 | 36 |
37 | 38 |
39 |
40 | 41 |
42 | 43 | --- 44 | 45 | # :pushpin: Table of Contents 46 | 47 | * [Demo Website](#eyes-demo-website) 48 | * [Technologies](#computer-technologies) 49 | * [Features](#rocket-features) 50 | * [How to run](#construction_worker-how-to-run) 51 | * [Found a bug? Missing a specific feature?](#bug-issues) 52 | * [Contributing](#tada-contributing) 53 | * [License](#closed_book-license) 54 | 55 | ## 📥 Layout available at: 56 |

57 | 58 | Direct Download 59 | 60 |

61 | 62 | # :eyes: Demo Website 63 | The demo website can be missing some features, clone and run the project to a full experience.
64 | 👉 demo: https://quantum-ecommerce.now.sh/ 65 | 66 | # :computer: Technologies 67 | This project was made using the follow technologies: 68 | 69 | * [Next.js](https://nextjs.org/) - To SSR and routes control 70 | * [GraphQL](https://graphql.org/) - To query language 71 | * [Apollo](https://www.apollographql.com/) - To graphql server and client 72 | * [Knex](https://knexjs.org/) - ORM 73 | * [Vercel](https://vercel.com/) - To deploy website 74 | 75 | # :rocket: Features 76 | 77 | - Authentication with Cookies Sessions. 78 | - Reset Password using email 79 | - List Products 80 | - Filter products by Category 81 | - Sort list of products 82 | - Live search 83 | - Add products to Wishlist 84 | - Add products to Cart 85 | - Checkout page 86 | - Payment with Paypal 87 | - Review Products 88 | 89 | # :construction_worker: How to run 90 | **You need to install [Node.js](https://nodejs.org/en/download/) and [Yarn](https://yarnpkg.com/) first, then:** 91 | 92 | ### Rename env file 93 | Rename `.env.local-exemple` to `.env.local` 94 | ### Install Dependencies 95 | ```bash 96 | yarn install 97 | ``` 98 | ### Set up database 99 | ```bash 100 | # Create DB using migrations 101 | yarn knex:migrate 102 | 103 | # Run seeds to populate database 104 | yarn knex:seed 105 | ``` 106 | ### Run Aplication 107 | ```bash 108 | yarn dev 109 | ``` 110 | 111 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 112 |
113 |
114 | Open [http://localhost:3000/api/graphql](http://localhost:3000/api/graphql) with your browser to run queries or to see docs of API. 115 | 116 | 117 | # :bug: Issues 118 | 119 | Feel free to **file a new issue** with a respective title and description on the the [Next.Js Ecommerce](https://github.com/RafaelGoulartB/next-ecommerce/issues) repository. If you already found a solution to your problem, **i would love to review your pull request**! 120 | 121 | 122 | --- 123 | 124 | 125 | [⬆ Back to Top](#pushpin-table-of-contents) 126 | 127 | 128 | 129 | # :tada: Contributing 130 | First of all, thank you for being interested in helping out, your time is always appreciated in every way. :100: 131 | 132 | Here's some tips: 133 | 134 | * Check the [issues page](https://github.com/RafaelGoulartB/next-ecommerce/issues) for already opened issues (or maybe even closed ones) that might already address your question/bug/feature request. 135 | * Feature requests are welcomed! Provide some details on why it would be helpful for you and others, explain how you're using bull-board and if possible even some screenshots if you are willing to mock something! 136 | 137 | Check out the [contributing](./CONTRIBUTING.md) page to see the best places to file issues, start discussions and begin contributing. 138 | 139 | # :closed_book: License 140 | 141 | Released in 2020. 142 | This project is under the [MIT license](./LICENSE). 143 | 144 | Made with love by [RafaelGoulartB](https://github.com/RafaelGoulartB) 🚀 145 | -------------------------------------------------------------------------------- /apollo/client/cache.js: -------------------------------------------------------------------------------- 1 | import { InMemoryCache } from '@apollo/client'; 2 | 3 | export const cache = new InMemoryCache({ 4 | typePolicies: { 5 | Query: { 6 | fields: { 7 | isDrawerOpen: { 8 | read() { 9 | return isDrawerOpenVar(); 10 | }, 11 | }, 12 | sortProductSection: { 13 | read() { 14 | return sortProductSectionVar(); 15 | }, 16 | }, 17 | cart: { 18 | read() { 19 | return { 20 | products: cartProductsVar(), 21 | cartCount: cartProductsVar().length, 22 | }; 23 | }, 24 | }, 25 | wishlist: { 26 | read() { 27 | return { 28 | products: wishlistProductsVar(), 29 | wishlistCount: wishlistProductsVar().length, 30 | }; 31 | }, 32 | }, 33 | }, 34 | }, 35 | }, 36 | }); 37 | 38 | export const isDrawerOpenVar = cache.makeVar(false); 39 | 40 | export const sortProductSectionVar = cache.makeVar(['rating', 'DESC']); 41 | 42 | export const cartProductsVar = cache.makeVar([]); 43 | 44 | export const wishlistProductsVar = cache.makeVar([]); 45 | -------------------------------------------------------------------------------- /apollo/client/index.js: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { ApolloClient } from '@apollo/client'; 3 | 4 | import { cache } from './cache'; 5 | 6 | let apolloClient; 7 | 8 | function createIsomorphLink() { 9 | if (typeof window === 'undefined') { 10 | const { SchemaLink } = require('@apollo/client/link/schema'); 11 | const { schema } = require('../schema'); 12 | return new SchemaLink({ schema }); 13 | } else { 14 | const { HttpLink } = require('@apollo/client/link/http'); 15 | return new HttpLink({ 16 | uri: '/api/graphql', 17 | credentials: 'same-origin', 18 | }); 19 | } 20 | } 21 | 22 | function createApolloClient() { 23 | return new ApolloClient({ 24 | ssrMode: typeof window === 'undefined', 25 | link: createIsomorphLink(), 26 | cache: cache, 27 | }); 28 | } 29 | 30 | export function initializeApollo(initialState = null) { 31 | const _apolloClient = apolloClient ?? createApolloClient(); 32 | 33 | // If your page has Next.js data fetching methods that use Apollo Client, the initial state 34 | // get hydrated here 35 | if (initialState) { 36 | _apolloClient.cache.restore(initialState); 37 | } 38 | // For SSG and SSR always create a new Apollo Client 39 | if (typeof window === 'undefined') return _apolloClient; 40 | // Create the Apollo Client once in the client 41 | if (!apolloClient) apolloClient = _apolloClient; 42 | 43 | return _apolloClient; 44 | } 45 | 46 | export function useApollo(initialState) { 47 | const store = useMemo(() => initializeApollo(initialState), [initialState]); 48 | return store; 49 | } 50 | -------------------------------------------------------------------------------- /apollo/client/mutations.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const SIGN_IN = gql` 4 | mutation SignInMutation($email: String!, $password: String!) { 5 | signIn(input: { email: $email, password: $password }) { 6 | user { 7 | id 8 | name 9 | email 10 | } 11 | } 12 | } 13 | `; 14 | 15 | export const SIGN_UP = gql` 16 | mutation SignUpMutation($name: String!, $email: String!, $password: String!) { 17 | signUp(input: { name: $name, email: $email, password: $password }) { 18 | user { 19 | id 20 | name 21 | email 22 | } 23 | } 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /apollo/client/queries.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const GET_DRAWER_STATE = gql` 4 | query isDrawerOpen { 5 | isDrawerOpen @client 6 | } 7 | `; 8 | 9 | export const SORT_PRODUCT_SECTION = gql` 10 | query sortProductSection { 11 | sortProductSection @client 12 | } 13 | `; 14 | 15 | export const CART = gql` 16 | query cart { 17 | cart @client { 18 | products 19 | cartCount 20 | } 21 | } 22 | `; 23 | 24 | export const WISHLIST = gql` 25 | query wishlist { 26 | wishlist @client { 27 | products 28 | wishlistCount 29 | } 30 | } 31 | `; 32 | 33 | export const CART_COUNT = gql` 34 | query cart { 35 | cart @client { 36 | cartCount 37 | } 38 | } 39 | `; 40 | 41 | export const WISHLIST_COUNT = gql` 42 | query wishlist { 43 | wishlist @client { 44 | wishlistCount 45 | } 46 | } 47 | `; 48 | 49 | export const VIEWER = gql` 50 | query ViewerQuery { 51 | viewer { 52 | id 53 | name 54 | email 55 | } 56 | } 57 | `; 58 | 59 | export const PRODUCTS = gql` 60 | query ProductsQuery($field: String!, $order: String!, $category: String) { 61 | products(sort: { field: $field, order: $order }, category: $category) { 62 | id 63 | name 64 | description 65 | img_url 66 | price 67 | rating 68 | } 69 | } 70 | `; 71 | 72 | export const PRODUCTS_BY_IDS = gql` 73 | query productsByIds($id: [ID]!) { 74 | productsById(id: $id) { 75 | id 76 | name 77 | description 78 | img_url 79 | price 80 | rating 81 | } 82 | } 83 | `; 84 | 85 | export const PRODUCTS_BY_IDS_PRICE = gql` 86 | query productsByIds($id: [ID]!) { 87 | productsById(id: $id) { 88 | price 89 | } 90 | } 91 | `; 92 | 93 | export const CATEGORIES = gql` 94 | query CategoriesQuery { 95 | categories { 96 | id 97 | name 98 | label 99 | md_icon 100 | } 101 | } 102 | `; 103 | -------------------------------------------------------------------------------- /apollo/resolvers.js: -------------------------------------------------------------------------------- 1 | import { AuthenticationError, UserInputError } from 'apollo-server-micro'; 2 | import { createUser, findUser, validatePassword } from '../lib/user'; 3 | import { listCategories } from '../lib/category'; 4 | import { 5 | listProducts, 6 | findProduct, 7 | CreateProduct, 8 | DeleteProduct, 9 | UpdateProduct, 10 | findProductsById, 11 | } from '../lib/product'; 12 | import { setLoginSession, getLoginSession } from '../lib/auth'; 13 | import { removeTokenCookie } from '../lib/auth-cookies'; 14 | 15 | export const resolvers = { 16 | Query: { 17 | async viewer(_parent, _args, context, _info) { 18 | try { 19 | const session = await getLoginSession(context.req); 20 | 21 | if (session) { 22 | return findUser({ email: session.email }); 23 | } 24 | } catch (error) { 25 | throw new AuthenticationError( 26 | 'Authentication token is invalid, please log in' 27 | ); 28 | } 29 | }, 30 | async products(_parent, args, _context, _info) { 31 | try { 32 | // Sort + Category 33 | if (args.sort && args.category) 34 | return listProducts({ sort: args.sort, category: args.category }); 35 | // Sort 36 | else if (args.sort) return listProducts({ sort: args.sort }); 37 | // Category 38 | else if (args.category) 39 | return listProducts({ category: args.category }); 40 | // Default 41 | return listProducts({ sort: false, category: false }); 42 | } catch (error) { 43 | throw new Error('It is not possible list products'); 44 | } 45 | }, 46 | async productsById(_parent, args, _context, _info) { 47 | try { 48 | return await findProductsById({ id: args.id }); 49 | } catch (error) { 50 | throw new Error('It is not possible list products'); 51 | } 52 | }, 53 | async product(_parent, args, _context, _info) { 54 | try { 55 | return findProduct({ id: args.id }); 56 | } catch (error) { 57 | throw new Error('It is not possible list product'); 58 | } 59 | }, 60 | async categories(_parent, _args, _context, _info) { 61 | try { 62 | return listCategories(); 63 | } catch (error) { 64 | throw new Error('It is not possible list categories'); 65 | } 66 | }, 67 | }, 68 | Mutation: { 69 | async signUp(_parent, args, _context, _info) { 70 | const userExist = await findUser({ email: args.input.email }); 71 | 72 | if (userExist) 73 | throw new UserInputError('email is already in use, try to login'); 74 | 75 | const user = await createUser(args.input); 76 | return { user }; 77 | }, 78 | async signIn(_parent, args, context, _info) { 79 | const user = await findUser({ email: args.input.email }); 80 | 81 | if (user && (await validatePassword(user, args.input.password))) { 82 | const session = { 83 | id: user.id, 84 | email: user.email, 85 | }; 86 | 87 | await setLoginSession(context.res, session); 88 | 89 | return { user }; 90 | } 91 | 92 | throw new UserInputError('Invalid email and password combination'); 93 | }, 94 | async signOut(_parent, _args, context, _info) { 95 | removeTokenCookie(context.res); 96 | return true; 97 | }, 98 | async createProduct(_parent, args, _context, _info) { 99 | try { 100 | const product = await CreateProduct(args.input); 101 | 102 | return { product }; 103 | } catch (error) { 104 | throw new Error('It is not possible create a new product'); 105 | } 106 | }, 107 | async deleteProduct(_parent, args, _context, _info) { 108 | try { 109 | await DeleteProduct({ id: args.id }); 110 | return true; 111 | } catch (error) { 112 | throw new Error('It is not possible delete the product'); 113 | } 114 | }, 115 | async updateProduct(_parent, args, _context, _info) { 116 | try { 117 | const product = await UpdateProduct(args.id, args.input); 118 | return { product }; 119 | } catch (error) { 120 | throw new Error('it is not possible update the product'); 121 | } 122 | }, 123 | }, 124 | }; 125 | -------------------------------------------------------------------------------- /apollo/schema.js: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from 'graphql-tools'; 2 | import { typeDefs } from './typeDefs'; 3 | import { resolvers } from './resolvers'; 4 | 5 | export const schema = makeExecutableSchema({ 6 | typeDefs, 7 | resolvers, 8 | }); 9 | -------------------------------------------------------------------------------- /apollo/typeDefs.js: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server-micro'; 2 | 3 | export const typeDefs = gql` 4 | type User { 5 | id: ID! 6 | name: String! 7 | email: String! 8 | createdAt: Int! 9 | } 10 | type Product { 11 | id: ID! 12 | name: String! 13 | description: String! 14 | img_url: String! 15 | price: String! 16 | rating: String! 17 | createdAt: Int 18 | updatedAt: Int 19 | user_id: ID! 20 | } 21 | type Category { 22 | id: ID! 23 | name: String! 24 | label: String! 25 | md_icon: String! 26 | createdAt: Int 27 | } 28 | input SignUpInput { 29 | name: String! 30 | email: String! 31 | password: String! 32 | } 33 | input SignInInput { 34 | email: String! 35 | password: String! 36 | } 37 | input ProductInput { 38 | name: String! 39 | description: String! 40 | img_url: String! 41 | price: String! 42 | rating: String! 43 | category_id: Int! 44 | } 45 | input UpdateProductInput { 46 | name: String! 47 | description: String! 48 | img_url: String! 49 | price: String! 50 | rating: String! 51 | } 52 | type SignUpPayload { 53 | user: User! 54 | } 55 | type SignInPayload { 56 | user: User! 57 | } 58 | type ProductPayload { 59 | product: Product! 60 | } 61 | input Sort { 62 | field: String! 63 | order: String! = ASC 64 | } 65 | 66 | type Query { 67 | user(id: ID!): User! 68 | users: [User]! 69 | viewer: User 70 | products(sort: [Sort!], category: String): [Product]! 71 | productsById(id: [ID]): [Product!] 72 | product(id: ID!): Product 73 | categories: [Category]! 74 | } 75 | type Mutation { 76 | signUp(input: SignUpInput!): SignUpPayload! 77 | signIn(input: SignInInput!): SignInPayload! 78 | signOut: Boolean! 79 | createProduct(input: ProductInput!): ProductPayload 80 | deleteProduct(id: ID!): Boolean! 81 | updateProduct(id: ID!, input: UpdateProductInput!): ProductPayload 82 | } 83 | `; 84 | -------------------------------------------------------------------------------- /components/alerts/error.js: -------------------------------------------------------------------------------- 1 | export default function Alert({ message }) { 2 | return ( 3 | <> 4 |

{message}

5 | 6 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/alerts/success.js: -------------------------------------------------------------------------------- 1 | export default function Alert({ message }) { 2 | return ( 3 | <> 4 |

{message}

5 | 6 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/alerts/warnig.js: -------------------------------------------------------------------------------- 1 | export default function Warning({ message }) { 2 | return ( 3 | <> 4 |

{message}

5 | 6 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/asideCategories.js: -------------------------------------------------------------------------------- 1 | import CategoriesItem from './categoriesItem'; 2 | import { useQuery } from '@apollo/client'; 3 | import { CATEGORIES } from '../apollo/client/queries'; 4 | import offlineCategories from '../db/offlineData/categories'; 5 | 6 | export default function AsideCategories() { 7 | const { data, loading, error } = useQuery(CATEGORIES); 8 | 9 | if (loading) return <>; 10 | 11 | // Offline data 12 | if (!data?.categories || error) 13 | return ( 14 | 35 | ); 36 | 37 | return ( 38 | 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /components/categoriesItem.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { 3 | MdDesktopWindows, 4 | MdDesktopMac, 5 | MdLaptop, 6 | MdKeyboard, 7 | MdMemory, 8 | MdSpeaker, 9 | MdSmartphone, 10 | MdTv, 11 | MdVideogameAsset, 12 | MdWatch, 13 | MdKeyboardArrowRight, 14 | } from 'react-icons/md'; 15 | const iconSlugs = { 16 | MdDesktopWindows, 17 | MdDesktopMac, 18 | MdLaptop, 19 | MdKeyboard, 20 | MdMemory, 21 | MdSpeaker, 22 | MdSmartphone, 23 | MdTv, 24 | MdVideogameAsset, 25 | MdWatch, 26 | }; 27 | 28 | export default function CategoriesItem({ category }) { 29 | const Icon = iconSlugs[category.md_icon]; 30 | 31 | return ( 32 |
  • 33 | 34 | 35 |
    36 |
    37 | 38 |
    39 |

    {category.label}

    40 |
    41 |
    42 | 43 |
    44 |
    45 | 46 | 47 | 76 |
  • 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /components/emptySection.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function EmptySection({ name }) { 4 | return ( 5 | <> 6 | {name && ( 7 | 8 |

    9 | You do not have any product in your {name} 10 |

    11 | 12 | )} 13 | 14 | {!name && ( 15 | 16 |

    This section is empty

    17 | 18 | )} 19 | 20 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /components/finishOrderCart.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@apollo/client'; 2 | import { CART, PRODUCTS_BY_IDS_PRICE } from '../apollo/client/queries'; 3 | import { useState } from 'react'; 4 | 5 | export default function FinishOrderCart() { 6 | const [finalPrice, setFinalPrice] = useState(0); 7 | const cart = useQuery(CART); 8 | 9 | const { data, loading, error } = useQuery(PRODUCTS_BY_IDS_PRICE, { 10 | variables: { 11 | id: cart.data.cart.products, 12 | }, 13 | }); 14 | 15 | if (loading) return <>; 16 | 17 | if (error) return <>; 18 | 19 | return ( 20 |
    21 |
    22 |

    Total({cart?.data.cart.cartCount} Item):

    23 |

    $ {finalPrice}

    24 |
    25 | 26 | 82 |
    83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /components/footer.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { 3 | FaFacebookF, 4 | FaTwitter, 5 | FaInstagram, 6 | FaYoutube, 7 | FaCcVisa, 8 | FaCcMastercard, 9 | FaCcPaypal, 10 | FaCcAmazonPay, 11 | } from 'react-icons/fa'; 12 | import Logo from './logo'; 13 | 14 | export default function Fotter() { 15 | return ( 16 | 193 | ); 194 | } 195 | -------------------------------------------------------------------------------- /components/form/InputContainer.js: -------------------------------------------------------------------------------- 1 | export default function InputContainer({ children }) { 2 | return ( 3 | <> 4 | 5 |
    {children}
    6 | 7 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/form/button.js: -------------------------------------------------------------------------------- 1 | export default function Button({ type, title }) { 2 | return ( 3 | <> 4 | 5 | 6 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /components/form/formContainer.js: -------------------------------------------------------------------------------- 1 | export default function FormContainer({ children }) { 2 | return ( 3 | <> 4 |
    {children}
    5 | 6 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /components/form/input.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export default function Input({ type, name, placeholder, onChange, value }) { 4 | function handleChange(event) { 5 | const { value } = event.target; 6 | onChange(value); 7 | } 8 | 9 | return ( 10 | <> 11 | 18 | 19 | 56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /components/header/header-desktop.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@apollo/client'; 2 | import Link from 'next/link'; 3 | import { 4 | FaShoppingCart, 5 | FaRegHeart, 6 | FaUser, 7 | FaSignOutAlt, 8 | FaBars, 9 | } from 'react-icons/fa'; 10 | import { CART_COUNT } from '../../apollo/client/queries'; 11 | 12 | import Logo from '../logo'; 13 | import SearchBox from '../search-box'; 14 | 15 | export default function HeaderDesktop({ viewer }) { 16 | const cart = useQuery(CART_COUNT); 17 | 18 | return ( 19 | <> 20 |
    21 | 22 | 23 | 24 | 25 |
    26 | 27 | 28 | 29 |

    30 | {cart.data.cart.cartCount}{' '} 31 | Items 32 |

    33 |
    34 | 35 | 36 | 37 | 38 |

    Wishlist

    39 |
    40 | 41 | {!viewer && ( 42 | 43 | 44 | 45 |

    Sign In

    46 |
    47 | 48 | )} 49 | {viewer && ( 50 | <> 51 | 52 | 53 | 54 |

    {viewer.name}

    55 |
    56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | )} 64 |
    65 |
    66 |
    67 |
    68 | 69 | 82 |
    83 | 84 | 98 | 99 |
    100 |
    101 |

    Help

    102 |
    103 |
    104 |

    USD

    105 |
    106 |
    107 |

    Language

    108 |
    109 |
    110 |
    111 | 212 | 213 | ); 214 | } 215 | -------------------------------------------------------------------------------- /components/header/header-mobile.js: -------------------------------------------------------------------------------- 1 | import { isDrawerOpenVar } from '../../apollo/client/cache'; 2 | 3 | import Logo from '../logo'; 4 | import OpenDrawerButton from './open-drawer-button'; 5 | import SideDrawer from './side-drawer'; 6 | 7 | export default function HeaderMobile({ viewer }) { 8 | function toggleDrawer() { 9 | isDrawerOpenVar(!isDrawerOpenVar()); 10 | } 11 | 12 | return ( 13 |
    14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 |
    28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /components/header/index.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@apollo/client'; 2 | import { VIEWER } from '../../apollo/client/queries'; 3 | 4 | import HeaderMobile from './header-mobile'; 5 | import HeaderDesktop from './header-desktop'; 6 | 7 | export default function Header() { 8 | const { data, loading, error } = useQuery(VIEWER); 9 | const viewer = data?.viewer; 10 | 11 | return ( 12 |
    13 | 16 | 17 | 20 | 21 | 45 |
    46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /components/header/open-drawer-button.js: -------------------------------------------------------------------------------- 1 | export default function ToggleDrawerButton({ openDrawer }) { 2 | return ( 3 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /components/header/side-drawer.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { useQuery } from '@apollo/client'; 3 | import SearchBox from '../search-box'; 4 | import { GET_DRAWER_STATE } from '../../apollo/client/queries'; 5 | 6 | export default function SideDrawer({ closeDrawer }) { 7 | const { data, loading, error } = useQuery(GET_DRAWER_STATE); 8 | 9 | return ( 10 |
    14 | 17 | 18 |
    19 | 20 |
    21 | 22 | 39 | 40 | 103 |
    104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /components/headerBarProducts.js: -------------------------------------------------------------------------------- 1 | import { sortProductSectionVar } from '../apollo/client/cache'; 2 | import { useQuery } from '@apollo/client'; 3 | import { SORT_PRODUCT_SECTION } from '../apollo/client/queries'; 4 | 5 | export default function HeaderBarProducts() { 6 | const { data } = useQuery(SORT_PRODUCT_SECTION); 7 | 8 | function handlePopularProductsClick() { 9 | sortProductSectionVar(['rating', 'DESC']); 10 | } 11 | function handleLowPriceProductsClick() { 12 | sortProductSectionVar(['price', 'ASC']); 13 | } 14 | function handleHighPriceProductsClick() { 15 | sortProductSectionVar(['price', 'DESC']); 16 | } 17 | 18 | return ( 19 |
    20 |
    21 | 31 | Popular products 32 | 33 | 43 | Low price 44 | 45 | 55 | High price 56 | 57 |
    58 | 113 |
    114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /components/loading-page.js: -------------------------------------------------------------------------------- 1 | import Loader from 'react-loader-spinner'; 2 | 3 | export default function LoadingPage() { 4 | return ( 5 |
    6 | 12 | 19 |
    20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /components/logo.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function Logo() { 4 | return ( 5 | <> 6 | 7 | Quantum 8 | 9 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components/page-container.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | 3 | export default function PageContainer({ title, description, children }) { 4 | return ( 5 |
    6 | 7 | {title || 'Quantum E-commerce - Next Project'} 8 | {description !== false && ( 9 | 16 | )} 17 | 18 | 19 | 20 |
    {children}
    21 | 22 | 32 |
    33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /components/page.js: -------------------------------------------------------------------------------- 1 | import PageContainer from './page-container'; 2 | import Header from './header'; 3 | import Footer from './footer'; 4 | 5 | export default function Page({ title, description, children }) { 6 | return ( 7 | 8 |
    9 | 10 |
    {children}
    11 | 12 |