├── .browserslistrc
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── backend
├── index.js
└── package.json
├── jsconfig.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
├── img
│ ├── GmailLogin.png
│ ├── GmailLogo.png
│ ├── GoogleCalendar.png
│ ├── GoogleContacts.png
│ ├── GoogleKeep.png
│ └── GoogleTasks.png
└── index.html
├── src
├── App.vue
├── assets
│ ├── logo.png
│ └── tailwind.css
├── components
│ ├── IconComponent.vue
│ ├── MessageRow.vue
│ └── UserComponent.vue
├── firebase-init.js
├── main.js
├── router
│ └── index.js
├── store
│ └── user-store.js
└── views
│ ├── HomeView.vue
│ ├── LoginView.vue
│ ├── MessageView.vue
│ └── template
│ └── EmailView.vue
├── tailwind.config.js
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 | not ie 11
5 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | 'extends': [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended'
9 | ],
10 | parserOptions: {
11 | parser: '@babel/eslint-parser'
12 | },
13 | rules: {
14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gmail-clone
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/backend/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const app = express();
3 | const cors = require('cors')
4 | const bodyParser = require('body-parser')
5 |
6 | const { OAuth2Client } = require("google-auth-library");
7 | const client = new OAuth2Client('450137103869-7npi8pvgiqmm72cflnl5tgvnjdcg22fb.apps.googleusercontent.com'); // EG: 184413169503-jfhjjaskdfhdsakfhjsa.apps.googleusercontent.com
8 |
9 | app.use(bodyParser.json())
10 | app.use(cors())
11 |
12 | app.post("/api/google-login", async (req, res) => {
13 |
14 | const ticket = await client.verifyIdToken({
15 | idToken: req.body.token,
16 | });
17 |
18 | res.status(200).json(ticket.getPayload())
19 | });
20 |
21 | app.listen(4001, () => {
22 | console.log(`Server is ready at http://localhost:${4001}`);
23 | });
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "watch": "nodemon index.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "body-parser": "^1.20.2",
14 | "cors": "^2.8.5",
15 | "express": "^4.18.2",
16 | "google-auth-library": "^8.7.0",
17 | "nodemon": "^2.0.22"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "baseUrl": "./",
6 | "moduleResolution": "node",
7 | "paths": {
8 | "@/*": [
9 | "src/*"
10 | ]
11 | },
12 | "lib": [
13 | "esnext",
14 | "dom",
15 | "dom.iterable",
16 | "scripthost"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gmail-clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "autoprefixer": "^10",
12 | "axios": "^1.3.4",
13 | "core-js": "^3.8.3",
14 | "firebase": "^9.19.1",
15 | "google-auth-library": "^8.7.0",
16 | "moment": "^2.29.4",
17 | "pinia": "^2.0.33",
18 | "pinia-plugin-persistedstate": "^3.1.0",
19 | "postcss": "^8",
20 | "tailwindcss": "^3",
21 | "uuid": "^9.0.0",
22 | "vue": "^3.2.13",
23 | "vue-material-design-icons": "^5.2.0",
24 | "vue-router": "^4.0.3",
25 | "vue3-google-login": "^2.0.15"
26 | },
27 | "devDependencies": {
28 | "@babel/core": "^7.12.16",
29 | "@babel/eslint-parser": "^7.12.16",
30 | "@vue/cli-plugin-babel": "~5.0.0",
31 | "@vue/cli-plugin-eslint": "~5.0.0",
32 | "@vue/cli-plugin-router": "~5.0.0",
33 | "@vue/cli-service": "~5.0.0",
34 | "eslint": "^7.32.0",
35 | "eslint-plugin-vue": "^8.0.3",
36 | "sass": "^1.32.7",
37 | "sass-loader": "^12.0.0",
38 | "vue-cli-plugin-tailwind": "~3.0.0"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neto112/gmail-clone/4c7d696109f08abaa048fd106387117ba3445ff0/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/GmailLogin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neto112/gmail-clone/4c7d696109f08abaa048fd106387117ba3445ff0/public/img/GmailLogin.png
--------------------------------------------------------------------------------
/public/img/GmailLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neto112/gmail-clone/4c7d696109f08abaa048fd106387117ba3445ff0/public/img/GmailLogo.png
--------------------------------------------------------------------------------
/public/img/GoogleCalendar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neto112/gmail-clone/4c7d696109f08abaa048fd106387117ba3445ff0/public/img/GoogleCalendar.png
--------------------------------------------------------------------------------
/public/img/GoogleContacts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neto112/gmail-clone/4c7d696109f08abaa048fd106387117ba3445ff0/public/img/GoogleContacts.png
--------------------------------------------------------------------------------
/public/img/GoogleKeep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neto112/gmail-clone/4c7d696109f08abaa048fd106387117ba3445ff0/public/img/GoogleKeep.png
--------------------------------------------------------------------------------
/public/img/GoogleTasks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neto112/gmail-clone/4c7d696109f08abaa048fd106387117ba3445ff0/public/img/GoogleTasks.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | <%= htmlWebpackPlugin.options.title %>
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neto112/gmail-clone/4c7d696109f08abaa048fd106387117ba3445ff0/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @tailwind components;
4 |
5 | @tailwind utilities;
6 |
--------------------------------------------------------------------------------
/src/components/IconComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
16 | {{ text }}
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/MessageRow.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{ from }}
23 |
24 |
25 |
26 | {{ subject }}
27 |
28 |
29 | - {{ body }}...
30 |
31 |
32 |
33 |
34 |
37 | {{ time }}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
64 |
65 |
89 |
--------------------------------------------------------------------------------
/src/components/UserComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
10 |
22 |
26 |
27 |
![]()
28 |
29 |
30 | {{ userStore.firstName }} {{ userStore.lastName }}
31 |
32 |
35 | {{ userStore.email }}
36 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
49 |
71 |
72 |
--------------------------------------------------------------------------------
/src/firebase-init.js:
--------------------------------------------------------------------------------
1 | import { initializeApp } from "firebase/app";
2 | import { getFirestore } from 'firebase/firestore'
3 |
4 | const firebaseConfig = {
5 | apiKey: "AIzaSyCBlEkdpxpwa-zLyKPrcikOujCTsttT0K4",
6 | authDomain: "clone-7c093.firebaseapp.com",
7 | projectId: "clone-7c093",
8 | storageBucket: "clone-7c093.appspot.com",
9 | messagingSenderId: "363365425552",
10 | appId: "1:363365425552:web:ed88ba9567f8f6f93e9315"
11 | };
12 |
13 | const app = initializeApp(firebaseConfig);
14 | const db = getFirestore(app)
15 |
16 | export { db }
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import './assets/tailwind.css'
5 |
6 | import vue3GoogleLogin from 'vue3-google-login'
7 |
8 | import { createPinia } from 'pinia'
9 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
10 |
11 | const pinia = createPinia()
12 | pinia.use(piniaPluginPersistedstate)
13 |
14 | const app = createApp(App)
15 |
16 | app.use(router)
17 | app.use(pinia)
18 | app.use(vue3GoogleLogin, {
19 | clientId: '450137103869-7npi8pvgiqmm72cflnl5tgvnjdcg22fb.apps.googleusercontent.com' // EG: 184413169503-jfhjjaskdfhdsakfhjsa.apps.googleusercontent.com
20 | })
21 |
22 | app.mount('#app')
23 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router'
2 | import HomeView from '../views/HomeView.vue'
3 | import EmailView from '../views/template/EmailView.vue'
4 | import MessageView from '../views/MessageView.vue'
5 | import LoginView from '../views/LoginView.vue'
6 |
7 | import { useUserStore } from '@/store/user-store'
8 |
9 | const routes = [
10 | {
11 | path: '/',
12 | component: LoginView
13 | },
14 | {
15 | path: '/email',
16 | beforeEnter: (to, from, next) => {
17 | useUserStore().email ? next() : next('/')
18 | },
19 | component: EmailView,
20 | children: [
21 | {
22 | path: '',
23 | component: HomeView
24 | },
25 | {
26 | path: 'message/:id',
27 | component: MessageView
28 | }
29 | ]
30 | }
31 | ]
32 |
33 | const router = createRouter({
34 | history: createWebHistory(process.env.BASE_URL),
35 | routes
36 | })
37 |
38 | export default router
39 |
--------------------------------------------------------------------------------
/src/store/user-store.js:
--------------------------------------------------------------------------------
1 |
2 | import { defineStore } from 'pinia'
3 | import axios from 'axios'
4 | import { v4 as uuid } from 'uuid';
5 | import { db } from '@/firebase-init'
6 | import {
7 | collection,
8 | onSnapshot,
9 | where,
10 | query,
11 | doc,
12 | setDoc,
13 | getDoc,
14 | deleteDoc,
15 | orderBy
16 | } from "firebase/firestore";
17 | import moment from 'moment'
18 |
19 | axios.defaults.baseURL = 'http://localhost:4001/'
20 |
21 | export const useUserStore = defineStore('user', {
22 | state: () => ({
23 | sub: '',
24 | email: '',
25 | picture: '',
26 | firstName: '',
27 | lastName: '',
28 | emails: []
29 | }),
30 | actions: {
31 | async getUserDetailsFromGoogle(data) {
32 | let res = await axios.post('api/google-login', {
33 | token: data.credential
34 | })
35 |
36 | this.$state.sub = res.data.sub
37 | this.$state.email = res.data.email
38 | this.$state.picture = res.data.picture
39 | this.$state.firstName = res.data.given_name
40 | this.$state.lastName = res.data.family_name
41 | },
42 | getEmailsByEmailAddress() {
43 | const q = query(collection(db, "emails"), where("toEmail", "==", this.$state.email), orderBy("createdAt", "desc"));
44 |
45 | onSnapshot(q,
46 | (querySnapshot) => {
47 | let resultArray = []
48 | querySnapshot.forEach((doc) => {
49 | resultArray.push({
50 | id: doc.id,
51 | firstName: doc.data().firstName,
52 | lastName: doc.data().lastName,
53 | fromEmail: doc.data().email,
54 | toEmail: doc.data().toEmail,
55 | subject: doc.data().subject,
56 | body: doc.data().body,
57 | hasViewed: doc.data().hasViewed,
58 | createdAt: moment(doc.data().createdAt).format("MMM D HH:mm")
59 | })
60 | });
61 | this.$state.emails = resultArray
62 | }
63 | , (error) => {
64 | console.log(error)
65 | })
66 | },
67 | async getEmailById(id) {
68 |
69 | const docRef = doc(db, "emails", id);
70 | const docSnap = await getDoc(docRef);
71 |
72 | if (docSnap.exists()) {
73 | return {
74 | id: id,
75 | body: docSnap.data().body,
76 | createdAt: moment(docSnap.data().createdAt).format("MMM D HH:mm"),
77 | firstName: docSnap.data().firstName,
78 | fromEmail: docSnap.data().fromEmail,
79 | lastName: docSnap.data().lastName,
80 | subject: docSnap.data().subject,
81 | toEmail: docSnap.data().toEmail,
82 | hasViewed: docSnap.data().hasViewed,
83 | }
84 | } else {
85 | console.log("No such document!");
86 | }
87 | },
88 | async sendEmail(data) {
89 | try {
90 | await setDoc(doc(db, "emails/" + uuid()), {
91 | firstName: this.$state.firstName,
92 | lastName: this.$state.lastName,
93 | fromEmail: this.$state.email,
94 | toEmail: data.toEmail,
95 | subject: data.subject,
96 | body: data.body,
97 | hasViewed: false,
98 | createdAt: Date.now()
99 | });
100 | } catch (error) {
101 | console.log(error)
102 | }
103 | },
104 | async deleteEmail(id) {
105 | try {
106 | await deleteDoc(doc(db, "emails", id));
107 | } catch (error) {
108 | console.log(error)
109 | }
110 | },
111 |
112 | },
113 | })
--------------------------------------------------------------------------------
/src/views/HomeView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
1-50 of 153
14 |
15 |
16 |
17 |
25 |
26 |
27 |
28 |
29 |
30 |
41 |
--------------------------------------------------------------------------------
/src/views/LoginView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ userStore }}
5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/views/MessageView.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
17 |
18 |
27 |
28 |
1-50 of 153
29 |
30 |
31 |
{{email.subject}}
32 |
33 |

37 |
38 |
39 |
40 |
{{email.fromEmail}}
41 |
{{email.createdAt}}
42 |
43 |
to me
44 |
45 |
{{email.body}}
46 |
47 |
48 |
49 |
50 |
51 |
87 |
88 |
--------------------------------------------------------------------------------
/src/views/template/EmailView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
56 |
57 |
58 |
107 |
108 |
109 |
110 |
111 |
112 |

113 |
114 |
115 |
116 |

117 |
118 |
119 |
120 |

121 |
122 |
123 |
124 |

125 |
126 |
127 |
128 |
129 |
132 |
133 |
134 |
135 |
140 |
143 |
New message
144 |
149 |
150 |
151 |
152 |
153 |
To
154 |
159 |
160 |
161 |
162 |
Subject
163 |
168 |
169 |
170 |
171 |
172 |
173 |
179 |
180 |
181 |
182 |
188 |
189 |
190 |
191 |
192 |
222 |
223 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./public/**/*.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('@vue/cli-service')
2 | module.exports = defineConfig({
3 | transpileDependencies: true
4 | })
5 |
--------------------------------------------------------------------------------