├── .editorconfig ├── .gitignore ├── README.md ├── angular-cli.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── karma.conf.js ├── npm-debug.log.284780475 ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── actions │ │ ├── feeds.actions.ts │ │ ├── index.ts │ │ ├── list.actions.ts │ │ ├── login.actions.ts │ │ ├── user-list.actions.ts │ │ └── user.actions.ts │ ├── app.module.ts │ ├── app.routes.ts │ ├── components │ │ ├── feed-detail │ │ │ ├── fd-summary │ │ │ │ ├── fd-summary.component.css │ │ │ │ ├── fd-summary.component.html │ │ │ │ └── fd-summary.component.ts │ │ │ ├── feed-detail.component.css │ │ │ ├── feed-detail.component.html │ │ │ └── feed-detail.component.ts │ │ ├── feed │ │ │ ├── feed.component.css │ │ │ ├── feed.component.html │ │ │ └── feed.component.ts │ │ ├── feeds │ │ │ ├── feeds.component.css │ │ │ ├── feeds.component.html │ │ │ └── feeds.component.ts │ │ ├── index.ts │ │ ├── shared │ │ │ └── modal │ │ │ │ ├── modal.component.css │ │ │ │ ├── modal.component.html │ │ │ │ └── modal.component.ts │ │ └── suggested-lists │ │ │ ├── suggested-lists.component.css │ │ │ ├── suggested-lists.component.html │ │ │ └── suggested-lists.component.ts │ ├── containers │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── dashboard-page │ │ │ ├── dashboard-page.component.css │ │ │ ├── dashboard-page.component.html │ │ │ └── dashboard-page.component.ts │ │ ├── list-feed-page │ │ │ ├── list-feed-page.component.css │ │ │ ├── list-feed-page.component.html │ │ │ └── list-feed-page.component.ts │ │ ├── login-page │ │ │ ├── login-page.component.css │ │ │ ├── login-page.component.html │ │ │ └── login-page.component.ts │ │ ├── signup-page │ │ │ ├── signu-page.component.ts │ │ │ ├── signup-page.component.css │ │ │ └── signup-page.component.html │ │ └── suggestions-page │ │ │ ├── suggestions-page.component.css │ │ │ ├── suggestions-page.component.html │ │ │ └── suggestions-page.component.ts │ ├── effects │ │ ├── feeds.effects.ts │ │ ├── list.effects.ts │ │ ├── user-auth.effect.ts │ │ ├── user-lists.effects.ts │ │ └── user.effects.ts │ ├── index.ts │ ├── models │ │ ├── base.ts │ │ ├── entities.ts │ │ ├── index.ts │ │ ├── list.ts │ │ ├── tweet.ts │ │ ├── urls.ts │ │ ├── user-auth.ts │ │ ├── user-list.ts │ │ ├── user-profile.ts │ │ └── user.ts │ ├── pipes │ │ ├── capitalize.ts │ │ ├── index.ts │ │ ├── linkify.ts │ │ ├── mentions.ts │ │ └── stringify.ts │ ├── reducers │ │ ├── feeds.reducer.ts │ │ ├── index.ts │ │ ├── list.reducer.ts │ │ ├── user-auth.reducer.ts │ │ ├── user-list.reducer.ts │ │ └── user.reducer.ts │ ├── services │ │ ├── api.service.ts │ │ ├── auth-guard.service.ts │ │ ├── response-parse.service.ts │ │ └── user-auth.service.ts │ └── util.ts ├── assets │ ├── .gitkeep │ ├── background.jpg │ ├── css │ │ └── style.css │ └── poi.gif ├── 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 /.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 | -------------------------------------------------------------------------------- /.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 | 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 | secrets.ts 28 | # e2e 29 | /e2e/*.js 30 | /e2e/*.map 31 | 32 | #System Files 33 | .DS_Store 34 | Thumbs.db 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Listify 2 | 3 | This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.19-3. 4 | 5 | ## Deployed Link 6 | 7 | https://aviabird.github.io/listify/ 8 | 9 | 10 | ## Backend Source of Listify 11 | 12 | https://github.com/aviabird/listify-backend 13 | 14 | 15 | ## Backend Deployed Heroku Link 16 | 17 | 'https://listify-backend.herokuapp.com/api' 18 | 19 | 20 | ## Addiional Instructions 21 | 22 | If you are setting up this project for fun on your localhost then 23 | you might want to change the `baseUrl` in `enviornment.ts` to 24 | heroku link: 'https://listify-backend.herokuapp.com/api' 25 | 26 | ## Development server 27 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 28 | 29 | ## Code scaffolding 30 | 31 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class`. 32 | 33 | ## Build 34 | 35 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 36 | 37 | ## Running unit tests 38 | 39 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 40 | 41 | ## Running end-to-end tests 42 | 43 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 44 | Before running the tests make sure you are serving the app via `ng serve`. 45 | 46 | ## Deploying to Github Pages 47 | 48 | Run `ng github-pages:deploy` to deploy to Github Pages. 49 | 50 | ## Further help 51 | 52 | To get more help on the `angular-cli` use `ng --help` or go check out the [Angular-CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 53 | -------------------------------------------------------------------------------- /angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.19-3", 4 | "name": "listify" 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": "ist", 19 | "mobile": false, 20 | "styles": [ 21 | "styles.css" 22 | ], 23 | "scripts": [], 24 | "environments": { 25 | "source": "environments/environment.ts", 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 | } 60 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { IStalkFrontendPage } from './app.po'; 2 | 3 | describe('listify App', function() { 4 | let page: IStalkFrontendPage; 5 | 6 | beforeEach(() => { 7 | page = new IStalkFrontendPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('ist works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class IStalkFrontendPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('ist-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /npm-debug.log.284780475: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/npm-debug.log.284780475 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "listify", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "start": "ng serve", 8 | "lint": "tslint \"src/**/*.ts\"", 9 | "test": "ng test", 10 | "pree2e": "webdriver-manager update", 11 | "e2e": "protractor" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/common": "^2.3.1", 16 | "@angular/compiler": "^2.3.1", 17 | "@angular/compiler-cli": "^2.4.1", 18 | "@angular/core": "^2.3.1", 19 | "@angular/forms": "^2.3.1", 20 | "@angular/http": "^2.3.1", 21 | "@angular/platform-browser": "^2.3.1", 22 | "@angular/platform-browser-dynamic": "^2.3.1", 23 | "@angular/platform-server": "^2.4.1", 24 | "@angular/router": "^3.3.1", 25 | "@ngrx/core": "^1.2.0", 26 | "@ngrx/effects": "^2.0.0", 27 | "@ngrx/router-store": "^1.2.5", 28 | "@ngrx/store": "^2.2.1", 29 | "@ngrx/store-devtools": "^3.2.2", 30 | "@ngrx/store-log-monitor": "^3.0.2", 31 | "@types/node": "^6.0.54", 32 | "core-js": "^2.4.1", 33 | "firebase": "^3.6.4", 34 | "karma-remap-istanbul": "^0.2.2", 35 | "ng-semantic": "^1.1.13", 36 | "ng2-restangular": "^0.1.23", 37 | "primeui": "^4.1.15", 38 | "reselect": "^2.5.4", 39 | "rxjs": "^5.0.1", 40 | "ts-helpers": "^1.1.1", 41 | "typescript": "2.0.10", 42 | "zone.js": "^0.7.2" 43 | }, 44 | "devDependencies": { 45 | "@types/jasmine": "^2.2.30", 46 | "@types/node": "^6.0.42", 47 | "angular-cli": "1.0.0-beta.19-3", 48 | "codelyzer": "1.0.0-beta.1", 49 | "jasmine-core": "2.4.1", 50 | "jasmine-spec-reporter": "2.5.0", 51 | "karma": "1.2.0", 52 | "karma-chrome-launcher": "^2.0.0", 53 | "karma-cli": "^1.0.1", 54 | "karma-jasmine": "^1.0.2", 55 | "karma-remap-istanbul": "^0.2.1", 56 | "protractor": "4.0.9", 57 | "ts-node": "1.2.1", 58 | "tslint": "3.13.0", 59 | "typescript": "2.0.10", 60 | "webdriver-manager": "10.2.5" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /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/actions/feeds.actions.ts: -------------------------------------------------------------------------------- 1 | import { Tweet } from './../models/tweet'; 2 | import { type } from '../util'; 3 | import { Action } from '@ngrx/store'; 4 | 5 | // const of all ActionTypes.... 6 | export const ActionTypes = { 7 | GET_FEEDS_FOR_ID: type('Get Tweets for Id'), 8 | GET_FEEDS_FOR_ID_SUCCESS: type('Get Tweets for Id Success'), 9 | GET_ALL_FEEDS: type('Get All Feeds'), 10 | GET_ALL_FEEDS_SUCCESS: type('Get All Feeds Success'), 11 | SELECT_FEED: type('Select Feed'), 12 | ADD_FEED_TO_FAV: type('Add Feed To Fav'), 13 | ADD_FEED_TO_FAV_SUCCESS: type('Add Feed To Fav Success'), 14 | REMOVE_FEED_FROM_FAV: type('Remove Feed From Fav'), 15 | REMOVE_FEED_FROM_FAV_SUCESS: type('Remove Feed From Fav Success'), 16 | RETWEET: type('Retweet'), 17 | RETWEET_SUCCESS: type('Retweet Success'), 18 | REPLY: type('Reply'), 19 | REPLY_SUCCESS: type('Reply Success') 20 | } 21 | 22 | export class FeedsActions { 23 | 24 | getAllFeeds(): Action { 25 | return { 26 | type: ActionTypes.GET_ALL_FEEDS 27 | } 28 | } 29 | 30 | /**Changed Feeds type to any from Tweet 31 | * TODO: make a complete model of tweet 32 | */ 33 | getAllFeedsSuccess(feeds: any): Action { 34 | return { 35 | type: ActionTypes.GET_ALL_FEEDS_SUCCESS, 36 | payload: feeds 37 | } 38 | } 39 | 40 | getFeedsForId(userListId: any): Action { 41 | return { 42 | type: ActionTypes.GET_FEEDS_FOR_ID, 43 | payload: userListId 44 | } 45 | } 46 | 47 | /** 48 | * Change Feeds type to any from Tweet 49 | * 50 | * TODO: make a complete model of tweet - voidzero 51 | * 52 | * Get Feeds For Paticular List ID 53 | * 54 | * @param : feeds 55 | * 56 | * @return : Action With payload feeds 57 | */ 58 | getFeedsForIdSuccess(feeds: any): Action { 59 | return { 60 | type: ActionTypes.GET_FEEDS_FOR_ID_SUCCESS, 61 | payload: feeds 62 | } 63 | } 64 | 65 | /** 66 | * Action to selcet a feed and add it to store 67 | * 68 | * @param: feedId 69 | * 70 | * @return Action with payload feedId 71 | */ 72 | selectFeed(feedId: string): Action { 73 | return { 74 | type: ActionTypes.SELECT_FEED, 75 | payload: feedId 76 | } 77 | } 78 | 79 | /** 80 | * Action that triggers when user adds 81 | * a particular feed to Fav 82 | * 83 | * @param : feed 84 | * 85 | * @return : Action with payload Feed 86 | */ 87 | addFeedToFav(feed: any):Action{ 88 | return { 89 | type: ActionTypes.ADD_FEED_TO_FAV, 90 | payload: feed 91 | } 92 | } 93 | /** 94 | * Action that triggers when fav action completes 95 | * 96 | * @param : feed 97 | * 98 | * @return : Action with payload Feed 99 | */ 100 | addFeedToFavSuccess(feed: any): Action { 101 | return { 102 | type: ActionTypes.ADD_FEED_TO_FAV_SUCCESS, 103 | payload: feed 104 | } 105 | } 106 | 107 | /** 108 | * Action that triggers when user removes 109 | * a particular feed to Fav 110 | * 111 | * @param : feed 112 | * 113 | * @return : Action with payload Feed 114 | */ 115 | removeFeedFromFav(feed: any):Action{ 116 | return { 117 | type: ActionTypes.REMOVE_FEED_FROM_FAV, 118 | payload: feed 119 | } 120 | } 121 | 122 | /** 123 | * Action that triggers when remove fav action completes 124 | * 125 | * @param : feed 126 | * 127 | * @return : Action with payload Feed 128 | */ 129 | removeFeedFromFavSuccess(feed: any): Action { 130 | return { 131 | type: ActionTypes.REMOVE_FEED_FROM_FAV_SUCESS, 132 | payload: feed 133 | } 134 | } 135 | 136 | /** 137 | * Action that triggers when retweet is clicked 138 | * 139 | * @param : feed 140 | * 141 | * @return : Action with payload Feed 142 | */ 143 | retweet(feed): Action { 144 | return { 145 | type: ActionTypes.RETWEET, 146 | payload: feed 147 | } 148 | } 149 | 150 | /** 151 | * Action that triggers when retweet is Success 152 | * 153 | * @param : feed 154 | * 155 | * @return : Action with payload feed 156 | */ 157 | retweetSuccess(feed): Action { 158 | return { 159 | type: ActionTypes.RETWEET_SUCCESS, 160 | payload: feed 161 | } 162 | } 163 | 164 | /** 165 | * Action that triggers when a user replies to 166 | * a particular tweet 167 | * 168 | * @param : {feedId: feedId, message: message} 169 | * 170 | * @return : Action with payload messageWithFeed 171 | */ 172 | reply(messageWithFeed: {}): Action { 173 | return { 174 | type: ActionTypes.REPLY, 175 | payload: messageWithFeed 176 | } 177 | } 178 | 179 | /** 180 | * Action that triggers when a reply to 181 | * a tweet is success 182 | * 183 | * @param : {boolean} status 184 | * 185 | * @return : Action with payload status 186 | */ 187 | replySuccess(feed): Action { 188 | return { 189 | type: ActionTypes.REPLY_SUCCESS, 190 | payload: feed 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/app/actions/index.ts: -------------------------------------------------------------------------------- 1 | import { LoginActions } from './login.actions'; 2 | import { UserActions } from './user.actions'; 3 | import { ListActions } from './list.actions'; 4 | import { FeedsActions } from './feeds.actions'; 5 | import { UserListActions } from './user-list.actions'; 6 | export { 7 | LoginActions, 8 | UserActions, 9 | ListActions, 10 | FeedsActions, 11 | UserListActions 12 | }; 13 | 14 | export default [ 15 | LoginActions, 16 | UserActions, 17 | ListActions, 18 | FeedsActions, 19 | UserListActions 20 | ]; -------------------------------------------------------------------------------- /src/app/actions/list.actions.ts: -------------------------------------------------------------------------------- 1 | import { type } from '../util'; 2 | import { Action } from '@ngrx/store'; 3 | import { List } from '../models'; 4 | import { UserList } from '../models'; 5 | 6 | export const ActionTypes = { 7 | RETRIVE_LISTS: type('Retrive Lists'), 8 | RETRIVE_LISTS_SUCCESS: type('Retrive Lists Success'), 9 | FOLLOW_LIST: type('Follow List'), 10 | FOLLOW_LIST_SUCCESS: type('Follow List Success'), 11 | UNFOLLOW_LIST: type('UnFollow List'), 12 | UNFOLLOW_LIST_SUCCESS: type('UnFollow List Success'), 13 | UPDATE_LISTS: type('Update Lists') 14 | } 15 | 16 | export class ListActions { 17 | 18 | retriveLists(): Action { 19 | return { 20 | type: ActionTypes.RETRIVE_LISTS 21 | } 22 | } 23 | 24 | retriveListsSuccess(suggestedLists: List[]): Action { 25 | return { 26 | type: ActionTypes.RETRIVE_LISTS_SUCCESS, 27 | payload: suggestedLists 28 | } 29 | } 30 | 31 | follow(listId: string): Action { 32 | return { 33 | type: ActionTypes.FOLLOW_LIST, 34 | payload: listId 35 | } 36 | } 37 | 38 | followSuccess(response: any): Action { 39 | return { 40 | type: ActionTypes.FOLLOW_LIST_SUCCESS, 41 | payload: response 42 | } 43 | } 44 | 45 | unFollowList(listId: string): Action{ 46 | return { 47 | type: ActionTypes.UNFOLLOW_LIST, 48 | payload: listId 49 | } 50 | } 51 | 52 | unFollowListSuccess(response: any): Action { 53 | return { 54 | type: ActionTypes.UNFOLLOW_LIST_SUCCESS, 55 | payload: response 56 | } 57 | } 58 | 59 | updateLists(response: any): Action { 60 | return { 61 | type: ActionTypes.UPDATE_LISTS, 62 | payload: response 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/app/actions/login.actions.ts: -------------------------------------------------------------------------------- 1 | import { type } from '../util'; 2 | import { Action } from '@ngrx/store'; 3 | import { UserAuth } from '../models'; 4 | /** 5 | * A Hash Constant which has all types of Login Action 6 | * { key: type } 7 | */ 8 | export const ActionTypes = { 9 | LOGIN: type("Login"), 10 | LOGIN_SUCCESS: type("Login Success"), 11 | LOGIN_SERVER: type("Login Server"), 12 | LOGIN_SERVER_SUCCESS: type("Login Server Success"), 13 | LOGOUT: type('Logout'), 14 | LOGOUT_SUCCESS: type('Logout Success'), 15 | SIGNUP: type('Sign Up'), 16 | SIGNUP_SUCCESS: type('Sign Up Success'), 17 | SIGNUP_SERVER: type('Sign Up Server'), 18 | SIGNUP_SERVER_SUCCESS: type('Sign Up Server Success'), 19 | STORE_USER: type('Store User'), 20 | STORE_USER_SUCCESS: type('Store User Success') 21 | } 22 | 23 | /** 24 | * List of all functions i.e action that can be 25 | * performed while logging in a user; 26 | */ 27 | export class LoginActions { 28 | /** 29 | * Returns an action. 30 | * 31 | * @param provider: a string to specify the social media from which the user is logging in. E.g: 'facebook' 32 | * @return {Action} an Action with type 'Login' and a payload provider 33 | */ 34 | login(provider: string): Action { 35 | return { 36 | type: ActionTypes.LOGIN, 37 | payload: provider 38 | }; 39 | } 40 | 41 | /** 42 | * Returns an Action. 43 | * 44 | * Note : taking user_auth as input and returning user as a payload 45 | * which latter a reducer stores it in a state. 46 | * There is an another approach where we take a loginResponse 47 | * as whole and then in reducer we disect it and retrive user 48 | * details. 49 | * 50 | * TODO: Discuss the above two approach with the team. 51 | * and implement the elegant one. 52 | * 53 | * Note: Ignore the above message, currently takking login response 54 | * as payload. 55 | * 56 | * @param loginData: Data of type any received after login a user 57 | * @return {Action} an Action with type 'Login Success' 58 | * 59 | */ 60 | loginSuccess(userAuth: UserAuth): Action { 61 | return { 62 | type: ActionTypes.LOGIN_SUCCESS, 63 | payload: userAuth 64 | } 65 | } 66 | 67 | /** 68 | * Returns an Action 69 | * 70 | * @return {Action} an Action with type 'Logout' 71 | */ 72 | logout(): Action { 73 | return { 74 | type: ActionTypes.LOGOUT 75 | } 76 | } 77 | 78 | /** 79 | * Returns an Action 80 | * 81 | * @return {Action} an Action with type 'Logout Success' 82 | */ 83 | logoutSuccess(userAuth): Action { 84 | return { 85 | type: ActionTypes.LOGOUT_SUCCESS 86 | } 87 | } 88 | 89 | signUp(provider: string): Action { 90 | return { 91 | type: ActionTypes.SIGNUP, 92 | payload: provider 93 | } 94 | } 95 | 96 | signUpSuccess(userAuth: UserAuth): Action { 97 | return { 98 | type: ActionTypes.SIGNUP_SUCCESS, 99 | payload: userAuth 100 | } 101 | } 102 | 103 | 104 | storeUser(email, userAuth): Action { 105 | return { 106 | type: ActionTypes.STORE_USER, 107 | payload: { email: email, userAuth: userAuth } 108 | } 109 | } 110 | 111 | storeUserSuccess(userAuth): Action { 112 | return { 113 | type: ActionTypes.STORE_USER_SUCCESS, 114 | payload: userAuth 115 | } 116 | } 117 | 118 | loginServer(userAuth): Action { 119 | return { 120 | type: ActionTypes.LOGIN_SERVER, 121 | payload: userAuth 122 | } 123 | } 124 | 125 | loginServerSuccess(userAuth): Action { 126 | return { 127 | type: ActionTypes.LOGIN_SERVER_SUCCESS, 128 | payload: userAuth 129 | } 130 | } 131 | 132 | signUpServer(userAuth): Action { 133 | return { 134 | type: ActionTypes.SIGNUP_SERVER, 135 | payload: userAuth 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/app/actions/user-list.actions.ts: -------------------------------------------------------------------------------- 1 | import { type } from '../util'; 2 | import { Action } from '@ngrx/store'; 3 | import { UserList } from '../models/'; 4 | 5 | export const ActionTypes = { 6 | GET_USER_LISTS: type("Get User List"), 7 | GET_USER_LISTS_SUCCESS: type("Get User List Success") 8 | } 9 | 10 | 11 | export class UserListActions { 12 | getUserLists(): Action { 13 | return { 14 | type: ActionTypes.GET_USER_LISTS 15 | } 16 | } 17 | 18 | getUserListsSuccess(userLists: UserList[]): Action { 19 | return { 20 | type: ActionTypes.GET_USER_LISTS_SUCCESS, 21 | payload: userLists 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/app/actions/user.actions.ts: -------------------------------------------------------------------------------- 1 | import { type } from '../util'; 2 | import { Action } from '@ngrx/store'; 3 | import { User } from '../models/'; 4 | 5 | /** 6 | * A Hash Constant which has all types of Login Action 7 | * { key: type } 8 | */ 9 | export const ActionTypes = { 10 | LOAD_PROFILE: type("Load Profile"), 11 | LOAD_PROFILE_SUCCESS: type("Load Profile Success") 12 | } 13 | 14 | /** 15 | * List of all Action. 16 | * TODO: Naming Convention for this Action class 17 | * Hint: These are Actions performed on a logged in user to gets its details 18 | * or are the actions that are performed by dashboard. 19 | */ 20 | export class UserActions { 21 | /** 22 | * Returns an Action 23 | * 24 | * @return {Action} an Action with type 'Load Profile' and null payload. 25 | */ 26 | loadProfile(): Action { 27 | return { 28 | type: ActionTypes.LOAD_PROFILE 29 | } 30 | } 31 | 32 | /** 33 | * Returns an Action 34 | * 35 | * @return {Action} an Action with type 'Load Profile Success' 36 | * and payload as User 37 | */ 38 | loadProfileSuccess(user): Action { 39 | return { 40 | type: ActionTypes.LOAD_PROFILE_SUCCESS, 41 | payload: user 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | //========================= IStalk ======================================== 2 | /** 3 | * Core Modules and Libraries used in App. 4 | */ 5 | import { BrowserModule } from '@angular/platform-browser'; 6 | import { NgModule } from '@angular/core'; 7 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 8 | import { HttpModule } from '@angular/http'; 9 | import { CommonModule } from '@angular/common'; 10 | import { RestangularModule } from 'ng2-restangular'; 11 | import { environment } from '../environments/environment'; 12 | import { PipesModule } from './pipes'; 13 | 14 | /**Semantic UI */ 15 | import { NgSemanticModule } from 'ng-semantic'; 16 | 17 | /** ALL Services used in APP */ 18 | import { AuthGuardService } from './services/auth-guard.service'; 19 | import { UserAuthService } from './services/user-auth.service'; 20 | import { ApiService } from './services/api.service'; 21 | import { ResponseParseService } from './services/response-parse.service'; 22 | 23 | //========================= NGRX Releated Imports =========================== 24 | 25 | /** 26 | * Ngrx Store Modules 27 | */ 28 | import { StoreModule } from '@ngrx/store'; 29 | import { StoreDevtoolsModule } from '@ngrx/store-devtools'; 30 | import { StoreLogMonitorModule, useLogMonitor } from '@ngrx/store-log-monitor'; 31 | import { EffectsModule } from '@ngrx/effects'; 32 | import { RouterStoreModule } from '@ngrx/router-store'; 33 | 34 | /** All SideEffects in APP */ 35 | import { UserAuthEffects } from './effects/user-auth.effect'; 36 | import { UserEffects } from './effects/user.effects'; 37 | import { ListEffects } from './effects/list.effects'; 38 | import { FeedsEffects } from './effects/feeds.effects'; 39 | import { UserListsEffects } from './effects/user-lists.effects'; 40 | 41 | /**ALL Ngrx Actions that can be fired in app loaded as one.*/ 42 | import actions from './actions'; 43 | 44 | /**Global Reducer of APP */ 45 | import reducer from './reducers'; 46 | 47 | //====================================================================== 48 | 49 | /**All Components in APP */ 50 | import { ComponentsModule } from './components'; 51 | 52 | /**All Routes in APP */ 53 | import { routing } from './app.routes'; 54 | 55 | /**All Containers in APP */ 56 | import { SignUpPageComponent } from './containers/signup-page/signu-page.component'; 57 | import { ListFeedPageComponent } from './containers/list-feed-page/list-feed-page.component'; 58 | import { SuggestionsPageComponent } from './containers/suggestions-page/suggestions-page.component'; 59 | import { DashboardPageComponent } from './containers/dashboard-page/dashboard-page.component'; 60 | import { LoginPageComponent } from './containers/login-page/login-page.component'; 61 | import { AppComponent } from './containers/app.component'; 62 | 63 | @NgModule({ 64 | declarations: [ 65 | AppComponent, 66 | LoginPageComponent, 67 | SignUpPageComponent, 68 | DashboardPageComponent, 69 | SuggestionsPageComponent, 70 | ListFeedPageComponent 71 | ], 72 | imports: [ 73 | ComponentsModule, 74 | NgSemanticModule, 75 | BrowserModule, 76 | FormsModule, 77 | ReactiveFormsModule, 78 | HttpModule, 79 | ComponentsModule, 80 | PipesModule, 81 | CommonModule, 82 | routing, 83 | // Importing RestangularModule and making default configs for restanglar 84 | RestangularModule.forRoot((RestangularProvider) => { 85 | RestangularProvider.setBaseUrl(environment.baseUrl); 86 | RestangularProvider.setDefaultHeaders({'Content-Type':'application/json'}); 87 | } 88 | ), 89 | StoreModule.provideStore(reducer), 90 | RouterStoreModule.connectRouter(), 91 | StoreDevtoolsModule.instrumentStore({ 92 | monitor: useLogMonitor({ 93 | visible: false, 94 | position: 'right' 95 | }) 96 | }), 97 | StoreLogMonitorModule, 98 | EffectsModule.run(UserAuthEffects), 99 | EffectsModule.run(UserEffects), 100 | EffectsModule.run(ListEffects), 101 | EffectsModule.run(FeedsEffects), 102 | EffectsModule.run(UserListsEffects) 103 | ], 104 | providers: [ 105 | actions, 106 | UserAuthService, 107 | AuthGuardService, 108 | ApiService, 109 | ResponseParseService 110 | ], 111 | bootstrap: [AppComponent] 112 | }) 113 | export class AppModule { } 114 | -------------------------------------------------------------------------------- /src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | /**Required Angular 2 Modules for Router */ 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { AuthGuardService } from './services/auth-guard.service'; 4 | 5 | /** Componets required for routing */ 6 | import { SuggestionsPageComponent } from './containers/suggestions-page/suggestions-page.component'; 7 | import { ListFeedPageComponent } from './containers/list-feed-page/list-feed-page.component'; 8 | import { FeedDetailComponent } from './components/feed-detail/feed-detail.component'; 9 | import { FeedsComponent } from './components/feeds/feeds.component'; 10 | import { DashboardPageComponent } from './containers/dashboard-page/dashboard-page.component'; 11 | import { SignUpPageComponent } from './containers/signup-page/signu-page.component'; 12 | import { LoginPageComponent } from './containers/login-page/login-page.component'; 13 | 14 | const routes: Routes = [ 15 | { path: '', redirectTo: 'login', pathMatch: 'full'}, 16 | { path: 'login', component: LoginPageComponent }, 17 | { path: 'request-email', component: SignUpPageComponent }, 18 | { path: 'dashboard', component: DashboardPageComponent, 19 | children: [ 20 | { path: '', redirectTo: 'feeds', pathMatch: 'full' }, 21 | { path: 'feeds', 22 | component: FeedsComponent, 23 | children: [ 24 | { path: 'tweet/:tweet_id', component: FeedDetailComponent } 25 | ] 26 | }, 27 | { 28 | path: 'feeds/:id', 29 | component: ListFeedPageComponent, 30 | children: [ 31 | { path: 'tweet/:tweet_id', component: FeedDetailComponent } 32 | ] 33 | }, 34 | { path: 'suggestions', 35 | component: SuggestionsPageComponent 36 | }, 37 | ], 38 | canLoad: [AuthGuardService] 39 | } 40 | ]; 41 | 42 | export const routing = RouterModule.forRoot(routes); -------------------------------------------------------------------------------- /src/app/components/feed-detail/fd-summary/fd-summary.component.css: -------------------------------------------------------------------------------- 1 | .center-pad { 2 | padding: 25px; 3 | } 4 | 5 | .rounded-border{ 6 | border-radius: 5px; 7 | } 8 | 9 | .full-width-hr{ 10 | width: 100%; 11 | } -------------------------------------------------------------------------------- /src/app/components/feed-detail/fd-summary/fd-summary.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |
9 | 10 | {{feed.user.name}} 11 | 12 |
13 |
14 | @{{feed.user.screen_name}} 15 |
16 |
17 |
18 |
19 |
20 |

