├── .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 |
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 |
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 |
5 |
6 |
7 |
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 |
55 |
56 |
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 |
25 |
26 |
27 |
33 |
34 |
35 |
41 |
42 |
43 |
49 |
50 |
51 |
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 |
61 |
62 |
{name}
63 |
64 |
65 |
66 |
67 |
68 |
69 | {" "}
70 | {quantity} × = {" "}
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
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 |
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 |
69 | Share with someone
70 |
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 |
74 |
75 |
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 | router.push("/parcel")}
40 | className="underline link hover:no-underline"
41 | >
42 | Parcel Page
43 | {" "}
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 |
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 |
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 |
74 | Share with someone
75 |
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 | router.push("/food")}
47 | className="underline link hover:no-underline"
48 | >
49 | Homepage Store
50 | {" "}
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 |
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 |
62 |
63 |
64 |
{name}
65 |
{description}
66 |
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 |
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 |
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 |
80 |
81 |
82 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------