├── .github ├── settings.yml ├── FUNDING.yml └── workflows │ ├── commitlint.yml │ ├── lint.yml │ └── release.yml ├── public ├── worker-5rESfc3pmXErPUzCgmt78.js ├── logo-192x192.png ├── logo-512x512.png ├── robots.txt ├── sitemap.xml ├── manifest.json └── favicon.svg ├── .husky ├── pre-commit └── commit-msg ├── commitlint.config.js ├── .eslintignore ├── .prettierrc.json ├── src ├── pages │ ├── api │ │ ├── auth │ │ │ └── [...auth0].ts │ │ ├── webhook.ts │ │ └── create-checkout-session.ts │ ├── _document.tsx │ ├── success.tsx │ ├── _app.tsx │ ├── medicine │ │ └── index.tsx │ ├── parcel │ │ ├── orders.tsx │ │ ├── [id].tsx │ │ └── index.tsx │ ├── food │ │ ├── index.tsx │ │ └── [id].tsx │ ├── orders.tsx │ ├── books │ │ └── index.tsx │ ├── index.tsx │ └── cart.tsx ├── types │ ├── orderTypes.ts │ ├── bookTypes.ts │ ├── ParcelType.ts │ ├── userType.ts │ └── itemTypes.ts ├── app │ └── store.ts ├── services │ └── StorageService.ts ├── components │ ├── meds │ │ ├── Main.tsx │ │ └── OfferProduct.tsx │ ├── FoodItem.tsx │ ├── Items.tsx │ ├── Footer.tsx │ ├── eats │ │ └── Main.tsx │ ├── Banner.tsx │ ├── CartItem.tsx │ ├── parcel │ │ └── Order.tsx │ ├── OfferProduct.tsx │ ├── Order.tsx │ ├── Header.tsx │ └── Cart.tsx ├── slices │ └── basketSlice.ts ├── styles │ └── globals.css └── utils │ └── requests.ts ├── .prettierignore ├── postcss.config.js ├── next-env.d.ts ├── CHANGELOG.md ├── .eslintrc ├── permissions.ts ├── tsconfig.json ├── firebase.ts ├── .gitignore ├── LICENSE ├── tailwind.config.js ├── README.md ├── next.config.js ├── package.json ├── .env.sample ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md /.github/settings.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: avneesh0612 2 | custom: ["blog.avneesh.tech/sponsor"] 3 | -------------------------------------------------------------------------------- /public/worker-5rESfc3pmXErPUzCgmt78.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";self.__WB_DISABLE_DEV_LOGS=!0})(); -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn run pre-commit 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit 5 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | }; 4 | -------------------------------------------------------------------------------- /public/logo-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avneesh0612/voyagger/HEAD/public/logo-192x192.png -------------------------------------------------------------------------------- /public/logo-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avneesh0612/voyagger/HEAD/public/logo-512x512.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .next 2 | next-env.d.ts 3 | node_modules 4 | yarn.lock 5 | package-lock.json 6 | public 7 | .github -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | user-agent: * 2 | disallow: /downloads/ 3 | 4 | user-agent: magicsearchbot 5 | disallow: /uploads/ -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "arrowParens": "avoid", 4 | "tabWidth": 2, 5 | "singleQuote": false 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/api/auth/[...auth0].ts: -------------------------------------------------------------------------------- 1 | import { handleAuth } from "@auth0/nextjs-auth0"; 2 | 3 | export default handleAuth(); 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | next-env.d.ts 3 | node_modules 4 | yarn.lock 5 | package-lock.json 6 | public 7 | .github 8 | README.md 9 | CHANGELOG.md -------------------------------------------------------------------------------- /src/types/orderTypes.ts: -------------------------------------------------------------------------------- 1 | interface orderType { 2 | id: string; 3 | amount: number; 4 | amountShipping: number; 5 | images: [string]; 6 | timestamp: number; 7 | } 8 | 9 | export type { orderType }; 10 | -------------------------------------------------------------------------------- /src/app/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import basketReducer from "../slices/basketSlice"; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | basket: basketReducer, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /src/types/bookTypes.ts: -------------------------------------------------------------------------------- 1 | interface book { 2 | volumeInfo: { 3 | imageLinks: { 4 | thumbnail: string; 5 | }; 6 | previewLink: string; 7 | title: string; 8 | authors: [string]; 9 | }; 10 | id: string; 11 | } 12 | export default book; 13 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://www.voyagger.tech// 5 | daily 6 | 0.7 7 | 8 | -------------------------------------------------------------------------------- /src/types/ParcelType.ts: -------------------------------------------------------------------------------- 1 | interface ParcelType { 2 | id: string; 3 | pickupaddress: string; 4 | recipientphone: string; 5 | recipientsaddress: string; 6 | usermail: string; 7 | username: string; 8 | weight: string; 9 | zip: string; 10 | } 11 | export default ParcelType; 12 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Commit Messages 2 | on: [pull_request, push] 3 | 4 | jobs: 5 | commitlint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | - uses: wagoid/commitlint-github-action@v4 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 (2021-12-01) 2 | 3 | 4 | ### Features 5 | 6 | * convert to ts ([bce4371](https://github.com/avneesh0612/voyagger/commit/bce4371b48770f4b1e7e61b1ce8ecfec9e1b4215)) 7 | * convert to ts ([fff41cb](https://github.com/avneesh0612/voyagger/commit/fff41cbe7decc4067b99b748cb1a5e5dd3c69e6f)) 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/types/userType.ts: -------------------------------------------------------------------------------- 1 | interface user { 2 | email?: string; 3 | email_verified?: boolean; 4 | family_name?: string; 5 | given_name?: string; 6 | locale?: string; 7 | name?: string; 8 | nickname?: string; 9 | picture?: string; 10 | sub?: string; 11 | updated_at?: string; 12 | address?: string; 13 | } 14 | 15 | export type { user }; 16 | -------------------------------------------------------------------------------- /src/types/itemTypes.ts: -------------------------------------------------------------------------------- 1 | interface Salad { 2 | id: string; 3 | active: boolean; 4 | name: string; 5 | image: string; 6 | price: number; 7 | category: string; 8 | description: string; 9 | } 10 | 11 | interface Category { 12 | id: string; 13 | name: string; 14 | image: string; 15 | } 16 | interface Medicines { 17 | image: string; 18 | name: string; 19 | price: number; 20 | id: string; 21 | } 22 | 23 | export type { Salad, Category, Medicines }; 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "google", 5 | "prettier", 6 | "eslint:recommended" 7 | ], 8 | "rules": { 9 | "react/react-in-jsx-scope": "off", 10 | "object-curly-spacing": [2, "always"], 11 | "quotes": [ 12 | 2, 13 | "double", 14 | { 15 | "avoidEscape": true 16 | } 17 | ], 18 | "no-undef": "off", 19 | "require-jsdoc": "off", 20 | "new-cap": "off" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/services/StorageService.ts: -------------------------------------------------------------------------------- 1 | const LOCALSTORAGE_KEY_PREFIX = "Voyagger-basket"; 2 | 3 | export default { 4 | get(item: string) { 5 | try { 6 | return window.localStorage.getItem(`${LOCALSTORAGE_KEY_PREFIX}:${item}`); 7 | } catch (e) { 8 | return null; 9 | } 10 | }, 11 | set(item: string, value: string) { 12 | try { 13 | window.localStorage.setItem(`${LOCALSTORAGE_KEY_PREFIX}:${item}`, value); 14 | } catch (e) {} 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /permissions.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: process.env.permission_type, 3 | project_id: process.env.permission_project_id, 4 | private_key_id: process.env.permission_private_key_id, 5 | private_key: process.env.permission_private_key, 6 | client_email: process.env.permission_client_email, 7 | client_id: process.env.permission_client_id, 8 | auth_uri: process.env.permission_auth_uri, 9 | token_uri: process.env.permission_token_uri, 10 | auth_provider_x509_cert_url: 11 | process.env.permission_auth_provider_x509_cert_url, 12 | client_x509_cert_url: process.env.permission_client_x509_cert_url, 13 | }; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "include": [ 22 | "next-env.d.ts", 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @next/next/no-document-import-in-page 2 | import Document, { Head, Html, Main, NextScript } from "next/document"; 3 | 4 | class MyDocument extends Document { 5 | render() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | ); 19 | } 20 | } 21 | 22 | export default MyDocument; 23 | -------------------------------------------------------------------------------- /firebase.ts: -------------------------------------------------------------------------------- 1 | import firebase from "firebase/compat/app"; 2 | import { getFirestore } from "firebase/firestore"; 3 | 4 | export const firebaseConfig = { 5 | apiKey: process.env.firebase_config_api_key, 6 | authDomain: process.env.firebase_authDomain, 7 | projectId: process.env.firebase_projectId, 8 | storageBucket: process.env.firebase_storageBucket, 9 | messagingSenderId: process.env.firebase_messagingSenderId, 10 | appId: process.env.firebase_appId, 11 | measurementId: process.env.firebase_measurementid, 12 | }; 13 | 14 | const app = !firebase.apps.length 15 | ? firebase.initializeApp(firebaseConfig) 16 | : firebase.app(); 17 | 18 | const db = getFirestore(app); 19 | 20 | export { db }; 21 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the main branch 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | run-linters: 15 | name: Run linters 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Check out Git repository 20 | uses: actions/checkout@v2 21 | 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 12 26 | 27 | - name: Install Node.js dependencies 28 | run: yarn ci 29 | 30 | - name: Run linters 31 | run: | 32 | yarn lint 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Releases 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | changelog: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: conventional Changelog Action 15 | id: changelog 16 | uses: TriPSs/conventional-changelog-action@v3.7.1 17 | with: 18 | github-token: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | - name: create release 21 | uses: actions/create-release@v1 22 | if: ${{ steps.changelog.outputs.skipped == 'false' }} 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | tag_name: ${{ steps.changelog.outputs.tag }} 27 | release_name: ${{ steps.changelog.outputs.tag }} 28 | body: ${{ steps.changelog.outputs.clean_changelog }} 29 | -------------------------------------------------------------------------------- /.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 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # package lock 38 | package-lock.json 39 | 40 | # PWA files 41 | **/public/workbox-*.js 42 | **/public/sw.js 43 | **/public/workbox-*.js.map 44 | **/public/sw.js.map 45 | **/public/fallback-*.js 46 | **/public/fallback-*.js.map 47 | public/fallback-development.js 48 | public/worker-development.js 49 | public/worker-zIwDTXVlDNdEXKXBr52r9.js 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Avneesh Agarwal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/meds/Main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Medicines } from "../../types/itemTypes"; 3 | import OfferProduct from "./OfferProduct"; 4 | 5 | interface MainProps { 6 | medicines: [Medicines]; 7 | } 8 | 9 | const Main: React.FC = ({ medicines }) => { 10 | return ( 11 |
12 |
13 | 14 |
15 |
16 |

Best offers