21 | 22 |

23 |
24 |
25 |
26 |
27 |

Created at: {{feed.created_at | date:'short'}}

28 |
29 |
30 |
31 |
32 |
33 | 34 | 35 | {{ feed.retweet_count }} 36 | 37 | 38 | 39 | {{ feed.favorite_count }} 40 | 41 |
42 |
43 |
44 | 45 |
46 |
47 |
48 | 50 |
Submit Reply
51 |
52 |
53 |
54 |
55 |
56 |
57 | {{ reply.text }} 58 |
59 |
60 |
61 |
62 |
63 | Show this project some ❤ on Github 64 |
65 |
66 |
67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /src/app/components/feed-detail/fd-summary/fd-summary.component.ts: -------------------------------------------------------------------------------- 1 | import { FeedsActions } from './../../../actions/feeds.actions'; 2 | import { ApiService } from './../../../services/api.service'; 3 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 4 | @Component({ 5 | selector: 'ist-fd-summary', 6 | templateUrl: './fd-summary.component.html', 7 | styleUrls: ['./fd-summary.component.css'] 8 | }) 9 | export class FdSummaryComponent { 10 | @Input() feed: any; 11 | @Output() replyClicked = new EventEmitter(); 12 | mentions; 13 | constructor(private api: ApiService){ 14 | } 15 | 16 | reply(message: any){ 17 | var payload = { feed: this.feed, message: message} 18 | this.replyClicked.emit(payload); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/components/feed-detail/feed-detail.component.css: -------------------------------------------------------------------------------- 1 | .center-align{ 2 | text-align: center; 3 | } -------------------------------------------------------------------------------- /src/app/components/feed-detail/feed-detail.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/components/feed-detail/feed-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Router, ActivatedRoute } from '@angular/router'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Store } from '@ngrx/store'; 5 | import { AppState, getSelectedFeed } from '../../reducers/index'; 6 | import { Subscription } from 'rxjs'; 7 | import { ApiService } from '../../services/api.service'; 8 | import { FeedsActions } from '../../actions/feeds.actions'; 9 | import { back } from '@ngrx/router-store'; 10 | 11 | declare var $: any; 12 | 13 | @Component({ 14 | selector: 'ist-feed-detail', 15 | templateUrl: './feed-detail.component.html', 16 | styleUrls: ['./feed-detail.component.css'], 17 | changeDetection: ChangeDetectionStrategy.OnPush 18 | }) 19 | export class FeedDetailComponent implements OnInit, OnDestroy { 20 | private subscription: Subscription; 21 | feed: Observable; 22 | feedId: any; 23 | constructor(public store: Store, 24 | private router: Router, 25 | private route: ActivatedRoute, 26 | private feedActions: FeedsActions) { 27 | this.feed = this.store.select(getSelectedFeed); 28 | } 29 | 30 | ngOnInit() { 31 | this.subscription = this.route.params.subscribe( 32 | (params: any) => { 33 | this.feedId = params['tweet_id']; 34 | this.store.dispatch(this.feedActions.selectFeed(this.feedId)); 35 | } 36 | ); 37 | this.loadModal(); 38 | } 39 | 40 | reply(messageWithFeed: any){ 41 | this.store.dispatch(this.feedActions.reply(messageWithFeed)); 42 | } 43 | 44 | loadModal(){ 45 | let that = this; 46 | try{ 47 | $('.ui.modal').modal({ 48 | onApprove : function() { return false; }, 49 | onHide: function(){ 50 | that.store.dispatch(back()); 51 | $('.ui.modal').remove(); 52 | } 53 | }).modal('show'); 54 | } catch(e) { 55 | 56 | console.log("Error is",e); 57 | } 58 | } 59 | 60 | ngOnDestroy(){ 61 | this.subscription.unsubscribe(); 62 | } 63 | } -------------------------------------------------------------------------------- /src/app/components/feed/feed.component.css: -------------------------------------------------------------------------------- 1 | .clickable:hover{ 2 | cursor: pointer; 3 | } -------------------------------------------------------------------------------- /src/app/components/feed/feed.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{ feed.user.name }}
3 |
4 | @{{feed.user.screen_name}} 5 |
6 |
7 |

