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