├── public ├── logo.png ├── favicon.ico ├── loader.gif ├── pink-dress.png ├── purple-jacket.png ├── yellow-jacket.png ├── fullscreen-exit.svg ├── fullscreen.svg ├── women.svg ├── men.svg ├── sort.svg ├── home.svg ├── close.svg ├── manifest.json ├── location.svg ├── zeit.svg ├── offline.html ├── flash.svg ├── sw.js ├── earrings.svg ├── phone.svg ├── checkout.svg ├── tshirt.svg ├── user.svg ├── inspiration.svg ├── shoes.svg ├── activewear.svg └── logo.svg ├── sass ├── pages │ ├── _checkout.scss │ ├── _app.scss │ └── _suggested-products.scss └── components │ ├── _splash-screen.scss │ ├── _detail.scss │ ├── _explore.scss │ ├── _card.scss │ ├── _form.scss │ ├── _modal.scss │ ├── _carousel.scss │ ├── _spinner.scss │ ├── _input.scss │ ├── _homepage.scss │ ├── _header.scss │ ├── _cart.scss │ ├── _layout.scss │ └── _button.scss ├── .prettierrc.js ├── components ├── SplashScreen.tsx ├── Loader.tsx ├── Homepage.tsx ├── Spinner.tsx ├── SuggestedProducts │ ├── index.tsx │ └── ProductCard.tsx ├── AllProducts │ └── index.tsx ├── Explore.tsx ├── Carousel.tsx ├── button │ └── ToolbarButton.tsx ├── Toolbar.tsx ├── Layout.tsx ├── BannerCard.tsx ├── Modal │ ├── Register.tsx │ ├── Authentication.tsx │ └── Login.tsx ├── Cart.tsx ├── Header.tsx ├── Detail.tsx └── Newin.tsx ├── .vscode └── settings.json ├── lib ├── accessToken.ts └── withApollo.tsx ├── next-env.d.ts ├── utils ├── text-util.ts └── actions.ts ├── pages ├── account │ ├── my-orders.tsx │ └── profile.tsx ├── api │ ├── auth │ │ └── [...nextauth].js │ ├── products │ │ └── [category].ts │ └── graphql.js ├── index.tsx ├── category │ └── [slug].tsx ├── product │ └── detail │ │ └── [...detail].tsx ├── _app.tsx └── checkout │ └── confirm.tsx ├── .gitignore ├── codegen.yml ├── next.config.js ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── graphql ├── user.graphql └── generated │ └── graphql.tsx ├── tsconfig.json ├── package.json ├── README.md └── LICENSE /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reeversedev/devdevdev/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reeversedev/devdevdev/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reeversedev/devdevdev/HEAD/public/loader.gif -------------------------------------------------------------------------------- /sass/pages/_checkout.scss: -------------------------------------------------------------------------------- 1 | #shipping-info { 2 | border-left: 1px solid gainsboro; 3 | } 4 | -------------------------------------------------------------------------------- /public/pink-dress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reeversedev/devdevdev/HEAD/public/pink-dress.png -------------------------------------------------------------------------------- /public/purple-jacket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reeversedev/devdevdev/HEAD/public/purple-jacket.png -------------------------------------------------------------------------------- /public/yellow-jacket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reeversedev/devdevdev/HEAD/public/yellow-jacket.png -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | semi: false, 5 | singleQuote: true, 6 | } 7 | -------------------------------------------------------------------------------- /components/SplashScreen.tsx: -------------------------------------------------------------------------------- 1 | const SplashScreen = ({ children }) => { 2 | return
{children}
3 | } 4 | 5 | export default SplashScreen 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Set the default 3 | "editor.formatOnSave": false, 4 | // Enable per-language 5 | "[javascript]": { 6 | "editor.formatOnSave": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/accessToken.ts: -------------------------------------------------------------------------------- 1 | let accessToken = '' 2 | 3 | export const setAccessToken = (s: string) => { 4 | accessToken = s 5 | } 6 | 7 | export const getAccessToken = () => { 8 | return accessToken 9 | } 10 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | declare module '*.graphqls' { 5 | import { DocumentNode } from 'graphql' 6 | export default typeof DocumentNode 7 | } 8 | -------------------------------------------------------------------------------- /utils/text-util.ts: -------------------------------------------------------------------------------- 1 | export function capitalize(text: string) { 2 | let setStringName = text 3 | setStringName = `${setStringName[0].toUpperCase()}${setStringName.substring( 4 | 1 5 | )}` 6 | return setStringName 7 | } 8 | -------------------------------------------------------------------------------- /public/fullscreen-exit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/women.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/men.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Loader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Loader = () => { 4 | return ( 5 |
6 | Loading... 7 |
8 | ) 9 | } 10 | 11 | export default Loader 12 | -------------------------------------------------------------------------------- /utils/actions.ts: -------------------------------------------------------------------------------- 1 | export const openCart = () => ({ 2 | type: 'OPEN_CART', 3 | payload: true, 4 | }) 5 | 6 | export const closeCart = () => ({ 7 | type: 'CLOSE_CART', 8 | payload: true, 9 | }) 10 | 11 | export const fullviewCart = () => ({ 12 | type: 'FULL_VIEW', 13 | payload: true, 14 | }) 15 | -------------------------------------------------------------------------------- /public/sort.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/account/my-orders.tsx: -------------------------------------------------------------------------------- 1 | import Layout from '../../components/Layout' 2 | 3 | const MyOrders = () => { 4 | return ( 5 |
6 | 7 |
8 |

My Orders

9 |
10 |
11 |
12 | ) 13 | } 14 | 15 | export default MyOrders 16 | -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].js: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth' 2 | import Providers from 'next-auth/providers' 3 | 4 | const options = { 5 | providers: [ 6 | Providers.GitHub({ 7 | clientId: process.env.GITHUB_ID, 8 | clientSecret: process.env.GITHUB_SECRET, 9 | }), 10 | ], 11 | } 12 | 13 | export default (req, res) => NextAuth(req, res, options) 14 | -------------------------------------------------------------------------------- /public/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /public/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Homepage.tsx: -------------------------------------------------------------------------------- 1 | import Category from '../pages/category/[slug]' 2 | import Explore from './Explore' 3 | 4 | const HomePage = () => { 5 | return ( 6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | ) 15 | } 16 | 17 | export default HomePage 18 | -------------------------------------------------------------------------------- /codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: 'http://ec2-13-234-240-74.ap-south-1.compute.amazonaws.com:4000/graphql' 3 | documents: './graphql/*.graphql' 4 | generates: 5 | ./graphql/generated/graphql.tsx: 6 | plugins: 7 | - 'typescript' 8 | - 'typescript-operations' 9 | - 'typescript-react-apollo' 10 | config: 11 | withHOC: false 12 | withComponent: false 13 | withHooks: true 14 | -------------------------------------------------------------------------------- /sass/components/_splash-screen.scss: -------------------------------------------------------------------------------- 1 | .splash-screen { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | height: 100vh; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | color: #fff; 11 | 12 | img { 13 | width: 30%; 14 | } 15 | } 16 | 17 | @media screen and (max-width: 425px) { 18 | .splash-screen { 19 | img { 20 | width: 60%; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | const Spinner = () => { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ) 12 | } 13 | 14 | export default Spinner 15 | -------------------------------------------------------------------------------- /pages/api/products/[category].ts: -------------------------------------------------------------------------------- 1 | import { NowRequest, NowResponse } from '@now/node' 2 | import { products } from '../../../utils/products' 3 | 4 | export default (req: NowRequest, res: NowResponse) => { 5 | const { 6 | query: { category }, 7 | } = req 8 | 9 | const result = products[category.toString()].map((product, index) => { 10 | return { ...product, ['id']: index } 11 | }) 12 | 13 | res.json({ products: result }) 14 | } 15 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import Router from 'next/router' 3 | import Layout from '../components/Layout' 4 | import Category from './category/[slug]' 5 | 6 | const Home = () => { 7 | useEffect(() => { 8 | const { pathname } = Router 9 | if (pathname == '/') { 10 | Router.push('/category/new-in') 11 | } 12 | }) 13 | 14 | return {/* */} 15 | } 16 | 17 | export default Home 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devdevdev", 3 | "short_name": "devdevdev", 4 | "theme_color": "#9e19af", 5 | "background_color": "#ffffff", 6 | "description": "The Trendy Store", 7 | "display": "fullscreen", 8 | "orientation": "portrait", 9 | "scope": "/", 10 | "start_url": "/", 11 | "icons": [ 12 | { 13 | "src": "/logo.png", 14 | "sizes": "512x512", 15 | "type": "image/png", 16 | "purpose": "any" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withSass = require('@zeit/next-sass') 2 | 3 | module.exports = withSass({ 4 | webpack(config, options) { 5 | config.module.rules.push({ 6 | test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/, 7 | use: { 8 | loader: 'url-loader', 9 | options: { 10 | limit: 100000, 11 | }, 12 | }, 13 | }) 14 | 15 | return config 16 | }, 17 | env: { 18 | BACKEND: 'https://devstore.xyz', 19 | // BACKEND: 'http://localhost:4000', 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /pages/api/graphql.js: -------------------------------------------------------------------------------- 1 | import { gql, ApolloServer } from 'apollo-server-micro' 2 | 3 | const typeDefs = gql` 4 | type Query { 5 | hello: String! 6 | } 7 | ` 8 | const resolvers = { 9 | Query: { 10 | hello: (_parent, _args, _context) => { 11 | return 'Hello' 12 | }, 13 | }, 14 | } 15 | 16 | const apolloServer = new ApolloServer({ 17 | typeDefs, 18 | resolvers, 19 | }) 20 | 21 | const handler = apolloServer.createHandler({ path: '/api/graphql' }) 22 | 23 | export const config = { 24 | api: { 25 | bodyParser: false, 26 | }, 27 | } 28 | 29 | export default handler 30 | -------------------------------------------------------------------------------- /components/SuggestedProducts/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ProductCard from './ProductCard' 3 | import { products } from '../../utils/products' 4 | 5 | const SuggestedProducts = ({ category }) => { 6 | return ( 7 |
8 |

Suggested Products