8 |
9 |
10 |
11 |
12 | 13 | 15 | 17 | {{ feed.retweet_count }}   18 | 19 | 20 | 22 | 24 | {{ feed.favorite_count }} 25 | 26 |
27 |
28 | {{feed.user.name}} 29 |
30 |
-------------------------------------------------------------------------------- /src/app/components/feed/feed.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 2 | import { Tweet } from '../../models' 3 | 4 | declare var $: any; 5 | 6 | @Component({ 7 | selector: 'ist-feed', 8 | templateUrl: './feed.component.html', 9 | styleUrls: ['./feed.component.css'] 10 | }) 11 | export class FeedComponent { 12 | @Input() feed: any; 13 | @Output() favClicked = new EventEmitter(); 14 | @Output() removeFavClicked = new EventEmitter(); 15 | @Output() retweetClicked = new EventEmitter(); 16 | /**Add to fav 17 | * 18 | * Note: to Fav a tweet it needs a `id_str` not `id` of a tweet 19 | */ 20 | addtoFavClicked(){ 21 | this.favClicked.emit(this.feed); 22 | } 23 | 24 | removeFromFavClicked(){ 25 | this.removeFavClicked.emit(this.feed); 26 | } 27 | 28 | retweetBtnClicked(){ 29 | this.retweetClicked.emit(this.feed); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/app/components/feeds/feeds.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/src/app/components/feeds/feeds.component.css -------------------------------------------------------------------------------- /src/app/components/feeds/feeds.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 8 | 9 |
10 |
11 |
12 | 13 | -------------------------------------------------------------------------------- /src/app/components/feeds/feeds.component.ts: -------------------------------------------------------------------------------- 1 | import { Component,OnInit } from '@angular/core'; 2 | import { ApiService } from '../../services/api.service'; 3 | import { Store } from '@ngrx/store'; 4 | import { AppState, getAllFeeds } from '../../reducers'; 5 | import { FeedsActions } from '../../actions/feeds.actions'; 6 | import { Observable } from 'rxjs/Observable'; 7 | import { Router } from '@angular/router'; 8 | 9 | declare var $: any; 10 | 11 | @Component({ 12 | selector: 'ist-feeds', 13 | templateUrl: './feeds.component.html', 14 | styleUrls: ['./feeds.component.css'] 15 | }) 16 | export class FeedsComponent implements OnInit { 17 | feeds: Observable; 18 | constructor(private store: Store, 19 | private feedActions: FeedsActions, 20 | private router: Router) { 21 | this.feeds = this.store.select(getAllFeeds); 22 | } 23 | 24 | ngOnInit() { 25 | this.store.dispatch(this.feedActions.getAllFeeds()); 26 | } 27 | 28 | /** 29 | * Dispatch a store action to add a particular 30 | * feed to fav 31 | * 32 | * @param feed 33 | */ 34 | addToFav(feed){ 35 | this.store.dispatch(this.feedActions.addFeedToFav(feed)); 36 | } 37 | 38 | /** 39 | * Dispatch a store action to remove a particular 40 | * feed from fav 41 | * 42 | * @param feed 43 | */ 44 | removeFromFav(feed){ 45 | this.store.dispatch(this.feedActions.removeFeedFromFav(feed)); 46 | } 47 | 48 | /** 49 | * Dispatch a store action to retweet a particular 50 | * feed 51 | * 52 | * @param feed 53 | */ 54 | retweet(feed){ 55 | this.store.dispatch(this.feedActions.retweet(feed)); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/app/components/index.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | import { PipesModule } from '../pipes'; 6 | 7 | /** Components */ 8 | import { FeedComponent } from './feed/feed.component'; 9 | import { SuggestedListsComponent } from './suggested-lists/suggested-lists.component'; 10 | import { FeedDetailComponent } from './feed-detail/feed-detail.component'; 11 | import { ModalComponent } from './shared/modal/modal.component'; 12 | import { FeedsComponent } from './feeds/feeds.component'; 13 | import { FdSummaryComponent } from './feed-detail/fd-summary/fd-summary.component'; 14 | 15 | export const COMPONENTS = [ 16 | FeedComponent, 17 | FeedsComponent, 18 | ModalComponent, 19 | FeedDetailComponent, 20 | SuggestedListsComponent, 21 | FdSummaryComponent 22 | ]; 23 | 24 | @NgModule({ 25 | imports: [ 26 | CommonModule, 27 | ReactiveFormsModule, 28 | RouterModule, 29 | PipesModule 30 | ], 31 | declarations: COMPONENTS, 32 | exports: COMPONENTS 33 | }) 34 | export class ComponentsModule { } -------------------------------------------------------------------------------- /src/app/components/shared/modal/modal.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/src/app/components/shared/modal/modal.component.css -------------------------------------------------------------------------------- /src/app/components/shared/modal/modal.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/shared/modal/modal.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | AfterViewInit, 4 | trigger, 5 | state, 6 | style, 7 | transition, 8 | animate 9 | } from '@angular/core'; 10 | 11 | @Component({ 12 | selector: 'ist-modal', 13 | templateUrl: './modal.component.html', 14 | styleUrls: ['./modal.component.css'], 15 | animations: [ 16 | trigger('flyInUp', [ 17 | state('in', style({ transform: 'translateY(-50%)' })), 18 | transition('void => *', [ 19 | style({ transform: 'translateY(100%)' }), 20 | animate(500) 21 | ]), 22 | transition('* => void', [ 23 | animate(500, style({ transform: 'translateY(-100%)' })) 24 | ]) 25 | ]) 26 | ] 27 | }) 28 | export class ModalComponent { 29 | } 30 | -------------------------------------------------------------------------------- /src/app/components/suggested-lists/suggested-lists.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/src/app/components/suggested-lists/suggested-lists.component.css -------------------------------------------------------------------------------- /src/app/components/suggested-lists/suggested-lists.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{list.name | capitalize | stringify }} 3 |
4 | Joined in 2013 5 |
6 |
7 |
8 | {{ list.description}} 9 |
10 |
11 | Amazing collection of {{ list.name }} coders 12 |
13 |
14 |
15 |
16 | Follow 17 |
18 |
19 | UnFollow 20 |
-------------------------------------------------------------------------------- /src/app/components/suggested-lists/suggested-lists.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ist-suggested-lists', 5 | templateUrl: './suggested-lists.component.html', 6 | styleUrls: ['./suggested-lists.component.css'] 7 | }) 8 | export class SuggestedListsComponent { 9 | @Input() list; 10 | @Output() followClicked = new EventEmitter(); 11 | @Output() unFollowClicked = new EventEmitter(); 12 | 13 | followList(){ 14 | this.followClicked.emit(this.list.id); 15 | } 16 | 17 | unFollowList(){ 18 | this.unFollowClicked.emit(this.list.id); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/containers/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/src/app/containers/app.component.css -------------------------------------------------------------------------------- /src/app/containers/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/containers/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Store } from '@ngrx/store'; 4 | import { AppState, getLoginState } from '../reducers/index'; 5 | import { LoginActions } from '../actions/login.actions'; 6 | import { UserAuthService } from '../services/user-auth.service'; 7 | 8 | 9 | @Component({ 10 | selector: 'ist-root', 11 | templateUrl: './app.component.html', 12 | styleUrls: ['./app.component.css'] 13 | }) 14 | export class AppComponent { 15 | constructor(private router: Router, 16 | private loginActions: LoginActions, 17 | private store: Store, 18 | private api: UserAuthService){ 19 | 20 | this.store.let(getLoginState()) 21 | .filter(state => state.server_token !== null) 22 | .subscribe(() => this.router.navigate(['/dashboard'])); 23 | } 24 | ngOnInit(){ 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/containers/dashboard-page/dashboard-page.component.css: -------------------------------------------------------------------------------- 1 | /* 2 | Make Background light 3 | Greyish and make it acquire full height 4 | */ 5 | .dashboard { 6 | background: #F1F3FA; 7 | height: 100vh; 8 | } 9 | 10 | .profile { 11 | margin: 20px 0; 12 | } 13 | 14 | .profile-sidebar { 15 | padding: 20px 0 10px 0; 16 | background: #fff; 17 | } 18 | 19 | /*SideBar UserPic*/ 20 | .profile-userpic img { 21 | float: none; 22 | margin: 0 auto; 23 | width: 50%; 24 | height: 50%; 25 | -webkit-border-radius: 50% !important; 26 | -moz-border-radius: 50% !important; 27 | border-radius: 50% !important; 28 | } 29 | 30 | 31 | .profile-usertitle { 32 | text-align: center; 33 | margin-top: 20px; 34 | } 35 | 36 | .profile-usertitle-name { 37 | color: #5a7391; 38 | font-size: 16px; 39 | font-weight: 600; 40 | margin-bottom: 7px; 41 | } 42 | 43 | .item::hover{ 44 | cursor: pointer; 45 | } 46 | 47 | a:hover { 48 | cursor: pointer; 49 | } 50 | 51 | .list-link { 52 | padding: 10px 0px 10px 5px; 53 | } 54 | 55 | .list-link:hover{ 56 | cursor: pointer; 57 | } -------------------------------------------------------------------------------- /src/app/containers/dashboard-page/dashboard-page.component.html: -------------------------------------------------------------------------------- 1 |
2 | 53 |
54 |
55 | 56 |
57 |
58 |
-------------------------------------------------------------------------------- /src/app/containers/dashboard-page/dashboard-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Store } from '@ngrx/store'; 5 | import { AppState, getUserList, getUserState } from '../../reducers/index'; 6 | import { UserActions } from '../../actions/user.actions'; 7 | import { LoginActions } from '../../actions/login.actions'; 8 | import { User } from '../../models/'; 9 | import { UserListActions } from '../../actions/user-list.actions'; 10 | import { go } from '@ngrx/router-store'; 11 | 12 | @Component({ 13 | selector: 'ist-dashboard-page', 14 | templateUrl: './dashboard-page.component.html', 15 | styleUrls: ['./dashboard-page.component.css'] 16 | }) 17 | export class DashboardPageComponent implements OnInit { 18 | lists; 19 | userList$: Observable; 20 | user$: Observable; 21 | constructor(private router: Router, 22 | private userActions: UserActions, 23 | private loginActions: LoginActions, 24 | private userListActions: UserListActions, 25 | private store: Store) { 26 | this.userList$ = this.store.select(getUserList); 27 | this.user$ = this.store.select(getUserState); 28 | } 29 | 30 | signOutUser(){ 31 | this.store.dispatch(this.loginActions.logout()); 32 | } 33 | 34 | ngOnInit() { 35 | this.store.dispatch(this.userListActions.getUserLists()); 36 | this.store.dispatch(this.userActions.loadProfile()); 37 | } 38 | 39 | backToHome(){ 40 | this.store.dispatch(go(['/dashboard/feeds'])) 41 | } 42 | 43 | goToSuggestionPage(){ 44 | this.store.dispatch(go('/dashboard/suggestions')) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/containers/list-feed-page/list-feed-page.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/src/app/containers/list-feed-page/list-feed-page.component.css -------------------------------------------------------------------------------- /src/app/containers/list-feed-page/list-feed-page.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /src/app/containers/list-feed-page/list-feed-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router, ActivatedRoute } from '@angular/router'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Store } from '@ngrx/store'; 5 | import { AppState, getSelectedUserListIdFeeds } from '../../reducers/index'; 6 | import { Subscription } from 'rxjs'; 7 | import { FeedsActions } from '../../actions/feeds.actions'; 8 | 9 | @Component({ 10 | selector: 'ist-list-feed-page', 11 | templateUrl: './list-feed-page.component.html', 12 | styleUrls: ['./list-feed-page.component.css'] 13 | }) 14 | export class ListFeedPageComponent implements OnInit { 15 | private subscription: Subscription; 16 | private userListId: string; 17 | feeds: Observable; 18 | 19 | constructor(private router: Router, 20 | private route: ActivatedRoute, 21 | private store: Store, 22 | private feedsActions: FeedsActions) { 23 | this.feeds = this.store.select(getSelectedUserListIdFeeds); 24 | } 25 | 26 | ngOnInit() { 27 | this.subscription = this.route.params.subscribe( 28 | (params: any) => { 29 | this.userListId = params['id']; 30 | this.store.dispatch(this.feedsActions.getFeedsForId(this.userListId)); 31 | }) 32 | } 33 | 34 | /** 35 | * Dispatch a store action to add a particular 36 | * feed to fav 37 | * 38 | * @param feed 39 | */ 40 | addToFav(feed){ 41 | this.store.dispatch(this.feedsActions.addFeedToFav(feed)); 42 | } 43 | 44 | /** 45 | * Dispatch a store action to remove a particular 46 | * feed from fav 47 | * 48 | * @param feed 49 | */ 50 | removeFromFav(feed){ 51 | this.store.dispatch(this.feedsActions.removeFeedFromFav(feed)); 52 | } 53 | 54 | /** 55 | * Dispatch a store action to retweet a particular 56 | * feed 57 | * 58 | * @param feed 59 | */ 60 | retweet(feed){ 61 | this.store.dispatch(this.feedsActions.retweet(feed)); 62 | } 63 | 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/app/containers/login-page/login-page.component.css: -------------------------------------------------------------------------------- 1 | .lightbox { 2 | position: fixed; 3 | top: 0px; 4 | left: 0px; 5 | width: 100%; 6 | height: 100%; 7 | background: lightblue; 8 | text-align: center; 9 | } 10 | 11 | .tc-content { 12 | position: relative; 13 | top: 30%; 14 | } -------------------------------------------------------------------------------- /src/app/containers/login-page/login-page.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/containers/login-page/login-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | import {Observable} from 'rxjs/Observable'; 3 | import 'rxjs'; 4 | import { Store } from '@ngrx/store'; 5 | import { AppState, getLoginState } from '../../reducers/index'; 6 | import { LoginActions } from '../../actions/login.actions'; 7 | 8 | @Component({ 9 | selector: 'ist-login-page', 10 | templateUrl: './login-page.component.html', 11 | styleUrls: ['./login-page.component.css'], 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | providers: [LoginActions] 14 | }) 15 | export class LoginPageComponent implements OnInit { 16 | photo; 17 | constructor(private loginActions: LoginActions, 18 | private store: Store) { 19 | } 20 | 21 | ngOnInit() { 22 | } 23 | 24 | login(){ 25 | this.store.dispatch(this.loginActions.login('twitter')) 26 | } 27 | signUp(){ 28 | this.store.dispatch(this.loginActions.signUp('twitter')) 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/app/containers/signup-page/signu-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | import {Observable} from 'rxjs/Observable'; 4 | import 'rxjs'; 5 | import { Store } from '@ngrx/store'; 6 | import { AppState,getLoginState } from '../../reducers/index'; 7 | import { LoginActions } from '../../actions/login.actions'; 8 | 9 | @Component({ 10 | selector: 'ist-signup-page', 11 | templateUrl: './signup-page.component.html', 12 | styleUrls: ['./signup-page.component.css'] 13 | }) 14 | export class SignUpPageComponent implements OnInit { 15 | emailForm: FormGroup; 16 | email; 17 | userAuth: any; 18 | constructor(private fb: FormBuilder, 19 | private loginActions: LoginActions, 20 | private store: Store) { 21 | this.store.select(state => this.userAuth = state.userAuth).subscribe() 22 | } 23 | 24 | ngOnInit() { 25 | this.emailForm = this.fb.group({ 26 | 'email': ['', Validators.required] 27 | }); 28 | } 29 | 30 | login() { 31 | this.store.dispatch(this.loginActions.storeUser(this.email, this.userAuth)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/containers/signup-page/signup-page.component.css: -------------------------------------------------------------------------------- 1 | .lightbox { 2 | position: fixed; 3 | top: 0px; 4 | left: 0px; 5 | width: 100%; 6 | height: 100%; 7 | background: lightblue; 8 | text-align: center; 9 | } 10 | 11 | .tc-content { 12 | position: relative; 13 | top: 30%; 14 | } -------------------------------------------------------------------------------- /src/app/containers/signup-page/signup-page.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/containers/suggestions-page/suggestions-page.component.css: -------------------------------------------------------------------------------- 1 | .card { 2 | 3 | } -------------------------------------------------------------------------------- /src/app/containers/suggestions-page/suggestions-page.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 7 | 8 |
9 |
10 |
11 |
12 | 15 |
16 |
-------------------------------------------------------------------------------- /src/app/containers/suggestions-page/suggestions-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ApiService } from '../../services/api.service'; 3 | import { Router } from '@angular/router'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import { Store } from '@ngrx/store'; 6 | import { AppState, getLists } from '../../reducers/index'; 7 | import { ListActions } from '../../actions/list.actions'; 8 | import { List } from '../../models/list'; 9 | 10 | @Component({ 11 | selector: 'ist-suggestions-page', 12 | templateUrl: './suggestions-page.component.html', 13 | styleUrls: ['./suggestions-page.component.css'], 14 | providers: [ApiService] 15 | }) 16 | export class SuggestionsPageComponent implements OnInit { 17 | suggestedList$: Observable; 18 | 19 | constructor(private api: ApiService, 20 | private router: Router, 21 | private listActions: ListActions, 22 | private store: Store) { 23 | this.suggestedList$ = this.store.select(getLists); 24 | } 25 | 26 | ngOnInit() { 27 | this.store.dispatch(this.listActions.retriveLists()) 28 | } 29 | 30 | follow(listId){ 31 | this.store.dispatch(this.listActions.follow(listId)); 32 | } 33 | 34 | unfollow(listId){ 35 | this.store.dispatch(this.listActions.unFollowList(listId)); 36 | } 37 | 38 | goToFeedsDashboard(){ 39 | this.router.navigate(['/dashboard']); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/effects/feeds.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Effect, Actions } from '@ngrx/effects'; 3 | import { ActionTypes, FeedsActions } from '../actions/feeds.actions'; 4 | import { Action } from '@ngrx/store'; 5 | import { ApiService } from '../services/api.service'; 6 | import { ResponseParseService } from '../services/response-parse.service'; 7 | import { Tweet } from '../models'; 8 | 9 | @Injectable() 10 | export class FeedsEffects { 11 | constructor(private actions$: Actions, 12 | private feedsActions: FeedsActions, 13 | private apiService: ApiService, 14 | private responseParser: ResponseParseService 15 | ) { } 16 | 17 | @Effect() getFeedsForId$ = this.actions$ 18 | .ofType(ActionTypes.GET_FEEDS_FOR_ID) 19 | .map((action: Action) => action.payload) 20 | .switchMap((userListId: any) => this.apiService.getListsTimeLine(userListId)) 21 | .map((response: any) => this.responseParser.createTweetsObj(response)) 22 | .map((tweets: Tweet[]) => this.feedsActions.getFeedsForIdSuccess(tweets)); 23 | 24 | 25 | @Effect() getAllFeeds$ = this.actions$ 26 | .ofType(ActionTypes.GET_ALL_FEEDS) 27 | .switchMap(() => this.apiService.all_feeds()) 28 | .map((response: any) => this.responseParser.createTweetsObj(response)) 29 | .map((tweets: Tweet[]) => this.feedsActions.getAllFeedsSuccess(tweets)); 30 | 31 | @Effect() addFeedToFav$ = this.actions$ 32 | .ofType(ActionTypes.ADD_FEED_TO_FAV) 33 | .map((action: Action) => action.payload) 34 | .switchMap((feed: any) => this.apiService.addToFav(feed)) 35 | .map((response: any) => this.feedsActions.addFeedToFavSuccess(response.feed)); 36 | 37 | @Effect() removeFeedFromFav$ = this.actions$ 38 | .ofType(ActionTypes.REMOVE_FEED_FROM_FAV) 39 | .map((action: Action) => action.payload) 40 | .switchMap((feed: any) => this.apiService.removeFromFav(feed)) 41 | .map((response: any) => this.feedsActions.removeFeedFromFavSuccess(response.feed)); 42 | 43 | @Effect() retweet$ = this.actions$ 44 | .ofType(ActionTypes.RETWEET) 45 | .map((action: Action) => action.payload) 46 | .switchMap((feed: any) => this.apiService.retweet(feed)) 47 | .map((response: any) => this.feedsActions.retweetSuccess(response.feed)); 48 | 49 | @Effect() reply$ = this.actions$ 50 | .ofType(ActionTypes.REPLY) 51 | .map((action: Action) => action.payload) 52 | .switchMap((messageWithFeed: any) => this.apiService.reply(messageWithFeed)) 53 | .map((response: any) => this.feedsActions.replySuccess(response.feed)); 54 | } 55 | -------------------------------------------------------------------------------- /src/app/effects/list.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Effect, Actions } from '@ngrx/effects'; 3 | import { ActionTypes, ListActions } from '../actions/list.actions'; 4 | import { Action } from '@ngrx/store'; 5 | import { ApiService } from '../services/api.service'; 6 | import { ResponseParseService } from '../services/response-parse.service'; 7 | import { List } from '../models'; 8 | 9 | @Injectable() 10 | export class ListEffects { 11 | constructor( 12 | private actions$: Actions, 13 | private listActions: ListActions, 14 | private apiService: ApiService, 15 | private responseParser: ResponseParseService 16 | ){ } 17 | 18 | @Effect() retriveLists$ = this.actions$ 19 | .ofType(ActionTypes.RETRIVE_LISTS) 20 | .switchMap(() => this.apiService.retriveSuggestion()) 21 | .filter(response => response !== null ) 22 | .map(response => this.responseParser.createSuggestedListsObj(response)) 23 | .map((lists: List[]) => this.listActions.retriveListsSuccess(lists)); 24 | 25 | @Effect() followList$ = this.actions$ 26 | .ofType(ActionTypes.FOLLOW_LIST) 27 | .map((action: Action) => action.payload) 28 | .switchMap((listId: string) => this.apiService.followList(listId)) 29 | .map((response) => this.listActions.followSuccess(response)) 30 | 31 | 32 | @Effect() followListSuccess$ = this.actions$ 33 | .ofType(ActionTypes.FOLLOW_LIST_SUCCESS) 34 | .map((action: Action) => action.payload.new_list) 35 | .map((new_list: any) => this.responseParser.createSuggestedListsObj(new_list)) 36 | .map((updatedLists: List[]) => this.listActions.updateLists(updatedLists)); 37 | 38 | @Effect() unFollowList$ = this.actions$ 39 | .ofType(ActionTypes.UNFOLLOW_LIST) 40 | .map((action: Action) => action.payload) 41 | .switchMap((listId: string) => this.apiService.unFollowList(listId)) 42 | .map((response: any) => this.listActions.unFollowListSuccess(response)); 43 | } 44 | -------------------------------------------------------------------------------- /src/app/effects/user-auth.effect.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Effect, Actions } from '@ngrx/effects'; 3 | import { ActionTypes, LoginActions } from '../actions/login.actions'; 4 | import { Action } from '@ngrx/store'; 5 | import { UserAuthService } from '../services/user-auth.service'; 6 | import { UserAuth } from '../models'; 7 | import { go } from '@ngrx/router-store'; 8 | 9 | @Injectable() 10 | export class UserAuthEffects { 11 | constructor(private actions$: Actions, 12 | private loginActions: LoginActions, 13 | private userAuthService: UserAuthService 14 | ){} 15 | 16 | @Effect() login$ = this.actions$ 17 | .ofType(ActionTypes.LOGIN) 18 | .map((action: Action) => action.payload) 19 | .switchMap((payload: any) => this.userAuthService.login()) 20 | .map((userAuth: UserAuth) => { 21 | this.userAuthService.storeUserAuthInLocalstorage(userAuth); 22 | return this.loginActions.loginSuccess(userAuth); 23 | } ); 24 | 25 | @Effect() loginSuccess$ = this.actions$ 26 | .ofType(ActionTypes.LOGIN_SUCCESS) 27 | .map((action: Action) => action.payload) 28 | .switchMap((userAuth: UserAuth) => this.userAuthService.loginServer(userAuth)) 29 | .map((userAuth: UserAuth) => { 30 | if(userAuth.server_token){ 31 | this.userAuthService.storeServerToken(userAuth.server_token); 32 | return this.loginActions.loginServerSuccess(userAuth); 33 | } else{ 34 | // User is Not present but he has authrised his twitter 35 | // account with our app hence taking him to request email page 36 | return this.loginActions.signUpSuccess(userAuth); 37 | } 38 | }) 39 | 40 | @Effect() signup$ = this.actions$ 41 | .ofType(ActionTypes.SIGNUP) 42 | .map((action: Action) => action.payload) 43 | .switchMap((payload) => this.userAuthService.signUp()) 44 | .map((userAuth: UserAuth) => { 45 | this.userAuthService.storeUserAuthInLocalstorage(userAuth); 46 | return this.loginActions.signUpSuccess(userAuth); 47 | } ); 48 | 49 | 50 | @Effect() signUpSucces$ = this.actions$ 51 | .ofType(ActionTypes.SIGNUP_SUCCESS) 52 | .map(() => go(['/request-email'])); 53 | 54 | 55 | @Effect() storeUser$ = this.actions$ 56 | .ofType(ActionTypes.STORE_USER) 57 | .map((action: Action) => action.payload) 58 | .switchMap((payload) => this.userAuthService.storeUsertoBackend(payload)) 59 | .map((userAuth) => { 60 | this.userAuthService.storeServerToken(userAuth.server_token); 61 | return this.loginActions.storeUserSuccess(userAuth) 62 | }) 63 | 64 | @Effect() storeUserSuccess$ = this.actions$ 65 | .ofType(ActionTypes.STORE_USER_SUCCESS) 66 | .map(() => go(['/dashboard/suggestions'])); 67 | 68 | @Effect() logout$ = this.actions$ 69 | .ofType(ActionTypes.LOGOUT) 70 | .switchMap(() => this.userAuthService.logout()) 71 | .map(() => this.loginActions.logoutSuccess(UserAuth)) 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/app/effects/user-lists.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Effect, Actions } from '@ngrx/effects'; 3 | import { ActionTypes,UserListActions } from '../actions/user-list.actions'; 4 | import { Action } from '@ngrx/store'; 5 | import { ApiService } from '../services/api.service'; 6 | import { ResponseParseService } from '../services/response-parse.service'; 7 | import { UserList } from '../models'; 8 | import {Observable} from 'rxjs/Observable'; 9 | 10 | @Injectable() 11 | export class UserListsEffects { 12 | constructor( 13 | private actions$: Actions, 14 | private userListActions: UserListActions, 15 | private apiService: ApiService, 16 | private reponseParser: ResponseParseService 17 | ){ } 18 | 19 | @Effect() getUserLists$ = this.actions$ 20 | .ofType(ActionTypes.GET_USER_LISTS) 21 | .switchMap(() => this.apiService.getUserLists()) 22 | .map((response:any) => this.reponseParser.createUserListsobj(response)) 23 | .map((userLists: UserList[]) => this.userListActions.getUserListsSuccess(userLists)); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/app/effects/user.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Effect, Actions } from '@ngrx/effects'; 3 | import { ActionTypes, UserActions } from '../actions/user.actions'; 4 | import { Action } from '@ngrx/store'; 5 | import { ApiService } from '../services/api.service'; 6 | import { ResponseParseService } from '../services/response-parse.service'; 7 | import { User } from '../models'; 8 | 9 | @Injectable() 10 | export class UserEffects { 11 | 12 | constructor( 13 | private actions$: Actions, 14 | private userActions: UserActions, 15 | private api: ApiService, 16 | private responseParser: ResponseParseService 17 | ){ } 18 | 19 | @Effect() loadUserProfile$ = this.actions$ 20 | .ofType(ActionTypes.LOAD_PROFILE) 21 | .switchMap(() => this.api.getUserDetail()) 22 | .map((response:any) => this.responseParser.createUserObj(response)) 23 | .map((user: User) => this.userActions.loadProfileSuccess(user)) 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './containers/app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /src/app/models/base.ts: -------------------------------------------------------------------------------- 1 | export class Base { 2 | public id: any; 3 | 4 | constructor(attributes?) { 5 | if(attributes){ 6 | let keys = Object.keys(attributes) 7 | if (keys.length) { 8 | keys.forEach(el => { 9 | this[el] = attributes[el]; 10 | }) 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/app/models/entities.ts: -------------------------------------------------------------------------------- 1 | import { Urls } from './urls'; 2 | import { Base } from './base'; 3 | export class Entities extends Base{ 4 | public hashtags: Array; 5 | public symbols: Array; 6 | public urls: Urls[] 7 | } -------------------------------------------------------------------------------- /src/app/models/index.ts: -------------------------------------------------------------------------------- 1 | export { UserAuth } from './user-auth'; 2 | export { UserProfile } from './user-profile'; 3 | export { User } from './user'; 4 | export { List } from './list'; 5 | export { UserList } from './user-list'; 6 | export { Tweet } from './tweet'; 7 | export { Entities } from './entities' 8 | export { Urls } from './urls'; 9 | export { Base } from './base'; -------------------------------------------------------------------------------- /src/app/models/list.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | 3 | export class List extends Base { 4 | name: string; 5 | description: string; 6 | image_url: string; 7 | isFollowing: boolean; 8 | } -------------------------------------------------------------------------------- /src/app/models/tweet.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | import { User } from './user'; 3 | import { Entities } from './entities'; 4 | export class Tweet extends Base { 5 | created_at: string 6 | text: string; 7 | retweet_count: number; 8 | favorite_count: number; 9 | user: User; 10 | user_list_id: string; 11 | entities: Entities; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/models/urls.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | 3 | export class Urls extends Base { 4 | url: string; 5 | expanded_url: string; 6 | display_url: string; 7 | indices: Array; 8 | } -------------------------------------------------------------------------------- /src/app/models/user-auth.ts: -------------------------------------------------------------------------------- 1 | export class UserAuth { 2 | constructor( 3 | public user_id: string = localStorage.getItem('user_id'), 4 | public access_token: string = localStorage.getItem('access_token'), 5 | public secret_token: string = localStorage.getItem('secret_token'), 6 | public server_token: string = localStorage.getItem('server_token') 7 | ) { } 8 | } -------------------------------------------------------------------------------- /src/app/models/user-list.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | 3 | export class UserList extends Base { 4 | list_id: any; 5 | twitter_list_id: string; 6 | slug: string; 7 | name: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/models/user-profile.ts: -------------------------------------------------------------------------------- 1 | export class UserProfile { 2 | constructor(public first_name: string = null, 3 | public last_name: string = null) {} 4 | } -------------------------------------------------------------------------------- /src/app/models/user.ts: -------------------------------------------------------------------------------- 1 | import { UserProfile } from './user-profile'; 2 | import { Base } from './base'; 3 | import { Entities } from './entities'; 4 | 5 | export class User extends Base { 6 | name: string; 7 | screen_name: string; 8 | profile_image_url: string; 9 | description: string; 10 | location: string; 11 | entities: Entities; 12 | } -------------------------------------------------------------------------------- /src/app/pipes/capitalize.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'capitalize' }) 4 | export class CapitalizePipe implements PipeTransform { 5 | 6 | transform(value: any) { 7 | if (value) { 8 | return value.charAt(0).toUpperCase() + value.slice(1); 9 | } 10 | return value; 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /src/app/pipes/index.ts: -------------------------------------------------------------------------------- 1 | import { MentionsPipe } from './mentions'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { LinkifyPipe } from './linkify'; 5 | import { CapitalizePipe } from './capitalize'; 6 | import { StringifyPipe } from './stringify'; 7 | 8 | export const PIPES = [ 9 | LinkifyPipe, 10 | CapitalizePipe, 11 | StringifyPipe, 12 | MentionsPipe 13 | ]; 14 | 15 | @NgModule({ 16 | declarations: PIPES, 17 | exports: PIPES 18 | }) 19 | export class PipesModule { } -------------------------------------------------------------------------------- /src/app/pipes/linkify.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { Urls } from '../models'; 3 | 4 | @Pipe({name: 'linkify'}) 5 | export class LinkifyPipe implements PipeTransform { 6 | 7 | transform(text: string, urls: Urls[] ): string { 8 | return this.linkify(text, urls) 9 | } 10 | 11 | private linkify(text: string, urls: Urls[]): any { 12 | var replacedText = text; 13 | urls.forEach(el => { 14 | replacedText = replacedText.replace(el.url, '' + el.display_url + '') 15 | }) 16 | return replacedText; 17 | } 18 | } -------------------------------------------------------------------------------- /src/app/pipes/mentions.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | /** 4 | * Converts a array of mentions into a string 5 | * to show in input box of reply 6 | * 7 | * @param { Array } mentions 8 | * E.g : [{sceen_name: 'coderaga'}, {screen_name: '_voidzero'}] 9 | * 10 | * @return A String with all mentions in it if any 11 | * E.g : "@coderaga @_voidzero" 12 | */ 13 | @Pipe({name: 'mentions'}) 14 | export class MentionsPipe implements PipeTransform { 15 | transform(mentions: Array, ownerScreenName: string){ 16 | 17 | var text = ""; 18 | var newMentions = this.getAllMentionsArray(mentions, ownerScreenName) 19 | 20 | newMentions.forEach(el => { 21 | text = text + "@" + el.screen_name + " "; 22 | }) 23 | return text; 24 | } 25 | 26 | private getAllMentionsArray(mentions, ownerScreenName){ 27 | var ownerScreenNameObj = { screen_name: ownerScreenName }; 28 | if(mentions){ 29 | mentions.push(ownerScreenNameObj); 30 | return mentions; 31 | } 32 | else{ 33 | var newMentions = []; 34 | newMentions.push(ownerScreenNameObj); 35 | return newMentions; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/app/pipes/stringify.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | 4 | @Pipe({ name: 'stringify' }) 5 | export class StringifyPipe implements PipeTransform { 6 | transform(value: any) { 7 | if (value) { 8 | return value.replace(/\d+|[-_]/g, ''); 9 | } 10 | return value; 11 | } 12 | } -------------------------------------------------------------------------------- /src/app/reducers/feeds.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { Tweet } from '../models/'; 3 | import { ActionTypes } from '../actions/feeds.actions'; 4 | 5 | export type State = { 6 | ids: string[]; 7 | entities: { [id: string]: any }; 8 | selectedUserListId: string; 9 | selectedFeedId: string; 10 | } 11 | 12 | const initialState: State = { 13 | ids: [], 14 | entities: {}, 15 | selectedUserListId: null, 16 | selectedFeedId: null 17 | } 18 | 19 | export default function(state = initialState, action: Action): State { 20 | switch(action.type){ 21 | case ActionTypes.GET_FEEDS_FOR_ID: { 22 | return Object.assign({}, state, { 23 | selectedUserListId: action.payload 24 | }); 25 | } 26 | 27 | case ActionTypes.GET_FEEDS_FOR_ID_SUCCESS: { 28 | const tweets: Tweet[] = action.payload 29 | 30 | // filter all new tweets and ids 31 | /** 32 | * TODO: Rethink over this approach 33 | */ 34 | // const newTweets: any = tweets.filter(tweet => !state.entities[tweet.id]); 35 | 36 | const newTweets: any = tweets; 37 | 38 | const newTweetIds = tweets 39 | .filter(tweet => !state.entities[tweet.id]) 40 | .map(tweet => tweet.id); 41 | 42 | const newEntities = newTweets 43 | .reduce((entities: { [id: string]: Tweet }, tweet: Tweet) => { 44 | return Object.assign(entities, { [tweet.id]: tweet }) 45 | }, {}); 46 | 47 | return Object.assign({}, state, { 48 | ids: [...state.ids, ...newTweetIds], 49 | entities: Object.assign({}, state.entities, newEntities) 50 | }) 51 | } 52 | 53 | case ActionTypes.GET_ALL_FEEDS_SUCCESS: { 54 | const tweets: any = action.payload 55 | /** 56 | * TODO: Rethink over this approach 57 | */ 58 | // const newTweets: any = tweets.filter(tweet => !state.entities[tweet.id]); 59 | 60 | const newTweets: any = tweets; 61 | 62 | const newTweetIds = tweets 63 | .filter(tweet => !state.entities[tweet.id]) 64 | .map(tweet => tweet.id); 65 | 66 | const newEntities = newTweets 67 | .reduce((entities: { [id: string]: Tweet }, tweet: Tweet) => { 68 | return Object.assign(entities, { [tweet.id]: tweet }) 69 | }, {}); 70 | 71 | return Object.assign({}, state, { 72 | ids: [...state.ids, ...newTweetIds], 73 | entities: Object.assign({}, state.entities, newEntities) 74 | }) 75 | } 76 | 77 | case ActionTypes.SELECT_FEED: { 78 | return Object.assign({}, state, { 79 | selectedFeedId: action.payload 80 | }); 81 | } 82 | 83 | case ActionTypes.ADD_FEED_TO_FAV_SUCCESS: { 84 | const tweets: any = action.payload 85 | /** 86 | * TODO: Rethink over this approach 87 | */ 88 | // const newTweets: any = tweets.filter(tweet => !state.entities[tweet.id]); 89 | 90 | const newTweets: any = tweets; 91 | 92 | const newTweetIds = tweets 93 | .filter(tweet => !state.entities[tweet.id]) 94 | .map(tweet => tweet.id); 95 | 96 | const newEntities = newTweets 97 | .reduce((entities: { [id: string]: Tweet }, tweet: Tweet) => { 98 | return Object.assign(entities, { [tweet.id]: tweet }) 99 | }, {}); 100 | 101 | return Object.assign({}, state, { 102 | ids: [...state.ids, ...newTweetIds], 103 | entities: Object.assign({}, state.entities, newEntities) 104 | }) 105 | } 106 | 107 | case ActionTypes.REMOVE_FEED_FROM_FAV_SUCESS: { 108 | const tweets: any = action.payload 109 | /** 110 | * TODO: Rethink over this approach 111 | */ 112 | // const newTweets: any = tweets.filter(tweet => !state.entities[tweet.id]); 113 | 114 | const newTweets: any = tweets; 115 | 116 | const newTweetIds = tweets 117 | .filter(tweet => !state.entities[tweet.id]) 118 | .map(tweet => tweet.id); 119 | 120 | const newEntities = newTweets 121 | .reduce((entities: { [id: string]: Tweet }, tweet: Tweet) => { 122 | return Object.assign(entities, { [tweet.id]: tweet }) 123 | }, {}); 124 | 125 | return Object.assign({}, state, { 126 | ids: [...state.ids, ...newTweetIds], 127 | entities: Object.assign({}, state.entities, newEntities) 128 | }) 129 | } 130 | 131 | case ActionTypes.RETWEET_SUCCESS: { 132 | const tweets: any = action.payload 133 | /** 134 | * TODO: Rethink over this approach 135 | */ 136 | // const newTweets: any = tweets.filter(tweet => !state.entities[tweet.id]); 137 | 138 | const newTweets: any = tweets; 139 | 140 | const newTweetIds = tweets 141 | .filter(tweet => !state.entities[tweet.id]) 142 | .map(tweet => tweet.id); 143 | 144 | const newEntities = newTweets 145 | .reduce((entities: { [id: string]: Tweet }, tweet: Tweet) => { 146 | return Object.assign(entities, { [tweet.id]: tweet }) 147 | }, {}); 148 | 149 | return Object.assign({}, state, { 150 | ids: [...state.ids, ...newTweetIds], 151 | entities: Object.assign({}, state.entities, newEntities) 152 | }) 153 | } 154 | 155 | case ActionTypes.REPLY_SUCCESS : { 156 | const tweets: any = action.payload 157 | /** 158 | * TODO: Rethink over this approach 159 | */ 160 | // const newTweets: any = tweets.filter(tweet => !state.entities[tweet.id]); 161 | 162 | const newTweets: any = tweets; 163 | 164 | const newTweetIds = tweets 165 | .filter(tweet => !state.entities[tweet.id]) 166 | .map(tweet => tweet.id); 167 | 168 | const newEntities = newTweets 169 | .reduce((entities: { [id: string]: Tweet }, tweet: Tweet) => { 170 | return Object.assign(entities, { [tweet.id]: tweet }) 171 | }, {}); 172 | 173 | return Object.assign({}, state, { 174 | ids: [...state.ids, ...newTweetIds], 175 | entities: Object.assign({}, state.entities, newEntities) 176 | }) 177 | } 178 | 179 | default: { 180 | return state; 181 | } 182 | } 183 | } 184 | 185 | export const getIds = (state: State) => state.ids; 186 | export const getEntities = (state: State) => state.entities; 187 | export const getSelectedUserListId = (state: State) => state.selectedUserListId; 188 | export const getSelectedFeedId = (state: State) => state.selectedFeedId; 189 | -------------------------------------------------------------------------------- /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 { Observable } from 'rxjs/Observable'; 6 | import { RouterState, routerReducer } from '@ngrx/router-store'; 7 | import { createSelector } from 'reselect'; 8 | 9 | import userAuth, * as fromUserAuth from './user-auth.reducer'; 10 | import user, * as fromUser from './user.reducer'; 11 | import userList, * as fromUserList from './user-list.reducer'; 12 | import lists, * as fromLists from './list.reducer'; 13 | import feeds, * as fromFeeds from './feeds.reducer'; 14 | 15 | // Entire State of a App 16 | export interface AppState { 17 | userAuth: fromUserAuth.AuthState; 18 | user: fromUser.UserState; 19 | userList: fromUserList.State; 20 | lists: fromLists.State; 21 | feeds: fromFeeds.State; 22 | router: RouterState; 23 | } 24 | 25 | // Export all the reducers 26 | export default compose(combineReducers)({ 27 | userAuth: userAuth, 28 | user: user, 29 | userList: userList, 30 | lists: lists, 31 | feeds: feeds, 32 | router: routerReducer 33 | }); 34 | 35 | /** 36 | * Get Login State returns UserAuth from the store 37 | * and depending on the persense of access_token 38 | * in userAuth the login status of user is defined. 39 | */ 40 | export function getLoginState(){ 41 | return (state$: Observable) => state$ 42 | .select(state => state.userAuth) 43 | } 44 | 45 | export const getUserState = (appState: AppState) => appState.user; 46 | 47 | export const getListsState = (appState: AppState) => appState.lists; 48 | export const getListsEntities = createSelector(getListsState, fromLists.getEntities); 49 | export const getListsIds = createSelector(getListsState, fromLists.getIds); 50 | export const getLists = createSelector(getListsEntities, getListsIds, (lists, ids) => { 51 | return ids.map(id => lists[id]); 52 | }); 53 | 54 | export const getUserListsState = (appState: AppState) => appState.userList; 55 | export const getUserListEntities = createSelector(getUserListsState, fromUserList.getEntities); 56 | export const getUserListIds = createSelector(getUserListsState, fromUserList.getIds); 57 | export const getUserList = createSelector(getUserListEntities, getUserListIds, (userLists, ids) => { 58 | return ids.map(id => userLists[id]); 59 | }); 60 | 61 | 62 | export const getFeedsState = (appState: AppState) => appState.feeds; 63 | export const getFeedsIds = createSelector(getFeedsState, fromFeeds.getIds); 64 | export const getFeedsEntities = createSelector(getFeedsState, fromFeeds.getEntities); 65 | export const getAllFeeds = createSelector(getFeedsEntities, getFeedsIds, (feeds, ids) => { 66 | return ids.map(id => feeds[id]); 67 | }); 68 | export const getSelectedUserListID = createSelector(getFeedsState, fromFeeds.getSelectedUserListId); 69 | 70 | export const getSelectedUserListIdFeeds = createSelector(getAllFeeds, getSelectedUserListID, (feeds, userListId) => { 71 | return feeds.filter(feed => feed.user_list_id === userListId); 72 | }) 73 | 74 | 75 | export const getSelectedFeedId = createSelector(getFeedsState, fromFeeds.getSelectedFeedId); 76 | 77 | export const getSelectedFeed = createSelector(getFeedsEntities, getSelectedFeedId, (feeds, id) => { 78 | return feeds[id]; 79 | }); 80 | 81 | export function isFollowing(listId){ 82 | return createSelector(getUserListEntities, (userList) => { 83 | for (var key in userList) { 84 | var value = userList[key]; 85 | if(value.list_id.$oid === listId){ 86 | return true 87 | } else{ 88 | console.log("list id is", this.list.id); 89 | return false 90 | } 91 | } 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /src/app/reducers/list.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { List } from '../models/'; 3 | import { ActionTypes } from '../actions/list.actions'; 4 | 5 | export type State = { 6 | ids: string[]; 7 | entities: { [id: string]: List }; 8 | } 9 | 10 | const initialState: State = { 11 | ids: [], 12 | entities: {} 13 | } 14 | 15 | export default function(state = initialState, action: Action): State { 16 | switch(action.type){ 17 | case ActionTypes.UPDATE_LISTS: 18 | case ActionTypes.RETRIVE_LISTS_SUCCESS: { 19 | const Lists: List[] = action.payload; 20 | 21 | const newLists: List[] = Lists; 22 | 23 | const newListIds = Lists 24 | .filter(list => !state.entities[list.id]) 25 | .map(list => list.id); 26 | 27 | const newEntities = newLists 28 | .reduce((entities: { [id: string]: List }, list: List) => { 29 | return Object.assign(entities, { 30 | [list.id]: list 31 | }); 32 | }, {}); 33 | 34 | return Object.assign({} , state, { 35 | ids: [ ...state.ids, ...newListIds ], 36 | entities: Object.assign({}, state.entities, newEntities) 37 | }) 38 | } 39 | case ActionTypes.UNFOLLOW_LIST_SUCCESS: { 40 | const unfollowed_list: List = action.payload.unfollowed_list; 41 | 42 | return Object.assign({}, state, { 43 | entities: Object.assign({}, state.entities, 44 | {[unfollowed_list.id]: unfollowed_list} 45 | ) 46 | }) 47 | } 48 | default: { 49 | return state; 50 | } 51 | } 52 | } 53 | 54 | export const getIds = (state: State) => state.ids; 55 | export const getEntities = (state: State) => state.entities; 56 | -------------------------------------------------------------------------------- /src/app/reducers/user-auth.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { ActionTypes, LoginActions } from '../actions/login.actions'; 3 | import { UserAuth } from '../models/'; 4 | 5 | export type AuthState = UserAuth; 6 | 7 | const initialState: AuthState = new UserAuth() 8 | 9 | export default function(state = initialState, action: Action): AuthState { 10 | switch(action.type){ 11 | 12 | case ActionTypes.LOGIN_SUCCESS: { 13 | var userAuth: UserAuth = action.payload; 14 | return Object.assign({}, state, userAuth); 15 | } 16 | 17 | case ActionTypes.LOGIN_SERVER_SUCCESS: { 18 | var userAuth: UserAuth = action.payload; 19 | return Object.assign({}, state, userAuth); 20 | } 21 | 22 | case ActionTypes.SIGNUP_SUCCESS: { 23 | var userAuth: UserAuth = action.payload; 24 | return Object.assign({}, state, userAuth); 25 | } 26 | 27 | case ActionTypes.LOGOUT_SUCCESS: { 28 | return Object.assign({}, state, new UserAuth()); 29 | } 30 | 31 | case ActionTypes.STORE_USER_SUCCESS: { 32 | var userAuth: UserAuth = action.payload 33 | return Object.assign({}, state, userAuth) 34 | } 35 | 36 | default: { 37 | return state; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/reducers/user-list.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { UserList } from '../models/'; 3 | import { ActionTypes as ListActionTypes }from '../actions/list.actions'; 4 | import { ActionTypes as UserListActions } from '../actions/user-list.actions'; 5 | import { ResponseParseService } from '../services/response-parse.service'; 6 | 7 | export type State = { 8 | ids: string[]; 9 | entities: { [id: string]: UserList }; 10 | } 11 | 12 | const initialState: State = { 13 | ids: [], 14 | entities: {} 15 | }; 16 | 17 | export default function(state = initialState, action: Action): State { 18 | switch(action.type){ 19 | case ListActionTypes.FOLLOW_LIST_SUCCESS: { 20 | let response = action.payload; 21 | 22 | //Parse the response here using responseParserService 23 | let responseParserService = new ResponseParseService; 24 | let userList = responseParserService.createUserListobj(response.new_user_list); 25 | 26 | return Object.assign({}, state, { 27 | entities: Object.assign({}, state.entities, 28 | {[userList.id]: userList} 29 | ), 30 | ids: [ ...state.ids, userList.id ] 31 | }) 32 | } 33 | case UserListActions.GET_USER_LISTS_SUCCESS: { 34 | 35 | const UserLists: UserList[] = action.payload; 36 | const newUserLists: UserList[] = UserLists 37 | .filter(list => !state.entities[list.id]) 38 | 39 | const newUserListIds = UserLists 40 | .filter(userList => !state.entities[userList.id]) 41 | .map(list => list.id); 42 | 43 | const newEntities = newUserLists 44 | .reduce((entities: { [id: string]: UserList }, userList: UserList) => { 45 | return Object.assign(entities, {[userList.id]: userList}); 46 | }, {}) 47 | 48 | return Object.assign({}, state, { 49 | ids: [...state.ids, ...newUserListIds], 50 | entities: Object.assign({}, state.entities, newEntities) 51 | }) 52 | } 53 | case ListActionTypes.UNFOLLOW_LIST_SUCCESS: { 54 | const userListId: string = action.payload.user_list_id; 55 | const newIds = state.ids.filter(val => val != userListId); 56 | const newEntities = newIds.reduce((entities: { [id: string]: UserList }, id: string) => { 57 | return Object.assign(entities, { 58 | [id]: state.entities[id] 59 | }); 60 | }, {}); 61 | 62 | return Object.assign({}, state, { 63 | entities: newEntities, 64 | ids: newIds 65 | }) 66 | } 67 | 68 | default: { 69 | return state; 70 | } 71 | } 72 | } 73 | 74 | export const getIds = (state: State) => state.ids; 75 | export const getEntities = (state: State) => state.entities; -------------------------------------------------------------------------------- /src/app/reducers/user.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { User } from '../models/'; 3 | import { ActionTypes } from '../actions/user.actions'; 4 | export type UserState = User; 5 | 6 | const initialState: UserState = new User(); 7 | 8 | export default function(state = initialState, action: Action): UserState { 9 | switch(action.type){ 10 | case ActionTypes.LOAD_PROFILE_SUCCESS: { 11 | const user: User = action.payload; 12 | return Object.assign({}, state, user) 13 | } 14 | default: { 15 | return state; 16 | } 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/app/services/api.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { Http, Headers } from '@angular/http'; 4 | import { environment } from '../../environments/environment'; 5 | import { Restangular } from 'ng2-restangular'; 6 | import { List, UserList, Tweet, User } from '../models'; 7 | 8 | var BASE_URL: string = environment.baseUrl; 9 | 10 | @Injectable() 11 | export class ApiService { 12 | 13 | constructor(private http: Http, public restAngular: Restangular) { } 14 | 15 | retriveSuggestion(): Observable { 16 | var token = this.getServerToken() 17 | return this.restAngular.all('lists/suggest') 18 | .post(null, {}, { 'Authorization': token }) 19 | } 20 | 21 | followList(listId: string): Observable { 22 | var token = this.getServerToken() 23 | var ListId = { id: listId } 24 | return this.restAngular.all('users/create_list') 25 | .post(ListId, {}, { 'Authorization': token }) 26 | } 27 | 28 | getServerToken(): any { 29 | return localStorage.getItem('server_token'); 30 | } 31 | 32 | getListsTimeLine(indexId: any): Observable { 33 | var token = this.getServerToken() 34 | var attr = { index_id: indexId }; 35 | return this.restAngular 36 | .all('users/list_timeline') 37 | .post(attr, {}, { 'Authorization': token }) 38 | } 39 | 40 | getUserLists(): Observable { 41 | var token = this.getServerToken() 42 | return this.restAngular 43 | .all('users/user_list') 44 | .post(null, {}, {'Authorization': token}); 45 | } 46 | 47 | getUserDetail(): Observable{ 48 | var token = this.getServerToken() 49 | return this.restAngular.all('users/user_detail') 50 | .post(null, {}, {'Authorization': token}); 51 | } 52 | 53 | all_feeds(): any { 54 | var token = this.getServerToken() 55 | return this.restAngular.all('users/all_feeds') 56 | .post(null, {}, {'Authorization': token}); 57 | } 58 | 59 | 60 | unFollowList(listId: string): Observable{ 61 | var token = this.getServerToken() 62 | var ListId = { id: listId } 63 | return this.restAngular.all('users/unfollow_list') 64 | .post(ListId, {}, {'Authorization': token}); 65 | } 66 | 67 | addToFav(feed: any): any { 68 | var token = this.getServerToken() 69 | var favoritedFeed = { feed: feed } 70 | return this.restAngular.all('tweets/add_tweet_to_fav') 71 | .post(favoritedFeed, {}, {'Authorization': token}); 72 | } 73 | 74 | removeFromFav(feed: any): any { 75 | var token = this.getServerToken() 76 | var favoritedFeed = { feed: feed } 77 | return this.restAngular.all('tweets/remove_tweet_from_fav') 78 | .post(favoritedFeed, {}, {'Authorization': token}); 79 | } 80 | 81 | retweet(feed: any): any { 82 | var token = this.getServerToken() 83 | var favoritedFeed = { feed: feed } 84 | return this.restAngular.all('tweets/retweet') 85 | .post(favoritedFeed, {}, {'Authorization': token}); 86 | } 87 | 88 | reply(messageWithFeed: any): any { 89 | var token = this.getServerToken() 90 | return this.restAngular.all('tweets/reply') 91 | .post(messageWithFeed, {}, {'Authorization': token}); 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/app/services/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, CanLoad, CanActivateChild, Route, ActivatedRouteSnapshot, CanActivate } from '@angular/router'; 3 | import { Observable } from 'rxjs/Rx'; 4 | import { Store } from '@ngrx/store'; 5 | import { AppState, getLoginState } from '../reducers'; 6 | import 'rxjs/add/operator/take'; 7 | import 'rxjs/add/operator/filter'; 8 | 9 | 10 | /** 11 | * Here we Override the CanActivate, CanLoad, CanActivateChild class methods 12 | * and use `guard` to return a boolean true or false depending on user login status. 13 | */ 14 | @Injectable() 15 | export class AuthGuardService implements CanActivate, CanLoad, CanActivateChild { 16 | 17 | constructor(private router: Router, 18 | private store: Store) { } 19 | 20 | canLoad(route: Route): Observable{ 21 | return this.guard(); 22 | } 23 | 24 | canActivateChild(childRoute: ActivatedRouteSnapshot): Observable { 25 | return this.guard(); 26 | } 27 | 28 | canActivate(){ 29 | return this.guard(); 30 | } 31 | 32 | /** 33 | * `Take` 1 takes the only 1 value from the stream of events 34 | * and stop taking the furthur values. i.e is it just stops 35 | * listening to the observable. 36 | * Ref. https://jsbin.com/xizixax/4/edit?html,js,console,output 37 | */ 38 | guard(): Observable { 39 | return this.store.let(getLoginState()) 40 | .take(1) 41 | .map(state => state.access_token ? true: this.handleAuthFail()) 42 | } 43 | 44 | handleAuthFail(){ 45 | this.router.navigate(['/login']); 46 | return false; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/app/services/response-parse.service.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Rx'; 2 | import { Injectable } from '@angular/core'; 3 | import { List, UserList, Tweet, User, Entities, Urls } from '../models'; 4 | 5 | @Injectable() 6 | export class ResponseParseService { 7 | 8 | constructor() { } 9 | 10 | createUserListobj(dbUserListobj: any): UserList{ 11 | var attr = {id: dbUserListobj.id, 12 | list_id: dbUserListobj.list_id, 13 | twitter_list_id: dbUserListobj.twitter_list_id, 14 | slug: dbUserListobj.slug, 15 | name: dbUserListobj.name 16 | } 17 | var userList: UserList = new UserList(attr); 18 | return userList; 19 | } 20 | 21 | createUserListsobj(dbUserListsobj: any): UserList[] { 22 | var userLists = []; 23 | dbUserListsobj.forEach(element => { 24 | var attr = {id: element.id, 25 | list_id: element.list_id, 26 | twitter_list_id: element.twitter_list_id, 27 | slug: element.slug, 28 | name: element.name 29 | } 30 | var userList = new UserList(attr); 31 | userLists.push(userList); 32 | }); 33 | return userLists; 34 | } 35 | 36 | createSuggestedListsObj(response: any): List[] { 37 | var suggLists = [] 38 | response.forEach(element => { 39 | var attr = {id: element.id, name: element.name, 40 | description: element.description, 41 | image_url: element.image_url, 42 | isFollowing: element.isFollowing 43 | } 44 | var suggestedList = new List(attr) 45 | suggLists.push(suggestedList); 46 | }); 47 | return suggLists; 48 | } 49 | 50 | 51 | createTweetsObj(dbTweetsObj: any): any { 52 | return dbTweetsObj; 53 | /** 54 | * Below Code will be used in future to parse the response 55 | * Currently returning entire response object as it is. 56 | */ 57 | 58 | // var tweets = []; 59 | // dbTweetsObj.forEach(element => { 60 | // // User 61 | // var dbuser = element.user; 62 | // var user_attr = { 63 | // id: dbuser.id, 64 | // name: dbuser.name, 65 | // screen_name: dbuser.screen_name, 66 | // profile_image_url: dbuser.profile_image_url 67 | // } 68 | 69 | // // Entities 70 | // var entity = element.entities 71 | 72 | // var entity_attr = { 73 | // hashtags: >entity.hashtags, 74 | // symbols: >entity.symbols, 75 | // urls: this.retriveUrlObj(entity.urls) 76 | // } 77 | 78 | // // Main Tweet 79 | // var attr = { 80 | // id: element.id, 81 | // text: element.text, 82 | // retweet_count: element.retweet_count, 83 | // favorite_count: element.favorite_count, 84 | // user: new User(user_attr), 85 | // user_list_id: element.user_list_id, 86 | // entities: new Entities(entity_attr) 87 | // } 88 | 89 | // var tweet = new Tweet(attr) 90 | // tweets.push(tweet); 91 | // }); 92 | // return tweets; 93 | } 94 | 95 | retriveUrlObj(urls: Array): Urls[] { 96 | var urlsObj = []; 97 | urls.forEach(el => { 98 | var url_attr = { 99 | url: el.url, 100 | expanded_url: el.expanded_url, 101 | display_url: el.display_url, 102 | indices: el.indices 103 | } 104 | 105 | var url = new Urls(url_attr) 106 | 107 | urlsObj.push(url) 108 | } 109 | ) 110 | return urlsObj; 111 | } 112 | 113 | createUserObj(response: any): User { 114 | var user_detail = response.user_detail 115 | var attr = { 116 | id: user_detail.id, 117 | name: user_detail.name, 118 | screen_name: user_detail.screen_name, 119 | profile_image_url: user_detail.profile_image_url_https, 120 | description: user_detail.description, 121 | location: user_detail.location 122 | } 123 | var user: User = new User(attr); 124 | return user; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/app/services/user-auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { UserAuth, UserProfile, User } from '../models'; 4 | import * as firebase from "firebase"; 5 | import { Http, Headers } from '@angular/http'; 6 | import { Secrets } from '../../secrets'; 7 | import { environment } from '../../environments/environment'; 8 | import { Restangular } from 'ng2-restangular'; 9 | 10 | var BASE_URL: string = environment.baseUrl; 11 | /** 12 | * @class UserAuthService: 13 | * 14 | * Auth Service 15 | * Used to Login, Logout, Retrive User details and etc.. 16 | * operation that can be carried out on provider API. 17 | */ 18 | @Injectable() 19 | export class UserAuthService { 20 | 21 | /** 22 | * @constructor 23 | */ 24 | constructor(private http: Http, public restAngular: Restangular) { 25 | var config = { 26 | apiKey: Secrets.firebaseApiKey, 27 | authDomain: Secrets.firebaseAuthDomain, 28 | databaseURL: Secrets.firebaseDatabaseUrl, 29 | storageBucket: Secrets.firebaseStorageBucket, 30 | messagingSenderId: Secrets.firebaseMessagingSenderId, 31 | }; 32 | firebase.initializeApp(config); 33 | } 34 | 35 | signUp(): Observable { 36 | var provider = new firebase.auth.TwitterAuthProvider(); 37 | return Observable.create(observer => { 38 | firebase.auth().signInWithPopup(provider).then(result => { 39 | const userAuth = new UserAuth( 40 | result.user.providerData[0]['uid'], 41 | result.credential.accessToken, 42 | result.credential.secret) 43 | 44 | observer.next(userAuth); 45 | }) 46 | }); 47 | } 48 | 49 | loginServer(userAuth: UserAuth): Observable { 50 | return this.restAngular.all('/auth/sign_in') 51 | .post(userAuth) 52 | .map(response => { 53 | // If Success 54 | if(response.status){ 55 | var token = response.token; 56 | var newUserAuth = new UserAuth(userAuth.user_id, 57 | userAuth.access_token, 58 | userAuth.secret_token, 59 | token) 60 | return newUserAuth; 61 | } else { 62 | return userAuth 63 | } 64 | } 65 | ) 66 | } 67 | 68 | login(): Observable { 69 | var provider = new firebase.auth.TwitterAuthProvider(); 70 | return Observable.create(observer => { 71 | firebase.auth().signInWithPopup(provider).then(result => { 72 | const userAuth = new UserAuth( 73 | result.user.providerData[0]['uid'], 74 | result.credential.accessToken, 75 | result.credential.secret) 76 | observer.next(userAuth); 77 | }) 78 | }); 79 | } 80 | 81 | logout(): Observable { 82 | return Observable.of( 83 | localStorage.removeItem('access_token'), 84 | localStorage.removeItem('server_token'), 85 | localStorage.removeItem('user_id'), 86 | localStorage.removeItem('secret_token')); 87 | } 88 | 89 | storeUsertoBackend(payload): Observable { 90 | return this.restAngular.all('/auth/sign_up').post(payload) 91 | .map(response => { 92 | var token = response.token 93 | var userAuth = payload.userAuth 94 | var newUserAuth = new UserAuth(userAuth.user_id, userAuth.access_token, userAuth.secret_token, token) 95 | return newUserAuth; 96 | }) 97 | } 98 | 99 | storeServerToken(token): void { 100 | localStorage.setItem('server_token', token); 101 | } 102 | 103 | storeUserAuthInLocalstorage(userAuth): void { 104 | localStorage.setItem('user_id', userAuth.user_id); 105 | localStorage.setItem('access_token', userAuth.access_token); 106 | localStorage.setItem('secret_token', userAuth.secret_token); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /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/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/src/assets/background.jpg -------------------------------------------------------------------------------- /src/assets/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-size: cover; 3 | } -------------------------------------------------------------------------------- /src/assets/poi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/src/assets/poi.gif -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | baseUrl: 'https://listify-backend.herokuapp.com/api' 4 | }; 5 | -------------------------------------------------------------------------------- /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 | baseUrl: 'http://127.0.0.1:3000/api' 9 | // baseUrl: 'https://listify-backend.herokuapp.com/api' 10 | 11 | }; 12 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aviabird/listify/dc88bc50f333ebcfe0e9f272f90929b19844a13f/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Listify 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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; 5 | -------------------------------------------------------------------------------- /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, "ist"], 97 | "component-selector-prefix": [true, "ist"], 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 | --------------------------------------------------------------------------------