17 |
18 |
19 |
20 | {medicines.map((medicine: Medicines) => ( 21 |
22 | 28 |
29 | ))} 30 |
31 |
32 | ); 33 | }; 34 | 35 | export default Main; 36 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: "jit", 3 | purge: [ 4 | "./src/pages/**/*.{js,ts,jsx,tsx}", 5 | "./src/components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | darkMode: false, // or 'media' or 'class' 8 | theme: { 9 | extend: { 10 | animation: { 11 | blob: "blob 7s infinite", 12 | }, 13 | keyframes: { 14 | blob: { 15 | "0%": { 16 | transform: "translate(0px, 0px) scale(1)", 17 | }, 18 | "33%": { 19 | transform: "translate(300px, -50px) scale(1.3)", 20 | }, 21 | "66%": { 22 | transform: "translate(-200px, 200px) scale(0.8)", 23 | }, 24 | "100%": { 25 | transform: "translate(0px, 0px) scale(1)", 26 | }, 27 | }, 28 | }, 29 | fontFamily: { 30 | Poppins: ["Poppins", "sans-serif"], 31 | lobster: ["Lobster", "cursive"], 32 | }, 33 | colors: { 34 | bgmain: "#fad8d2", 35 | text: "#431B16", 36 | prussianblue: "#023047", 37 | redmarker: "#F24A51", 38 | peachmedium: "#FEC5BB", 39 | peachdark: "#ffb1a3", 40 | }, 41 | }, 42 | }, 43 | variants: { 44 | extend: {}, 45 | }, 46 | plugins: [], 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/FoodItem.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { useRouter } from "next/dist/client/router"; 3 | import Image from "next/image"; 4 | 5 | interface FoodItemProps { 6 | image: string; 7 | name: string; 8 | categoryRoute: string; 9 | } 10 | 11 | const FoodItem: React.FC = ({ image, name, categoryRoute }) => { 12 | const router = useRouter(); 13 | 14 | return ( 15 | router.push(`/food/?category=${name.toLowerCase()}`)} 25 | > 26 | {name} 34 |

{name}

35 |
36 | ); 37 | }; 38 | 39 | export default FoodItem; 40 | -------------------------------------------------------------------------------- /src/components/Items.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import Link from "next/link"; 3 | 4 | interface FoodItemProps { 5 | image: string; 6 | text: string; 7 | href: string; 8 | repeat?: boolean; 9 | } 10 | 11 | const Items: React.FC = ({ image, text, repeat, href }) => { 12 | return ( 13 | 14 | 15 | 25 |
31 |

32 | {text} 33 |

34 |
35 |
36 | 37 | ); 38 | }; 39 | 40 | export default Items; 41 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import Image from "next/image"; 3 | import React from "react"; 4 | 5 | const Footer: React.FC = () => { 6 | return ( 7 |
8 | 9 | 13 | About Project 14 | 15 | 16 | 17 |

18 | Made with 🤎 by 19 | 20 | 25 | Avneesh Agarwal 26 | 27 | 28 |

29 | 30 | 31 | 32 | GitHub logo 39 | 40 | 41 |
42 | ); 43 | }; 44 | 45 | export default Footer; 46 | -------------------------------------------------------------------------------- /src/slices/basketSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import { Salad } from "../types/itemTypes"; 3 | 4 | const initialState = { 5 | items: [], 6 | }; 7 | 8 | export const basketSlice = createSlice({ 9 | name: "basket", 10 | initialState, 11 | reducers: { 12 | hydrate: (state, action) => { 13 | return action.payload; 14 | }, 15 | addToBasket: (state: any, action) => { 16 | state.items = [...state.items, action.payload]; 17 | }, 18 | removeFromBasket: (state, action) => { 19 | let pos = state.items.findIndex( 20 | (item: Salad) => item.id === action.payload.id 21 | ); 22 | let newBasket = [...state.items]; 23 | 24 | if (pos > -1) { 25 | newBasket.splice(pos, 1); 26 | } 27 | 28 | state.items = newBasket; 29 | }, 30 | removeGroupedFromBasket: (state, action) => { 31 | let newBasket = state.items.filter( 32 | (item: Salad) => item.id !== action.payload.id 33 | ); 34 | 35 | state.items = newBasket; 36 | }, 37 | clearBasket: (state) => { 38 | state.items = []; 39 | }, 40 | }, 41 | }); 42 | 43 | export const { 44 | addToBasket, 45 | removeFromBasket, 46 | removeGroupedFromBasket, 47 | hydrate, 48 | clearBasket, 49 | } = basketSlice.actions; 50 | 51 | export const selectItems = (state: any) => state.basket.items; 52 | export const selectTotal = (state: any) => 53 | state.basket.items.reduce( 54 | (total: number, item: Salad) => total + item.price, 55 | 0 56 | ); 57 | 58 | export default basketSlice.reducer; 59 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Voyagger", 3 | "short_name": "Voyagger", 4 | "icons": [ 5 | { 6 | "src": "/logo-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png", 9 | "purpose": "any maskable" 10 | }, 11 | 12 | { 13 | "src": "/logo-512x512.png", 14 | "sizes": "512x512", 15 | "type": "image/png", 16 | "purpose": "any" 17 | } 18 | ], 19 | "shortcuts": [ 20 | { 21 | "name": "My orders", 22 | "short_name": "My orders", 23 | "description": "View all your medicine and food orders", 24 | "url": "/orders", 25 | "icons": [ 26 | { 27 | "src": "/logo-192x192.png", 28 | "sizes": "192x192", 29 | "type": "image/png" 30 | }, 31 | 32 | { 33 | "src": "/logo-512x512.png", 34 | "sizes": "512x512", 35 | "type": "image/png" 36 | } 37 | ] 38 | }, 39 | { 40 | "name": "Parcel tracking", 41 | "short_name": "Parcel tracking", 42 | "description": "Track all your orders", 43 | "url": "/parcel/orders", 44 | "icons": [ 45 | { 46 | "src": "/logo-192x192.png", 47 | "sizes": "192x192", 48 | "type": "image/png" 49 | }, 50 | 51 | { 52 | "src": "/logo-512x512.png", 53 | "sizes": "512x512", 54 | "type": "image/png" 55 | } 56 | ] 57 | } 58 | ], 59 | 60 | "theme_color": "#FFFFFF", 61 | "background_color": "#FFFFFF", 62 | "start_url": "/", 63 | "display": "standalone", 64 | "orientation": "portrait" 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to Voyagger 👋

2 |

3 | 4 | License: MIT 5 | 6 | 7 | Twitter: avneesh0612 8 | 9 |