9 |
10 | {products[category].map((card, index) => { 11 | return ( 12 | 13 | ) 14 | })} 15 |
16 |
17 | ) 18 | } 19 | 20 | export default SuggestedProducts 21 | -------------------------------------------------------------------------------- /sass/components/_detail.scss: -------------------------------------------------------------------------------- 1 | .detail { 2 | padding: 20px; 3 | 4 | .detail-panel { 5 | margin: 40px 20px; 6 | p { 7 | line-height: 2rem; 8 | } 9 | .name { 10 | font-size: 20px; 11 | font-weight: 200; 12 | } 13 | .product-id { 14 | font-size: 14px; 15 | font-weight: 600; 16 | color: gray; 17 | } 18 | .price-panel { 19 | .price-tag { 20 | font-size: 22px; 21 | font-weight: 700; 22 | color: purple; 23 | margin-bottom: 0; 24 | } 25 | } 26 | } 27 | } 28 | 29 | @media screen and (max-width: 425px) { 30 | .detail { 31 | .detail-panel { 32 | margin: 0px; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sass/components/_explore.scss: -------------------------------------------------------------------------------- 1 | .explore { 2 | position: fixed; 3 | top: 5rem; 4 | li { 5 | a { 6 | text-decoration: none; 7 | color: black; 8 | cursor: pointer; 9 | } 10 | span { 11 | margin: 0px 10px; 12 | font-size: 14px; 13 | font-weight: 300; 14 | } 15 | } 16 | } 17 | 18 | @media (min-width: 769px) and (max-width: 1024px) { 19 | .explore { 20 | top: 5.5rem; 21 | } 22 | } 23 | 24 | @media screen and (max-width: 425px) { 25 | .explore { 26 | position: relative; 27 | width: 100%; 28 | top: 0; 29 | ul { 30 | display: flex; 31 | flex-wrap: wrap; 32 | justify-content: space-between; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /graphql/user.graphql: -------------------------------------------------------------------------------- 1 | mutation register( 2 | $firstName: String! 3 | $lastName: String 4 | $email: String! 5 | $password: String! 6 | ) { 7 | register( 8 | firstName: $firstName 9 | lastName: $lastName 10 | email: $email 11 | password: $password 12 | ) 13 | } 14 | 15 | mutation login($email: String!, $password: String!) { 16 | login(email: $email, password: $password) { 17 | accessToken 18 | profile: user { 19 | id 20 | firstName 21 | lastName 22 | email 23 | } 24 | } 25 | } 26 | 27 | mutation Logout { 28 | logout 29 | } 30 | 31 | query profile { 32 | profile { 33 | id 34 | firstName 35 | lastName 36 | email 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve" 16 | }, 17 | "exclude": ["node_modules"], 18 | "include": [ 19 | "next-env.d.ts", 20 | "**/*.ts", 21 | "**/*.tsx", 22 | "pages/product/detail/[...detail].tsx", 23 | "pages/category/[slug].tsx", 24 | "lib/withApollo.tsz" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /sass/components/_card.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | height: 500px; 3 | width: 200px; 4 | border-radius: 25px; 5 | background: #fff; 6 | 7 | img { 8 | border-radius: 15px 15px 0 0; 9 | } 10 | 11 | .card-detail { 12 | position: absolute; 13 | top: calc(100% - 14rem); 14 | left: 5px; 15 | .title { 16 | font-size: 16px; 17 | font-weight: 600; 18 | } 19 | .subtitle { 20 | font-weight: 200; 21 | } 22 | } 23 | 24 | &:hover { 25 | cursor: pointer; 26 | transform: scale(1.1); 27 | z-index: 1; 28 | height: 450px; 29 | } 30 | transition: all ease-in-out 0.3s; 31 | } 32 | 33 | @media screen and (max-width: 425px) { 34 | .card { 35 | width: 180px; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /components/SuggestedProducts/ProductCard.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | const ProductCard = ({ 4 | name, 5 | image, 6 | brand, 7 | priceCurrency, 8 | price, 9 | type, 10 | id, 11 | }) => { 12 | return ( 13 | 17 |
18 | {name} 19 |
20 |

{brand}

21 |

{name}

22 |

23 | {priceCurrency} {price} 24 |

25 |
26 |
27 | 28 | ) 29 | } 30 | 31 | export default ProductCard 32 | -------------------------------------------------------------------------------- /public/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/zeit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sass/pages/_app.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Nunito:200,300,400,600,700,900&display=swap'); 2 | 3 | // Components 4 | @import '../components/layout'; 5 | @import '../components/input'; 6 | @import '../components/header'; 7 | @import '../components/homepage'; 8 | @import '../components/button'; 9 | @import '../components/carousel'; 10 | @import '../components/detail'; 11 | @import '../components/card'; 12 | @import '../components/explore'; 13 | @import '../components/cart'; 14 | @import '../components/splash-screen'; 15 | @import '../components/modal'; 16 | @import '../components/form'; 17 | @import '../components/spinner'; 18 | 19 | // Pages 20 | @import '../pages/suggested-products'; 21 | @import '../pages/checkout'; 22 | 23 | body { 24 | margin: 4rem 0; 25 | background-color: #ffff; 26 | } 27 | 28 | * { 29 | font-family: 'Nunito', sans-serif; 30 | } 31 | -------------------------------------------------------------------------------- /components/AllProducts/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import ProductCard from '../SuggestedProducts/ProductCard' 3 | import Loader from '../Loader' 4 | 5 | const AllProducts = ({ slug }) => { 6 | const [loading, setLoading] = useState(true) 7 | const [products, setProducts] = useState([]) 8 | 9 | useEffect(() => { 10 | fetch(`/api/products/${slug}`) 11 | .then((data) => data.json()) 12 | .then(({ products }) => { 13 | setLoading(false) 14 | setProducts(products) 15 | }) 16 | }, [slug]) 17 | 18 | return ( 19 |
20 | {loading && } 21 | {products.length > 0 && 22 | products.map((product) => { 23 | return 24 | })} 25 |
26 | ) 27 | } 28 | 29 | export default AllProducts 30 | -------------------------------------------------------------------------------- /sass/components/_form.scss: -------------------------------------------------------------------------------- 1 | .form { 2 | .form-item { 3 | width: 50%; 4 | margin: 10px 0px; 5 | label { 6 | display: block; 7 | width: 100%; 8 | padding-bottom: 10px; 9 | } 10 | input { 11 | display: block; 12 | height: 25px; 13 | width: 80%; 14 | padding: 10px; 15 | } 16 | } 17 | button[type='submit'] { 18 | margin: 20px 0px; 19 | border: transparent; 20 | background-color: purple; 21 | color: #fff; 22 | font-size: 18px; 23 | font-weight: 500; 24 | padding: 10px 20px; 25 | cursor: pointer; 26 | } 27 | button[disabled] { 28 | background-color: gray; 29 | } 30 | } 31 | 32 | @media screen and (max-width: 425px) { 33 | .form { 34 | .form-item { 35 | width: 100%; 36 | input { 37 | width: -webkit-fill-available; 38 | } 39 | } 40 | button[type='submit'] { 41 | align-self: flex-end; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/offline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | dev dev dev 9 | 19 | 20 | 21 |
22 |
23 | devdevdev logo 24 |
25 |

Looks like you are offline 😟

26 |

Please check your internet connection and try again!

27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /sass/pages/_suggested-products.scss: -------------------------------------------------------------------------------- 1 | .suggested-products { 2 | padding: 0 50px; 3 | overflow: hidden; 4 | 5 | .products { 6 | min-height: 30rem; 7 | display: flex; 8 | overflow-x: auto; 9 | scroll-snap-type: x mandatory; 10 | scroll-behavior: smooth; 11 | 12 | &::-webkit-scrollbar { 13 | width: 10px; 14 | height: 10px; 15 | } 16 | 17 | &::-webkit-scrollbar-thumb { 18 | background: rgb(56, 23, 56); 19 | border-radius: 25px; 20 | } 21 | 22 | &::-webkit-scrollbar-track { 23 | background: transparent; 24 | } 25 | 26 | .card { 27 | flex-shrink: 0; 28 | transform-origin: center center; 29 | // transform: scale(1); 30 | // transition: transform 0.5s; 31 | 32 | margin-right: 20px; 33 | margin-bottom: 20px; 34 | } 35 | } 36 | } 37 | 38 | @media screen and (max-width: 425px) { 39 | .suggested-products { 40 | padding: 20px; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /pages/account/profile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Layout from '../../components/Layout' 3 | import { useProfileQuery } from '../../graphql/generated/graphql' 4 | import Router from 'next/router' 5 | import Loader from '../../components/Loader' 6 | 7 | const Profile = () => { 8 | const { data, loading, error } = useProfileQuery() 9 | 10 | if (loading) { 11 | return ( 12 | 13 |
14 | 15 |
16 |
17 | ) 18 | } 19 | if (error) { 20 | return error.graphQLErrors.map((err) => { 21 | err.message === 'not authenticated' && Router.push('/') 22 | }) 23 | } 24 | const { 25 | profile: { firstName, lastName, email }, 26 | } = data 27 | return ( 28 | 29 |
30 |

31 | Hey, {firstName} {lastName}! 32 |

{' '} 33 |

{email}

34 |
35 |
36 | ) 37 | } 38 | 39 | export default Profile 40 | -------------------------------------------------------------------------------- /components/Explore.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | export const exploreItems = [ 4 | { name: 'New In', icon: '/flash.svg', slug: 'new-in' }, 5 | { name: 'Clothing', icon: '/tshirt.svg', slug: 'clothing' }, 6 | { name: 'Shoes', icon: '/shoes.svg', slug: 'shoes' }, 7 | ] 8 | 9 | const Explore = () => { 10 | return ( 11 |
12 |
13 |

Explore

14 |
15 |
    16 | {exploreItems.map(({ name, icon, slug }, index) => { 17 | return ( 18 |
  • 19 | 23 | 24 | icon{' '} 25 | {name} 26 | 27 | 28 |
  • 29 | ) 30 | })} 31 |
32 |
33 |
34 |
35 | ) 36 | } 37 | 38 | export default Explore 39 | -------------------------------------------------------------------------------- /sass/components/_modal.scss: -------------------------------------------------------------------------------- 1 | .modal { 2 | display: block; /* Hidden by default */ 3 | position: fixed; /* Stay in place */ 4 | z-index: 1; /* Sit on top */ 5 | left: 0; 6 | top: 0; 7 | width: 100%; /* Full width */ 8 | height: 100%; /* Full height */ 9 | overflow: auto; /* Enable scroll if needed */ 10 | background-color: rgb(0, 0, 0); /* Fallback color */ 11 | background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */ 12 | transition: all 2s ease-in-out; 13 | } 14 | 15 | /* Modal Content/Box */ 16 | .modal-content { 17 | display: flex; 18 | flex-direction: column; 19 | background-color: #fefefe; 20 | margin: calc(100vh - 50%) auto; /* 15% from the top and centered */ 21 | padding: 20px; 22 | width: 80%; /* Could be more or less, depending on screen size */ 23 | } 24 | 25 | /* The Close Button */ 26 | .close { 27 | color: #aaa; 28 | float: right; 29 | font-size: 28px; 30 | font-weight: bold; 31 | } 32 | 33 | .close:hover, 34 | .close:focus { 35 | color: black; 36 | text-decoration: none; 37 | cursor: pointer; 38 | } 39 | 40 | @media screen and (max-width: 425px) { 41 | .modal { 42 | .modal-content { 43 | margin: 10px; 44 | width: fit-content; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sass/components/_carousel.scss: -------------------------------------------------------------------------------- 1 | .carousel { 2 | padding: 20px; 3 | .main-box { 4 | height: 600px; 5 | width: 450px; 6 | margin: 50px; 7 | background-repeat: no-repeat; 8 | background-origin: border-box; 9 | background-size: cover; 10 | } 11 | .option-box { 12 | height: 100px; 13 | width: 100px; 14 | cursor: pointer; 15 | margin-right: 10px; 16 | background-repeat: no-repeat; 17 | background-origin: border-box; 18 | background-size: cover; 19 | background-position: 100% 100%; 20 | 21 | &:hover { 22 | box-shadow: 0px 0px 1px 0px; 23 | } 24 | } 25 | } 26 | 27 | @media screen and (max-width: 425px) { 28 | .carousel { 29 | .main-box { 30 | height: 450px; 31 | width: 100%; 32 | margin: 50px; 33 | background-repeat: no-repeat; 34 | background-origin: border-box; 35 | background-size: cover; 36 | background-position: 100% 100%; 37 | } 38 | .option-box { 39 | height: 100px; 40 | width: 100px; 41 | margin-right: 10px; 42 | background-repeat: no-repeat; 43 | background-origin: border-box; 44 | background-size: cover; 45 | background-position: 100% 100%; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/flash.svg: -------------------------------------------------------------------------------- 1 | flashPaolo Valzania -------------------------------------------------------------------------------- /components/Carousel.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | const products = [ 4 | 'https://assets.myntassets.com/f_webp,fl_progressive/h_960,q_80,w_720/v1/assets/images/11353052/2020/3/2/6609162b-104e-46f6-b5d7-a698dc03c4051583147962360-ADIDAS-Originals-Men-Casual-Shoes-6591583147960924-1.jpg', 5 | 'https://assets.myntassets.com/f_webp,fl_progressive/h_960,q_80,w_720/v1/assets/images/11353052/2020/3/2/2c0e954b-085e-40ff-b4f7-c41d916350af1583147962308-ADIDAS-Originals-Men-Casual-Shoes-6591583147960924-2.jpg', 6 | 'https://assets.myntassets.com/f_webp,fl_progressive/h_960,q_80,w_720/v1/assets/images/11353052/2020/3/2/a87bf314-f8b1-46fd-978e-2a891f404d1e1583147962268-ADIDAS-Originals-Men-Casual-Shoes-6591583147960924-3.jpg', 7 | 'https://assets.myntassets.com/f_webp,fl_progressive/h_960,q_80,w_720/v1/assets/images/11353052/2020/3/2/77e60cee-9191-4f10-9e1a-0d39225356a71583147962222-ADIDAS-Originals-Men-Casual-Shoes-6591583147960924-4.jpg', 8 | ] 9 | 10 | const Carousel = ({ image }) => { 11 | return ( 12 |
13 |
17 |
18 | ) 19 | } 20 | 21 | export default Carousel 22 | -------------------------------------------------------------------------------- /public/sw.js: -------------------------------------------------------------------------------- 1 | /* Service Worker */ 2 | 3 | self.addEventListener("install", function (event) { 4 | let offlineRequest = new Request('offline.html'); 5 | 6 | event.waitUntil( 7 | 8 | // Once the whole app is loaded, it'll fetch and cache the offline entities 9 | 10 | fetch(offlineRequest) 11 | .then(function (response) { 12 | return caches.open('offline') 13 | .then(function (cache) { 14 | return Promise.all([ 15 | cache.put(offlineRequest, response), 16 | cache.addAll(['/logo.svg']) 17 | ]); 18 | }) 19 | }) 20 | ) 21 | }); 22 | 23 | self.addEventListener("fetch", function (event) { 24 | let request = event.request; 25 | 26 | // for every get request 27 | if (request.method === "GET") { 28 | event.respondWith( 29 | caches.match(request) 30 | .then(function(response){ 31 | return response || fetch(request); // respond from cache if there's a match, otherwise make the network call 32 | }) 33 | .catch(function(){ 34 | return caches.match('/offline.html'); // if GET results in an error, render offline.html 35 | }) 36 | ) 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /components/button/ToolbarButton.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | const ToolbarButton = ({ className, link }) => { 4 | return ( 5 |
6 | {link ? ( 7 | 8 | 9 | {' '} 10 | 11 | {className && 12 | className.charAt(0).toUpperCase() + className.slice(1)} 13 | 14 | 15 | 16 | ) : ( 17 | 18 | {' '} 19 | 20 | {className && 21 | className.charAt(0).toUpperCase() + className.slice(1)} 22 | 23 | 24 | )} 25 | 26 |
27 |

Choose Gender

28 |
29 | 33 | 37 |
38 |
39 |
40 | ) 41 | } 42 | 43 | export default ToolbarButton 44 | -------------------------------------------------------------------------------- /components/Toolbar.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from 'react' 2 | import { UserContext } from '../pages/_app' 3 | 4 | const GenderToggleButton = () => { 5 | const [gender, setGender] = useState(false) 6 | return ( 7 | <> 8 | setGender(!gender)} 14 | /> 15 | 29 | 30 | ) 31 | } 32 | 33 | const Toolbar = ({ buttons }) => { 34 | const { 35 | cart: { displayCart }, 36 | setCart, 37 | } = useContext(UserContext) 38 | return ( 39 |
40 | 41 |
42 | ) 43 | } 44 | 45 | export default Toolbar 46 | -------------------------------------------------------------------------------- /components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import Header from './Header' 2 | import Toolbar from './Toolbar' 3 | import Cart from './Cart' 4 | 5 | import '../sass/pages/_app.scss' 6 | 7 | import { useContext, useState, useEffect } from 'react' 8 | import { UserContext } from '../pages/_app' 9 | import { withApollo } from '../lib/withApollo' 10 | import { useProfileQuery } from '../graphql/generated/graphql' 11 | 12 | const buttons = [ 13 | { 14 | name: 'Home', 15 | img: '/home.svg', 16 | type: 'link', 17 | link: '/', 18 | className: 'home', 19 | }, 20 | { name: 'Sort', img: '/home.svg', type: 'block', className: 'sort' }, 21 | ] 22 | 23 | const Layout = (props) => { 24 | const { data, error } = useProfileQuery() 25 | const [profile, setProfile] = useState(null) 26 | const { 27 | cart: { displayCart, view }, 28 | } = useContext(UserContext) 29 | 30 | if (error) { 31 | localStorage.setItem('status', error.message) 32 | } 33 | 34 | useEffect(() => { 35 | if (data !== undefined) { 36 | setProfile(data.profile) 37 | } 38 | }, [data]) 39 | 40 | return ( 41 |
42 |
43 | {props.children} 44 | {displayCart && ( 45 |
46 | 47 |
48 | )} 49 |
50 | 51 |
52 |
53 | ) 54 | } 55 | 56 | export default withApollo(Layout) 57 | -------------------------------------------------------------------------------- /pages/category/[slug].tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useContext } from 'react' 2 | import Layout from '../../components/Layout' 3 | import { useRouter } from 'next/router' 4 | import Toolbar from '../../components/Toolbar' 5 | import AllProducts from '../../components/AllProducts' 6 | import Explore, { exploreItems } from '../../components/Explore' 7 | import Newin from '../../components/Newin' 8 | import { capitalize } from '../../utils/text-util' 9 | 10 | const Category = () => { 11 | const router = useRouter() 12 | const { slug } = router.query 13 | const { name, icon } = exploreItems.filter(({ slug }) => slug === slug)[0] 14 | 15 | return ( 16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 |
25 | icon {/*@ts-ignore*/} 26 |

{capitalize(slug)}

27 |
28 | 29 |
30 |
31 | {slug === 'new-in' ? : } 32 |
33 |
34 |
35 |
36 |
37 | ) 38 | } 39 | 40 | export default Category 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devdevdev", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "gen": "graphql-codegen --config codegen.yml" 10 | }, 11 | "dependencies": { 12 | "@apollo/react-hooks": "^3.1.5", 13 | "@apollo/react-ssr": "^3.1.5", 14 | "@zeit/next-sass": "^1.0.1", 15 | "apollo-boost": "^0.4.9", 16 | "apollo-link-context": "^1.0.20", 17 | "apollo-link-token-refresh": "^0.3.1", 18 | "apollo-server-core": "^2.14.2", 19 | "apollo-server-micro": "^2.18.1", 20 | "cookie": "^0.4.1", 21 | "graphql": "^15.0.0", 22 | "isomorphic-unfetch": "^3.0.0", 23 | "jwt-decode": "^2.2.0", 24 | "mongodb": "^3.6.2", 25 | "next": "^9.4.4", 26 | "next-auth": "^3.1.0", 27 | "node-sass": "^4.14.1", 28 | "pg": "^8.4.2", 29 | "react": "^16.13.1", 30 | "react-dom": "^16.13.1", 31 | "url-loader": "^4.1.0" 32 | }, 33 | "devDependencies": { 34 | "@graphql-codegen/cli": "^1.15.1", 35 | "@graphql-codegen/plugin-helpers": "^1.15.1", 36 | "@graphql-codegen/typescript": "^1.15.1", 37 | "@graphql-codegen/typescript-operations": "^1.15.1", 38 | "@graphql-codegen/typescript-react-apollo": "^1.15.1", 39 | "@now/node": "^1.6.1", 40 | "@types/graphql": "^14.5.0", 41 | "@types/react": "^16.9.35", 42 | "graphql-let": "^0.10.0", 43 | "husky": "^4.2.5", 44 | "prettier": "2.0.5", 45 | "pretty-quick": "^2.0.1", 46 | "typescript": "^3.9.5" 47 | }, 48 | "husky": { 49 | "hooks": { 50 | "pre-commit": "pretty-quick --staged" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pages/product/detail/[...detail].tsx: -------------------------------------------------------------------------------- 1 | import Layout from '../../../components/Layout' 2 | import Detail from '../../../components/Detail' 3 | import Carousel from '../../../components/Carousel' 4 | import SuggestedProducts from '../../../components/SuggestedProducts' 5 | 6 | import { useRouter } from 'next/router' 7 | import { products } from '../../../utils/products' 8 | import Loader from '../../../components/Loader' 9 | 10 | function ProductDetail({ product, category }) { 11 | const router = useRouter() 12 | 13 | if (router.isFallback) { 14 | return ( 15 | 16 |
17 | 18 |
19 |
20 | ) 21 | } 22 | 23 | return ( 24 | 25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 |
37 | ) 38 | } 39 | 40 | export async function getStaticProps(context) { 41 | const { 42 | params: { detail }, 43 | } = context 44 | const entry = detail[0] 45 | const category = detail[1] 46 | const product = products[category][entry] 47 | return { 48 | props: { product, category }, 49 | } 50 | } 51 | 52 | export async function getStaticPaths() { 53 | return { 54 | paths: [], 55 | fallback: true, 56 | } 57 | } 58 | 59 | export default ProductDetail 60 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import {useReducer, createContext, useEffect} from 'react' 2 | import { ApolloProvider } from '@apollo/react-hooks' 3 | 4 | import Layout from '../components/Layout' 5 | import { withApollo } from '../lib/withApollo' 6 | 7 | type UserContextType = { 8 | cart: { 9 | displayCart: Boolean 10 | view: String 11 | } 12 | setCart: any 13 | } 14 | 15 | export const UserContext = createContext({ 16 | cart: { 17 | displayCart: true, 18 | view: 'zero', 19 | }, 20 | setCart: () => {}, 21 | }) 22 | 23 | const initialState = { displayCart: true, view: 'zero' } 24 | 25 | function reducer(state = initialState, action) { 26 | switch (action.type) { 27 | case 'OPEN_CART': 28 | return { ...state, displayCart: true, view: 'half' } 29 | case 'CLOSE_CART': 30 | return { ...state, displayCart: true, view: 'zero' } 31 | case 'FULL_VIEW': 32 | return { ...state, displayCart: true, view: 'full' } 33 | default: 34 | return state 35 | } 36 | } 37 | 38 | function MyApp({Component, pageProps, apolloClient}) { 39 | useEffect(() => { 40 | if("serviceWorker" in navigator){ 41 | window.addEventListener("load", function(){ 42 | navigator.serviceWorker 43 | .register("/sw.js") 44 | .catch(()=>{console.log("Service Worker Failed to Register :(")}) 45 | }) 46 | } 47 | }, []); 48 | 49 | const [cart, setCart] = useReducer(reducer, initialState) 50 | return ( 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ) 59 | } 60 | 61 | export default withApollo(MyApp) 62 | -------------------------------------------------------------------------------- /components/BannerCard.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from 'react' 2 | 3 | type CartType = 'horizontal' | 'vertical' 4 | 5 | type TextPosition = 'start' | 'center' | 'end' 6 | 7 | type Text = { 8 | content: string 9 | size: '12' | '14' | '16' | '18' | '20' | '22' 10 | weight: '200' | '300' | '400' | '600' | '700' | '800' 11 | backDropFilter?: string 12 | } 13 | 14 | interface IBannerCardProps { 15 | type: CartType 16 | backgroundColor: string 17 | imageStyle?: CSSProperties 18 | title: Text 19 | subtitle?: Text 20 | textPosition: TextPosition 21 | } 22 | 23 | const BannerCard: React.FC = (props) => { 24 | const { 25 | type, 26 | backgroundColor, 27 | imageStyle, 28 | title, 29 | subtitle, 30 | textPosition, 31 | } = props 32 | return ( 33 |
39 |
44 | {title && ( 45 |

49 | {title.content} 50 |

51 | )} 52 | {subtitle && ( 53 |

56 | {subtitle.content} 57 |

58 | )} 59 |
60 |
61 |
62 | ) 63 | } 64 | 65 | export default BannerCard 66 | -------------------------------------------------------------------------------- /sass/components/_spinner.scss: -------------------------------------------------------------------------------- 1 | .sk-chase { 2 | width: 10px; 3 | height: 10px; 4 | position: relative; 5 | animation: sk-chase 2.5s infinite linear both; 6 | } 7 | 8 | .sk-chase-dot { 9 | width: 100%; 10 | height: 100%; 11 | position: absolute; 12 | left: 0; 13 | top: 0; 14 | animation: sk-chase-dot 2s infinite ease-in-out both; 15 | } 16 | 17 | .sk-chase-dot:before { 18 | content: ''; 19 | display: block; 20 | width: 25%; 21 | height: 25%; 22 | background-color: #fff; 23 | border-radius: 100%; 24 | animation: sk-chase-dot-before 2s infinite ease-in-out both; 25 | } 26 | 27 | .sk-chase-dot:nth-child(1) { 28 | animation-delay: -1.1s; 29 | } 30 | .sk-chase-dot:nth-child(2) { 31 | animation-delay: -1s; 32 | } 33 | .sk-chase-dot:nth-child(3) { 34 | animation-delay: -0.9s; 35 | } 36 | .sk-chase-dot:nth-child(4) { 37 | animation-delay: -0.8s; 38 | } 39 | .sk-chase-dot:nth-child(5) { 40 | animation-delay: -0.7s; 41 | } 42 | .sk-chase-dot:nth-child(6) { 43 | animation-delay: -0.6s; 44 | } 45 | .sk-chase-dot:nth-child(1):before { 46 | animation-delay: -1.1s; 47 | } 48 | .sk-chase-dot:nth-child(2):before { 49 | animation-delay: -1s; 50 | } 51 | .sk-chase-dot:nth-child(3):before { 52 | animation-delay: -0.9s; 53 | } 54 | .sk-chase-dot:nth-child(4):before { 55 | animation-delay: -0.8s; 56 | } 57 | .sk-chase-dot:nth-child(5):before { 58 | animation-delay: -0.7s; 59 | } 60 | .sk-chase-dot:nth-child(6):before { 61 | animation-delay: -0.6s; 62 | } 63 | 64 | @keyframes sk-chase { 65 | 100% { 66 | transform: rotate(360deg); 67 | } 68 | } 69 | 70 | @keyframes sk-chase-dot { 71 | 80%, 72 | 100% { 73 | transform: rotate(360deg); 74 | } 75 | } 76 | 77 | @keyframes sk-chase-dot-before { 78 | 50% { 79 | transform: scale(0.4); 80 | } 81 | 100%, 82 | 0% { 83 | transform: scale(1); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /public/earrings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Modal/Register.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { useState, useEffect } from 'react' 3 | import { useRegisterMutation } from '../../graphql/generated/graphql' 4 | import Spinner from '../Spinner' 5 | 6 | interface IRegisterFormProps { 7 | onModalClose: () => any 8 | } 9 | 10 | const fields = [ 11 | { name: 'firstName', label: 'First Name', type: 'text' }, 12 | { name: 'lastName', label: 'Last Name', type: 'text' }, 13 | { name: 'email', label: 'Email', type: 'email' }, 14 | { name: 'password', label: 'Password', type: 'password' }, 15 | ] 16 | 17 | const Register: React.FC = ({ onModalClose }) => { 18 | const [state, setState] = useState({ 19 | firstName: '', 20 | lastName: '', 21 | email: '', 22 | password: '', 23 | }) 24 | 25 | const [register, { loading, error, data }] = useRegisterMutation({ 26 | variables: state, 27 | }) 28 | 29 | useEffect(() => { 30 | if (data) { 31 | return data.register && onModalClose() 32 | } 33 | }, [data]) 34 | 35 | return ( 36 |
37 |
{ 39 | e.preventDefault() 40 | register() 41 | }} 42 | > 43 |
44 | {fields.map(({ label, name, type }, index) => ( 45 |
46 | 47 | setState({ ...state, [name]: e.target.value })} 51 | /> 52 |
53 | ))} 54 | 57 |
58 |
59 |
60 | ) 61 | } 62 | 63 | export default Register 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # devdevdev 2 | 3 | ![](https://img.shields.io/github/license/reeversedev/devdevdev) 4 | 5 | Here, the latest in trend is always on top of the stack. 6 | 7 | Take a [look](https://devdevdev.now.sh) 8 | 9 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app). 10 | 11 | ## Getting Started 12 | 13 | Install dependencies: 14 | 15 | ```bash 16 | npm install 17 | # or 18 | yarn 19 | ``` 20 | 21 | Run the development server: 22 | 23 | ```bash 24 | npm run dev 25 | # or 26 | yarn dev 27 | ``` 28 | 29 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 30 | 31 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 32 | 33 | ## Reporting a Bug / Feature 34 | 35 | ![](https://img.shields.io/github/issues/reeversedev/devdevdev) 36 | 37 | Report an [issue](https://github.com/reeversedev/devdevdev/issues) here. 38 | 39 | Use the issue [template](https://github.com/reeversedev/devdevdev/tree/master/.github/ISSUE_TEMPLATE). 40 | 41 | ## Learn More 42 | 43 | To learn more about Next.js, take a look at the following resources: 44 | 45 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 46 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 47 | 48 | You can check out [the Next.js GitHub repository](https://github.com/zeit/next.js/) - your feedback and contributions are welcome! 49 | 50 | ## Deploy on ZEIT Now 51 | 52 | The easiest way to deploy your Next.js app is to use the [ZEIT Now Platform](https://zeit.co/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 53 | 54 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 55 | 56 | ![](https://img.shields.io/twitter/follow/reeversedev?style=social) 57 | -------------------------------------------------------------------------------- /public/phone.svg: -------------------------------------------------------------------------------- 1 | Jemis Mali -------------------------------------------------------------------------------- /public/checkout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Modal/Authentication.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { useState, useEffect } from 'react' 3 | import Spinner from '../Spinner' 4 | import Login from './Login' 5 | import Register from './Register' 6 | 7 | interface IAuthenticationFormProps { 8 | onModalClose: () => any 9 | } 10 | 11 | const Authentication: React.FC = ({ 12 | onModalClose, 13 | }) => { 14 | const [mode, setMode] = useState('Login') 15 | 16 | return ( 17 |
18 |
19 |
20 | onModalClose()}> 21 | x 22 | 23 |
24 |
25 |
26 |
27 |
36 |
37 |
38 |

