├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── cover.png ├── functions ├── getUserData.js ├── hello.js └── serviceAccountKey.json ├── netlify.toml ├── package.json ├── public ├── _redirects ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── DCHQ-dark.svg │ ├── DCHQ-small-dark.svg │ ├── DCHQ-small.svg │ ├── DCHQ.svg │ ├── contentful.png │ ├── dropbox.png │ ├── figma.png │ ├── logo.png │ ├── logout.svg │ ├── moon.svg │ ├── notion.png │ ├── slack.png │ └── sun.svg ├── components │ ├── Header.vue │ ├── HelloWorld.vue │ ├── Notification.vue │ ├── RequestAccount.vue │ └── ThemeSwitch.vue ├── firebase.js ├── functions │ ├── getUserData.js │ ├── hello.js │ └── serviceAccountKey.json ├── global-styles │ ├── colors.scss │ └── typography.scss ├── main.js ├── router.js ├── store.js └── views │ ├── Home.vue │ ├── Manage.vue │ ├── SignInFlow │ ├── Recover.vue │ ├── Request.vue │ └── SignIn.vue │ └── Team.vue ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Thomas Wang 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Vue.js 4 | 5 |

6 |

7 | Vue.js for Designers 8 |

9 | 10 | [Vue.js](https://github.com/vuejs/vue) is a powerful, easy-to-use JavaScript framework that is component-based, just like React. This course will walk you through how to build a beautiful admin dashboard that you can use for your own data on the web. Create stunning charts, delightful animations, and powerful administrative commands. Full video tutorials: https://designcode.io/vue. 11 | 12 | ## ⚡️ Course Downloads 13 | 14 | 1. [Intro to Vue.js](https://github.com/thomaswangio/vue-admin-dashboard/tree/293b57a08ab745a3d42dfd99c098833541719772) 15 | 2. [Global Styling with Sass](https://github.com/thomaswangio/vue-admin-dashboard/tree/ab0f7dcfe575b338a443ca3680f8ba99111a52a8) 16 | 3. [Single File Components](https://github.com/thomaswangio/vue-admin-dashboard/tree/05dbe70a9745edcf42be96d02e238b83ae5143bf) 17 | 4. [Dark Mode Part 1](https://github.com/thomaswangio/vue-admin-dashboard/tree/a4ab92e0f5a6ad186d5a785c3f86228455651c4a) 18 | 5. [Dark Mode Part 2](https://github.com/thomaswangio/vue-admin-dashboard/tree/f3366f209d1388ce73a51a7bf70056eb78d52de4) 19 | 6. [Animations and Transitions](https://github.com/thomaswangio/vue-admin-dashboard/tree/ec9ac65ece01830d5d2060c610a210d3a2a71b3f) 20 | 7. [Accounts with Netlify](https://github.com/thomaswangio/vue-admin-dashboard/tree/5700810705c56fa8a4a8657cb00cee89a14d4ee3) 21 | 8. [Accounts with Slack](https://github.com/thomaswangio/vue-admin-dashboard/tree/ea78a10cac69f8c3053c1f9fa8538c63143197e2) 22 | 9. [Charts and Data Visualization](https://github.com/thomaswangio/vue-admin-dashboard/tree/71d8bb1e0f8763222554d85ab7b14541cb9bcd60) 23 | 10. [Database with Firestore](https://github.com/thomaswangio/vue-admin-dashboard/tree/bc61877bfda94bb112fd5afa362eb2d37275ab13) 24 | 11. [Lambda Functions with Netlify](https://github.com/thomaswangio/vue-admin-dashboard/tree/0df2be6068f9160e90fa2a4d46011b1d7717597d) 25 | 26 | ## ⚙️ Install Yarn and Vue CLI 27 | 28 | Make sure that you have the [Yarn](https://yarnpkg.com/en/) installed, then install the [Vue CLI](https://cli.vuejs.org/): 29 | 30 | ```sh 31 | yarn global add @vue/cli 32 | ``` 33 | 34 | If you have trouble with getting this project to run, please run the version of `@vue/cli` used in the course: 35 | 36 | ```sh 37 | yarn global add @vue/cli@3.3.0 38 | ``` 39 | 40 | ## ✏️ Manage your Project from the Vue CLI GUI 41 | 42 | After installing the Vue CLI, you can open the Vue Project Manager by using the following command: 43 | 44 | ```sh 45 | vue ui 46 | ``` 47 | 48 | Note: If you are importing this project instead of installing a new project from scratch, first run `yarn` in your project to install the dependencies. 49 | 50 | ```sh 51 | yarn 52 | ``` 53 | 54 | Then you can run the project with: 55 | 56 | ```sh 57 | yarn serve 58 | ``` 59 | 60 | ## 🎓 Learning Vue.js 61 | 62 | The Design+Code Vue.js course lives [here](https://designcode.io/vue). Start learning by creating this Web App! 63 | 64 | Looking for more guidance? Full documentation for Vue is [on their website](https://vuejs.org/). To see the documentation for the Vue CLL, which was used to create and configure this project, go [here](https://cli.vuejs.org/guide/). 65 | 66 | ## 👾 Demo 67 | 68 | Feel free to play around with the finished website at: https://vue-hq-2.netlify.app. The username is `support@designcode.io` and password is `123456789`. 69 | 70 | ## 🚀 Deploy 71 | 72 | Launch this site to Netlify by clicking below! Or go to [Netlify](https://www.netlify.app/) to deploy your own project. 73 | 74 | [![Deploy to Netlify](https://www.netlify.app/img/deploy/button.svg)](https://app.netlify.app/start/deploy?repository=https://github.com/thomaswangio/vue-admin-dashboard) 75 | 76 | ## 💭 Q&A and Issues 77 | 78 | Feel free to [ask any questions or note issues](https://github.com/thomaswangio/vue-admin-dashboard/issues/new)! 79 | 80 | - [Fixing dark and light text](https://github.com/thomaswangio/vue-admin-dashboard/issues/2) 81 | - [Using rem units instead of px](https://github.com/thomaswangio/vue-admin-dashboard/issues/3) 82 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaswang-archive/vue-admin-dashboard/2aecdd70b7cf7385b6a74daeed15a36a1fc60b50/cover.png -------------------------------------------------------------------------------- /functions/getUserData.js: -------------------------------------------------------------------------------- 1 | const { zipFunctions } = require("@netlify/zip-it-and-ship-it"); 2 | 3 | zipFunctions("functions", "functions-dist") 4 | 5 | var admin = require("firebase-admin"); 6 | 7 | var serviceAccount = require("./serviceAccountKey.json"); 8 | 9 | admin.initializeApp({ 10 | credential: admin.credential.cert(serviceAccount), 11 | databaseURL: "https://vue-hq.firebaseio.com" 12 | }); 13 | 14 | 15 | exports.handler = function (event, context, callback) { 16 | const { user } = context.clientContext; 17 | 18 | // Do the user check here 19 | // If not user, callback a status code of 401, meaning unauthorized 20 | 21 | const { email, subscriptionId } = event.queryStringParameters; 22 | 23 | console.log("Searched email: " + email); 24 | console.log("Searched subscription ID: " + subscriptionId); 25 | 26 | const searchKey = email.length > 0 ? "email" : "subscriptionId" 27 | const searchQuery = email.length > 0 ? email : subscriptionId 28 | 29 | const firestore = admin.firestore(); 30 | 31 | firestore.collection('users').where(searchKey, '==', searchQuery).limit(1).get().then(response => { 32 | if (response.empty) { return Promise.reject() } 33 | 34 | const userInfo = response.docs[0].data() 35 | 36 | console.log(userInfo) 37 | 38 | callback(null, { 39 | statusCode: 200, 40 | headers: { 41 | "Access-Control-Allow-Origin": "*", // Required for CORS support to work 42 | "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS 43 | }, 44 | body: JSON.stringify(userInfo) 45 | }) 46 | }).catch(error => { 47 | console.log(error) 48 | 49 | callback(null, { 50 | statusCode: 500, 51 | headers: { 52 | "Access-Control-Allow-Origin": "*", // Required for CORS support to work 53 | "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS 54 | }, 55 | body: error 56 | }) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /functions/hello.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context, callback) { 2 | callback(null, { 3 | statusCode: 200, 4 | headers: { 5 | "Access-Control-Allow-Origin": "*", // Required for CORS support to work 6 | "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS 7 | }, 8 | body: "Hello, World" 9 | }); 10 | } -------------------------------------------------------------------------------- /functions/serviceAccountKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account", 3 | "project_id": "vue-hq", 4 | "private_key_id": "7f290e3e535486a33eca36efd6d642872bb93c54", 5 | "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRKX1OTPLUB/gZ\nJlU7VgffVTiPuD6F3WsEgRpJ1o0BK4crGP1+R4VjdKcXgFyG9Mlt2S6Kzy2j/vLZ\ntjl7QdP72btOCr28+SlOYKLfz3i+HmzXMb+T1koJtfOAU/WVCR02Bhmx6asNq1Cy\nsUvuqFOFyCpI6iv8TgAiKKOUqLW1EUE/1mPOGbv9ToFdPaUpQNVfcpBUdO4dt9WF\nJf9gshuO2GgU4gIm3vY6RQtQ/ymOEYYKpVmU6PH+EIIjB2m8xkoR9tpYDGAsrps2\nznVIP6XNfqNWqic00BUF9QswhTMu1ti2G198RxGsv74dc/c80kb84TwIpNg1Je93\nftCm4ZmtAgMBAAECggEAAyd+sJqaeDYdOLaKRy3Mn+1CFXDFiJvxNuKCNKYxVC4w\nM1HaKM2ypkWma2/6dT07IxaeYWYvHa+vloxCoP2Pe9cpwEs11065SmmiAIFkJGyD\nCa/CCtxgG3YMqQfFxqyaLda5C897+Tsh1BVhEW/6anm9TCzejZyDhgBaOdiy0iXc\ndv3bx7VIIfGsapcHoVreUizwWUjmLfrSOKV/H+DT6QK2tcrECWJMRpEtlue6aNwT\nXK9qG1F6bUxomDKRy+hWnnVGZBcceMI+QoJdV9w5AOSMUkX/vUy1HpaJV2dTvgqb\nLp9BRUFBV98P9yES1bbbmiQ7nKLDlHhizpGXbZQc5QKBgQD7KvdMCzwbY5XBtvOq\n8aUgK2O947KpS+A4y94EKMd04qrDVJOm1Rk+HRb/T0h50979OXZpuOIkkTuujiXI\n/bkhazX2Q/qF+WnH5v4jp8WF8b+VX5wzGRd7mIRUEclCx+7dC38Hfuct80MHxA3s\nr9QTwtICuLfHq+slVjbyQn3HawKBgQDVL6O/5G+rrg/b/MXn0Dc0dUOc8AwOXr/9\nN8HH69zkMGB79Cjt4xVTvBoPyLuc1tK5qVoTJ9rVr3FNrtPQoMwYIG9Wjs1pTOZH\nEoXYWKrxNFySHMnp7dhDnVyz2siX5MlyaXul/vcW+mWHaqgZ41OuhXpDiNBB3LrY\n5+8GaP+hRwKBgQDnsu4ox2Buf8KBzi0gcAR9FanQXYHOLUGuA1tPCPNev076HEaN\nK546r4ksgs4AHNRjUyQKSVTMmPGuiFERBgZHbuh7wqAUAU5064FcEQPlGWs799RX\nSvlfugWwrCu2oEuYvGc12fqXaAZY3qJdyqHgjONBQXfACbdFXTAY8f+n+wKBgQCM\n74ezzpM8zj/R0VQMoow8c+GVGOT/gwgtoPTM9FL90DWY/JsqWVu6FbqtAKl3UFXT\nQ/A2hbyYU/n1v0RswkDrGlZAxPtrR9lc1xQBKO0ptNIWDk42BnVYjenBRTwjnq7G\nopGJ9froB/WjBpHHO5AQ6lEf4Iy9Xe1whDRy6mtE9QKBgGW3amuZRaBfWRhYQZqN\ne6PMXQP9DFce+HCa/f5d96AU8nORyOn10Wkd/D4DZYFqqbE/DEE5BeEdtk1WXvWb\nNtFpCdiNaV12iDEPrJ2aLElqKRlYDD6Q/Ckp6vNuFHcyA8Twr+FwSeqlQvAoHBHF\nlhdRoJO8b7rb3Ij1iGEaU9qy\n-----END PRIVATE KEY-----\n", 6 | "client_email": "firebase-adminsdk-vgwbn@vue-hq.iam.gserviceaccount.com", 7 | "client_id": "106063469757469881133", 8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 9 | "token_uri": "https://oauth2.googleapis.com/token", 10 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 11 | "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-vgwbn%40vue-hq.iam.gserviceaccount.com" 12 | } 13 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | functions = "./functions" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-dashboard", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "lint": "vue-cli-service lint", 8 | "start:lambda": "netlify-lambda serve src/functions", 9 | "build:lambda": "netlify-lambda build src/functions", 10 | "build:vue": "vue-cli-service build" 11 | }, 12 | "dependencies": { 13 | "@firebase/app": "^0.6.5", 14 | "@firebase/firestore": "^1.15.0", 15 | "@netlify/zip-it-and-ship-it": "^0.2.0", 16 | "animate.css": "^3.7.0", 17 | "apexcharts": "^3.2.2", 18 | "firebase-admin": "^7.0.0", 19 | "gotrue-js": "^0.9.24", 20 | "netlify-identity-widget": "^1.5.2", 21 | "netlify-lambda": "^1.4.2", 22 | "vue": "^2.5.21", 23 | "vue-apexcharts": "^1.2.9", 24 | "vue-firestore": "^0.3.16", 25 | "vue-github-buttons": "^3.0.1", 26 | "vue-router": "^3.0.1", 27 | "vuex": "^3.0.1" 28 | }, 29 | "devDependencies": { 30 | "@vue/cli-plugin-babel": "^3.3.0", 31 | "@vue/cli-plugin-eslint": "^3.3.0", 32 | "@vue/cli-service": "^3.3.0", 33 | "@vue/eslint-config-prettier": "^4.0.1", 34 | "babel-eslint": "^10.0.1", 35 | "eslint": "^5.8.0", 36 | "eslint-plugin-vue": "^5.0.0", 37 | "node-sass": "^4.9.0", 38 | "sass-loader": "^7.0.1", 39 | "vue-template-compiler": "^2.5.21" 40 | }, 41 | "eslintConfig": { 42 | "root": true, 43 | "env": { 44 | "node": true 45 | }, 46 | "extends": [ 47 | "plugin:vue/essential" 48 | ], 49 | "rules": {}, 50 | "parserOptions": { 51 | "parser": "babel-eslint" 52 | } 53 | }, 54 | "postcss": { 55 | "plugins": { 56 | "autoprefixer": {} 57 | } 58 | }, 59 | "browserslist": [ 60 | "> 1%", 61 | "last 2 versions", 62 | "not ie <= 8" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | # Netlify settings for single-page application 2 | /* /index.html 200 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaswang-archive/vue-admin-dashboard/2aecdd70b7cf7385b6a74daeed15a36a1fc60b50/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue HQ Demo Site by Design+Code 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 51 |
52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 175 | -------------------------------------------------------------------------------- /src/assets/DCHQ-dark.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 | -------------------------------------------------------------------------------- /src/assets/DCHQ-small-dark.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 | -------------------------------------------------------------------------------- /src/assets/DCHQ-small.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 | -------------------------------------------------------------------------------- /src/assets/DCHQ.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 | -------------------------------------------------------------------------------- /src/assets/contentful.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaswang-archive/vue-admin-dashboard/2aecdd70b7cf7385b6a74daeed15a36a1fc60b50/src/assets/contentful.png -------------------------------------------------------------------------------- /src/assets/dropbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaswang-archive/vue-admin-dashboard/2aecdd70b7cf7385b6a74daeed15a36a1fc60b50/src/assets/dropbox.png -------------------------------------------------------------------------------- /src/assets/figma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaswang-archive/vue-admin-dashboard/2aecdd70b7cf7385b6a74daeed15a36a1fc60b50/src/assets/figma.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaswang-archive/vue-admin-dashboard/2aecdd70b7cf7385b6a74daeed15a36a1fc60b50/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/notion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaswang-archive/vue-admin-dashboard/2aecdd70b7cf7385b6a74daeed15a36a1fc60b50/src/assets/notion.png -------------------------------------------------------------------------------- /src/assets/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaswang-archive/vue-admin-dashboard/2aecdd70b7cf7385b6a74daeed15a36a1fc60b50/src/assets/slack.png -------------------------------------------------------------------------------- /src/assets/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 48 | 49 | 105 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | 14 | 15 | 17 | -------------------------------------------------------------------------------- /src/components/Notification.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 47 | -------------------------------------------------------------------------------- /src/components/RequestAccount.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 31 | 32 | 59 | -------------------------------------------------------------------------------- /src/components/ThemeSwitch.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 70 | -------------------------------------------------------------------------------- /src/firebase.js: -------------------------------------------------------------------------------- 1 | import { firebase } from "@firebase/app"; 2 | import "@firebase/firestore"; 3 | 4 | const firebaseApp = firebase.initializeApp({ 5 | apiKey: "AIzaSyBZcqLLNNKG-Gpcdr-2he4QAX8q1yvgEww", 6 | authDomain: "vue-hq.firebaseapp.com", 7 | databaseURL: "https://vue-hq.firebaseio.com", 8 | projectId: "vue-hq", 9 | storageBucket: "vue-hq.appspot.com", 10 | messagingSenderId: "857669172723" 11 | }); 12 | 13 | export const db = firebaseApp.firestore(); -------------------------------------------------------------------------------- /src/functions/getUserData.js: -------------------------------------------------------------------------------- 1 | const { zipFunctions } = require("@netlify/zip-it-and-ship-it"); 2 | 3 | zipFunctions("functions", "functions-dist") 4 | 5 | var admin = require("firebase-admin"); 6 | 7 | var serviceAccount = require("./serviceAccountKey.json"); 8 | 9 | admin.initializeApp({ 10 | credential: admin.credential.cert(serviceAccount), 11 | databaseURL: "https://vue-hq.firebaseio.com" 12 | }); 13 | 14 | 15 | exports.handler = function (event, context, callback) { 16 | const { user } = context.clientContext; 17 | 18 | // Do the user check here 19 | // If not user, callback a status code of 401, meaning unauthorized 20 | 21 | const { email, subscriptionId } = event.queryStringParameters; 22 | 23 | console.log("Searched email: " + email); 24 | console.log("Searched subscription ID: " + subscriptionId); 25 | 26 | const searchKey = email.length > 0 ? "email" : "subscriptionId" 27 | const searchQuery = email.length > 0 ? email : subscriptionId 28 | 29 | const firestore = admin.firestore(); 30 | 31 | firestore.collection('users').where(searchKey, '==', searchQuery).limit(1).get().then(response => { 32 | if (response.empty) { return Promise.reject() } 33 | 34 | const userInfo = response.docs[0].data() 35 | 36 | console.log(userInfo) 37 | 38 | callback(null, { 39 | statusCode: 200, 40 | headers: { 41 | "Access-Control-Allow-Origin": "*", // Required for CORS support to work 42 | "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS 43 | }, 44 | body: JSON.stringify(userInfo) 45 | }) 46 | }).catch(error => { 47 | console.log(error) 48 | 49 | callback(null, { 50 | statusCode: 500, 51 | headers: { 52 | "Access-Control-Allow-Origin": "*", // Required for CORS support to work 53 | "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS 54 | }, 55 | body: error 56 | }) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /src/functions/hello.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context, callback) { 2 | callback(null, { 3 | statusCode: 200, 4 | headers: { 5 | "Access-Control-Allow-Origin": "*", // Required for CORS support to work 6 | "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS 7 | }, 8 | body: "Hello, World" 9 | }); 10 | } -------------------------------------------------------------------------------- /src/functions/serviceAccountKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account", 3 | "project_id": "vue-hq", 4 | "private_key_id": "7f290e3e535486a33eca36efd6d642872bb93c54", 5 | "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRKX1OTPLUB/gZ\nJlU7VgffVTiPuD6F3WsEgRpJ1o0BK4crGP1+R4VjdKcXgFyG9Mlt2S6Kzy2j/vLZ\ntjl7QdP72btOCr28+SlOYKLfz3i+HmzXMb+T1koJtfOAU/WVCR02Bhmx6asNq1Cy\nsUvuqFOFyCpI6iv8TgAiKKOUqLW1EUE/1mPOGbv9ToFdPaUpQNVfcpBUdO4dt9WF\nJf9gshuO2GgU4gIm3vY6RQtQ/ymOEYYKpVmU6PH+EIIjB2m8xkoR9tpYDGAsrps2\nznVIP6XNfqNWqic00BUF9QswhTMu1ti2G198RxGsv74dc/c80kb84TwIpNg1Je93\nftCm4ZmtAgMBAAECggEAAyd+sJqaeDYdOLaKRy3Mn+1CFXDFiJvxNuKCNKYxVC4w\nM1HaKM2ypkWma2/6dT07IxaeYWYvHa+vloxCoP2Pe9cpwEs11065SmmiAIFkJGyD\nCa/CCtxgG3YMqQfFxqyaLda5C897+Tsh1BVhEW/6anm9TCzejZyDhgBaOdiy0iXc\ndv3bx7VIIfGsapcHoVreUizwWUjmLfrSOKV/H+DT6QK2tcrECWJMRpEtlue6aNwT\nXK9qG1F6bUxomDKRy+hWnnVGZBcceMI+QoJdV9w5AOSMUkX/vUy1HpaJV2dTvgqb\nLp9BRUFBV98P9yES1bbbmiQ7nKLDlHhizpGXbZQc5QKBgQD7KvdMCzwbY5XBtvOq\n8aUgK2O947KpS+A4y94EKMd04qrDVJOm1Rk+HRb/T0h50979OXZpuOIkkTuujiXI\n/bkhazX2Q/qF+WnH5v4jp8WF8b+VX5wzGRd7mIRUEclCx+7dC38Hfuct80MHxA3s\nr9QTwtICuLfHq+slVjbyQn3HawKBgQDVL6O/5G+rrg/b/MXn0Dc0dUOc8AwOXr/9\nN8HH69zkMGB79Cjt4xVTvBoPyLuc1tK5qVoTJ9rVr3FNrtPQoMwYIG9Wjs1pTOZH\nEoXYWKrxNFySHMnp7dhDnVyz2siX5MlyaXul/vcW+mWHaqgZ41OuhXpDiNBB3LrY\n5+8GaP+hRwKBgQDnsu4ox2Buf8KBzi0gcAR9FanQXYHOLUGuA1tPCPNev076HEaN\nK546r4ksgs4AHNRjUyQKSVTMmPGuiFERBgZHbuh7wqAUAU5064FcEQPlGWs799RX\nSvlfugWwrCu2oEuYvGc12fqXaAZY3qJdyqHgjONBQXfACbdFXTAY8f+n+wKBgQCM\n74ezzpM8zj/R0VQMoow8c+GVGOT/gwgtoPTM9FL90DWY/JsqWVu6FbqtAKl3UFXT\nQ/A2hbyYU/n1v0RswkDrGlZAxPtrR9lc1xQBKO0ptNIWDk42BnVYjenBRTwjnq7G\nopGJ9froB/WjBpHHO5AQ6lEf4Iy9Xe1whDRy6mtE9QKBgGW3amuZRaBfWRhYQZqN\ne6PMXQP9DFce+HCa/f5d96AU8nORyOn10Wkd/D4DZYFqqbE/DEE5BeEdtk1WXvWb\nNtFpCdiNaV12iDEPrJ2aLElqKRlYDD6Q/Ckp6vNuFHcyA8Twr+FwSeqlQvAoHBHF\nlhdRoJO8b7rb3Ij1iGEaU9qy\n-----END PRIVATE KEY-----\n", 6 | "client_email": "firebase-adminsdk-vgwbn@vue-hq.iam.gserviceaccount.com", 7 | "client_id": "106063469757469881133", 8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 9 | "token_uri": "https://oauth2.googleapis.com/token", 10 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 11 | "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-vgwbn%40vue-hq.iam.gserviceaccount.com" 12 | } 13 | -------------------------------------------------------------------------------- /src/global-styles/colors.scss: -------------------------------------------------------------------------------- 1 | // Background Colors 2 | $dark-blue: #212c4f; 3 | $super-dark-blue: #1b233f; 4 | $light-gray: #f0f3f5; 5 | 6 | // Text Colors 7 | $dark-green: #205284; 8 | $dark-gray: #5b6175; 9 | $light-blue: #c6d0eb; 10 | $black: #000000; 11 | $white: #ffffff; 12 | 13 | // Button Colors 14 | $purple: #7b42f6; 15 | $violet: #b01eff; 16 | $pink: #e1467c; 17 | $blue: #3672f8; 18 | $middle-blue: #546bfb; 19 | $teal: #56ccf2; 20 | $green: #a6e1d5; 21 | $turquoise: #14f1d9; 22 | -------------------------------------------------------------------------------- /src/global-styles/typography.scss: -------------------------------------------------------------------------------- 1 | // Font Family 2 | $system-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, 3 | Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; 4 | 5 | // Headings 6 | @mixin heading-1($color: $white) { 7 | font-size: 80px; 8 | line-height: 86px; 9 | font-weight: bold; 10 | color: $color; 11 | } 12 | 13 | @mixin heading-2($color: $white) { 14 | font-size: 60px; 15 | line-height: 86px; 16 | font-weight: bold; 17 | color: $color; 18 | } 19 | 20 | @mixin heading-3($color: $white) { 21 | font-size: 30px; 22 | line-height: 43px; 23 | font-weight: bold; 24 | color: $color; 25 | } 26 | 27 | @mixin heading-4($color: $white) { 28 | font-size: 24px; 29 | line-height: 34px; 30 | font-weight: bold; 31 | color: $color; 32 | } 33 | 34 | // Body Text 35 | %large { 36 | line-height: 37px; 37 | font-size: 24px; 38 | } 39 | 40 | @mixin large-text($color: $white) { 41 | @extend %large; 42 | color: $color; 43 | } 44 | 45 | @mixin large-text-bold($color: $white) { 46 | @extend %large; 47 | font-weight: bold; 48 | color: $color; 49 | } 50 | 51 | %medium { 52 | line-height: 31px; 53 | font-size: 20px; 54 | } 55 | 56 | @mixin medium-text($color: $white) { 57 | @extend %medium; 58 | color: $color; 59 | } 60 | 61 | @mixin medium-text-bold($color: $white) { 62 | @extend %medium; 63 | font-weight: bold; 64 | color: $color; 65 | } 66 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import store from "./store"; 5 | import * as netlifyIdentityWidget from "netlify-identity-widget"; 6 | import GoTrue from "gotrue-js"; 7 | import VueFirestore from "vue-firestore"; 8 | import "vue-github-buttons/dist/vue-github-buttons.css"; // Stylesheet 9 | import VueGitHubButtons from "vue-github-buttons"; // Component plugin 10 | 11 | Vue.config.productionTip = false; 12 | 13 | // Initialize Netlify Identity 14 | netlifyIdentityWidget.init(); 15 | 16 | // Initialize GoTrue JS for Netlify 17 | export const auth = new GoTrue({ 18 | APIUrl: "https://vue-hq.netlify.app/.netlify/identity", 19 | setCookie: true, 20 | }); 21 | 22 | // Initialize Vue Firestore 23 | Vue.use(VueFirestore); 24 | 25 | // Initialize Github Buttons 26 | Vue.use(VueGitHubButtons); 27 | 28 | new Vue({ 29 | router, 30 | store, 31 | render: (h) => h(App), 32 | }).$mount("#app"); 33 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | import Home from "./views/Home.vue"; 4 | import Team from "./views/Team.vue"; 5 | import Manage from "./views/Manage.vue"; 6 | import SignIn from "./views/SignInFlow/SignIn"; 7 | import Request from "./views/SignInFlow/Request"; 8 | import Recover from "./views/SignInFlow/Recover"; 9 | import * as netlifyIdentityWidget from "netlify-identity-widget"; 10 | 11 | Vue.use(Router); 12 | 13 | const router = new Router({ 14 | mode: "history", 15 | base: process.env.BASE_URL, 16 | routes: [ 17 | { 18 | path: "/", 19 | name: "home", 20 | component: Home, 21 | meta: { 22 | requiresAuth: true 23 | } 24 | }, 25 | { 26 | path: "/Manage", 27 | name: "manage", 28 | component: Manage, 29 | meta: { 30 | requiresAuth: true 31 | } 32 | }, 33 | { 34 | path: "/team", 35 | name: "team", 36 | component: Team, 37 | meta: { 38 | requiresAuth: true 39 | } 40 | }, 41 | { 42 | path: "/signin", 43 | name: "signin", 44 | component: SignIn 45 | }, 46 | { 47 | path: "/request", 48 | name: "request", 49 | component: Request 50 | }, 51 | { 52 | path: "/recover", 53 | name: "recover", 54 | component: Recover 55 | } 56 | ] 57 | }); 58 | 59 | router.beforeEach((to, from, next) => { 60 | const currentUser = netlifyIdentityWidget.currentUser(); 61 | const requiresAuth = to.matched.some(record => { 62 | return record.meta.requiresAuth 63 | }) 64 | 65 | if (requiresAuth && !currentUser) { 66 | next("signin"); 67 | } else { 68 | next(); 69 | } 70 | }) 71 | 72 | export default router; 73 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | 4 | Vue.use(Vuex); 5 | 6 | // Initial State 7 | if (window.matchMedia("(prefers-color-scheme: dark)").matches) { 8 | window.localStorage.setItem("isDarkMode", "true"); 9 | } 10 | 11 | const userSelectedDarkMode = window.localStorage.getItem("isDarkMode") === "true"; 12 | 13 | const state = { 14 | isDarkMode: userSelectedDarkMode 15 | } 16 | 17 | // Getters 18 | const getters = { 19 | isDarkMode(state) { 20 | return state.isDarkMode 21 | } 22 | } 23 | 24 | // Mutations 25 | const mutations = { 26 | toggleDarkMode(state) { 27 | if (state.isDarkMode === true) { 28 | state.isDarkMode = false; 29 | window.localStorage.setItem("isDarkMode", "false"); 30 | } else { 31 | state.isDarkMode = true; 32 | window.localStorage.setItem("isDarkMode", "true"); 33 | } 34 | } 35 | } 36 | 37 | // Actions 38 | const actions = { 39 | triggerDarkMode(context) { 40 | context.commit('toggleDarkMode'); 41 | } 42 | } 43 | 44 | export default new Vuex.Store({ 45 | state, 46 | getters, 47 | mutations, 48 | actions 49 | }); 50 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 343 | 344 | 422 | -------------------------------------------------------------------------------- /src/views/Manage.vue: -------------------------------------------------------------------------------- 1 | 84 | 85 | 146 | 147 | 216 | -------------------------------------------------------------------------------- /src/views/SignInFlow/Recover.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 73 | 74 | 89 | -------------------------------------------------------------------------------- /src/views/SignInFlow/Request.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 80 | 81 | 97 | -------------------------------------------------------------------------------- /src/views/SignInFlow/SignIn.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 103 | 104 | 127 | -------------------------------------------------------------------------------- /src/views/Team.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 90 | 91 | 183 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | css: { 3 | loaderOptions: { 4 | sass: { 5 | data: ` 6 | @import "@/global-styles/colors.scss"; 7 | @import "@/global-styles/typography.scss"; 8 | ` 9 | } 10 | } 11 | } 12 | }; 13 | --------------------------------------------------------------------------------