├── .editorconfig ├── .firebaserc ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── angular-cli.json ├── database.rules.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── firebase.json ├── functions ├── index.js └── package.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── Validators │ │ ├── url-validator.directive.ts │ │ └── url.ts │ ├── actions │ │ ├── project.actions.ts │ │ ├── topic.actions.ts │ │ └── user.actions.ts │ ├── app.module.ts │ ├── app.routes.ts │ ├── auth-config.ts │ ├── components │ │ ├── admin-navigation │ │ │ ├── admin-navigation.component.css │ │ │ ├── admin-navigation.component.html │ │ │ └── admin-navigation.component.ts │ │ ├── all-projects │ │ │ ├── all-projects.component.css │ │ │ ├── all-projects.component.html │ │ │ └── all-projects.component.ts │ │ ├── login │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ └── login.component.ts │ │ ├── navigation │ │ │ ├── navigation.component.css │ │ │ ├── navigation.component.html │ │ │ └── navigation.component.ts │ │ ├── newsletter-card │ │ │ ├── newsletter-card.component.css │ │ │ ├── newsletter-card.component.html │ │ │ ├── newsletter-card.component.spec.ts │ │ │ └── newsletter-card.component.ts │ │ ├── project-card │ │ │ ├── project-card.component.css │ │ │ ├── project-card.component.html │ │ │ └── project-card.component.ts │ │ ├── project-edit │ │ │ ├── project-edit.component.css │ │ │ ├── project-edit.component.html │ │ │ └── project-edit.component.ts │ │ ├── project │ │ │ ├── project.component.css │ │ │ ├── project.component.html │ │ │ └── project.component.ts │ │ └── shared │ │ │ ├── ad │ │ │ ├── ad.component.css │ │ │ ├── ad.component.html │ │ │ ├── ad.component.spec.ts │ │ │ └── ad.component.ts │ │ │ ├── footer │ │ │ ├── footer.component.css │ │ │ ├── footer.component.html │ │ │ └── footer.component.ts │ │ │ ├── header │ │ │ ├── header.component.css │ │ │ ├── header.component.html │ │ │ ├── header.component.ts │ │ │ └── profile-dropdown │ │ │ │ ├── profile-dropdown.component.css │ │ │ │ ├── profile-dropdown.component.html │ │ │ │ └── profile-dropdown.component.ts │ │ │ └── modal │ │ │ ├── modal.component.css │ │ │ ├── modal.component.html │ │ │ ├── modal.component.spec.ts │ │ │ └── modal.component.ts │ ├── container │ │ ├── admin-page │ │ │ ├── admin-page.component.css │ │ │ ├── admin-page.component.html │ │ │ └── admin-page.component.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── login-page │ │ │ ├── login-page.component.css │ │ │ ├── login-page.component.html │ │ │ └── login-page.component.ts │ │ ├── project-detail-page │ │ │ ├── project-detail-page.component.css │ │ │ ├── project-detail-page.component.html │ │ │ └── project-detail-page.component.ts │ │ └── projects-page │ │ │ ├── projects-page.component.css │ │ │ ├── projects-page.component.html │ │ │ └── projects-page.component.ts │ ├── dummyData.ts │ ├── effects │ │ ├── project.effects.ts │ │ ├── topic.effects.ts │ │ └── user.effects.ts │ ├── guards │ │ └── auth.guards.ts │ ├── index.ts │ ├── models │ │ ├── base.ts │ │ ├── index.ts │ │ ├── project.ts │ │ ├── topic.ts │ │ └── user.ts │ ├── pipes │ │ ├── is-upvoted-by-current-user.pipe.ts │ │ ├── search-filter.pipe.ts │ │ └── trim-text.pipe.ts │ ├── reducers │ │ ├── index.ts │ │ ├── projects.reducer.ts │ │ ├── topic.reducer.ts │ │ └── user.reducer.ts │ ├── services │ │ ├── authentication.service.ts │ │ ├── node_modules │ │ │ └── .yarn-integrity │ │ ├── project.service.ts │ │ ├── response-parser.service.ts │ │ ├── toasty-notifier.service.ts │ │ └── yarn.lock │ └── util.ts ├── assets │ ├── .gitkeep │ └── images │ │ ├── Missing-target.png │ │ └── logo.png ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.json └── typings.d.ts ├── tslint.json └── yarn.lock /.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 = 0 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "angularhunt-89db2" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.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 | /bower_components 10 | /functions/node_modules 11 | # IDEs and editors 12 | /.idea 13 | /.vscode 14 | .project 15 | .classpath 16 | *.launch 17 | .settings/ 18 | 19 | # misc 20 | /.sass-cache 21 | /connect.lock 22 | /coverage/* 23 | /libpeerconnection.log 24 | npm-debug.log 25 | testem.log 26 | /typings 27 | 28 | # e2e 29 | /e2e/*.js 30 | /e2e/*.map 31 | 32 | #System Files 33 | .DS_Store 34 | Thumbs.db 35 | 36 | # Secrets 37 | src/app/secrets.ts 38 | # functions 39 | functions/node_modules -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Where to start? 2 | 3 | There are many different ways to contribute to AngularHunt's development, just find the one that best fits with your skills. Examples of contributions we would love to receive include: 4 | 5 | * Mobile compatibility (Responsiveness)[] 6 | * Documentation improvements 7 | * Bug reports 8 | * Patch reviews 9 | * UI enhancements 10 | 11 | Responsiveness get's the highest priority so we'd love PR's related to that. 12 | 13 | We’ll be more than happy to list your name/link in our README and give credits. It’s a nice opportunity for developers who want to experience how open source works or get more experience with this technology or just to connect with cool people and teams. 14 | 15 | Let's build something cool together. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | AngularHunt Logo 4 | 5 |

6 | 7 |

8 | Rate & Discuss(coming soon) about open source angular projects. 9 |

10 | 11 |

12 | Visit website 13 |

14 | 15 |

16 | PRs Welcome 17 | Pivotal Project page 18 |