10 | 11 | > Connecting people, Changing lives 12 | 13 | ### 🏠 [Homepage](https://www.voyagger.tech//) 14 | 15 | ### ✨ [Demo](https://www.voyagger.tech//) 16 | 17 | ## Install 18 | 19 | ```sh 20 | yarn install 21 | ``` 22 | 23 | ## Usage 24 | 25 | ```sh 26 | yarn dev 27 | ``` 28 | 29 | ## 🤝 Contributing 30 | 31 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/avneesh0612/Voyagger/issues). 32 | 33 | See [Contribution.MD](https://github.com/avneesh0612/voyagger/blob/main/CONTRIBUTING.md) for details about how to contribute 34 | ## Author 35 | 36 | 👤 **Avneesh Agarwal** 37 | 38 | - Website: https://www.voyagger.tech// 39 | - Twitter: [@avneesh0612](https://twitter.com/avneesh0612) 40 | - Github: [@avneesh0612](https://github.com/avneesh0612) 41 | - LinkedIn: [@avneesh-agarwal-78312b20a](https://linkedin.com/in/avneesh-agarwal-78312b20a) 42 | 43 | ## Show your support 44 | 45 | Give a ⭐️ if this project helped you! 46 | 47 | ## 📝 License 48 | 49 | Copyright © 2021 [Avneesh Agarwal](https://github.com/avneesh0612).
50 | This project is [MIT](https://github.com/avneesh0612/Voyagger/blob/main/LICENSE) licensed. 51 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); 2 | @import url("https://fonts.googleapis.com/css2?family=Lobster&display=swap"); 3 | 4 | @tailwind base; 5 | @tailwind components; 6 | @tailwind utilities; 7 | 8 | @layer utilities { 9 | .animation-delay-2000 { 10 | animation-delay: 2.5s; 11 | } 12 | .animation-delay-4000 { 13 | animation-delay: 5s; 14 | } 15 | } 16 | 17 | @layer components { 18 | .sidebar_icon { 19 | @apply h-[40px] md:min-h-[48px] w-[40px] md:min-w-[48px] text-black bg-gray-100 rounded-full p-2; 20 | } 21 | body { 22 | @apply overflow-x-hidden bg-bgmain text-text; 23 | } 24 | } 25 | 26 | .bg-gradient-radial { 27 | background: rgb(248, 93, 73); 28 | background: radial-gradient( 29 | circle, 30 | rgba(248, 93, 73, 1) 50%, 31 | rgba(252, 176, 69, 1) 100% 32 | ); 33 | } 34 | 35 | .green-gradient { 36 | background: linear-gradient(45deg, #4ae491, #49aa75); 37 | } 38 | 39 | .hidescrollbar::-webkit-scrollbar { 40 | display: none; 41 | } 42 | 43 | .react-multi-carousel-item { 44 | margin: 0px 10px; 45 | } 46 | 47 | .hidescrollbar { 48 | -ms-overflow-style: none; 49 | scrollbar-width: none; 50 | } 51 | 52 | ::-webkit-scrollbar { 53 | width: 10px; 54 | } 55 | 56 | ::-webkit-scrollbar-track { 57 | border-radius: 10px; 58 | background-color: #ffffff !important; 59 | } 60 | 61 | ::-webkit-scrollbar-thumb { 62 | border-radius: 10px; 63 | background-color: #fa8072; 64 | } 65 | 66 | html { 67 | scroll-behavior: smooth; 68 | } 69 | -------------------------------------------------------------------------------- /src/components/eats/Main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Category, Salad } from "../../types/itemTypes"; 3 | import FoodItem from "../FoodItem"; 4 | import OfferProduct from "../OfferProduct"; 5 | 6 | interface MainProps { 7 | salads: [Salad]; 8 | categories: [Category]; 9 | categoryRoute: string; 10 | } 11 | 12 | const Main: React.FC = ({ salads, categories, categoryRoute }) => { 13 | return ( 14 |
15 |
16 | {categories.map((category: Category) => ( 17 |
18 | 23 |
24 | ))} 25 |
26 |
27 |
28 |

Best offers

29 |
30 |
31 |
32 | {salads.map((salad: Salad) => ( 33 |
34 | 43 |
44 | ))} 45 |
46 |
47 | ); 48 | }; 49 | 50 | export default Main; 51 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withPWA = require("next-pwa"); 2 | 3 | module.exports = withPWA({ 4 | pwa: { 5 | dest: "public", 6 | register: true, 7 | skipWaiting: true, 8 | disable: process.env.NODE_ENV === "development", 9 | }, 10 | 11 | images: { 12 | domains: [ 13 | "res.cloudinary.com", 14 | "lh3.googleusercontent.com", 15 | "images.dominos.co.in", 16 | "avatars.githubusercontent.com", 17 | "books.google.com", 18 | "upload.wikimedia.org", 19 | "s.gravatar.com", 20 | "en.wikipedia.org", 21 | ], 22 | }, 23 | 24 | env: { 25 | firebase_config_api_key: process.env.FIREBASE_CONFIG_API_KEY, 26 | firebase_authDomain: process.env.FIREBASE_AUTH_DOMAIN, 27 | firebase_projectId: process.env.FIREBASE_PROJECT_ID, 28 | firebase_storageBucket: process.env.FIREBASE_STORAGE_BUCKET, 29 | firebase_messagingSenderId: process.env.FIREBASE_MESSAGEING_SENDER_ID, 30 | firebase_appId: process.env.FIREBASE_APP_ID, 31 | firebase_measurementid: process.env.FIREBASE_MEASUREMENT_ID, 32 | stripe_public_key: process.env.STRIPE_PUBLIC_KEY, 33 | permission_type: process.env.PERMISSION_TYPE, 34 | permission_project_id: process.env.PERMISSION_PROJECT_ID, 35 | permission_private_key_id: process.env.PERMISSION_PRIVATE_KEY_ID, 36 | permission_private_key: process.env.PERMISSION_PRIVATE_KEY, 37 | permission_client_email: process.env.PERMISSION_CLIENT_EMAIL, 38 | permission_client_id: process.env.PERMISSION_CLIENT_ID, 39 | permission_auth_uri: process.env.PERMISSION_AUTH_URI, 40 | permission_token_uri: process.env.PERMISSION_TOKEN_URI, 41 | permission_auth_provider_x509_cert_url: 42 | process.env.PERMISSION_AUTH_PROVIDER_URL, 43 | permission_client_x509_cert_url: process.env.PERMISSION_CLIENT_PROVIDER_URL, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/utils/requests.ts: -------------------------------------------------------------------------------- 1 | const requests = { 2 | fetchFiction: { 3 | title: "Fiction", 4 | url: `/volumes?q=fiction&key=${process.env.FIREBASE_CONFIG_API_KEY}`, 5 | }, 6 | 7 | fetchScifi: { 8 | title: "Sci-Fi", 9 | url: `/volumes?q=sci-fi&key=${process.env.FIREBASE_CONFIG_API_KEY}`, 10 | }, 11 | fetchreligion: { 12 | title: "Religion & Spirituality", 13 | url: `/volumes?q=Religion&key=${process.env.FIREBASE_CONFIG_API_KEY}`, 14 | }, 15 | 16 | fetchMysteries: { 17 | title: "Mysteries", 18 | url: `/volumes?q=mysteries&key=${process.env.FIREBASE_CONFIG_API_KEY}`, 19 | }, 20 | 21 | fetchRomance: { 22 | title: "Romance", 23 | url: `/volumes?q=romace&key=${process.env.FIREBASE_CONFIG_API_KEY}`, 24 | }, 25 | 26 | fetchCrime: { 27 | title: "True Crime", 28 | url: `/volumes?q=crime&key=${process.env.FIREBASE_CONFIG_API_KEY}`, 29 | }, 30 | 31 | fetchMystery: { 32 | title: "Mystery", 33 | url: `/volumes?q=selfdevelopment&key=${process.env.FIREBASE_CONFIG_API_KEY}`, 34 | }, 35 | 36 | fetchBiographies: { 37 | title: "Biographies and Memoirs", 38 | url: `/volumes?q=biographies&key=${process.env.FIREBASE_CONFIG_API_KEY}`, 39 | }, 40 | 41 | fetchHistory: { 42 | title: "History", 43 | url: `/volumes?q=history&key=${process.env.FIREBASE_CONFIG_API_KEY}`, 44 | }, 45 | }; 46 | 47 | export default requests; 48 | 49 | // Self-Improvement 50 | // Personal Growth 51 | // Home & Garden 52 | // Gardening 53 | // Mystery, Thriller & Crime Fiction 54 | // Suspense 55 | // True Crime 56 | // Science Fiction & Fantasy 57 | // Young Adult 58 | // Dystopian 59 | // Paranormal, Occult & Supernatural 60 | // Romance 61 | // Historical Fiction 62 | // Science & Mathematics 63 | // History 64 | // Study Aids & Test Prep 65 | // Business 66 | // Small Business & Entrepreneurs 67 | // All categories 68 | -------------------------------------------------------------------------------- /src/pages/success.tsx: -------------------------------------------------------------------------------- 1 | import { CheckCircleIcon } from "@heroicons/react/solid"; 2 | import { motion } from "framer-motion"; 3 | import { NextSeo } from "next-seo"; 4 | import { useRouter } from "next/router"; 5 | import { useEffect } from "react"; 6 | import { useDispatch } from "react-redux"; 7 | import Header from "../components/Header"; 8 | import { clearBasket } from "../slices/basketSlice"; 9 | 10 | const Success: React.FC = () => { 11 | const router = useRouter(); 12 | const dispatch = useDispatch(); 13 | 14 | useEffect(() => { 15 | dispatch(clearBasket()); 16 | }, [dispatch]); 17 | 18 | return ( 19 |
20 |
21 | 22 |
23 |
24 |
25 | 26 |

27 | Thank you, your order has been confirmed! 28 |

29 |
30 |

31 | Thank you for shopping with us. We'll send a confirmation once 32 | item has shipped, if you would like to check the status of your 33 | order(s) please press the link below. 34 |

35 | router.push("/orders")} 42 | className="mt-8 shadow-xl text-prussianblue bg-white bg-opacity-25 w-60 text-center mx-auto rounded-lg p-2 font-semibold backdrop-filter backdrop-blur-xl focus:outline-none" 43 | > 44 | Go to my orders 45 | 46 |
47 |
48 |
49 | ); 50 | }; 51 | 52 | export default Success; 53 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { UserProvider } from "@auth0/nextjs-auth0"; 2 | import { NextSeo } from "next-seo"; 3 | import { AppProps } from "next/app"; 4 | import Head from "next/head"; 5 | import NextNProgress from "nextjs-progressbar"; 6 | import { Toaster } from "react-hot-toast"; 7 | import { Provider } from "react-redux"; 8 | import { store } from "../app/store"; 9 | import StorageService from "../services/StorageService"; 10 | import { hydrate } from "../slices/basketSlice"; 11 | import "../styles/globals.css"; 12 | 13 | const MyApp = ({ Component, pageProps }: AppProps) => { 14 | store.subscribe(() => { 15 | StorageService.set("basket", JSON.stringify(store.getState().basket)); 16 | }); 17 | 18 | let basket = StorageService.get("basket"); 19 | basket = basket ? JSON.parse(basket) : { items: [] }; 20 | store.dispatch(hydrate(basket)); 21 | 22 | return ( 23 | 24 | 25 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | }; 58 | 59 | export default MyApp; 60 | -------------------------------------------------------------------------------- /src/components/meds/OfferProduct.tsx: -------------------------------------------------------------------------------- 1 | import { PlusIcon } from "@heroicons/react/solid"; 2 | import { motion } from "framer-motion"; 3 | import Image from "next/image"; 4 | import Currency from "react-currency-formatter"; 5 | import toast from "react-hot-toast"; 6 | import { useDispatch } from "react-redux"; 7 | import { addToBasket } from "../../slices/basketSlice"; 8 | 9 | interface FoodItemProps { 10 | image: string; 11 | name: string; 12 | price: number; 13 | id: string; 14 | } 15 | 16 | const OfferProduct: React.FC = ({ name, price, image, id }) => { 17 | const dispatch = useDispatch(); 18 | 19 | const addItemTobasket = () => { 20 | const product = { 21 | name, 22 | price, 23 | image, 24 | id, 25 | }; 26 | 27 | dispatch(addToBasket(product)); 28 | toast.success("Added item to basket", { 29 | style: { 30 | borderRadius: "100px", 31 | }, 32 | }); 33 | }; 34 | 35 | return ( 36 | 42 | 47 | salad 55 | 56 |
57 |

{name}

58 |
59 | 60 | 65 |
66 |
67 |
68 | ); 69 | }; 70 | 71 | export default OfferProduct; 72 | -------------------------------------------------------------------------------- /src/pages/medicine/index.tsx: -------------------------------------------------------------------------------- 1 | import { getSession, withPageAuthRequired } from "@auth0/nextjs-auth0/"; 2 | import { NextSeo } from "next-seo"; 3 | import React, { useEffect } from "react"; 4 | import { db } from "../../../firebase"; 5 | import Cart from "../../components/Cart"; 6 | import Header from "../../components/Header"; 7 | import Main from "../../components/meds/Main"; 8 | import { Medicines } from "../../types/itemTypes"; 9 | import { user } from "../../types/userType"; 10 | import { 11 | doc, 12 | updateDoc, 13 | getDoc, 14 | collection, 15 | getDocs, 16 | } from "firebase/firestore"; 17 | 18 | interface HomeProps { 19 | user: user; 20 | dbuser: user; 21 | medicines: [Medicines]; 22 | } 23 | 24 | const Home: React.FC = ({ user, dbuser, medicines }) => { 25 | useEffect(() => { 26 | if (user?.email) { 27 | const updateUsers = async () => { 28 | const userRef = doc(db, `users/${user?.email}`); 29 | 30 | await updateDoc(userRef, { 31 | email: user?.email, 32 | name: user?.name, 33 | photoURL: user?.picture, 34 | }); 35 | }; 36 | 37 | updateUsers(); 38 | } 39 | }, [user]); 40 | 41 | return ( 42 |
43 | 44 |
45 |
46 |
47 |
48 | 49 |
50 | ); 51 | }; 52 | 53 | export default Home; 54 | 55 | export const getServerSideProps = withPageAuthRequired({ 56 | async getServerSideProps(context: any) { 57 | const session = getSession(context.req, context.res); 58 | 59 | const userref = doc(db, `users/${session?.user.email}`); 60 | 61 | const userRes = await getDoc(userref); 62 | 63 | const dbuser = { 64 | id: userRes.id, 65 | ...userRes.data(), 66 | }; 67 | 68 | const medicinesRef = collection(db, "products/medicine/medicine"); 69 | 70 | const allmedicines = await getDocs(medicinesRef); 71 | 72 | const medicines = allmedicines.docs.map(medicine => ({ 73 | id: medicine.id, 74 | ...medicine.data(), 75 | })); 76 | 77 | return { props: { dbuser, medicines } }; 78 | }, 79 | }); 80 | -------------------------------------------------------------------------------- /src/components/Banner.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import React from "react"; 3 | import { Carousel } from "react-responsive-carousel"; 4 | import "react-responsive-carousel/lib/styles/carousel.min.css"; 5 | 6 | const Banner: React.FC = () => { 7 | return ( 8 |
9 |
10 | 18 |
19 | banner 25 |
26 |
27 | banner 33 |
34 |
35 | banner 41 |
42 |
43 | banner 49 |
50 |
51 | banner 57 |
58 |
59 |
60 | ); 61 | }; 62 | 63 | export default Banner; 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build", 6 | "start": "next start", 7 | "format": "prettier --write \"src/{components,content,docs,pages,styles,utils}/**/*.{js,jsx,ts,tsx,css,scss,json,yaml,md}\"", 8 | "format:check": "prettier --check \"src/{components,content,docs,pages,styles,utils}/**/*.{js,jsx,ts,tsx,css,scss,json,yaml,md}\"", 9 | "lint": "eslint --fix 'src/{components,pages,utils}/**/*.{js,jsx,ts,tsx}'", 10 | "lint:check": "eslint 'src/{components,pages,utils}/**/*.{js,jsx,ts,tsx}'", 11 | "prepare": "husky install", 12 | "pre-commit": "yarn run format && yarn run lint && git add -A .", 13 | "ci": "rm -rf node_modules && yarn install --frozen-lockfile" 14 | }, 15 | "dependencies": { 16 | "@auth0/nextjs-auth0": "^1.5.0", 17 | "@heroicons/react": "^1.0.4", 18 | "@reduxjs/toolkit": "^1.6.2", 19 | "@stripe/stripe-js": "^1.19.1", 20 | "axios": "^0.22.0", 21 | "firebase": "^9.1.2", 22 | "firebase-admin": "^9.12.0", 23 | "framer-motion": "^4.1.17", 24 | "lodash": "^4.17.21", 25 | "micro": "^9.3.4", 26 | "mobile-detect": "^1.4.5", 27 | "moment": "^2.29.1", 28 | "next": "latest", 29 | "next-pwa": "^5.3.1", 30 | "next-seo": "^4.28.1", 31 | "nextjs-progressbar": "^0.0.11", 32 | "react": "^17.0.2", 33 | "react-currency-formatter": "^1.1.0", 34 | "react-dom": "^17.0.2", 35 | "react-hot-toast": "^2.1.1", 36 | "react-redux": "^7.2.5", 37 | "react-responsive-carousel": "^3.2.21", 38 | "stripe": "^8.179.0" 39 | }, 40 | "devDependencies": { 41 | "@commitlint/cli": "^15.0.0", 42 | "@commitlint/config-conventional": "^15.0.0", 43 | "@types/lodash": "^4.14.175", 44 | "@types/micro": "^7.3.6", 45 | "@types/node": "^16.10.3", 46 | "@types/react": "^17.0.27", 47 | "@types/react-currency-formatter": "^1.1.4", 48 | "@types/stripe": "^8.0.417", 49 | "autoprefixer": "^10.3.7", 50 | "eslint": "<8.0.0", 51 | "eslint-config-google": "^0.14.0", 52 | "eslint-config-next": "^11.1.2", 53 | "eslint-config-prettier": "^8.3.0", 54 | "husky": "^7.0.4", 55 | "postcss": "^8.3.9", 56 | "prettier": "^2.5.0", 57 | "tailwindcss": "^2.2.16", 58 | "typescript": "^4.4.3" 59 | }, 60 | "license": "MIT", 61 | "browser": { 62 | "child_process": false 63 | }, 64 | "version": "0.1.0" 65 | } -------------------------------------------------------------------------------- /src/pages/api/webhook.ts: -------------------------------------------------------------------------------- 1 | import * as admin from "firebase-admin"; 2 | import { IncomingMessage } from "http"; 3 | import { buffer } from "micro"; 4 | import stripelib from "stripe"; 5 | 6 | const serviceAccount = require("../../../permissions"); 7 | const app = !admin.apps.length 8 | ? admin.initializeApp({ 9 | credential: admin.credential.cert(serviceAccount), 10 | }) 11 | : admin.app(); 12 | 13 | // @ts-ignore: ENV vars would be present 14 | const stripe = new stripelib.Stripe(process.env.STRIPE_SECRET_KEY, { 15 | apiVersion: "2020-08-27", 16 | typescript: true, 17 | }); 18 | const endpointSecret = process.env.STRIPE_SIGNING_SECRET; 19 | const fulfillOrder = async (session: any) => { 20 | const images = JSON.parse(session.metadata.images).map((image: any) => 21 | JSON.stringify(image) 22 | ); 23 | 24 | return app 25 | .firestore() 26 | .collection("users") 27 | .doc(session.metadata.email) 28 | .collection("orders") 29 | .doc(session.id) 30 | .set({ 31 | amount: session.amount_total / 100, 32 | amount_shipping: session.total_details.amount_shipping / 100, 33 | images: images, 34 | timestamp: admin.firestore.FieldValue.serverTimestamp(), 35 | }); 36 | }; 37 | 38 | const handler = async ( 39 | req: IncomingMessage, 40 | res: { 41 | status: (arg0: number) => { 42 | (): any; 43 | new (): any; 44 | send: { (arg0: string): any; new (): any }; 45 | json: { (arg0: { ok: boolean }): any; new (): any }; 46 | }; 47 | json: (arg0: { ok: boolean }) => void; 48 | } 49 | ) => { 50 | if (req.method === "POST") { 51 | const requestBuffer = await buffer(req); 52 | const payload = requestBuffer.toString(); 53 | const sig = req.headers["stripe-signature"]; 54 | 55 | let event; 56 | 57 | try { 58 | // @ts-ignore 59 | event = stripe.webhooks.constructEvent(payload, sig, endpointSecret); 60 | } catch (err: any) { 61 | return res.status(400).send(`Webhook error: ${err.message}`); 62 | } 63 | 64 | if (event.type === "checkout.session.completed") { 65 | const session = event.data.object; 66 | 67 | return fulfillOrder(session) 68 | .then(() => res.status(200).json({ ok: true })) 69 | .catch(err => res.status(400).send(`Webhook Error: ${err.message}`)); 70 | } 71 | res.json({ ok: true }); 72 | } 73 | }; 74 | 75 | export const config = { 76 | api: { 77 | bodyParser: false, 78 | externalResolver: true, 79 | }, 80 | }; 81 | 82 | export default handler; 83 | -------------------------------------------------------------------------------- /src/components/CartItem.tsx: -------------------------------------------------------------------------------- 1 | import { MinusSmIcon, PlusIcon } from "@heroicons/react/solid"; 2 | import Image from "next/image"; 3 | import Currency from "react-currency-formatter"; 4 | import toast from "react-hot-toast"; 5 | import { useDispatch } from "react-redux"; 6 | import { addToBasket, removeFromBasket } from "../slices/basketSlice"; 7 | 8 | interface CartItemProps { 9 | image: string; 10 | name: string; 11 | price: number; 12 | quantity: number; 13 | id: number; 14 | } 15 | 16 | const CartItem: React.FC = ({ 17 | price, 18 | quantity, 19 | name, 20 | image, 21 | id, 22 | }) => { 23 | const dispatch = useDispatch(); 24 | const removeItemFromFoodbasket = () => { 25 | dispatch(removeFromBasket({ id })); 26 | toast.error("Removed item from basket", { 27 | style: { 28 | borderRadius: "100px", 29 | }, 30 | }); 31 | }; 32 | const addItemToFoodbasket = () => { 33 | const product = { 34 | name, 35 | price, 36 | image, 37 | id, 38 | }; 39 | 40 | dispatch(addToBasket(product)); 41 | toast.success("Increased item", { 42 | style: { 43 | borderRadius: "100px", 44 | }, 45 | }); 46 | }; 47 | 48 | const total = price * quantity; 49 | 50 | return ( 51 |
52 |
53 |
54 | salad 61 |
62 |