Don't hide!

39 |

Sign up now to receive exclusive offers.

40 |

41 | In order to receive the best deals that are customized to your 42 | tastes . 43 |

44 |

45 | {mode === 'Register' 46 | ? 'Already have an account?' 47 | : "Don't have an account yet?"}{' '} 48 | 50 | setMode(mode === 'Login' ? 'Register' : 'Login') 51 | } 52 | className="cursor-pointer" 53 | > 54 | {mode === 'Register' ? 'Login' : 'Register'} 55 | 56 |

57 | {mode === 'Login' ? ( 58 | 59 | ) : ( 60 | 61 | )} 62 |
63 |
64 |
65 |
66 |
67 |
68 | ) 69 | } 70 | 71 | export default Authentication 72 | -------------------------------------------------------------------------------- /components/Modal/Login.tsx: -------------------------------------------------------------------------------- 1 | import { useState, FormEvent } from 'react' 2 | import { 3 | useLoginMutation, 4 | ProfileDocument, 5 | ProfileQuery, 6 | } from '../../graphql/generated/graphql' 7 | import { withApollo } from '../../lib/withApollo' 8 | import { setAccessToken } from '../../lib/accessToken' 9 | import Spinner from '../Spinner' 10 | 11 | const fields = [ 12 | { name: 'email', label: 'Email', type: 'email' }, 13 | { name: 'password', label: 'Password', type: 'password' }, 14 | ] 15 | 16 | const Login = ({ onModalClose }) => { 17 | const [state, setState] = useState({ 18 | email: '', 19 | password: '', 20 | }) 21 | 22 | const [login, { loading, error, data }] = useLoginMutation() 23 | 24 | const onSubmit = async (e: FormEvent) => { 25 | try { 26 | e.preventDefault() 27 | const response = await login({ 28 | variables: state, 29 | update: (store, { data }) => { 30 | if (!data) { 31 | return null 32 | } 33 | 34 | store.writeQuery({ 35 | query: ProfileDocument, 36 | data: { 37 | profile: data.login.profile, 38 | }, 39 | }) 40 | }, 41 | }) 42 | if (response && response.data) { 43 | setAccessToken(response.data.login.accessToken) 44 | return onModalClose() 45 | // Router.reload() 46 | } 47 | } catch (error) { 48 | return onModalClose() 49 | } 50 | } 51 | 52 | return ( 53 |
54 |
55 |
56 | {fields.map(({ label, name, type }, index) => ( 57 |
58 | 59 | setState({ ...state, [name]: e.target.value })} 63 | required 64 | /> 65 |
66 | ))} 67 | {error && 68 | error.graphQLErrors.map((err) => { 69 | return {err.message} 70 | })} 71 |
72 | 79 |
80 |
81 |
82 |
83 | ) 84 | } 85 | 86 | export default withApollo(Login) 87 | -------------------------------------------------------------------------------- /sass/components/_input.scss: -------------------------------------------------------------------------------- 1 | .no-border { 2 | border: none; 3 | } 4 | 5 | label.search-bar { 6 | display: flex; 7 | input { 8 | width: 45rem; 9 | font-size: 14px; 10 | font-weight: 300; 11 | 12 | &:focus, 13 | :active { 14 | outline: none; 15 | } 16 | } 17 | } 18 | 19 | // will figure out later 20 | label.search-bar::before { 21 | content: ''; 22 | display: inline-block; 23 | height: 25px; 24 | width: 25px; 25 | background-image: url('https://commons.wikimedia.org/wiki/File:Search_Icon.svg'); 26 | } 27 | 28 | // toggle-container 29 | 30 | .toggle-container { 31 | display: none; 32 | 33 | // add default box-sizing for this scope 34 | &, 35 | &:after, 36 | &:before, 37 | & *, 38 | & *:after, 39 | & *:before, 40 | & + .toggle-button { 41 | box-sizing: border-box; 42 | &::selection { 43 | background: none; 44 | } 45 | } 46 | 47 | + .toggle-button { 48 | outline: 0; 49 | display: block; 50 | width: 12em; 51 | height: 2.8em; 52 | position: relative; 53 | cursor: pointer; 54 | user-select: none; 55 | border: 1px solid #f0f0f0; 56 | 57 | &:after, 58 | &:before { 59 | position: relative; 60 | display: block; 61 | width: 50%; 62 | height: 100%; 63 | content: ''; 64 | } 65 | 66 | &:after { 67 | position: absolute; 68 | top: 0; 69 | left: 0; 70 | } 71 | span { 72 | margin: 10px; 73 | z-index: 1; 74 | font-size: 14px; 75 | font-weight: 700; 76 | } 77 | #female { 78 | position: absolute; 79 | top: 0; 80 | } 81 | #male { 82 | position: absolute; 83 | top: 0; 84 | right: 1rem; 85 | } 86 | } 87 | 88 | &:checked + .toggle-button:after { 89 | left: 50%; 90 | } 91 | } 92 | 93 | // themes 94 | .toggle-container-light { 95 | + .toggle-button { 96 | background: #fff; 97 | border-radius: 5px; 98 | padding: 2px; 99 | transition: all 0.4s ease; 100 | &:after { 101 | border-radius: 5px; 102 | opacity: 4.5; 103 | transition: all 0.2s ease; 104 | background: rgb(58, 182, 114); 105 | } 106 | } 107 | } 108 | 109 | @media (min-width: 769px) and (max-width: 1024px) { 110 | label.search-bar { 111 | input { 112 | width: 25rem; 113 | } 114 | } 115 | } 116 | 117 | @media (min-width: 426px) and (max-width: 768px) { 118 | label.search-bar { 119 | input { 120 | width: 15rem; 121 | } 122 | } 123 | } 124 | 125 | @media screen and (max-width: 425px) { 126 | label.search-bar { 127 | display: none; 128 | width: 100%; 129 | input { 130 | width: 100%; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /public/tshirt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Cart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { products } from '../utils/products' 3 | import { fullviewCart, openCart, closeCart } from '../utils/actions' 4 | import { UserContext } from '../pages/_app' 5 | 6 | const Cart = () => { 7 | const { 8 | cart: { view }, 9 | setCart, 10 | } = useContext(UserContext) 11 | 12 | return ( 13 |
14 |
15 |

Cart

16 |
17 | 20 | view === 'half' ? setCart(fullviewCart()) : setCart(openCart()) 21 | } 22 | > 23 | setCart(closeCart())} 26 | > 27 |
28 |
29 |
30 |
31 | {products.clothing.map((cloth, index) => { 32 | return ( 33 |
34 |
44 |
45 |

{cloth.brand}

46 |

{cloth.name}

47 |

Size: XL • Color: White

48 |
49 |
50 |

₹ {cloth.price}

51 |
52 |
53 |
54 |
55 | 56 | 2 57 | 58 |
59 |
60 |
61 |
62 |

63 | ₹ {parseFloat(cloth.price) * 2} 64 |

65 |
66 |
67 | ) 68 | })} 69 |
70 |
71 |
78 |
79 |