19 | 20 | ## What is AngularHunt? 21 | 22 | AngularHunt is a platform where developers can **RATE**, **Discuss**(coming soon) about latest open source angular 2.x+ projects. 23 | 24 | We wrote a little piece on our blog introducing [**AngularHunt**](https://medium.com/aviabird/introducing-angularhunt-942ffd78673d). 25 | 26 | ## Contributing 27 | 28 | Where to start? 29 | 30 | There are many different ways to contribute to AngularHunt's development, just find the one that best fits with your skills. Examples of contributions we would love to receive include: 31 | 32 | * Mobile compatibility (Responsiveness) 33 | * Documentation improvements 34 | * Bug reports 35 | * Patch reviews 36 | * UI enhancements 37 | 38 | ## Development server 39 | 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. 40 | 41 | ## Development server with Service Worker 42 | Run `npm run build--prod` to build in production with service worker pre-cache and then `npm run static-serve` to serve. 43 | 44 | ## Build with Service Worker 45 | 46 | Run `npm run build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `npm run build--prod` for a production build. 47 | 48 | 49 | ## Who are we? 50 | 51 | We are [Aviabird Technologies](https://aviabird.com). 52 | 53 | __We love to create awesome Web & Mobile products.__ 54 | 55 | __We are very proud of our work.__ 56 | 57 | We love technologies like Golang, Elixir, Scala, Ruby, Javascript, Typescript, Swift, Java. 58 | 59 | We love some frameworks too:- 60 | * Ruby On Rails 61 | * Phoenix/Elixir framework. 62 | * Spring framework. 63 | * AngularJs (1.x+ & 2.x+) 64 | * ReactJs 65 | * BackboneJs 66 | 67 | ### We are avialible for hire 68 | 69 | __If you want to hire us for a project, please contact us on `hello@aviabird.com`.__ 70 | 71 | -------------------------------------------------------------------------------- /angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.19-3", 4 | "name": "angularhunt" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "test": "test.ts", 17 | "tsconfig": "tsconfig.json", 18 | "prefix": "app", 19 | "mobile": false, 20 | "styles": [ 21 | "styles.css" 22 | ], 23 | "scripts": ["../node_modules/noty/js/noty/packaged/jquery.noty.packaged.js"], 24 | "environmentSource": "environments/environment.ts", 25 | "environments": { 26 | "dev": "environments/environment.ts", 27 | "prod": "environments/environment.prod.ts" 28 | } 29 | } 30 | ], 31 | "addons": [], 32 | "packages": [], 33 | "e2e": { 34 | "protractor": { 35 | "config": "./protractor.conf.js" 36 | } 37 | }, 38 | "test": { 39 | "karma": { 40 | "config": "./karma.conf.js" 41 | } 42 | }, 43 | "defaults": { 44 | "styleExt": "css", 45 | "prefixInterfaces": false, 46 | "inline": { 47 | "style": false, 48 | "template": false 49 | }, 50 | "spec": { 51 | "class": false, 52 | "component": true, 53 | "directive": true, 54 | "module": false, 55 | "pipe": true, 56 | "service": true 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | ".read": true, 4 | ".write": "auth != null" 5 | } 6 | } -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { TestAppPage } from './app.po'; 2 | 3 | describe('angularhunt App', function() { 4 | let page: AngularHuntPage; 5 | 6 | beforeEach(() => { 7 | page = new AngularHuntPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class TestAppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firebase": "AngularHunt", 3 | "public": "dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "hosting": { 10 | "public": "dist", 11 | "rewrites": [{ 12 | "source": "**", 13 | "destination": "/index.html" 14 | }] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | var functions = require('firebase-functions'); 2 | 3 | // // Create and Deploy Your First Cloud Functions 4 | // // https://firebase.google.com/docs/functions/write-firebase-functions 5 | // 6 | // exports.helloWorld = functions.https.onRequest((request, response) => { 7 | // response.send("Hello from Firebase!"); 8 | // }); 9 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "dependencies": { 5 | "firebase-admin": "~4.1.2", 6 | "firebase-functions": "^0.5" 7 | }, 8 | "private": true 9 | } 10 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', 'angular-cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-remap-istanbul'), 12 | require('angular-cli/plugins/karma') 13 | ], 14 | files: [ 15 | { pattern: './src/test.ts', watched: false } 16 | ], 17 | preprocessors: { 18 | './src/test.ts': ['angular-cli'] 19 | }, 20 | remapIstanbulReporter: { 21 | reports: { 22 | html: 'coverage', 23 | lcovonly: './coverage/coverage.lcov' 24 | } 25 | }, 26 | angularCli: { 27 | config: './angular-cli.json', 28 | environment: 'dev' 29 | }, 30 | reporters: config.angularCli && config.angularCli.codeCoverage 31 | ? ['progress', 'karma-remap-istanbul'] 32 | : ['progress'], 33 | port: 9876, 34 | colors: true, 35 | logLevel: config.LOG_INFO, 36 | autoWatch: true, 37 | browsers: ['Chrome'], 38 | singleRun: false 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularhunt", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "ng": "./node_modules/@angular/cli/bin/ng", 8 | "start": "ng serve", 9 | "build": "ng build", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e", 13 | "build--prod": "ng build --prod --no-sourcemap --stats-json", 14 | "sw": "sw-precache --root=dist --config=sw-precache-config.js", 15 | "static-serve": "cd dist && live-server --port=4200 --host=localhost --entry-file=/index.html" 16 | }, 17 | "private": true, 18 | "dependencies": { 19 | "@angular/common": "4.0.0", 20 | "@angular/compiler": "4.0.0", 21 | "@angular/core": "4.0.0", 22 | "@angular/forms": "4.0.0", 23 | "@angular/http": "4.0.0", 24 | "@angular/platform-browser": "4.0.0", 25 | "@angular/platform-browser-dynamic": "4.0.0", 26 | "@angular/router": "4.0.0", 27 | "@ngrx/core": "^1.2.0", 28 | "@ngrx/effects": "^2.0.0", 29 | "@ngrx/store": "^2.2.1", 30 | "angularfire2": "^2.0.0-beta.8", 31 | "core-js": "^2.4.1", 32 | "firebase": "^3.8.0", 33 | "font-awesome": "^4.7.0", 34 | "ng2-adsense": "^2.2.1", 35 | "ng2-sharebuttons": "^2.1.0", 36 | "ngx-bootstrap": "^1.6.6", 37 | "noty": "^2.4.1", 38 | "reselect": "^2.5.4", 39 | "rxjs": "^5.1.0", 40 | "ts-helpers": "^1.1.1", 41 | "zone.js": "^0.8.4" 42 | }, 43 | "devDependencies": { 44 | "@angular/cli": "1.0.0", 45 | "@angular/compiler-cli": "4.0.0", 46 | "@types/jasmine": "2.5.38", 47 | "@types/node": "~6.0.60", 48 | "codelyzer": "~2.0.0-beta.4", 49 | "firebase-tools": "^3.5.0", 50 | "jasmine-core": "~2.5.2", 51 | "jasmine-spec-reporter": "~3.2.0", 52 | "karma": "~1.4.1", 53 | "karma-chrome-launcher": "~2.0.0", 54 | "karma-cli": "~1.0.1", 55 | "karma-coverage-istanbul-reporter": "^0.2.0", 56 | "karma-jasmine": "~1.1.0", 57 | "karma-jasmine-html-reporter": "^0.2.2", 58 | "ngrx-store-freeze": "^0.1.9", 59 | "node-sass": "^4.5.0", 60 | "protractor": "~5.1.0", 61 | "raw-loader": "^0.5.1", 62 | "sass-loader": "^6.0.3", 63 | "sw-precache": "^5.0.0", 64 | "ts-node": "~2.0.0", 65 | "tslint": "~4.4.2", 66 | "typescript": "2.1.6" 67 | }, 68 | "main": "index.js", 69 | "repository": "git@github.com:aviabird/angularhunt.git", 70 | "author": "voidzero " 71 | } 72 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/Validators/url-validator.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, forwardRef } from '@angular/core'; 2 | import { NG_VALIDATORS, Validator, AbstractControl } from '@angular/forms'; 3 | 4 | import { UrlValidator } from './url'; 5 | 6 | const URL_VALIDATOR: any = { 7 | provide: NG_VALIDATORS, 8 | useExisting: forwardRef(() => UrlValidatorDirective), 9 | multi: true 10 | }; 11 | 12 | @Directive({ 13 | selector: '[url][formControlName],[url][formControl],[url][ngModel]', 14 | providers: [URL_VALIDATOR] 15 | }) 16 | export class UrlValidatorDirective implements Validator { 17 | validate(c: AbstractControl): {[key: string]: any} { 18 | return UrlValidator(c); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/Validators/url.ts: -------------------------------------------------------------------------------- 1 | import { AbstractControl, Validators, ValidatorFn } from '@angular/forms'; 2 | 3 | export const UrlValidator: ValidatorFn = (control: AbstractControl): { [key: string]: boolean } => { 4 | if (Validators.required(control) !== undefined 5 | && Validators.required(control) !== null) { 6 | return null; 7 | } 8 | let v: string = control.value; 9 | return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(v) ? null : { 'url': true }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/app/actions/project.actions.ts: -------------------------------------------------------------------------------- 1 | import { User } from './../models/user'; 2 | import { type } from '../util'; 3 | import { Action } from '@ngrx/store'; 4 | import { Project } from '../models'; 5 | 6 | export const ActionTypes = { 7 | RETRIVE_PROJECTS: type('Retrive Lists'), 8 | RETRIVE_PROJECTS_SUCCESS: type('Retrive Lists Success'), 9 | SELECT_PROJECT: type('Select Project'), 10 | TOGGLE_UPVOTE: type('Toggle Upvote'), 11 | UPDATE_PROJECT_SUCCESS: type('Update Project Success'), 12 | TOGGLE_UPVOTE_SUCCESS: type('Toggle Upvote Success') 13 | // SAVE_NEW_PROJECT: type('Save New Project'), 14 | // SAVE_NEW_PROJECT_SUCCESS: type('Save New Project Success'), 15 | }; 16 | 17 | export class ProjectActions { 18 | 19 | retriveProjects(): Action { 20 | return { 21 | type: ActionTypes.RETRIVE_PROJECTS 22 | } 23 | } 24 | 25 | retriveProjectsSuccess(projects: Project[]): Action { 26 | return { 27 | type: ActionTypes.RETRIVE_PROJECTS_SUCCESS, 28 | payload: projects 29 | } 30 | } 31 | 32 | selectProject(projectId: any): Action { 33 | return { 34 | type: ActionTypes.SELECT_PROJECT, 35 | payload: projectId 36 | }; 37 | } 38 | 39 | toggleUpvote(project: Project, action: string, user: User): Action { 40 | return { 41 | type: ActionTypes.TOGGLE_UPVOTE, 42 | payload: {project: project, action: action, user: user} 43 | }; 44 | } 45 | 46 | /**Temp Action for deployment will be replaced by 47 | * updateProjectSuccess 48 | */ 49 | toggleUpvoteSuccess(): Action { 50 | return { 51 | type: ActionTypes.TOGGLE_UPVOTE_SUCCESS 52 | }; 53 | } 54 | 55 | updateProjectSuccess(project: any): Action { 56 | return { 57 | type: ActionTypes.UPDATE_PROJECT_SUCCESS, 58 | payload: project 59 | }; 60 | } 61 | 62 | // saveNewProject(project: any): Action { 63 | // return { 64 | // type: ActionTypes.SAVE_NEW_PROJECT, 65 | // payload: project 66 | // }; 67 | // } 68 | 69 | // saveNewProjectSuccess(project: any): Action { 70 | // return { 71 | // type: ActionTypes.SAVE_NEW_PROJECT_SUCCESS, 72 | // payload: project 73 | // }; 74 | // } 75 | } 76 | -------------------------------------------------------------------------------- /src/app/actions/topic.actions.ts: -------------------------------------------------------------------------------- 1 | import { Topic } from './../models/topic'; 2 | import { type } from '../util'; 3 | import { Action } from '@ngrx/store'; 4 | 5 | export const ActionTypes = { 6 | LOAD_TOPICS: type('Load Topics'), 7 | LOAD_TOPICS_SUCCESS: type('Load Topics Success'), 8 | }; 9 | 10 | export class TopicActions { 11 | 12 | loadTopics(): Action { 13 | return { 14 | type: ActionTypes.LOAD_TOPICS 15 | }; 16 | } 17 | 18 | loadTopicsSuccess(response: Topic[]): Action { 19 | return { 20 | type: ActionTypes.LOAD_TOPICS_SUCCESS, 21 | payload: response 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/actions/user.actions.ts: -------------------------------------------------------------------------------- 1 | import { type } from '../util'; 2 | import { Action } from '@ngrx/store'; 3 | import { User } from '../models'; 4 | 5 | export const ActionTypes = { 6 | LOGIN: type('Login'), 7 | LOGIN_SUCCESS: type('Login Success'), 8 | LOGOUT: type('Logout'), 9 | LOGOUT_SUCCESS: type('Logout Success'), 10 | LOAD_CURRENT_USER_PROFILE: type('Load Current User Profile'), 11 | LOAD_CURRENT_USER_PROFILE_SUCCESS: type('Load Current User Profile Success'), 12 | LOAD_UPVOTED_PROJECT_IDS: type('Load Upvoted Project Ids'), 13 | LOAD_UPVOTED_PROJECT_IDS_SUCCESS: type('Load Upvoted Project Ids Success') 14 | }; 15 | 16 | export class UserActions { 17 | 18 | login(provider: string): Action { 19 | return { 20 | type: ActionTypes.LOGIN, 21 | payload: provider 22 | }; 23 | } 24 | 25 | // TODO: response should be user 26 | loginSuccess(response: any): Action { 27 | return { 28 | type: ActionTypes.LOGIN_SUCCESS, 29 | payload: response 30 | }; 31 | } 32 | 33 | logout(): Action { 34 | return { 35 | type: ActionTypes.LOGOUT 36 | }; 37 | } 38 | 39 | logoutSuccess(): Action { 40 | return { 41 | type: ActionTypes.LOGOUT_SUCCESS 42 | } 43 | } 44 | 45 | loadCurrentUserProfile(): Action { 46 | return { 47 | type: ActionTypes.LOAD_CURRENT_USER_PROFILE 48 | }; 49 | } 50 | 51 | loadCurrentUserProfileSuccess(user: User): Action { 52 | return { 53 | type: ActionTypes.LOAD_CURRENT_USER_PROFILE_SUCCESS, 54 | payload: user 55 | }; 56 | } 57 | 58 | loadUpvotedProjectIds(payload: { userId: any, projectIds: String[] }): Action { 59 | return { 60 | type: ActionTypes.LOAD_UPVOTED_PROJECT_IDS, 61 | payload: payload 62 | }; 63 | }; 64 | 65 | loadUpvotedProjectIdsSuccess(ids: String[]): Action { 66 | return { 67 | type: ActionTypes.LOAD_UPVOTED_PROJECT_IDS_SUCCESS, 68 | payload: ids 69 | }; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { TopicEffects } from './effects/topic.effects'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { NgModule } from '@angular/core'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { HttpModule, JsonpModule } from '@angular/http'; 6 | 7 | import { ModalModule, BsDropdownModule } from 'ngx-bootstrap'; 8 | import { ShareButtonsModule } from 'ng2-sharebuttons'; 9 | 10 | /**NgRx Store */ 11 | import { StoreModule } from '@ngrx/store'; 12 | import { EffectsModule } from '@ngrx/effects'; 13 | 14 | /**Services */ 15 | import { ProjectService } from './services/project.service'; 16 | import { AuthenticationService } from './services/authentication.service'; 17 | import { ResponseParserService } from './services/response-parser.service'; 18 | import { ToastyNotifierService } from './services/toasty-notifier.service'; 19 | 20 | /** All SideEffects in APP */ 21 | import { ProjectEffects } from './effects/project.effects'; 22 | import { UserEffects } from './effects/user.effects'; 23 | 24 | /**Global Reducer of APP */ 25 | import reducer from './reducers'; 26 | 27 | /**All Routes in APP */ 28 | import { routing } from './app.routes'; 29 | 30 | /**Actions */ 31 | import { ProjectActions } from './actions/project.actions'; 32 | import { UserActions } from './actions/user.actions'; 33 | import { TopicActions } from './actions/topic.actions'; 34 | 35 | /**AngularFire */ 36 | import { AngularFireModule, AuthMethods } from 'angularfire2'; 37 | import { secretKeys } from './secrets'; 38 | 39 | /**AuthGuard */ 40 | import { CanActivateViaAuthGuard } from './guards/auth.guards'; 41 | 42 | 43 | // Must export the config 44 | export const firebaseConfig = { 45 | apiKey: secretKeys.FIREBASE_API_KEY, 46 | authDomain: 'angularhunt-89db2.firebaseapp.com', 47 | databaseURL: 'https://angularhunt-89db2.firebaseio.com', 48 | storageBucket: 'angularhunt-89db2.appspot.com', 49 | messagingSenderId: '281473446041' 50 | }; 51 | 52 | import { IsUpvotedByCurrentUserPipe } from './pipes/is-upvoted-by-current-user.pipe'; 53 | 54 | /**All Components */ 55 | import { AppComponent } from './container/app.component'; 56 | import { ProjectsPageComponent } from './container/projects-page/projects-page.component'; 57 | import { ProjectCardComponent } from './components/project-card/project-card.component'; 58 | import { ProjectDetailPageComponent } from './container/project-detail-page/project-detail-page.component'; 59 | import { HeaderComponent } from './components/shared/header/header.component'; 60 | import { ProjectComponent } from './components/project/project.component'; 61 | import { NewsletterCardComponent } from './components/newsletter-card/newsletter-card.component'; 62 | import { NavigationComponent } from './components/navigation/navigation.component'; 63 | import { ModalComponent } from './components/shared/modal/modal.component'; 64 | import { ProfileDropdownComponent } from './components/shared/header/profile-dropdown/profile-dropdown.component'; 65 | import { TrimTextPipe } from './pipes/trim-text.pipe'; 66 | import { LoginComponent } from './components/login/login.component'; 67 | import { LoginPageComponent } from './container/login-page/login-page.component'; 68 | import { FooterComponent } from './components/shared/footer/footer.component'; 69 | import { AdminPageComponent } from './container/admin-page/admin-page.component'; 70 | import { UrlValidatorDirective } from './Validators/url-validator.directive'; 71 | import { AdminNavigationComponent } from './components/admin-navigation/admin-navigation.component'; 72 | import { ProjectEditComponent } from './components/project-edit/project-edit.component'; 73 | import { AllProjectsComponent } from './components/all-projects/all-projects.component'; 74 | import { SearchFilterPipe } from './pipes/search-filter.pipe'; 75 | import { AdComponent } from './components/shared/ad/ad.component'; 76 | 77 | @NgModule({ 78 | declarations: [ 79 | AppComponent, 80 | ProjectsPageComponent, 81 | ProjectCardComponent, 82 | ProjectDetailPageComponent, 83 | HeaderComponent, 84 | ProjectComponent, 85 | NewsletterCardComponent, 86 | NavigationComponent, 87 | ModalComponent, 88 | ProfileDropdownComponent, 89 | IsUpvotedByCurrentUserPipe, 90 | TrimTextPipe, 91 | LoginComponent, 92 | LoginPageComponent, 93 | FooterComponent, 94 | AdminPageComponent, 95 | UrlValidatorDirective, 96 | AdminNavigationComponent, 97 | ProjectEditComponent, 98 | AllProjectsComponent, 99 | SearchFilterPipe, 100 | AdComponent, 101 | ], 102 | imports: [ 103 | BrowserModule, 104 | FormsModule, 105 | ReactiveFormsModule, 106 | HttpModule, 107 | JsonpModule, 108 | routing, 109 | StoreModule.provideStore(reducer), 110 | ModalModule.forRoot(), 111 | BsDropdownModule.forRoot(), 112 | ShareButtonsModule.forRoot(), 113 | EffectsModule.run(ProjectEffects), 114 | EffectsModule.run(UserEffects), 115 | EffectsModule.run(TopicEffects), 116 | AngularFireModule.initializeApp(firebaseConfig, { 117 | method: AuthMethods.Redirect 118 | }), 119 | ], 120 | providers: [ 121 | TopicActions, 122 | UserActions, 123 | ProjectActions, 124 | CanActivateViaAuthGuard, 125 | ProjectService, 126 | AuthenticationService, 127 | ResponseParserService, 128 | ToastyNotifierService 129 | ], 130 | bootstrap: [AppComponent] 131 | }) 132 | export class AppModule { } 133 | -------------------------------------------------------------------------------- /src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { AllProjectsComponent } from './components/all-projects/all-projects.component'; 2 | import { ProjectEditComponent } from './components/project-edit/project-edit.component'; 3 | /**Required Angular 2 Modules for Router */ 4 | import { Routes, RouterModule } from '@angular/router'; 5 | /** Componets required for routing */ 6 | import { ProjectsPageComponent } from './container/projects-page/projects-page.component'; 7 | import { ProjectDetailPageComponent } from './container/project-detail-page/project-detail-page.component'; 8 | import { LoginPageComponent } from './container/login-page/login-page.component'; 9 | import { AdminPageComponent } from './container/admin-page/admin-page.component'; 10 | 11 | /**AuthGuard */ 12 | import { CanActivateViaAuthGuard } from './guards/auth.guards'; 13 | 14 | const routes: Routes = [ 15 | { path: '', redirectTo: 'home', pathMatch: 'full' }, 16 | { 17 | path: 'home', 18 | children: [ 19 | { 20 | path: '', 21 | component: ProjectsPageComponent 22 | }, 23 | { 24 | path: ':id', 25 | component: ProjectDetailPageComponent 26 | } 27 | ] 28 | }, 29 | { path: 'login', component: LoginPageComponent }, 30 | { path: 'admin', component: AdminPageComponent, 31 | canActivate: [ CanActivateViaAuthGuard ], 32 | children: [ 33 | { path: '', redirectTo: 'new-project', pathMatch: 'full' }, 34 | { path: 'new-project', component: ProjectEditComponent }, 35 | { path: 'edit/:id', component: ProjectEditComponent }, 36 | { path: 'all-projects', component: AllProjectsComponent } 37 | ] 38 | } 39 | ]; 40 | 41 | export const routing = RouterModule.forRoot(routes); 42 | -------------------------------------------------------------------------------- /src/app/auth-config.ts: -------------------------------------------------------------------------------- 1 | // import { environment } from './../environments/environment'; 2 | // import { CustomConfig } from 'ng2-ui-auth'; 3 | // /** 4 | // * Created by VoidZero on 09/02/2017. 5 | // */ 6 | 7 | // export const GOOGLE_CLIENT_ID = '64504024589-1oukh9057md9e2g1rrads4ppufdv3ge2.apps.googleusercontent.com'; 8 | // const baseUrl = environment.API_ENDPOINT + '/auth/google'; 9 | 10 | // export class MyAuthConfig extends CustomConfig { 11 | // defaultHeaders = {'Content-Type': 'application/json'}; 12 | // providers = {google: { clientId: GOOGLE_CLIENT_ID, url: baseUrl }}; 13 | // } 14 | -------------------------------------------------------------------------------- /src/app/components/admin-navigation/admin-navigation.component.css: -------------------------------------------------------------------------------- 1 | /*-----------------------LEFT PART-------------------------------*/ 2 | 3 | .navigation { 4 | margin-right: 20px; 5 | width: 210px; 6 | box-sizing: border-box; 7 | overflow: hidden; 8 | } 9 | 10 | .stickyNavigation { 11 | position: relative; 12 | width: 210px; 13 | } 14 | 15 | .title { 16 | display: -ms-flexbox; 17 | display: flex; 18 | -ms-flex-pack: justify; 19 | justify-content: space-between; 20 | -ms-flex-align: baseline; 21 | align-items: baseline; 22 | margin-bottom: 10px; 23 | } 24 | 25 | .secondaryBoldText { 26 | font-weight: 600; 27 | font-size: 12px; 28 | font-weight: 400; 29 | letter-spacing: .6px; 30 | line-height: 16px; 31 | text-transform: uppercase; 32 | color: #999; 33 | margin: 0; 34 | } 35 | 36 | .list { 37 | padding: 0; 38 | margin: 0; 39 | list-style: none; 40 | } 41 | 42 | .list li { 43 | width: 100%; 44 | } 45 | 46 | .active-Item { 47 | border: 1px solid #e5e5e5; 48 | background-color: #fff; 49 | -ms-flex-align: center; 50 | align-items: center; 51 | box-sizing: border-box; 52 | display: -ms-flexbox; 53 | display: flex; 54 | padding: 4px; 55 | margin-bottom: 4px; 56 | border-radius: 3px; 57 | text-decoration: none; 58 | } 59 | 60 | .active-Item:hover, 61 | .item:hover { 62 | border: 1px solid #e5e5e5; 63 | background-color: #fbfbfb; 64 | } 65 | 66 | .greyIcon { 67 | background-color: #e8e8e8; 68 | display: -ms-flexbox; 69 | display: flex; 70 | -ms-flex-align: center; 71 | align-items: center; 72 | -ms-flex-pack: distribute; 73 | justify-content: space-around; 74 | border-radius: 3px; 75 | height: 24px; 76 | margin-right: 10px; 77 | width: 24px; 78 | } 79 | 80 | .item { 81 | -ms-flex-align: center; 82 | align-items: center; 83 | border: 1px solid transparent; 84 | box-sizing: border-box; 85 | display: -ms-flexbox; 86 | display: flex; 87 | padding: 4px; 88 | margin-bottom: 4px; 89 | border-radius: 3px; 90 | text-decoration: none; 91 | } 92 | 93 | .containericon { 94 | border-radius: 3px; 95 | margin-right: 10px; 96 | position: relative; 97 | } 98 | 99 | 100 | .item-text { 101 | display: block; 102 | color: #000; 103 | overflow: hidden; 104 | white-space: nowrap; 105 | text-overflow: ellipsis; 106 | width: 140px; 107 | font-size: 14px; 108 | line-height: 18px; 109 | margin: 0; 110 | } 111 | 112 | .lazyLoadContainer { 113 | border-radius: inherit; 114 | height: 100%; 115 | left: 0; 116 | position: absolute; 117 | top: 0; 118 | width: 100%; 119 | display: inherit; 120 | } 121 | 122 | img { 123 | vertical-align: bottom; 124 | border: none; 125 | height: 24px; 126 | width: 24px; 127 | } 128 | 129 | .ion-ios-plus-empty { 130 | font-size: 24px; 131 | } 132 | 133 | li:last-child .item-text { 134 | color: #da552f; 135 | } -------------------------------------------------------------------------------- /src/app/components/admin-navigation/admin-navigation.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/admin-navigation/admin-navigation.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-admin-navigation', 5 | templateUrl: './admin-navigation.component.html', 6 | styleUrls: ['./admin-navigation.component.css'] 7 | }) 8 | export class AdminNavigationComponent { 9 | } 10 | -------------------------------------------------------------------------------- /src/app/components/all-projects/all-projects.component.css: -------------------------------------------------------------------------------- 1 | table { 2 | font-family: arial, sans-serif; 3 | border-collapse: collapse; 4 | width: 100%; 5 | } 6 | 7 | td, th { 8 | border: 1px solid #dddddd; 9 | text-align: left; 10 | padding: 8px; 11 | } 12 | 13 | .divider { 14 | color: black; 15 | margin: 3px; 16 | border-left: 1px solid black; 17 | height: 13px; 18 | width: 3px; 19 | } 20 | 21 | .link { 22 | cursor: pointer; 23 | text-decoration: underline; 24 | color: blue; 25 | } -------------------------------------------------------------------------------- /src/app/components/all-projects/all-projects.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Search: 5 |
6 |
7 | 8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 |
Project NameProject Author NameApproved StatusActions
{{project.name}}{{project.author_name}}{{project.approved}} 23 | Edit 24 | 25 | Delete 26 |
29 |
-------------------------------------------------------------------------------- /src/app/components/all-projects/all-projects.component.ts: -------------------------------------------------------------------------------- 1 | import { ProjectService } from './../../services/project.service'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { Project } from './../../models/project'; 4 | import { Store } from '@ngrx/store'; 5 | import { 6 | AppState, 7 | getProjects 8 | } from '../../reducers/index'; 9 | import { ProjectActions } from '../../actions/project.actions'; 10 | import { ToastyNotifierService } from './../../services/toasty-notifier.service'; 11 | 12 | @Component({ 13 | selector: 'app-all-projects', 14 | templateUrl: './all-projects.component.html', 15 | styleUrls: ['./all-projects.component.css'] 16 | }) 17 | export class AllProjectsComponent implements OnInit { 18 | projects: Project[] = null; 19 | searchQuery = ''; 20 | 21 | constructor( 22 | private projectService: ProjectService, 23 | private projectActions: ProjectActions, 24 | private store: Store, 25 | private toasterService: ToastyNotifierService) { 26 | this.store.select(getProjects) 27 | .subscribe(projects => this.projects = projects); 28 | } 29 | 30 | ngOnInit() { 31 | this.store.dispatch(this.projectActions.retriveProjects()); 32 | } 33 | 34 | 35 | loadMoreProjects() { 36 | this.store.dispatch(this.projectActions.retriveProjects()); 37 | } 38 | 39 | deleteProject(id) { 40 | this.projectService.deleteProject(id).then(() => 41 | this.toasterService.pop({ result: 'success', msg: 'Project Deleted Succesfully' }) 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/components/login/login.component.css: -------------------------------------------------------------------------------- 1 | .login { 2 | background: url("../../../assets/images/Missing-target.png") top/300px 200px no-repeat; 3 | padding: 160px 10px 50px; 4 | text-align: center; 5 | } 6 | 7 | h1 { 8 | display: block; 9 | font-size: 2em; 10 | -webkit-margin-before: 0.67em; 11 | -webkit-margin-after: 0.67em; 12 | -webkit-margin-start: 0px; 13 | -webkit-margin-end: 0px; 14 | font-weight: bold; 15 | } 16 | 17 | .title { 18 | font-size: 22px; 19 | font-weight: 600; 20 | } 21 | 22 | .intro { 23 | padding-bottom: 20px; 24 | color: #444; 25 | font-size: 18px; 26 | margin: 0 auto; 27 | max-width: 500px; 28 | } 29 | 30 | p { 31 | display: block; 32 | -webkit-margin-before: 1em; 33 | -webkit-margin-after: 1em; 34 | -webkit-margin-start: 0px; 35 | -webkit-margin-end: 0px; 36 | } 37 | 38 | a { 39 | color: #da552f; 40 | background-color: transparent; 41 | text-decoration: none; 42 | cursor: pointer; 43 | } 44 | 45 | .buttonGroup { 46 | display: flex; 47 | justify-content: center; 48 | margin: 0 10px; 49 | } 50 | 51 | .googleSolidColor { 52 | background: #c53929; 53 | border-color: #c53929; 54 | } 55 | 56 | 57 | .twitterSolidColor { 58 | background: #00aced; 59 | border-color: #00aced; 60 | } 61 | 62 | .solidVariant { 63 | color: #fff; 64 | fill: #fff; 65 | } 66 | 67 | .mediumSize { 68 | height: 34px; 69 | padding: 0 13px; 70 | } 71 | 72 | .button { 73 | margin-right: 10px; 74 | border-radius: 3px; 75 | border: 1px solid transparent; 76 | box-sizing: border-box; 77 | outline: 0; 78 | display: -ms-inline-flexbox; 79 | display: inline-flex; 80 | -ms-flex-align: center; 81 | align-items: center; 82 | -ms-flex-pack: center; 83 | justify-content: center; 84 | text-align: center; 85 | text-decoration: none!important; 86 | } 87 | 88 | .secondaryText { 89 | letter-spacing: .3px; 90 | line-height: 16px; 91 | text-transform: uppercase; 92 | font-size: 11px; 93 | font-weight: 600; 94 | } 95 | 96 | .facebookSolidColor { 97 | background: #3b5998; 98 | border-color: #3b5998; 99 | } 100 | 101 | .loginInfo { 102 | color: #999; 103 | padding-top: 10px; 104 | } 105 | 106 | .fa { 107 | padding-right: 8px; 108 | font-size: 20px; 109 | } -------------------------------------------------------------------------------- /src/app/components/login/login.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { AppState } from './../../reducers/index'; 2 | import { Store } from '@ngrx/store'; 3 | import { UserActions } from './../../actions/user.actions'; 4 | import { Component, OnInit } from '@angular/core'; 5 | 6 | @Component({ 7 | selector: 'app-login', 8 | templateUrl: './login.component.html', 9 | styleUrls: ['./login.component.css'] 10 | }) 11 | export class LoginComponent implements OnInit { 12 | 13 | constructor(private userActions: UserActions, 14 | private store: Store) { } 15 | 16 | ngOnInit() { 17 | } 18 | 19 | login(provider: string) { 20 | this.store.dispatch(this.userActions.login(provider)); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/navigation/navigation.component.css: -------------------------------------------------------------------------------- 1 | /*-----------------------LEFT PART-------------------------------*/ 2 | 3 | .navigation { 4 | margin-right: 20px; 5 | width: 210px; 6 | box-sizing: border-box; 7 | overflow: hidden; 8 | } 9 | 10 | .stickyNavigation { 11 | position: relative; 12 | width: 210px; 13 | } 14 | 15 | .title { 16 | display: -ms-flexbox; 17 | display: flex; 18 | -ms-flex-pack: justify; 19 | justify-content: space-between; 20 | -ms-flex-align: baseline; 21 | align-items: baseline; 22 | margin-bottom: 10px; 23 | } 24 | 25 | .secondaryBoldText { 26 | font-weight: 600; 27 | font-size: 12px; 28 | font-weight: 400; 29 | letter-spacing: .6px; 30 | line-height: 16px; 31 | text-transform: uppercase; 32 | color: #999; 33 | margin: 0; 34 | } 35 | 36 | .list { 37 | padding: 0; 38 | margin: 0; 39 | list-style: none; 40 | } 41 | 42 | .list li { 43 | width: 100%; 44 | } 45 | 46 | .active-Item { 47 | border: 1px solid #e5e5e5; 48 | background-color: #fff; 49 | -ms-flex-align: center; 50 | align-items: center; 51 | box-sizing: border-box; 52 | display: -ms-flexbox; 53 | display: flex; 54 | padding: 4px; 55 | margin-bottom: 4px; 56 | border-radius: 3px; 57 | text-decoration: none; 58 | } 59 | 60 | .active-Item:hover, 61 | .item:hover { 62 | border: 1px solid #e5e5e5; 63 | background-color: #fbfbfb; 64 | } 65 | 66 | .greyIcon { 67 | background-color: #e8e8e8; 68 | display: -ms-flexbox; 69 | display: flex; 70 | -ms-flex-align: center; 71 | align-items: center; 72 | -ms-flex-pack: distribute; 73 | justify-content: space-around; 74 | border-radius: 3px; 75 | height: 24px; 76 | margin-right: 10px; 77 | width: 24px; 78 | } 79 | 80 | .item { 81 | -ms-flex-align: center; 82 | align-items: center; 83 | border: 1px solid transparent; 84 | box-sizing: border-box; 85 | display: -ms-flexbox; 86 | display: flex; 87 | padding: 4px; 88 | margin-bottom: 4px; 89 | border-radius: 3px; 90 | text-decoration: none; 91 | } 92 | 93 | .containericon { 94 | border-radius: 3px; 95 | margin-right: 10px; 96 | position: relative; 97 | } 98 | 99 | 100 | .item-text { 101 | display: block; 102 | color: #000; 103 | overflow: hidden; 104 | white-space: nowrap; 105 | text-overflow: ellipsis; 106 | width: 140px; 107 | font-size: 14px; 108 | line-height: 18px; 109 | margin: 0; 110 | } 111 | 112 | .lazyLoadContainer { 113 | border-radius: inherit; 114 | height: 100%; 115 | left: 0; 116 | position: absolute; 117 | top: 0; 118 | width: 100%; 119 | display: inherit; 120 | } 121 | 122 | img { 123 | vertical-align: bottom; 124 | border: none; 125 | height: 24px; 126 | width: 24px; 127 | } 128 | 129 | .ion-ios-plus-empty { 130 | font-size: 24px; 131 | } 132 | 133 | li:last-child .item-text { 134 | color: #da552f; 135 | } 136 | 137 | @media only screen and (max-width: 768px) { 138 | .navigation { 139 | display: none; 140 | } 141 | } -------------------------------------------------------------------------------- /src/app/components/navigation/navigation.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/navigation/navigation.component.ts: -------------------------------------------------------------------------------- 1 | import { Topic } from './../../models/topic'; 2 | import { Component, Input } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'app-navigation', 6 | templateUrl: './navigation.component.html', 7 | styleUrls: ['./navigation.component.css'] 8 | }) 9 | export class NavigationComponent { 10 | @Input() topics: Topic[]; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/components/newsletter-card/newsletter-card.component.css: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------TOP-MIDDLE-PART----------------------------------*/ 2 | 3 | 4 | .top-middle-container { 5 | width: 567px; 6 | position: relative; 7 | cursor: pointer; 8 | } 9 | 10 | .fullwidth-top-middle-box { 11 | padding: 20px; 12 | width: 526px; 13 | background: #fff; 14 | border-radius: 3px; 15 | border: 1px solid rgba(0,0,0,.09); 16 | box-shadow: 0 1px 4px 0 rgba(0,0,0,.04); 17 | margin: 0 0 20px; 18 | cursor: pointer; 19 | } 20 | 21 | 22 | .preview { 23 | border: 1px solid #e8e8e8; 24 | margin-bottom: 20px; 25 | padding-right: 20px; 26 | display: -ms-flexbox; 27 | display: flex; 28 | text-decoration: none; 29 | background-color: transparent; 30 | cursor: pointer; 31 | } 32 | 33 | .background-image { 34 | background-image: url("https://firebasestorage.googleapis.com/v0/b/angularhunt-89db2.appspot.com/o/email-subs.jpg?alt=media&token=ecf1a384-75c5-40fb-a8f9-b5bac1910260"); 35 | background-size: cover; 36 | cursor: pointer; 37 | } 38 | 39 | .background-image { 40 | width: 180px; 41 | margin-right: 20px; 42 | border-right: 1px solid #e8e8e8; 43 | } 44 | 45 | .main-text { 46 | -ms-flex-item-align: center; 47 | -ms-grid-row-align: center; 48 | align-self: center; 49 | padding-top: 5px; 50 | padding-bottom: 5px; 51 | } 52 | 53 | .featured-text { 54 | font-size: 20px; 55 | font-weight: 200; 56 | letter-spacing: .2px; 57 | line-height: 24px; 58 | color: #000; 59 | font-family: proxima-nova,Proxima Nova,helvetica,arial,sans-serif; 60 | margin: 0; 61 | margin-bottom: 11px; 62 | cursor: pointer; 63 | } 64 | 65 | .text-base { 66 | font-size: 14px; 67 | font-weight: 400; 68 | line-height: 18px; 69 | color: #999; 70 | margin: 0; 71 | cursor: pointer; 72 | } 73 | 74 | .cta { 75 | text-align: left; 76 | display: -ms-flexbox; 77 | display: flex; 78 | -ms-flex-pack: justify; 79 | justify-content: space-between; 80 | cursor: pointer; 81 | } 82 | 83 | .cta 84 | .bold-text { 85 | width: 235px; 86 | } 87 | 88 | .bold-text { 89 | font-weight: 600; 90 | font-size: 14px; 91 | line-height: 18px; 92 | color: #000; 93 | margin: 0; 94 | cursor: pointer; 95 | } 96 | 97 | .emoji { 98 | height: 14px; 99 | width: 14px; 100 | cursor: pointer; 101 | } 102 | 103 | .form_2 { 104 | display: -ms-flexbox; 105 | display: flex; 106 | -ms-flex-pack: end; 107 | justify-content: flex-end; 108 | -ms-flex-direction: row; 109 | flex-direction: row; 110 | cursor: pointer; 111 | width: 100%; 112 | position: relative; 113 | } 114 | 115 | .fieldWrap { 116 | width: 78%; 117 | padding-right: 2%; 118 | display: -ms-flexbox; 119 | display: flex; 120 | cursor: pointer; 121 | } 122 | 123 | .field_base { 124 | width: 100%; 125 | border-radius: 3px; 126 | border: 1px solid #e8e8e8; 127 | box-sizing: border-box; 128 | color: #000; 129 | font-size: 14px !important; 130 | font-weight: 400 !important; 131 | line-height: 18px !important; 132 | font: inherit; 133 | cursor: pointer; 134 | padding-left: 10px; 135 | } 136 | 137 | .field_base::placeholder { 138 | color: #999; 139 | } 140 | 141 | .button_mediumSize { 142 | width: 20%; 143 | border-radius: 3px; 144 | border: 1px solid transparent; 145 | box-sizing: border-box; 146 | outline: 0; 147 | display: -ms-inline-flexbox; 148 | display: inline-flex; 149 | -ms-flex-align: center; 150 | align-items: center; 151 | -ms-flex-pack: center; 152 | justify-content: center; 153 | text-align: center; 154 | text-decoration: none !important; 155 | font-size: 12px; 156 | font-weight: 600; 157 | letter-spacing: .6px; 158 | line-height: 16px; 159 | text-transform: uppercase; 160 | color: #fff; 161 | fill: #fff; 162 | background: #da552f; 163 | border-color: #da552f; 164 | height: 34px; 165 | padding: 0 13px; 166 | cursor: pointer; 167 | } 168 | 169 | .button_mediumSize:hover { 170 | background: #ea532a; 171 | border-color: #ea532a; 172 | } 173 | 174 | .buttonContainer { 175 | -ms-flex-align: center; 176 | align-items: center; 177 | display: -ms-inline-flexbox; 178 | display: inline-flex; 179 | -ms-flex-wrap: nowrap; 180 | flex-wrap: nowrap; 181 | -ms-flex-pack: center; 182 | justify-content: center; 183 | width: inherit; 184 | cursor: pointer; 185 | } 186 | 187 | 188 | @media only screen and (max-width: 768px) { 189 | .top-middle-container { 190 | width: auto; 191 | } 192 | .fullwidth-top-middle-box { 193 | width: auto; 194 | } 195 | } 196 | 197 | @media only screen and (max-width: 414px) { 198 | .fieldWrap { 199 | width: 70%; 200 | } 201 | .button_mediumSize { 202 | width: 28%; 203 | } 204 | } 205 | 206 | @media only screen and (max-width: 320px) { 207 | .preview { 208 | padding-right: 5px; 209 | } 210 | .fieldWrap { 211 | width: 60%; 212 | } 213 | .button_mediumSize { 214 | width: 37%; 215 | } 216 | } 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /src/app/components/newsletter-card/newsletter-card.component.html: -------------------------------------------------------------------------------- 1 |
2 | 27 |
-------------------------------------------------------------------------------- /src/app/components/newsletter-card/newsletter-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NewsletterCardComponent } from './newsletter-card.component'; 4 | 5 | describe('NewsletterCardComponent', () => { 6 | let component: NewsletterCardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ NewsletterCardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(NewsletterCardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/newsletter-card/newsletter-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-newsletter-card', 5 | templateUrl: './newsletter-card.component.html', 6 | styleUrls: ['./newsletter-card.component.css'] 7 | }) 8 | export class NewsletterCardComponent { 9 | @Output() subClickEvent = new EventEmitter(); 10 | 11 | onSubscribe(email: string) { 12 | this.subClickEvent.emit(email); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/components/project-card/project-card.component.css: -------------------------------------------------------------------------------- 1 | .post-item { 2 | overflow: hidden; 3 | position: relative; 4 | border-bottom: 1px solid #e8e8e8; 5 | padding: 20px 20px; 6 | background-color: transparent; 7 | display: flex; 8 | display: -ms-flexbox; 9 | align-items: center; 10 | cursor: pointer; 11 | } 12 | 13 | .post-item:hover { 14 | background-color: #f9f9f9; 15 | } 16 | 17 | .post-item:hover .hidden-icon, 18 | .post-item:hover .vote-button-hidden, 19 | .post-item:hover .vote-button-ion, 20 | .post-item:hover .vote-button-chat { 21 | background-color: white; 22 | } 23 | 24 | .thumbsnail-bottom { 25 | margin-right: 20px; 26 | position: relative; 27 | width: 80px; 28 | height: 80px; 29 | border-radius: 3px; 30 | vertical-align: bottom; 31 | border: none; 32 | cursor: pointer; 33 | } 34 | 35 | .content-post-bottom { 36 | display: -ms-flexbox; 37 | display: flex; 38 | -ms-flex-direction: column; 39 | flex-direction: column; 40 | -ms-flex-pack: center; 41 | justify-content: center; 42 | margin-top: -20px; 43 | cursor: pointer; 44 | } 45 | 46 | .title-bottom { 47 | font-size: 20px; 48 | font-weight: 300; 49 | letter-spacing: .4px; 50 | line-height: 24px; 51 | color: #000; 52 | cursor: pointer; 53 | } 54 | 55 | .text-post { 56 | font-size: 13px; 57 | font-weight: 400; 58 | line-height: 18px; 59 | color: #999; 60 | margin: 5px 0; 61 | cursor: pointer; 62 | overflow: hidden; 63 | height: 39px; 64 | } 65 | 66 | .meta { 67 | -ms-flex-align: center; 68 | align-items: center; 69 | -ms-flex-pack: justify; 70 | justify-content: space-between; 71 | position: absolute; 72 | display: -ms-flexbox; 73 | display: flex; 74 | height: 24px; 75 | bottom: 20px; 76 | } 77 | 78 | .topic { 79 | display: flex; 80 | display: -ms-flexbox; 81 | -ms-flex-align: center; 82 | align-items: center; 83 | -ms-flex-negative: 0; 84 | flex-shrink: 0; 85 | white-space: nowrap; 86 | cursor: pointer; 87 | } 88 | 89 | .hidden-icon { 90 | background-color: transparent; 91 | display: -ms-flexbox; 92 | -ms-flex-align: center; 93 | align-items: center !important; 94 | -ms-flex-pack: distribute; 95 | justify-content: space-around !important; 96 | border-radius: 3px; 97 | height: 24px; 98 | width: 22px; 99 | color: #999; 100 | fill: #999; 101 | border: 1px solid #e8e8e8; 102 | box-sizing: border-box; 103 | outline: 0; 104 | display: -ms-inline-flexbox; 105 | display: inline-flex; 106 | -ms-flex-pack: center; 107 | justify-content: center !important; 108 | text-align: center; 109 | text-decoration: none !important; 110 | margin-left: 126px; 111 | display: none; 112 | padding: 1px; 113 | cursor: pointer; 114 | } 115 | 116 | .button_smallSize { 117 | background: #e8e8e8; 118 | border-color: #e8e8e8; 119 | color: #999; 120 | fill: #999; 121 | height: 24px; 122 | padding: 0 8px; 123 | border-radius: 3px; 124 | border: 1px solid transparent; 125 | box-sizing: border-box; 126 | outline: 0; 127 | display: -ms-inline-flexbox; 128 | display: inline-flex; 129 | -ms-flex-align: center; 130 | align-items: center; 131 | -ms-flex-pack: center; 132 | justify-content: center; 133 | text-align: center; 134 | text-decoration: none !important; 135 | font-size: 10px; 136 | font-weight: 400; 137 | letter-spacing: .6px; 138 | line-height: 16px; 139 | text-transform: uppercase; 140 | cursor: pointer; 141 | } 142 | 143 | .button-Container { 144 | -ms-flex-align: center; 145 | align-items: center; 146 | display: -ms-inline-flexbox; 147 | display: inline-flex; 148 | -ms-flex-wrap: nowrap; 149 | flex-wrap: nowrap; 150 | -ms-flex-pack: center; 151 | justify-content: center; 152 | width: inherit; 153 | cursor: pointer; 154 | } 155 | 156 | .more-topics { 157 | margin-left: .35em; 158 | line-height: 13px; 159 | cursor: pointer; 160 | font-size: 10px; 161 | font-weight: 400; 162 | letter-spacing: .6px; 163 | line-height: 16px; 164 | text-transform: uppercase; 165 | color: #999; 166 | text-decoration: none; 167 | cursor: pointer; 168 | } 169 | 170 | .more-topics:hover { 171 | text-decoration: underline; 172 | } 173 | 174 | .action-buttons { 175 | display: -ms-flexbox; 176 | display: flex; 177 | -ms-flex-negative: 0; 178 | flex-shrink: 0; 179 | margin-left: auto; 180 | margin-right: 10px; 181 | margin-top: 15px; 182 | } 183 | 184 | .action-buttons:hover { 185 | z-index: 555555 !important; 186 | } 187 | 188 | .vote-button-hidden { 189 | display: flex !important; 190 | margin-left: 5px; 191 | font-size: 12px; 192 | height: 24px; 193 | background-color: transparent; 194 | border: 1px solid #e8e8e8; 195 | color: #999; 196 | display: none !important; 197 | cursor: poiner; 198 | } 199 | 200 | /*.vote-button-hidden:hover, 201 | .vote-button-ion:hover,*/ 202 | .vote-button-chat:hover, 203 | .hidden-icon:hover { 204 | background-color: #a8a8a8; 205 | } 206 | 207 | .post-item:hover .hidden-icon, 208 | .post-item:hover .vote-button-hidden { 209 | display: flex !important; 210 | } 211 | 212 | 213 | .vote-button-ion { 214 | display: flex; 215 | display: flex; 216 | margin-left: 6px; 217 | font-size: 12px; 218 | height: 24px; 219 | border: 1px solid #e8e8e8; 220 | background-color: transparent; 221 | color: #000; 222 | cursor: pointer; 223 | } 224 | 225 | .active { 226 | background-color: #da552f !important; 227 | color: white; 228 | } 229 | 230 | 231 | .vote-button-chat{ 232 | display: flex; 233 | margin-left: 6px; 234 | font-size: 12px; 235 | height: 24px; 236 | background-color: transparent; 237 | border: 1px solid #e8e8e8; 238 | color: #999; 239 | cursor: pointer; 240 | } 241 | 242 | a { 243 | text-decoration: none; 244 | } 245 | 246 | 247 | @media only screen and (max-width: 480px) { 248 | .meta { 249 | position: static; 250 | } 251 | .action-buttons { 252 | margin: 0; 253 | } 254 | } 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /src/app/components/project-card/project-card.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
{{project.name}}
8 |
{{project.description | trimText}}
9 | 10 | 11 |
12 | 13 | 17 | 18 |
19 | 20 | 22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 | 33 | 37 |
38 |
39 |
40 |
-------------------------------------------------------------------------------- /src/app/components/project-card/project-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './../../models/project'; 2 | import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'app-project-card', 6 | templateUrl: './project-card.component.html', 7 | styleUrls: ['./project-card.component.css'], 8 | changeDetection: ChangeDetectionStrategy.OnPush 9 | }) 10 | export class ProjectCardComponent implements OnInit { 11 | @Input() project: Project = null; 12 | @Input() isUpvotedByCurrUser: boolean; 13 | @Output() toggleUpvoteClick = new EventEmitter(); 14 | action: string; 15 | 16 | get id() { 17 | return this.project.id; 18 | } 19 | 20 | constructor() { } 21 | 22 | 23 | ngOnInit() { 24 | } 25 | 26 | onToggleUpvote() { 27 | this.action = this.isUpvotedByCurrUser ? 'removote' : 'upvote'; 28 | this.toggleUpvoteClick 29 | .emit({ project: this.project, action: this.action }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/components/project-edit/project-edit.component.css: -------------------------------------------------------------------------------- 1 | .section-form { 2 | width: 37em; 3 | margin: 0 auto; 4 | background-color: #f9f9f9; 5 | padding: 1em; 6 | } 7 | .section-form .intro { 8 | margin-bottom: 2em; 9 | } 10 | .section-form h1 { 11 | text-align: center; 12 | font-size: 1.6em; 13 | margin-bottom: 0.6em; 14 | } 15 | .section-form h2 { 16 | text-align: center; 17 | font-size: 1.23077em; 18 | } 19 | 20 | input[type=text] { 21 | height: 45px; 22 | } 23 | 24 | input[type=text], 25 | textarea { 26 | width: 100%; 27 | margin-bottom: 1em; 28 | font-size: .9em; 29 | color: #cfcece; 30 | font-weight: 300; 31 | padding: .5em 1em; 32 | border: none; 33 | color: #555555; 34 | font-weight: 300; 35 | } 36 | 37 | input[type=button] { 38 | cursor: pointer; 39 | margin: 0; 40 | padding: .5em 2em; 41 | background: #ea532a; 42 | border-color: #ea532a; 43 | border: none; 44 | color: #fff; 45 | font-size: 1em; 46 | font-weight: 400; 47 | } 48 | 49 | input[type=button]:disabled { 50 | background: grey 51 | } 52 | 53 | .value-err { 54 | height: 18px; 55 | padding-top: 2px; 56 | color: #ea6c6c ; 57 | } -------------------------------------------------------------------------------- /src/app/components/project-edit/project-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Create New Project

5 |
6 | * This is a mandatory field 9 | 13 | 14 | 18 | 19 | * This is a mandatory field 22 | 27 | 28 | 29 | Please enter a valid Url 33 | 37 | 38 | Please enter a valid Url 42 | 46 | 47 | * This is a mandatory field 50 | 54 | 55 | 59 | 60 | * This is a mandatory field 63 | 67 | 68 | 73 |
74 |
-------------------------------------------------------------------------------- /src/app/components/project-edit/project-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './../../models/project'; 2 | import { AppState, getSelectedProject } from './../../reducers/index'; 3 | import { Store } from '@ngrx/store'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import { ActivatedRoute, Router } from '@angular/router'; 6 | import { ToastyNotifierService } from './../../services/toasty-notifier.service'; 7 | import { Component, OnInit, OnDestroy } from '@angular/core'; 8 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 9 | import { UrlValidator } from './../../Validators/url'; 10 | import { ProjectService } from './../../services/project.service'; 11 | import { Subscription } from 'rxjs/Subscription'; 12 | import { ProjectActions } from '../../actions/project.actions'; 13 | 14 | @Component({ 15 | selector: 'app-project-edit', 16 | templateUrl: './project-edit.component.html', 17 | styleUrls: ['./project-edit.component.css'] 18 | }) 19 | export class ProjectEditComponent implements OnInit, OnDestroy { 20 | actionsSubscription: Subscription; 21 | projectForm: FormGroup; 22 | formSubmit: Boolean = false; 23 | projectId: string; 24 | project$: Observable; 25 | isNewProject: boolean; 26 | project: Project = null; 27 | 28 | constructor( 29 | private route: ActivatedRoute, 30 | private store: Store, 31 | private router: Router, 32 | private projectActions: ProjectActions, 33 | private projectService: ProjectService, 34 | private fb: FormBuilder, 35 | private toasterService: ToastyNotifierService) { 36 | 37 | this.project$ = this.store.select(getSelectedProject); 38 | this.isNewProject = this.checkIfNewProject(); 39 | 40 | if (!this.isNewProject) { 41 | this.project$.subscribe(project => this.project = project); 42 | this.actionsSubscription = this.route.params.subscribe( 43 | (params: any) => { 44 | this.projectId = params['id']; 45 | this.store.dispatch(this.projectActions.selectProject(this.projectId)); 46 | } 47 | ); 48 | 49 | } 50 | this.initForm(); 51 | } 52 | 53 | ngOnInit() { 54 | } 55 | 56 | initForm() { 57 | if (this.isNewProject) { 58 | this.projectForm = this.initNewProjectForm(); 59 | } else { 60 | this.projectForm = this.initExistingProjectForm(); 61 | }; 62 | } 63 | 64 | 65 | private checkIfNewProject() { 66 | return (this.router.url === '/admin/new-project') ? true : false; 67 | } 68 | 69 | 70 | initExistingProjectForm() { 71 | return this.fb.group({ 72 | 'name': [this.project.name, Validators.required], 73 | 'git_url': [this.project.git_url, Validators.compose([Validators.required, UrlValidator])], 74 | 'description': [this.project.description, Validators.required], 75 | 'demo_url': [this.project.demo_url, Validators.compose([Validators.required, UrlValidator])], 76 | 'image_url': [this.project.image_url, Validators.required], 77 | 'author_name': [this.project.author_name, Validators.required], 78 | 'caption': [this.project.caption], 79 | 'twitter_id': [this.project.twitter_id], 80 | 'approved': [true], 81 | 'created_at': [new Date().toString()], 82 | 'upvotes': [this.project.upvotes] 83 | }); 84 | } 85 | 86 | initNewProjectForm() { 87 | return this.fb.group({ 88 | 'name': ['', Validators.required], 89 | 'git_url': ['', Validators.compose([Validators.required, UrlValidator])], 90 | 'description': ['', Validators.required], 91 | 'demo_url': ['', Validators.compose([Validators.required, UrlValidator])], 92 | 'image_url': ['', Validators.required], 93 | 'author_name': ['', Validators.required], 94 | 'caption': [''], 95 | 'twitter_id': [''], 96 | 'approved': [true], 97 | 'created_at': [new Date().toString()], 98 | 'upvotes': [0] 99 | }); 100 | } 101 | 102 | onSubmit() { 103 | this.formSubmit = true; 104 | 105 | if (this.isNewProject) { 106 | this.projectService.saveNewProject(this.projectForm.value).then(() => { 107 | this.toasterService 108 | .pop({ result: 'success', msg: 'Your Project is Successfully Saved' }); 109 | 110 | this.projectForm.reset(); 111 | return; 112 | }, 113 | (err: Error) => { 114 | return this.toasterService 115 | .pop({ result: 'error', msg: err.message }); 116 | }); 117 | } else { 118 | this.projectService.updateProject(this.project.$key, this.projectForm.value).then(() => { 119 | this.toasterService 120 | .pop({ result: 'success', msg: 'Your Project is Successfully Updated' }); 121 | return; 122 | }, 123 | (err: Error) => { 124 | return this.toasterService 125 | .pop({ result: 'error', msg: err.message }); 126 | }); 127 | } 128 | 129 | } 130 | 131 | ngOnDestroy() { 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/app/components/project/project.component.css: -------------------------------------------------------------------------------- 1 | .main { 2 | background-color: #fff; 3 | border: 1px solid #e8e8e8; 4 | min-width: 0; 5 | width:702px; 6 | } 7 | 8 | .top { 9 | position: relative; 10 | } 11 | 12 | .container-gallery { 13 | margin-bottom: 20px; 14 | } 15 | 16 | .canvas { 17 | height: 360px; 18 | overflow: hidden; 19 | } 20 | 21 | .canvas ol { 22 | display: flex; 23 | list-style: none; 24 | margin: 0; 25 | padding: 0; 26 | overflow-x: scroll; 27 | overflow-y: hidden; 28 | position: relative; 29 | } 30 | 31 | .canvas li { 32 | height: 360px; 33 | display: -ms-flexbox; 34 | display: flex; 35 | -ms-flex: 0 0 auto; 36 | flex: 0 0 auto; 37 | margin: 0 10px 20px 0; 38 | } 39 | 40 | 41 | .container-text { 42 | position: relative; 43 | height: 360px; 44 | width: 202.759px 45 | } 46 | 47 | .thumbsnails { 48 | margin-left: 40px !important; 49 | display: -ms-flexbox !important; 50 | display: flex !important; 51 | } 52 | 53 | .thumbnail_u { 54 | margin-top: 10px; 55 | cursor: pointer; 56 | border: 1px solid #999; 57 | margin-right: 10px; 58 | } 59 | 60 | 61 | .timestamp { 62 | bottom: 0; 63 | padding: 5px; 64 | position: absolute; 65 | right: 30px; 66 | text-align: right; 67 | } 68 | 69 | .secondaryText-base { 70 | font-size: 12px; 71 | font-weight: 400; 72 | letter-spacing: .6px; 73 | line-height: 16px; 74 | text-transform: uppercase; 75 | color: #999; 76 | } 77 | 78 | .information-section { 79 | margin: 30px 19px; 80 | flex-flow: row nowrap; 81 | -ms-flex-flow: row nowrap; 82 | display: -ms-flexbox; 83 | display: flex; 84 | position: relative; 85 | } 86 | 87 | .thumbnail-4 { 88 | margin-right: 20px; 89 | min-width: 120px; 90 | height: 128px; 91 | } 92 | 93 | .thumbnail-4 img { 94 | object-fit: cover; 95 | } 96 | 97 | .inner_content { 98 | padding: 0 10px; 99 | background-color: #f1f1f1; 100 | -ms-flex-flow: column nowrap; 101 | flex-flow: column nowrap; 102 | -ms-flex: 1 1 auto; 103 | flex: 1 1 auto; 104 | -ms-flex-pack: justify; 105 | justify-content: space-between; 106 | } 107 | 108 | .headline_content { 109 | margin: 0 0 10px; 110 | } 111 | 112 | .headline-base { 113 | font-size: 28px; 114 | font-weight: 600; 115 | line-height: 34px; 116 | color: #000; 117 | margin: 0; 118 | } 119 | 120 | .tagline-heading { 121 | flex: 1 1 auto; 122 | margin: 0 10px 10px 1px; 123 | font-size: 16px; 124 | line-height: 20px; 125 | font-weight: 400; 126 | color: #999; 127 | margin-bottom: 21px; 128 | } 129 | 130 | .topicWrap { 131 | display: inline-block; 132 | margin: 0 15px 10px 0; 133 | white-space: nowrap; 134 | position: relative; 135 | } 136 | 137 | .button_smallSize{ 138 | background: #e8e8e8; 139 | border-color: #e8e8e8; 140 | color: #999; 141 | height: 24px; 142 | padding: 0 8px !important; 143 | border-radius: 3px; 144 | border: 1px solid transparent; 145 | box-sizing: border-box; 146 | -ms-flex-align: center; 147 | align-items: center; 148 | -ms-flex-pack: center; 149 | text-align: center; 150 | text-decoration: none!important; 151 | font-size: 12px; 152 | font-weight: 400; 153 | letter-spacing: .6px; 154 | line-height: 16px; 155 | text-transform: uppercase; 156 | outline: 0; 157 | display: -ms-inline-flexbox; 158 | display: inline-flex; 159 | justify-content: center; 160 | text-align: center; 161 | text-decoration: none!important; 162 | } 163 | 164 | .action-section { 165 | border-top: 1px solid #e8e8e8; 166 | border-bottom: 1px solid #e8e8e8; 167 | padding: 20px 0; 168 | } 169 | 170 | .action-inner { 171 | display: flex; 172 | margin: 0 40px; 173 | } 174 | 175 | .action-section a { 176 | margin: 0 10px 0 0; 177 | } 178 | 179 | .comboButton { 180 | display: inline-block; 181 | position: relative; 182 | margin: 0 10px 0 0; 183 | } 184 | 185 | .secondaryBoldText { 186 | font-weight: 600; 187 | font-size: 12px; 188 | letter-spacing: .6px; 189 | line-height: 16px; 190 | text-transform: uppercase; 191 | } 192 | 193 | .inactiveLink { 194 | background: #da552f; 195 | display: inline-block; 196 | height: 34px; 197 | line-height: 34px; 198 | box-sizing: border-box; 199 | border-radius: 3px 0 0 3px; 200 | } 201 | 202 | .inactiveLink a { 203 | padding: 0 12px; 204 | color: #fff; 205 | } 206 | 207 | .inactivePopoverToggle { 208 | border-radius: 0 3px 3px 0; 209 | border-left: 1px solid #fff; 210 | /*cursor: pointer;*/ 211 | text-align: center; 212 | width: 28px; 213 | background: #da552f; 214 | color: #fff; 215 | display: inline-block; 216 | height: 34px; 217 | line-height: 34px; 218 | box-sizing: border-box; 219 | margin-left: -4px; 220 | } 221 | 222 | .ion-arrow-down-b { 223 | font-size: 10px; 224 | } 225 | 226 | .button_mediumSize { 227 | background: #fff; 228 | color: #000; 229 | height: 34px; 230 | padding: 0 13px; 231 | border-radius: 3px; 232 | border: 1px solid #e8e8e8; 233 | box-sizing: border-box; 234 | -ms-flex-pack: center; 235 | justify-content: center; 236 | text-align: center; 237 | text-decoration: none!important; 238 | font-weight: 600; 239 | font-size: 12px; 240 | letter-spacing: .6px; 241 | line-height: 16px; 242 | text-transform: uppercase; 243 | } 244 | 245 | .button_mediumSize:hover { 246 | background-color: #f1f1f1; 247 | } 248 | 249 | .ion-android-open { 250 | font-size: 15px; 251 | } 252 | 253 | a { 254 | text-decoration: none; 255 | } 256 | 257 | .active { 258 | background-color: #da552f !important; 259 | color: white; 260 | } 261 | 262 | share-buttons { 263 | float: left; 264 | padding: 10px; 265 | width: 155px; 266 | padding: 0px; 267 | } -------------------------------------------------------------------------------- /src/app/components/project/project.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 12 | 13 | 14 | 19 | 20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 |

{{ project.name }}

35 |
36 |
37 |

{{ project.description }}

38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 |
AngularHunt
46 |
47 |
48 |
49 |
50 | 51 |
52 |
53 | 54 | 55 | 56 | 57 |
58 |
59 |
60 | 61 | 62 | 63 | visit site 64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | 106 |
107 |
108 | 109 |
-------------------------------------------------------------------------------- /src/app/components/project/project.component.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './../../models/project'; 2 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'app-project', 6 | templateUrl: './project.component.html', 7 | styleUrls: ['./project.component.css'] 8 | }) 9 | export class ProjectComponent { 10 | @Input() project: Project = null; 11 | @Input() isUpvotedByCurrUser: boolean; 12 | @Output() toggleUpvoteClick = new EventEmitter(); 13 | productTags: string[] = ['angularhunt', 'products']; 14 | action: string; 15 | 16 | onToggleUpvote() { 17 | this.action = this.isUpvotedByCurrUser ? 'removeVote' : 'upvote'; 18 | this.toggleUpvoteClick.emit({ project: this.project, action: this.action }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/components/shared/ad/ad.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/angularhunt/b33a4139d579b08673022afb42db40d9a160c128/src/app/components/shared/ad/ad.component.css -------------------------------------------------------------------------------- /src/app/components/shared/ad/ad.component.html: -------------------------------------------------------------------------------- 1 |

2 | ad works! 3 |

4 | -------------------------------------------------------------------------------- /src/app/components/shared/ad/ad.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AdComponent } from './ad.component'; 4 | 5 | describe('AdComponent', () => { 6 | let component: AdComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AdComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AdComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/shared/ad/ad.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, AfterViewInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-ad', 5 | template: ` 6 |
7 | 12 | 13 |
14 | `, 15 | styleUrls: ['./ad.component.css'] 16 | }) 17 | export class AdComponent implements AfterViewInit { 18 | 19 | constructor() { } 20 | 21 | ngAfterViewInit() { 22 | setTimeout(() => { 23 | try { 24 | (window['adsbygoogle'] = window['adsbygoogle'] || []).push({}); 25 | }catch (e) { 26 | console.error('error'); 27 | } 28 | }, 2000); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/app/components/shared/footer/footer.component.css: -------------------------------------------------------------------------------- 1 | .site-footer { 2 | background: #201e1c; 3 | color: #7b7a7a; 4 | font-size: 16px; 5 | overflow: hidden; 6 | /*padding: 0 0 5%;*/ 7 | position: relative; 8 | text-align: center; 9 | } 10 | 11 | .wrap { 12 | margin: 0 auto; 13 | max-width: 100%; 14 | } 15 | 16 | .site-footer .social-icons { 17 | /*padding: 5% 0;*/ 18 | } 19 | 20 | .site-footer .social-icons a { 21 | border: none; 22 | border-radius: 3px; 23 | color: #7b7a7a; 24 | display: inline-block; 25 | font-size: 20px; 26 | text-decoration: none; 27 | margin: 0; 28 | /*padding: 4% 5%;*/ 29 | cursor: auto; 30 | } 31 | 32 | .creds a { 33 | text-decoration: none; 34 | } -------------------------------------------------------------------------------- /src/app/components/shared/footer/footer.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
9 | 10 |
11 |
12 |
-------------------------------------------------------------------------------- /src/app/components/shared/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.css'] 7 | }) 8 | export class FooterComponent { 9 | } 10 | -------------------------------------------------------------------------------- /src/app/components/shared/header/header.component.css: -------------------------------------------------------------------------------- 1 | .header { 2 | background: #fff; 3 | border-bottom: 1px solid #e8e8e8; 4 | position: relative; 5 | } 6 | .constraintWidth { 7 | max-width: 1100px; 8 | padding: 0 15px; 9 | box-sizing: border-box; 10 | margin: 0 auto; 11 | min-width: 320px; 12 | 13 | } 14 | .content { 15 | display: -ms-flexbox; 16 | display: flex; 17 | padding: 15px 0; 18 | position: relative; 19 | height: 32px; 20 | justify-content: space-between; 21 | -ms-flex-pack: justify; 22 | } 23 | 24 | /*---------------LEFT SIDE--------------------*/ 25 | 26 | .leftside { 27 | ms-flex-align: center; 28 | align-items: center; 29 | -ms-flex-pack: start; 30 | justify-content: flex-start; 31 | display: -ms-flexbox; 32 | display: flex; 33 | } 34 | 35 | .logo { 36 | margin: 0 10px 0 0; 37 | } 38 | 39 | .name { 40 | display: block; 41 | position: relative; 42 | top: 1px; 43 | text-decoration: none; 44 | } 45 | 46 | .name strong { 47 | color: #da552f; 48 | display: block; 49 | font-size: 18px; 50 | font-weight: 600; 51 | line-height: 18px; 52 | } 53 | 54 | .name span { 55 | display: block; 56 | margin-top: 2px; 57 | font-size: 13px; 58 | color: #999; 59 | } 60 | 61 | /*-----------------CENTER PART----------------*/ 62 | 63 | .container { 64 | left: 50%; 65 | margin-left: -150px; 66 | position: absolute; 67 | top: 16px; 68 | width: 280px; 69 | } 70 | 71 | .form { 72 | position: relative; 73 | margin: 0 5px 0 0; 74 | width: 100%; 75 | } 76 | 77 | .text { 78 | font-size: 14px; 79 | font-weight: 400; 80 | line-height: 18px; 81 | color: #999; 82 | margin: 0; 83 | } 84 | 85 | .ion-ios-search { 86 | fill: #999; 87 | font-size: 18px; 88 | left: 8px; 89 | position: absolute; 90 | top: 8px; 91 | 92 | } 93 | 94 | .input { 95 | color: #999; 96 | border: 1px solid #e8e8e8; 97 | border-radius: 3px; 98 | background: #fff; 99 | height: 25px; 100 | padding: 2px 10px 0 32px; 101 | width: 100%; 102 | } 103 | 104 | /*-------------------------------RIGHT SIDE----------------------*/ 105 | 106 | .rightside { 107 | display: -ms-flexbox; 108 | display: flex; 109 | -ms-flex-align: center; 110 | align-items: center; 111 | -ms-flex-pack: end; 112 | justify-content: flex-end; 113 | } 114 | 115 | .ion-ios-more { 116 | text-align: center; 117 | -ms-flex-align: center; 118 | align-items: center; 119 | display: -ms-inline-flexbox; 120 | display: inline-flex; 121 | -ms-flex-pack: center; 122 | justify-content: center; 123 | height: 32px; 124 | width: 32px; 125 | } 126 | 127 | .divider { 128 | color: #e8e8e8; 129 | margin-left: 13px; 130 | border-left: 1px solid #e8e8e8; 131 | height: 13px; 132 | width: 3px; 133 | } 134 | 135 | .logged-in-user-area{ 136 | width: 200px; 137 | float: left; 138 | } 139 | 140 | 141 | .login-buttons { 142 | display: block; 143 | margin: 0 0 0 5px; 144 | } 145 | 146 | .login { 147 | height: 30px; 148 | margin-left: 5px; 149 | background: #fff; 150 | border-color: #e8e8e8; 151 | color: #000; 152 | fill: #000; 153 | padding: 0 13px; 154 | font-weight: 600; 155 | font-size: 12px; 156 | line-height: 16px; 157 | text-transform: uppercase; 158 | border-radius: 3px; 159 | border: 1px solid transparent; 160 | box-sizing: border-box; 161 | outline: 0; 162 | display: -ms-inline-flexbox; 163 | display: inline-flex; 164 | -ms-flex-align: center; 165 | align-items: center; 166 | -ms-flex-pack: center; 167 | justify-content: center; 168 | text-align: center; 169 | text-decoration: none !important; 170 | border: 1px solid #e8e8e8; 171 | 172 | } 173 | 174 | .signup { 175 | height: 30px; 176 | margin-left: 5px; 177 | background: #da552f; 178 | border-color: #da552f; 179 | color: #fff; 180 | fill: #fff; 181 | padding: 0 13px; 182 | border-radius: 3px; 183 | border: 1px solid transparent; 184 | box-sizing: border-box; 185 | outline: 0; 186 | display: -ms-inline-flexbox; 187 | display: inline-flex; 188 | -ms-flex-align: center; 189 | align-items: center; 190 | -ms-flex-pack: center; 191 | justify-content: center; 192 | text-align: center; 193 | text-decoration: none !important; 194 | font-weight: 600; 195 | font-size: 12px; 196 | /*font-weight: 400;*/ 197 | letter-spacing: .6px; 198 | line-height: 16px; 199 | text-transform: uppercase; 200 | } 201 | 202 | .signup:hover { 203 | background: #ea532a; 204 | border-color: #ea532a; 205 | cursor: pointer; 206 | } 207 | 208 | 209 | .buttoncontainer { 210 | -ms-flex-align: center; 211 | align-items: center; 212 | display: -ms-inline-flexbox; 213 | display: inline-flex; 214 | -ms-flex-wrap: nowrap; 215 | flex-wrap: nowrap; 216 | -ms-flex-pack: center; 217 | justify-content: center; 218 | width: inherit; 219 | } 220 | 221 | .overlayInactive { 222 | display: block; 223 | } 224 | 225 | .avatar { 226 | border-radius: 50%; 227 | height: 32px; 228 | width: 32px; 229 | } 230 | 231 | @media only screen and (min-width: 769px) { 232 | .mobile-header { 233 | display: none; 234 | } 235 | } 236 | 237 | @media only screen and (max-width: 768px) { 238 | 239 | .header{ 240 | display: none; 241 | } 242 | 243 | .mobile-header { 244 | background: #fff; 245 | border-bottom: 1px solid #e8e8e8; 246 | position: relative; 247 | max-width: 768px; 248 | min-width: 320px; 249 | padding: 15px 10px; 250 | } 251 | 252 | .mobile-content { 253 | display: flex; 254 | height: 32px; 255 | position: relative; 256 | } 257 | 258 | .ion-ios-search { 259 | color: #999; 260 | height: 14px; 261 | left: 8px; 262 | position: absolute; 263 | top: 8px; 264 | width: 14px; 265 | } 266 | 267 | .mobile-logo { 268 | position: absolute; 269 | left: 50%; 270 | margin-left: -17px; 271 | } 272 | 273 | .mobile-icon { 274 | height: 32px; 275 | width: 32px; 276 | position: absolute; 277 | right: 0; 278 | top: 7px; 279 | } 280 | 281 | .ion-ios-search-strong{ 282 | color: #999; 283 | font-size: 20px; 284 | height: 14px; 285 | position: absolute; 286 | top: 8px; 287 | width: 14px; 288 | padding-left: 14px; 289 | } 290 | } 291 | 292 | @media only screen and (max-width: 320px) { 293 | .mobile-header { 294 | padding: 10px 5px; 295 | } 296 | } 297 | 298 | 299 | 300 | -------------------------------------------------------------------------------- /src/app/components/shared/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 14 | 15 |
16 |
17 | 21 |
22 |
23 | 24 |
25 | 28 | 29 | 34 | 35 |
36 | 39 | 40 |
41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 | 53 |
54 | 57 |
58 | 59 |
60 |
61 |
62 |
-------------------------------------------------------------------------------- /src/app/components/shared/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { ProjectService } from './../../../services/project.service'; 2 | import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy} from '@angular/core'; 3 | import { User } from '../../../models'; 4 | 5 | @Component({ 6 | selector: 'app-header', 7 | templateUrl: './header.component.html', 8 | styleUrls: ['./header.component.css'], 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | }) 11 | export class HeaderComponent { 12 | @Output() logoutClicked = new EventEmitter(); 13 | @Input() user: User = null; 14 | formLink = 'https://docs.google.com/forms/d/1_VG72BTaHMR4hd4P5nbONi4MThLnnbYRDRcgmKfmSrs'; 15 | constructor(private projectService: ProjectService) { } 16 | 17 | logout() { 18 | this.logoutClicked.emit(); 19 | } 20 | 21 | uploaData() { 22 | this.projectService.sendData(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/app/components/shared/header/profile-dropdown/profile-dropdown.component.css: -------------------------------------------------------------------------------- 1 | .fa { 2 | font-size: 2rem; 3 | } 4 | 5 | .nav .open>a, .nav .open>a:focus, .nav .open>a:hover { 6 | background-color: transparent; 7 | } 8 | 9 | .cart { 10 | cursor: pointer; 11 | } 12 | 13 | .avatar { 14 | border-radius: 50%; 15 | height: 32px; 16 | width: 32px; 17 | } 18 | 19 | a { 20 | background-color: transparent; 21 | } 22 | a:active, 23 | a:hover { 24 | outline: 0; 25 | cursor: pointer; 26 | } 27 | abbr[title] { 28 | border-bottom: 1px dotted; 29 | } 30 | b, 31 | strong { 32 | font-weight: bold; 33 | } 34 | dfn { 35 | font-style: italic; 36 | } 37 | h1 { 38 | font-size: 2em; 39 | margin: 0.67em 0; 40 | } 41 | mark { 42 | background: #ff0; 43 | color: #000; 44 | } 45 | small { 46 | font-size: 80%; 47 | } 48 | sub, 49 | sup { 50 | font-size: 75%; 51 | line-height: 0; 52 | position: relative; 53 | vertical-align: baseline; 54 | } 55 | sup { 56 | top: -0.5em; 57 | } 58 | sub { 59 | bottom: -0.25em; 60 | } 61 | img { 62 | border: 0; 63 | } 64 | svg:not(:root) { 65 | overflow: hidden; 66 | } 67 | figure { 68 | margin: 1em 40px; 69 | } 70 | hr { 71 | -webkit-box-sizing: content-box; 72 | -moz-box-sizing: content-box; 73 | box-sizing: content-box; 74 | height: 0; 75 | } 76 | pre { 77 | overflow: auto; 78 | } 79 | 80 | legend { 81 | border: 0; 82 | padding: 0; 83 | } 84 | textarea { 85 | overflow: auto; 86 | } 87 | optgroup { 88 | font-weight: bold; 89 | } 90 | table { 91 | border-collapse: collapse; 92 | border-spacing: 0; 93 | } 94 | td, 95 | th { 96 | padding: 0; 97 | } 98 | * { 99 | -webkit-box-sizing: border-box; 100 | -moz-box-sizing: border-box; 101 | box-sizing: border-box; 102 | } 103 | *:before, 104 | *:after { 105 | -webkit-box-sizing: border-box; 106 | -moz-box-sizing: border-box; 107 | box-sizing: border-box; 108 | } 109 | 110 | a { 111 | color: #337ab7; 112 | text-decoration: none; 113 | } 114 | a:hover, 115 | a:focus { 116 | color: #23527c; 117 | text-decoration: underline; 118 | } 119 | a:focus { 120 | outline: 5px auto -webkit-focus-ring-color; 121 | outline-offset: -2px; 122 | } 123 | figure { 124 | margin: 0; 125 | } 126 | img { 127 | vertical-align: middle; 128 | } 129 | .img-responsive { 130 | display: block; 131 | max-width: 100%; 132 | height: auto; 133 | } 134 | .img-rounded { 135 | border-radius: 6px; 136 | } 137 | .img-thumbnail { 138 | padding: 4px; 139 | line-height: 1.42857143; 140 | background-color: #ffffff; 141 | border: 1px solid #dddddd; 142 | border-radius: 4px; 143 | -webkit-transition: all 0.2s ease-in-out; 144 | -o-transition: all 0.2s ease-in-out; 145 | transition: all 0.2s ease-in-out; 146 | display: inline-block; 147 | max-width: 100%; 148 | height: auto; 149 | } 150 | .img-circle { 151 | border-radius: 50%; 152 | } 153 | hr { 154 | margin-top: 20px; 155 | margin-bottom: 20px; 156 | border: 0; 157 | border-top: 1px solid #eeeeee; 158 | } 159 | .sr-only { 160 | position: absolute; 161 | width: 1px; 162 | height: 1px; 163 | margin: -1px; 164 | padding: 0; 165 | overflow: hidden; 166 | clip: rect(0, 0, 0, 0); 167 | border: 0; 168 | } 169 | .sr-only-focusable:active, 170 | .sr-only-focusable:focus { 171 | position: static; 172 | width: auto; 173 | height: auto; 174 | margin: 0; 175 | overflow: visible; 176 | clip: auto; 177 | } 178 | [role="button"] { 179 | cursor: pointer; 180 | } 181 | .caret { 182 | display: inline-block; 183 | width: 0; 184 | height: 0; 185 | margin-left: 2px; 186 | vertical-align: middle; 187 | border-top: 4px dashed; 188 | border-top: 4px solid \9; 189 | border-right: 4px solid transparent; 190 | border-left: 4px solid transparent; 191 | } 192 | .dropup, 193 | .dropdown { 194 | position: relative; 195 | } 196 | .dropdown-toggle:focus { 197 | outline: 0; 198 | } 199 | .dropdown-menu { 200 | position: absolute; 201 | top: 100%; 202 | left: 0; 203 | z-index: 1000; 204 | display: none; 205 | float: left; 206 | min-width: 160px; 207 | padding: 5px 0; 208 | margin: 2px 0 0; 209 | list-style: none; 210 | font-size: 14px; 211 | text-align: left; 212 | background-color: #ffffff; 213 | border: 1px solid #cccccc; 214 | border: 1px solid rgba(0, 0, 0, 0.15); 215 | border-radius: 4px; 216 | -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 217 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 218 | -webkit-background-clip: padding-box; 219 | background-clip: padding-box; 220 | } 221 | .dropdown-menu.pull-right { 222 | right: 0; 223 | left: auto; 224 | } 225 | .dropdown-menu .divider { 226 | height: 1px; 227 | margin: 9px 0; 228 | overflow: hidden; 229 | background-color: #e5e5e5; 230 | } 231 | .dropdown-menu > li > a { 232 | display: block; 233 | padding: 3px 20px; 234 | clear: both; 235 | font-weight: normal; 236 | line-height: 1.42857143; 237 | color: #333333; 238 | white-space: nowrap; 239 | } 240 | .dropdown-menu > li > a:hover, 241 | .dropdown-menu > li > a:focus { 242 | text-decoration: none; 243 | color: #262626; 244 | background-color: #f5f5f5; 245 | } 246 | .dropdown-menu > .active > a, 247 | .dropdown-menu > .active > a:hover, 248 | .dropdown-menu > .active > a:focus { 249 | color: #ffffff; 250 | text-decoration: none; 251 | outline: 0; 252 | background-color: #337ab7; 253 | } 254 | .dropdown-menu > .disabled > a, 255 | .dropdown-menu > .disabled > a:hover, 256 | .dropdown-menu > .disabled > a:focus { 257 | color: #777777; 258 | } 259 | .dropdown-menu > .disabled > a:hover, 260 | .dropdown-menu > .disabled > a:focus { 261 | text-decoration: none; 262 | background-color: transparent; 263 | background-image: none; 264 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 265 | cursor: not-allowed; 266 | } 267 | .open > .dropdown-menu { 268 | display: block; 269 | } 270 | .open > a { 271 | outline: 0; 272 | } 273 | .dropdown-menu-right { 274 | left: auto; 275 | right: 0; 276 | } 277 | .dropdown-menu-left { 278 | left: 0; 279 | right: auto; 280 | } 281 | .dropdown-header { 282 | display: block; 283 | padding: 3px 20px; 284 | font-size: 12px; 285 | line-height: 1.42857143; 286 | color: #777777; 287 | white-space: nowrap; 288 | } 289 | .dropdown-backdrop { 290 | position: fixed; 291 | left: 0; 292 | right: 0; 293 | bottom: 0; 294 | top: 0; 295 | z-index: 990; 296 | } 297 | .pull-right > .dropdown-menu { 298 | right: 0; 299 | left: auto; 300 | } 301 | .dropup .caret, 302 | .navbar-fixed-bottom .dropdown .caret { 303 | border-top: 0; 304 | border-bottom: 4px dashed; 305 | border-bottom: 4px solid \9; 306 | content: ""; 307 | } 308 | .dropup .dropdown-menu, 309 | .navbar-fixed-bottom .dropdown .dropdown-menu { 310 | top: auto; 311 | bottom: 100%; 312 | margin-bottom: 2px; 313 | } 314 | @media (min-width: 768px) { 315 | .navbar-right .dropdown-menu { 316 | left: auto; 317 | right: 0; 318 | } 319 | .navbar-right .dropdown-menu-left { 320 | left: 0; 321 | right: auto; 322 | } 323 | } 324 | .clearfix:before, 325 | .clearfix:after { 326 | content: " "; 327 | display: table; 328 | } 329 | .clearfix:after { 330 | clear: both; 331 | } 332 | .center-block { 333 | display: block; 334 | margin-left: auto; 335 | margin-right: auto; 336 | } 337 | .pull-right { 338 | float: right !important; 339 | } 340 | .pull-left { 341 | float: left !important; 342 | } 343 | .hide { 344 | display: none !important; 345 | } 346 | .show { 347 | display: block !important; 348 | } 349 | .invisible { 350 | visibility: hidden; 351 | } 352 | .text-hide { 353 | font: 0/0 a; 354 | color: transparent; 355 | text-shadow: none; 356 | background-color: transparent; 357 | border: 0; 358 | } 359 | .hidden { 360 | display: none !important; 361 | } 362 | .affix { 363 | position: fixed; 364 | } 365 | -------------------------------------------------------------------------------- /src/app/components/shared/header/profile-dropdown/profile-dropdown.component.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/components/shared/header/profile-dropdown/profile-dropdown.component.ts: -------------------------------------------------------------------------------- 1 | import { User } from './../../../../models/user'; 2 | import { Component,Input, Output, EventEmitter } from '@angular/core'; 3 | // import { AuthService } from '../../../core/services/auth.service'; 4 | 5 | @Component({ 6 | selector: 'app-profile-dropdown', 7 | templateUrl: './profile-dropdown.component.html', 8 | styleUrls: ['./profile-dropdown.component.css'] 9 | }) 10 | export class ProfileDropdownComponent { 11 | @Input() user: User = null; 12 | @Output() logoutClicked = new EventEmitter(); 13 | validEmailPattern: RegExp = /^[a-z][a-zA-Z0-9_.]*(\.[a-zA-Z][a-zA-Z0-9_.]*)?@aviabird.com/; 14 | 15 | 16 | isAdmin() { 17 | return this.validEmailPattern.test(this.user.email); 18 | } 19 | 20 | logout() { 21 | this.logoutClicked.emit(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/shared/modal/modal.component.css: -------------------------------------------------------------------------------- 1 | article, 2 | aside, 3 | details, 4 | figcaption, 5 | figure, 6 | footer, 7 | header, 8 | hgroup, 9 | main, 10 | menu, 11 | nav, 12 | section, 13 | summary { 14 | display: block; 15 | } 16 | audio, 17 | canvas, 18 | progress, 19 | video { 20 | display: inline-block; 21 | vertical-align: baseline; 22 | } 23 | audio:not([controls]) { 24 | display: none; 25 | height: 0; 26 | } 27 | [hidden], 28 | template { 29 | display: none; 30 | } 31 | a { 32 | background-color: transparent; 33 | } 34 | a:active, 35 | a:hover { 36 | outline: 0; 37 | } 38 | abbr[title] { 39 | border-bottom: 1px dotted; 40 | } 41 | b, 42 | strong { 43 | font-weight: bold; 44 | } 45 | dfn { 46 | font-style: italic; 47 | } 48 | h1 { 49 | font-size: 2em; 50 | margin: 0.67em 0; 51 | } 52 | mark { 53 | background: #ff0; 54 | color: #000; 55 | } 56 | small { 57 | font-size: 80%; 58 | } 59 | sub, 60 | sup { 61 | font-size: 75%; 62 | line-height: 0; 63 | position: relative; 64 | vertical-align: baseline; 65 | } 66 | sup { 67 | top: -0.5em; 68 | } 69 | sub { 70 | bottom: -0.25em; 71 | } 72 | img { 73 | border: 0; 74 | } 75 | svg:not(:root) { 76 | overflow: hidden; 77 | } 78 | figure { 79 | margin: 1em 40px; 80 | } 81 | hr { 82 | -webkit-box-sizing: content-box; 83 | -moz-box-sizing: content-box; 84 | box-sizing: content-box; 85 | height: 0; 86 | } 87 | pre { 88 | overflow: auto; 89 | } 90 | code, 91 | kbd, 92 | pre, 93 | samp { 94 | font-family: monospace, monospace; 95 | font-size: 1em; 96 | } 97 | button, 98 | input, 99 | optgroup, 100 | select, 101 | textarea { 102 | color: inherit; 103 | font: inherit; 104 | margin: 0; 105 | } 106 | button { 107 | overflow: visible; 108 | } 109 | button, 110 | select { 111 | text-transform: none; 112 | } 113 | button, 114 | html input[type="button"], 115 | input[type="reset"], 116 | input[type="submit"] { 117 | -webkit-appearance: button; 118 | cursor: pointer; 119 | } 120 | button[disabled], 121 | html input[disabled] { 122 | cursor: default; 123 | } 124 | button::-moz-focus-inner, 125 | input::-moz-focus-inner { 126 | border: 0; 127 | padding: 0; 128 | } 129 | input { 130 | line-height: normal; 131 | } 132 | input[type="checkbox"], 133 | input[type="radio"] { 134 | -webkit-box-sizing: border-box; 135 | -moz-box-sizing: border-box; 136 | box-sizing: border-box; 137 | padding: 0; 138 | } 139 | input[type="number"]::-webkit-inner-spin-button, 140 | input[type="number"]::-webkit-outer-spin-button { 141 | height: auto; 142 | } 143 | input[type="search"] { 144 | -webkit-appearance: textfield; 145 | -webkit-box-sizing: content-box; 146 | -moz-box-sizing: content-box; 147 | box-sizing: content-box; 148 | } 149 | input[type="search"]::-webkit-search-cancel-button, 150 | input[type="search"]::-webkit-search-decoration { 151 | -webkit-appearance: none; 152 | } 153 | fieldset { 154 | border: 1px solid #c0c0c0; 155 | margin: 0 2px; 156 | padding: 0.35em 0.625em 0.75em; 157 | } 158 | legend { 159 | border: 0; 160 | padding: 0; 161 | } 162 | textarea { 163 | overflow: auto; 164 | } 165 | optgroup { 166 | font-weight: bold; 167 | } 168 | table { 169 | border-collapse: collapse; 170 | border-spacing: 0; 171 | } 172 | td, 173 | th { 174 | padding: 0; 175 | } 176 | * { 177 | -webkit-box-sizing: border-box; 178 | -moz-box-sizing: border-box; 179 | box-sizing: border-box; 180 | } 181 | *:before, 182 | *:after { 183 | -webkit-box-sizing: border-box; 184 | -moz-box-sizing: border-box; 185 | box-sizing: border-box; 186 | } 187 | html { 188 | font-size: 10px; 189 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 190 | } 191 | body { 192 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 193 | font-size: 14px; 194 | line-height: 1.42857143; 195 | color: #333333; 196 | background-color: #ffffff; 197 | } 198 | input, 199 | button, 200 | select, 201 | textarea { 202 | font-family: inherit; 203 | font-size: inherit; 204 | line-height: inherit; 205 | } 206 | a { 207 | color: #337ab7; 208 | text-decoration: none; 209 | } 210 | a:hover, 211 | a:focus { 212 | color: #23527c; 213 | text-decoration: underline; 214 | } 215 | a:focus { 216 | outline: 5px auto -webkit-focus-ring-color; 217 | outline-offset: -2px; 218 | } 219 | figure { 220 | margin: 0; 221 | } 222 | img { 223 | vertical-align: bottom; 224 | border: none; 225 | } 226 | .img-responsive { 227 | display: block; 228 | max-width: 100%; 229 | height: auto; 230 | } 231 | .img-rounded { 232 | border-radius: 6px; 233 | } 234 | .img-thumbnail { 235 | padding: 4px; 236 | line-height: 1.42857143; 237 | background-color: #ffffff; 238 | border: 1px solid #dddddd; 239 | border-radius: 4px; 240 | -webkit-transition: all 0.2s ease-in-out; 241 | -o-transition: all 0.2s ease-in-out; 242 | transition: all 0.2s ease-in-out; 243 | display: inline-block; 244 | max-width: 100%; 245 | height: auto; 246 | } 247 | .img-circle { 248 | border-radius: 50%; 249 | } 250 | hr { 251 | margin-top: 20px; 252 | margin-bottom: 20px; 253 | border: 0; 254 | border-top: 1px solid #eeeeee; 255 | } 256 | .sr-only { 257 | position: absolute; 258 | width: 1px; 259 | height: 1px; 260 | margin: -1px; 261 | padding: 0; 262 | overflow: hidden; 263 | clip: rect(0, 0, 0, 0); 264 | border: 0; 265 | } 266 | .sr-only-focusable:active, 267 | .sr-only-focusable:focus { 268 | position: static; 269 | width: auto; 270 | height: auto; 271 | margin: 0; 272 | overflow: visible; 273 | clip: auto; 274 | } 275 | [role="button"] { 276 | cursor: pointer; 277 | } 278 | .modal-open { 279 | overflow: hidden; 280 | } 281 | .modal { 282 | display: block; 283 | background: rgba(255,255,255,0.9); 284 | display: none; 285 | overflow: hidden; 286 | position: fixed; 287 | top: 0; 288 | right: 0; 289 | bottom: 0; 290 | left: 0; 291 | z-index: 1050; 292 | -webkit-overflow-scrolling: touch; 293 | outline: 0; 294 | } 295 | .modal.fade .modal-dialog { 296 | -webkit-transform: translate(0, -25%); 297 | -ms-transform: translate(0, -25%); 298 | -o-transform: translate(0, -25%); 299 | transform: translate(0, -25%); 300 | -webkit-transition: -webkit-transform 0.3s ease-out; 301 | -o-transition: -o-transform 0.3s ease-out; 302 | transition: transform 0.3s ease-out; 303 | /*padding-top: 80px;*/ 304 | background: transparent; 305 | bottom: 0; 306 | left: 0; 307 | position: fixed; 308 | right: 0; 309 | top: 0; 310 | z-index: 100; 311 | overflow-x: hidden; 312 | overflow-y: scroll; 313 | } 314 | 315 | .modal.in .modal-dialog { 316 | -webkit-transform: translate(0, 0); 317 | -ms-transform: translate(0, 0); 318 | -o-transform: translate(0, 0); 319 | transform: translate(0, 0); 320 | } 321 | .modal-open .modal { 322 | overflow-x: hidden; 323 | overflow-y: scroll; 324 | } 325 | .modal-dialog { 326 | position: relative; 327 | width: auto; 328 | margin: 10px; 329 | width: 90%; 330 | height: 90%; 331 | } 332 | .modal-content { 333 | position: relative; 334 | -webkit-background-clip: padding-box; 335 | background-clip: padding-box; 336 | outline: 0; 337 | } 338 | .modal-backdrop { 339 | position: fixed; 340 | top: 0; 341 | right: 0; 342 | bottom: 0; 343 | left: 0; 344 | z-index: 1040; 345 | background-color: #000000; 346 | } 347 | .modal-backdrop.fade { 348 | opacity: 0; 349 | filter: alpha(opacity=0); 350 | } 351 | .modal-backdrop.in { 352 | opacity: 0.5; 353 | filter: alpha(opacity=50); 354 | } 355 | .modal-header { 356 | padding: 15px; 357 | } 358 | .modal-header .close { 359 | margin-top: -2px; 360 | border-radius: 99px; 361 | width: 40px; 362 | height: 40px; 363 | background: lightgray; 364 | border: none; 365 | font-size: 30px; 366 | color: white; 367 | 368 | } 369 | .modal-title { 370 | margin: 0; 371 | line-height: 1.42857143; 372 | } 373 | .modal-body { 374 | position: relative; 375 | margin-top: 0px; 376 | 377 | } 378 | 379 | .modal-footer { 380 | padding: 15px; 381 | text-align: right; 382 | border-top: 1px solid #e5e5e5; 383 | } 384 | .modal-footer .btn + .btn { 385 | margin-left: 5px; 386 | margin-bottom: 0; 387 | } 388 | .modal-footer .btn-group .btn + .btn { 389 | margin-left: -1px; 390 | } 391 | .modal-footer .btn-block + .btn-block { 392 | margin-left: 0; 393 | } 394 | .modal-scrollbar-measure { 395 | position: absolute; 396 | top: -9999px; 397 | width: 50px; 398 | height: 50px; 399 | overflow: scroll; 400 | } 401 | @media (min-width: 500px) { 402 | .modal-dialog { 403 | width: 500px; 404 | margin: 30px auto; 405 | } 406 | .modal-content { 407 | -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); 408 | box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); 409 | } 410 | .modal-sm { 411 | width: 300px; 412 | } 413 | } 414 | 415 | @media (min-width: 992px) { 416 | .modal-lg { 417 | width: 100%; 418 | } 419 | } 420 | .clearfix:before, 421 | .clearfix:after, 422 | .modal-header:before, 423 | .modal-header:after, 424 | .modal-footer:before, 425 | .modal-footer:after { 426 | content: " "; 427 | display: table; 428 | } 429 | .clearfix:after, 430 | .modal-header:after, 431 | .modal-footer:after { 432 | clear: both; 433 | } 434 | .center-block { 435 | display: block; 436 | margin-left: auto; 437 | margin-right: auto; 438 | } 439 | .pull-right { 440 | float: left !important; 441 | margin-top: -25px !important; 442 | margin-left: -41px !important; 443 | border-radius: 50% !important; 444 | } 445 | 446 | .pull-left { 447 | float: left !important; 448 | } 449 | .hide { 450 | display: none !important; 451 | } 452 | .show { 453 | display: block !important; 454 | } 455 | .invisible { 456 | visibility: hidden; 457 | } 458 | .text-hide { 459 | font: 0/0 a; 460 | color: transparent; 461 | text-shadow: none; 462 | background-color: transparent; 463 | border: 0; 464 | } 465 | .hidden { 466 | display: none !important; 467 | } 468 | .affix { 469 | position: fixed; 470 | } 471 | 472 | /*Custom Css*/ 473 | 474 | /*.close pull-right { 475 | position: absolute; 476 | background: #bbb; 477 | padding: 14px 14px 13px; 478 | width: 12px; 479 | height: 12px; 480 | }*/ 481 | 482 | .content { 483 | margin: 0 auto; 484 | align-items: center; 485 | display: flex; 486 | /*justify-content: center;*/ 487 | } 488 | 489 | .wrapper { 490 | padding-top: 0; 491 | width: 100%; 492 | } 493 | 494 | .container { 495 | display: flex; 496 | margin: 0 auto; 497 | width: 100%; 498 | position: relative; 499 | } 500 | 501 | -------------------------------------------------------------------------------- /src/app/components/shared/modal/modal.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/shared/modal/modal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ModalComponent } from './modal.component'; 4 | 5 | describe('ModalComponent', () => { 6 | let component: ModalComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ModalComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ModalComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/shared/modal/modal.component.ts: -------------------------------------------------------------------------------- 1 | import { ModalDirective } from 'ngx-bootstrap'; 2 | import { Component, ViewChild, AfterViewInit } from '@angular/core'; 3 | import { Location } from '@angular/common'; 4 | 5 | @Component({ 6 | selector: 'app-modal', 7 | templateUrl: './modal.component.html', 8 | styleUrls: ['./modal.component.css'] 9 | }) 10 | export class ModalComponent implements AfterViewInit { 11 | @ViewChild('lgModal') public lgModal: ModalDirective; 12 | 13 | constructor(private location: Location) { } 14 | 15 | ngAfterViewInit() { 16 | this.lgModal.show(); 17 | } 18 | closeModal() { 19 | this.lgModal.hide(); 20 | this.location.back(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/container/admin-page/admin-page.component.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/container/admin-page/admin-page.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /src/app/container/admin-page/admin-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-admin-page', 5 | templateUrl: './admin-page.component.html', 6 | styleUrls: ['./admin-page.component.css'] 7 | }) 8 | export class AdminPageComponent implements OnInit { 9 | constructor() { } 10 | 11 | ngOnInit() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/container/app.component.css: -------------------------------------------------------------------------------- 1 | .body-part { 2 | width: 1291px; 3 | } 4 | 5 | .constraintWidth-conainer-content { 6 | max-width: 1100px; 7 | -ms-flex-flow: row nowrap; 8 | flex-flow: row nowrap; 9 | display: -ms-flexbox; 10 | display: flex; 11 | -ms-flex: 1; 12 | flex: 1; 13 | margin-top: 30px !important; 14 | padding: 0 15px; 15 | box-sizing: border-box; 16 | margin: 0 auto; 17 | min-width: 320px; 18 | } 19 | 20 | @media only screen and (max-width: 768px) { 21 | .body-part { 22 | width: 100%; 23 | } 24 | .main-container { 25 | width: 100%; 26 | } 27 | } 28 | 29 | @media only screen and (max-width: 320px) { 30 | .constraintWidth-conainer-content { 31 | margin-top: 20px !important; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/container/app.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |
7 |
8 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 | -------------------------------------------------------------------------------- /src/app/container/app.component.ts: -------------------------------------------------------------------------------- 1 | import { TopicActions } from './../actions/topic.actions'; 2 | import { getTopics } from './../reducers/index'; 3 | import { Router } from '@angular/router'; 4 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 5 | import { Observable } from 'rxjs/Observable'; 6 | import { Store } from '@ngrx/store'; 7 | import { AppState, getCurrentUser } from '../reducers/index'; 8 | import { UserActions } from '../actions/user.actions'; 9 | import { User } from '../models'; 10 | 11 | @Component({ 12 | selector: 'app-root', 13 | templateUrl: './app.component.html', 14 | styleUrls: ['./app.component.css'], 15 | changeDetection: ChangeDetectionStrategy.OnPush 16 | }) 17 | export class AppComponent implements OnInit { 18 | title: string; 19 | user$: Observable; 20 | topics$: Observable; 21 | loginModalHidden: Boolean = true; 22 | 23 | constructor(private userActions: UserActions, 24 | private topicActions: TopicActions, 25 | private store: Store, 26 | private router: Router) { 27 | 28 | this.store.dispatch(this.userActions.loadCurrentUserProfile()); 29 | this.store.dispatch(this.topicActions.loadTopics()); 30 | 31 | this.user$ = this.store.select(getCurrentUser); 32 | this.topics$ = this.store.select(getTopics); 33 | } 34 | 35 | logout() { 36 | this.store.dispatch(this.userActions.logout()); 37 | } 38 | 39 | ngOnInit() { 40 | } 41 | 42 | getAccessTokenToken(): any { 43 | return localStorage.getItem('access_token'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/container/login-page/login-page.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/angularhunt/b33a4139d579b08673022afb42db40d9a160c128/src/app/container/login-page/login-page.component.css -------------------------------------------------------------------------------- /src/app/container/login-page/login-page.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/container/login-page/login-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Router, ActivatedRoute } from '@angular/router'; 2 | import { AppState, getUserAuthStatus } from './../../reducers/index'; 3 | import { Store } from '@ngrx/store'; 4 | import { Component, OnInit } from '@angular/core'; 5 | 6 | @Component({ 7 | selector: 'app-login-page', 8 | templateUrl: './login-page.component.html', 9 | styleUrls: ['./login-page.component.css'] 10 | }) 11 | export class LoginPageComponent implements OnInit { 12 | returnUrl: String = '/home'; 13 | constructor( 14 | private store: Store, 15 | private route: ActivatedRoute, 16 | private router: Router) { 17 | this.redirectIfUserLoggedIn(); 18 | } 19 | 20 | ngOnInit() { 21 | } 22 | 23 | 24 | redirectIfUserLoggedIn() { 25 | this.store.select(getUserAuthStatus).subscribe( 26 | data => { 27 | if (data === true) { this.router.navigate([this.returnUrl]); } 28 | } 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/container/project-detail-page/project-detail-page.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/angularhunt/b33a4139d579b08673022afb42db40d9a160c128/src/app/container/project-detail-page/project-detail-page.component.css -------------------------------------------------------------------------------- /src/app/container/project-detail-page/project-detail-page.component.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/container/project-detail-page/project-detail-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './../../models/project'; 2 | import { User } from './../../models/user'; 3 | import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, AfterViewInit } from '@angular/core'; 4 | import { ActivatedRoute, Router } from '@angular/router'; 5 | import { Store } from '@ngrx/store'; 6 | import { Subscription } from 'rxjs/Subscription'; 7 | import { Observable } from 'rxjs/Observable'; 8 | import { AppState, 9 | getSelectedProject, 10 | getCurrentUser, 11 | getUpvotedProjectIds } from '../../reducers/index'; 12 | import { ProjectActions } from '../../actions/project.actions'; 13 | import { ToastyNotifierService } from './../../services/toasty-notifier.service'; 14 | 15 | @Component({ 16 | selector: 'app-project-detail-page', 17 | templateUrl: './project-detail-page.component.html', 18 | styleUrls: ['./project-detail-page.component.css'], 19 | changeDetection: ChangeDetectionStrategy.OnPush 20 | }) 21 | export class ProjectDetailPageComponent implements OnInit, OnDestroy, AfterViewInit { 22 | actionsSubscription: Subscription; 23 | project$: Observable; 24 | projectId: string; 25 | user: User; 26 | upvotedProjectIds$: Observable; 27 | 28 | constructor(private projectActions: ProjectActions, 29 | private store: Store, 30 | private route: ActivatedRoute, 31 | private toasterService: ToastyNotifierService, 32 | private router: Router) { 33 | this.project$ = this.store.select(getSelectedProject); 34 | 35 | this.store.select(getCurrentUser) 36 | .subscribe((user: User) => this.user = user); 37 | 38 | this.upvotedProjectIds$ = this.store.select(getUpvotedProjectIds); 39 | } 40 | 41 | ngOnInit() { 42 | this.actionsSubscription = this.route.params.subscribe( 43 | (params: any) => { 44 | this.projectId = params['id']; 45 | this.store.dispatch(this.projectActions.selectProject(this.projectId)); 46 | } 47 | ); 48 | } 49 | 50 | ngAfterViewInit() { 51 | } 52 | 53 | ngOnDestroy() { 54 | this.actionsSubscription.unsubscribe(); 55 | } 56 | 57 | toggleUpvote(payload: {project: Project, action: string }) { 58 | if (this.user) { 59 | this.store 60 | .dispatch(this.projectActions 61 | .toggleUpvote(payload.project, payload.action, this.user)); 62 | } else { 63 | this.router.navigate(['/login']); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/app/container/projects-page/projects-page.component.css: -------------------------------------------------------------------------------- 1 | .flex-display { 2 | display: flex; 3 | } 4 | 5 | .primary-content { 6 | float: left; 7 | -ms-flex: 1; 8 | flex: 1; 9 | min-width: 0; 10 | height: 580px; 11 | cursor: pointer; 12 | } 13 | 14 | .fullwidth-bottom-middle-box { 15 | background: #fff; 16 | border-radius: 3px; 17 | border: 1px solid rgba(0,0,0,.09); 18 | box-shadow: 0 1px 4px 0 rgba(0,0,0,.04); 19 | margin: 0 0 20px; 20 | width: 566PX; 21 | margin-top: 20px; 22 | } 23 | 24 | 25 | .bottom-content { 26 | width: 564px; 27 | } 28 | 29 | .bottom-header { 30 | margin: 0 20px; 31 | -ms-flex-align: baseline; 32 | align-items: baseline; 33 | border-bottom: 1px solid #e8e8e8; 34 | box-sizing: border-box; 35 | display: -ms-flexbox; 36 | display: flex; 37 | -ms-flex-pack: justify; 38 | justify-content: space-between; 39 | padding: 15px 0; 40 | cursor: pointer; 41 | } 42 | 43 | .title-base { 44 | line-height: 20px; 45 | font-size: 20px; 46 | font-weight: 200; 47 | letter-spacing: .2px; 48 | color: #000; 49 | font-style: normal; 50 | cursor: pointer; 51 | } 52 | 53 | .feednavigation-base { 54 | font-size: 12px; 55 | font-weight: 400; 56 | letter-spacing: .6px; 57 | line-height: 16px; 58 | text-transform: uppercase; 59 | font-family: proxima-nova,Proxima Nova,helvetica,arial,sans-serif; 60 | cursor: pointer; 61 | } 62 | 63 | 64 | .activelink-base { 65 | text-decoration: none; 66 | color: #000; 67 | border-right: 1px solid #e8e8e8; 68 | cursor: pointer; 69 | padding: 0 .5em; 70 | } 71 | .newest { 72 | text-decoration: none; 73 | color: #999; 74 | padding: 0 .5em; 75 | cursor: pointer; 76 | } 77 | 78 | .activelink-base:hover, 79 | .newest:hover { 80 | text-decoration: underline; 81 | } 82 | 83 | .post-list { 84 | list-style: none; 85 | margin: 0; 86 | padding: 0; 87 | } 88 | 89 | .button_mediumSize { 90 | border-radius: 3px; 91 | border: 1px solid transparent; 92 | box-sizing: border-box; 93 | outline: 0; 94 | display: -ms-inline-flexbox; 95 | display: inline-flex; 96 | -ms-flex-align: center; 97 | align-items: center; 98 | -ms-flex-pack: center; 99 | justify-content: center; 100 | text-align: center; 101 | text-decoration: none !important; 102 | font-size: 12px; 103 | font-weight: 600; 104 | letter-spacing: .6px; 105 | line-height: 16px; 106 | text-transform: uppercase; 107 | color: #fff; 108 | fill: #fff; 109 | background: #da552f; 110 | border-color: #da552f; 111 | height: 34px; 112 | padding: 0 13px; 113 | cursor: pointer; 114 | } 115 | 116 | .button_mediumSize:hover { 117 | background: #ea532a; 118 | border-color: #ea532a; 119 | } 120 | 121 | .buttonContainer { 122 | -ms-flex-align: center; 123 | align-items: center; 124 | display: -ms-inline-flexbox; 125 | display: inline-flex; 126 | -ms-flex-wrap: nowrap; 127 | flex-wrap: nowrap; 128 | -ms-flex-pack: center; 129 | justify-content: center; 130 | width: inherit; 131 | cursor: pointer; 132 | } 133 | 134 | .loadMore-container { 135 | text-align: center; 136 | padding: 10px; 137 | } 138 | 139 | /*Secondary Section csss*/ 140 | 141 | .secondary-section { 142 | width: 260px; 143 | float: left; 144 | } 145 | 146 | .sidebar { 147 | margin-left: 20px; 148 | width: 250px; 149 | min-width: 250px; 150 | } 151 | 152 | .padded-box { 153 | background: #fff; 154 | border-radius: 3px; 155 | border: 1px solid #e8e8e8; 156 | padding: 20px; 157 | } 158 | 159 | .featured { 160 | font-size: 20px; 161 | font-weight: 300; 162 | letter-spacing: .4px; 163 | line-height: 24px; 164 | } 165 | 166 | .box-text { 167 | display: block; 168 | margin: 10px 0 20px; 169 | font-size: 13px; 170 | font-weight: 400; 171 | line-height: 18px; 172 | color: #999; 173 | } 174 | 175 | .actions{ 176 | text-align: center; 177 | } 178 | 179 | .fa { 180 | padding-right: 8px; 181 | font-size: 20px; 182 | } 183 | 184 | @media only screen and (max-width: 768px) { 185 | .secondary-section { 186 | display: none; 187 | } 188 | .fullwidth-bottom-middle-box { 189 | width: 100%; 190 | } 191 | .bottom-content { 192 | width: 100%; 193 | } 194 | 195 | .loadMore-container { 196 | text-align: center; 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/app/container/projects-page/projects-page.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 | 8 | 9 |
10 |
11 |
12 | Projects 13 | 17 |
18 |
19 |
    20 |
  • 21 | 25 |
  • 26 |
27 |
28 |
29 | 35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 |
43 | 67 | 68 | 69 | 70 | 71 |
72 |
73 |
74 | 75 | 76 | 77 | 86 | -------------------------------------------------------------------------------- /src/app/container/projects-page/projects-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@angular/router'; 2 | import { User } from './../../models/user'; 3 | import { Project } from './../../models/project'; 4 | import { ProjectService } from './../../services/project.service'; 5 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 6 | import { Observable } from 'rxjs/Observable'; 7 | import { Store } from '@ngrx/store'; 8 | import { 9 | AppState, 10 | getProjects, 11 | getCurrentUser, 12 | getProjectsIds, 13 | getUpvotedProjectIds 14 | } from '../../reducers/index'; 15 | import { ProjectActions } from '../../actions/project.actions'; 16 | import { UserActions } from './../../actions/user.actions'; 17 | import { ToastyNotifierService } from './../../services/toasty-notifier.service'; 18 | 19 | @Component({ 20 | selector: 'app-projects-page', 21 | templateUrl: './projects-page.component.html', 22 | styleUrls: ['./projects-page.component.css'], 23 | changeDetection: ChangeDetectionStrategy.OnPush 24 | }) 25 | export class ProjectsPageComponent implements OnInit { 26 | projects$: Observable; 27 | user: User; 28 | projectIds: Observable; 29 | upvotedProjectIds$: Observable; 30 | 31 | constructor(private projectsService: ProjectService, 32 | private projectActions: ProjectActions, 33 | private userActions: UserActions, 34 | private store: Store, 35 | private toasterService: ToastyNotifierService, 36 | private router: Router) { 37 | 38 | this.projects$ = this.store.select(getProjects); 39 | 40 | this.projectIds = this.store.select(getProjectsIds); 41 | 42 | this.upvotedProjectIds$ = this.store.select(getUpvotedProjectIds); 43 | 44 | this.store.select(getCurrentUser) 45 | .subscribe((user: User) => { 46 | this.user = user; 47 | if (this.user) { 48 | this.projectIds.subscribe((ids: string[]) => { 49 | this.store.dispatch(this.userActions.loadUpvotedProjectIds( 50 | { userId: this.user.uid, projectIds: ids } 51 | )); 52 | }); 53 | } 54 | }); 55 | } 56 | 57 | ngOnInit() { 58 | this.store.dispatch(this.projectActions.retriveProjects()); 59 | } 60 | 61 | toggleUpvote(payload: { project: Project, action: string }) { 62 | if (this.user) { 63 | this.store 64 | .dispatch(this.projectActions 65 | .toggleUpvote(payload.project, payload.action, this.user)); 66 | } else { 67 | this.router.navigate(['/login']); 68 | } 69 | } 70 | 71 | subscribeToNewsLetter(email: string) { 72 | this.projectsService.subscribeToNewsLetter(email) 73 | .subscribe((res) => { 74 | return this.toasterService 75 | .pop({ result: res.result, msg: res.msg }); 76 | }); 77 | } 78 | 79 | loadMoreProjects() { 80 | this.store.dispatch(this.projectActions.retriveProjects()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/app/dummyData.ts: -------------------------------------------------------------------------------- 1 | // ================== For Topics ====================================== 2 | 3 | export const dummyData = [ 4 | { 5 | name: 'tech', 6 | description: 'Hardware or software. Invention or innovation. If someone’s pushing technology forward, you’ll find it here.', 7 | bannerUrl: 'https://ph-files.imgix.net/701fd26a-c028-48ec-a7e8-bf0f89d48e03' 8 | }, 9 | { 10 | name: 'Games', 11 | description: 'Find something new and exciting to play at home or on the go. Work can wait. Have some fun.', 12 | bannerUrl: 'https://ph-files.imgix.net/701fd26a-c028-48ec-a7e8-bf0f89d48e03' 13 | }, 14 | ]; 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | // ================================= For Projects ======================================================= 23 | // export const dummyData = [ 24 | // { 25 | // 'user_id': 1, 26 | // 'git_url': 'https://github.com/aviabird/angularspree', 27 | // 'description': 'AngularSpree is a plug and play front-end application for SPREE E-Commerce API built with ❤️ using Angular2, Redux, Observables & ImmutableJs.', 28 | // 'demo_url': 'https://angularspree.firebaseapp.com/', 29 | // 'author_name': 'Aviabird', 30 | // 'name': 'AngularSpree', 31 | // 'caption': 'AngularSpree', 32 | // 'twitter_id': 'http://twitter.com/avia_bird', 33 | // 'approved': true, 34 | // 'image_url': 'https://cdn-images-1.medium.com/max/1000/1*Sdo-tCpvStzwurH8-UoFvA.png', 35 | // upvotes: 0 36 | // }, 37 | // { 38 | // 'user_id': 1, 39 | // 'git_url': ' https://github.com/orizens/echoes-ng2', 40 | // 'description': 'Echoes is a great youtube player developed by Oren Farhi. It’s fun & easy to listen or watch videos from youtube with Echoes. What if youtube was designed to be used as music player? This repository is an implementation of Echoes Player with angular 2 — It’s still a work in progress aimed at learning angular 2.', 41 | // 'demo_url': 'http://echoesplayer.com/', 42 | // 'author_name': 'Oren Farhi', 43 | // 'name': 'Youtube Echoes player', 44 | // 'caption': 'Youtube Echoes player', 45 | // 'twitter_id': 'http://twitter.com/orizens', 46 | // 'approved': true, 47 | // 'image_url': 'https://cdn-images-1.medium.com/max/800/1*WeZ3yi2DwUF71dcQCD5Cbw.png', 48 | // upvotes: 0 49 | // }, 50 | // { 51 | // 'user_id': 1, 52 | // 'git_url': 'https://github.com/aviabird/travel-app', 53 | // 'description': 'Yatrum is a full fledged application in production. This app is a travel diary app for travellers. Travellers can create itinerary of trips.', 54 | // 'demo_url': 'http://yatrum.com/', 55 | // 'author_name': 'AviaBird', 56 | // 'name': 'Yatrum', 57 | // 'caption': 'Yatrum', 58 | // 'twitter_id': 'http://twitter.com/yatrum_', 59 | // 'approved': true, 60 | // 'image_url': 'https://cdn-images-1.medium.com/max/1400/1*9V6r_ipFoswXn9OOPBpjwQ.png', 61 | // upvotes: 0 62 | // }, 63 | // { 64 | // 'user_id': 1, 65 | // 'git_url': 'https://github.com/Centroida/sg-app-client', 66 | // 'description': 'This project is intended to facilitate the processes of the Student Government and provide information about the student organisations in AUBG. It comes with a CMS and functionality to manage student applications for clubs and other activities. Students are also able write evaluations (anonymously) for the faculty members in AUBG.', 67 | // 'demo_url': 'https://github.com/Centroida/sg-app-client', 68 | // 'author_name': 'Centroida', 69 | // 'name': 'AUBG Student Government Web Client', 70 | // 'caption': 'AUBG Student Government Web Client', 71 | // 'twitter_id': 'http://twitter.com/avia-bird', 72 | // 'approved': true, 73 | // 'image_url': 'https://cdn-images-1.medium.com/max/1400/1*evN8cFteCLakFHBNQNinLQ.png', 74 | // upvotes: 0 75 | // }, 76 | // { 77 | // 'user_id': 1, 78 | // 'git_url': 'https://github.com/Centroida/sg-app-client', 79 | // 'description': 'A basic SoundCloud API client built with Angular2 and NgRx.', 80 | // 'demo_url': 'https://soundcloud-ngrx.herokuapp.com/', 81 | // 'author_name': 'Richard Park', 82 | // 'name': 'SoundCloud NgRx', 83 | // 'caption': 'SoundCloud NgRx', 84 | // 'twitter_id': 'http://twitter.com/avia-bird', 85 | // 'approved': true, 86 | // 'image_url': 'https://cdn-images-1.medium.com/max/800/1*0FIAMqZHUf3ZeVUHTXYS3g.png', 87 | // upvotes: 0 88 | // }, 89 | // { 90 | // 'user_id': 1, 91 | // 'git_url': 'https://github.com/ng-book/angular2-rxjs-chat', 92 | // 'description': 'This repo shows an example chat application using RxJS and Angular 2. The goal is to show how to use the Observables data architecture pattern within Angular 2.', 93 | // 'demo_url': 'http://rxjs.ng-book.com/', 94 | // 'author_name': 'ng-book', 95 | // 'name': 'Angular 2 RxJS Chat', 96 | // 'caption': 'Angular 2 RxJS Chat', 97 | // 'twitter_id': 'http://twitter.com/avia-bird', 98 | // 'approved': true, 99 | // 'image_url': 'https://cdn-images-1.medium.com/max/800/1*rgrFcA-xSK1hUuCpJIoj8A.png', 100 | // upvotes: 0 101 | // }, 102 | // { 103 | // 'user_id': 1, 104 | // 'git_url': 'https://github.com/housseindjirdeh/angular2-hn', 105 | // 'description': 'A progressive Hacker News client built with Angular. This application is complete with test cases so, that’s a big plus plus.', 106 | // 'demo_url': 'https://angular2-hn.firebaseapp.com/', 107 | // 'author_name': 'Houssein Djirdeh', 108 | // 'name': 'Hacker News progressive app', 109 | // 'caption': 'Hacker News progressive app', 110 | // 'twitter_id': 'http://twitter.com/hdjirdeh', 111 | // 'approved': true, 112 | // 'image_url': 'https://cdn-images-1.medium.com/max/800/1*37GSSxt7iOW263XNUA-IuA.gif', 113 | // upvotes: 0 114 | // }, 115 | // { 116 | // 'user_id': 1, 117 | // 'git_url': 'https://github.com/aviabird/pinterest', 118 | // 'description': 'A pinterest type clone for bloggers. Application utilising @ngrx libraries, showcasing common patterns and best practices', 119 | // 'demo_url': 'https://aviabird.github.io/pinterest', 120 | // 'author_name': 'Aviabird', 121 | // 'name': ' Pinterest clone', 122 | // 'caption': ' Pinterest clone', 123 | // 'twitter_id': 'http://twitter.com/avia-bird', 124 | // 'approved': true, 125 | // 'image_url': 'https://cdn-images-1.medium.com/max/800/1*hKeC4_ulSUKTPreCzPOQVg.png', 126 | // upvotes: 0 127 | // } 128 | // ]; 129 | -------------------------------------------------------------------------------- /src/app/effects/project.effects.ts: -------------------------------------------------------------------------------- 1 | import { ProjectActions, ActionTypes } from './../actions/project.actions'; 2 | import { Injectable } from '@angular/core'; 3 | import { Effect, Actions } from '@ngrx/effects'; 4 | import { Action } from '@ngrx/store'; 5 | 6 | import { ProjectService } from './../services/project.service'; 7 | import 'rxjs/add/operator/switchMap'; 8 | import 'rxjs/add/operator/map'; 9 | 10 | @Injectable() 11 | export class ProjectEffects { 12 | 13 | @Effect() getAllProjects$ = this.actions$ 14 | .ofType(ActionTypes.RETRIVE_PROJECTS) 15 | .switchMap(() => this.projectService.getAllProjects()) 16 | .map((response: any) => this.projectActions.retriveProjectsSuccess(response)); 17 | 18 | @Effect() upvoteProject$ = this.actions$ 19 | .ofType(ActionTypes.TOGGLE_UPVOTE) 20 | .map((action: Action) => action.payload) 21 | .switchMap((payload) => this.projectService.toggleUpvote(payload)) 22 | .map((data) => this.projectActions.toggleUpvoteSuccess()); 23 | 24 | // @Effect() saveNewProject$ = this.actions$ 25 | // .ofType(ActionTypes.SAVE_NEW_PROJECT) 26 | // .switchMap((action: Action) => this.projectService.saveNewProject(action.payload)) 27 | // .map((response: any) => this.projectActions.saveNewProjectSuccess(response)); 28 | 29 | constructor(private actions$: Actions, 30 | private projectActions: ProjectActions, 31 | private projectService: ProjectService 32 | ) { } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/app/effects/topic.effects.ts: -------------------------------------------------------------------------------- 1 | import { TopicActions, ActionTypes } from './../actions/topic.actions'; 2 | import { Injectable } from '@angular/core'; 3 | import { Effect, Actions } from '@ngrx/effects'; 4 | import { ProjectService } from './../services/project.service'; 5 | import { Topic } from './../models/topic'; 6 | import 'rxjs/add/operator/switchMap'; 7 | import 'rxjs/add/operator/map'; 8 | 9 | @Injectable() 10 | export class TopicEffects { 11 | 12 | @Effect() getAllTopics$ = this.actions$ 13 | .ofType(ActionTypes.LOAD_TOPICS) 14 | .switchMap(() => this.projectService.getAllTopics()) 15 | .map((response: Topic[]) => this.topicActions.loadTopicsSuccess(response)); 16 | 17 | constructor(private actions$: Actions, 18 | private topicActions: TopicActions, 19 | private projectService: ProjectService 20 | ) { } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/app/effects/user.effects.ts: -------------------------------------------------------------------------------- 1 | import { ResponseParserService } from './../services/response-parser.service'; 2 | import { Injectable } from '@angular/core'; 3 | import { Effect, Actions } from '@ngrx/effects'; 4 | import { Action, Store } from '@ngrx/store'; 5 | import { AppState } from '../reducers/index'; 6 | import { ActionTypes, UserActions } from '../actions/user.actions'; 7 | import { AuthenticationService } from './../services/authentication.service'; 8 | import { ProjectService } from './../services/project.service'; 9 | import 'rxjs/add/operator/switchMap'; 10 | import 'rxjs/add/operator/map'; 11 | 12 | @Injectable() 13 | export class UserEffects { 14 | 15 | @Effect() login$ = this.actions$ 16 | .ofType(ActionTypes.LOGIN) 17 | .map((action: Action) => action.payload) 18 | .switchMap((provider: string) => this.authService.login(provider)) 19 | .filter((payload) => payload.user != null) 20 | .switchMap(payload => this.authService.storeNewUser(payload)) 21 | .map((payload) => this.userActions.loginSuccess(payload)); 22 | 23 | 24 | @Effect() loadCurrentUserProfile$ = this.actions$ 25 | .ofType(ActionTypes.LOAD_CURRENT_USER_PROFILE) 26 | .switchMap(() => this.authService.authStatus()) 27 | .filter((payload) => payload.user != null) 28 | .switchMap((payload) => this.authService.updateUserAuth(payload)) 29 | .map((response) => this.userActions.loadCurrentUserProfileSuccess(response.user)); 30 | 31 | @Effect() logout$ = this.actions$ 32 | .ofType(ActionTypes.LOGOUT) 33 | .map(() => this.authService.logout()) 34 | .map(() => this.userActions.logoutSuccess()); 35 | 36 | @Effect() laodUpvotedProjectIds = this.actions$ 37 | .ofType(ActionTypes.LOAD_UPVOTED_PROJECT_IDS) 38 | .switchMap((action: Action) => this.projectService.loadUpvotedrojectIds(action.payload)) 39 | .map((upvotedProjectIds: string[]) => { 40 | return this.userActions.loadUpvotedProjectIdsSuccess(upvotedProjectIds) 41 | }); 42 | 43 | constructor(private actions$: Actions, 44 | private userActions: UserActions, 45 | private authService: AuthenticationService, 46 | private parser: ResponseParserService, 47 | private store: Store, 48 | private projectService: ProjectService 49 | ) { } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/app/guards/auth.guards.ts: -------------------------------------------------------------------------------- 1 | import { Subscription } from 'rxjs/Rx'; 2 | // import { TripsService } from './../services/trips.service'; 3 | // import * as fromTripActions from './../actions/trips.action'; 4 | import * as fromRoot from './../reducers/index'; 5 | import { Store } from '@ngrx/store'; 6 | // import { Observable } from 'rxjs/Observable'; 7 | import { User } from './../models/user'; 8 | import { Injectable, OnDestroy } from '@angular/core'; 9 | import { Router, CanActivate } from '@angular/router'; 10 | 11 | 12 | @Injectable() 13 | export class CanActivateViaAuthGuard implements CanActivate, OnDestroy { 14 | isAuthenticated: boolean; 15 | subscription: Subscription; 16 | user: User; 17 | validEmailPattern: RegExp = /^[a-z][a-zA-Z0-9_.]*(\.[a-zA-Z][a-zA-Z0-9_.]*)?@aviabird.com/; 18 | 19 | constructor(private store: Store, private router: Router) { 20 | } 21 | 22 | canActivate() { 23 | this.subscription = this.store 24 | .select(fromRoot.getCurrentUser) 25 | .subscribe(user => { 26 | this.user = user; 27 | if (!this.user || !this.validEmailPattern.test(this.user.email)) { 28 | this.router.navigate(['/login']); 29 | } 30 | }); 31 | 32 | return true; 33 | } 34 | 35 | ngOnDestroy() { 36 | this.subscription.unsubscribe(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './container/app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /src/app/models/base.ts: -------------------------------------------------------------------------------- 1 | export class Base { 2 | public $key: string; 3 | 4 | constructor(attributes?) { 5 | if (attributes) { 6 | let keys = Object.keys(attributes); 7 | // Temp Solution 8 | this.$key = attributes.$key; 9 | if (keys.length) { 10 | keys.forEach(el => { 11 | this[el] = attributes[el]; 12 | }); 13 | } 14 | } 15 | }; 16 | 17 | get id(): string { 18 | return this.$key; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/models/index.ts: -------------------------------------------------------------------------------- 1 | export { Project } from './project'; 2 | export { User } from './user'; -------------------------------------------------------------------------------- /src/app/models/project.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | export class Project extends Base { 3 | git_url: string; 4 | description: string; 5 | demo_url: string; 6 | image_url: string; 7 | author_name: string; 8 | name: string; 9 | caption: string; 10 | twitter_id: string; 11 | approved: boolean; 12 | created_at: string; 13 | upvotes: number; 14 | upvoted_by: any; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/models/topic.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | 3 | export class Topic extends Base { 4 | name: string; 5 | description: string; 6 | bannerUrl: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/models/user.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | export class User extends Base { 3 | name: string; 4 | avatar: string; 5 | email: string; 6 | provider: string; 7 | uid: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/pipes/is-upvoted-by-current-user.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'isUpvotedByCurrentUser' 5 | }) 6 | export class IsUpvotedByCurrentUserPipe implements PipeTransform { 7 | 8 | transform(value: String, args: String[]): any { 9 | return args.indexOf(value) !== -1 ? true : false; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/pipes/search-filter.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { Project } from './../models/project'; 3 | 4 | @Pipe({ 5 | name: 'searchFilter' 6 | }) 7 | export class SearchFilterPipe implements PipeTransform { 8 | 9 | transform(projects: Project[], args: any): any { 10 | return projects.filter(project => project.name.toLowerCase() 11 | .indexOf(args.toLowerCase()) !== -1); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/pipes/trim-text.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'trimText' 5 | }) 6 | export class TrimTextPipe implements PipeTransform { 7 | 8 | transform(value: string, args?: any): any { 9 | if (!value) { 10 | return ''; 11 | } 12 | if (value.length > 140) { 13 | return `${value.substring(0, 140)}...`; 14 | } else { 15 | return value; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import '@ngrx/core/add/operator/select'; 2 | import 'rxjs/add/operator/let'; 3 | import { compose } from '@ngrx/core/compose'; 4 | import { combineReducers } from '@ngrx/store'; 5 | import { createSelector } from 'reselect'; 6 | 7 | import * as fromProjects from './projects.reducer'; 8 | import * as fromUsers from './user.reducer'; 9 | import * as fromTopics from './topic.reducer'; 10 | 11 | // Entire State of a App 12 | export interface AppState { 13 | projects: fromProjects.State; 14 | users: fromUsers.State; 15 | topics: fromTopics.State; 16 | } 17 | 18 | // Export all the reducers 19 | export default compose(combineReducers)({ 20 | projects: fromProjects.projectReducer, 21 | users: fromUsers.userReducer, 22 | topics: fromTopics.topicReducer 23 | }); 24 | 25 | 26 | 27 | /**Retrive Projects from ProjectState */ 28 | export const getProjectsState = (appState: AppState) => appState.projects; 29 | export const getProjectsEntities = createSelector(getProjectsState, fromProjects.getEntities); 30 | export const getProjectsIds = createSelector(getProjectsState, fromProjects.getIds); 31 | export const getProjects = createSelector(getProjectsEntities, getProjectsIds, (projects, ids) => { 32 | return ids.map(id => projects[id]); 33 | }); 34 | 35 | export const getSelectedProjectId = createSelector(getProjectsState, fromProjects.getSelectedProjectId); 36 | export const getSelectedProject = createSelector(getProjectsEntities, getSelectedProjectId, (projects, id) => { 37 | return projects[id]; 38 | }); 39 | 40 | 41 | /**Retrive Users */ 42 | export const getUsersState = (appState: AppState) => appState.users; 43 | 44 | export const getCurrentUser = createSelector(getUsersState, fromUsers.getUser); 45 | export const getUpvotedProjectIds = createSelector(getUsersState, fromUsers.getUpvotedProjectIds); 46 | export const getUserAuthStatus = createSelector(getUsersState, fromUsers.getAuthStatus); 47 | 48 | /** Retrive Topics */ 49 | export const getTopicsState = (appState: AppState ) => appState.topics; 50 | export const getTopicsIds = createSelector(getTopicsState, fromTopics.getIds); 51 | export const getTopicsEntities = createSelector(getTopicsState, fromTopics.getEntities); 52 | export const getTopics = createSelector(getTopicsEntities, getTopicsIds, (topics, ids) => { 53 | return ids.map(id => topics[id]); 54 | }); 55 | -------------------------------------------------------------------------------- /src/app/reducers/projects.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { Project } from '../models/'; 3 | import { ActionTypes } from '../actions/project.actions'; 4 | 5 | export type State = { 6 | ids: string[]; 7 | entities: { [id: string]: Project }; 8 | selectedProjectId: string; 9 | }; 10 | 11 | const initialState: State = { 12 | ids: [], 13 | entities: {}, 14 | selectedProjectId: null 15 | }; 16 | 17 | export const projectReducer = (state = initialState, action: Action): State => { 18 | switch (action.type) { 19 | case ActionTypes.RETRIVE_PROJECTS_SUCCESS: { 20 | const Projects: Project[] = action.payload; 21 | 22 | const newProjects: Project[] = Projects; 23 | 24 | const newProjectsIds = Projects 25 | .filter(list => !state.entities[list.id]) 26 | .map(list => list.id); 27 | 28 | const newEntities = newProjects 29 | .reduce((entities: { [id: string]: Project }, project: Project) => { 30 | return Object.assign(entities, { 31 | [project.id]: project 32 | }); 33 | }, {}); 34 | 35 | return Object.assign({}, state, { 36 | ids: [...state.ids, ...newProjectsIds], 37 | entities: Object.assign({}, state.entities, newEntities) 38 | }); 39 | }; 40 | 41 | case ActionTypes.SELECT_PROJECT: { 42 | return Object.assign({}, state, { 43 | selectedProjectId: action.payload 44 | }); 45 | }; 46 | 47 | 48 | case ActionTypes.UPDATE_PROJECT_SUCCESS: { 49 | const updatedProject = action.payload; 50 | const updatedProjectId = updatedProject.id; 51 | 52 | let newProjects = state.entities; 53 | newProjects[updatedProjectId] = updatedProject; 54 | 55 | return Object.assign({}, state, { 56 | entities: Object.assign({}, state.entities, newProjects) 57 | 58 | }); 59 | }; 60 | 61 | default: { 62 | return state; 63 | }; 64 | } 65 | } 66 | 67 | export const getIds = (state: State) => state.ids; 68 | export const getEntities = (state: State) => state.entities; 69 | export const getSelectedProjectId = (state: State) => state.selectedProjectId; 70 | -------------------------------------------------------------------------------- /src/app/reducers/topic.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Topic } from './../models/topic'; 2 | import { Action } from '@ngrx/store'; 3 | import { ActionTypes } from '../actions/topic.actions'; 4 | 5 | export type State = { 6 | entities: { [id: string]: Topic }; 7 | ids: string[]; 8 | selectedTopicId: string; 9 | }; 10 | 11 | const initialState: State = { 12 | entities: {}, 13 | ids: [], 14 | selectedTopicId: null, 15 | }; 16 | 17 | 18 | export const topicReducer = (state = initialState, action: Action): State => { 19 | switch (action.type) { 20 | 21 | case ActionTypes.LOAD_TOPICS_SUCCESS: { 22 | const Topics: Topic[] = action.payload; 23 | 24 | const newTopics: Topic[] = Topics; 25 | 26 | const newTopicsIds = Topics 27 | .filter(list => !state.entities[list.id]) 28 | .map(list => list.id); 29 | 30 | const newEntities = newTopics 31 | .reduce((entities: { [id: string]: Topic }, Topic: Topic) => { 32 | return Object.assign(entities, { 33 | [Topic.id]: Topic 34 | }); 35 | }, {}); 36 | 37 | return Object.assign({}, state, { 38 | ids: [...state.ids, ...newTopicsIds], 39 | entities: Object.assign({}, state.entities, newEntities) 40 | }); 41 | } 42 | /* falls through */ 43 | default: { 44 | return state; 45 | } 46 | } 47 | }; 48 | 49 | export const getIds = (state: State) => state.ids; 50 | export const getEntities = (state: State) => state.entities; 51 | -------------------------------------------------------------------------------- /src/app/reducers/user.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { ActionTypes } from '../actions/user.actions'; 3 | import { User } from './../models/user'; 4 | 5 | export type State = { 6 | isAuthenticated: boolean; 7 | user: User | null; 8 | entities: { [id: string]: User }; 9 | ids: string[]; 10 | selectedUserId: string; 11 | access_token: string; 12 | upvotedProjectIds: Array; 13 | }; 14 | 15 | const initialState: State = { 16 | isAuthenticated: false, 17 | user: null, 18 | entities: {}, 19 | ids: [], 20 | selectedUserId: null, 21 | access_token: localStorage.getItem('access_token'), 22 | upvotedProjectIds: [] 23 | }; 24 | 25 | 26 | export const userReducer = (state = initialState, action: Action): State => { 27 | switch (action.type) { 28 | case ActionTypes.LOGIN_SUCCESS: { 29 | let user = action.payload; 30 | let newState = { 31 | isAuthenticated: true, 32 | user: user 33 | }; 34 | 35 | return Object.assign({}, state, newState); 36 | } 37 | 38 | case ActionTypes.LOAD_CURRENT_USER_PROFILE_SUCCESS: { 39 | let user = action.payload; 40 | let isAuth = false; 41 | 42 | if (user) { 43 | isAuth = true; 44 | } 45 | 46 | let newState = { 47 | isAuthenticated: isAuth, 48 | user: user 49 | }; 50 | 51 | return Object.assign({}, state, newState); 52 | } 53 | 54 | case ActionTypes.LOGOUT_SUCCESS: { 55 | let newState = { 56 | isAuthenticated: false, 57 | user: null, 58 | access_token: null, 59 | upvotedProjectIds: [] 60 | }; 61 | 62 | return Object.assign({}, state, newState); 63 | } 64 | 65 | case ActionTypes.LOAD_UPVOTED_PROJECT_IDS_SUCCESS: { 66 | return Object.assign({}, 67 | state, 68 | { upvotedProjectIds: action.payload }); 69 | } 70 | 71 | default: { 72 | return state; 73 | } 74 | } 75 | }; 76 | 77 | export const getIds = (state: State) => state.ids; 78 | export const getEntities = (state: State) => state.entities; 79 | export const getUser = (state: State) => state.user; 80 | export const getUpvotedProjectIds = (state: State) => state.upvotedProjectIds; 81 | export const getAuthStatus = (state: State) => state.isAuthenticated; 82 | -------------------------------------------------------------------------------- /src/app/services/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import { Http } from '@angular/http'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { Injectable } from '@angular/core'; 4 | import 'rxjs/add/observable/of'; 5 | 6 | import { 7 | AngularFire, AngularFireDatabase, AuthProviders 8 | } from 'angularfire2'; 9 | 10 | @Injectable() 11 | export class AuthenticationService { 12 | userAuth: Observable; 13 | 14 | constructor(private af: AngularFire, 15 | public db: AngularFireDatabase, 16 | private http: Http) { 17 | 18 | this.userAuth = this.af.auth.map( 19 | user => this._changeState(user), 20 | error => console.log(error), 21 | ); 22 | } 23 | 24 | login(provider: string): Observable { 25 | this.af.auth.login({ 26 | provider: this._getProvider(provider) 27 | }); 28 | return this.userAuth; 29 | } 30 | 31 | logout(): any { 32 | this.af.auth.logout(); 33 | localStorage.removeItem('access_token') 34 | return this.userAuth; 35 | } 36 | 37 | storeNewUser(userAuth) { 38 | let user = userAuth.user; 39 | 40 | return this.findbyUID(user.uid).map( 41 | obj => { 42 | if (!obj.$value) { 43 | this.db 44 | .object(`users/${user.uid}`) 45 | .set(user) 46 | .then(() => console.log('New User Added to DB')) 47 | .catch(() => console.clear()); 48 | } 49 | return this.updateUserAuth(userAuth); 50 | } 51 | ); 52 | } 53 | 54 | findbyUID(uid: string) { 55 | return this.db.object(`users/${uid}`); 56 | } 57 | 58 | updateUserAuth(userAuth) { 59 | 60 | return this.findbyUID(userAuth.user.uid).switchMap(() => { 61 | // User is logged in good time to update 62 | // the user here in case user updates info(image) 63 | this.db 64 | .object(`users/${userAuth.user.uid}`) 65 | .set(userAuth.user) 66 | .then(() => console.log('User added/updated in DB')) 67 | .catch(() => console.clear()); 68 | return Observable.of(userAuth); 69 | } 70 | ); 71 | } 72 | 73 | authStatus() { 74 | return this.userAuth; 75 | } 76 | 77 | private _changeState(user: any = null) { 78 | if (user) { 79 | return { 80 | user: this._getUserInfo(user), 81 | isAuthenticated: true 82 | }; 83 | } else { 84 | return { 85 | user: null, 86 | isAuthenticated: false 87 | }; 88 | } 89 | } 90 | 91 | private _getUserInfo(user: any): any { 92 | if (!user) { 93 | return {}; 94 | } 95 | let data = user.auth.providerData[0]; 96 | return { 97 | name: data.displayName, 98 | avatar: data.photoURL, 99 | email: data.email, 100 | provider: data.providerId, 101 | uid: user.auth.uid 102 | }; 103 | } 104 | 105 | private _getProvider(from: string) { 106 | switch (from) { 107 | case 'twitter': return AuthProviders.Twitter; 108 | case 'facebook': return AuthProviders.Facebook; 109 | case 'github': return AuthProviders.Github; 110 | case 'google': return AuthProviders.Google; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/app/services/node_modules/.yarn-integrity: -------------------------------------------------------------------------------- 1 | 549854b8a60607db81d4c58008d59f812d744acba026266f380acd942941356a -------------------------------------------------------------------------------- /src/app/services/project.service.ts: -------------------------------------------------------------------------------- 1 | import { Topic } from './../models/topic'; 2 | import { User } from './../models/user'; 3 | import { Project } from './../models/project'; 4 | import { Injectable } from '@angular/core'; 5 | import { Observable } from 'rxjs/Observable'; 6 | import { Http, Jsonp } from '@angular/http'; 7 | import { AngularFireDatabase } from 'angularfire2'; 8 | import { dummyData } from './../dummyData'; 9 | 10 | 11 | @Injectable() 12 | export class ProjectService { 13 | userId = '5bc67e9ba994773e66c535640'; 14 | listId = 'cf7d6ebd15'; 15 | mailChimpUrl = 'https://aviabird.us15.list-manage.com/subscribe/post-json'; 16 | projectsCount = 0; 17 | 18 | constructor(private http: Http, 19 | private jsonp: Jsonp, 20 | public db: AngularFireDatabase) { } 21 | 22 | 23 | getAllTopics(): Observable { 24 | return this.db.list('/topics') 25 | .map(response => response.map(topic => new Topic(topic))); 26 | } 27 | 28 | sendData() { 29 | dummyData.forEach(project => { 30 | this.db.list('/topics').push(project); 31 | }); 32 | } 33 | 34 | getAllProjects(): Observable { 35 | this.projectsCount += 5; 36 | return this.db.list('/projects', { 37 | query: { 38 | limitToFirst: this.projectsCount, 39 | } 40 | }) 41 | .map(response => { 42 | return response.map(project => new Project(project)); 43 | }); 44 | } 45 | 46 | /** 47 | * User Like/Dislikes Project 48 | * TODO: Needs Serious Refactoring.... 49 | * This should retrun the updated project. 50 | * currenly only upvoting; 51 | * Lol.. @voizero why so serious ? - @ashish173 52 | * @method upvoteProject 53 | * @param object {project: any, user: any } of project 54 | * @return { Observable } Observable with updated project object 55 | */ 56 | toggleUpvote(payload: any): firebase.Promise { 57 | let project: Project = payload.project; 58 | let user: User = payload.user; 59 | let action: string = payload.action; 60 | 61 | if (action === 'upvote') { 62 | return this.upvote(project, user); 63 | } else { 64 | return this.removeVote(project, user); 65 | } 66 | } 67 | 68 | upvote(project, user): firebase.Promise { 69 | let newupvote = project.upvotes ? project.upvotes + 1 : 1; 70 | let user_projects = { user_id: user.uid, project_id: project.$key } 71 | let customKey = `${user.uid}_${project.$key}`; 72 | 73 | return this.db.list('/projects').update(project.$key, 74 | { upvotes: newupvote }).then(() => { 75 | let toSend = this.db.object(`/users_projects`); 76 | toSend.update({ [customKey]: user_projects }); 77 | }); 78 | } 79 | 80 | removeVote(project, user): firebase.Promise { 81 | let key = `${user.uid}_${project.id}`; 82 | 83 | let newUpvote = project.upvotes - 1; 84 | 85 | return this.db.list('/projects').update(project.$key, { 86 | upvotes: newUpvote 87 | }).then(() => { 88 | this.db.list('/users_projects').remove(key); 89 | }); 90 | } 91 | 92 | getAccessTokenToken(): any { 93 | return localStorage.getItem('access_token'); 94 | } 95 | 96 | subscribeToNewsLetter(email: string) { 97 | const url = `${this.mailChimpUrl}?u=${this.userId}&id=${this.listId}&subscribe=Subscribe&EMAIL=${email}&c=JSONP_CALLBACK`; 98 | return this.jsonp.request(url, { method: 'Get' }) 99 | .map(res => res.json()); 100 | } 101 | 102 | loadUpvotedrojectIds(payload: any): Observable { 103 | let userId = payload.userId; 104 | let projectIds = payload.projectIds; 105 | return this.db.list('/users_projects', { 106 | query: { 107 | orderByChild: 'user_id', 108 | equalTo: userId 109 | } 110 | }).map((users_projects: any) => { 111 | return users_projects.map(u_p => { 112 | let presence = projectIds.indexOf(u_p.project_id); 113 | if (presence !== -1) { 114 | return u_p.project_id; 115 | } else { 116 | return null; 117 | } 118 | // return projectIds.indexOf(u_p.project_id) !== -1 ? u_p.project_id : null; 119 | }); 120 | }); 121 | }; 122 | 123 | saveNewProject(project: any): firebase.Promise { 124 | return this.db.list('/projects').push(project); 125 | } 126 | 127 | updateProject(key: string, project: any): firebase.Promise { 128 | return this.db.list('/projects').update(key, project); 129 | } 130 | 131 | deleteProject(id: string): firebase.Promise { 132 | return this.db.list('/projects').remove(id); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/app/services/response-parser.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { User } from '../models'; 3 | @Injectable() 4 | export class ResponseParserService { 5 | 6 | getUserObj(response: any): User { 7 | let raw_user = response.user; 8 | let attr = { 9 | id: raw_user.id, 10 | email: raw_user.email 11 | } 12 | 13 | let user = new User(attr); 14 | return user; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/services/toasty-notifier.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | declare var noty: any; 4 | 5 | export interface Toasty { 6 | result: string; 7 | msg: string; 8 | } 9 | 10 | @Injectable() 11 | export class ToastyNotifierService { 12 | 13 | pop(toasty: Toasty) { 14 | let n = noty({ timeout: 4000 }); 15 | if (toasty.result === 'success') { 16 | n.setType('success'); 17 | } else { 18 | n.setType('error'); 19 | } 20 | n.setText(toasty.msg); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/services/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/app/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This function coerces a string into a string literal type. 3 | * Using tagged union types in TypeScript 2.0, this enables 4 | * powerful typechecking of our reducers. 5 | * 6 | * Since every action label passes through this function it 7 | * is a good place to ensure all of our action labels 8 | * are unique. 9 | */ 10 | 11 | let typeCache: { [label: string]: boolean } = {}; 12 | export function type(label: T | ''): T { 13 | if (typeCache[label]) { 14 | throw new Error(`Action type "${label}" is not unqiue"`); 15 | } 16 | 17 | typeCache[label] = true; 18 | 19 | return label; 20 | } -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/angularhunt/b33a4139d579b08673022afb42db40d9a160c128/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/images/Missing-target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/angularhunt/b33a4139d579b08673022afb42db40d9a160c128/src/assets/images/Missing-target.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/angularhunt/b33a4139d579b08673022afb42db40d9a160c128/src/assets/images/logo.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 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/angularhunt/b33a4139d579b08673022afb42db40d9a160c128/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Hunt - Rate & Discuss about Angular projects 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | Loading... 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppModule } from './app/'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. 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 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import '../node_modules/font-awesome/css/font-awesome.min.css'; 3 | /*@import '../node_modules/angular2-toaster/toaster.css';*/ 4 | 5 | html { 6 | height: 100%; 7 | font-family: "proxima-nova", "Proxima Nova", helvetica, arial, sans-serif; 8 | } 9 | 10 | body { 11 | background: #f9f9f9; 12 | margin: 0; 13 | min-height: 100%; 14 | position: relative; 15 | } 16 | 17 | share-buttons button { 18 | width: 70px; 19 | height: 35px; 20 | padding: none; 21 | font-size: 1.2rem; 22 | } 23 | 24 | share-buttons .sb-buttons .facebook button { 25 | background: #3B5996; 26 | border-color: #3b5998; 27 | color: white; 28 | } 29 | 30 | share-buttons .sb-buttons .twitter button { 31 | background: #00b4f5; 32 | border-color: #00b4f5; 33 | color: white; 34 | } 35 | 36 | .constraintWidth-conainer-content { 37 | max-width: 90%; 38 | -ms-flex-flow: row nowrap; 39 | flex-flow: row nowrap; 40 | display: -ms-flexbox; 41 | display: flex; 42 | -ms-flex: 1; 43 | flex: 1; 44 | margin-top: 30px !important; 45 | padding: 0 15px; 46 | box-sizing: border-box; 47 | margin: 0 auto; 48 | min-width: 320px; 49 | } 50 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 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 | 10 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 11 | declare var __karma__: any; 12 | declare var require: any; 13 | 14 | // Prevent Karma from running prematurely. 15 | __karma__.loaded = function () {}; 16 | 17 | 18 | Promise.all([ 19 | System.import('@angular/core/testing'), 20 | System.import('@angular/platform-browser-dynamic/testing') 21 | ]) 22 | // First, initialize the Angular testing environment. 23 | .then(([testing, testingBrowser]) => { 24 | testing.getTestBed().initTestEnvironment( 25 | testingBrowser.BrowserDynamicTestingModule, 26 | testingBrowser.platformBrowserDynamicTesting() 27 | ); 28 | }) 29 | // Then we find all the tests. 30 | .then(() => require.context('./', true, /\.spec\.ts/)) 31 | // And load the modules. 32 | .then(context => context.keys().map(context)) 33 | // Finally, start Karma to run the tests. 34 | .then(__karma__.start, __karma__.error); 35 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "lib": ["es6", "dom"], 7 | "mapRoot": "./", 8 | "module": "es6", 9 | "moduleResolution": "node", 10 | "outDir": "../dist/out-tsc", 11 | "sourceMap": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "../node_modules/@types" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | // Typings reference file, you can add your own global typings here 2 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html 3 | 4 | declare var System: any; -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "label-position": true, 19 | "label-undefined": true, 20 | "max-line-length": [ 21 | true, 22 | 140 23 | ], 24 | "member-access": false, 25 | "member-ordering": [ 26 | true, 27 | "static-before-instance", 28 | "variables-before-functions" 29 | ], 30 | "no-arg": true, 31 | "no-bitwise": true, 32 | "no-console": [ 33 | true, 34 | "debug", 35 | "info", 36 | "time", 37 | "timeEnd", 38 | "trace" 39 | ], 40 | "no-construct": true, 41 | "no-debugger": true, 42 | "no-duplicate-key": true, 43 | "no-duplicate-variable": true, 44 | "no-empty": false, 45 | "no-eval": true, 46 | "no-inferrable-types": true, 47 | "no-shadowed-variable": true, 48 | "no-string-literal": false, 49 | "no-switch-case-fall-through": true, 50 | "no-trailing-whitespace": true, 51 | "no-unused-expression": true, 52 | "no-unused-variable": true, 53 | "no-unreachable": true, 54 | "no-use-before-declare": true, 55 | "no-var-keyword": true, 56 | "object-literal-sort-keys": false, 57 | "one-line": [ 58 | true, 59 | "check-open-brace", 60 | "check-catch", 61 | "check-else", 62 | "check-whitespace" 63 | ], 64 | "quotemark": [ 65 | true, 66 | "single" 67 | ], 68 | "radix": true, 69 | "semicolon": [ 70 | "always" 71 | ], 72 | "triple-equals": [ 73 | true, 74 | "allow-null-check" 75 | ], 76 | "typedef-whitespace": [ 77 | true, 78 | { 79 | "call-signature": "nospace", 80 | "index-signature": "nospace", 81 | "parameter": "nospace", 82 | "property-declaration": "nospace", 83 | "variable-declaration": "nospace" 84 | } 85 | ], 86 | "variable-name": false, 87 | "whitespace": [ 88 | true, 89 | "check-branch", 90 | "check-decl", 91 | "check-operator", 92 | "check-separator", 93 | "check-type" 94 | ], 95 | 96 | "directive-selector-prefix": [true, "app"], 97 | "component-selector-prefix": [true, "app"], 98 | "directive-selector-name": [true, "camelCase"], 99 | "component-selector-name": [true, "kebab-case"], 100 | "directive-selector-type": [true, "attribute"], 101 | "component-selector-type": [true, "element"], 102 | "use-input-property-decorator": true, 103 | "use-output-property-decorator": true, 104 | "use-host-property-decorator": true, 105 | "no-input-rename": true, 106 | "no-output-rename": true, 107 | "use-life-cycle-interface": true, 108 | "use-pipe-transform-interface": true, 109 | "component-class-suffix": true, 110 | "directive-class-suffix": true, 111 | "templates-use-public": true, 112 | "invoke-injectable": true 113 | } 114 | } 115 | --------------------------------------------------------------------------------