{name}

63 |
64 |
65 | 68 |

69 | {" "} 70 | {quantity} × = {" "} 71 |

72 | 73 | 74 | 75 |
76 | 79 |
80 | ); 81 | }; 82 | 83 | export default CartItem; 84 | -------------------------------------------------------------------------------- /src/components/parcel/Order.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import toast from "react-hot-toast"; 3 | 4 | interface OrderProps { 5 | pickupaddress: string; 6 | zip: string; 7 | recipientphone: string; 8 | recipientsaddress: string; 9 | usermail: string; 10 | username: string; 11 | weight: string; 12 | id: string; 13 | } 14 | 15 | const Order: React.FC = ({ 16 | pickupaddress, 17 | recipientphone, 18 | zip, 19 | recipientsaddress, 20 | weight, 21 | id, 22 | }) => { 23 | const CopyLink = () => { 24 | navigator.clipboard.writeText(`https://www.voyagger.tech/parcel/${id}`); 25 | toast.success("copied link to clipboard"); 26 | }; 27 | 28 | return ( 29 | 35 |
36 |

Order ID

37 | 38 | #{id} 39 | 40 |
41 |
42 |

Status

43 |

Order Placed

44 |
45 |
46 |

Zip Code

47 |

{zip}

48 |
49 |
50 |
51 |

Pickup Address:

52 |

{pickupaddress}

53 |
54 |
55 |

Recipient‘s Phone:

56 |

{recipientphone}

57 |
58 |
59 |

Recipient‘s address:

60 |

{recipientsaddress}

61 |
62 |
63 |

Weight:

64 |

{weight}

65 |
66 |
67 | 68 | 71 |
72 | ); 73 | }; 74 | 75 | export default Order; 76 | -------------------------------------------------------------------------------- /src/components/OfferProduct.tsx: -------------------------------------------------------------------------------- 1 | import { PlusIcon } from "@heroicons/react/solid"; 2 | import { motion } from "framer-motion"; 3 | import Image from "next/image"; 4 | import { useRouter } from "next/router"; 5 | import Currency from "react-currency-formatter"; 6 | import toast from "react-hot-toast"; 7 | import { useDispatch } from "react-redux"; 8 | import { addToBasket } from "../slices/basketSlice"; 9 | 10 | interface FoodItemProps { 11 | image: string; 12 | name: string; 13 | price: number; 14 | active?: boolean; 15 | id: string; 16 | category: string; 17 | description: string; 18 | } 19 | 20 | const OfferProduct: React.FC = ({ 21 | name, 22 | price, 23 | image, 24 | active, 25 | id, 26 | category, 27 | description, 28 | }) => { 29 | const dispatch = useDispatch(); 30 | 31 | const router = useRouter(); 32 | 33 | const addItemTobasket = () => { 34 | const product = { 35 | name, 36 | price, 37 | image, 38 | active, 39 | id, 40 | description, 41 | }; 42 | 43 | dispatch(addToBasket(product)); 44 | toast.success("Added item to basket", { 45 | style: { 46 | borderRadius: "100px", 47 | }, 48 | }); 49 | }; 50 | 51 | return ( 52 | 58 | 63 | router.push(`/food/${id}/?category=${category.toLowerCase()}`) 64 | } 65 | > 66 | salad 74 | 75 |
80 |

{name}

81 |
82 | 83 | 89 |
90 |
91 |
92 | ); 93 | }; 94 | 95 | export default OfferProduct; 96 | -------------------------------------------------------------------------------- /src/pages/api/create-checkout-session.ts: -------------------------------------------------------------------------------- 1 | import { groupBy } from "lodash"; 2 | import path from "path"; 3 | import stripelib from "stripe"; 4 | 5 | // @ts-ignore: ENV vars would be present 6 | const stripe = new stripelib.Stripe(process.env.STRIPE_SECRET_KEY, { 7 | apiVersion: "2020-08-27", 8 | typescript: true, 9 | }); 10 | 11 | const handler = async ( 12 | req: { body: { items: any; email: string; name: string } }, 13 | res: { 14 | status: (arg0: number) => { 15 | (): any; 16 | new (): any; 17 | json: { (arg0: { id: any }): void; new (): any }; 18 | }; 19 | } 20 | ) => { 21 | const { items, email, name } = req.body; 22 | 23 | const groupedItems = Object.values(groupBy(items, "id")); 24 | 25 | const transformedItems = groupedItems.map(group => ({ 26 | description: group[0].description, 27 | quantity: group.length, 28 | price_data: { 29 | currency: "inr", 30 | unit_amount: group[0].price * 100, 31 | product_data: { 32 | name: group[0].name, 33 | images: [group[0].image], 34 | }, 35 | }, 36 | })); 37 | 38 | const groupedImages = Object.values( 39 | groupBy(items.map((item: { image: string }) => path.basename(item.image))) 40 | ).map(group => [group.length, group[0]]); 41 | 42 | const session = await stripe.checkout.sessions.create({ 43 | payment_method_types: ["card"], 44 | shipping_rates: [ 45 | // @ts-ignore: ENV vars would be present 46 | process.env.HOST === "http://localhost:3000" 47 | ? process.env.STRIPE_SHIPPING_RATE 48 | : "shr_1JLoNhSFCeAarzuF943bcl3G", 49 | ], 50 | shipping_address_collection: { 51 | allowed_countries: [ 52 | "GB", 53 | "AF", 54 | "QA", 55 | "RU", 56 | "AO", 57 | "US", 58 | "FR", 59 | "IN", 60 | "PL", 61 | "RS", 62 | "SG", 63 | "RO", 64 | "PT", 65 | "PH", 66 | "PK", 67 | "NP", 68 | "MN", 69 | "MX", 70 | "LK", 71 | "KR", 72 | "JP", 73 | "IL", 74 | "ID", 75 | "GR", 76 | "IT", 77 | "ES", 78 | "EG", 79 | "DE", 80 | "CN", 81 | "BR", 82 | "BD", 83 | "AU", 84 | "AE", 85 | ], 86 | }, 87 | line_items: transformedItems, 88 | mode: "payment", 89 | success_url: `${process.env.HOST}/success`, 90 | cancel_url: `${process.env.HOST}/cart`, 91 | metadata: { 92 | email, 93 | name, 94 | images: JSON.stringify(groupedImages), 95 | }, 96 | }); 97 | 98 | res.status(200).json({ id: session.id }); 99 | }; 100 | 101 | export default handler; 102 | -------------------------------------------------------------------------------- /src/pages/parcel/orders.tsx: -------------------------------------------------------------------------------- 1 | import { getSession, withPageAuthRequired } from "@auth0/nextjs-auth0/"; 2 | import { motion } from "framer-motion"; 3 | import { useRouter } from "next/router"; 4 | import { db } from "../../../firebase"; 5 | import Header from "../../components/Header"; 6 | import Order from "../../components/parcel/Order"; 7 | import { collection, where, getDocs, query } from "firebase/firestore"; 8 | import ParcelType from "../../types/ParcelType"; 9 | 10 | interface Props { 11 | orders: [ParcelType]; 12 | } 13 | 14 | const Orders: React.FC = ({ orders }) => { 15 | const router = useRouter(); 16 | return ( 17 |
18 |
19 |
20 | 26 |

27 | Your Parcels 28 |

29 | 30 |