80 | Total:{' '} 81 | 82 | ₹ 83 | {products.clothing 84 | .reduce((acc, { price }, i) => acc + parseInt(price) * 2, 0) 85 | .toLocaleString('en')} 86 | 87 |

88 |

89 | (Inclusive of all taxes + Shipping charges) 90 |

91 |
92 | 93 | Checkout 94 |
95 |
96 | ) 97 | } 98 | 99 | export default Cart 100 | -------------------------------------------------------------------------------- /public/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Link from 'next/link' 3 | 4 | import { useContext, useState, useEffect } from 'react' 5 | import { openCart, closeCart } from '../utils/actions' 6 | import { UserContext } from '../pages/_app' 7 | import Authentication from './Modal/Authentication' 8 | import { useLogoutMutation, User } from '../graphql/generated/graphql' 9 | import { setAccessToken } from '../lib/accessToken' 10 | 11 | const links = [ 12 | { name: 'Home', url: '/category/new-in' }, 13 | { name: 'My Orders', url: '/account/my-orders' }, 14 | { name: 'Settings', url: '/account/profile' }, 15 | ] 16 | 17 | const Header: React.FC = (profile) => { 18 | const [loginForm, setLoginForm] = useState(false) 19 | const [profileInfo, setProfileInfo] = useState(profile) 20 | const { firstName, lastName } = profileInfo 21 | const { 22 | cart: { displayCart }, 23 | setCart, 24 | } = useContext(UserContext) 25 | 26 | const [logoutOperation, { client }] = useLogoutMutation() 27 | 28 | const logoutFunction = async () => { 29 | try { 30 | await logoutOperation() 31 | setAccessToken('') 32 | await client.resetStore() 33 | } catch (err) {} 34 | } 35 | 36 | return ( 37 |
38 | 39 | dev dev dev 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 62 | 63 | {/* Active when login is implemented */} 64 | {firstName && ( 65 |
setCart(!displayCart ? closeCart() : openCart())} 68 | > 69 | 73 | Cart: 74 | 2 75 |
76 | )} 77 | {!firstName && ( 78 |
79 | setLoginForm(!loginForm)} 82 | > 83 | user 84 | Login 85 | 86 | {loginForm && ( 87 | setLoginForm(false)} /> 88 | )} 89 |
90 | )} 91 | 92 | {/* Active when login is implemented */} 93 | {firstName && ( 94 |
95 |
96 | 97 | 98 | {firstName} {lastName} 99 | 100 | 101 |
102 |
    103 | {links.map(({ name, url }, i) => ( 104 |
  • 105 | 106 | {name} 107 | 108 |
  • 109 | ))} 110 |
  • 111 | Sign out 112 |
  • 113 |
114 |
115 |
116 |
117 | )} 118 |
119 | ) 120 | } 121 | 122 | export default Header 123 | -------------------------------------------------------------------------------- /pages/checkout/confirm.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Layout from '../../components/Layout' 3 | import { products } from '../../utils/products' 4 | 5 | const Confirm = () => { 6 | return ( 7 | 8 |
9 |
10 |
11 | Checkout 12 |

Confirm and Pay

13 |

{products.clothing.length} items

14 |
15 |
16 | {products.clothing.map((item) => { 17 | return ( 18 |
19 |
30 |

39 | {item.name} 40 |

41 |

42 | {item.priceCurrency} {item.price} 43 |

44 |
45 | ) 46 | })} 47 |
48 |
49 |
50 |
51 |

Shipping Information

52 |
53 |

Shipping Information

54 |

55 | user{' '} 56 | Prateek Gogia 57 |

58 |

59 | address 60 | 101, Holy Place India, Earth 61 |

62 |

63 | phone 64 | 408-857-1212 65 |

66 |
67 |
68 |
69 |

Subtotal

70 |

71 | ₹{' '} 72 | {products.clothing.reduce( 73 | (acc, item) => acc + parseFloat(item.price), 74 | 0 75 | )} 76 |

77 |
78 |
79 |

Shipping Charges

80 |

*Free*

81 |
82 |
83 |

Total price:

84 |

85 | ₹{' '} 86 | {products.clothing.reduce( 87 | (acc, item) => acc + parseFloat(item.price), 88 | 0 89 | )} 90 |

91 |
92 |
93 | 98 |
99 |
100 |
101 | 102 | ) 103 | } 104 | 105 | export default Confirm 106 | -------------------------------------------------------------------------------- /public/inspiration.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sass/components/_homepage.scss: -------------------------------------------------------------------------------- 1 | .homepage { 2 | padding: 0rem 3rem; 3 | display: flex; 4 | flex-direction: row; 5 | z-index: 5; 6 | 7 | h1 { 8 | font-size: 2rem; 9 | } 10 | 11 | ul { 12 | list-style: none; 13 | padding: 0; 14 | 15 | li { 16 | padding: 10px 0px; 17 | font-weight: 200; 18 | } 19 | } 20 | 21 | .category { 22 | position: absolute; 23 | top: 7rem; 24 | width: 100%; 25 | transition: all ease-in 0.5s; 26 | z-index: 3; 27 | 28 | .sticky-toolbar { 29 | position: fixed; 30 | background: #fff; 31 | z-index: 3; 32 | top: 4.5rem; 33 | height: 100px; 34 | box-sizing: border-box; 35 | margin-right: 20px; 36 | } 37 | 38 | .highlights { 39 | section.vertical-section { 40 | display: flex; 41 | flex-direction: column; 42 | justify-content: space-between; 43 | height: 350px; 44 | } 45 | section.horizontal-section { 46 | display: flex; 47 | flex-direction: row; 48 | justify-content: space-between; 49 | height: 350px; 50 | gap: 5%; 51 | } 52 | } 53 | 54 | .category-horizontal-card { 55 | height: 160px; 56 | width: 100%; 57 | display: flex; 58 | border-radius: 15px; 59 | } 60 | .category-vertical-card { 61 | height: 100%; 62 | width: 100%; 63 | border-radius: 15px; 64 | display: flex; 65 | align-items: flex-end; 66 | background-color: rgb(226, 224, 224); 67 | } 68 | } 69 | } 70 | 71 | @media (min-width: 1441px) and (max-width: 2560px) { 72 | .sticky-toolbar { 73 | left: 27.5rem; 74 | width: calc(100vw - 19%); 75 | } 76 | } 77 | 78 | @media (min-width: 1441px) and (max-width: 1920px) { 79 | .sticky-toolbar { 80 | left: 21rem; 81 | width: calc(100vw - 20%); 82 | } 83 | } 84 | 85 | @media (min-width: 1025px) and (max-width: 1440px) { 86 | .sticky-toolbar { 87 | left: 16.5rem; 88 | width: calc(100vw - 21.5%); 89 | } 90 | } 91 | 92 | @media (min-width: 769px) and (max-width: 1024px) { 93 | .homepage { 94 | h1 { 95 | font-size: 1.6rem; 96 | } 97 | .category { 98 | .sticky-toolbar { 99 | top: 4.45rem; 100 | left: 12.5rem; 101 | width: calc(100vw - 21.5%); 102 | } 103 | } 104 | } 105 | .toolbar { 106 | margin-right: 30px; 107 | } 108 | } 109 | 110 | @media (min-width: 426px) and (max-width: 768px) { 111 | .homepage { 112 | padding: 0rem 2rem; 113 | } 114 | .homepage h1 { 115 | font-size: 1.6rem; 116 | } 117 | .homepage .category { 118 | width: 100%; 119 | padding: 0 0 0 calc(100% - 82.5%); 120 | transition: all ease-in 0.3s; 121 | z-index: 3; 122 | .sticky-toolbar { 123 | padding: 0 2rem 0 0; 124 | } 125 | } 126 | .toolbar { 127 | transform: translateY(5px); 128 | } 129 | } 130 | 131 | // For all mobile screens 132 | @media screen and (max-width: 425px) { 133 | .homepage { 134 | padding: 0 20px; 135 | transform: translateY(2rem); 136 | display: flex; 137 | flex-direction: column; 138 | h1 { 139 | font-size: 1.5rem; 140 | } 141 | .category { 142 | padding: 0; 143 | top: 2rem; 144 | 145 | .highlights { 146 | padding: 0 0 3rem 0; 147 | 148 | section.vertical-section { 149 | display: flex; 150 | flex-direction: column; 151 | justify-content: space-between; 152 | height: 350px; 153 | } 154 | section.horizontal-section { 155 | display: flex; 156 | flex-direction: column; 157 | justify-content: space-between; 158 | height: 850px; 159 | } 160 | } 161 | 162 | .toolbar { 163 | flex-wrap: wrap-reverse; 164 | padding: 1rem 0; 165 | width: 100%; 166 | } 167 | 168 | .sticky-toolbar { 169 | position: fixed; 170 | width: 100%; 171 | left: 0; 172 | bottom: 0; 173 | padding: 0; 174 | h1 { 175 | font-size: 1.5rem; 176 | } 177 | } 178 | 179 | .content { 180 | top: 2rem; 181 | } 182 | 183 | .category-vertical-card { 184 | background-size: cover; 185 | background-position: 100% 100%; 186 | height: 400px; 187 | width: 100%; 188 | } 189 | } 190 | } 191 | .toolbar { 192 | width: 100%; 193 | background: #fff; 194 | position: fixed; 195 | bottom: 0; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /components/Detail.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | const specifications = [ 4 | { heading: 'Type', description: 'Sneakers' }, 5 | { heading: 'Toe Shape', description: 'Round Toe' }, 6 | { heading: 'Pattern', description: 'Colourblocked' }, 7 | { heading: 'Fastening', description: 'Lace-Ups' }, 8 | { heading: 'Shoe Width', description: 'Wide' }, 9 | { heading: 'Ankle Height', description: 'Regular' }, 10 | { heading: 'Insole', description: 'Comfort Insole' }, 11 | { heading: 'Sole Material', description: 'Rubber' }, 12 | { heading: 'Micro Trend', description: 'Contrast Sole' }, 13 | { heading: 'Warranty', description: '3 months' }, 14 | ] 15 | 16 | const Detail = ({ brand, name, price, priceCurrency }) => { 17 | const [quantity, setQuantity] = useState(0) 18 | 19 | return ( 20 |
21 |
22 |

{brand}

23 |

{name}

24 |

ID: 250900097

25 |
26 |

27 | {priceCurrency} {price} 28 |

29 |
30 | Sizes 31 | 38 |
39 |
40 |
41 |
42 | 43 | {/* Will add this in Cart Menu */} 44 | {/* */} 45 | 46 |
47 | 48 | {quantity} 49 | 50 |
51 |
52 |
53 |

Product Details

54 | 55 |
56 |

Special technologies

57 |

Responsive Boost midsole Lightweight, supportive feel

58 |
59 | 60 |
61 |

62 | About ADIDAS Original Men Nite Jogger Sneakers 63 |

64 |

65 | The first Nite Jogger debuted in 1980, when recreational running 66 | was becoming popular. True to their origins, these shoes flash 67 | reflective details. Made of a mix of nylon, mesh and leather, they 68 | show off wintry silver hues. Boost cushioning adds a responsive, 69 | energy-returning feel. 70 |

71 |
72 | 73 |
74 |

Product design details

75 |

76 | Main materials: mesh and synthetic upper/ textile lining/ rubber 77 | outsole Brand colour: -ROYBLU/GREONE/FTWWHT Lace-up closure 78 | Colourblocked upper Warranty: 3 months Warranty provided by brand/ 79 | manufacturer 80 |

81 |
82 | 83 |
84 |

Material & Care

85 |

Mesh and synthetic Wipe with a clean, dry cloth when needed

86 |
87 | 88 |
89 |

Specifications

90 |
91 | {specifications.map(({ heading, description }, index) => { 92 | return ( 93 |
94 |

95 | {heading} 96 |

97 |

{description}

98 |
99 | ) 100 | })} 101 |
102 |
103 |
104 |
105 |
106 | ) 107 | } 108 | 109 | export default Detail 110 | -------------------------------------------------------------------------------- /public/shoes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sass/components/_header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | padding: 0 3rem; 3 | position: fixed; 4 | top: 0; 5 | left: 0px; 6 | right: 0; 7 | background: #fff; 8 | z-index: 7; 9 | 10 | .logo { 11 | padding: 10px 0px; 12 | cursor: pointer; 13 | img { 14 | width: 9rem; 15 | } 16 | } 17 | .cart { 18 | margin-right: 5px; 19 | } 20 | } 21 | 22 | .hello-name { 23 | display: flex; 24 | align-items: center; 25 | 26 | img { 27 | border-radius: 50%; 28 | margin: 0px 10px; 29 | } 30 | 31 | .name { 32 | margin-left: 5px; 33 | } 34 | 35 | .profile-menu { 36 | visibility: hidden; 37 | position: absolute; 38 | top: 50px; 39 | right: 45px; 40 | background: #fff; 41 | width: 178px; 42 | box-sizing: border-box; 43 | border: 0.5px solid gray; 44 | border-radius: 15px; 45 | transform: translateY(0%); 46 | transition: all ease-in-out 0.3s; 47 | 48 | ul { 49 | padding: 0; 50 | list-style: none; 51 | li { 52 | padding: 10px; 53 | font-size: 14px; 54 | 55 | a { 56 | text-decoration: none; 57 | } 58 | } 59 | } 60 | } 61 | 62 | &:hover { 63 | cursor: pointer; 64 | .profile-menu { 65 | visibility: visible; 66 | transform: translateY(1em); 67 | } 68 | } 69 | } 70 | 71 | @media (min-width: 769px) and (max-width: 1024px) { 72 | .header { 73 | .logo { 74 | img { 75 | width: 8rem; 76 | } 77 | } 78 | } 79 | } 80 | 81 | @media (min-width: 426px) and (max-width: 768px) { 82 | .header { 83 | padding: 0 2rem; 84 | .logo { 85 | img { 86 | width: 7rem; 87 | } 88 | } 89 | } 90 | .profile-menu { 91 | right: 30px; 92 | } 93 | } 94 | 95 | @media screen and (max-width: 425px) { 96 | .header { 97 | padding: 1rem; 98 | align-items: center; 99 | } 100 | 101 | .hello-name { 102 | display: flex; 103 | align-items: center; 104 | width: 100%; 105 | cursor: none; 106 | 107 | img { 108 | border-radius: 50%; 109 | margin: 0px 10px; 110 | height: 50px; 111 | } 112 | 113 | .name { 114 | margin-left: 5px; 115 | } 116 | 117 | .profile-menu { 118 | position: absolute; 119 | opacity: 0; 120 | width: 0%; 121 | height: 120vh; 122 | top: 0; 123 | left: 0; 124 | background: #fff; 125 | box-sizing: border-box; 126 | border: 0; 127 | border-radius: 0px; 128 | transform: translateY(0em); 129 | transition: all ease-in-out 0.4s; 130 | box-shadow: 5px 0px 5px 0px #e4e4e4; 131 | 132 | ul { 133 | padding: 0; 134 | list-style: none; 135 | min-width: 200px; 136 | width: 100%; 137 | transform: translate(0, 3rem); 138 | li { 139 | padding: 20px 30px; 140 | font-size: 16px; 141 | } 142 | } 143 | } 144 | } 145 | 146 | .hello-name:hover { 147 | cursor: none; 148 | .profile-menu { 149 | opacity: 1; 150 | width: 80%; 151 | visibility: visible; 152 | transform: translateY(0em); 153 | animation-duration: 1.5s; 154 | animation-name: menu-enlarge; 155 | } 156 | img { 157 | z-index: 1; 158 | animation-duration: 2s; 159 | animation-name: image-slide-in; 160 | transform: translate(245px); 161 | } 162 | span { 163 | z-index: 1; 164 | transform: translateX(-45%); 165 | // animation-delay: 3s; 166 | animation-duration: 2s; 167 | animation-name: show-name; 168 | visibility: visible; 169 | } 170 | .cart { 171 | display: none; 172 | } 173 | } 174 | 175 | @keyframes image-slide-in { 176 | 0% { 177 | transform: translate(0); 178 | } 179 | 180 | 100% { 181 | transform: translate(245px); 182 | } 183 | } 184 | } 185 | 186 | @media screen and (max-width: 325px) { 187 | .hello-name:hover { 188 | img { 189 | z-index: 1; 190 | animation-duration: 2s; 191 | animation-name: image-slide-in; 192 | transform: translate(170px); 193 | } 194 | } 195 | @keyframes image-slide-in { 196 | 0% { 197 | transform: translate(0); 198 | } 199 | 200 | 100% { 201 | transform: translate(170px); 202 | } 203 | } 204 | } 205 | 206 | @media (min-width: 326px) and (max-width: 375px) { 207 | .hello-name:hover { 208 | img { 209 | z-index: 1; 210 | animation-duration: 2s; 211 | animation-name: image-slide-in; 212 | transform: translate(210px); 213 | } 214 | } 215 | @keyframes image-slide-in { 216 | 0% { 217 | transform: translate(0); 218 | } 219 | 220 | 100% { 221 | transform: translate(210px); 222 | } 223 | } 224 | } 225 | 226 | @keyframes menu-enlarge { 227 | 0% { 228 | width: 0%; 229 | } 230 | 231 | 100% { 232 | width: 80%; 233 | } 234 | } 235 | 236 | @keyframes show-name { 237 | 0% { 238 | z-index: 0; 239 | visibility: hidden; 240 | transform: translateX(0); 241 | } 242 | 243 | 100% { 244 | visibility: visible; 245 | z-index: 1; 246 | transform: translateX(-45%); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /sass/components/_cart.scss: -------------------------------------------------------------------------------- 1 | .close-icon { 2 | background: url('/close.svg'); 3 | } 4 | .zero-open { 5 | height: 0vh; 6 | transition: all 0.1s ease-in-out; 7 | .cart-footer { 8 | height: 0vh; 9 | } 10 | } 11 | .half-open { 12 | height: 50vh; 13 | transition: all 0.1s ease-in-out; 14 | .cart-size { 15 | background: url('/fullscreen.svg'); 16 | } 17 | } 18 | .full-open { 19 | height: 90vh; 20 | transition: all 0.1s ease-in-out; 21 | .cart-size { 22 | background: url('/fullscreen-exit.svg'); 23 | } 24 | } 25 | .cart-container { 26 | position: fixed; 27 | overflow: hidden; 28 | width: 100vw; 29 | z-index: 3; 30 | bottom: 0; 31 | border-radius: 25px 25px 0 0; 32 | box-shadow: 0 0 2px #777; 33 | // animation-name: cart; 34 | animation-duration: 0.5s; 35 | 36 | .cart { 37 | position: absolute; 38 | z-index: 3; 39 | background-color: #fff; 40 | box-shadow: 3px 3px 3px 2px #777; 41 | height: 100%; 42 | width: 100%; 43 | padding: 0 5px; 44 | transition: all 0.5s ease-in-out; 45 | 46 | .cart-header { 47 | padding: 0 40px; 48 | 49 | h1 { 50 | width: 140px; 51 | padding-bottom: 20px; 52 | } 53 | 54 | .cart-icon { 55 | height: 3rem; 56 | width: 3rem; 57 | background-size: 2rem; 58 | background-position: center; 59 | background-repeat: no-repeat; 60 | } 61 | } 62 | 63 | .cart-items-container { 64 | max-height: calc(100% - 12rem); 65 | overflow-y: scroll; 66 | padding-right: 50px; 67 | padding-left: 35px; 68 | 69 | .items { 70 | max-height: 100%; 71 | overflow-y: auto; 72 | width: 100%; 73 | padding-right: 40px; 74 | 75 | /* Let's get this party started */ 76 | &::-webkit-scrollbar { 77 | width: 12px; 78 | cursor: pointer; 79 | } 80 | 81 | /* Track */ 82 | &::-webkit-scrollbar-track { 83 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 84 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 85 | -webkit-border-radius: 10px; 86 | border-radius: 10px; 87 | height: 20px; 88 | } 89 | 90 | /* Handle */ 91 | &::-webkit-scrollbar-thumb { 92 | -webkit-border-radius: 10px; 93 | border-radius: 10px; 94 | background-color: purple; 95 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5); 96 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5); 97 | } 98 | &::-webkit-scrollbar-thumb:window-inactive { 99 | background-color: purple; 100 | } 101 | 102 | .item { 103 | display: flex; 104 | justify-content: space-between; 105 | margin: 10px; 106 | box-shadow: 0px 0px 1px #777; 107 | border-radius: 15px; 108 | max-height: 150px; 109 | 110 | .description { 111 | margin: 0 10px; 112 | overflow: hidden; 113 | 114 | .name { 115 | white-space: normal; 116 | text-overflow: ellipsis; 117 | max-width: 80%; 118 | } 119 | .brand { 120 | font-weight: 700; 121 | } 122 | 123 | .volume { 124 | color: gray; 125 | font-size: 14px; 126 | align-items: center; 127 | } 128 | } 129 | .price { 130 | font-size: 20px; 131 | font-weight: 700; 132 | color: purple; 133 | } 134 | } 135 | } 136 | } 137 | .cart-footer { 138 | position: fixed; 139 | bottom: 0rem; 140 | right: 0rem; 141 | width: 100%; 142 | background: #fff; 143 | .total-price { 144 | font-size: 20px; 145 | } 146 | .extra-info { 147 | font-size: 12px; 148 | } 149 | } 150 | } 151 | } 152 | 153 | @keyframes cart { 154 | 0% { 155 | height: 0vh; 156 | } 157 | 100% { 158 | height: 50vh; 159 | } 160 | } 161 | 162 | @media screen and (max-width: 425px) { 163 | .cart-container { 164 | animation-duration: 0.1s; 165 | .cart { 166 | width: auto !important; 167 | top: 4rem; 168 | 169 | h1 { 170 | padding: 0 20px; 171 | } 172 | 173 | .cart-header { 174 | padding: 0; 175 | } 176 | 177 | .cart-items-container { 178 | max-height: calc(100% - 15rem); 179 | overflow-y: scroll; 180 | 181 | .items { 182 | padding: 0; 183 | .item { 184 | .price { 185 | margin-top: 0; 186 | font-size: 18px; 187 | } 188 | .description { 189 | .name { 190 | max-width: 100%; 191 | } 192 | } 193 | } 194 | } 195 | } 196 | .cart-footer { 197 | padding: 0 20px; 198 | } 199 | } 200 | } 201 | 202 | .half-open { 203 | height: 100vh; 204 | .cart-size { 205 | display: none; 206 | } 207 | } 208 | .full-open { 209 | height: 100vh; 210 | .cart-size { 211 | display: none; 212 | } 213 | } 214 | 215 | @keyframes cart { 216 | 0% { 217 | height: 0vh; 218 | } 219 | 100% { 220 | height: 100vh; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /public/activewear.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Newin.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import BannerCard from './BannerCard' 3 | 4 | const horizontalImageStyle = { 5 | height: '100%', 6 | width: '100%', 7 | maxWidth: '160px', 8 | right: 10, 9 | backgroundSize: 'cover', 10 | backgroundPosition: '0px 0px', 11 | borderRadius: '15px', 12 | backgroundRepeat: 'no-repeat', 13 | } 14 | 15 | const Newin = () => { 16 | return ( 17 |
18 |
19 |
20 | 30 | 50 |
51 |
52 | 73 | 93 |
94 |
95 | 96 |
97 |
98 |
99 | 118 | 135 |
136 |
137 | 159 | 180 |
181 |
182 |
183 |
184 | ) 185 | } 186 | 187 | export default Newin 188 | -------------------------------------------------------------------------------- /sass/components/_layout.scss: -------------------------------------------------------------------------------- 1 | // display 2 | .d-flex { 3 | display: flex; 4 | } 5 | 6 | .d-grid { 7 | display: grid; 8 | } 9 | 10 | .d-block { 11 | display: block; 12 | } 13 | 14 | .d-none { 15 | display: none; 16 | } 17 | 18 | // visibility 19 | .v-hidden { 20 | visibility: hidden; 21 | } 22 | 23 | .v-visible { 24 | visibility: visible; 25 | } 26 | 27 | // cursor 28 | 29 | .cursor-pointer { 30 | cursor: pointer; 31 | } 32 | 33 | .flex-row { 34 | flex-direction: row; 35 | } 36 | 37 | .flex-column { 38 | flex-direction: column; 39 | } 40 | 41 | .flex-wrap { 42 | flex-wrap: wrap; 43 | } 44 | 45 | // justify-content 46 | .justify-content-between { 47 | justify-content: space-between; 48 | } 49 | 50 | .justify-content-center { 51 | justify-content: center; 52 | } 53 | 54 | .justify-content-end { 55 | justify-content: flex-end; 56 | } 57 | 58 | // align-items 59 | .align-items-center { 60 | align-items: center; 61 | } 62 | .align-items-end { 63 | align-items: flex-end; 64 | } 65 | 66 | //fonts 67 | .font-weight-200 { 68 | font-weight: 200; 69 | } 70 | .font-weight-300 { 71 | font-weight: 300; 72 | } 73 | .font-weight-400 { 74 | font-weight: 400; 75 | } 76 | .font-weight-600 { 77 | font-weight: 600; 78 | } 79 | .font-weight-700 { 80 | font-weight: 700; 81 | } 82 | .font-weight-800 { 83 | font-weight: 800; 84 | } 85 | 86 | .font-size-12 { 87 | font-size: 12px; 88 | } 89 | 90 | .font-size-14 { 91 | font-size: 14px; 92 | } 93 | 94 | .font-size-16 { 95 | font-size: 16px; 96 | } 97 | 98 | .font-size-18 { 99 | font-size: 18px; 100 | } 101 | 102 | .font-size-20 { 103 | font-size: 20px; 104 | } 105 | 106 | .font-size-22 { 107 | font-size: 22px; 108 | } 109 | 110 | //padding 111 | .p-0 { 112 | padding: 0; 113 | } 114 | .p-1 { 115 | padding: 1rem; 116 | } 117 | .p-2 { 118 | padding: 2rem; 119 | } 120 | .p-3 { 121 | padding: 3rem; 122 | } 123 | .p-4 { 124 | padding: 4rem; 125 | } 126 | .p-5 { 127 | padding: 5rem; 128 | } 129 | 130 | .px-1 { 131 | padding: 0 1rem; 132 | } 133 | .px-2 { 134 | padding: 0 2rem; 135 | } 136 | .px-3 { 137 | padding: 0 3rem; 138 | } 139 | .px-4 { 140 | padding: 0 4rem; 141 | } 142 | .px-5 { 143 | padding: 0 5rem; 144 | } 145 | .pl-0 { 146 | padding-left: 0; 147 | } 148 | .pr-0 { 149 | padding-right: 0; 150 | } 151 | 152 | //margin 153 | 154 | .m-0 { 155 | margin: 0; 156 | } 157 | .m-1 { 158 | margin: 1rem; 159 | } 160 | .m-2 { 161 | margin: 2rem; 162 | } 163 | .m-3 { 164 | margin: 3rem; 165 | } 166 | .m-4 { 167 | margin: 4rem; 168 | } 169 | .m-5 { 170 | margin: 5rem; 171 | } 172 | 173 | .mx-0 { 174 | margin: 0 0; 175 | } 176 | .mx-1 { 177 | margin: 0 1rem; 178 | } 179 | .mx-2 { 180 | margin: 0 2rem; 181 | } 182 | .mx-3 { 183 | margin: 0 3rem; 184 | } 185 | .mx-4 { 186 | margin: 0 4rem; 187 | } 188 | .mx-5 { 189 | margin: 0 5rem; 190 | } 191 | 192 | .my-0 { 193 | margin: 0 0; 194 | } 195 | .my-1 { 196 | margin: 1rem 0; 197 | } 198 | .my-2 { 199 | margin: 2rem 0; 200 | } 201 | .my-3 { 202 | margin: 3rem 0; 203 | } 204 | .my-4 { 205 | margin: 4rem 0; 206 | } 207 | .my-5 { 208 | margin: 5rem 0; 209 | } 210 | 211 | .mt-0 { 212 | margin-top: 0; 213 | } 214 | .mt-1 { 215 | margin-top: 1rem; 216 | } 217 | .mt-2 { 218 | margin-top: 2rem; 219 | } 220 | .mt-3 { 221 | margin-top: 3rem; 222 | } 223 | .mt-4 { 224 | margin-top: 4rem; 225 | } 226 | .mt-5 { 227 | margin-top: 5rem; 228 | } 229 | 230 | .mb-0 { 231 | margin-bottom: 0; 232 | } 233 | .mb-1 { 234 | margin-bottom: 1rem; 235 | } 236 | .mb-2 { 237 | margin-bottom: 2rem; 238 | } 239 | .mb-3 { 240 | margin-bottom: 3rem; 241 | } 242 | .mb-4 { 243 | margin-bottom: 4rem; 244 | } 245 | .mb-5 { 246 | margin-bottom: 5rem; 247 | } 248 | 249 | // width 250 | 251 | .w-10 { 252 | width: 10%; 253 | } 254 | 255 | .w-20 { 256 | width: 20%; 257 | } 258 | 259 | .w-30 { 260 | width: 30%; 261 | margin: 0px 10px; 262 | } 263 | 264 | .w-40 { 265 | width: 40%; 266 | } 267 | 268 | .w-50 { 269 | width: 50%; 270 | } 271 | 272 | .w-60 { 273 | width: 60%; 274 | } 275 | 276 | .w-70 { 277 | width: 70%; 278 | } 279 | 280 | .w-100 { 281 | width: 100%; 282 | } 283 | 284 | // text 285 | .text-center { 286 | text-align: center; 287 | } 288 | 289 | .text-muted { 290 | color: grey; 291 | } 292 | 293 | .text-uppercase { 294 | text-transform: uppercase; 295 | } 296 | 297 | // colored backgrounds 298 | 299 | .purple-background { 300 | background-color: rgb(250, 195, 250); 301 | position: relative; 302 | &::after { 303 | content: ''; 304 | position: absolute; 305 | height: 160px; 306 | width: 100%; 307 | background-size: contain; 308 | background-position: 100%; 309 | border-radius: 15px; 310 | background-repeat: no-repeat; 311 | } 312 | } 313 | 314 | .yellow-background { 315 | background-color: rgb(243, 211, 122); 316 | position: relative; 317 | 318 | &::before { 319 | content: ''; 320 | position: absolute; 321 | height: 160px; 322 | width: 100%; 323 | background-size: contain; 324 | background-position: 90%; 325 | background-repeat: no-repeat; 326 | } 327 | } 328 | 329 | .position-relative { 330 | position: relative; 331 | } 332 | .position-absolute { 333 | position: absolute; 334 | } 335 | 336 | .bg-purple { 337 | background-color: rgb(252, 233, 255); 338 | } 339 | 340 | .rounded { 341 | border-radius: 25px; 342 | } 343 | 344 | select { 345 | width: 100px; 346 | height: 50px; 347 | border-radius: 0px; 348 | padding: 10px; 349 | cursor: pointer; 350 | background-color: #fff; 351 | font-size: 14px; 352 | outline: none; 353 | } 354 | 355 | @media (min-width: 769px) and (max-width: 2560px) { 356 | .d-lg-none { 357 | display: none; 358 | } 359 | } 360 | 361 | @media (min-width: 426px) and (max-width: 768px) { 362 | .flex-md-column { 363 | flex-direction: column; 364 | } 365 | .w-md-100 { 366 | width: 100%; 367 | } 368 | .w-md-10 { 369 | width: 10%; 370 | } 371 | 372 | .w-md-20 { 373 | width: 20%; 374 | } 375 | 376 | .w-md-30 { 377 | width: 30%; 378 | margin: 0px 10px; 379 | } 380 | 381 | .w-md-40 { 382 | width: 40%; 383 | } 384 | 385 | .w-md-50 { 386 | width: 50%; 387 | } 388 | 389 | .w-md-60 { 390 | width: 60%; 391 | } 392 | 393 | .w-md-70 { 394 | width: 70%; 395 | } 396 | } 397 | 398 | @media screen and (max-width: 425px) { 399 | .flex-sm-column { 400 | flex-direction: column; 401 | } 402 | .align-items-sm-start { 403 | align-items: flex-start; 404 | } 405 | .d-sm-none { 406 | display: none; 407 | } 408 | .w-sm-100 { 409 | width: 100%; 410 | } 411 | 412 | .m-sm-0 { 413 | margin: 0; 414 | } 415 | .my-sm-1 { 416 | margin: 1rem 0; 417 | } 418 | .my-sm-2 { 419 | margin: 2rem 0; 420 | } 421 | .my-sm-3 { 422 | margin: 3rem 0; 423 | } 424 | .my-sm-4 { 425 | margin: 4rem 0; 426 | } 427 | .my-sm-5 { 428 | margin: 5rem 0; 429 | } 430 | 431 | .p-sm-0 { 432 | padding: 0; 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /sass/components/_button.scss: -------------------------------------------------------------------------------- 1 | button { 2 | transition: all ease-in-out 0.5s; 3 | } 4 | 5 | .toolbar-button { 6 | a { 7 | border-radius: 10px; 8 | box-shadow: none; 9 | height: 50px; 10 | border: 0; 11 | font-weight: 700; 12 | font-size: 14px; 13 | margin: 10px; 14 | cursor: pointer; 15 | outline: none; 16 | background: #fff; 17 | position: relative; 18 | transition: all 0.3s ease-in-out; 19 | color: black; 20 | text-decoration: none; 21 | 22 | &:hover { 23 | background-color: rgb(138, 138, 218); 24 | color: white; 25 | } 26 | } 27 | span.content { 28 | margin: 10px; 29 | } 30 | .popup-menu { 31 | display: none; 32 | } 33 | 34 | &:active, 35 | &:hover, 36 | &:focus-within { 37 | .popup-menu { 38 | display: block; 39 | position: absolute; 40 | top: 5rem; 41 | min-height: 50px; 42 | min-width: 70px; 43 | background: #fff; 44 | padding: 20px; 45 | border-radius: 15px; 46 | } 47 | } 48 | 49 | a.home { 50 | display: flex; 51 | align-items: center; 52 | justify-content: space-between; 53 | 54 | &::before { 55 | content: ''; 56 | height: 20px; 57 | width: 20px; 58 | background-size: contain; 59 | background-image: url('/home.svg'); 60 | transform: scaleX(-1); 61 | } 62 | 63 | span.icon { 64 | display: none; 65 | } 66 | 67 | &:hover { 68 | background-color: #fff; 69 | color: black; 70 | } 71 | } 72 | 73 | a.cart { 74 | display: flex; 75 | align-items: center; 76 | justify-content: space-between; 77 | width: auto; 78 | &::before { 79 | content: ''; 80 | height: 20px; 81 | width: 20px; 82 | background-size: contain; 83 | background-image: url('/close.svg'); 84 | transform: scaleX(-1); 85 | } 86 | span.content { 87 | width: auto; 88 | opacity: 1; 89 | } 90 | 91 | span.icon { 92 | opacity: 1; 93 | } 94 | 95 | &:hover, 96 | &:active { 97 | transition: width 0.5s ease-in-out; 98 | } 99 | } 100 | 101 | a.female { 102 | display: flex; 103 | align-items: center; 104 | justify-content: space-between; 105 | 106 | span.icon { 107 | content: ''; 108 | height: 20px; 109 | width: 20px; 110 | background-size: contain; 111 | background-image: url('https://cdn.iconscout.com/icon/free/png-256/female-symbol-1438487-1213996.png'); 112 | } 113 | 114 | span.content { 115 | width: 0px; 116 | opacity: 0; 117 | } 118 | 119 | &:hover, 120 | &:focus, 121 | &:active { 122 | background-color: rgb(240, 165, 215); 123 | width: 120px; 124 | transition: width 0.5s ease-in-out; 125 | 126 | span.icon { 127 | opacity: 1; 128 | } 129 | 130 | span.content { 131 | width: auto; 132 | opacity: 1; 133 | } 134 | } 135 | } 136 | a.male { 137 | display: flex; 138 | align-items: center; 139 | justify-content: space-between; 140 | 141 | &::before { 142 | content: ''; 143 | height: 20px; 144 | width: 20px; 145 | background-size: contain; 146 | background-image: url('https://cdn.iconscout.com/icon/free/png-256/male-symbol-1438497-1214006.png'); 147 | } 148 | 149 | span.content { 150 | width: 0px; 151 | opacity: 0; 152 | } 153 | 154 | &:hover { 155 | background-color: rgb(120, 225, 248); 156 | width: 120px; 157 | transition: width 0.5s ease-in-out; 158 | 159 | span.icon { 160 | opacity: 1; 161 | } 162 | 163 | span.content { 164 | width: auto; 165 | opacity: 1; 166 | } 167 | } 168 | } 169 | a.sort { 170 | display: flex; 171 | align-items: center; 172 | justify-content: space-between; 173 | width: auto; 174 | 175 | &::before { 176 | content: ''; 177 | height: 20px; 178 | width: 20px; 179 | background-size: contain; 180 | background-image: url('https://cdn.iconscout.com/icon/free/png-256/sort-1780015-1518405.png'); 181 | transform: scaleX(-1); 182 | } 183 | 184 | span.content { 185 | width: auto; 186 | opacity: 1; 187 | } 188 | 189 | span.icon { 190 | opacity: 1; 191 | } 192 | 193 | &:hover, 194 | &:active { 195 | transition: width 0.5s ease-in-out; 196 | } 197 | } 198 | a.filter { 199 | display: flex; 200 | align-items: center; 201 | justify-content: space-between; 202 | 203 | span.icon { 204 | display: block; 205 | height: 25px; 206 | width: 25px; 207 | border-radius: 50%; 208 | background-color: black; 209 | color: white; 210 | line-height: 1.8; 211 | } 212 | 213 | span.content { 214 | width: 0px; 215 | opacity: 0; 216 | } 217 | 218 | &:hover, 219 | &:active { 220 | width: 120px; 221 | transition: width 0.5s ease-in-out; 222 | 223 | span.icon { 224 | opacity: 1; 225 | } 226 | 227 | span.content { 228 | width: auto; 229 | opacity: 1; 230 | } 231 | } 232 | } 233 | } 234 | 235 | .transaction { 236 | display: flex; 237 | justify-content: space-evenly; 238 | align-items: center; 239 | height: 50px; 240 | width: 160px; 241 | border-radius: 15px; 242 | background-color: rgb(250, 217, 27); 243 | color: black; 244 | font-size: 14px; 245 | font-weight: 700; 246 | transition: all ease-in-out 0.5s; 247 | cursor: pointer; 248 | outline: none; 249 | 250 | &:hover { 251 | background-color: purple; 252 | color: white; 253 | } 254 | } 255 | 256 | a.transaction.add-to-cart { 257 | height: 45px; 258 | width: 200px; 259 | border-radius: 10px; 260 | color: rgb(56, 23, 56); 261 | background-color: white; 262 | font-size: 18px; 263 | transition: all ease-in-out 0.5s; 264 | cursor: pointer; 265 | outline: none; 266 | border: 0.5px solid rgb(56, 23, 56); 267 | 268 | &:hover { 269 | background-color: purple; 270 | color: white; 271 | } 272 | } 273 | 274 | .button-group { 275 | display: flex; 276 | justify-content: space-between; 277 | align-items: center; 278 | font-size: 16px; 279 | 280 | button { 281 | height: 30px; 282 | width: 30px; 283 | border-radius: 50%; 284 | font-size: 16px; 285 | cursor: pointer; 286 | outline: none; 287 | background-color: rgb(56, 23, 56); 288 | color: #fff; 289 | border: 0; 290 | 291 | &:hover { 292 | background-color: #fff; 293 | color: black; 294 | border: 1px solid rgb(56, 23, 56); 295 | } 296 | } 297 | 298 | span { 299 | width: 40px; 300 | text-align: center; 301 | } 302 | } 303 | 304 | @media screen and (max-width: 425px) { 305 | a.transaction.buynow, 306 | a.transaction.add-to-cart { 307 | width: 120px; 308 | } 309 | } 310 | 311 | @media (min-width: 769px) and (max-width: 1024px) { 312 | .toolbar-button { 313 | a { 314 | height: 50px; 315 | } 316 | } 317 | } 318 | 319 | @media (min-width: 426px) and (max-width: 768px) { 320 | .toolbar-button { 321 | a { 322 | height: 50px; 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /lib/withApollo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Head from 'next/head' 3 | import { ApolloClient } from 'apollo-client' 4 | import { InMemoryCache, NormalizedCacheObject } from 'apollo-cache-inmemory' 5 | import { HttpLink } from 'apollo-link-http' 6 | import { setContext } from 'apollo-link-context' 7 | import fetch from 'isomorphic-unfetch' 8 | import { TokenRefreshLink } from 'apollo-link-token-refresh' 9 | import jwtDecode from 'jwt-decode' 10 | import { getAccessToken, setAccessToken } from './accessToken' 11 | import { onError } from 'apollo-link-error' 12 | import { ApolloLink } from 'apollo-link' 13 | import cookie from 'cookie' 14 | 15 | const isServer = () => typeof window === 'undefined' 16 | 17 | /** 18 | * Creates and provides the apolloContext 19 | * to a next.js PageTree. Use it by wrapping 20 | * your PageComponent via HOC pattern. 21 | * @param {Function|Class} PageComponent 22 | * @param {Object} [config] 23 | * @param {Boolean} [config.ssr=true] 24 | */ 25 | export function withApollo(PageComponent: any, { ssr = true } = {}) { 26 | const WithApollo = ({ 27 | apolloClient, 28 | serverAccessToken, 29 | apolloState, 30 | ...pageProps 31 | }: any) => { 32 | if (!isServer() && !getAccessToken()) { 33 | setAccessToken(serverAccessToken) 34 | } 35 | const client = apolloClient || initApolloClient(apolloState) 36 | return 37 | } 38 | 39 | if (process.env.NODE_ENV !== 'production') { 40 | // Find correct display name 41 | const displayName = 42 | PageComponent.displayName || PageComponent.name || 'Component' 43 | 44 | // Warn if old way of installing apollo is used 45 | if (displayName === 'App') { 46 | console.warn('This withApollo HOC only works with PageComponents.') 47 | } 48 | 49 | // Set correct display name for devtools 50 | WithApollo.displayName = `withApollo(${displayName})` 51 | } 52 | 53 | if (ssr || PageComponent.getInitialProps) { 54 | WithApollo.getInitialProps = async (ctx: any) => { 55 | const { 56 | AppTree, 57 | ctx: { req, res }, 58 | } = ctx 59 | 60 | let serverAccessToken = '' 61 | 62 | if (isServer()) { 63 | try { 64 | const cookies = cookie.parse(req.headers.cookie) 65 | if (cookies.jid) { 66 | const response = await fetch( 67 | `${process.env.BACKEND}/refresh_token`, 68 | { 69 | method: 'POST', 70 | credentials: 'include', 71 | headers: { 72 | cookie: 'jid=' + cookies.jid, 73 | }, 74 | } 75 | ) 76 | const data = await response.json() 77 | serverAccessToken = data.accessToken 78 | } 79 | } catch (err) { 80 | console.log(err, 'err') 81 | } 82 | } 83 | 84 | // Run all GraphQL queries in the component tree 85 | // and extract the resulting data 86 | const apolloClient = (ctx.ctx.apolloClient = initApolloClient( 87 | {}, 88 | serverAccessToken 89 | )) 90 | 91 | const pageProps = PageComponent.getInitialProps 92 | ? await PageComponent.getInitialProps(ctx) 93 | : {} 94 | 95 | // Only on the server 96 | if (typeof window === 'undefined') { 97 | // When redirecting, the response is finished. 98 | // No point in continuing to render 99 | if (res && res.finished) { 100 | return {} 101 | } 102 | 103 | if (ssr) { 104 | try { 105 | // Run all GraphQL queries 106 | const { getDataFromTree } = await import('@apollo/react-ssr') 107 | await getDataFromTree( 108 | 115 | ) 116 | } catch (error) { 117 | // Prevent Apollo Client GraphQL errors from crashing SSR. 118 | // Handle them in components via the data.error prop: 119 | // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error 120 | console.error('Error while running `getDataFromTree`', error) 121 | } 122 | } 123 | 124 | // getDataFromTree does not call componentWillUnmount 125 | // head side effect therefore need to be cleared manually 126 | Head.rewind() 127 | } 128 | 129 | // Extract query data from the Apollo store 130 | const apolloState = apolloClient.cache.extract() 131 | 132 | return { 133 | ...pageProps, 134 | apolloState, 135 | serverAccessToken, 136 | } 137 | } 138 | } 139 | 140 | return WithApollo 141 | } 142 | 143 | let apolloClient: ApolloClient | null = null 144 | 145 | /** 146 | * Always creates a new apollo client on the server 147 | * Creates or reuses apollo client in the browser. 148 | */ 149 | function initApolloClient(initState: any, serverAccessToken?: string) { 150 | // Make sure to create a new client for every server-side request so that data 151 | // isn't shared between connections (which would be bad) 152 | if (isServer()) { 153 | return createApolloClient(initState, serverAccessToken) 154 | } 155 | 156 | // Reuse client on the client-side 157 | if (!apolloClient) { 158 | // setAccessToken(cookie.parse(document.cookie).test); 159 | apolloClient = createApolloClient(initState) 160 | } 161 | 162 | return apolloClient 163 | } 164 | 165 | /** 166 | * Creates and configures the ApolloClient 167 | * @param {Object} [initialState={}] 168 | * @param {Object} config 169 | */ 170 | function createApolloClient(initialState = {}, serverAccessToken?: string) { 171 | const httpLink = new HttpLink({ 172 | uri: 'http://localhost:4000/graphql', 173 | credentials: 'include', 174 | fetch, 175 | }) 176 | 177 | const refreshLink = new TokenRefreshLink({ 178 | accessTokenField: 'accessToken', 179 | isTokenValidOrUndefined: () => { 180 | const token = getAccessToken() 181 | 182 | if (!token) { 183 | return true 184 | } 185 | 186 | try { 187 | const { exp } = jwtDecode(token) 188 | if (Date.now() >= exp * 1000) { 189 | return false 190 | } else { 191 | return true 192 | } 193 | } catch { 194 | return false 195 | } 196 | }, 197 | fetchAccessToken: () => { 198 | return fetch('http://localhost:4000/refresh_token', { 199 | method: 'POST', 200 | credentials: 'include', 201 | }) 202 | }, 203 | handleFetch: (accessToken) => { 204 | setAccessToken(accessToken) 205 | }, 206 | handleError: (err) => { 207 | console.warn('Your refresh token is invalid. Try to relogin') 208 | console.error(err) 209 | }, 210 | }) 211 | 212 | const authLink = setContext((_request, { headers }) => { 213 | const token = isServer() ? serverAccessToken : getAccessToken() 214 | return { 215 | headers: { 216 | ...headers, 217 | authorization: token ? `bearer ${token}` : '', 218 | }, 219 | } 220 | }) 221 | 222 | const errorLink = onError(({ graphQLErrors, networkError }) => { 223 | console.log(graphQLErrors) 224 | console.log(networkError) 225 | }) 226 | 227 | return new ApolloClient({ 228 | ssrMode: typeof window === 'undefined', // Disables forceFetch on the server (so queries are only run once) 229 | link: ApolloLink.from([refreshLink as any, authLink, errorLink, httpLink]), 230 | cache: new InMemoryCache().restore(initialState), 231 | }) 232 | } 233 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /graphql/generated/graphql.tsx: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | import * as ApolloReactCommon from '@apollo/react-common' 3 | import * as ApolloReactHooks from '@apollo/react-hooks' 4 | export type Maybe = T | null 5 | /** All built-in and custom scalars, mapped to their actual values */ 6 | export type Scalars = { 7 | ID: string 8 | String: string 9 | Boolean: boolean 10 | Int: number 11 | Float: number 12 | } 13 | 14 | export type Query = { 15 | __typename?: 'Query' 16 | hello: Scalars['String'] 17 | profile: User 18 | Users: Array 19 | } 20 | 21 | export type User = { 22 | __typename?: 'User' 23 | id: Scalars['Int'] 24 | firstName: Scalars['String'] 25 | lastName: Scalars['String'] 26 | email: Scalars['String'] 27 | } 28 | 29 | export type Mutation = { 30 | __typename?: 'Mutation' 31 | revokeRefreshTokensForUser: Scalars['Boolean'] 32 | logout: Scalars['Boolean'] 33 | register: Scalars['Boolean'] 34 | login: LoginResponse 35 | } 36 | 37 | export type MutationRevokeRefreshTokensForUserArgs = { 38 | userId: Scalars['Int'] 39 | } 40 | 41 | export type MutationRegisterArgs = { 42 | password: Scalars['String'] 43 | email: Scalars['String'] 44 | lastName?: Maybe 45 | firstName: Scalars['String'] 46 | } 47 | 48 | export type MutationLoginArgs = { 49 | password: Scalars['String'] 50 | email: Scalars['String'] 51 | } 52 | 53 | export type LoginResponse = { 54 | __typename?: 'LoginResponse' 55 | accessToken: Scalars['String'] 56 | user: User 57 | } 58 | 59 | export type RegisterMutationVariables = { 60 | firstName: Scalars['String'] 61 | lastName?: Maybe 62 | email: Scalars['String'] 63 | password: Scalars['String'] 64 | } 65 | 66 | export type RegisterMutation = { __typename?: 'Mutation' } & Pick< 67 | Mutation, 68 | 'register' 69 | > 70 | 71 | export type LoginMutationVariables = { 72 | email: Scalars['String'] 73 | password: Scalars['String'] 74 | } 75 | 76 | export type LoginMutation = { __typename?: 'Mutation' } & { 77 | login: { __typename?: 'LoginResponse' } & Pick< 78 | LoginResponse, 79 | 'accessToken' 80 | > & { 81 | profile: { __typename?: 'User' } & Pick< 82 | User, 83 | 'id' | 'firstName' | 'lastName' | 'email' 84 | > 85 | } 86 | } 87 | 88 | export type LogoutMutationVariables = {} 89 | 90 | export type LogoutMutation = { __typename?: 'Mutation' } & Pick< 91 | Mutation, 92 | 'logout' 93 | > 94 | 95 | export type ProfileQueryVariables = {} 96 | 97 | export type ProfileQuery = { __typename?: 'Query' } & { 98 | profile: { __typename?: 'User' } & Pick< 99 | User, 100 | 'id' | 'firstName' | 'lastName' | 'email' 101 | > 102 | } 103 | 104 | export const RegisterDocument = gql` 105 | mutation register( 106 | $firstName: String! 107 | $lastName: String 108 | $email: String! 109 | $password: String! 110 | ) { 111 | register( 112 | firstName: $firstName 113 | lastName: $lastName 114 | email: $email 115 | password: $password 116 | ) 117 | } 118 | ` 119 | export type RegisterMutationFn = ApolloReactCommon.MutationFunction< 120 | RegisterMutation, 121 | RegisterMutationVariables 122 | > 123 | 124 | /** 125 | * __useRegisterMutation__ 126 | * 127 | * To run a mutation, you first call `useRegisterMutation` within a React component and pass it any options that fit your needs. 128 | * When your component renders, `useRegisterMutation` returns a tuple that includes: 129 | * - A mutate function that you can call at any time to execute the mutation 130 | * - An object with fields that represent the current status of the mutation's execution 131 | * 132 | * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; 133 | * 134 | * @example 135 | * const [registerMutation, { data, loading, error }] = useRegisterMutation({ 136 | * variables: { 137 | * firstName: // value for 'firstName' 138 | * lastName: // value for 'lastName' 139 | * email: // value for 'email' 140 | * password: // value for 'password' 141 | * }, 142 | * }); 143 | */ 144 | export function useRegisterMutation( 145 | baseOptions?: ApolloReactHooks.MutationHookOptions< 146 | RegisterMutation, 147 | RegisterMutationVariables 148 | > 149 | ) { 150 | return ApolloReactHooks.useMutation< 151 | RegisterMutation, 152 | RegisterMutationVariables 153 | >(RegisterDocument, baseOptions) 154 | } 155 | export type RegisterMutationHookResult = ReturnType 156 | export type RegisterMutationResult = ApolloReactCommon.MutationResult< 157 | RegisterMutation 158 | > 159 | export type RegisterMutationOptions = ApolloReactCommon.BaseMutationOptions< 160 | RegisterMutation, 161 | RegisterMutationVariables 162 | > 163 | export const LoginDocument = gql` 164 | mutation login($email: String!, $password: String!) { 165 | login(email: $email, password: $password) { 166 | accessToken 167 | profile: user { 168 | id 169 | firstName 170 | lastName 171 | email 172 | } 173 | } 174 | } 175 | ` 176 | export type LoginMutationFn = ApolloReactCommon.MutationFunction< 177 | LoginMutation, 178 | LoginMutationVariables 179 | > 180 | 181 | /** 182 | * __useLoginMutation__ 183 | * 184 | * To run a mutation, you first call `useLoginMutation` within a React component and pass it any options that fit your needs. 185 | * When your component renders, `useLoginMutation` returns a tuple that includes: 186 | * - A mutate function that you can call at any time to execute the mutation 187 | * - An object with fields that represent the current status of the mutation's execution 188 | * 189 | * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; 190 | * 191 | * @example 192 | * const [loginMutation, { data, loading, error }] = useLoginMutation({ 193 | * variables: { 194 | * email: // value for 'email' 195 | * password: // value for 'password' 196 | * }, 197 | * }); 198 | */ 199 | export function useLoginMutation( 200 | baseOptions?: ApolloReactHooks.MutationHookOptions< 201 | LoginMutation, 202 | LoginMutationVariables 203 | > 204 | ) { 205 | return ApolloReactHooks.useMutation( 206 | LoginDocument, 207 | baseOptions 208 | ) 209 | } 210 | export type LoginMutationHookResult = ReturnType 211 | export type LoginMutationResult = ApolloReactCommon.MutationResult< 212 | LoginMutation 213 | > 214 | export type LoginMutationOptions = ApolloReactCommon.BaseMutationOptions< 215 | LoginMutation, 216 | LoginMutationVariables 217 | > 218 | export const LogoutDocument = gql` 219 | mutation Logout { 220 | logout 221 | } 222 | ` 223 | export type LogoutMutationFn = ApolloReactCommon.MutationFunction< 224 | LogoutMutation, 225 | LogoutMutationVariables 226 | > 227 | 228 | /** 229 | * __useLogoutMutation__ 230 | * 231 | * To run a mutation, you first call `useLogoutMutation` within a React component and pass it any options that fit your needs. 232 | * When your component renders, `useLogoutMutation` returns a tuple that includes: 233 | * - A mutate function that you can call at any time to execute the mutation 234 | * - An object with fields that represent the current status of the mutation's execution 235 | * 236 | * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; 237 | * 238 | * @example 239 | * const [logoutMutation, { data, loading, error }] = useLogoutMutation({ 240 | * variables: { 241 | * }, 242 | * }); 243 | */ 244 | export function useLogoutMutation( 245 | baseOptions?: ApolloReactHooks.MutationHookOptions< 246 | LogoutMutation, 247 | LogoutMutationVariables 248 | > 249 | ) { 250 | return ApolloReactHooks.useMutation( 251 | LogoutDocument, 252 | baseOptions 253 | ) 254 | } 255 | export type LogoutMutationHookResult = ReturnType 256 | export type LogoutMutationResult = ApolloReactCommon.MutationResult< 257 | LogoutMutation 258 | > 259 | export type LogoutMutationOptions = ApolloReactCommon.BaseMutationOptions< 260 | LogoutMutation, 261 | LogoutMutationVariables 262 | > 263 | export const ProfileDocument = gql` 264 | query profile { 265 | profile { 266 | id 267 | firstName 268 | lastName 269 | email 270 | } 271 | } 272 | ` 273 | 274 | /** 275 | * __useProfileQuery__ 276 | * 277 | * To run a query within a React component, call `useProfileQuery` and pass it any options that fit your needs. 278 | * When your component renders, `useProfileQuery` returns an object from Apollo Client that contains loading, error, and data properties 279 | * you can use to render your UI. 280 | * 281 | * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; 282 | * 283 | * @example 284 | * const { data, loading, error } = useProfileQuery({ 285 | * variables: { 286 | * }, 287 | * }); 288 | */ 289 | export function useProfileQuery( 290 | baseOptions?: ApolloReactHooks.QueryHookOptions< 291 | ProfileQuery, 292 | ProfileQueryVariables 293 | > 294 | ) { 295 | return ApolloReactHooks.useQuery( 296 | ProfileDocument, 297 | baseOptions 298 | ) 299 | } 300 | export function useProfileLazyQuery( 301 | baseOptions?: ApolloReactHooks.LazyQueryHookOptions< 302 | ProfileQuery, 303 | ProfileQueryVariables 304 | > 305 | ) { 306 | return ApolloReactHooks.useLazyQuery( 307 | ProfileDocument, 308 | baseOptions 309 | ) 310 | } 311 | export type ProfileQueryHookResult = ReturnType 312 | export type ProfileLazyQueryHookResult = ReturnType 313 | export type ProfileQueryResult = ApolloReactCommon.QueryResult< 314 | ProfileQuery, 315 | ProfileQueryVariables 316 | > 317 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------