├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .firebaserc ├── .gitignore ├── LICENSE ├── README.md ├── apps └── landing │ ├── .editorconfig │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── nuxt.config.js │ ├── package.json │ └── src │ ├── assets │ └── styles │ │ ├── global.css │ │ └── vuetify │ │ ├── _colors.scss │ │ ├── _dark.scss │ │ ├── _elevations.scss │ │ ├── _index.scss │ │ ├── _light.scss │ │ ├── _mixins.scss │ │ ├── _theme.scss │ │ └── _variables.scss │ ├── components │ └── common │ │ ├── LangSwitcher.vue │ │ ├── NewsletterForm.vue │ │ └── UserMenu.vue │ ├── config │ ├── index.js │ └── vuetify.options.js │ ├── firebase │ ├── helpers │ │ └── auth.js │ ├── index.js │ └── models │ │ └── firemodel.js │ ├── lang │ ├── en-US.js │ └── pt-PT.js │ ├── layouts │ ├── backoffice.vue │ ├── default.vue │ └── error.vue │ ├── middleware │ └── redirectIfAuth.js │ ├── mixins │ └── requiresAuth.js │ ├── pages │ ├── about.vue │ ├── auth │ │ ├── check.vue │ │ ├── forgot-password.vue │ │ ├── login.vue │ │ └── register.vue │ ├── contact.vue │ ├── dashboard │ │ ├── index.vue │ │ └── profile.vue │ ├── faq.vue │ ├── index.vue │ ├── policy.vue │ └── terms.vue │ ├── plugins │ └── firebase.js │ ├── static │ ├── favicon.ico │ ├── icon.png │ └── images │ │ ├── error.svg │ │ ├── features │ │ ├── feature1.svg │ │ ├── feature2.svg │ │ ├── feature3.svg │ │ └── feature4.svg │ │ ├── landing.png │ │ ├── logo.png │ │ ├── maintenance.svg │ │ ├── partners │ │ ├── github.png │ │ ├── instagram.png │ │ └── slack.png │ │ ├── share-card.png │ │ └── testimonies │ │ ├── person1.png │ │ ├── person2.png │ │ └── person3.png │ └── store │ └── app │ ├── actions.js │ ├── index.js │ └── mutations.js ├── config ├── app.dist.json ├── keys │ └── key.dist.json └── landing │ └── env.dist.json ├── firebase.json ├── firelayer.js ├── lerna.json ├── package.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_size = 2 7 | indent_style = space 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | *.js.map 4 | *.d.ts 5 | .yarn 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | '@firelayer' 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "firelayer-templates" 4 | }, 5 | "targets": { 6 | "firelayer-templates": { 7 | "hosting": { 8 | "landing": [ 9 | "firelayer-starter-template" 10 | ] 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | node_modules 7 | .firebase 8 | .firelayer 9 | .DS_Store 10 | Thumbs.db 11 | key.json 12 | *.key.json 13 | env*.json 14 | app.json 15 | !*dist.json 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, João Teixeira 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Firelayer 3 | 4 | 5 |
6 | 7 | Landing [https://firelayer-starter-template.web.app](https://firelayer-starter-template.web.app) 8 | 9 | 10 | ### Firelayer Documentation 11 | 12 | [Documentation Website](https://firelayer.io/docs) 13 | 14 | ## Installation Guide 15 | You can use an existing Firebase project or create a new project on the [Firebase Console](https://console.firebase.google.com). 16 | 17 | Follow the guide on preparing a Firebase project for Firelayer 18 | **[Setting up Firebase - Guide](https://firelayer.io/docs/setting-up-firebase)** 19 | 20 | #### Starting in a new project 21 | ```sh 22 | firelayer init new-project -t landing 23 | ``` 24 | 25 | #### Adding to a current firelayer project 26 | ```sh 27 | firelayer add:template landing 28 | ``` 29 | 30 | ### Quick Start 31 | > After Install 32 | 33 | To start run **`yarn dev`** or **`npm run dev`** and the three applications should be ready on: 34 | - Landing - http://localhost:8080 35 | > ports may change if already in use by other services 36 | 37 | ## Content 38 | 39 | #### [`apps/landing` Landing Page](/apps/landing/README.md) 40 | - Sign In, register, verify email and reset password 41 | - User Dashboard 42 | - Localization 43 | - Vue with Nuxt & Vuetify Components 44 | 45 | ## Development 46 | 47 | Landing template is organized as a monorepo using [Lerna](https://lerna.js.org/) and yarn workspaces. Useful scripts include: 48 | 49 | #### `yarn bootstrap` 50 | > Installs package dependencies and links packages together - using lerna and yarn workspaces 51 | 52 | #### `yarn build` 53 | > Cleans the previous builds and starts building on all sub packages - using lerna run build 54 | 55 | #### `yarn dev` 56 | > Starts the dev mode on all sub packages - using lerna run dev 57 | 58 | ## License 59 | 60 | Firelayer is open-sourced software licensed under the [MIT license](https://github.com/firelayer/firelayer/blob/master/LICENSE). 61 | -------------------------------------------------------------------------------- /apps/landing/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /apps/landing/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | '@firelayer/eslint-config-vue' 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /apps/landing/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | node_modules 7 | package-json.lock 8 | dist 9 | .firebase 10 | .DS_Store 11 | .cache 12 | .nuxt 13 | -------------------------------------------------------------------------------- /apps/landing/README.md: -------------------------------------------------------------------------------- 1 | 2 | Firelayer 3 | 4 | 5 | ### Firelayer Documentation 6 | 7 | [Documentation Website](https://firelayer.io/docs) 8 | 9 | ## Tools used to build this package 10 | 11 | [Nuxt](https://nuxtjs.org) 12 | [Vuetify](https://vuetifyjs.com) 13 | [Firelayer](https://firelayer.io) 14 | [Firebase](https://firebase.google.com) 15 | -------------------------------------------------------------------------------- /apps/landing/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const appName = path.basename(path.resolve(process.cwd())) 4 | const config = JSON.parse(process.env[appName] || '{}') 5 | const isProd = process.env.NODE_ENV === 'production' 6 | 7 | // Proxy configurations for local development 8 | const proxy = isProd ? {} : { 9 | '/api': { 10 | target: `http://localhost:5000/${config.firebase.projectId}/us-central1/api` 11 | } 12 | } 13 | 14 | /** 15 | * Base url where the page will be 16 | */ 17 | const baseUrl = 'https://yourdomain.com' 18 | 19 | /** 20 | * SEO content 21 | */ 22 | const SEO = { 23 | name: 'The Company', 24 | title: 'Landing Page - A new awesome service', 25 | description: 'This service is so new and so awesome that even the pope is asking for it', 26 | shortDescription: 'The best service', 27 | keywords: 'service, awesome, great' 28 | } 29 | 30 | export default { 31 | generate: { fallback: true }, 32 | mode: 'universal', 33 | target: 'static', 34 | srcDir: 'src/', 35 | /** 36 | * Environment 37 | */ 38 | env: { 39 | config: process.env[appName] || {} 40 | }, 41 | 42 | /* 43 | ** Headers of the page 44 | */ 45 | head: { 46 | title: SEO.title, 47 | meta: [ 48 | { charset: 'utf-8' }, 49 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 50 | { 'http-equiv': 'X-UA-Compatible', content: 'ie=edge' }, 51 | { hid: 'description', name: 'description', content: SEO.description }, 52 | { hid: 'keywords', name: 'keywords', content: SEO.keywords }, 53 | // open graph 54 | { hid: 'og:site_name', property: 'og:site_name', content: SEO.name }, 55 | { hid: 'og:title', property: 'og:title', content: SEO.title }, 56 | { hid: 'og:type', property: 'og:type', content: 'website' }, 57 | { hid: 'og:description', property: 'og:description', content: SEO.shortDescription }, 58 | { hid: 'og:image', property: 'og:image', content: `${baseUrl}/images/share-card.png` }, 59 | // twitter 60 | { hid: 'twitter:card', name: 'twitter:card', content: 'summary_large_image' }, 61 | { hid: 'twitter:title', name: 'twitter:title', content: SEO.title }, 62 | { hid: 'twitter:description', name: 'twitter:description', content: SEO.shortDescription }, 63 | { hid: 'twitter:image', name: 'twitter:image', content: `${baseUrl}/images/share-card.png` }, 64 | { hid: 'twitter:image:alt', name: 'twitter:image:alt', content: SEO.name }, 65 | { hid: 'robots', name: 'robots', content: 'index,follow' } 66 | ], 67 | link: [ 68 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, 69 | { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Quicksand:wght@500;600;700&display=swap' } 70 | ] 71 | }, 72 | 73 | /* 74 | ** Customize the progress-bar color 75 | */ 76 | loading: { color: '#1976d2' }, 77 | 78 | /* 79 | ** Global CSS 80 | */ 81 | css: [ 82 | '~/assets/styles/global.css' 83 | ], 84 | 85 | /* 86 | ** Plugins to load before mounting the App 87 | */ 88 | plugins: [ 89 | '~/plugins/firebase.js' 90 | ], 91 | 92 | /** 93 | * Build modules 94 | */ 95 | buildModules: [ 96 | ['@nuxtjs/vuetify', { 97 | customVariables: ['~/assets/styles/vuetify'], 98 | optionsPath: '~/config/vuetify.options.js', 99 | treeShake: true, 100 | defaultAssets: { 101 | font: false 102 | } 103 | }] 104 | ], 105 | 106 | styleResources: { 107 | scss: [ 108 | '~/assets/styles/vuetify/_index.scss' 109 | ] 110 | }, 111 | 112 | /* 113 | ** Nuxt.js modules 114 | */ 115 | modules: [ 116 | '@nuxtjs/style-resources', 117 | ['@nuxtjs/axios', { 118 | baseURL: '/' 119 | }], 120 | '@nuxtjs/proxy', 121 | ['@nuxtjs/pwa', { 122 | meta: false, 123 | workbox: false, 124 | oneSignal: false 125 | }], 126 | ['nuxt-i18n', { 127 | detectBrowserLanguage: { 128 | useCookie: true, 129 | cookieKey: 'i18n_redirected' 130 | }, 131 | locales: [{ 132 | code: 'en', 133 | name: 'English', 134 | file: 'en-US.js' 135 | }, { 136 | code: 'pt', 137 | name: 'Português', 138 | file: 'pt-PT.js' 139 | }], 140 | lazy: true, 141 | langDir: 'lang/', 142 | defaultLocale: 'en', 143 | vueI18n: { 144 | fallbackLocale: 'en' 145 | } 146 | }], 147 | '@nuxtjs/sitemap' 148 | ], 149 | 150 | /** 151 | * Proxy 152 | */ 153 | proxy, 154 | 155 | /** 156 | * Sitemap 157 | */ 158 | sitemap: { 159 | hostname: baseUrl, 160 | gzip: true, 161 | exclude: [ 162 | '/_static/' 163 | ] 164 | }, 165 | 166 | /** 167 | * Manifest file 168 | */ 169 | manifest: { 170 | name: 'Firelayer', 171 | 'short_name': 'Firelayer', 172 | description: 'Firelayer - Jump-start you Firebase Web Project' 173 | }, 174 | 175 | /** 176 | * Bundle rendered 177 | */ 178 | render: { 179 | bundleRenderer: { 180 | shouldPreload: (file, type) => { 181 | return ['style', 'font'].includes(type) 182 | } 183 | } 184 | }, 185 | /* 186 | ** Build configuration 187 | */ 188 | build: { 189 | publicPath: '/_static/', 190 | extractCSS: true, 191 | /* 192 | ** You can extend webpack config here 193 | */ 194 | extend (config, ctx) {} 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /apps/landing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "landing", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "nuxt start", 7 | "build": "nuxt generate", 8 | "dev": "nuxt", 9 | "clean": "rimraf .nuxt dist", 10 | "http": "cd dist && npx http-server", 11 | "lint": "eslint --ignore-path .gitignore --ext .js,.vue --report-unused-disable-directives ." 12 | }, 13 | "dependencies": { 14 | "@nuxtjs/axios": "^5.9.7", 15 | "core-js": "2", 16 | "firebase": "8.6.5", 17 | "nuxt": "^2.14.3", 18 | "nuxt-i18n": "^6.10.1" 19 | }, 20 | "resolutions": { 21 | "sass": "1.32.0", 22 | "vuetify": "2.5.3" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.8.4", 26 | "@firelayer/eslint-config-vue": "^1.3.3", 27 | "@nuxtjs/proxy": "^1.3.3", 28 | "@nuxtjs/pwa": "^3.0.0-beta.20", 29 | "@nuxtjs/sitemap": "^2.2.0", 30 | "@nuxtjs/style-resources": "^1.0.0", 31 | "@nuxtjs/vuetify": "^1.11.3", 32 | "eslint": "^6.8.0", 33 | "rimraf": "^3.0.2", 34 | "sass": "1.32.0", 35 | "sass-loader": "^10.1.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /apps/landing/src/assets/styles/global.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: 'Quicksand', sans-serif; 3 | font-size: 16px; 4 | -ms-text-size-adjust: 100%; 5 | -webkit-text-size-adjust: 100%; 6 | -moz-osx-font-smoothing: grayscale; 7 | -webkit-font-smoothing: antialiased; 8 | box-sizing: border-box; 9 | overflow: auto !important; 10 | } 11 | 12 | body { 13 | background-color: #f5f6f7; 14 | color: #333b4f; 15 | } 16 | 17 | a { 18 | text-decoration: none; 19 | } 20 | -------------------------------------------------------------------------------- /apps/landing/src/assets/styles/vuetify/_colors.scss: -------------------------------------------------------------------------------- 1 | $primary: #1976d2; 2 | $secondary: #26303d; 3 | $accent: #50749a; 4 | $error: #e06b6b; 5 | $info: #7da3bf; 6 | $success: #6bc56f; 7 | $warning: #ead11a; 8 | -------------------------------------------------------------------------------- /apps/landing/src/assets/styles/vuetify/_dark.scss: -------------------------------------------------------------------------------- 1 | $material-dark: ( 2 | 'status-bar': ( 3 | 'regular': map-get($shades, 'black'), 4 | 'lights-out': rgba(map-get($shades, 'black'), 0.2) 5 | ), 6 | 'app-bar': map-get($blue-grey, 'darken-4'), 7 | 'background': darken(#19212b, 10%), 8 | 'calendar': ( 9 | 'background-color': #19212b, 10 | 'outside-background-color': #202020, 11 | 'line-color': map-get($blue-grey, 'base'), 12 | 'interval-color': map-get($blue-grey, 'lighten-3'), 13 | 'interval-line-color': map-get($blue-grey, 'darken-2'), 14 | 'text-color': map-get($shades, 'white'), 15 | 'past-color': rgba(map-get($shades, 'white'), .50) 16 | ), 17 | 'cards': #19212b,//map-get($blue-grey, 'darken-3'), 18 | 'chips': #555, 19 | 'dividers': rgba(map-get($shades, 'white'), 0.12), 20 | 'text': ( 21 | 'theme': map-get($shades, 'white'), 22 | 'primary': map-get($shades, 'white'), 23 | 'secondary': rgba(map-get($shades, 'white'), 0.7), 24 | 'disabled': rgba(map-get($shades, 'white'), 0.5), 25 | 'link': map-get($blue, 'accent-1'), 26 | 'link-hover': map-get($blue-grey, 'lighten-3') 27 | ), 28 | 'icons': ( 29 | 'active': map-get($shades, 'white'), 30 | 'inactive': rgba(map-get($shades, 'white'), 0.5) 31 | ), 32 | 'inputs': ( 33 | 'box': map-get($shades, 'white'), 34 | 'solo-inverted': rgba(map-get($shades, 'white'), 0.16), 35 | 'solo-inverted-focused': map-get($shades, 'white'), 36 | 'solo-inverted-focused-text': map-get(map-get($material-light, 'text'), 'primary') 37 | ), 38 | 'buttons': ( 39 | 'disabled': rgba(map-get($shades, 'white'), 0.3), 40 | 'focused': rgba(map-get($shades, 'white'), 0.12), 41 | 'focused-alt': rgba(map-get($shades, 'white'), 0.1), 42 | 'pressed': rgba(#ccc, 0.25) 43 | ), 44 | 'expansion-panels': ( 45 | 'focus': #494949 46 | ), 47 | 'selection-controls': ( 48 | 'thumb': ( 49 | 'inactive': map-get($blue-grey, 'lighten-1'), 50 | 'disabled': map-get($blue-grey, 'darken-3') 51 | ), 52 | 'track': ( 53 | 'inactive': rgba(map-get($shades, 'white'), 0.3), 54 | 'disabled': rgba(map-get($shades, 'white'), 0.1) 55 | ) 56 | ), 57 | 'slider': ( 58 | 'active': rgba(map-get($shades, 'white'), 0.3), 59 | 'inactive': rgba(map-get($shades, 'white'), 0.2), 60 | 'disabled': rgba(map-get($shades, 'white'), 0.2), 61 | 'discrete': map-get($shades, 'white') 62 | ), 63 | 'skeleton': linear-gradient(90deg, transparent, rgba(255, 255, 255, .05), transparent), 64 | 'states': ( 65 | 'hover': 0.08, 66 | 'focus': 0.24, 67 | 'selected': 0.16, 68 | 'activated': 0.24, 69 | 'pressed': 0.32, 70 | 'dragged': 0.16 71 | ), 72 | 'tabs': rgba(map-get($shades, 'white'), 0.6), 73 | 'toggle-buttons': ( 74 | 'color': map-get($shades, 'white') 75 | ), 76 | 'text-fields': ( 77 | 'filled': rgba(map-get($shades, 'white'), 0.08), 78 | 'filled-hover': rgba(map-get($shades, 'white'), 0.16), 79 | 'outlined': rgba(map-get($shades, 'white'), 0.24), 80 | 'outlined-disabled': rgba(map-get($shades, 'white'), 0.16), 81 | 'outlined-hover': map-get($shades, 'white') 82 | ), 83 | 'input-bottom-line': rgba(map-get($shades, 'white'), 0.7), 84 | 'stepper': ( 85 | 'active': rgba(map-get($shades, 'white'), 1), 86 | 'completed': rgba(map-get($shades, 'white'), 0.87), 87 | 'hover': rgba(map-get($shades, 'white'), 0.75) 88 | ), 89 | 'table': ( 90 | 'active': #505050, 91 | 'hover': map-get($blue-grey, 'darken-2'), 92 | 'group': map-get($blue-grey, 'darken-2') 93 | ), 94 | 'picker': ( 95 | 'body': map-get($blue-grey, 'darken-3'), 96 | 'clock': map-get($blue-grey, 'darken-2'), 97 | 'indeterminateTime': map-get($blue-grey, 'darken-1'), 98 | 'title': map-get($blue-grey, 'darken-2') 99 | ), 100 | 'color-picker': ( 101 | 'checkerboard': rgba(map-get($shades, 'white'), 0.12) 102 | ), 103 | 'bg-color': #19212b, 104 | 'fg-color': map-get($shades, 'white'), 105 | 'text-color': map-get($shades, 'white'), 106 | 'primary-text-percent': 1, 107 | 'secondary-text-percent': 0.7, 108 | 'disabledORhints-text-percent': 0.5, 109 | 'divider-percent': 0.12, 110 | 'active-icon-percent': 1, 111 | 'inactive-icon-percent': 0.5 112 | ); 113 | -------------------------------------------------------------------------------- /apps/landing/src/assets/styles/vuetify/_elevations.scss: -------------------------------------------------------------------------------- 1 | $shadow-key-umbra-opacity: rgba(51, 51, 79, 0.05); 2 | $shadow-key-penumbra-opacity: rgba(51, 51, 79, 0.05); 3 | $shadow-key-ambient-opacity: rgba(51, 51, 79, 0.05); 4 | 5 | $shadow-key-umbra: ( 6 | 0: (0px 0px 0px 0px $shadow-key-umbra-opacity), 7 | 1: (0px 2px 5px 0 $shadow-key-umbra-opacity), 8 | 2: (0px 2px 8px 0 $shadow-key-umbra-opacity), 9 | 3: (0px 3px 4px 0 $shadow-key-umbra-opacity), 10 | 4: (0px 2px 5px 0 $shadow-key-umbra-opacity), 11 | 5: (0px 3px 5px 0 $shadow-key-umbra-opacity), 12 | 6: (0px 3px 5px 0 $shadow-key-umbra-opacity), 13 | 7: (0px 4px 5px -2px $shadow-key-umbra-opacity), 14 | 8: (0px 5px 5px -3px $shadow-key-umbra-opacity), 15 | 9: (0px 5px 6px -3px $shadow-key-umbra-opacity), 16 | 10: (0px 6px 6px -3px $shadow-key-umbra-opacity), 17 | 11: (0px 6px 7px -4px $shadow-key-umbra-opacity), 18 | 12: (0px 7px 8px -4px $shadow-key-umbra-opacity), 19 | 13: (0px 7px 8px -4px $shadow-key-umbra-opacity), 20 | 14: (0px 7px 9px -4px $shadow-key-umbra-opacity), 21 | 15: (0px 8px 9px -5px $shadow-key-umbra-opacity), 22 | 16: (0px 8px 10px -5px $shadow-key-umbra-opacity), 23 | 17: (0px 8px 11px -5px $shadow-key-umbra-opacity), 24 | 18: (0px 9px 11px -5px $shadow-key-umbra-opacity), 25 | 19: (0px 9px 12px -6px $shadow-key-umbra-opacity), 26 | 20: (0px 10px 13px -6px $shadow-key-umbra-opacity), 27 | 21: (0px 10px 13px -6px $shadow-key-umbra-opacity), 28 | 22: (0px 10px 14px -6px $shadow-key-umbra-opacity), 29 | 23: (0px 11px 14px -7px $shadow-key-umbra-opacity), 30 | 24: (0px 11px 15px -7px $shadow-key-umbra-opacity) 31 | ); 32 | 33 | $shadow-key-penumbra: ( 34 | 0: (0px 0px 0px 0px $shadow-key-penumbra-opacity), 35 | 1: (0px 1px 1px 0px $shadow-key-penumbra-opacity), 36 | 2: (0px 2px 2px 0px $shadow-key-penumbra-opacity), 37 | 3: (0px 3px 4px 0px $shadow-key-penumbra-opacity), 38 | 4: (0px 4px 5px 0px $shadow-key-penumbra-opacity), 39 | 5: (0px 5px 8px 0px $shadow-key-penumbra-opacity), 40 | 6: (0px 6px 10px 0px $shadow-key-penumbra-opacity), 41 | 7: (0px 7px 10px 1px $shadow-key-penumbra-opacity), 42 | 8: (0px 8px 10px 1px $shadow-key-penumbra-opacity), 43 | 9: (0px 9px 12px 1px $shadow-key-penumbra-opacity), 44 | 10: (0px 10px 14px 1px $shadow-key-penumbra-opacity), 45 | 11: (0px 11px 15px 1px $shadow-key-penumbra-opacity), 46 | 12: (0px 12px 17px 2px $shadow-key-penumbra-opacity), 47 | 13: (0px 13px 19px 2px $shadow-key-penumbra-opacity), 48 | 14: (0px 14px 21px 2px $shadow-key-penumbra-opacity), 49 | 15: (0px 15px 22px 2px $shadow-key-penumbra-opacity), 50 | 16: (0px 16px 24px 2px $shadow-key-penumbra-opacity), 51 | 17: (0px 17px 26px 2px $shadow-key-penumbra-opacity), 52 | 18: (0px 18px 28px 2px $shadow-key-penumbra-opacity), 53 | 19: (0px 19px 29px 2px $shadow-key-penumbra-opacity), 54 | 20: (0px 20px 31px 3px $shadow-key-penumbra-opacity), 55 | 21: (0px 21px 33px 3px $shadow-key-penumbra-opacity), 56 | 22: (0px 22px 35px 3px $shadow-key-penumbra-opacity), 57 | 23: (0px 23px 36px 3px $shadow-key-penumbra-opacity), 58 | 24: (0px 24px 38px 3px $shadow-key-penumbra-opacity) 59 | ); 60 | 61 | $shadow-key-ambient: ( 62 | 0: (0px 1px 2px 0px $shadow-key-ambient-opacity), 63 | 1: (0px 1px 2px 0px $shadow-key-ambient-opacity), 64 | 2: (0px 1px 2px 0px $shadow-key-ambient-opacity), 65 | 3: (0px 1px 2px 0px $shadow-key-ambient-opacity), 66 | 4: (0px 1px 2px 0px $shadow-key-ambient-opacity), 67 | 5: (0px 1px 2px 0px $shadow-key-ambient-opacity), 68 | 6: (0px 1px 2px 0px $shadow-key-ambient-opacity), 69 | 7: (0px 2px 16px 1px $shadow-key-ambient-opacity), 70 | 8: (0px 3px 14px 2px $shadow-key-ambient-opacity), 71 | 9: (0px 3px 16px 2px $shadow-key-ambient-opacity), 72 | 10: (0px 4px 18px 3px $shadow-key-ambient-opacity), 73 | 11: (0px 4px 20px 3px $shadow-key-ambient-opacity), 74 | 12: (0px 5px 22px 4px $shadow-key-ambient-opacity), 75 | 13: (0px 5px 24px 4px $shadow-key-ambient-opacity), 76 | 14: (0px 5px 26px 4px $shadow-key-ambient-opacity), 77 | 15: (0px 6px 28px 5px $shadow-key-ambient-opacity), 78 | 16: (0px 6px 30px 5px $shadow-key-ambient-opacity), 79 | 17: (0px 6px 32px 5px $shadow-key-ambient-opacity), 80 | 18: (0px 7px 34px 6px $shadow-key-ambient-opacity), 81 | 19: (0px 7px 36px 6px $shadow-key-ambient-opacity), 82 | 20: (0px 8px 38px 7px $shadow-key-ambient-opacity), 83 | 21: (0px 8px 40px 7px $shadow-key-ambient-opacity), 84 | 22: (0px 8px 42px 7px $shadow-key-ambient-opacity), 85 | 23: (0px 9px 44px 8px $shadow-key-ambient-opacity), 86 | 24: (0px 9px 46px 8px $shadow-key-ambient-opacity) 87 | ); 88 | -------------------------------------------------------------------------------- /apps/landing/src/assets/styles/vuetify/_index.scss: -------------------------------------------------------------------------------- 1 | @import './_variables'; 2 | @import './_colors'; 3 | @import './_elevations'; 4 | @import './_mixins'; 5 | // Theme specific setup 6 | // ordering is important 7 | @import './_light'; 8 | @import './_dark'; 9 | @import './_theme'; 10 | -------------------------------------------------------------------------------- /apps/landing/src/assets/styles/vuetify/_light.scss: -------------------------------------------------------------------------------- 1 | @import '~vuetify/src/styles/settings/_colors.scss'; 2 | 3 | $material-light: ( 4 | 'status-bar': ( 5 | 'regular': map-get($blue-grey, 'lighten-2'), 6 | 'lights-out': rgba(map-get($shades, 'white'), 0.7) 7 | ), 8 | 'app-bar': #edf2f7, 9 | 'background': #f7f9fc, 10 | 'calendar': ( 11 | 'background-color': map-get($shades, 'white'), 12 | 'outside-background-color': #f7f7f7, 13 | 'line-color': map-get($blue-grey, 'lighten-2'), 14 | 'interval-color': map-get($blue-grey, 'darken-3'), 15 | 'interval-line-color': map-get($blue-grey, 'lighten-2'), 16 | 'text-color': map-get($shades, 'black'), 17 | 'past-color': rgba(map-get($shades, 'black'), .38) 18 | ), 19 | 'cards': map-get($shades, 'white'), 20 | 'chips': #e0e0e0, 21 | 'dividers': rgba(map-get($shades, 'black'), 0.12), 22 | 'text': ( 23 | 'theme': map-get($shades, 'white'), 24 | 'primary': #202c36, 25 | 'secondary': rgba(map-get($shades, 'black'), 0.8), 26 | 'disabled': rgba(map-get($shades, 'black'), 0.58), 27 | 'link': map-get($blue, 'darken-2'), 28 | 'link-hover': map-get($blue-grey, 'darken-3') 29 | ), 30 | 'icons': ( 31 | 'active': rgba(map-get($shades, 'black'), 0.8), 32 | 'inactive': rgba(map-get($shades, 'black'), 0.38) 33 | ), 34 | 'inputs': ( 35 | 'box': rgba(map-get($shades, 'black'), 0.04), 36 | 'solo-inverted': rgba(map-get($shades, 'black'), 0.16), 37 | 'solo-inverted-focused': map-get($blue-grey, 'darken-3'), 38 | 'solo-inverted-focused-text': map-get($shades, 'white') 39 | ), 40 | 'buttons': ( 41 | 'disabled': rgba(map-get($shades, 'black'), 0.26), 42 | 'focused': rgba(map-get($shades, 'black'), 0.12), 43 | 'focused-alt': rgba(map-get($shades, 'white'), 0.6), 44 | 'pressed': rgba(#999, 0.4) 45 | ), 46 | 'expansion-panels': ( 47 | 'focus': map-get($blue-grey, 'lighten-5') 48 | ), 49 | 'selection-controls': ( 50 | 'thumb': ( 51 | 'inactive': map-get($shades, 'white'), 52 | 'disabled': map-get($blue-grey, 'lighten-5') 53 | ), 54 | 'track': ( 55 | 'inactive': rgba(map-get($shades, 'black'), 0.38), 56 | 'disabled': rgba(map-get($shades, 'black'), 0.12) 57 | ) 58 | ), 59 | 'slider': ( 60 | 'active': rgba(map-get($shades, 'black'), 0.38), 61 | 'inactive': rgba(map-get($shades, 'black'), 0.26), 62 | 'disabled': rgba(map-get($shades, 'black'), 0.26), 63 | 'discrete': map-get($shades, 'black') 64 | ), 65 | 'skeleton': linear-gradient(90deg, transparent, rgba(255, 255, 255, .3), transparent), 66 | 'states': ( 67 | 'hover': 0.04, 68 | 'focus': 0.12, 69 | 'selected': 0.08, 70 | 'activated': 0.12, 71 | 'pressed': 0.16, 72 | 'dragged': 0.08 73 | ), 74 | 'tabs': map-get($blue-grey, 'darken-1'), 75 | 'toggle-buttons': ( 76 | 'color': map-get($shades, 'black') 77 | ), 78 | 'text-fields': ( 79 | 'filled': rgba(map-get($shades, 'black'), 0.06), 80 | 'filled-hover': rgba(map-get($shades, 'black'), 0.12), 81 | 'outlined': rgba(map-get($shades, 'black'), 0.38), 82 | 'outlined-disabled': rgba(map-get($shades, 'black'), 0.26), 83 | 'outlined-hover': rgba(map-get($shades, 'black'), 0.86) 84 | ), 85 | 'input-bottom-line': rgba(map-get($shades, 'black'), 0.42), 86 | 'stepper': ( 87 | 'active': rgba(map-get($shades, 'white'), 1), 88 | 'completed': rgba(map-get($shades, 'black'), 0.87), 89 | 'hover': rgba(map-get($shades, 'black'), 0.54) 90 | ), 91 | 'table': ( 92 | 'active': map-get($blue-grey, 'lighten-4'), 93 | 'hover': map-get($blue-grey, 'lighten-5'), 94 | 'group': map-get($blue-grey, 'lighten-5') 95 | ), 96 | 'picker': ( 97 | 'body': map-get($shades, 'white'), 98 | 'clock': map-get($blue-grey, 'lighten-2'), 99 | 'indeterminateTime': map-get($blue-grey, 'lighten-1'), 100 | 'title': map-get($blue-grey, 'lighten-2') 101 | ), 102 | 'color-picker': ( 103 | 'checkerboard': rgba(map-get($shades, 'white'), 0) 104 | ), 105 | 'bg-color': map-get($shades, 'white'), 106 | 'fg-color': map-get($shades, 'black'), 107 | 'text-color': map-get($shades, 'black'), 108 | 'primary-text-percent': 0.87, 109 | 'secondary-text-percent': 0.6, 110 | 'disabledORhints-text-percent': 0.38, 111 | 'divider-percent': 0.12, 112 | 'active-icon-percent': 0.54, 113 | 'inactive-icon-percent': 0.38 114 | ); 115 | -------------------------------------------------------------------------------- /apps/landing/src/assets/styles/vuetify/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin media($media) { 2 | @media #{map-get($display-breakpoints, $media)} 3 | {@content;} 4 | } 5 | -------------------------------------------------------------------------------- /apps/landing/src/assets/styles/vuetify/_theme.scss: -------------------------------------------------------------------------------- 1 | $material-theme: $material-light; 2 | -------------------------------------------------------------------------------- /apps/landing/src/assets/styles/vuetify/_variables.scss: -------------------------------------------------------------------------------- 1 | $color-pack: true; 2 | $body-font-family: 'Quicksand', sans-serif; 3 | $font-size-root: 18px; 4 | $line-height-root: 1.5; 5 | $border-radius-root: 6px; 6 | 7 | $spacer: 8px; 8 | 9 | $grid-breakpoints: ( 10 | 'xs': 0, 11 | 'sm': 600px, 12 | 'md': 960px, 13 | 'lg': 1280px - 16px, 14 | 'xl': 1920px - 16px 15 | ); 16 | 17 | $grid-gutter: $spacer * 6; 18 | $form-grid-gutter: $spacer * 2; 19 | $grid-columns: 12; 20 | 21 | $container-padding-x: $grid-gutter / 2; 22 | 23 | $grid-gutters: ( 24 | 'xs': $grid-gutter / 12, 25 | 'sm': $grid-gutter / 6, 26 | 'md': $grid-gutter / 3, 27 | 'lg': $grid-gutter * 2/3, 28 | 'xl': $grid-gutter 29 | ); 30 | 31 | $container-max-widths: ( 32 | 'md': map-get($grid-breakpoints, 'md') * 0.9375, 33 | 'lg': map-get($grid-breakpoints, 'lg') * 0.9375, 34 | 'xl': map-get($grid-breakpoints, 'xl') * 0.9375 35 | ); 36 | 37 | $display-breakpoints: ( 38 | 'print-only': 'only print', 39 | 'screen-only': 'only screen', 40 | 'xs-only': 'only screen and (max-width: #{map-get($grid-breakpoints, 'sm') - 1})', 41 | 'sm-only': 'only screen and (min-width: #{map-get($grid-breakpoints, 'sm')}) and (max-width: #{map-get($grid-breakpoints, 'md') - 1})', 42 | 'sm-and-down': 'only screen and (max-width: #{map-get($grid-breakpoints, 'md') - 1})', 43 | 'sm-and-up': 'only screen and (min-width: #{map-get($grid-breakpoints, 'sm')})', 44 | 'md-only': 'only screen and (min-width: #{map-get($grid-breakpoints, 'md')}) and (max-width: #{map-get($grid-breakpoints, 'lg') - 1})', 45 | 'md-and-down': 'only screen and (max-width: #{map-get($grid-breakpoints, 'lg') - 1})', 46 | 'md-and-up': 'only screen and (min-width: #{map-get($grid-breakpoints, 'md')})', 47 | 'lg-only': 'only screen and (min-width: #{map-get($grid-breakpoints, 'lg')}) and (max-width: #{map-get($grid-breakpoints, 'xl') - 1})', 48 | 'lg-and-down': 'only screen and (max-width: #{map-get($grid-breakpoints, 'xl') - 1})', 49 | 'lg-and-up': 'only screen and (min-width: #{map-get($grid-breakpoints, 'lg')})', 50 | 'xl-only': 'only screen and (min-width: #{map-get($grid-breakpoints, 'xl')})' 51 | ); 52 | 53 | $font-weights: ( 54 | 'thin': 300, 55 | 'light': 400, 56 | 'regular': 500, 57 | 'medium': 500, 58 | 'bold': 600, 59 | 'black': 700 60 | ); 61 | 62 | $heading-font-family: $body-font-family; 63 | $blockquote-font-size: 18px; 64 | $blockquote-font-weight: 500; 65 | $code-color: #bd4147; 66 | $code-kbd-border-radius: 3px; 67 | $code-kbd-font-size: 85%; 68 | $code-kbd-font-weight: 700; 69 | $input-top-spacing: 16px; 70 | $text-field-active-label-height: 12px; 71 | 72 | // BUTTONS 73 | $btn-font-weight: 700; 74 | $btn-text-transform: none; 75 | $btn-font-size: 1.2rem; 76 | $btn-letter-spacing: normal; 77 | $btn-sizes: ( 78 | 'x-small': 20, 79 | 'small': 28, 80 | 'default': 36, 81 | 'large': 44, 82 | 'x-large': 56 83 | ); 84 | -------------------------------------------------------------------------------- /apps/landing/src/components/common/LangSwitcher.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 31 | -------------------------------------------------------------------------------- /apps/landing/src/components/common/NewsletterForm.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 27 | -------------------------------------------------------------------------------- /apps/landing/src/components/common/UserMenu.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 71 | 72 | 77 | -------------------------------------------------------------------------------- /apps/landing/src/config/index.js: -------------------------------------------------------------------------------- 1 | let config = {} 2 | 3 | try { 4 | config = JSON.parse(process.env.config) 5 | } catch (error) { 6 | config = {} 7 | } 8 | 9 | export default config 10 | -------------------------------------------------------------------------------- /apps/landing/src/config/vuetify.options.js: -------------------------------------------------------------------------------- 1 | // vuetify.options.js 2 | export default { 3 | theme: { 4 | themes: { 5 | light: { 6 | primary: '#1976d2', 7 | secondary: '#26303d', 8 | accent: '#50749a', 9 | error: '#e06b6b', 10 | info: '#7da3bf', 11 | success: '#6bc56f', 12 | warning: '#ead11a' 13 | } 14 | } 15 | }, 16 | icons: { 17 | iconfont: 'mdi' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/landing/src/firebase/helpers/auth.js: -------------------------------------------------------------------------------- 1 | import { auth } from '../index' 2 | 3 | export const signUp = (email, password) => { 4 | return auth().createUserWithEmailAndPassword(email, password) 5 | } 6 | 7 | export const signIn = (email, password) => { 8 | return auth().signInWithEmailAndPassword(email, password) 9 | } 10 | 11 | export const signInWithProvider = (provider) => { 12 | const driver = getProviderInstance(provider.id) 13 | 14 | return auth().signInWithPopup(driver) 15 | } 16 | 17 | export const getProviderInstance = (providerId) => { 18 | switch (providerId) { 19 | case 'google': return new auth.GoogleAuthProvider() 20 | case 'facebook': return new auth.FacebookAuthProvider() 21 | case 'apple': return new auth.OAuthProvider('apple.com') 22 | case 'twitter': return new auth.TwitterAuthProvider() 23 | case 'github': return new auth.GithubAuthProvider() 24 | case 'microsoft': return new auth.OAuthProvider('microsoft.com') 25 | case 'yahoo': return new auth.OAuthProvider('yahoo.com') 26 | } 27 | 28 | return new Error(`Provider '${providerId}' not found.`) 29 | } 30 | -------------------------------------------------------------------------------- /apps/landing/src/firebase/index.js: -------------------------------------------------------------------------------- 1 | import _firebase from 'firebase/app' 2 | import 'firebase/auth' 3 | import 'firebase/firestore' 4 | import 'firebase/database' 5 | import 'firebase/storage' 6 | import config from '../config' 7 | 8 | if (_firebase.apps.length === 0) { 9 | _firebase.initializeApp(config.firebase) 10 | } 11 | 12 | export const firebase = _firebase 13 | export const { auth, storage } = firebase 14 | export const db = firebase.firestore 15 | export const realtime = firebase.database 16 | 17 | export const timestamp = firebase.firestore.Timestamp.now 18 | export const { serverTimestamp } = firebase.firestore.FieldValue 19 | 20 | export { Firemodel } from './models/firemodel' 21 | -------------------------------------------------------------------------------- /apps/landing/src/firebase/models/firemodel.js: -------------------------------------------------------------------------------- 1 | import { db, timestamp, serverTimestamp } from '..' 2 | 3 | export class Firemodel { 4 | constructor(id) { 5 | this.data = {} 6 | 7 | if (id) { 8 | if (typeof id === 'string') { 9 | this.id = id 10 | } else { 11 | const snapshot = id 12 | 13 | this.id = snapshot.id 14 | this.data = snapshot.data() 15 | this.snapshot = snapshot 16 | } 17 | } 18 | } 19 | 20 | get collection() { 21 | return db().collection('_') 22 | } 23 | 24 | get doc() { 25 | return this.collection.doc(this.id) 26 | } 27 | 28 | async set(id, data) { 29 | const serverStamp = serverTimestamp() 30 | const emulatedStamp = timestamp() 31 | 32 | await this.collection.doc(id).set({ 33 | ...data, 34 | createdAt: serverStamp, 35 | updatedAt: serverStamp 36 | }) 37 | 38 | this.id = id 39 | this.data = { 40 | ...data, 41 | createdAt: emulatedStamp, 42 | updatedAt: emulatedStamp 43 | } 44 | 45 | return this 46 | } 47 | 48 | async save(data) { 49 | const serverStamp = serverTimestamp() 50 | 51 | // so we don't have to trigger a .get() just to see the new stamps 52 | const emulatedStamp = timestamp() 53 | 54 | if (this.id) { 55 | // omit createdAt 56 | const copy = {} 57 | const keys = Object.keys(data) 58 | 59 | for (let i = 0, len = keys.length; i < len; i++) { 60 | const key = keys[i] 61 | 62 | if (key !== 'createdAt') copy[key] = data[key] 63 | } 64 | 65 | await this.doc.update({ 66 | ...copy, 67 | updatedAt: serverStamp 68 | }) 69 | 70 | this.data = { 71 | ...this.data, 72 | ...data, 73 | updatedAt: emulatedStamp 74 | } 75 | } else { 76 | const result = await this.collection.add({ 77 | ...data, 78 | createdAt: serverStamp, 79 | updatedAt: serverStamp 80 | }) 81 | 82 | this.id = result.id 83 | this.data = { 84 | ...data, 85 | createdAt: emulatedStamp, 86 | updatedAt: emulatedStamp 87 | } 88 | } 89 | 90 | return this 91 | } 92 | 93 | async get() { 94 | if (!this.id) throw new Error('Could not get document, missing id property') 95 | 96 | const result = await this.doc.get() 97 | 98 | this.data = result.data() 99 | 100 | return this 101 | } 102 | 103 | delete() { 104 | return this.doc.delete() 105 | } 106 | } 107 | 108 | export default Firemodel 109 | -------------------------------------------------------------------------------- /apps/landing/src/lang/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | title: 'Landing page for Project title', 4 | getstarted: 'Get Started', 5 | about: 'About', 6 | feature1: 'Feature', 7 | feature2: 'Frequent Updates', 8 | feature3: 'Social Feature', 9 | testimonies: 'What people are saying', 10 | stat1: 'Posts', 11 | stat2: 'Followers', 12 | stat3: 'Stories', 13 | stat4: 'Repositories' 14 | }, 15 | newsletter: { 16 | title: 'TheCompany Newsletter', 17 | subtitle: 'Get the latest TheCompany news to your inbox.', 18 | button: 'Subscribe' 19 | }, 20 | common: { 21 | about: 'About Us', 22 | contact: 'Contact Us', 23 | faq: 'Frequently Asked Questions', 24 | tos: 'Terms of Service', 25 | policy: 'Privacy Policy' 26 | }, 27 | footer: { 28 | title1: 'General Resources', 29 | title2: 'Terms' 30 | }, 31 | faq: { 32 | call: 'Have other question? Please fell free to reach out:' 33 | }, 34 | dashboard: { 35 | logged: 'Logged with:', 36 | maintenance: 'Under Maintenance' 37 | }, 38 | contact: { 39 | name: 'Name', 40 | email: 'Email', 41 | message: 'Message', 42 | button: 'Send Message' 43 | }, 44 | usermenu: { 45 | profile: 'Profile', 46 | signin: 'Sign In', 47 | dashboard: 'Dashboard', 48 | signout: 'Sign Out' 49 | }, 50 | error: { 51 | notfound: 'Page Not Found', 52 | other: 'An Error Ocurred' 53 | }, 54 | check: { 55 | title: 'Set New Password', 56 | backtosign: 'Back to Sign In', 57 | newpassword: 'New Password', 58 | button: 'Set new password and Sign in', 59 | error: 'The action link is invalid', 60 | verifylink: 'Verifying link...', 61 | verifyemail: 'Verifying email address...', 62 | emailverified: 'Email verified! Redirecting...' 63 | }, 64 | forgot: { 65 | title: 'Forgot Password?', 66 | subtitle: 'Enter your account email address and we will send you a link to reset your password.', 67 | email: 'Email', 68 | button: 'Request Password Reset', 69 | backtosign: 'Back to Sign In' 70 | }, 71 | login: { 72 | title: 'Sign In', 73 | email: 'Email', 74 | password: 'Password', 75 | button: 'Sign In', 76 | orsign: 'Or sign in with', 77 | forgot: 'Forgot password?', 78 | noaccount: 'Don\'t have an account?', 79 | create: 'Create one here', 80 | error: 'The email / password combination is invalid' 81 | }, 82 | register: { 83 | title: 'Create Account', 84 | name: 'Full name', 85 | email: 'Email', 86 | password: 'Password', 87 | button: 'Create Account', 88 | orsign: 'Or sign up with', 89 | agree: 'By signing up, you agree to the', 90 | account: 'Already have an account?', 91 | signin: 'Sign In' 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /apps/landing/src/lang/pt-PT.js: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | title: 'Página inicial do Projecto', 4 | getstarted: 'Começar', 5 | about: 'Sobre', 6 | feature1: 'Funcionalidade', 7 | feature2: 'Atualizações Frequentes', 8 | feature3: 'Funcionalidade Social', 9 | testimonies: 'O que dizem', 10 | stat1: 'Posts', 11 | stat2: 'Seguidores', 12 | stat3: 'Stories', 13 | stat4: 'Repositórios' 14 | }, 15 | newsletter: { 16 | title: 'TheCompany Newsletter', 17 | subtitle: 'Subscreve e obtém as últimas notícias de TheCompany na tua caixa de correio.', 18 | button: 'Subscreve' 19 | }, 20 | common: { 21 | about: 'Sobre Nós', 22 | contact: 'Contacte-nos', 23 | faq: 'Perguntas Frequentes', 24 | tos: 'Termos de Serviço', 25 | policy: 'Política de Privacidade' 26 | }, 27 | footer: { 28 | title1: 'Recursos Gerais', 29 | title2: 'Termos' 30 | }, 31 | faq: { 32 | call: 'Tens outra questão? Entra em contacto:' 33 | }, 34 | dashboard: { 35 | logged: 'Entrou com:', 36 | maintenance: 'Em Manutenção' 37 | }, 38 | contact: { 39 | name: 'Nome', 40 | email: 'Email', 41 | message: 'Mensagem', 42 | button: 'Enviar Mensagem' 43 | }, 44 | usermenu: { 45 | profile: 'Perfil', 46 | signin: 'Entrar', 47 | dashboard: 'Painel', 48 | signout: 'Sair' 49 | }, 50 | error: { 51 | notfound: 'Página Não Encontrada', 52 | other: 'Ocorreu um Erro' 53 | }, 54 | check: { 55 | title: 'Insira a nova palavra-passe', 56 | backtosign: 'Voltar ao inicio', 57 | newpassword: 'Nova palavra-passe', 58 | button: 'Gravar nova palavar passe e entrar', 59 | error: 'Este link é inválido', 60 | verifylink: 'Verificando o link...', 61 | verifyemail: 'Verificando o endereço de email...', 62 | emailverified: 'Email verificado! Redirecionando...' 63 | }, 64 | forgot: { 65 | title: 'Esqueceu a palavra-passe?', 66 | subtitle: 'Digite o endereço de e-mail da sua conta e enviaremos um link para redefinir sua palavra-passe.', 67 | email: 'Email', 68 | button: 'Solicitar redefinição de palavra-passe', 69 | backtosign: 'Voltar ao inicio' 70 | }, 71 | login: { 72 | title: 'Entrar', 73 | email: 'Email', 74 | password: 'Palavra-passe', 75 | button: 'Entrar', 76 | orsign: 'Ou entrar com', 77 | forgot: 'Esqueceu a palavra-passe?', 78 | noaccount: 'Não tem uma conta?', 79 | create: 'Crie uma aqui', 80 | error: 'A combinação de email / palavra-passe é inválida' 81 | }, 82 | register: { 83 | title: 'Criar Conta', 84 | name: 'Nome completo', 85 | email: 'Email', 86 | password: 'Palavra-passe', 87 | button: 'Criar a conta', 88 | orsign: 'Ou criar com', 89 | agree: 'Ao criar uma conta, concordo com os', 90 | account: 'Já tem uma conta?', 91 | signin: 'Entrar' 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /apps/landing/src/layouts/backoffice.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 125 | -------------------------------------------------------------------------------- /apps/landing/src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 116 | -------------------------------------------------------------------------------- /apps/landing/src/layouts/error.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 41 | -------------------------------------------------------------------------------- /apps/landing/src/middleware/redirectIfAuth.js: -------------------------------------------------------------------------------- 1 | export default function ({ app, store, redirect }) { 2 | if (store.state.app.user) { 3 | redirect(app.localePath('/dashboard')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/src/mixins/requiresAuth.js: -------------------------------------------------------------------------------- 1 | export default { 2 | async asyncData({ app, store, redirect }) { 3 | if (process.client) { 4 | if (!store.state.app.isAuthReady) await waitForAuthReady(store) 5 | 6 | if (!store.state.app.user) { 7 | redirect(app.localePath('/auth/login')) 8 | } 9 | } 10 | 11 | return {} 12 | } 13 | } 14 | 15 | function waitForAuthReady(store) { 16 | return new Promise((resolve) => { 17 | const unwatch = store.watch( 18 | (state) => state.app.isAuthReady, 19 | (isAuthReady) => { 20 | resolve(isAuthReady) 21 | unwatch() 22 | } 23 | ) 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /apps/landing/src/pages/about.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /apps/landing/src/pages/auth/check.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 169 | 170 | 176 | -------------------------------------------------------------------------------- /apps/landing/src/pages/auth/forgot-password.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 92 | 93 | 99 | -------------------------------------------------------------------------------- /apps/landing/src/pages/auth/login.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 195 | 196 | 211 | -------------------------------------------------------------------------------- /apps/landing/src/pages/auth/register.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 233 | 234 | 249 | -------------------------------------------------------------------------------- /apps/landing/src/pages/contact.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /apps/landing/src/pages/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /apps/landing/src/pages/dashboard/profile.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 81 | 82 | 87 | -------------------------------------------------------------------------------- /apps/landing/src/pages/faq.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /apps/landing/src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 132 | 178 | 179 | 204 | -------------------------------------------------------------------------------- /apps/landing/src/pages/policy.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /apps/landing/src/pages/terms.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /apps/landing/src/plugins/firebase.js: -------------------------------------------------------------------------------- 1 | import { auth } from '../firebase/index' 2 | 3 | export default ({ app, store, redirect }) => { 4 | auth().onAuthStateChanged((user) => { 5 | const { currentRoute } = app.router 6 | 7 | if (!store.state.app.isAuthReady) store.commit('app/SET_AUTH_READY') 8 | 9 | if (user) { 10 | store.commit('app/SET_USER', { 11 | uid: user.uid, 12 | email: user.email, 13 | photoURL: user.photoURL, 14 | displayName: user.displayName 15 | }) 16 | 17 | if (/^auth/.test(currentRoute.name)) { 18 | redirect(app.localePath('/dashboard')) 19 | } 20 | } else { 21 | store.commit('app/SET_USER', null) 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /apps/landing/src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/favicon.ico -------------------------------------------------------------------------------- /apps/landing/src/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/icon.png -------------------------------------------------------------------------------- /apps/landing/src/static/images/error.svg: -------------------------------------------------------------------------------- 1 | not found -------------------------------------------------------------------------------- /apps/landing/src/static/images/features/feature1.svg: -------------------------------------------------------------------------------- 1 | all the data -------------------------------------------------------------------------------- /apps/landing/src/static/images/features/feature2.svg: -------------------------------------------------------------------------------- 1 | good team -------------------------------------------------------------------------------- /apps/landing/src/static/images/features/feature3.svg: -------------------------------------------------------------------------------- 1 | viral tweet -------------------------------------------------------------------------------- /apps/landing/src/static/images/features/feature4.svg: -------------------------------------------------------------------------------- 1 | web_developer -------------------------------------------------------------------------------- /apps/landing/src/static/images/landing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/images/landing.png -------------------------------------------------------------------------------- /apps/landing/src/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/images/logo.png -------------------------------------------------------------------------------- /apps/landing/src/static/images/maintenance.svg: -------------------------------------------------------------------------------- 1 | healthy_habit -------------------------------------------------------------------------------- /apps/landing/src/static/images/partners/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/images/partners/github.png -------------------------------------------------------------------------------- /apps/landing/src/static/images/partners/instagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/images/partners/instagram.png -------------------------------------------------------------------------------- /apps/landing/src/static/images/partners/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/images/partners/slack.png -------------------------------------------------------------------------------- /apps/landing/src/static/images/share-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/images/share-card.png -------------------------------------------------------------------------------- /apps/landing/src/static/images/testimonies/person1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/images/testimonies/person1.png -------------------------------------------------------------------------------- /apps/landing/src/static/images/testimonies/person2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/images/testimonies/person2.png -------------------------------------------------------------------------------- /apps/landing/src/static/images/testimonies/person3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firelayer/landing-template/cd06511d7c5e4ad1694c45bab61f7768308611f0/apps/landing/src/static/images/testimonies/person3.png -------------------------------------------------------------------------------- /apps/landing/src/store/app/actions.js: -------------------------------------------------------------------------------- 1 | const updateProfile = async ({ commit, dispatch, state }, userProfile) => { 2 | const { displayName } = userProfile 3 | const userSearch = new User(query) 4 | 5 | try { 6 | const user = await userSearch.get() 7 | 8 | commit('LOADED', [user]) 9 | } catch (error) { 10 | commit('LOADED', []) 11 | } 12 | } 13 | 14 | export default { 15 | updateProfile 16 | } 17 | -------------------------------------------------------------------------------- /apps/landing/src/store/app/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions' 2 | import mutations from './mutations' 3 | 4 | const state = () => ({ 5 | isAuthReady: false, 6 | user: null, 7 | settings: { 8 | maintenance: false 9 | } 10 | }) 11 | 12 | export default { 13 | state, 14 | actions, 15 | mutations 16 | } 17 | -------------------------------------------------------------------------------- /apps/landing/src/store/app/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | SET_USER: (state, user) => { 3 | state.user = user 4 | }, 5 | SET_AUTH_READY: (state) => { 6 | state.isAuthReady = true 7 | }, 8 | SET_SETTINGS: (state, settings) => { 9 | state.settings = settings 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/app.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "firebase": { 3 | "apiKey": "", 4 | "authDomain": "", 5 | "databaseURL": "", 6 | "projectId": "", 7 | "storageBucket": "", 8 | "messagingSenderId": "", 9 | "appId": "", 10 | "measurementId": "" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /config/keys/key.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "", 3 | "project_id": "", 4 | "private_key_id": "", 5 | "private_key": "", 6 | "client_email": "", 7 | "client_id": "", 8 | "auth_uri": "", 9 | "token_uri": "", 10 | "auth_provider_x509_cert_url": "", 11 | "client_x509_cert_url": "" 12 | } 13 | -------------------------------------------------------------------------------- /config/landing/env.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "https://mydomain.com" 3 | } 4 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": [ 3 | { 4 | "target": "landing", 5 | "public": "apps/landing/dist", 6 | "ignore": [ 7 | "firebase.json", 8 | "**/.*", 9 | "**/node_modules/**" 10 | ], 11 | "rewrites": [ 12 | { 13 | "source": "/api{,/**}", 14 | "function": "api" 15 | } 16 | ] 17 | } 18 | ], 19 | "emulators": { 20 | "functions": { 21 | "port": 5001 22 | }, 23 | "firestore": { 24 | "port": 8080 25 | }, 26 | "database": { 27 | "port": 9000 28 | }, 29 | "hosting": { 30 | "port": 5000 31 | }, 32 | "pubsub": { 33 | "port": 8085 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /firelayer.js: -------------------------------------------------------------------------------- 1 | /* 2 | |--------------------------------------------------------------------- 3 | | Firelayer Template Config File 4 | |--------------------------------------------------------------------- 5 | | 6 | | Landing - Website built with Nuxt.js 7 | | 8 | */ 9 | module.exports = async function({ chalk, open, logger, prompt, targetDir }) { 10 | const website = 'https://firelayer.io/templates/landing' 11 | 12 | console.log(`\nTemplate Documentation - ${chalk.bold.green(website)}\n`) 13 | 14 | open(website) 15 | } 16 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "registry": "https://registry.npmjs.org", 6 | "packages": [ 7 | "apps/*" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "landing-template", 3 | "private": true, 4 | "description": "Firelayer Landing - Nuxt Landing Website", 5 | "license": "MIT", 6 | "author": "João Teixeira ", 7 | "workspaces": [ 8 | "apps/*" 9 | ], 10 | "scripts": { 11 | "bootstrap": "yarn --ignore-engines && lerna bootstrap", 12 | "build": "firelayer run \"lerna run build\"", 13 | "build:landing": "firelayer run \"cd apps/landing && npm run build\"", 14 | "clean": "lerna run --parallel clean", 15 | "deploy": "npm run build && firebase deploy", 16 | "dev": "firelayer run \"lerna run dev --parallel\"", 17 | "dev:landing": "firelayer run \"cd apps/landing && npm run dev\"", 18 | "emulators": "firelayer run \"firebase emulators:start\"", 19 | "landing": "yarn dev:landing", 20 | "lint": "lerna run lint", 21 | "reset": "lerna clean --yes && npm run clean", 22 | "test": "lerna run test --parallel" 23 | }, 24 | "engines": { 25 | "node": ">= 10.16.0", 26 | "yarn": ">= 1.12.0" 27 | }, 28 | "resolutions": { 29 | "sass": "1.32.0", 30 | "vuetify": "2.5.3" 31 | }, 32 | "devDependencies": { 33 | "@firelayer/eslint-config": "^1.3.2", 34 | "eslint": "^6.8.0", 35 | "lerna": "^3.20.2", 36 | "typescript": "^3.8.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true 9 | }, 10 | "exclude": [ 11 | "**/dist", 12 | "node_modules", 13 | "**/node_modules" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------