31 | {orders?.length > 0 ? ( 32 | <> 33 | {orders?.length} Order{orders.length > 1 && "s"} 34 | 35 | ) : ( 36 | <> 37 | You don't have any parcels yet. Go visit the{" "} 38 | {" "} 44 | to send some parcels. 45 | 46 | )} 47 |

48 |
49 | 50 | {orders?.map((order: ParcelType) => ( 51 |
52 | 53 |
54 | ))} 55 |
56 |
57 | ); 58 | }; 59 | 60 | export default Orders; 61 | 62 | export const getServerSideProps = withPageAuthRequired({ 63 | async getServerSideProps(context) { 64 | const user = getSession(context.req, context.res); 65 | 66 | const parcelOrdersQuery = query( 67 | collection(db, "parcels"), 68 | where("usermail", "==", "user?.user.email") 69 | ); 70 | 71 | const ParcelOrders = await getDocs(parcelOrdersQuery); 72 | 73 | const orders = await Promise.all( 74 | ParcelOrders.docs.map(async order => ({ 75 | id: order.id, 76 | pickupaddress: order.data().pickupaddress, 77 | recipientphone: order.data().recipientphone, 78 | recipientsaddress: order.data().recipientsaddress, 79 | usermail: order.data().usermail, 80 | username: order.data().username, 81 | weight: order.data().weight, 82 | zip: order.data().zip, 83 | })) 84 | ); 85 | 86 | return { 87 | props: { 88 | orders, 89 | user, 90 | }, 91 | }; 92 | }, 93 | }); 94 | -------------------------------------------------------------------------------- /src/pages/food/index.tsx: -------------------------------------------------------------------------------- 1 | import { getSession, withPageAuthRequired } from "@auth0/nextjs-auth0/"; 2 | import { NextSeo } from "next-seo"; 3 | import React, { useEffect } from "react"; 4 | import { db } from "../../../firebase"; 5 | import Cart from "../../components/Cart"; 6 | import Main from "../../components/eats/Main"; 7 | import Header from "../../components/Header"; 8 | import { Category, Salad } from "../../types/itemTypes"; 9 | import { user } from "../../types/userType"; 10 | import { 11 | collection, 12 | query, 13 | getDoc, 14 | getDocs, 15 | updateDoc, 16 | doc, 17 | orderBy, 18 | } from "firebase/firestore"; 19 | 20 | interface HomeProps { 21 | salads: [Salad]; 22 | categories: [Category]; 23 | user: user; 24 | dbuser: user; 25 | category: string; 26 | } 27 | 28 | const Home: React.FC = ({ 29 | salads, 30 | categories, 31 | user, 32 | dbuser, 33 | category, 34 | }) => { 35 | useEffect(() => { 36 | if (user?.email) { 37 | const updateUsers = async () => { 38 | const userRef = doc(db, `users/${user?.email}`); 39 | 40 | await updateDoc(userRef, { 41 | email: user?.email, 42 | name: user?.name, 43 | photoURL: user?.picture, 44 | }); 45 | }; 46 | 47 | updateUsers(); 48 | } 49 | }, [user]); 50 | 51 | return ( 52 |
53 | 54 |
55 |
56 |
61 |
62 | 63 |
64 | ); 65 | }; 66 | 67 | export default Home; 68 | 69 | export const getServerSideProps = withPageAuthRequired({ 70 | async getServerSideProps(context: any) { 71 | let category = context.query.category; 72 | 73 | if (!category) { 74 | category = "pizza"; 75 | } 76 | 77 | const session = getSession(context.req, context.res); 78 | 79 | const userref = doc(db, `users/${session?.user.email}`); 80 | 81 | const userRes = await getDoc(userref); 82 | 83 | const dbuser = { 84 | id: userRes.id, 85 | ...userRes.data(), 86 | }; 87 | 88 | const saladsRef = collection(db, `products/food/${category}`); 89 | 90 | const saladsQuery = query(saladsRef, orderBy("active", "desc")); 91 | 92 | const allsalads = await getDocs(saladsQuery); 93 | 94 | const salads = allsalads.docs.map(salad => ({ 95 | id: salad.id, 96 | ...salad.data(), 97 | })); 98 | 99 | const categoriesRef = collection(db, "products/food/categories"); 100 | 101 | const allcategories = await getDocs(categoriesRef); 102 | 103 | const categories = allcategories.docs.map(salad => ({ 104 | id: salad.id, 105 | ...salad.data(), 106 | })); 107 | 108 | return { 109 | props: { 110 | salads, 111 | categories, 112 | category, 113 | dbuser, 114 | }, 115 | }; 116 | }, 117 | }); 118 | -------------------------------------------------------------------------------- /src/components/Order.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { groupBy } from "lodash"; 3 | import moment from "moment"; 4 | import Image from "next/image"; 5 | import path from "path"; 6 | import Currency from "react-currency-formatter"; 7 | 8 | interface OrderProps { 9 | id: string; 10 | amount: number; 11 | amountShipping: number; 12 | images: [string]; 13 | timestamp: number; 14 | } 15 | 16 | const Order: React.FC = ({ 17 | id, 18 | amount, 19 | amountShipping, 20 | images, 21 | timestamp, 22 | }) => { 23 | let groupedImages; 24 | 25 | if (images.every(image => !image.startsWith("["))) { 26 | groupedImages = Object.values( 27 | groupBy(images.map(image => path.basename(image))) 28 | ).map(group => [group.length, group[0]]); 29 | } else { 30 | groupedImages = [...images.map(image => JSON.parse(image))]; 31 | } 32 | 33 | return ( 34 | 40 |
41 |
42 |

ORDER PLACED

43 |

{moment.unix(timestamp).format("MM/DD/YYYY")}

44 |
45 | 46 |
47 |

TOTAL

48 |

49 | 50 | 51 | {" "} 52 | (Including for{" "} 53 | {""} 54 | Delivery) 55 |

56 |
57 | 58 |

59 | {images.length} {""} 60 | {images.length === 1 ? "item" : "items"} 61 |

62 | 63 |

64 | ORDER #{id} 65 |

66 |
67 | 68 |
69 |
70 | {groupedImages.map(group => ( 71 |
72 |
73 |
74 | image 80 |
81 |
82 | {group[0] > 1 && ( 83 |
84 | × {group[0]} 85 |
86 | )} 87 |
88 | ))} 89 |
90 |
91 |
92 | ); 93 | }; 94 | 95 | export default Order; 96 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | # Auth0 2 | AUTH0_SECRET='791dbc2bcd85f887912a5c98f26e0e7090442dfa0a1dfe33b5848188fa3839b4' 3 | AUTH0_BASE_URL='http://localhost:3000' 4 | AUTH0_ISSUER_BASE_URL='https://avneesh0612.us.auth0.com' 5 | AUTH0_CLIENT_ID='7SqxihF6VNb0yNCudX2qjmd2vd84Ztsu' 6 | AUTH0_CLIENT_SECRET='r1YTMuh0VZbE_WQnUuA-YIy97aOqMztDVokQwqbBO6PAYx0bMLPKXNHY0MjzQxwQ' 7 | 8 | # Firebase 9 | FIREBASE_MEASUREMENT_ID="G-6YT0T2GW78" 10 | FIREBASE_APP_ID="1:981973597448:web:b2ac192135988df2a340bd" 11 | FIREBASE_MESSAGEING_SENDER_ID="981973597448" 12 | FIREBASE_STORAGE_BUCKET="voyager-developmen.appspot.com" 13 | FIREBASE_PROJECT_ID="voyager-developmen" 14 | FIREBASE_AUTH_DOMAIN="voyager-developmen.firebaseapp.com" 15 | FIREBASE_CONFIG_API_KEY="AIzaSyC61I6HDzJPfxWN7ANmNpUxrcZTWdcSZzk" 16 | 17 | # Stripe 18 | STRIPE_SIGNING_SECRET="whsec_QcwcdZEGlDIsG5BQe9V0iFGotphZAiJ7" 19 | HOST="http://localhost:3000" 20 | STRIPE_SECRET_KEY="sk_test_51JQY90SFCK0n3kd3T7WIc1h0B9vlGsJrwetby1i7hongwgGufUsz8IioqTPMHxix5BZ4uYmfwrCcWKZJAXXPAsh600qPCQO6KJ" 21 | STRIPE_PUBLIC_KEY="pk_test_51JQY90SFCK0n3kd3cbqLphItaBERQdMyIHdlEwx9Gv11laqkYB51T54nPAaf7wsqVLCAG52f5Qc5Yo3JiCewMTvm00yvevpMcs" 22 | STRIPE_SHIPPING_RATE=shr_1JQa0SSFCK0n3kd38NRJOcjE 23 | 24 | # Firebase permissions 25 | PERMISSION_TYPE="service_account" 26 | PERMISSION_PROJECT_ID="voyager-developmen" 27 | PERMISSION_PRIVATE_KEY_ID="4781a8bece41a3cfdea93a0d1019f368d3502ef2" 28 | PERMISSION_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCUTZ749Zkq3p+x\nWmQ/cYKpBVzWjkgNiUeq/AtbIPfpiJut9zY459QQ8AFc+e2lB1OcNl0Jud3y7Tl4\ncImtziov1jQ8X4W3RiBhJ7MD3c9/wXlBMqR1sy0qI31dhm4Pxcpo8XZCjxMyvmZ7\nFB7kp3cQBnveNkUIpvBQH376JmNBID9fBJ15ZBoVdFAYWohbjrSmskeDzAHXNsph\nvBRb38frVg1/5B7RRGw5U9WhKDiKGVkHYeilUngNC8ths8mMal5uxd+6Zm6nPeVu\ngCzPrKxBGgXLqIB5ghO0FGbYIr/yhxYCIMiu6LO0BUrj9zvpH3HFc7KaQAJfjVq5\nFQS/+PjBAgMBAAECggEAQsdzAqrgxg+J/Bv2USUlT0Olas2Vv1amKWWmHWpKh+Om\nKl9LkLM/aEMSchHugyW57fkCyvrhaN6ORt/x9wwDLhg33EmtFfpjYSw1rVOHeIEf\nvw51RLSibTue22rJi5umSbwU4uK3I93dmqVUReTstZAd3dE1I7C8PG/6RtzbS7rw\noDOfP92EcqU0yRimOirU0QEDop146ZVQRBLtDBGPcnKmEt2/QtGTBzdEF4Td70CP\n8Vvoqm5nySqhI/2MKyjCxv0jp64BW1DR8243mmyv3eeigt7m9m1KCYH1ULhPeCfH\nLfJ4NUXzgn1Q2ypFjvvf7ZNL6Tzm4xkWS9MHQLs+2wKBgQDFXgNmof4s2uSkq1ag\nmi4Z81R9PnAoCR+ZUXeqwcfkNjtxhS79PX8PaqVlr8wNk2I7leGtzbfYXSoGN6QN\nZhBJer4FEAvgmn79/jSoCpYJrSg88aarKMvRb7lo/zrGtbCnPJ2T0mDsAY/SVIIP\nP113SY5KKyi6kPrAWpBqafd9vwKBgQDAXDx9drSk5ooSErOgCrAxLkyumwolbqFZ\nXRlWGu1mWMEzk7iPm9t8G65wXqe59rcfOydxN70Gxjqos3PazPrEZWqZW7J8yuxt\ncRDLMzI1wU4ucoeVgAFU4XdY0tLvc+Lh8QzqHLxyd4lJI6q6FUZWD426CuXQtIbN\nP4YvdHIpfwKBgDw7FI6doRPPOTeHkkgwxSDmQUJ3a4LMRfhkBED4Iihi5IEgQ9bE\njaIGybLek0cRU0kb1GNWBGTjCZAcKtRr8Ux7SMICw50niNm6WhduI5uQXFc858AU\nEx83GT4Rpb4+dEqVFQGnkixzzZBCee5tR/i/Wc0InsVQuTU6bhgLfpvBAoGAL8N3\nXavpBP0dkYlFQtsEjuGxNrXWmh7TP45HaUL8aapmJrlqXXZU1IdHFC3ctedV5xJY\nI9u0Owdjr1oHzW+SYMvR4UyMkEIO3MnzYpFOyVw7XnsfwXZsXjgx20NWDxEWaAXj\nsAn8nOujkh6iGNyJf3sTNPvZvq3kvvgkCIqAgl8CgYBYeUVvtnsLSVADWUvWcdYF\neoOeqcsx5QvPcmea98IvQOmdu07tDUfGFuyAFoqIxkqYRsmlVgGBbmkRdufXS3Cu\nnZXKJnMTBpLSWgp4HC4q7wfGVwqnUmf8kuXYSmims2FzxmPQGsE/H7AZ0lwjYOuj\nhQ0NXJaX/xoPCNQ0qyCF7Q==\n-----END PRIVATE KEY-----\n" 29 | PERMISSION_CLIENT_EMAIL="firebase-adminsdk-n5ggx@voyager-developmen.iam.gserviceaccount.com" 30 | PERMISSION_CLIENT_ID="115109409345022971033" 31 | PERMISSION_AUTH_URI="https://accounts.google.com/o/oauth2/auth" 32 | PERMISSION_TOKEN_URI="https://oauth2.googleapis.com/token" 33 | PERMISSION_AUTH__X509_CERT_URL="https://www.googleapis.com/oauth2/v1/certs" 34 | PERMISSION_CLIENT__X509_CERT_URL="https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-n5ggx%40voyager-developmen.iam.gserviceaccount.com" 35 | -------------------------------------------------------------------------------- /src/pages/parcel/[id].tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import toast from "react-hot-toast"; 3 | import { db } from "../../../firebase"; 4 | import Header from "../../components/Header"; 5 | import { getDoc, doc } from "firebase/firestore"; 6 | 7 | interface ParcelProps { 8 | parcel: { 9 | pickupaddress: string; 10 | zip: string; 11 | recipientphone: string; 12 | recipientsaddress: string; 13 | usermail: string; 14 | username: string; 15 | weight: string; 16 | id: string; 17 | }; 18 | } 19 | 20 | const parcelTrack: React.FC = ({ parcel }) => { 21 | const CopyLink = () => { 22 | navigator.clipboard.writeText( 23 | `https://www.voyagger.tech/parcel/${parcel.id}` 24 | ); 25 | toast.success("copied link to clipboard"); 26 | }; 27 | 28 | return ( 29 |
30 |
31 |
32 | 38 |
39 |

