├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── angular.json ├── browserslist ├── docs ├── COMPODOC.md ├── DEVELOPER.md ├── FEATURES.md ├── TODO.md └── TSCOV.md ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── package-lock.json ├── package.json ├── src ├── app │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── app.routing.ts │ ├── components │ │ ├── backgrounds │ │ │ ├── background-email │ │ │ │ └── background-email.component.ts │ │ │ ├── background-empty │ │ │ │ └── background-empty.component.ts │ │ │ ├── background-normal │ │ │ │ └── background-normal.component.ts │ │ │ ├── background-slant │ │ │ │ └── background-slant.component.ts │ │ │ └── backgrounds.module.ts │ │ ├── blocks │ │ │ ├── blocks.module.ts │ │ │ ├── card-block │ │ │ │ ├── card-block.component.html │ │ │ │ ├── card-block.component.scss │ │ │ │ └── card-block.component.ts │ │ │ ├── first-block │ │ │ │ ├── first-block.component.html │ │ │ │ ├── first-block.component.scss │ │ │ │ └── first-block.component.ts │ │ │ ├── second-block │ │ │ │ ├── second-block.component.html │ │ │ │ ├── second-block.component.scss │ │ │ │ └── second-block.component.ts │ │ │ └── third-block │ │ │ │ ├── third-block.component.html │ │ │ │ ├── third-block.component.scss │ │ │ │ └── third-block.component.ts │ │ ├── email-me │ │ │ ├── email-me.component.html │ │ │ ├── email-me.component.scss │ │ │ ├── email-me.component.spec.ts │ │ │ └── email-me.component.ts │ │ └── misc │ │ │ ├── carousel │ │ │ ├── carousel.component.html │ │ │ ├── carousel.component.scss │ │ │ └── carousel.component.ts │ │ │ ├── change │ │ │ ├── change-detail.component.ts │ │ │ └── change-detection.component.ts │ │ │ ├── drag-drop │ │ │ ├── drag-drop.component.html │ │ │ ├── drag-drop.component.scss │ │ │ └── drag-drop.component.ts │ │ │ ├── expansion-panel │ │ │ ├── expansion-panel.component.html │ │ │ ├── expansion-panel.component.scss │ │ │ └── expansion-panel.component.ts │ │ │ ├── misc.component.html │ │ │ ├── misc.component.scss │ │ │ ├── misc.component.ts │ │ │ ├── misc.module.ts │ │ │ ├── stepper │ │ │ ├── stepper.component.html │ │ │ ├── stepper.component.scss │ │ │ └── stepper.component.ts │ │ │ ├── table │ │ │ ├── table.component.html │ │ │ ├── table.component.scss │ │ │ └── table.component.ts │ │ │ └── virtual-reality │ │ │ ├── virtreal.component.html │ │ │ ├── virtreal.component.scss │ │ │ └── virtreal.component.ts │ ├── firebase.config.ts │ ├── pages │ │ ├── about-me │ │ │ ├── about-me.component.html │ │ │ ├── about-me.component.spec.ts │ │ │ └── about-me.component.ts │ │ ├── auth │ │ │ ├── auth.component.html │ │ │ ├── auth.component.scss │ │ │ ├── auth.component.ts │ │ │ ├── auth.module.ts │ │ │ └── phone-signin │ │ │ │ ├── phone-signin.component.html │ │ │ │ ├── phone-signin.component.scss │ │ │ │ ├── phone-signin.component.spec.ts │ │ │ │ └── phone-signin.component.ts │ │ ├── contact │ │ │ ├── contact.component.html │ │ │ ├── contact.component.scss │ │ │ ├── contact.component.spec.ts │ │ │ └── contact.component.ts │ │ ├── home │ │ │ ├── home.component.html │ │ │ └── home.component.ts │ │ ├── not-found │ │ │ └── not-found.component.ts │ │ └── profile │ │ │ ├── profile-settings.component.html │ │ │ ├── profile-settings.component.scss │ │ │ ├── profile-settings.component.ts │ │ │ ├── profile.component.html │ │ │ ├── profile.component.scss │ │ │ ├── profile.component.ts │ │ │ └── profile.module.ts │ └── shared │ │ ├── index.ts │ │ ├── interfaces │ │ ├── image.interface.ts │ │ ├── index.ts │ │ └── scene.interface.ts │ │ ├── layout │ │ ├── footer │ │ │ ├── footer.component.html │ │ │ ├── footer.component.scss │ │ │ └── footer.component.ts │ │ ├── header │ │ │ ├── header.component.html │ │ │ ├── header.component.scss │ │ │ └── header.component.ts │ │ └── index.ts │ │ ├── models │ │ ├── contact.model.ts │ │ ├── index.ts │ │ ├── phone-number.model.ts │ │ ├── profile.model.ts │ │ └── user.model.ts │ │ ├── pipes │ │ ├── async-observable-pipe.component.ts │ │ ├── currency-pipe.component.ts │ │ ├── date-pipe.component.ts │ │ ├── json-pipe.component.ts │ │ ├── pipes.module.ts │ │ ├── titlecase-pipe.component.ts │ │ └── year-pipe.component.ts │ │ ├── services │ │ ├── alert.service.ts │ │ ├── auth-guard.service.ts │ │ ├── auth.service.ts │ │ ├── index.ts │ │ ├── user.service.ts │ │ └── window.service.ts │ │ └── validators │ │ ├── email.ts │ │ └── index.ts ├── assets │ ├── .gitkeep │ ├── css │ │ └── bootstrap-social.css │ └── img │ │ ├── angular.png │ │ ├── angular2.png │ │ ├── mb-bg-01.png │ │ ├── mb-bg-02.png │ │ ├── mb-bg-03.png │ │ ├── mb-bg-04.png │ │ ├── mb-bg-05.png │ │ ├── mb-bg-06.png │ │ └── mountsthelens.png ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json ├── tslint.json └── typings.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | * **Please check if the PR fulfills these requirements** 2 | - [ ] The commit message follows our guidelines 3 | - [ ] Tests for the changes have been added (for bug fixes / features) 4 | - [ ] Docs have been added / updated (for bug fixes / features) 5 | 6 | 7 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 8 | 9 | 10 | 11 | * **What is the current behavior?** (You can also link to an open issue here) 12 | 13 | 14 | 15 | * **What is the new behavior (if this is a feature change)?** 16 | 17 | 18 | 19 | * **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?) 20 | 21 | 22 | 23 | * **Other information**: 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # compodoc documentation 11 | /documentation 12 | 13 | # IDEs and editors 14 | /.idea 15 | .project 16 | .classpath 17 | .c9/ 18 | *.launch 19 | .settings/ 20 | 21 | # IDE - VSCode 22 | .vscode 23 | .vscode/* 24 | !.vscode/settings.json 25 | !.vscode/tasks.json 26 | !.vscode/launch.json 27 | !.vscode/extensions.json 28 | 29 | # misc 30 | /.sass-cache 31 | /connect.lock 32 | /coverage/* 33 | /libpeerconnection.log 34 | npm-debug.log 35 | testem.log 36 | /typings 37 | package-lock.json 38 | 39 | # e2e 40 | /e2e/*.js 41 | /e2e/*.map 42 | 43 | #System Files 44 | .DS_Store 45 | Thumbs.db 46 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jeroenouw@outlook.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 - 2019 jeroenouw 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 | [![demolive](https://img.shields.io/badge/demo-live-green.svg)](http://ngxmatfire.jerouw.nl/) 2 | [![npmversion](https://img.shields.io/npm/v/ngxmatfire.svg)]() 3 | [![npmlicense](https://img.shields.io/npm/l/ngxmatfire.svg)](https://github.com/jeroenouw/AngularMaterialFirebase/blob/master/LICENSE/) 4 | [![stars](https://img.shields.io/github/stars/jeroenouw/AngularMaterialFirebase.svg)](https://github.com/jeroenouw/AngularMaterialFirebase/stargazers) 5 | [![downloads](https://img.shields.io/npm/dt/ngxmatfire.svg)]() 6 | 7 | ![logo](https://jerouw.nl/wp-content/uploads/2017/05/ngfbmd.png "Logo") 8 | 9 | # Angular 8 | Material Design | Firebase - Starter kit 10 | 11 | A full stack starter app containing [Angular 8](https://angular.io), [Material](https://material.io/) and [Firebase](https://firebase.google.com/). See also: [AngularMaterialGo](https://github.com/jeroenouw/AngularMaterialGo) 12 | 13 | ## [Live demo](http://ngxmatfire.jerouw.nl) 14 | 15 | ![loginpage](https://jerouw.nl/wp-content/uploads/2017/05/ngfbmdprintscreen.png "Logo") 16 | 17 | ## Quick start, Development & Production 18 | 19 | [Get started now](https://github.com/jeroenouw/AngularMaterialFirebase/blob/master/docs/DEVELOPER.md) 20 | 21 | ## All features 22 | 23 | [Discover all the features in this project](https://github.com/jeroenouw/AngularMaterialFirebase/blob/master/docs/FEATURES.md) 24 | 25 | ## [Live automated documentation](http://ngxmatfire-docs.jerouw.nl) 26 | 27 | [Generate technical documentation automatically with Compodoc](https://github.com/jeroenouw/AngularMaterialFirebase/blob/master/docs/COMPODOC.md) 28 | 29 | ## Check your type coverage 30 | 31 | [See your type coverage with tscov](https://github.com/jeroenouw/AngularMaterialFirebase/blob/master/docs/TSCOV.md) 32 | 33 | ## Future updates 34 | 35 | [To do and finish list](https://github.com/jeroenouw/AngularMaterialFirebase/blob/master/docs/TODO.md) 36 | 37 | ## Contributing 38 | 39 | Want to file a bug, contribute some code, or improve documentation? Feel free to place an [issue](https://github.com/jeroenouw/AngularMaterialFirebase/issues). 40 | 41 | ## License 42 | 43 | [![npmlicense](https://img.shields.io/npm/l/ngxmatfire.svg)](https://github.com/jeroenouw/AngularMaterialFirebase/blob/master/LICENSE/) 44 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngxmatfire": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/ngxmatfire", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "tsConfig": "src/tsconfig.app.json", 20 | "polyfills": "src/polyfills.ts", 21 | "assets": [ 22 | "src/assets", 23 | "src/favicon.ico", 24 | { 25 | "glob": "**/*", 26 | "input": "node_modules/ngx-auth-firebaseui/assets/", 27 | "output": "./assets/" 28 | } 29 | ], 30 | "styles": [ 31 | "src/styles.scss" 32 | ], 33 | "scripts": [] 34 | }, 35 | "configurations": { 36 | "production": { 37 | "optimization": true, 38 | "outputHashing": "all", 39 | "sourceMap": false, 40 | "extractCss": true, 41 | "namedChunks": false, 42 | "aot": true, 43 | "extractLicenses": true, 44 | "vendorChunk": false, 45 | "buildOptimizer": true, 46 | "fileReplacements": [ 47 | { 48 | "replace": "src/environments/environment.ts", 49 | "with": "src/environments/environment.prod.ts" 50 | } 51 | ] 52 | } 53 | } 54 | }, 55 | "serve": { 56 | "builder": "@angular-devkit/build-angular:dev-server", 57 | "options": { 58 | "browserTarget": "ngxmatfire:build" 59 | }, 60 | "configurations": { 61 | "production": { 62 | "browserTarget": "ngxmatfire:build:production" 63 | } 64 | } 65 | }, 66 | "extract-i18n": { 67 | "builder": "@angular-devkit/build-angular:extract-i18n", 68 | "options": { 69 | "browserTarget": "ngxmatfire:build" 70 | } 71 | }, 72 | "test": { 73 | "builder": "@angular-devkit/build-angular:karma", 74 | "options": { 75 | "main": "src/test.ts", 76 | "karmaConfig": "src/karma.conf.js", 77 | "polyfills": "src/polyfills.ts", 78 | "tsConfig": "src/tsconfig.spec.json", 79 | "scripts": [], 80 | "styles": [ 81 | "src/styles.scss" 82 | ], 83 | "assets": [ 84 | "src/assets", 85 | "src/favicon.ico" 86 | ] 87 | } 88 | }, 89 | "lint": { 90 | "builder": "@angular-devkit/build-angular:tslint", 91 | "options": { 92 | "tsConfig": [ 93 | "src/tsconfig.app.json", 94 | "src/tsconfig.spec.json" 95 | ], 96 | "exclude": [] 97 | } 98 | } 99 | } 100 | }, 101 | "ngxmatfire-e2e": { 102 | "root": "e2e/", 103 | "projectType": "application", 104 | "architect": { 105 | "e2e": { 106 | "builder": "@angular-devkit/build-angular:protractor", 107 | "options": { 108 | "protractorConfig": "e2e/protractor.conf.js", 109 | "devServerTarget": "ngxmatfire:serve" 110 | } 111 | }, 112 | "lint": { 113 | "builder": "@angular-devkit/build-angular:tslint", 114 | "options": { 115 | "tsConfig": "e2e/tsconfig.e2e.json", 116 | "exclude": [ 117 | "**/node_modules/**" 118 | ] 119 | } 120 | } 121 | } 122 | } 123 | }, 124 | "defaultProject": "ngxmatfire" 125 | } 126 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 10 | -------------------------------------------------------------------------------- /docs/COMPODOC.md: -------------------------------------------------------------------------------- 1 | # Compodoc automated documentation 2 | 3 | [Compodoc](https://compodoc.github.io/website/) is a documentation tool for Angular applications. It generates a static documentation of your application. Others developers of your team, or internet visitors for a public documentation, can easily understand the features of your application or library. 4 | 5 | ## How to run 6 | 7 | Generate documentation. 8 | 9 | ```cmd 10 | npm run compodoc:g 11 | ``` 12 | 13 | Generate documentation and serve on `http://localhost:8080`. 14 | 15 | ```cmd 16 | npm run compodoc:s 17 | ``` 18 | 19 | Generate documentation, serve on `http://localhost:8080` and watch changes. 20 | 21 | ```cmd 22 | npm run compodoc:w 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/DEVELOPER.md: -------------------------------------------------------------------------------- 1 | # DEVELOPER 2 | 3 | ## Quick start 4 | 5 | First clone this repo: `git clone https://github.com/jeroenouw/AngularMaterialFirebase.git`. 6 | Change directory to this project 7 | Run `npm install` to install all the dependencies. 8 | Run `npm start` to run this project. This will run with the AoT Compiler. 9 | Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 10 | Run `npm reset` if you want to re-install everything. 11 | 12 | ## Development 13 | 14 | For own projects please use different keys in `src/app/firebase.config.ts`: 15 | 16 | ```typescript 17 | 18 | export const firebaseKeys = { 19 | // For your own projects please use different keys 20 | apiKey: 'YOUR_KEY', 21 | authDomain: 'YOUR_DOMAIN', 22 | databaseURL: 'YOUR_URL', 23 | projectId: 'YOUR_ID', 24 | storageBucket: 'YOUR_KEY', 25 | messagingSenderId: 'YOUR_ID' 26 | } 27 | 28 | ``` 29 | 30 | Run `npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 31 | 32 | To build the development environment, run `npm run dist`. 33 | 34 | ## Production 35 | 36 | To build the production environment, run `npm run prod`. This will run with the AoT Compiler. 37 | To build the production environment without hashing in the files, run `npm run prod:hashless`. This will give packages without a hash. 38 | Also available: run `npm run prod:src`. 39 | -------------------------------------------------------------------------------- /docs/FEATURES.md: -------------------------------------------------------------------------------- 1 | # FEATURES 2 | 3 | ## Features in this project 4 | 5 | * Angular 8 front-end 6 | * Material Design 7 | * Firebase back-end (OAuth authentication and NoSQL database) 8 | * Automated documentation (compodoc) 9 | * Home page 10 | * About page 11 | * Contact page 12 | * Misc page 13 | * 404 page 14 | * Profile page 15 | * Profile settings page 16 | * Login page 17 | * Signup page 18 | * Save email (keep in touch) 19 | * ngx-auth-firebaseui for authentication (Anonymous, Email, Google, Twitter, Facebook and Github) 20 | * Verification email after signup 21 | * Auth Guard 22 | * Profile image animation 23 | * Password reset 24 | * Alerts 25 | * 360 view 26 | * Multiple pipes 27 | * Various components to use 28 | * Carousel 29 | * Back to top button 30 | * Loading spinner and/or progress bar 31 | 32 | ## Material Design usage in this project 33 | 34 | * Datepicker 35 | * Input 36 | * Slide toggle 37 | * Menu 38 | * Sidenav 39 | * Toolbar 40 | * Table 41 | * Stepper 42 | * Expansion panel 43 | * Card 44 | * Tabs 45 | * Buttons 46 | * Icon 47 | * Progress spinner 48 | * Progress bar 49 | * Tooltip 50 | * Snackbar 51 | * Drag & drop 52 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | All improvements to this repo will first be done in a sub branch. 4 | 5 | ## To Do List 6 | 7 | * Make every component responsive (@angular/flex-layout?) 8 | * Messaging 9 | 10 | ## To Finish List (known issues) 11 | 12 | ### Auth 13 | 14 | * Cellphone authentication (optimizing) 15 | * Session 16 | * Google, Facebook, Twitter & Github signin/signup 17 | 18 | ### Profile 19 | 20 | * Profile image uploading 21 | 22 | ### Tests 23 | 24 | * Unit 25 | * e2e 26 | 27 | ### Other 28 | 29 | * 360 view only working on localhost 30 | -------------------------------------------------------------------------------- /docs/TSCOV.md: -------------------------------------------------------------------------------- 1 | # liftr-tscov automated type coverage 2 | 3 | [tscov](https://github.com/jeroenouw/liftr-tscov) checks the type coverage of any TypeScript project. 4 | 5 | ## How to run 6 | 7 | See output type coverage (minimum coverage is set by 90%) 8 | 9 | ```cmd 10 | npm run tscov 11 | ``` 12 | 13 | See output type coverage with details 14 | 15 | ```cmd 16 | npm run tscov:d 17 | ``` 18 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Angular4ProjectPage } from './app.po'; 2 | import { browser, element, by } from 'protractor'; 3 | 4 | describe('angular4-project App', function() { 5 | let page: Angular4ProjectPage; 6 | 7 | beforeEach(() => { 8 | page = new Angular4ProjectPage(); 9 | }); 10 | 11 | /*it('should display message saying app works', () => { 12 | page.navigateTo(); 13 | expect(page.getParagraphText()).toEqual('app works!'); 14 | }); 15 | 16 | it('should display message lorem ipsum dolor...', () => { 17 | page.navigateTo(); 18 | expect(page.getParagraphText()).toEqual('Lorem ipsum dolor'); 19 | });*/ 20 | 21 | it('Button text should be register', () => { 22 | browser.get('http://localhost:4200/signup'); 23 | expect(browser.getButtonText()).toEqual('Register'); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class Angular4ProjectPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getHeadingText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | 12 | getParagraphText() { 13 | return element(by.css('app-first-block p')).getText(); 14 | } 15 | 16 | getButtonText() { 17 | return element(by.buttonText('Register')).getText(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngxmatfire", 3 | "version": "1.5.1", 4 | "author": "Jeroen Ouwehand", 5 | "description": "Full stack starter app with Angular 8, Material Design and Firebase.", 6 | "keywords": [ 7 | "angular", 8 | "angular 8", 9 | "firebase", 10 | "material", 11 | "nosql", 12 | "oauth", 13 | "starter-template", 14 | "ngx" 15 | ], 16 | "repository": "https://github.com/jeroenouw/AngularMaterialFirebase", 17 | "license": "MIT", 18 | "angular-cli": {}, 19 | "scripts": { 20 | "ng": "ng", 21 | "start": "ng serve", 22 | "test": "ng test", 23 | "pree2e": "webdriver-manager update --standalone false --gecko false", 24 | "e2e": "ng e2e", 25 | "lint": "ng lint", 26 | "reset": "rm -rf ./node_modules ./package-lock.json && npm install", 27 | "dist": "ng build", 28 | "prod": "ng build --prod", 29 | "prod:hashless": "ng build --prod --output-hashing none", 30 | "prod:src": "ng build --prod --sourcemaps", 31 | "compodoc:g": "compodoc -p src/tsconfig.app.json", 32 | "compodoc:s": "compodoc -p src/tsconfig.app.json --serve", 33 | "compodoc:w": "compodoc -p src/tsconfig.app.json --serve --watch", 34 | "tscov": "tscov -m 90", 35 | "tscov:d": "tscov --details", 36 | "link-upstream": "git remote add upstream https://github.com/jeroenouw/AngularMaterialFirebase", 37 | "sync": "git fetch && git checkout master && git merge upstream/master && git push" 38 | }, 39 | "dependencies": { 40 | "@angular/animations": "^8.0.0", 41 | "@angular/cdk": "^8.0.0", 42 | "@angular/common": "^8.0.0", 43 | "@angular/compiler": "^8.0.0", 44 | "@angular/core": "^8.0.0", 45 | "@angular/fire": "^5.1.3", 46 | "@angular/flex-layout": "^8.0.0-beta.26", 47 | "@angular/forms": "^8.0.0", 48 | "@angular/material": "^8.0.0", 49 | "@angular/platform-browser": "^8.0.0", 50 | "@angular/platform-browser-dynamic": "^8.0.0", 51 | "@angular/platform-server": "^8.0.0", 52 | "@angular/router": "^8.0.0", 53 | "core-js": "^3.1.3", 54 | "firebase": "^5.7.3", 55 | "hammerjs": "^2.0.8", 56 | "ngx-auth-firebaseui": "^2.2.1", 57 | "rxjs": "^6.5.0", 58 | "rxjs-compat": "^6.5.0", 59 | "ts-helpers": "*", 60 | "zone.js": "~0.9.1" 61 | }, 62 | "devDependencies": { 63 | "@angular-devkit/build-angular": "~0.800.0", 64 | "@angular/cli": "^8.0.0", 65 | "@angular/compiler-cli": "^8.0.0", 66 | "@angular/language-service": "^8.0.0", 67 | "@compodoc/compodoc": "^1.1.11", 68 | "@liftr/tscov": "^1.4.1", 69 | "@types/jasmine": "^3.3.9", 70 | "@types/node": "^11.10.4", 71 | "codelyzer": "^5.0.1", 72 | "jasmine-core": "^3.3.0", 73 | "jasmine-spec-reporter": "^4.2.1", 74 | "karma": "^4.0.1", 75 | "karma-chrome-launcher": "^2.2.0", 76 | "karma-cli": "^2.0.0", 77 | "karma-coverage-istanbul-reporter": "^2.0.4", 78 | "karma-jasmine": "^2.0.1", 79 | "karma-jasmine-html-reporter": "^1.4.0", 80 | "protractor": "^5.4.2", 81 | "rxjs-tslint": "^0.1.7", 82 | "ts-node": "^7.0.1", 83 | "tslint": "^5.13.1", 84 | "webpack": "^4.29.6" 85 | }, 86 | "peerDependencies": { 87 | "typescript": "3.4.5" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |
8 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .app-content { 2 | padding: 20px; 3 | } 4 | 5 | .app-content mat-card{ 6 | margin: 20px; 7 | } 8 | 9 | .container { 10 | z-index: 20; 11 | display: inherit; 12 | padding: 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | }); 13 | TestBed.compileComponents(); 14 | }); 15 | 16 | it('should create the app', async(() => { 17 | const fixture = TestBed.createComponent(AppComponent); 18 | const app = fixture.debugElement.componentInstance; 19 | expect(app).toBeTruthy(); 20 | })); 21 | 22 | it(`should have as title 'app works!'`, async(() => { 23 | const fixture = TestBed.createComponent(AppComponent); 24 | const app = fixture.debugElement.componentInstance; 25 | expect(app.title).toEqual('app works!'); 26 | })); 27 | 28 | it('should render title in a h1 tag', async(() => { 29 | const fixture = TestBed.createComponent(AppComponent); 30 | fixture.detectChanges(); 31 | const compiled = fixture.debugElement.nativeElement; 32 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 33 | })); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import * as firebase from 'firebase'; 3 | import { firebaseKeys } from './firebase.config'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.scss'] 9 | }) 10 | 11 | export class AppComponent implements OnInit { 12 | 13 | public ngOnInit(): void { 14 | firebase.initializeApp(firebaseKeys); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | // Modules 3rd party 2 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | import { MatButtonModule, MatCheckboxModule, MatMenuModule, MatInputModule, MatSnackBarModule, 6 | MatToolbarModule, MatDialogModule, MatSidenavModule, MatNativeDateModule, 7 | MatCardModule, MatTabsModule, MatIconModule } from '@angular/material'; 8 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 9 | import { HttpClientModule } from '@angular/common/http'; 10 | import { NgxAuthFirebaseUIModule } from 'ngx-auth-firebaseui'; 11 | 12 | // Modules 13 | import { BlocksModule } from './components/blocks/blocks.module'; 14 | import { AuthModule } from './pages/auth/auth.module'; 15 | import { BackgroundsModule } from './components/backgrounds/backgrounds.module'; 16 | import { ProfileModule } from './pages/profile/profile.module'; 17 | import { MiscModule } from './components/misc/misc.module'; 18 | import { PipesModule } from '@shared/pipes/pipes.module'; 19 | 20 | // Shared 21 | import { 22 | FooterComponent, 23 | HeaderComponent, 24 | UserService, 25 | AlertService, 26 | AuthGuardService, 27 | AuthService, 28 | WindowService 29 | } from '@shared'; 30 | 31 | // Main 32 | import { AppComponent } from './app.component'; 33 | import { AppRoutingModule } from './app.routing'; 34 | import { firebaseKeys } from './firebase.config'; 35 | 36 | // Pages 37 | import { HomeComponent } from './pages/home/home.component'; 38 | import { AboutMeComponent } from './pages/about-me/about-me.component'; 39 | import { ContactComponent } from './pages/contact/contact.component'; 40 | import { PageNotFoundComponent } from './pages/not-found/not-found.component'; 41 | 42 | // Components 43 | import { EmailMeComponent } from './components/email-me/email-me.component'; 44 | 45 | @NgModule({ 46 | declarations: [ 47 | AppComponent, 48 | HomeComponent, 49 | AboutMeComponent, 50 | ContactComponent, 51 | HeaderComponent, 52 | FooterComponent, 53 | PageNotFoundComponent, 54 | EmailMeComponent 55 | ], 56 | imports: [ 57 | BrowserModule, 58 | BrowserAnimationsModule, 59 | MatButtonModule, MatCheckboxModule, MatMenuModule, MatInputModule, MatSnackBarModule, 60 | MatToolbarModule, MatDialogModule, MatSidenavModule, MatNativeDateModule, 61 | MatCardModule, MatTabsModule, MatIconModule, 62 | FormsModule, 63 | ReactiveFormsModule, 64 | HttpClientModule, 65 | AppRoutingModule, 66 | PipesModule, 67 | BlocksModule, 68 | AuthModule, 69 | BackgroundsModule, 70 | ProfileModule, 71 | MiscModule, 72 | NgxAuthFirebaseUIModule.forRoot(firebaseKeys) 73 | ], 74 | providers: [ 75 | UserService, 76 | AlertService, 77 | AuthGuardService, 78 | AuthService, 79 | WindowService 80 | ], 81 | schemas: [ 82 | CUSTOM_ELEMENTS_SCHEMA 83 | ], 84 | bootstrap: [AppComponent] 85 | }) 86 | export class AppModule { 87 | } 88 | -------------------------------------------------------------------------------- /src/app/app.routing.ts: -------------------------------------------------------------------------------- 1 | // Modules 3rd party 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | 5 | // 404 page 6 | import { PageNotFoundComponent } from './pages/not-found/not-found.component'; 7 | 8 | // Pages 9 | import { HomeComponent } from './pages/home/home.component'; 10 | import { AboutMeComponent } from './pages/about-me/about-me.component'; 11 | import { ContactComponent } from './pages/contact/contact.component'; 12 | import { AuthComponent } from './pages/auth/auth.component'; 13 | import { ProfileComponent } from './pages/profile/profile.component'; 14 | import { ProfileSettingsComponent } from './pages/profile/profile-settings.component'; 15 | 16 | // Components 17 | import { MiscComponent } from './components/misc/misc.component'; 18 | 19 | // Protected 20 | import { AuthGuardService } from '@shared'; 21 | 22 | // Routing 23 | const appRoutes: Routes = [ 24 | 25 | // Public pages 26 | { path: '', redirectTo: '/home', pathMatch : 'full' }, 27 | { path: 'home', component: HomeComponent }, 28 | { path: 'about', component: AboutMeComponent }, 29 | { path: 'contact', component: ContactComponent }, 30 | { path: 'misc', component: MiscComponent }, 31 | { path: 'auth', component: AuthComponent }, 32 | 33 | // Protected pages 34 | // { path: 'profile/:uid/:name', component: ProfileComponent, canActivate: [AuthGuardService] }, 35 | { path: 'profile', component: ProfileComponent, canActivate: [AuthGuardService] }, 36 | { path: 'profile-settings', component: ProfileSettingsComponent, canActivate: [AuthGuardService] }, 37 | { path: '**', component: PageNotFoundComponent } 38 | ]; 39 | 40 | @NgModule({ 41 | imports: [RouterModule.forRoot(appRoutes)], 42 | exports: [RouterModule] 43 | }) 44 | 45 | export class AppRoutingModule { 46 | } 47 | -------------------------------------------------------------------------------- /src/app/components/backgrounds/background-email/background-email.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-background-email', 5 | template: ` 6 |
7 | 8 |
9 | `, 10 | styles: [` 11 | .background img { 12 | position: relative; 13 | width: 100%; 14 | max-height: 500px; 15 | margin: 0 auto; 16 | z-index: 1; 17 | box-shadow: 2px 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12); 18 | } 19 | 20 | @media only screen and (max-width: 1024px) { 21 | .background img { 22 | width: 100%; 23 | height: 270px; 24 | } 25 | } 26 | `] 27 | }) 28 | export class BackgroundEmailComponent { 29 | public fullImagePath: string = '/assets/img/mb-bg-01.png'; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/components/backgrounds/background-empty/background-empty.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-background-empty', 5 | template: `

`, 6 | styles: [` 7 | .background { 8 | position: relative; 9 | width: 100%; 10 | height: 500px; 11 | z-index: 1; 12 | }`] 13 | }) 14 | export class BackgroundEmptyComponent {} 15 | -------------------------------------------------------------------------------- /src/app/components/backgrounds/background-normal/background-normal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-background-normal', 5 | template: ` 6 |
7 | 8 |
9 | `, 10 | styles: [ 11 | `.background img { 12 | position: relative; 13 | width: 100%; 14 | max-height: 300px; 15 | margin: 0 auto; 16 | z-index: 1; 17 | box-shadow: 2px 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12); 18 | }` 19 | ] 20 | }) 21 | export class BackgroundNormalComponent { 22 | public fullImagePath: string = '/assets/img/mb-bg-06.png'; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/backgrounds/background-slant/background-slant.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-background-slant', 5 | template: `
`, 6 | styles: [ 7 | `.background-slant { 8 | position: relative; 9 | overflow: hidden; 10 | background-color: #673ab7; 11 | transform: skew(0,-4deg); 12 | -webkit-transform: skew(0,-4deg); 13 | -ms-transform: skew(0,-4deg); 14 | left: 0px; 15 | right: 0px; 16 | height: 520px; 17 | z-index: 1; 18 | } 19 | `] 20 | }) 21 | export class BackgroundSlantComponent {} 22 | -------------------------------------------------------------------------------- /src/app/components/backgrounds/backgrounds.module.ts: -------------------------------------------------------------------------------- 1 | // Modules 3rd party 2 | import { NgModule } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | 5 | // Components 6 | import { BackgroundNormalComponent } from './background-normal/background-normal.component'; 7 | import { BackgroundSlantComponent } from './background-slant/background-slant.component'; 8 | import { BackgroundEmptyComponent } from './background-empty/background-empty.component'; 9 | import { BackgroundEmailComponent } from './background-email/background-email.component'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | BackgroundNormalComponent, 14 | BackgroundSlantComponent, 15 | BackgroundEmptyComponent, 16 | BackgroundEmailComponent 17 | ], 18 | imports: [ 19 | CommonModule 20 | ], 21 | exports: [ 22 | BackgroundNormalComponent, 23 | BackgroundSlantComponent, 24 | BackgroundEmptyComponent, 25 | BackgroundEmailComponent 26 | ] 27 | }) 28 | export class BackgroundsModule { 29 | } 30 | -------------------------------------------------------------------------------- /src/app/components/blocks/blocks.module.ts: -------------------------------------------------------------------------------- 1 | // Modules 3rd party 2 | import { NgModule } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | import { MatButtonModule, MatCardModule } from '@angular/material'; 5 | 6 | // Components 7 | import { CardBlockComponent } from './card-block/card-block.component'; 8 | import { FirstBlockComponent } from './first-block/first-block.component'; 9 | import { SecondBlockComponent } from './second-block/second-block.component'; 10 | import { ThirdBlockComponent } from './third-block/third-block.component'; 11 | 12 | @NgModule({ 13 | declarations: [ 14 | CardBlockComponent, 15 | FirstBlockComponent, 16 | SecondBlockComponent, 17 | ThirdBlockComponent 18 | ], 19 | imports: [ 20 | CommonModule, 21 | MatButtonModule, MatCardModule 22 | ], 23 | exports: [ 24 | CardBlockComponent, 25 | FirstBlockComponent, 26 | SecondBlockComponent, 27 | ThirdBlockComponent 28 | ] 29 | }) 30 | export class BlocksModule { 31 | } 32 | -------------------------------------------------------------------------------- /src/app/components/blocks/card-block/card-block.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

{{titleCard}} {{numberCard}}

4 | {{subTitleCard}} 5 |
6 | 7 | 8 | 9 | 10 |

11 | {{contentCard}} 12 |

13 |
14 | 15 |

16 | 17 | 18 |

Fork on Github

19 |
20 |
-------------------------------------------------------------------------------- /src/app/components/blocks/card-block/card-block.component.scss: -------------------------------------------------------------------------------- 1 | .contentCard { 2 | width: 890px; 3 | height: 500px; 4 | position: absolute; 5 | padding: 24px; 6 | margin: 10px auto 0; 7 | left: 0; 8 | right: 0; 9 | z-index: 5; 10 | } 11 | 12 | .contentCard img { 13 | margin: 0 33px; 14 | max-width: 210px; 15 | max-height: 250px; 16 | } 17 | 18 | .buttonCard { 19 | display: grid; 20 | margin: 0; 21 | } 22 | 23 | .buttonCard a { 24 | text-decoration: none; 25 | } 26 | 27 | @media only screen and (max-width: 1024px) { 28 | 29 | .contentCard { 30 | width: 100%; 31 | height: 100%; 32 | } 33 | 34 | .contentCard img { 35 | margin: 0 10px; 36 | max-width: 30%; 37 | max-height: 80%; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/app/components/blocks/card-block/card-block.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-card-block', 5 | templateUrl: './card-block.component.html', 6 | styleUrls: ['./card-block.component.scss'] 7 | }) 8 | export class CardBlockComponent { 9 | titleCard = 'Angular'; 10 | numberCard = 8; 11 | subTitleCard = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisl ligula.'; 12 | contentCard = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisl ligula.'; 13 | angularImage: string; 14 | 15 | constructor() { 16 | this.angularImage = '/assets/img/angular2.png'; } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/blocks/first-block/first-block.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{titleOne}}

4 |

{{contentOne}}

5 |
6 |
7 | -------------------------------------------------------------------------------- /src/app/components/blocks/first-block/first-block.component.scss: -------------------------------------------------------------------------------- 1 | #contentOne { 2 | width: 930px; 3 | margin: 0 auto; 4 | left: 0; 5 | right: 0; 6 | padding: 60px 20px; 7 | z-index: 1; 8 | position: relative; 9 | } 10 | 11 | @media only screen and (max-width: 1024px) { 12 | 13 | #contentOne { 14 | width: 100%; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/app/components/blocks/first-block/first-block.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-first-block', 5 | templateUrl: './first-block.component.html', 6 | styleUrls: ['./first-block.component.scss'] 7 | }) 8 | export class FirstBlockComponent { 9 | titleOne = 'Starter kit'; 10 | contentOne = 'Angular 8 | Material Design | Firebase (OAuth authentication and NoSQL database)'; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/app/components/blocks/second-block/second-block.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{titleTwo}}

4 |

{{contentTwo}}

5 |
6 |
-------------------------------------------------------------------------------- /src/app/components/blocks/second-block/second-block.component.scss: -------------------------------------------------------------------------------- 1 | #contentTwo{ 2 | width: 930px; 3 | margin: 0 auto; 4 | left: 0; 5 | right: 0; 6 | padding: 120px 20px 120px; 7 | z-index: 3; 8 | position: relative; 9 | } 10 | 11 | @media only screen and (max-width: 1024px) { 12 | 13 | #contentTwo { 14 | width: 100%; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/app/components/blocks/second-block/second-block.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-second-block', 5 | templateUrl: './second-block.component.html', 6 | styleUrls: ['./second-block.component.scss'] 7 | }) 8 | export class SecondBlockComponent { 9 | titleTwo = 'Authentication and Database'; 10 | contentTwo = 'Provided by Firebase'; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/app/components/blocks/third-block/third-block.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{titleThree}}

4 |

{{contentThree}}

5 |
6 | 7 |

{{titleFour}}

8 |

{{contentFour}}

9 |
10 | 11 |

{{titleFive}}

12 |

{{contentFive}}

13 |
14 |
-------------------------------------------------------------------------------- /src/app/components/blocks/third-block/third-block.component.scss: -------------------------------------------------------------------------------- 1 | #contentThree { 2 | width: 930px; 3 | margin: 0 auto; 4 | left: 0; 5 | right: 0; 6 | padding: 120px 20px 120px; 7 | z-index: 3; 8 | position: relative; 9 | } 10 | 11 | #contentThree .mat-card { 12 | width: 30%; 13 | margin: 10px; 14 | display: inline-block; 15 | } 16 | 17 | @media only screen and (max-width: 1024px) { 18 | 19 | #contentThree, #contentThree .mat-card { 20 | width: 100%; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/app/components/blocks/third-block/third-block.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-third-block', 5 | templateUrl: './third-block.component.html', 6 | styleUrls: ['./third-block.component.scss'] 7 | }) 8 | export class ThirdBlockComponent { 9 | titleThree = 'Angular 8'; 10 | contentThree = 'Angular is a framework for building client applications in HTML and TypeScript that compiles to JavaScript.'; 11 | 12 | titleFour = 'Material 2'; 13 | contentFour = 'Material Design is a unified system that combines theory, resources, and tools for crafting digital experiences.'; 14 | 15 | titleFive = 'Firebase'; 16 | contentFive = 'Firebase is built on Google infrastructure and scales automatically, for even the largest apps.'; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/components/email-me/email-me.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Keep in touch!

4 | 5 | 6 | 7 |
8 | Please enter a valid email. 9 |
10 |
11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /src/app/components/email-me/email-me.component.scss: -------------------------------------------------------------------------------- 1 | .form { 2 | width: 500px; 3 | margin: 165px auto 0; 4 | position: absolute; 5 | left: 0; 6 | right: 0; 7 | z-index: 5; 8 | } 9 | 10 | .full-width { 11 | width: 100%; 12 | } 13 | 14 | @media only screen and (max-width: 1024px) { 15 | 16 | .form { 17 | width: 50%; 18 | margin: 10% auto 0; 19 | } 20 | 21 | } 22 | 23 | @media only screen and (max-width: 768px) { 24 | 25 | .form { 26 | width: 90%; 27 | margin: 10% auto; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/app/components/email-me/email-me.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { EmailMeComponent } from './email-me.component'; 7 | 8 | describe('EmailMeComponent', () => { 9 | let component: EmailMeComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ EmailMeComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(EmailMeComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/components/email-me/email-me.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm, FormBuilder, Validators, FormGroup } from '@angular/forms'; 3 | 4 | import { UserService, EmailValidator } from '@shared'; 5 | 6 | @Component({ 7 | selector: 'app-email-me', 8 | templateUrl: './email-me.component.html', 9 | styleUrls: ['./email-me.component.scss'] 10 | }) 11 | export class EmailMeComponent { 12 | public form: FormGroup; 13 | 14 | constructor( 15 | private userService: UserService, 16 | public formBuilder: FormBuilder) { 17 | this.form = formBuilder.group({ 18 | email: ['', Validators.compose([Validators.required, EmailValidator.isValid])] 19 | }); 20 | } 21 | 22 | public onSubmit(form: NgForm) { 23 | const email = form.value.email; 24 | return this.userService.keepInTouch(email); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/components/misc/carousel/carousel.component.html: -------------------------------------------------------------------------------- 1 |
2 | 10 |
-------------------------------------------------------------------------------- /src/app/components/misc/carousel/carousel.component.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | margin: 0px auto 10px; 4 | } 5 | 6 | .carousel { 7 | overflow:hidden; 8 | width:100%; 9 | } 10 | 11 | .slides { 12 | list-style:none; 13 | position:relative; 14 | width:500%; /* Each slide is 100% */ 15 | overflow:hidden; 16 | -moz-animation:carousel 30s infinite; 17 | -webkit-animation:carousel 30s infinite; 18 | animation:carousel 30s infinite; 19 | padding: inherit; 20 | } 21 | 22 | .slides > li { 23 | position:relative; 24 | float:left; 25 | width: 20%; /* Total of slides should be 100% */ 26 | } 27 | 28 | .carousel img { 29 | display:block; 30 | width:100%; 31 | max-width:100%; 32 | } 33 | 34 | .carousel h2 { 35 | margin-bottom: 0; 36 | font-size:1em; 37 | padding:1.5em 0.5em 1.5em 0.5em; 38 | position:absolute; 39 | right:0px; 40 | bottom:0px; 41 | left:0px; 42 | text-align:center; 43 | color:#fff; 44 | background-color:rgba(0,0,0,0.75); 45 | text-transform: uppercase; 46 | } 47 | 48 | @keyframes carousel { 49 | 0% { left:0%; } 50 | 11% { left:0%; } 51 | 12.5% { left:-100%; } 52 | 23.5% { left:-100%; } 53 | 25% { left:-200%; } 54 | 36% { left:-200%; } 55 | 37.5% { left:-300%; } 56 | 48.5% { left:-300%; } 57 | 50% { left:-400%; } 58 | 61% { left:-400%; } 59 | 62.5% { left:-300%; } 60 | 73.5% { left:-300%; } 61 | 75% { left:-200%; } 62 | 86% { left:-200%; } 63 | 87.5% { left:-100%; } 64 | 98.5% { left:-100%; } 65 | 100% { left:0%; } 66 | } 67 | -------------------------------------------------------------------------------- /src/app/components/misc/carousel/carousel.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Image } from '@shared'; 3 | 4 | @Component({ 5 | selector: 'app-carousel', 6 | templateUrl: './carousel.component.html', 7 | styleUrls: ['./carousel.component.scss'], 8 | }) 9 | 10 | export class CarouselComponent { 11 | public images = IMAGES; 12 | constructor() { 13 | } 14 | } 15 | 16 | const IMAGES: Image[] = [ 17 | { 'title': 'Carousel slide nr 1', 'url': '/assets/img/mb-bg-06.png' }, 18 | { 'title': 'Carousel slide nr 2', 'url': '/assets/img/mb-bg-05.png' }, 19 | { 'title': 'Carousel slide nr 3', 'url': '/assets/img/mb-bg-06.png' }, 20 | { 'title': 'Carousel slide nr 4', 'url': '/assets/img/mb-bg-05.png' }, 21 | { 'title': 'Carousel slide nr 5', 'url': '/assets/img/mb-bg-06.png' }, 22 | ]; 23 | -------------------------------------------------------------------------------- /src/app/components/misc/change/change-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, 2 | ChangeDetectionStrategy, DoCheck, 3 | KeyValueDiffers } from '@angular/core'; 4 | 5 | @Component({ 6 | selector: 'app-change-detail', 7 | template: ` 8 |
9 |

10 | Change Detail : 11 |

12 | 13 |

14 |
{{ course | json }}
15 |
16 | `, 17 | styles: [], 18 | changeDetection: ChangeDetectionStrategy.OnPush 19 | }) 20 | export class ChangeDetailComponent implements OnInit, DoCheck { 21 | 22 | @Input() course; 23 | differ; 24 | 25 | constructor(private differs: KeyValueDiffers) { 26 | this.differ = differs.find(Object).create(); 27 | } 28 | 29 | OnMouseEnter(e) { 30 | console.log(e); 31 | } 32 | 33 | ngOnInit() { 34 | } 35 | 36 | OnDetectChanges() { 37 | } 38 | 39 | ngDoCheck(): void { 40 | console.log(this.course); 41 | 42 | const changes = this.differ.diff(this.course); 43 | 44 | if (changes) { 45 | console.log('changes detected'); 46 | changes.forEachChangedItem(r => console.log('changed ', r.currentValue, r)); 47 | changes.forEachAddedItem(r => console.log('added ' + r.currentValue, r)); 48 | changes.forEachRemovedItem(r => console.log('removed ' + r.currentValue)); 49 | } else { 50 | console.log('nothing changed'); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/app/components/misc/change/change-detection.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, KeyValueDiffers } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-change-detection', 5 | template: ` 6 |

7 | Change Detection : 8 |

9 | 10 | 11 |

12 |
{{change2 | json}}
13 | 14 | `, 15 | styles: [`.source { 16 | background-color: #ccc 17 | }`] 18 | }) 19 | export class ChangeDetectionComponent implements OnInit { 20 | 21 | change1: any; 22 | change2: any; 23 | 24 | ngOnInit() { 25 | this.change1 = {title: 'Learning Angular', author: 'Person'}; 26 | this.change2 = this.change1; 27 | } 28 | 29 | OnMutate() { 30 | console.log('OnMutate'); 31 | this.change1.title = 'Learning Angular 8'; 32 | this.change1.author = 'Person A'; 33 | } 34 | 35 | OnChange() { 36 | console.log('OnChange'); 37 | this.change1 = {title: 'Learning React', author: 'Person B'}; 38 | this.change2 = this.change1; 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/app/components/misc/drag-drop/drag-drop.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

To do

4 | 5 |
10 |
{{item}}
11 |
12 |
13 | 14 |
15 |

Done

16 | 17 |
22 |
{{item}}
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/app/components/misc/drag-drop/drag-drop.component.scss: -------------------------------------------------------------------------------- 1 | .drag-drop-container { 2 | width: 400px; 3 | max-width: 100%; 4 | margin: 0 25px 25px 0; 5 | display: inline-block; 6 | vertical-align: top; 7 | } 8 | 9 | .drag-drop-list { 10 | border: solid 1px #ccc; 11 | min-height: 60px; 12 | background: white; 13 | border-radius: 4px; 14 | overflow: hidden; 15 | display: block; 16 | } 17 | 18 | .drag-drop-box { 19 | padding: 20px 10px; 20 | border-bottom: solid 1px #ccc; 21 | color: rgba(0, 0, 0, 0.87); 22 | display: flex; 23 | flex-direction: row; 24 | align-items: center; 25 | justify-content: space-between; 26 | box-sizing: border-box; 27 | cursor: move; 28 | background: white; 29 | font-size: 14px; 30 | } 31 | 32 | .cdk-drag-preview { 33 | box-sizing: border-box; 34 | border-radius: 4px; 35 | box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 36 | 0 8px 10px 1px rgba(0, 0, 0, 0.14), 37 | 0 3px 14px 2px rgba(0, 0, 0, 0.12); 38 | } 39 | 40 | .cdk-drag-placeholder { 41 | opacity: 0; 42 | } 43 | 44 | .cdk-drag-animating { 45 | transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); 46 | } 47 | 48 | .drag-drop-box:last-child { 49 | border: none; 50 | } 51 | 52 | .drag-drop-list.cdk-drop-list-dragging .drag-drop-box:not(.cdk-drag-placeholder) { 53 | transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); 54 | } 55 | -------------------------------------------------------------------------------- /src/app/components/misc/drag-drop/drag-drop.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop'; 3 | 4 | @Component({ 5 | selector: 'app-drag-drop', 6 | templateUrl: 'drag-drop.component.html', 7 | styleUrls: ['drag-drop.component.scss'], 8 | }) 9 | export class DragDropComponent { 10 | public todo: string[] = [ 11 | 'Add new features', 12 | 'Get 1000 stars' 13 | ]; 14 | 15 | public done: string[] = [ 16 | 'Angular', 17 | 'Firebase', 18 | 'Material desing', 19 | ]; 20 | 21 | public drop(event: CdkDragDrop): void { 22 | if (event.previousContainer === event.container) { 23 | moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); 24 | } else { 25 | transferArrayItem( 26 | event.previousContainer.data, 27 | event.container.data, 28 | event.previousIndex, 29 | event.currentIndex 30 | ); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/components/misc/expansion-panel/expansion-panel.component.html: -------------------------------------------------------------------------------- 1 |

mat expansion panel (accordion) :

2 | 3 | 4 | 5 | 6 | Personal data 7 | 8 | 9 | Type your name and age 10 | account_circle 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Destination 31 | 32 | 33 | Type the country name 34 | map 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Day of the trip 52 | 53 | 54 | Inform the date you wish to travel 55 | date_range 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/app/components/misc/expansion-panel/expansion-panel.component.scss: -------------------------------------------------------------------------------- 1 | .headers-align .mat-expansion-panel-header-title, 2 | .headers-align .mat-expansion-panel-header-description { 3 | flex-basis: 0; 4 | } 5 | 6 | .headers-align .mat-expansion-panel-header-description { 7 | justify-content: space-between; 8 | align-items: center; 9 | } -------------------------------------------------------------------------------- /src/app/components/misc/expansion-panel/expansion-panel.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-expansion-panel', 5 | templateUrl: './expansion-panel.component.html', 6 | styleUrls: ['./expansion-panel.component.scss'] 7 | }) 8 | export class ExpansionPanelComponent implements OnInit { 9 | step = 0; 10 | 11 | constructor() { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | setStep(index: number) { 17 | this.step = index; 18 | } 19 | 20 | nextStep() { 21 | this.step++; 22 | } 23 | 24 | prevStep() { 25 | this.step--; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/app/components/misc/misc.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |

Misc

7 |

Various components ready to use in your own project.

8 | 9 |
10 | 11 | 12 |

Two-way data binding :

13 | 14 | 15 | 16 | 17 | Repeat yourself : {{answer}} 18 | 19 |
20 | 21 |

Drag & drop :

22 | 23 | 24 |
25 | 26 |

mat Datepicker :

27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 |

mat Sidenav :

36 | 37 | 38 | This is the sidenav! 39 | 40 | 41 |
42 | 45 |
46 | 47 |
48 | 49 |
50 | 51 |

mat Tooltip :

52 | Hover me! 53 | 54 |

55 | 56 |

mat Slide toggle :

57 | 58 | Click me! 59 | 60 | 61 |


62 | 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 71 | 72 |
73 | 74 | 75 | 76 | 77 |
78 | 79 | 80 | 81 |
82 | 83 | 84 | 85 |
86 | 87 | 88 | 89 | 90 |
91 |
92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/app/components/misc/misc.component.scss: -------------------------------------------------------------------------------- 1 | #content { 2 | padding: 20px; 3 | } 4 | 5 | .container { 6 | height: 300px; 7 | border: 1px solid rgba(0, 0, 0, 0.5); 8 | } 9 | 10 | .sidenav-content { 11 | display: flex; 12 | height: 100%; 13 | align-items: center; 14 | justify-content: center; 15 | } 16 | 17 | .sidenav { 18 | padding: 25px; 19 | } -------------------------------------------------------------------------------- /src/app/components/misc/misc.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { MatDatepicker } from '@angular/material'; 3 | 4 | @Component({ 5 | selector: 'app-misc', 6 | templateUrl: './misc.component.html', 7 | styleUrls: ['./misc.component.scss'] 8 | }) 9 | export class MiscComponent implements OnInit { 10 | answer: string; 11 | 12 | constructor() { } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/components/misc/misc.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { MatButtonModule, MatCheckboxModule, MatInputModule, MatNativeDateModule, 5 | MatSlideToggleModule, MatStepperModule, MatTooltipModule, MatSidenavModule, 6 | MatTableModule, MatCardModule, MatDatepickerModule, MatExpansionModule, 7 | MatIconModule, MatToolbarModule } from '@angular/material'; 8 | 9 | // import { CdkTableModule } from '@angular/cdk'; 10 | import { DragDropModule } from '@angular/cdk/drag-drop'; 11 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 12 | import { PipesModule } from '@shared/pipes/pipes.module'; 13 | 14 | import { CarouselComponent } from './carousel/carousel.component'; 15 | import { MiscComponent } from './misc.component'; 16 | import { ChangeDetectionComponent } from './change/change-detection.component'; 17 | import { ChangeDetailComponent } from './change/change-detail.component'; 18 | import { VirtRealComponent } from './virtual-reality/virtreal.component'; 19 | import { TableComponent } from './table/table.component'; 20 | import { StepperComponent } from './stepper/stepper.component'; 21 | import { DragDropComponent } from './drag-drop/drag-drop.component'; 22 | import { ExpansionPanelComponent } from './expansion-panel/expansion-panel.component'; 23 | 24 | @NgModule({ 25 | declarations: [ 26 | CarouselComponent, 27 | MiscComponent, 28 | ChangeDetectionComponent, 29 | ChangeDetailComponent, 30 | VirtRealComponent, 31 | TableComponent, 32 | StepperComponent, 33 | DragDropComponent, 34 | ExpansionPanelComponent 35 | ], 36 | imports: [ 37 | CommonModule, 38 | MatButtonModule, MatCheckboxModule, MatInputModule, MatNativeDateModule, 39 | MatSlideToggleModule, MatStepperModule, MatTooltipModule, MatSidenavModule, 40 | MatTableModule, MatCardModule, MatDatepickerModule, MatExpansionModule, 41 | MatIconModule, MatToolbarModule, 42 | FormsModule, ReactiveFormsModule, 43 | PipesModule, 44 | DragDropModule 45 | ], 46 | exports: [ 47 | CarouselComponent, 48 | MiscComponent, 49 | ChangeDetectionComponent, 50 | ChangeDetailComponent, 51 | VirtRealComponent, 52 | TableComponent, 53 | StepperComponent, 54 | ExpansionPanelComponent 55 | ], 56 | schemas: [ 57 | CUSTOM_ELEMENTS_SCHEMA 58 | ] 59 | }) 60 | 61 | export class MiscModule { 62 | } 63 | -------------------------------------------------------------------------------- /src/app/components/misc/stepper/stepper.component.html: -------------------------------------------------------------------------------- 1 |

mat stepper :

2 | 3 | 4 | 5 | 6 |
7 | Fill out your name 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | Fill out your email 19 | 20 | 21 | 22 |
23 | 24 | 25 |
26 |
27 |
28 | 29 | Done 30 | You are now done. 31 |
32 | 33 |
34 |
35 |
-------------------------------------------------------------------------------- /src/app/components/misc/stepper/stepper.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/app/components/misc/stepper/stepper.component.scss -------------------------------------------------------------------------------- /src/app/components/misc/stepper/stepper.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | 4 | import { EmailValidator } from '@shared'; 5 | 6 | @Component({ 7 | selector: 'app-stepper', 8 | templateUrl: './stepper.component.html', 9 | styleUrls: ['./stepper.component.scss'] 10 | }) 11 | export class StepperComponent implements OnInit { 12 | isLinear = false; 13 | firstFormGroup: FormGroup; 14 | secondFormGroup: FormGroup; 15 | 16 | constructor(private _formBuilder: FormBuilder) { } 17 | 18 | ngOnInit() { 19 | this.firstFormGroup = this._formBuilder.group({ 20 | name: ['', Validators.required] 21 | }); 22 | this.secondFormGroup = this._formBuilder.group({ 23 | email: ['', Validators.compose([Validators.required, EmailValidator.isValid])] 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/components/misc/table/table.component.html: -------------------------------------------------------------------------------- 1 |

mat/CDK Table :

2 |
3 | 4 | 5 | 6 | ID 7 | {{row.id}} 8 | 9 | 10 | 11 | Progress 12 | {{row.progress}}% 13 | 14 | 15 | 16 | Name 17 | {{row.name}} 18 | 19 | 20 | 21 | Color 22 | {{row.color}} 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /src/app/components/misc/table/table.component.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | height: 300px; 5 | min-width: 300px; 6 | } 7 | 8 | .header { 9 | min-height: 64px; 10 | display: flex; 11 | align-items: center; 12 | padding-left: 24px; 13 | font-size: 20px; 14 | } 15 | 16 | .mat-table { 17 | overflow: auto; 18 | } 19 | 20 | .mat-header-cell .mat-sort-header-sorted { 21 | color: black; 22 | } -------------------------------------------------------------------------------- /src/app/components/misc/table/table.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { DataSource } from '@angular/cdk/collections'; 3 | import { MatSort } from '@angular/material'; 4 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 5 | import { Observable } from 'rxjs/Observable'; 6 | 7 | import 'rxjs/add/operator/startWith'; 8 | import 'rxjs/add/observable/merge'; 9 | import 'rxjs/add/operator/map'; 10 | 11 | @Component({ 12 | selector: 'app-table', 13 | templateUrl: './table.component.html', 14 | styleUrls: ['./table.component.scss'] 15 | }) 16 | export class TableComponent implements OnInit { 17 | displayedColumns = ['userId', 'userName', 'progress', 'color']; 18 | database = new DataBase(); 19 | dataSource: dataSource | null; 20 | 21 | @ViewChild(MatSort, {static: true}) sort: MatSort; 22 | 23 | constructor() { } 24 | 25 | ngOnInit() { 26 | this.dataSource = new dataSource(this.database, this.sort); 27 | } 28 | } 29 | 30 | // Local data 31 | const COLORS = ['maroon', 'red', 'orange', 'yellow', 'olive', 'green', 'purple', 32 | 'fuchsia', 'lime', 'teal', 'aqua', 'blue', 'navy', 'black', 'gray']; 33 | const NAMES = ['Maia', 'Asher', 'Olivia', 'Atticus', 'Amelia', 'Jack', 34 | 'Charlotte', 'Theodore', 'Isla', 'Oliver', 'Isabella', 'Jasper', 35 | 'Cora', 'Levi', 'Violet', 'Arthur', 'Mia', 'Thomas', 'Elizabeth']; 36 | 37 | export interface UserData { 38 | id: string; 39 | name: string; 40 | progress: string; 41 | color: string; 42 | } 43 | 44 | export class DataBase { 45 | // Stream that emits whenever the data has been modified 46 | dataChange: BehaviorSubject = new BehaviorSubject([]); 47 | get data(): UserData[] { return this.dataChange.value; } 48 | 49 | constructor() { 50 | // Fill up the database with 100 users 51 | for (let i = 0; i < 100; i++) { this.addUser(); } 52 | } 53 | 54 | // Adds a new user to the database 55 | addUser() { 56 | const copiedData = this.data.slice(); 57 | copiedData.push(this.createNewUser()); 58 | this.dataChange.next(copiedData); 59 | } 60 | 61 | // Builds and returns a new User 62 | private createNewUser() { 63 | const name = 64 | NAMES[Math.round(Math.random() * (NAMES.length - 1))] + ' ' + 65 | NAMES[Math.round(Math.random() * (NAMES.length - 1))].charAt(0) + '.'; 66 | 67 | return { 68 | id: (this.data.length + 1).toString(), 69 | name: name, 70 | progress: Math.round(Math.random() * 100).toString(), 71 | color: COLORS[Math.round(Math.random() * (COLORS.length - 1))] 72 | }; 73 | } 74 | } 75 | 76 | // Data source to provide what data should be rendered in the table 77 | export class dataSource extends DataSource { 78 | constructor(private database: DataBase, private sort: MatSort) { 79 | super(); 80 | } 81 | 82 | connect(): Observable { 83 | const displayDataChanges = [ 84 | this.database.dataChange, 85 | this.sort.sortChange 86 | ]; 87 | 88 | return Observable.merge(...displayDataChanges).map(() => { 89 | return this.getSortedData(); 90 | }); 91 | } 92 | 93 | disconnect() {} 94 | 95 | // Sorting the database data 96 | getSortedData(): UserData[] { 97 | const data = this.database.data.slice(); 98 | if (!this.sort.active || this.sort.direction === '') { return data; } 99 | 100 | return data.sort((a, b) => { 101 | let propertyA: number|string = ''; 102 | let propertyB: number|string = ''; 103 | 104 | switch (this.sort.active) { 105 | case 'userId': [propertyA, propertyB] = [a.id, b.id]; break; 106 | case 'userName': [propertyA, propertyB] = [a.name, b.name]; break; 107 | case 'progress': [propertyA, propertyB] = [a.progress, b.progress]; break; 108 | case 'color': [propertyA, propertyB] = [a.color, b.color]; break; 109 | } 110 | 111 | const valueA = isNaN(+propertyA) ? propertyA : +propertyA; 112 | const valueB = isNaN(+propertyB) ? propertyB : +propertyB; 113 | 114 | return (valueA < valueB ? -1 : 1) * (this.sort.direction === 'asc' ? 1 : -1); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/app/components/misc/virtual-reality/virtreal.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

360 view (Works only at localhost) :

4 |
5 |
6 |
-------------------------------------------------------------------------------- /src/app/components/misc/virtual-reality/virtreal.component.scss: -------------------------------------------------------------------------------- 1 | #content { 2 | padding: 20px; 3 | } -------------------------------------------------------------------------------- /src/app/components/misc/virtual-reality/virtreal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, AfterViewInit, Input, ViewChild } from '@angular/core'; 2 | import { Scene } from '@shared' 3 | 4 | declare let VRView: any; 5 | 6 | @Component({ 7 | selector: 'app-virtreal', 8 | templateUrl: './virtreal.component.html', 9 | styleUrls: ['./virtreal.component.scss'] 10 | }) 11 | export class VirtRealComponent implements AfterViewInit { 12 | 13 | viewer: any; 14 | @ViewChild('viewer', {static: true}) viewerElement: any; 15 | 16 | @Input() scenes: Scene; 17 | @Input() width: any; 18 | @Input() height: any; 19 | 20 | constructor() { 21 | } 22 | 23 | ngAfterViewInit() { 24 | this.viewer = new VRView.Player(`#${this.viewerElement.nativeElement.id}`, { 25 | image: 'assets/img/mountsthelens.png', 26 | width: 1200, 27 | height: 250 28 | }); 29 | 30 | this.viewer.on('ready', () => { 31 | 32 | this.loadScene('world'); 33 | this.viewer.on('click', (event) => event.id ? this.loadScene(event.id) : ''); 34 | }); 35 | } 36 | 37 | loadScene(id) { 38 | if (id) { 39 | // Set the image 40 | this.viewer.setContent({ 41 | image: this.scenes[id].image, 42 | // preview: this.scenes[id].preview, 43 | }); 44 | // Add all the hotspots for the scene 45 | const newScene = this.scenes[id]; 46 | const sceneHotspots = Object.keys(newScene.hotspots); 47 | for (let i = 0; i < sceneHotspots.length; i++) { 48 | const hotspotKey = sceneHotspots[i]; 49 | const hotspot = newScene.hotspots[hotspotKey]; 50 | 51 | this.viewer.addHotspot(hotspotKey, { 52 | pitch: hotspot.pitch, 53 | yaw: hotspot.yaw, 54 | radius: hotspot.radius, 55 | distance: hotspot.distance 56 | }); 57 | } 58 | } 59 | } 60 | 61 | getFirstScene() { 62 | for (const key in this.scenes) { 63 | if (this.scenes.hasOwnProperty(key)) { 64 | return key; 65 | } 66 | } 67 | return 0; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/app/firebase.config.ts: -------------------------------------------------------------------------------- 1 | export const firebaseKeys = { 2 | // For your own projects please use different keys 3 | apiKey: 'AIzaSyBUYZcc_HKi1TckbZPpSjSkxyFvml3Is0A', 4 | authDomain: 'angular4materialdesign.firebaseapp.com', 5 | databaseURL: 'https://angular4materialdesign.firebaseio.com', 6 | projectId: 'angular4materialdesign', 7 | storageBucket: 'angular4materialdesign.appspot.com', 8 | messagingSenderId: '136985605193' 9 | } 10 | -------------------------------------------------------------------------------- /src/app/pages/about-me/about-me.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

About me

4 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eleifend metus vel imperdiet cursus. 5 | Donec tempor placerat nibh ac hendrerit. Nulla feugiat augue ac fringilla facilisis. Aenean eget nibh at quam egestas commodo. 6 | Maecenas dapibus nisl quis nulla tristique, vitae laoreet tellus convallis. Interdum et malesuada fames ac ante ipsum primis in faucibus. 7 | In ultricies mattis rhoncus. Duis vestibulum tristique massa a fringilla. Suspendisse ut purus at massa elementum sollicitudin eu sit amet lacus. 8 | Nullam sit amet nisi risus. In diam dui, aliquet a lectus eu, suscipit finibus lorem. Interdum et malesuada fames ac ante ipsum primis in faucibus. 9 | Integer vitae facilisis felis, quis cursus velit. Suspendisse nec consectetur mauris, ut placerat ex. Duis eu consectetur eros. Nulla facilisi. 10 |

11 | 12 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eleifend metus vel imperdiet cursus. 13 | Etiam pretium sem a metus tristique, ut luctus quam rutrum. Vivamus commodo venenatis nunc ut placerat. 14 | Proin eleifend id dui sit amet imperdiet. Proin fringilla sit amet nunc sed consequat. Proin ac libero elit. 15 | Pellentesque magna purus, semper gravida porttitor nec, venenatis vitae urna. Nam luctus id eros eget convallis. 16 | Aliquam sem libero, mollis et leo id, semper finibus leo. Sed convallis leo ac tempor luctus. Integer metus arcu, 17 | imperdiet nec tincidunt at, sodales nec nulla. Nam suscipit urna eu vulputate aliquet. Vestibulum consequat nisl vel erat congue sollicitudin. 18 | Proin mattis ultrices blandit. Vivamus accumsan vulputate sem vel tincidunt. Etiam imperdiet imperdiet ante, sit amet suscipit tortor finibus maximus. 19 | Maecenas euismod pulvinar lorem. 20 |

21 |
22 |
23 | -------------------------------------------------------------------------------- /src/app/pages/about-me/about-me.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { AboutMeComponent } from './about-me.component'; 7 | 8 | describe('AboutMeComponent', () => { 9 | let component: AboutMeComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ AboutMeComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(AboutMeComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/pages/about-me/about-me.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-about-me', 5 | templateUrl: './about-me.component.html', 6 | styles: [` 7 | #content { 8 | padding: 20px; 9 | } 10 | `] 11 | }) 12 | export class AboutMeComponent {} 13 | -------------------------------------------------------------------------------- /src/app/pages/auth/auth.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/app/pages/auth/auth.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/app/pages/auth/auth.component.scss -------------------------------------------------------------------------------- /src/app/pages/auth/auth.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | import { AuthService } from '@shared'; 5 | 6 | @Component({ 7 | selector: 'app-auth', 8 | templateUrl: './auth.component.html', 9 | styleUrls: ['./auth.component.scss'] 10 | }) 11 | export class AuthComponent { 12 | 13 | constructor(private authService: AuthService) {} 14 | 15 | public onSuccess(): void { 16 | return this.authService.onSuccess(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/app/pages/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | // Modules 3rd party 2 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { MatButtonModule, MatInputModule, MatProgressBarModule, 7 | MatCardModule, MatIconModule } from '@angular/material'; 8 | import { NgxAuthFirebaseUIModule } from 'ngx-auth-firebaseui'; 9 | 10 | // Components 11 | import { AuthComponent } from './auth.component'; 12 | import { PhoneSigninComponent } from './phone-signin/phone-signin.component'; 13 | 14 | @NgModule({ 15 | declarations: [ 16 | AuthComponent, 17 | PhoneSigninComponent 18 | ], 19 | imports: [ 20 | CommonModule, 21 | BrowserModule, 22 | FormsModule, 23 | MatButtonModule, MatInputModule, MatProgressBarModule, 24 | MatCardModule, MatIconModule, 25 | NgxAuthFirebaseUIModule 26 | ], 27 | providers: [ 28 | ], 29 | schemas: [ 30 | CUSTOM_ELEMENTS_SCHEMA 31 | ], 32 | exports: [ 33 | AuthComponent, 34 | PhoneSigninComponent 35 | ] 36 | }) 37 | export class AuthModule { 38 | } 39 | -------------------------------------------------------------------------------- /src/app/pages/auth/phone-signin/phone-signin.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Phone Number login

Currently only saving phonenumber to database, login in progress. (temporary place)
4 | 5 |
+ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

20 | 21 | 22 | 23 |
24 |
25 |

Enter your verification code here


26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 |
34 |
35 | 36 |
37 | You have successfully logged in with your phone number! 38 | 39 | UserId: {{ currentUser?.uid }} 40 | 41 |
-------------------------------------------------------------------------------- /src/app/pages/auth/phone-signin/phone-signin.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/app/pages/auth/phone-signin/phone-signin.component.scss -------------------------------------------------------------------------------- /src/app/pages/auth/phone-signin/phone-signin.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PhoneSigninComponent } from './phone-signin.component'; 4 | 5 | describe('PhoneSigninComponent', () => { 6 | let component: PhoneSigninComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ PhoneSigninComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PhoneSigninComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/pages/auth/phone-signin/phone-signin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import * as firebase from 'firebase'; 4 | 5 | import { WindowService, PhoneNumber, AuthService, AlertService } from '@shared'; 6 | 7 | @Component({ 8 | selector: 'app-phone-signin', 9 | templateUrl: './phone-signin.component.html', 10 | styleUrls: ['./phone-signin.component.scss'] 11 | }) 12 | export class PhoneSigninComponent implements OnInit { 13 | phoneNumber = new PhoneNumber() 14 | isAuthenticated: string; 15 | 16 | token: string; 17 | windowRef: any; 18 | verificationCode: string; 19 | currentUser: any; 20 | 21 | constructor(private win: WindowService, 22 | private router: Router, 23 | private authService: AuthService, 24 | private alertService: AlertService) { 25 | this.isAuthenticated = this.authService.isAuthenticated() 26 | } 27 | 28 | ngOnInit() { 29 | this.windowRef = this.win.windowRef 30 | this.windowRef.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container') 31 | 32 | this.windowRef.recaptchaVerifier.render() 33 | } 34 | 35 | sendLoginCode() { 36 | const appVerifier = this.windowRef.recaptchaVerifier; 37 | const num = this.phoneNumber.e164; 38 | firebase.auth().signInWithPhoneNumber(num, appVerifier) 39 | .then(result => { 40 | this.windowRef.confirmationResult = result; 41 | this.alertService.showToaster('Login code is send'); 42 | }) 43 | .catch( error => console.log(error) ); 44 | } 45 | 46 | verifyLoginCode() { 47 | this.windowRef.confirmationResult 48 | .confirm(this.verificationCode) 49 | .then((result) => { 50 | const currentUser = result.user; 51 | }) 52 | .then(response => { 53 | this.router.navigate(['/']); 54 | firebase.auth().currentUser.getIdToken() 55 | .then( 56 | (token: string) => this.token = token 57 | ); 58 | this.alertService.showToaster('Login code is entered'); 59 | }) 60 | .catch( error => console.log(error, 'Incorrect code entered?')); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/app/pages/contact/contact.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Contact us

4 |
5 | 6 | 7 | 8 | 9 | 10 | 13 | 16 |
11 | 12 | 14 | 15 |
17 | 18 |

19 | 20 | 21 | 22 |

23 | 24 | 25 | 28 | 32 |
26 | 27 | 29 | 30 | {{postalCode.value.length}} / 6 31 |
33 |

34 | 35 | 36 | 37 |

38 |
39 | 40 | 41 |
42 |
43 |
-------------------------------------------------------------------------------- /src/app/pages/contact/contact.component.scss: -------------------------------------------------------------------------------- 1 | .form { 2 | width: 500px; 3 | } 4 | 5 | .full-width { 6 | width: 100%; 7 | } 8 | 9 | #content { 10 | padding: 20px; 11 | } -------------------------------------------------------------------------------- /src/app/pages/contact/contact.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { ContactComponent } from './contact.component'; 7 | 8 | describe('ContactComponent', () => { 9 | let component: ContactComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ ContactComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(ContactComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/pages/contact/contact.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | import { UserService } from '@shared'; 5 | 6 | @Component({ 7 | selector: 'app-contact', 8 | templateUrl: './contact.component.html', 9 | styleUrls: ['./contact.component.scss'] 10 | }) 11 | export class ContactComponent { 12 | 13 | constructor(private userService: UserService) {} 14 | 15 | public onSubmit(form: NgForm) { 16 | const company = form.value.company; 17 | const firstname = form.value.firstname; 18 | const lastname = form.value.lastname; 19 | const address = form.value.address; 20 | const city = form.value.city; 21 | const postal = form.value.postal; 22 | const message = form.value.message; 23 | return this.userService.contactFormSend(company, firstname, lastname, address, city, postal, message); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/app/pages/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |


4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/pages/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: 'home.component.html', 6 | styles: [` 7 | button { 8 | position: fixed; 9 | bottom: 70px; 10 | float: right; 11 | right: 10px; 12 | z-index: 10; 13 | }`] 14 | }) 15 | 16 | export class HomeComponent { 17 | 18 | public onToTop(): void { 19 | document.body.scrollTop = document.documentElement.scrollTop = 0; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/app/pages/not-found/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |
6 | 7 |

Page not found 404

8 | The page you're looking for doesn't exist, please return to the homepage. 9 |
10 |
11 | `, 12 | styles: ['#content { padding: 20px;}'] 13 | }) 14 | 15 | export class PageNotFoundComponent {} 16 | -------------------------------------------------------------------------------- /src/app/pages/profile/profile-settings.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

My profile settings

4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /src/app/pages/profile/profile-settings.component.scss: -------------------------------------------------------------------------------- 1 | .profile-settings { 2 | padding: 20px; 3 | } 4 | 5 | form { 6 | text-align: center; 7 | } 8 | 9 | mat-card { 10 | width: 400px; 11 | height: 530px; 12 | margin: 100px auto; 13 | } 14 | 15 | mat-form-field { 16 | width: 100%; 17 | } 18 | 19 | textarea { 20 | resize: none; 21 | } 22 | 23 | .avatar { 24 | margin: 10px auto; 25 | width: 120px; 26 | } 27 | 28 | a:hover { 29 | text-decoration: none; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/pages/profile/profile-settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | import * as firebase from 'firebase'; 5 | 6 | import { AuthService, AlertService, UserService } from '@shared'; 7 | 8 | @Component({ 9 | selector: 'app-profile-settings', 10 | templateUrl: './profile-settings.component.html', 11 | styleUrls: ['./profile-settings.component.scss'] 12 | }) 13 | export class ProfileSettingsComponent implements OnInit { 14 | public uid = firebase.auth().currentUser.uid; 15 | public displayName: string = 'Your username'; 16 | public bio: any = 'Your bio'; 17 | 18 | constructor( 19 | private authService: AuthService, 20 | private alertService: AlertService, 21 | private userService: UserService) { 22 | } 23 | 24 | public ngOnInit(): Promise { 25 | return firebase.database().ref().child(`users/${this.uid}`).once('value').then((snap) => { 26 | this.displayName = snap.val().displayName; 27 | this.bio = snap.val().bio; 28 | }); 29 | } 30 | 31 | public onPasswordReset(): void { 32 | this.userService.sendUserPasswordResetEmail(); 33 | this.alertService.showToaster('Reset password is sent to your email'); 34 | } 35 | 36 | public onUpdateUserInfo(form: NgForm): void { 37 | const displayName = form.value.displayName; 38 | const bio = form.value.bio; 39 | this.userService.updateUserInfo(firebase.auth().currentUser.uid, displayName, bio); 40 | this.alertService.showToaster('Your settings are saved'); 41 | } 42 | 43 | public onLogout(): void { 44 | this.authService.logout(); 45 | this.alertService.showToaster('Logout succesful'); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/app/pages/profile/profile.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 |

{{profileTitle}}

7 |

{{userEmail()}}

8 | {{displayName}} 9 |
10 |
11 | 12 |
13 | 14 | {{bio}} 15 | 16 | 17 | 18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /src/app/pages/profile/profile.component.scss: -------------------------------------------------------------------------------- 1 | .profile { 2 | padding: 20px; 3 | } 4 | 5 | mat-card { 6 | width: 400px; 7 | height: 460px; 8 | margin: 100px auto; 9 | } 10 | 11 | mat-card-header{ 12 | display: inline; 13 | } 14 | 15 | mat-card-header-text { 16 | margin: 0 auto; 17 | } 18 | 19 | mat-card-title { 20 | text-align: center; 21 | } 22 | 23 | .avatar { 24 | margin: 10px auto; 25 | width: 120px; 26 | } 27 | 28 | .avatar img { 29 | width: 120px; 30 | height: 120px; 31 | background-size: cover; 32 | background-position: center; 33 | border-radius: 50%; 34 | } 35 | 36 | a:hover { 37 | text-decoration: none; 38 | } 39 | 40 | .button { 41 | position: absolute; 42 | bottom: 35px; 43 | margin: 0 auto; 44 | } -------------------------------------------------------------------------------- /src/app/pages/profile/profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { trigger, state, style, transition, animate, keyframes } from '@angular/animations'; 3 | 4 | import * as firebase from 'firebase'; 5 | 6 | import { UserService, AlertService } from '@shared'; 7 | 8 | @Component({ 9 | selector: 'app-profile', 10 | templateUrl: './profile.component.html', 11 | styleUrls: ['./profile.component.scss'], 12 | animations: [ 13 | trigger('imageAnimation', [ 14 | 15 | state('small', style({ 16 | transform: 'scale(1)', 17 | })), 18 | state('large', style({ 19 | transform: 'scale(2)', 20 | })), 21 | 22 | transition('small <=> large', animate('500ms ease-in', keyframes([ 23 | style({opacity: 0, transform: 'translateY(-80%)', offset: 0}), 24 | style({opacity: 1, transform: 'translateY(25px)', offset: 1}) 25 | ]))), 26 | ]), 27 | ] 28 | }) 29 | export class ProfileComponent implements OnInit { 30 | public uid = firebase.auth().currentUser.uid; 31 | 32 | public fullImagePath: string = '/assets/img/mb-bg-04.png'; 33 | public profileTitle: string = 'My profile'; 34 | public displayName: string = 'Your username'; 35 | public bio: any = 'Your bio'; 36 | public state: string = 'small'; 37 | 38 | constructor( 39 | private userService: UserService, 40 | private alertService: AlertService) {} 41 | 42 | public ngOnInit(): Promise { 43 | return firebase.database().ref().child('users/' + this.uid).once('value').then((snap) => { 44 | this.displayName = snap.val().displayName, 45 | this.bio = snap.val().bio 46 | }); 47 | } 48 | 49 | public animateImage(): void { 50 | this.state = (this.state === 'small' ? 'large' : 'small'); 51 | } 52 | 53 | public userEmail(): void { 54 | this.userService.getUserProfileInformation(); 55 | firebase.auth().currentUser.email; 56 | } 57 | 58 | public onPasswordReset(): void { 59 | this.userService.sendUserPasswordResetEmail(); 60 | this.alertService.showToaster('Reset password is sent to your email'); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/app/pages/profile/profile.module.ts: -------------------------------------------------------------------------------- 1 | // Modules 3rd party 2 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | import { MatButtonModule, MatCheckboxModule, MatMenuModule, MatInputModule, 5 | MatToolbarModule, MatCardModule, MatDialogModule } from '@angular/material'; 6 | import { FormsModule } from '@angular/forms'; 7 | 8 | // Components 9 | import { ProfileComponent } from './profile.component'; 10 | import { ProfileSettingsComponent } from './profile-settings.component'; 11 | 12 | @NgModule({ 13 | declarations: [ 14 | ProfileComponent, 15 | ProfileSettingsComponent 16 | ], 17 | imports: [ 18 | CommonModule, 19 | MatButtonModule, MatCheckboxModule, MatMenuModule, MatInputModule, 20 | MatToolbarModule, MatCardModule, MatDialogModule, 21 | FormsModule 22 | ], 23 | exports: [ 24 | ProfileComponent, 25 | ProfileSettingsComponent 26 | ], 27 | schemas: [ 28 | CUSTOM_ELEMENTS_SCHEMA 29 | ] 30 | }) 31 | export class ProfileModule { 32 | } 33 | -------------------------------------------------------------------------------- /src/app/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './models'; 2 | export * from './layout'; 3 | export * from './services'; 4 | export * from './interfaces'; 5 | export * from './validators'; 6 | -------------------------------------------------------------------------------- /src/app/shared/interfaces/image.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Image { 2 | title: string; 3 | url: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/shared/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './scene.interface'; 2 | export * from './image.interface'; 3 | -------------------------------------------------------------------------------- /src/app/shared/interfaces/scene.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Scene { 2 | [key: string]: { 3 | image: string; 4 | hotspots?: Hotspot 5 | }; 6 | } 7 | 8 | export interface Hotspot { 9 | [key: string]: { 10 | pitch: number; 11 | yaw: number; 12 | radius: number, 13 | distance: number 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/shared/layout/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 2 |
©
3 | 4 | mail_outline 5 |
-------------------------------------------------------------------------------- /src/app/shared/layout/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | .icon { 2 | padding: 0 14px; 3 | } 4 | 5 | mat-icon { 6 | color:rgba(255,255,255,.87); 7 | } 8 | 9 | .spacer { 10 | flex: 1 1 auto; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/shared/layout/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-footer', 5 | templateUrl: 'footer.component.html', 6 | styleUrls: ['./footer.component.scss'] 7 | }) 8 | export class FooterComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/shared/layout/header/header.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | 56 |
57 | 58 | 59 | 60 | 61 |
62 | 63 |
64 | 65 | 66 | 67 |
68 | 71 |
72 | 73 | 74 | 75 |

Welcome

76 | 77 | 80 | 81 | 82 | 85 | face 86 | Profile 87 | 88 | 89 | 90 | 91 | {{item.title}} 92 | 93 | 94 | 99 | settings 100 | Settings 101 | 102 | 103 | 104 | lock_outline 105 | Logout 106 | 107 | 108 | 109 |
110 |
111 | -------------------------------------------------------------------------------- /src/app/shared/layout/header/header.component.scss: -------------------------------------------------------------------------------- 1 | // Header 2 | #content { 3 | padding: 20px; 4 | } 5 | 6 | .spacer { 7 | flex: 1 1 auto; 8 | } 9 | 10 | .spacer-light { 11 | margin: 0 10px; 12 | } 13 | 14 | .mat-toolbar.mat-primary { 15 | box-shadow: 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12); 16 | } 17 | 18 | span a, span a:hover { 19 | text-decoration: none; 20 | color:rgba(255,255,255,.87); 21 | } 22 | 23 | mat-icon { 24 | margin-right: 0px!important; 25 | } 26 | 27 | .menu-link a, .menu-link a:hover { 28 | font-size: 14px; 29 | text-decoration: none; 30 | color:rgba(255,255,255,.87); 31 | margin: 0 10px; 32 | } 33 | 34 | a, a:hover { 35 | text-decoration: none; 36 | color:rgba(0,0,0,.87); 37 | } 38 | 39 | .mat-icon { 40 | text-decoration: none; 41 | color:rgba(255,255,255,.87); 42 | } 43 | 44 | .login, .logout, .profile, .messages, .settings, .home, .about, .contact, .misc, .signup{ 45 | color: #333; 46 | } 47 | 48 | // Sidenav 49 | .sidenav-content { 50 | display: flex; 51 | height: 100%; 52 | align-items: center; 53 | justify-content: center; 54 | } 55 | 56 | .sidenav { 57 | width: 180px; 58 | z-index: 100; 59 | } 60 | 61 | .menu-link button, .menu-link a:hover { 62 | width: 100%; 63 | height: 48px; 64 | font-size: 14px; 65 | line-height: 48px; 66 | padding: 0 16px; 67 | text-decoration: none; 68 | text-align: left; 69 | } 70 | 71 | span { 72 | margin-left: 15px; 73 | } 74 | 75 | img { 76 | margin: 15px 15px 0; 77 | max-width: 150px; 78 | max-height: 180px; 79 | } 80 | -------------------------------------------------------------------------------- /src/app/shared/layout/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import * as firebase from 'firebase'; 3 | 4 | import { AuthService, AlertService } from '../../services'; 5 | 6 | @Component({ 7 | selector: 'app-header', 8 | templateUrl: 'header.component.html', 9 | styleUrls: ['./header.component.scss'], 10 | }) 11 | export class HeaderComponent { 12 | public isAuthenticated: string; 13 | public angularImage: string = '/assets/img/angular2.png'; 14 | 15 | public menuItems: Object[] = [ 16 | { 17 | icon: 'library_books', 18 | title: 'Medium @jeroenouw', 19 | link: 'https://medium.com/@jeroenouw' 20 | }, 21 | { 22 | icon: 'description', 23 | title: 'Generated docs', 24 | link: 'http://ngxmatfire-docs.jerouw.nl/' 25 | }, 26 | { 27 | icon: 'description', 28 | title: 'Features', 29 | link: 'https://github.com/jeroenouw/AngularMaterialFirebase/blob/master/docs/FEATURES.md' 30 | }, 31 | { 32 | icon: 'flight_takeoff', 33 | title: 'Quick start', 34 | link: 'https://github.com/jeroenouw/AngularMaterialFirebase/blob/master/docs/DEVELOPER.md' 35 | }, 36 | { 37 | icon: 'archive', 38 | title: 'NPM packages', 39 | link: 'https://www.npmjs.com/~jeroenouw' 40 | }, 41 | { 42 | icon: 'link', 43 | title: 'Fork on Github', 44 | link: 'https://github.com/jeroenouw/AngularMaterialFirebase' 45 | }, 46 | ]; 47 | 48 | constructor( 49 | public authService: AuthService, 50 | private alertService: AlertService, 51 | ) { 52 | this.isAuthenticated = this.authService.isAuthenticated() 53 | } 54 | 55 | public userUid(): string { 56 | return firebase.auth().currentUser.uid; 57 | } 58 | 59 | public userEmail(): string { 60 | return firebase.auth().currentUser.email; 61 | } 62 | 63 | public userName(): string { 64 | return firebase.auth().currentUser.displayName; 65 | } 66 | 67 | public onLogout(): void { 68 | this.alertService.showToaster('Logout succesful'); 69 | return this.authService.logout(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/app/shared/layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './footer/footer.component'; 2 | export * from './header/header.component'; 3 | -------------------------------------------------------------------------------- /src/app/shared/models/contact.model.ts: -------------------------------------------------------------------------------- 1 | export class Contact { 2 | constructor( 3 | public company: string, 4 | public firstname: string, 5 | public lastname: string, 6 | public address: string, 7 | public city: string, 8 | public province: string, 9 | public zipcode: string 10 | ) { } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/shared/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './profile.model'; 2 | export * from './user.model'; 3 | export * from './contact.model'; 4 | export * from './phone-number.model'; 5 | -------------------------------------------------------------------------------- /src/app/shared/models/phone-number.model.ts: -------------------------------------------------------------------------------- 1 | export class PhoneNumber { 2 | country: string; 3 | area: string; 4 | prefix: string; 5 | line: string; 6 | 7 | get e164() { 8 | const num = this.country + this.area + this.prefix + this.line; 9 | return `+${num}`; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/shared/models/profile.model.ts: -------------------------------------------------------------------------------- 1 | export class Profile { 2 | displayName: string; 3 | email: string; 4 | bio: string; 5 | photoUrl: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/shared/models/user.model.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | public displayName: string; 3 | public email: string; 4 | public bio: any; 5 | public image: any; 6 | public uid: any; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/shared/pipes/async-observable-pipe.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Observable } from 'rxjs/Rx'; 3 | 4 | @Component({ 5 | selector: 'app-async-observable-pipe', 6 | template: ` 7 |
8 |

Async Observable :

9 |
10 | Count: {{counter$ | async}} 11 |
12 |
13 | `, 14 | styles: [] 15 | }) 16 | export class AsyncObservablePipeComponent { 17 | 18 | public counter$: Observable; 19 | 20 | constructor() { 21 | this.counter$ = Observable.interval(1000); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/shared/pipes/currency-pipe.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-currency-pipe', 5 | template: `
6 |

Currency Pipe :

7 |
8 |

Angular: {{angularPrice | currency:'EUR':false}}

9 |

Angular 8: {{angular7Price | currency:'EUR':true:'4.2-2'}}

10 |
11 |
`, 12 | styles: [] 13 | }) 14 | export class CurrencyPipeComponent { 15 | public angularPrice = 0.567; 16 | public angular7Price = 1.3743; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/app/shared/pipes/date-pipe.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-date-pipe', 5 | template: ` 6 | {{today | date:'d'}}-{{today | date:'M'}}-{{today | date:'y'}} 7 | 8 | `, 9 | }) 10 | export class DatePipeComponent { 11 | public today: number = Date.now(); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/shared/pipes/json-pipe.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-json-pipe', 5 | template: ` 6 |
7 |
8 |

Json Pipe :

9 |

Without JSON pipe:

10 |
{{object}}
11 |

With JSON pipe:

12 |
{{object | json}}
13 |
14 |
15 | ` 16 | }) 17 | export class JsonPipeComponent { 18 | public object: Object = {abc: 'test', def: 'test2', nested: {def: 3, numbers: [1, 2, 3, 4, 5]}}; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/shared/pipes/pipes.module.ts: -------------------------------------------------------------------------------- 1 | // Modules 3rd party 2 | import { NgModule } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | 5 | // Pipes 6 | import { YearPipeComponent } from './year-pipe.component'; 7 | import { DatePipeComponent } from './date-pipe.component'; 8 | import { TitleCasePipeComponent } from './titlecase-pipe.component'; 9 | import { AsyncObservablePipeComponent } from './async-observable-pipe.component'; 10 | import { CurrencyPipeComponent } from './currency-pipe.component'; 11 | import { JsonPipeComponent } from './json-pipe.component'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | YearPipeComponent, 16 | DatePipeComponent, 17 | TitleCasePipeComponent, 18 | AsyncObservablePipeComponent, 19 | CurrencyPipeComponent, 20 | JsonPipeComponent 21 | ], 22 | imports: [ 23 | CommonModule 24 | ], 25 | exports: [ 26 | YearPipeComponent, 27 | DatePipeComponent, 28 | TitleCasePipeComponent, 29 | AsyncObservablePipeComponent, 30 | CurrencyPipeComponent, 31 | JsonPipeComponent 32 | ] 33 | }) 34 | export class PipesModule { 35 | } 36 | -------------------------------------------------------------------------------- /src/app/shared/pipes/titlecase-pipe.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-titlecase-pipe', 5 | template: ` 6 | {{message | titlecase}} 7 | ` 8 | }) 9 | export class TitleCasePipeComponent { 10 | public message = 'angular material firebase'; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/app/shared/pipes/year-pipe.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-year-pipe', 5 | template: ` 6 | {{today | date:'y'}} 7 | ` 8 | }) 9 | export class YearPipeComponent { 10 | public today: number = Date.now(); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/shared/services/alert.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { MatSnackBar } from '@angular/material'; 3 | 4 | @Injectable() 5 | export class AlertService { 6 | 7 | constructor(private snackBar: MatSnackBar) {} 8 | 9 | public showToaster(msg: string): void { 10 | this.snackBar.open(msg, null, { 11 | duration: 3500, 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/shared/services/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable() 7 | export class AuthGuardService implements CanActivate { 8 | constructor(private authService: AuthService, 9 | private router: Router) { } 10 | 11 | public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 12 | if (!this.authService.isAuthenticated()) { 13 | this.router.navigate(['/login']); 14 | return false; 15 | } else { 16 | return true; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/shared/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { AngularFireAuth } from '@angular/fire/auth'; 3 | import { Router } from '@angular/router'; 4 | import * as firebase from 'firebase'; 5 | 6 | @Injectable() 7 | export class AuthService { 8 | public token: string; 9 | 10 | constructor( 11 | private router: Router, 12 | private auth: AngularFireAuth) { } 13 | 14 | public onSuccess(): void { 15 | sessionStorage.setItem('session-alive', 'true'); 16 | this.token = 'some-temporary-token'; 17 | this.router.navigate(['/']); 18 | console.log('AUTH: ', this.auth); 19 | } 20 | 21 | public logout(): void { 22 | sessionStorage.removeItem('session-alive'); 23 | this.token = null; 24 | this.router.navigate(['/']); 25 | } 26 | 27 | public getIdToken(): string { 28 | firebase.auth().currentUser.getIdToken() 29 | .then( 30 | (token: string) => this.token = token 31 | ); 32 | return this.token; 33 | } 34 | 35 | public isAuthenticated(): string { 36 | return sessionStorage.getItem('session-alive'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/shared/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth-guard.service'; 2 | export * from './auth.service'; 3 | export * from './alert.service'; 4 | export * from './user.service'; 5 | export * from './window.service'; 6 | -------------------------------------------------------------------------------- /src/app/shared/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import * as firebase from 'firebase'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | import { User } from '../models'; 5 | import { AlertService } from './alert.service'; 6 | 7 | @Injectable() 8 | export class UserService { 9 | 10 | constructor(private alertService: AlertService) {} 11 | 12 | public saveUserInfo(uid: string, name: string, email: string): Promise { 13 | return firebase.database().ref().child('users/' + uid).set({ 14 | name: name, 15 | email: email 16 | }); 17 | } 18 | 19 | public updateUserInfo(uid: string, displayName: string, bio: string): Promise { 20 | return firebase.database().ref().child('users/' + uid).update({ 21 | displayName: displayName, 22 | bio: bio 23 | }); 24 | } 25 | 26 | public keepInTouch(email: string) { 27 | this.alertService.showToaster('Your email is saved'); 28 | return firebase.database().ref().child('touch/').push({ 29 | email: email 30 | }); 31 | } 32 | 33 | public contactFormSend( 34 | company: string, 35 | firstname: string, 36 | lastname: string, 37 | address: string, 38 | city: string, 39 | postal: string, 40 | message: string 41 | ) { 42 | this.alertService.showToaster('This contact form is saved'); 43 | return firebase.database().ref().child('contactform/').push({ 44 | company: company, 45 | firstname: firstname, 46 | lastname: lastname, 47 | address: address, 48 | city: city, 49 | postal: postal, 50 | message: message 51 | }); 52 | } 53 | 54 | public getUserProfileInformation(): void { 55 | const user = firebase.auth().currentUser; 56 | let name, email, photoUrl, uid, emailVerified; 57 | 58 | if (user != null) { 59 | name = user.displayName; 60 | email = user.email; 61 | photoUrl = user.photoURL; 62 | emailVerified = user.emailVerified; 63 | uid = user.uid; 64 | } 65 | } 66 | 67 | public verificationUserEmail(): Promise { 68 | return firebase.auth().currentUser.sendEmailVerification().then(() => { 69 | // Email sent. 70 | }, (error) => { 71 | // An error happened. 72 | }); 73 | } 74 | 75 | public sendUserPasswordResetEmail(): Promise { 76 | return firebase.auth().sendPasswordResetEmail(firebase.auth().currentUser.email).then(() => { 77 | // Email sent. 78 | }, (error) => { 79 | // An error happened. 80 | }); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/app/shared/services/window.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class WindowService { 5 | 6 | get windowRef(): Window { 7 | return window; 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/shared/validators/email.ts: -------------------------------------------------------------------------------- 1 | import { FormControl } from '@angular/forms'; 2 | 3 | export class EmailValidator { 4 | 5 | public static isValid (control: FormControl) { 6 | const RegEx = /^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/.test(control.value); 7 | 8 | if (RegEx) { 9 | return null; 10 | } 11 | 12 | return { 13 | 'invalidEmail': true 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/shared/validators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './email'; 2 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/css/bootstrap-social.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Social Buttons for Bootstrap 3 | * 4 | * Copyright 2013-2016 Panayiotis Lipiridis 5 | * Licensed under the MIT License 6 | * 7 | * https://github.com/lipis/bootstrap-social 8 | */ 9 | 10 | .btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)} 11 | .btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em} 12 | .btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em} 13 | .btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em} 14 | .btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)} 15 | .btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em} 16 | .btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em} 17 | .btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em} 18 | .btn-social-icon>:first-child{border:none;text-align:center;width:100% !important} 19 | .btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0} 20 | .btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0} 21 | .btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0} 22 | .btn-adn{color:#fff;background-color:#d87a68;border-color:rgba(0,0,0,0.2)}.btn-adn:focus,.btn-adn.focus{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)} 23 | .btn-adn:hover{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)} 24 | .btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active:hover,.btn-adn.active:hover,.open>.dropdown-toggle.btn-adn:hover,.btn-adn:active:focus,.btn-adn.active:focus,.open>.dropdown-toggle.btn-adn:focus,.btn-adn:active.focus,.btn-adn.active.focus,.open>.dropdown-toggle.btn-adn.focus{color:#fff;background-color:#b94630;border-color:rgba(0,0,0,0.2)} 25 | .btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none} 26 | .btn-adn.disabled:hover,.btn-adn[disabled]:hover,fieldset[disabled] .btn-adn:hover,.btn-adn.disabled:focus,.btn-adn[disabled]:focus,fieldset[disabled] .btn-adn:focus,.btn-adn.disabled.focus,.btn-adn[disabled].focus,fieldset[disabled] .btn-adn.focus{background-color:#d87a68;border-color:rgba(0,0,0,0.2)} 27 | .btn-adn .badge{color:#d87a68;background-color:#fff} 28 | .btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:focus,.btn-bitbucket.focus{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)} 29 | .btn-bitbucket:hover{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)} 30 | .btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active:hover,.btn-bitbucket.active:hover,.open>.dropdown-toggle.btn-bitbucket:hover,.btn-bitbucket:active:focus,.btn-bitbucket.active:focus,.open>.dropdown-toggle.btn-bitbucket:focus,.btn-bitbucket:active.focus,.btn-bitbucket.active.focus,.open>.dropdown-toggle.btn-bitbucket.focus{color:#fff;background-color:#0f253c;border-color:rgba(0,0,0,0.2)} 31 | .btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none} 32 | .btn-bitbucket.disabled:hover,.btn-bitbucket[disabled]:hover,fieldset[disabled] .btn-bitbucket:hover,.btn-bitbucket.disabled:focus,.btn-bitbucket[disabled]:focus,fieldset[disabled] .btn-bitbucket:focus,.btn-bitbucket.disabled.focus,.btn-bitbucket[disabled].focus,fieldset[disabled] .btn-bitbucket.focus{background-color:#205081;border-color:rgba(0,0,0,0.2)} 33 | .btn-bitbucket .badge{color:#205081;background-color:#fff} 34 | .btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:focus,.btn-dropbox.focus{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)} 35 | .btn-dropbox:hover{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)} 36 | .btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active:hover,.btn-dropbox.active:hover,.open>.dropdown-toggle.btn-dropbox:hover,.btn-dropbox:active:focus,.btn-dropbox.active:focus,.open>.dropdown-toggle.btn-dropbox:focus,.btn-dropbox:active.focus,.btn-dropbox.active.focus,.open>.dropdown-toggle.btn-dropbox.focus{color:#fff;background-color:#0a568c;border-color:rgba(0,0,0,0.2)} 37 | .btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none} 38 | .btn-dropbox.disabled:hover,.btn-dropbox[disabled]:hover,fieldset[disabled] .btn-dropbox:hover,.btn-dropbox.disabled:focus,.btn-dropbox[disabled]:focus,fieldset[disabled] .btn-dropbox:focus,.btn-dropbox.disabled.focus,.btn-dropbox[disabled].focus,fieldset[disabled] .btn-dropbox.focus{background-color:#1087dd;border-color:rgba(0,0,0,0.2)} 39 | .btn-dropbox .badge{color:#1087dd;background-color:#fff} 40 | .btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:focus,.btn-facebook.focus{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)} 41 | .btn-facebook:hover{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)} 42 | .btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active:hover,.btn-facebook.active:hover,.open>.dropdown-toggle.btn-facebook:hover,.btn-facebook:active:focus,.btn-facebook.active:focus,.open>.dropdown-toggle.btn-facebook:focus,.btn-facebook:active.focus,.btn-facebook.active.focus,.open>.dropdown-toggle.btn-facebook.focus{color:#fff;background-color:#23345a;border-color:rgba(0,0,0,0.2)} 43 | .btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none} 44 | .btn-facebook.disabled:hover,.btn-facebook[disabled]:hover,fieldset[disabled] .btn-facebook:hover,.btn-facebook.disabled:focus,.btn-facebook[disabled]:focus,fieldset[disabled] .btn-facebook:focus,.btn-facebook.disabled.focus,.btn-facebook[disabled].focus,fieldset[disabled] .btn-facebook.focus{background-color:#3b5998;border-color:rgba(0,0,0,0.2)} 45 | .btn-facebook .badge{color:#3b5998;background-color:#fff} 46 | .btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:focus,.btn-flickr.focus{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)} 47 | .btn-flickr:hover{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)} 48 | .btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active:hover,.btn-flickr.active:hover,.open>.dropdown-toggle.btn-flickr:hover,.btn-flickr:active:focus,.btn-flickr.active:focus,.open>.dropdown-toggle.btn-flickr:focus,.btn-flickr:active.focus,.btn-flickr.active.focus,.open>.dropdown-toggle.btn-flickr.focus{color:#fff;background-color:#a80057;border-color:rgba(0,0,0,0.2)} 49 | .btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none} 50 | .btn-flickr.disabled:hover,.btn-flickr[disabled]:hover,fieldset[disabled] .btn-flickr:hover,.btn-flickr.disabled:focus,.btn-flickr[disabled]:focus,fieldset[disabled] .btn-flickr:focus,.btn-flickr.disabled.focus,.btn-flickr[disabled].focus,fieldset[disabled] .btn-flickr.focus{background-color:#ff0084;border-color:rgba(0,0,0,0.2)} 51 | .btn-flickr .badge{color:#ff0084;background-color:#fff} 52 | .btn-foursquare{color:#fff;background-color:#f94877;border-color:rgba(0,0,0,0.2)}.btn-foursquare:focus,.btn-foursquare.focus{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)} 53 | .btn-foursquare:hover{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)} 54 | .btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active:hover,.btn-foursquare.active:hover,.open>.dropdown-toggle.btn-foursquare:hover,.btn-foursquare:active:focus,.btn-foursquare.active:focus,.open>.dropdown-toggle.btn-foursquare:focus,.btn-foursquare:active.focus,.btn-foursquare.active.focus,.open>.dropdown-toggle.btn-foursquare.focus{color:#fff;background-color:#e30742;border-color:rgba(0,0,0,0.2)} 55 | .btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none} 56 | .btn-foursquare.disabled:hover,.btn-foursquare[disabled]:hover,fieldset[disabled] .btn-foursquare:hover,.btn-foursquare.disabled:focus,.btn-foursquare[disabled]:focus,fieldset[disabled] .btn-foursquare:focus,.btn-foursquare.disabled.focus,.btn-foursquare[disabled].focus,fieldset[disabled] .btn-foursquare.focus{background-color:#f94877;border-color:rgba(0,0,0,0.2)} 57 | .btn-foursquare .badge{color:#f94877;background-color:#fff} 58 | .btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)}.btn-github:focus,.btn-github.focus{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)} 59 | .btn-github:hover{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)} 60 | .btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active:hover,.btn-github.active:hover,.open>.dropdown-toggle.btn-github:hover,.btn-github:active:focus,.btn-github.active:focus,.open>.dropdown-toggle.btn-github:focus,.btn-github:active.focus,.btn-github.active.focus,.open>.dropdown-toggle.btn-github.focus{color:#fff;background-color:#191919;border-color:rgba(0,0,0,0.2)} 61 | .btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none} 62 | .btn-github.disabled:hover,.btn-github[disabled]:hover,fieldset[disabled] .btn-github:hover,.btn-github.disabled:focus,.btn-github[disabled]:focus,fieldset[disabled] .btn-github:focus,.btn-github.disabled.focus,.btn-github[disabled].focus,fieldset[disabled] .btn-github.focus{background-color:#444;border-color:rgba(0,0,0,0.2)} 63 | .btn-github .badge{color:#444;background-color:#fff} 64 | .btn-google{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google:focus,.btn-google.focus{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)} 65 | .btn-google:hover{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)} 66 | .btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active:hover,.btn-google.active:hover,.open>.dropdown-toggle.btn-google:hover,.btn-google:active:focus,.btn-google.active:focus,.open>.dropdown-toggle.btn-google:focus,.btn-google:active.focus,.btn-google.active.focus,.open>.dropdown-toggle.btn-google.focus{color:#fff;background-color:#a32b1c;border-color:rgba(0,0,0,0.2)} 67 | .btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none} 68 | .btn-google.disabled:hover,.btn-google[disabled]:hover,fieldset[disabled] .btn-google:hover,.btn-google.disabled:focus,.btn-google[disabled]:focus,fieldset[disabled] .btn-google:focus,.btn-google.disabled.focus,.btn-google[disabled].focus,fieldset[disabled] .btn-google.focus{background-color:#dd4b39;border-color:rgba(0,0,0,0.2)} 69 | .btn-google .badge{color:#dd4b39;background-color:#fff} 70 | .btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:focus,.btn-instagram.focus{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)} 71 | .btn-instagram:hover{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)} 72 | .btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active:hover,.btn-instagram.active:hover,.open>.dropdown-toggle.btn-instagram:hover,.btn-instagram:active:focus,.btn-instagram.active:focus,.open>.dropdown-toggle.btn-instagram:focus,.btn-instagram:active.focus,.btn-instagram.active.focus,.open>.dropdown-toggle.btn-instagram.focus{color:#fff;background-color:#26455d;border-color:rgba(0,0,0,0.2)} 73 | .btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none} 74 | .btn-instagram.disabled:hover,.btn-instagram[disabled]:hover,fieldset[disabled] .btn-instagram:hover,.btn-instagram.disabled:focus,.btn-instagram[disabled]:focus,fieldset[disabled] .btn-instagram:focus,.btn-instagram.disabled.focus,.btn-instagram[disabled].focus,fieldset[disabled] .btn-instagram.focus{background-color:#3f729b;border-color:rgba(0,0,0,0.2)} 75 | .btn-instagram .badge{color:#3f729b;background-color:#fff} 76 | .btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:focus,.btn-linkedin.focus{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)} 77 | .btn-linkedin:hover{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)} 78 | .btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active:hover,.btn-linkedin.active:hover,.open>.dropdown-toggle.btn-linkedin:hover,.btn-linkedin:active:focus,.btn-linkedin.active:focus,.open>.dropdown-toggle.btn-linkedin:focus,.btn-linkedin:active.focus,.btn-linkedin.active.focus,.open>.dropdown-toggle.btn-linkedin.focus{color:#fff;background-color:#00405f;border-color:rgba(0,0,0,0.2)} 79 | .btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none} 80 | .btn-linkedin.disabled:hover,.btn-linkedin[disabled]:hover,fieldset[disabled] .btn-linkedin:hover,.btn-linkedin.disabled:focus,.btn-linkedin[disabled]:focus,fieldset[disabled] .btn-linkedin:focus,.btn-linkedin.disabled.focus,.btn-linkedin[disabled].focus,fieldset[disabled] .btn-linkedin.focus{background-color:#007bb6;border-color:rgba(0,0,0,0.2)} 81 | .btn-linkedin .badge{color:#007bb6;background-color:#fff} 82 | .btn-microsoft{color:#fff;background-color:#2672ec;border-color:rgba(0,0,0,0.2)}.btn-microsoft:focus,.btn-microsoft.focus{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)} 83 | .btn-microsoft:hover{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)} 84 | .btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active:hover,.btn-microsoft.active:hover,.open>.dropdown-toggle.btn-microsoft:hover,.btn-microsoft:active:focus,.btn-microsoft.active:focus,.open>.dropdown-toggle.btn-microsoft:focus,.btn-microsoft:active.focus,.btn-microsoft.active.focus,.open>.dropdown-toggle.btn-microsoft.focus{color:#fff;background-color:#0f4bac;border-color:rgba(0,0,0,0.2)} 85 | .btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none} 86 | .btn-microsoft.disabled:hover,.btn-microsoft[disabled]:hover,fieldset[disabled] .btn-microsoft:hover,.btn-microsoft.disabled:focus,.btn-microsoft[disabled]:focus,fieldset[disabled] .btn-microsoft:focus,.btn-microsoft.disabled.focus,.btn-microsoft[disabled].focus,fieldset[disabled] .btn-microsoft.focus{background-color:#2672ec;border-color:rgba(0,0,0,0.2)} 87 | .btn-microsoft .badge{color:#2672ec;background-color:#fff} 88 | .btn-odnoklassniki{color:#fff;background-color:#f4731c;border-color:rgba(0,0,0,0.2)}.btn-odnoklassniki:focus,.btn-odnoklassniki.focus{color:#fff;background-color:#d35b0a;border-color:rgba(0,0,0,0.2)} 89 | .btn-odnoklassniki:hover{color:#fff;background-color:#d35b0a;border-color:rgba(0,0,0,0.2)} 90 | .btn-odnoklassniki:active,.btn-odnoklassniki.active,.open>.dropdown-toggle.btn-odnoklassniki{color:#fff;background-color:#d35b0a;border-color:rgba(0,0,0,0.2)}.btn-odnoklassniki:active:hover,.btn-odnoklassniki.active:hover,.open>.dropdown-toggle.btn-odnoklassniki:hover,.btn-odnoklassniki:active:focus,.btn-odnoklassniki.active:focus,.open>.dropdown-toggle.btn-odnoklassniki:focus,.btn-odnoklassniki:active.focus,.btn-odnoklassniki.active.focus,.open>.dropdown-toggle.btn-odnoklassniki.focus{color:#fff;background-color:#b14c09;border-color:rgba(0,0,0,0.2)} 91 | .btn-odnoklassniki:active,.btn-odnoklassniki.active,.open>.dropdown-toggle.btn-odnoklassniki{background-image:none} 92 | .btn-odnoklassniki.disabled:hover,.btn-odnoklassniki[disabled]:hover,fieldset[disabled] .btn-odnoklassniki:hover,.btn-odnoklassniki.disabled:focus,.btn-odnoklassniki[disabled]:focus,fieldset[disabled] .btn-odnoklassniki:focus,.btn-odnoklassniki.disabled.focus,.btn-odnoklassniki[disabled].focus,fieldset[disabled] .btn-odnoklassniki.focus{background-color:#f4731c;border-color:rgba(0,0,0,0.2)} 93 | .btn-odnoklassniki .badge{color:#f4731c;background-color:#fff} 94 | .btn-openid{color:#fff;background-color:#f7931e;border-color:rgba(0,0,0,0.2)}.btn-openid:focus,.btn-openid.focus{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)} 95 | .btn-openid:hover{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)} 96 | .btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active:hover,.btn-openid.active:hover,.open>.dropdown-toggle.btn-openid:hover,.btn-openid:active:focus,.btn-openid.active:focus,.open>.dropdown-toggle.btn-openid:focus,.btn-openid:active.focus,.btn-openid.active.focus,.open>.dropdown-toggle.btn-openid.focus{color:#fff;background-color:#b86607;border-color:rgba(0,0,0,0.2)} 97 | .btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none} 98 | .btn-openid.disabled:hover,.btn-openid[disabled]:hover,fieldset[disabled] .btn-openid:hover,.btn-openid.disabled:focus,.btn-openid[disabled]:focus,fieldset[disabled] .btn-openid:focus,.btn-openid.disabled.focus,.btn-openid[disabled].focus,fieldset[disabled] .btn-openid.focus{background-color:#f7931e;border-color:rgba(0,0,0,0.2)} 99 | .btn-openid .badge{color:#f7931e;background-color:#fff} 100 | .btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:focus,.btn-pinterest.focus{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)} 101 | .btn-pinterest:hover{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)} 102 | .btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active:hover,.btn-pinterest.active:hover,.open>.dropdown-toggle.btn-pinterest:hover,.btn-pinterest:active:focus,.btn-pinterest.active:focus,.open>.dropdown-toggle.btn-pinterest:focus,.btn-pinterest:active.focus,.btn-pinterest.active.focus,.open>.dropdown-toggle.btn-pinterest.focus{color:#fff;background-color:#801419;border-color:rgba(0,0,0,0.2)} 103 | .btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none} 104 | .btn-pinterest.disabled:hover,.btn-pinterest[disabled]:hover,fieldset[disabled] .btn-pinterest:hover,.btn-pinterest.disabled:focus,.btn-pinterest[disabled]:focus,fieldset[disabled] .btn-pinterest:focus,.btn-pinterest.disabled.focus,.btn-pinterest[disabled].focus,fieldset[disabled] .btn-pinterest.focus{background-color:#cb2027;border-color:rgba(0,0,0,0.2)} 105 | .btn-pinterest .badge{color:#cb2027;background-color:#fff} 106 | .btn-reddit{color:#000;background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}.btn-reddit:focus,.btn-reddit.focus{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)} 107 | .btn-reddit:hover{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)} 108 | .btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active:hover,.btn-reddit.active:hover,.open>.dropdown-toggle.btn-reddit:hover,.btn-reddit:active:focus,.btn-reddit.active:focus,.open>.dropdown-toggle.btn-reddit:focus,.btn-reddit:active.focus,.btn-reddit.active.focus,.open>.dropdown-toggle.btn-reddit.focus{color:#000;background-color:#98ccff;border-color:rgba(0,0,0,0.2)} 109 | .btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none} 110 | .btn-reddit.disabled:hover,.btn-reddit[disabled]:hover,fieldset[disabled] .btn-reddit:hover,.btn-reddit.disabled:focus,.btn-reddit[disabled]:focus,fieldset[disabled] .btn-reddit:focus,.btn-reddit.disabled.focus,.btn-reddit[disabled].focus,fieldset[disabled] .btn-reddit.focus{background-color:#eff7ff;border-color:rgba(0,0,0,0.2)} 111 | .btn-reddit .badge{color:#eff7ff;background-color:#000} 112 | .btn-soundcloud{color:#fff;background-color:#f50;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:focus,.btn-soundcloud.focus{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)} 113 | .btn-soundcloud:hover{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)} 114 | .btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active:hover,.btn-soundcloud.active:hover,.open>.dropdown-toggle.btn-soundcloud:hover,.btn-soundcloud:active:focus,.btn-soundcloud.active:focus,.open>.dropdown-toggle.btn-soundcloud:focus,.btn-soundcloud:active.focus,.btn-soundcloud.active.focus,.open>.dropdown-toggle.btn-soundcloud.focus{color:#fff;background-color:#a83800;border-color:rgba(0,0,0,0.2)} 115 | .btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none} 116 | .btn-soundcloud.disabled:hover,.btn-soundcloud[disabled]:hover,fieldset[disabled] .btn-soundcloud:hover,.btn-soundcloud.disabled:focus,.btn-soundcloud[disabled]:focus,fieldset[disabled] .btn-soundcloud:focus,.btn-soundcloud.disabled.focus,.btn-soundcloud[disabled].focus,fieldset[disabled] .btn-soundcloud.focus{background-color:#f50;border-color:rgba(0,0,0,0.2)} 117 | .btn-soundcloud .badge{color:#f50;background-color:#fff} 118 | .btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:focus,.btn-tumblr.focus{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)} 119 | .btn-tumblr:hover{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)} 120 | .btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active:hover,.btn-tumblr.active:hover,.open>.dropdown-toggle.btn-tumblr:hover,.btn-tumblr:active:focus,.btn-tumblr.active:focus,.open>.dropdown-toggle.btn-tumblr:focus,.btn-tumblr:active.focus,.btn-tumblr.active.focus,.open>.dropdown-toggle.btn-tumblr.focus{color:#fff;background-color:#111c26;border-color:rgba(0,0,0,0.2)} 121 | .btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none} 122 | .btn-tumblr.disabled:hover,.btn-tumblr[disabled]:hover,fieldset[disabled] .btn-tumblr:hover,.btn-tumblr.disabled:focus,.btn-tumblr[disabled]:focus,fieldset[disabled] .btn-tumblr:focus,.btn-tumblr.disabled.focus,.btn-tumblr[disabled].focus,fieldset[disabled] .btn-tumblr.focus{background-color:#2c4762;border-color:rgba(0,0,0,0.2)} 123 | .btn-tumblr .badge{color:#2c4762;background-color:#fff} 124 | .btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:focus,.btn-twitter.focus{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)} 125 | .btn-twitter:hover{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)} 126 | .btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active:hover,.btn-twitter.active:hover,.open>.dropdown-toggle.btn-twitter:hover,.btn-twitter:active:focus,.btn-twitter.active:focus,.open>.dropdown-toggle.btn-twitter:focus,.btn-twitter:active.focus,.btn-twitter.active.focus,.open>.dropdown-toggle.btn-twitter.focus{color:#fff;background-color:#1583d7;border-color:rgba(0,0,0,0.2)} 127 | .btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none} 128 | .btn-twitter.disabled:hover,.btn-twitter[disabled]:hover,fieldset[disabled] .btn-twitter:hover,.btn-twitter.disabled:focus,.btn-twitter[disabled]:focus,fieldset[disabled] .btn-twitter:focus,.btn-twitter.disabled.focus,.btn-twitter[disabled].focus,fieldset[disabled] .btn-twitter.focus{background-color:#55acee;border-color:rgba(0,0,0,0.2)} 129 | .btn-twitter .badge{color:#55acee;background-color:#fff} 130 | .btn-vimeo{color:#fff;background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}.btn-vimeo:focus,.btn-vimeo.focus{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)} 131 | .btn-vimeo:hover{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)} 132 | .btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active:hover,.btn-vimeo.active:hover,.open>.dropdown-toggle.btn-vimeo:hover,.btn-vimeo:active:focus,.btn-vimeo.active:focus,.open>.dropdown-toggle.btn-vimeo:focus,.btn-vimeo:active.focus,.btn-vimeo.active.focus,.open>.dropdown-toggle.btn-vimeo.focus{color:#fff;background-color:#0f7b9f;border-color:rgba(0,0,0,0.2)} 133 | .btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none} 134 | .btn-vimeo.disabled:hover,.btn-vimeo[disabled]:hover,fieldset[disabled] .btn-vimeo:hover,.btn-vimeo.disabled:focus,.btn-vimeo[disabled]:focus,fieldset[disabled] .btn-vimeo:focus,.btn-vimeo.disabled.focus,.btn-vimeo[disabled].focus,fieldset[disabled] .btn-vimeo.focus{background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)} 135 | .btn-vimeo .badge{color:#1ab7ea;background-color:#fff} 136 | .btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:focus,.btn-vk.focus{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)} 137 | .btn-vk:hover{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)} 138 | .btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active:hover,.btn-vk.active:hover,.open>.dropdown-toggle.btn-vk:hover,.btn-vk:active:focus,.btn-vk.active:focus,.open>.dropdown-toggle.btn-vk:focus,.btn-vk:active.focus,.btn-vk.active.focus,.open>.dropdown-toggle.btn-vk.focus{color:#fff;background-color:#3a526b;border-color:rgba(0,0,0,0.2)} 139 | .btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none} 140 | .btn-vk.disabled:hover,.btn-vk[disabled]:hover,fieldset[disabled] .btn-vk:hover,.btn-vk.disabled:focus,.btn-vk[disabled]:focus,fieldset[disabled] .btn-vk:focus,.btn-vk.disabled.focus,.btn-vk[disabled].focus,fieldset[disabled] .btn-vk.focus{background-color:#587ea3;border-color:rgba(0,0,0,0.2)} 141 | .btn-vk .badge{color:#587ea3;background-color:#fff} 142 | .btn-yahoo{color:#fff;background-color:#720e9e;border-color:rgba(0,0,0,0.2)}.btn-yahoo:focus,.btn-yahoo.focus{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)} 143 | .btn-yahoo:hover{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)} 144 | .btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active:hover,.btn-yahoo.active:hover,.open>.dropdown-toggle.btn-yahoo:hover,.btn-yahoo:active:focus,.btn-yahoo.active:focus,.open>.dropdown-toggle.btn-yahoo:focus,.btn-yahoo:active.focus,.btn-yahoo.active.focus,.open>.dropdown-toggle.btn-yahoo.focus{color:#fff;background-color:#39074e;border-color:rgba(0,0,0,0.2)} 145 | .btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none} 146 | .btn-yahoo.disabled:hover,.btn-yahoo[disabled]:hover,fieldset[disabled] .btn-yahoo:hover,.btn-yahoo.disabled:focus,.btn-yahoo[disabled]:focus,fieldset[disabled] .btn-yahoo:focus,.btn-yahoo.disabled.focus,.btn-yahoo[disabled].focus,fieldset[disabled] .btn-yahoo.focus{background-color:#720e9e;border-color:rgba(0,0,0,0.2)} 147 | .btn-yahoo .badge{color:#720e9e;background-color:#fff} 148 | -------------------------------------------------------------------------------- /src/assets/img/angular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/img/angular.png -------------------------------------------------------------------------------- /src/assets/img/angular2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/img/angular2.png -------------------------------------------------------------------------------- /src/assets/img/mb-bg-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/img/mb-bg-01.png -------------------------------------------------------------------------------- /src/assets/img/mb-bg-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/img/mb-bg-02.png -------------------------------------------------------------------------------- /src/assets/img/mb-bg-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/img/mb-bg-03.png -------------------------------------------------------------------------------- /src/assets/img/mb-bg-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/img/mb-bg-04.png -------------------------------------------------------------------------------- /src/assets/img/mb-bg-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/img/mb-bg-05.png -------------------------------------------------------------------------------- /src/assets/img/mb-bg-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/img/mb-bg-06.png -------------------------------------------------------------------------------- /src/assets/img/mountsthelens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/assets/img/mountsthelens.png -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | // import 'zone.js/dist/zone-error'; 6 | 7 | export const environment = { 8 | production: false 9 | }; 10 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenouw/AngularMaterialFirebase/9e2ff9aedfa044bacb9f34f7846c7e0fbc8173bd/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 8 | Material Design | Firebase 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | App is loading... 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { environment } from './environments/environment'; 4 | import { AppModule } from './app/app.module'; 5 | import 'hammerjs'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule, { 12 | preserveWhitespaces: false 13 | }); 14 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular and is loaded before the app. 2 | // You can add your own extra polyfills to this file. 3 | // import 'core-js/es6/symbol'; 4 | // import 'core-js/es6/object'; 5 | // import 'core-js/es6/function'; 6 | // import 'core-js/es6/parse-int'; 7 | // import 'core-js/es6/parse-float'; 8 | // import 'core-js/es6/number'; 9 | // import 'core-js/es6/math'; 10 | // import 'core-js/es6/string'; 11 | // import 'core-js/es6/date'; 12 | // import 'core-js/es6/array'; 13 | // import 'core-js/es6/regexp'; 14 | // import 'core-js/es6/map'; 15 | // import 'core-js/es6/set'; 16 | // import 'core-js/es6/reflect'; 17 | 18 | // import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | 21 | // If you need to support the browsers/features below, uncomment the import 22 | // and run `npm install import-name-here'; 23 | // Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 24 | 25 | // Needed for: IE9 26 | // import 'classlist.js'; 27 | 28 | // Animations 29 | // Needed for: All but Chrome and Firefox, Not supported in IE9 30 | // import 'web-animations-js'; 31 | 32 | // Date, currency, decimal and percent pipes 33 | // Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 34 | // import 'intl'; 35 | 36 | // NgClass on SVG elements 37 | // Needed for: IE10, IE11 38 | // import 'classlist.js'; 39 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import '~@angular/material/prebuilt-themes/deeppurple-amber'; 3 | 4 | body { 5 | background-color: #f7f7f7; 6 | } 7 | 8 | /* Tablet format and lower */ 9 | @media only screen and (max-width: 1024px) { 10 | 11 | } 12 | 13 | /* Mobile format and lower */ 14 | @media only screen and (max-width: 768px) { 15 | 16 | } 17 | 18 | /* Mobile format landscape (wide) */ 19 | @media only screen and (min-width: 320px) and (max-width: 480px) { 20 | 21 | } 22 | 23 | /* Mobile format portrait (small) */ 24 | @media only screen and (max-width: 320px) { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare var __karma__: any; 17 | declare var require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [], 6 | "forceConsistentCasingInFileNames": false 7 | }, 8 | "exclude": [ 9 | "src/test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "custom", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "custom", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "module": "esnext", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "forceConsistentCasingInFileNames": false, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2017", 19 | "dom" 20 | ], 21 | "paths": { 22 | "@shared/*": [ 23 | "src/app/shared/*" 24 | ], 25 | "@shared": [ 26 | "src/app/shared" 27 | ] 28 | } 29 | }, 30 | "angularCompilerOptions": { 31 | "enableIvy": false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": false, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": false, 120 | "no-inputs-metadata-property": true, 121 | "no-outputs-metadata-property": true, 122 | "no-host-metadata-property": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-lifecycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "hammerjs": "registry:dt/hammerjs#2.0.8+20161224203613" 4 | } 5 | } 6 | --------------------------------------------------------------------------------