├── .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 | 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 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/MessageRow.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 64 | 65 | 89 | -------------------------------------------------------------------------------- /src/components/UserComponent.vue: -------------------------------------------------------------------------------- 1 | 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 | 29 | 30 | 41 | -------------------------------------------------------------------------------- /src/views/LoginView.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /src/views/MessageView.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 87 | 88 | -------------------------------------------------------------------------------- /src/views/template/EmailView.vue: -------------------------------------------------------------------------------- 1 | 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 | --------------------------------------------------------------------------------