Order parcel.ID

40 | 41 | #{parcel.id} 42 | 43 |
44 |
45 |

Status

46 |

Order Placed

47 |
48 |
49 |

Zip Code

50 |

{parcel.zip}

51 |
52 |
53 |
54 |

Pickup Address:

55 |

{parcel.pickupaddress}

56 |
57 |
58 |

Recipient‘s Phone:

59 |

{parcel.recipientphone}

60 |
61 |
62 |

Recipient‘s address:

63 |

{parcel.recipientsaddress}

64 |
65 |
66 |

Weight:

67 |

{parcel.weight}

68 |
69 |
70 | 76 |
77 |
78 |
79 | ); 80 | }; 81 | 82 | export default parcelTrack; 83 | 84 | export async function getServerSideProps(context: any) { 85 | const id = context.query.id; 86 | 87 | const parcelRef = doc(db, `parcels/${id}`); 88 | 89 | const parcelRes = await getDoc(parcelRef); 90 | 91 | const parcel = { 92 | id: parcelRes.id, 93 | ...parcelRes.data(), 94 | }; 95 | 96 | return { 97 | props: { 98 | parcel, 99 | }, 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /src/pages/orders.tsx: -------------------------------------------------------------------------------- 1 | import { getSession, withPageAuthRequired } from "@auth0/nextjs-auth0/"; 2 | import { motion } from "framer-motion"; 3 | import moment from "moment"; 4 | import { NextSeo } from "next-seo"; 5 | import { useRouter } from "next/router"; 6 | import { db } from "../../firebase"; 7 | import Header from "../components/Header"; 8 | import Order from "../components/Order"; 9 | import { orderType } from "../types/orderTypes"; 10 | import { user } from "../types/userType"; 11 | import { getDocs, collection, query, orderBy } from "firebase/firestore"; 12 | 13 | interface OrdersProps { 14 | orders: [orderType]; 15 | user: user; 16 | } 17 | 18 | const Orders: React.FC = ({ orders, user }) => { 19 | const router = useRouter(); 20 | 21 | return ( 22 |
23 |
24 | 25 |
26 | 32 |

33 | Your orders 34 |

35 | 36 | {user ? ( 37 |

38 | {orders?.length > 0 ? ( 39 | <> 40 | {orders?.length} Order{orders.length > 1 && "s"} 41 | 42 | ) : ( 43 | <> 44 | You don't have any order yet. Go visit the{" "} 45 | {" "} 51 | to purchase some items. 52 | 53 | )} 54 |

55 | ) : ( 56 |

Please sign in to see your orders.

57 | )} 58 |
59 | 60 |
61 | {orders?.map((order: orderType) => ( 62 | 70 | ))} 71 |
72 |
73 |
74 | ); 75 | }; 76 | 77 | export default Orders; 78 | 79 | export const getServerSideProps = withPageAuthRequired({ 80 | async getServerSideProps(context: any) { 81 | const user = getSession(context.req, context.res); 82 | 83 | const stripeOrdersRef = collection(db, `users/${user?.user.email}/orders`); 84 | 85 | const stripeOrdersQuery = query( 86 | stripeOrdersRef, 87 | orderBy("timestamp", "desc") 88 | ); 89 | 90 | const stripeOrders = await getDocs(stripeOrdersQuery); 91 | 92 | const orders = await Promise.all( 93 | stripeOrders.docs.map(async order => ({ 94 | id: order.id, 95 | amount: order.data().amount, 96 | amountShipping: order.data().amount_shipping, 97 | images: order.data().images, 98 | timestamp: moment(order.data().timestamp.toDate()).unix(), 99 | })) 100 | ); 101 | return { 102 | props: { 103 | orders, 104 | user, 105 | }, 106 | }; 107 | }, 108 | }); 109 | -------------------------------------------------------------------------------- /src/pages/books/index.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import Image from "next/image"; 3 | import { useRouter } from "next/router"; 4 | import Header from "../../components/Header"; 5 | import book from "../../types/bookTypes"; 6 | import requests from "../../utils/requests"; 7 | 8 | interface Booksprops { 9 | books: { 10 | items: [book]; 11 | }; 12 | routertitle: string; 13 | } 14 | 15 | const Index: React.FC = ({ books, routertitle }) => { 16 | const router = useRouter(); 17 | 18 | return ( 19 |
20 |
21 |
22 | 27 | {Object.entries(requests).map(([key, { title }]) => ( 28 |

router.push(`/books/?volume=${key}`)} 34 | > 35 | {title} 36 |

37 | ))} 38 |
39 | 44 | {books.items.map((book: book) => ( 45 | 56 | {book.volumeInfo.imageLinks?.thumbnail && ( 57 | 63 | 64 | {book.volumeInfo.title} 70 | 71 | 72 | )} 73 |

74 | {book?.volumeInfo.title.slice(0, 25)}{" "} 75 | {book?.volumeInfo.title.length > 25 ? "..." : ""} 76 |

77 |

78 | {book?.volumeInfo.authors}{" "} 79 |

80 |
81 | ))} 82 |
83 |
84 |
85 | ); 86 | }; 87 | 88 | export default Index; 89 | 90 | export async function getServerSideProps(context: any) { 91 | let volume = context.query.volume; 92 | 93 | if (!volume) { 94 | volume = "fetchFiction"; 95 | } 96 | 97 | // @ts-ignore 98 | const routertitle = requests[volume].title; 99 | 100 | // @ts-ignore 101 | const URL = `https://www.googleapis.com/books/v1${requests[volume]?.url}&maxResults=20`; 102 | 103 | const request = await fetch(URL).then(res => res.json()); 104 | 105 | return { 106 | props: { 107 | books: request, 108 | volume, 109 | routertitle, 110 | }, 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /src/pages/food/[id].tsx: -------------------------------------------------------------------------------- 1 | import { getSession, withPageAuthRequired } from "@auth0/nextjs-auth0/"; 2 | import { PlusIcon } from "@heroicons/react/solid"; 3 | import { NextSeo } from "next-seo"; 4 | import Image from "next/image"; 5 | import Currency from "react-currency-formatter"; 6 | import toast from "react-hot-toast"; 7 | import { useDispatch } from "react-redux"; 8 | import { db } from "../../../firebase"; 9 | import Cart from "../../components/Cart"; 10 | import { addToBasket } from "../../slices/basketSlice"; 11 | import { Salad } from "../../types/itemTypes"; 12 | import { user } from "../../types/userType"; 13 | import { doc, getDoc } from "firebase/firestore"; 14 | 15 | interface ProductProps { 16 | currentProduct: Salad; 17 | user: user; 18 | dbuser: user; 19 | } 20 | 21 | const Product: React.FC = ({ currentProduct, user, dbuser }) => { 22 | const name = currentProduct.name; 23 | const price = currentProduct.price; 24 | const image = currentProduct.image; 25 | const active = currentProduct.active; 26 | const id = currentProduct.id; 27 | const description = currentProduct.description; 28 | const dispatch = useDispatch(); 29 | 30 | const addItemToFoodbasket = () => { 31 | const product = { 32 | name, 33 | price, 34 | image, 35 | active, 36 | id, 37 | description, 38 | }; 39 | 40 | dispatch(addToBasket(product)); 41 | toast.success("Added item to basket", { 42 | style: { 43 | borderRadius: "100px", 44 | }, 45 | }); 46 | }; 47 | 48 | return ( 49 |
50 | 51 |
52 |
53 |
54 | salad 62 |
63 |
64 |

{name}

65 |

{description}

66 |
67 | 68 | 74 |
75 |
76 |
77 |
78 | 79 |
80 | ); 81 | }; 82 | 83 | export default Product; 84 | 85 | export const getServerSideProps = withPageAuthRequired({ 86 | async getServerSideProps(context: any) { 87 | const categoryId = context.query.category; 88 | const session = getSession(context.req, context.res); 89 | 90 | const userref = doc(db, `users/${session?.user.email}`); 91 | 92 | const userRes = await getDoc(userref); 93 | 94 | const dbuser = { 95 | id: userRes.id, 96 | ...userRes.data(), 97 | }; 98 | 99 | const pathId = context.query.id; 100 | 101 | const ref = doc(db, `products/food/${categoryId}/${pathId}`); 102 | 103 | const productRes = await getDoc(ref); 104 | 105 | const currentProduct = { 106 | id: productRes.id, 107 | ...productRes.data(), 108 | }; 109 | 110 | return { props: { currentProduct, pathId, categoryId, dbuser } }; 111 | }, 112 | }); 113 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { useUser } from "@auth0/nextjs-auth0"; 2 | import { motion } from "framer-motion"; 3 | import { NextSeo } from "next-seo"; 4 | import { useEffect } from "react"; 5 | import { db } from "../../firebase"; 6 | import Banner from "../components/Banner"; 7 | import Footer from "../components/Footer"; 8 | import Header from "../components/Header"; 9 | import Items from "../components/Items"; 10 | import Image from "next/image"; 11 | import { updateDoc, doc } from "firebase/firestore"; 12 | 13 | const Home: React.FC = () => { 14 | const { user, isLoading } = useUser(); 15 | 16 | useEffect(() => { 17 | if (user?.email) { 18 | const updateUser = async () => { 19 | const userRef = doc(db, `users/${user?.email}`); 20 | await updateDoc(userRef, { 21 | email: user?.email, 22 | name: user?.name, 23 | photoURL: user?.picture, 24 | }); 25 | }; 26 | updateUser(); 27 | } 28 | }); 29 | 30 | return ( 31 | <> 32 | {isLoading ? ( 33 |
34 | 35 | Voyagger 43 | 44 |
45 | ) : ( 46 |
47 |
48 |
49 |
50 | 54 |
55 | 56 |
57 | 63 | What do you want to do? 64 | 65 |
66 | 71 | 76 | 77 | 82 | 87 | 88 |
89 |
90 | 95 | 100 | 101 | 106 | 112 | 113 |
114 |
115 |
116 |
117 | )} 118 | 119 | ); 120 | }; 121 | 122 | export default Home; 123 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Voyagger 2 | 3 | ## Setting up workflow 4 | 5 | - Fork this repository 6 | - If you have yarn installed 7 | 8 | ``` 9 | yarn install 10 | ``` 11 | 12 | - If you don't have yarn installed 13 | 14 | ``` 15 | npm i -g yarn 16 | yarn install 17 | ``` 18 | 19 | ## Environment Variables 20 | 21 | To run this project, you will need to add the following environment variables to your .env file 22 | 23 | Auth0 24 | 25 | ``` 26 | AUTH0_SECRET 27 | AUTH0_BASE_URL 28 | AUTH0_ISSUER_BASE_URL 29 | AUTH0_CLIENT_ID 30 | AUTH0_CLIENT_SECRET 31 | ``` 32 | 33 | Firebase 34 | 35 | ``` 36 | FIREBASE_CONFIG_API_KEY 37 | FIREBASE_AUTH_DOMAIN 38 | FIREBASE_PROJECT_ID 39 | FIREBASE_STORAGE_BUCKET 40 | FIREBASE_MESSAGEING_SENDER_ID 41 | FIREBASE_APP_ID 42 | FIREBASE_MEASUREMENT_ID 43 | ``` 44 | 45 | Stripe 46 | 47 | ``` 48 | STRIPE_PUBLIC_KEY 49 | STRIPE_SECRET_KEY 50 | STRIPE_SIGNING_SECRET 51 | ``` 52 | 53 | Firebase permissions for Stripe webhook 54 | 55 | ``` 56 | PERMISSION_TYPE 57 | PERMISSION_PROJECT_ID 58 | PERMISSION_PRIVATE_KEY_ID 59 | PERMISSION_PRIVATE_KEY 60 | PERMISSION_CLIENT_EMAIL 61 | PERMISSION_CLIENT_ID 62 | PERMISSION_AUTH_URI 63 | PERMISSION_TOKEN_URI 64 | PERMISSION_AUTH_PROVIDER_URL 65 | PERMISSION_CLIENT_PROVIDER_URL 66 | HOST=http://localhost:3000 67 | ``` 68 | 69 | I created development credentials for making the job of contributing easier, so you can simply copy and paste these into `.env.local` to get your app up and running perfectly 70 | 71 | ``` 72 | # Auth0 73 | AUTH0_SECRET='791dbc2bcd85f887912a5c98f26e0e7090442dfa0a1dfe33b5848188fa3839b4' 74 | AUTH0_BASE_URL='http://localhost:3000' 75 | AUTH0_ISSUER_BASE_URL='https://avneesh0612.us.auth0.com' 76 | AUTH0_CLIENT_ID='7SqxihF6VNb0yNCudX2qjmd2vd84Ztsu' 77 | AUTH0_CLIENT_SECRET='r1YTMuh0VZbE_WQnUuA-YIy97aOqMztDVokQwqbBO6PAYx0bMLPKXNHY0MjzQxwQ' 78 | 79 | # Firebase 80 | FIREBASE_MEASUREMENT_ID="G-6YT0T2GW78" 81 | FIREBASE_APP_ID="1:981973597448:web:b2ac192135988df2a340bd" 82 | FIREBASE_MESSAGEING_SENDER_ID="981973597448" 83 | FIREBASE_STORAGE_BUCKET="voyager-developmen.appspot.com" 84 | FIREBASE_PROJECT_ID="voyager-developmen" 85 | FIREBASE_AUTH_DOMAIN="voyager-developmen.firebaseapp.com" 86 | FIREBASE_CONFIG_API_KEY="AIzaSyC61I6HDzJPfxWN7ANmNpUxrcZTWdcSZzk" 87 | 88 | # Stripe 89 | STRIPE_SIGNING_SECRET="whsec_QcwcdZEGlDIsG5BQe9V0iFGotphZAiJ7" 90 | HOST="http://localhost:3000" 91 | STRIPE_SECRET_KEY="sk_test_51JQY90SFCK0n3kd3T7WIc1h0B9vlGsJrwetby1i7hongwgGufUsz8IioqTPMHxix5BZ4uYmfwrCcWKZJAXXPAsh600qPCQO6KJ" 92 | STRIPE_PUBLIC_KEY="pk_test_51JQY90SFCK0n3kd3cbqLphItaBERQdMyIHdlEwx9Gv11laqkYB51T54nPAaf7wsqVLCAG52f5Qc5Yo3JiCewMTvm00yvevpMcs" 93 | STRIPE_SHIPPING_RATE=shr_1JQa0SSFCK0n3kd38NRJOcjE 94 | 95 | # Firebase permissions 96 | PERMISSION_TYPE="service_account" 97 | PERMISSION_PROJECT_ID="voyager-developmen" 98 | PERMISSION_PRIVATE_KEY_ID="4781a8bece41a3cfdea93a0d1019f368d3502ef2" 99 | PERMISSION_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCUTZ749Zkq3p+x\nWmQ/cYKpBVzWjkgNiUeq/AtbIPfpiJut9zY459QQ8AFc+e2lB1OcNl0Jud3y7Tl4\ncImtziov1jQ8X4W3RiBhJ7MD3c9/wXlBMqR1sy0qI31dhm4Pxcpo8XZCjxMyvmZ7\nFB7kp3cQBnveNkUIpvBQH376JmNBID9fBJ15ZBoVdFAYWohbjrSmskeDzAHXNsph\nvBRb38frVg1/5B7RRGw5U9WhKDiKGVkHYeilUngNC8ths8mMal5uxd+6Zm6nPeVu\ngCzPrKxBGgXLqIB5ghO0FGbYIr/yhxYCIMiu6LO0BUrj9zvpH3HFc7KaQAJfjVq5\nFQS/+PjBAgMBAAECggEAQsdzAqrgxg+J/Bv2USUlT0Olas2Vv1amKWWmHWpKh+Om\nKl9LkLM/aEMSchHugyW57fkCyvrhaN6ORt/x9wwDLhg33EmtFfpjYSw1rVOHeIEf\nvw51RLSibTue22rJi5umSbwU4uK3I93dmqVUReTstZAd3dE1I7C8PG/6RtzbS7rw\noDOfP92EcqU0yRimOirU0QEDop146ZVQRBLtDBGPcnKmEt2/QtGTBzdEF4Td70CP\n8Vvoqm5nySqhI/2MKyjCxv0jp64BW1DR8243mmyv3eeigt7m9m1KCYH1ULhPeCfH\nLfJ4NUXzgn1Q2ypFjvvf7ZNL6Tzm4xkWS9MHQLs+2wKBgQDFXgNmof4s2uSkq1ag\nmi4Z81R9PnAoCR+ZUXeqwcfkNjtxhS79PX8PaqVlr8wNk2I7leGtzbfYXSoGN6QN\nZhBJer4FEAvgmn79/jSoCpYJrSg88aarKMvRb7lo/zrGtbCnPJ2T0mDsAY/SVIIP\nP113SY5KKyi6kPrAWpBqafd9vwKBgQDAXDx9drSk5ooSErOgCrAxLkyumwolbqFZ\nXRlWGu1mWMEzk7iPm9t8G65wXqe59rcfOydxN70Gxjqos3PazPrEZWqZW7J8yuxt\ncRDLMzI1wU4ucoeVgAFU4XdY0tLvc+Lh8QzqHLxyd4lJI6q6FUZWD426CuXQtIbN\nP4YvdHIpfwKBgDw7FI6doRPPOTeHkkgwxSDmQUJ3a4LMRfhkBED4Iihi5IEgQ9bE\njaIGybLek0cRU0kb1GNWBGTjCZAcKtRr8Ux7SMICw50niNm6WhduI5uQXFc858AU\nEx83GT4Rpb4+dEqVFQGnkixzzZBCee5tR/i/Wc0InsVQuTU6bhgLfpvBAoGAL8N3\nXavpBP0dkYlFQtsEjuGxNrXWmh7TP45HaUL8aapmJrlqXXZU1IdHFC3ctedV5xJY\nI9u0Owdjr1oHzW+SYMvR4UyMkEIO3MnzYpFOyVw7XnsfwXZsXjgx20NWDxEWaAXj\nsAn8nOujkh6iGNyJf3sTNPvZvq3kvvgkCIqAgl8CgYBYeUVvtnsLSVADWUvWcdYF\neoOeqcsx5QvPcmea98IvQOmdu07tDUfGFuyAFoqIxkqYRsmlVgGBbmkRdufXS3Cu\nnZXKJnMTBpLSWgp4HC4q7wfGVwqnUmf8kuXYSmims2FzxmPQGsE/H7AZ0lwjYOuj\nhQ0NXJaX/xoPCNQ0qyCF7Q==\n-----END PRIVATE KEY-----\n" 100 | PERMISSION_CLIENT_EMAIL="firebase-adminsdk-n5ggx@voyager-developmen.iam.gserviceaccount.com" 101 | PERMISSION_CLIENT_ID="115109409345022971033" 102 | PERMISSION_AUTH_URI="https://accounts.google.com/o/oauth2/auth" 103 | PERMISSION_TOKEN_URI="https://oauth2.googleapis.com/token" 104 | PERMISSION_AUTH__X509_CERT_URL="https://www.googleapis.com/oauth2/v1/certs" 105 | PERMISSION_CLIENT__X509_CERT_URL="https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-n5ggx%40voyager-developmen.iam.gserviceaccount.com" 106 | ``` 107 | 108 | ## Starting the development server 109 | 110 | ``` 111 | yarn dev 112 | ``` 113 | 114 | #### Now you are ready to work on a new features/ fixing issues. 115 | 116 | When you are done, make a pull request and I will merge it soon 🥳🥳 117 | -------------------------------------------------------------------------------- /src/pages/parcel/index.tsx: -------------------------------------------------------------------------------- 1 | import { withPageAuthRequired } from "@auth0/nextjs-auth0/"; 2 | import { motion } from "framer-motion"; 3 | import { useRouter } from "next/router"; 4 | import React, { useState } from "react"; 5 | import toast from "react-hot-toast"; 6 | import { db } from "../../../firebase"; 7 | import Header from "../../components/Header"; 8 | import { user } from "../../types/userType"; 9 | import { collection, addDoc } from "firebase/firestore"; 10 | 11 | interface parcelProps { 12 | user: user; 13 | } 14 | 15 | const Index: React.FC = ({ user }) => { 16 | const [pickupaddress, setpickupaddress] = useState(""); 17 | const [recipientsaddress, setrecipientsaddress] = useState(""); 18 | const [recipientphone, setrecipientphone] = useState(""); 19 | const [zip, setzip] = useState(""); 20 | const [weight, setWeight] = useState("Under 1/2 kg"); 21 | const router = useRouter(); 22 | 23 | const pattern = new RegExp(/^[0-9\b]+$/); 24 | 25 | const addParcel = async (e: React.MouseEvent) => { 26 | e.preventDefault(); 27 | 28 | if (!pickupaddress) return toast.error("Please add your pickup address"); 29 | 30 | if (!recipientsaddress) 31 | return toast.error("Please add your recipeinet address"); 32 | 33 | if (!zip) return toast.error("Please add the zip code"); 34 | 35 | if (!recipientphone) 36 | return toast.error("Please add recipient's phone number"); 37 | if (!recipientphone) 38 | return toast.error("Please add recipient's phone number"); 39 | 40 | if (!pattern.test(recipientphone)) { 41 | return toast.error("Please enter a valid phone number"); 42 | } else if (recipientphone.length != 10) { 43 | return toast.error("Please enter a valid phone number"); 44 | } 45 | 46 | await addDoc(collection(db, "parcels"), { 47 | usermail: user.email, 48 | username: user.name, 49 | pickupaddress: pickupaddress, 50 | recipientsaddress: recipientsaddress, 51 | zip: zip, 52 | recipientphone: recipientphone, 53 | weight: weight, 54 | }); 55 | 56 | setpickupaddress(""); 57 | setrecipientsaddress(""); 58 | setWeight(""); 59 | setrecipientphone(""); 60 | toast.success("parcel added successfully"); 61 | router.push("/parcel/orders"); 62 | }; 63 | 64 | return ( 65 |
66 |
67 |
68 |
69 | 70 | setpickupaddress(e.target.value)} 74 | className="w-full rounded-lg bg-white/50 focus:outline-none pl-3 h-10 " 75 | /> 76 |
77 |
78 | 81 | setrecipientsaddress(e.target.value)} 85 | className="w-full rounded-lg bg-white/50 focus:outline-none pl-3 h-10 " 86 | /> 87 |
88 | 89 |
90 | 91 | setzip(e.target.value)} 95 | className="w-full rounded-lg bg-white/50 focus:outline-none pl-3 h-10 " 96 | /> 97 |
98 | 99 |
100 | 103 | setrecipientphone(e.target.value)} 107 | className="w-full rounded-lg bg-white/50 focus:outline-none pl-3 h-10 " 108 | /> 109 |
110 | 111 |
112 | 115 | 125 |
126 | 135 | Submit 136 | 137 |
138 |
139 | ); 140 | }; 141 | 142 | export default Index; 143 | 144 | export const getServerSideProps = withPageAuthRequired(); 145 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | avneeshagarwal0612@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | import { ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/outline"; 4 | import { motion } from "framer-motion"; 5 | import { useState } from "react"; 6 | import { useSelector } from "react-redux"; 7 | import { useUser } from "@auth0/nextjs-auth0"; 8 | import { selectItems } from "../slices/basketSlice"; 9 | import { ShoppingBagIcon } from "@heroicons/react/solid"; 10 | 11 | function Header() { 12 | const user = useUser(); 13 | const items = useSelector(selectItems); 14 | 15 | const [isNavOpen, setIsNavOpen] = useState(false); 16 | 17 | return ( 18 | 23 |
24 | 25 | 26 | Voyagger 34 | 35 | 36 |
37 | 45 | 46 | My orders 47 | 48 | 49 | 50 | 58 | 59 | Parcel tracking 60 | 61 | 62 |
63 |
64 |
65 | 73 | {user.user && Sign out} 74 | 75 | {!user?.user && Sign in} 76 | 77 |
78 |
79 |
80 | 81 |
82 |
83 | 84 | 85 | Voyagger 93 | 94 | 95 |
96 | 97 | 98 |
99 | 100 |

101 | {items.length} 102 |

103 |
104 | 105 | {isNavOpen ? ( 106 | setIsNavOpen(!isNavOpen)} 110 | /> 111 | ) : ( 112 | setIsNavOpen(!isNavOpen)} 116 | /> 117 | )} 118 |
119 | 120 | 129 | 137 | 138 | My orders 139 | 140 | 141 | 142 | 150 | 151 | Parcel tracking 152 | 153 | 154 | 155 | 163 | {user.user && Sign out} 164 | 165 | {!user?.user && Sign in} 166 | 167 | 168 |
169 | ); 170 | } 171 | 172 | export default Header; 173 | -------------------------------------------------------------------------------- /src/components/Cart.tsx: -------------------------------------------------------------------------------- 1 | import { useUser } from "@auth0/nextjs-auth0"; 2 | import { LocationMarkerIcon } from "@heroicons/react/solid"; 3 | import { loadStripe } from "@stripe/stripe-js"; 4 | import axios from "axios"; 5 | import { motion } from "framer-motion"; 6 | import { groupBy } from "lodash"; 7 | import Image from "next/image"; 8 | import React, { useEffect, useState } from "react"; 9 | import Currency from "react-currency-formatter"; 10 | import toast from "react-hot-toast"; 11 | import { useDispatch, useSelector } from "react-redux"; 12 | import { db } from "../../firebase"; 13 | import { clearBasket, selectItems, selectTotal } from "../slices/basketSlice"; 14 | import { user } from "../types/userType"; 15 | import CartItem from "./CartItem"; 16 | import { doc, updateDoc } from "firebase/firestore"; 17 | 18 | const stripePromise = loadStripe(process.env.stripe_public_key!); 19 | 20 | interface CartProps { 21 | ssruser?: user; 22 | dbuser: user; 23 | } 24 | 25 | const Cart: React.FC = ({ ssruser, dbuser }) => { 26 | const items = useSelector(selectItems); 27 | const total = useSelector(selectTotal); 28 | const dispatch = useDispatch(); 29 | const user = useUser(); 30 | const [address, setAddress] = useState(""); 31 | const [editShow, seteditShow] = useState(false); 32 | 33 | useEffect(() => { 34 | { 35 | dbuser?.address && setAddress(dbuser?.address); 36 | } 37 | }, [dbuser?.address]); 38 | 39 | const editAddress = async (e: React.FormEvent) => { 40 | e.preventDefault(); 41 | 42 | if (user.user && address) { 43 | const userRef = doc(db, `users/${dbuser.email}`); 44 | 45 | await updateDoc(userRef, { 46 | address: address, 47 | }); 48 | } 49 | seteditShow(!editShow); 50 | toast.success("Address updated!"); 51 | }; 52 | 53 | const createCheckOutSession = async () => { 54 | const stripe = await stripePromise; 55 | 56 | const checkoutSession = await axios.post("/api/create-checkout-session", { 57 | items: items, 58 | email: user.user?.email, 59 | name: user.user?.name, 60 | id: "", 61 | }); 62 | 63 | await stripe?.redirectToCheckout({ 64 | sessionId: checkoutSession.data?.id, 65 | }); 66 | }; 67 | 68 | const groupedItems = Object.values(groupBy(items, "id")); 69 | 70 | const emptyFoodbasket = () => { 71 | dispatch(clearBasket()); 72 | toast.error("Emptied basket", { 73 | style: { 74 | borderRadius: "100px", 75 | }, 76 | }); 77 | }; 78 | 79 | return ( 80 |
81 |
82 |
83 | {ssruser?.picture && ( 84 | avatar 92 | )} 93 |

94 | {ssruser?.name} 95 |

96 |
97 |
98 |

My order

99 |

103 | Empty cart 104 |

105 |
106 |
107 |

112 | Your basket is empty 113 |

114 | {groupedItems.map((group, i) => ( 115 |
116 | 123 |
124 | ))} 125 |
126 |
127 |

Total

128 |

129 | 130 |

131 |
132 |
133 |
134 |

Address

135 |

seteditShow(!editShow)} 138 | > 139 | Edit 140 |

141 |
142 |
143 | 144 |
145 | {editShow ? ( 146 |
147 | setAddress(e.target.value)} 151 | placeholder="Please enter your Address" 152 | /> 153 |
154 | ) : ( 155 |
156 | {address ? address : "Please add your address"} 157 |
158 | )} 159 |
160 |
161 | 171 | Checkout 172 | 173 |
174 | ); 175 | }; 176 | 177 | export default Cart; 178 | -------------------------------------------------------------------------------- /src/pages/cart.tsx: -------------------------------------------------------------------------------- 1 | import { getSession, withPageAuthRequired } from "@auth0/nextjs-auth0"; 2 | import { ArrowLeftIcon, LocationMarkerIcon } from "@heroicons/react/solid"; 3 | import { loadStripe } from "@stripe/stripe-js"; 4 | import axios from "axios"; 5 | import { motion } from "framer-motion"; 6 | import { groupBy } from "lodash"; 7 | import { NextSeo } from "next-seo"; 8 | import Image from "next/image"; 9 | import Link from "next/link"; 10 | import { useEffect, useState } from "react"; 11 | import Currency from "react-currency-formatter"; 12 | import toast from "react-hot-toast"; 13 | import { useDispatch, useSelector } from "react-redux"; 14 | import { db } from "../../firebase"; 15 | import CartItem from "../components/CartItem"; 16 | import { clearBasket, selectItems, selectTotal } from "../slices/basketSlice"; 17 | import { user } from "../types/userType"; 18 | import { doc, getDoc, updateDoc } from "firebase/firestore"; 19 | 20 | const stripePromise = loadStripe(process.env.stripe_public_key!); 21 | 22 | interface CartProps { 23 | user?: user; 24 | dbuser: user; 25 | } 26 | 27 | const Cart: React.FC = ({ user, dbuser }) => { 28 | const items = useSelector(selectItems); 29 | const total = useSelector(selectTotal); 30 | const dispatch = useDispatch(); 31 | const [address, setAddress] = useState(""); 32 | const [editShow, seteditShow] = useState(false); 33 | 34 | useEffect(() => { 35 | { 36 | dbuser?.address && setAddress(dbuser?.address); 37 | } 38 | }, [dbuser?.address]); 39 | 40 | const editAddress = async (e: React.FormEvent) => { 41 | e.preventDefault(); 42 | 43 | if (user?.name && address) { 44 | const userRef = doc(db, `users/${dbuser.email}`); 45 | 46 | await updateDoc(userRef, { 47 | address: address, 48 | }); 49 | } 50 | seteditShow(!editShow); 51 | toast.success("Address updated!"); 52 | }; 53 | 54 | const createCheckOutSession = async () => { 55 | const stripe = await stripePromise; 56 | 57 | const checkoutSession = await axios.post("/api/create-checkout-session", { 58 | items: items, 59 | email: user?.email, 60 | name: user?.name, 61 | id: "", 62 | }); 63 | 64 | await stripe?.redirectToCheckout({ 65 | sessionId: checkoutSession?.data?.id, 66 | }); 67 | }; 68 | 69 | const groupedItems = Object.values(groupBy(items, "id")); 70 | 71 | const emptyFoodbasket = () => { 72 | dispatch(clearBasket()); 73 | toast.error("Emptied basket", { 74 | style: { 75 | borderRadius: "100px", 76 | }, 77 | }); 78 | }; 79 | 80 | return ( 81 |
82 | 83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 | {user?.picture && ( 91 | avatar 99 | )} 100 |

101 | {user?.name} 102 |

103 |
104 |
105 |

My order

106 |

110 | Empty cart 111 |

112 |
113 |
114 |

117 | Your basket is empty 118 |

119 | {groupedItems.map((group, i) => ( 120 |
121 | 128 |
129 | ))} 130 |
131 |
132 |

Total

133 |

134 | 135 |

136 |
137 |
138 |
139 |

Address

140 |

seteditShow(!editShow)} 143 | > 144 | Edit 145 |

146 |
147 |
148 | 149 |
150 | {editShow ? ( 151 |
152 | setAddress(e.target.value)} 156 | placeholder="Please enter your Address" 157 | /> 158 |
159 | ) : ( 160 |
161 | {dbuser.address ? address : "Please add your address"} 162 |
163 | )} 164 |
165 |
166 | 176 | Checkout 177 | 178 |
179 | ); 180 | }; 181 | 182 | export default Cart; 183 | 184 | export const getServerSideProps = withPageAuthRequired({ 185 | async getServerSideProps(context: any) { 186 | const session = getSession(context.req, context.res); 187 | 188 | const userRef = doc(db, `users/${session?.user.email}`); 189 | 190 | const userRes = await getDoc(userRef); 191 | 192 | const dbuser = { 193 | id: userRes.id, 194 | ...userRes.data(), 195 | }; 196 | 197 | return { 198 | props: { 199 | dbuser, 200 | }, 201 | }; 202 | }, 203 | }); 204 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | --------------------------------------------------------------------------------