├── Chapter01 └── index.html ├── Chapter02 ├── css │ ├── bootstrap.min.css │ └── font-awesome.min.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── index.html ├── js │ ├── firebase-config.js │ ├── react-form.js │ └── ticket-table.js ├── reactForm.html └── viewTickets.html ├── Chapter03 ├── README.md ├── _gitignore ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── App.jsx │ ├── App.test.js │ ├── add-ticket │ ├── AddTicketForm.jsx │ └── AddTicketForm.test.js │ ├── bootstrap │ └── bootstrap.min.css │ ├── firebase │ └── firebase-config.js │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff │ ├── header │ ├── header.css │ └── header.jsx │ ├── home │ └── index.jsx │ ├── index.js │ ├── login │ ├── login.css │ └── login.jsx │ ├── logout │ └── logout.jsx │ ├── registerServiceWorker.js │ └── tickets-listing │ ├── ViewTickets.jsx │ └── ViewTickets.test.js ├── Chapter04 └── seat-booking-app-final.7z ├── Chapter05 ├── README.md ├── _firebaserc ├── _gitignore ├── database.rules.json ├── firebase.json ├── package-lock.json ├── package.json ├── public │ └── index.html └── src │ ├── App.jsx │ ├── App.test.js │ ├── add-ticket │ ├── AddTicketForm.jsx │ └── AddTicketForm.test.js │ ├── admin │ ├── getAllUsers.jsx │ ├── newUserForm.jsx │ └── tickets.jsx │ ├── bootstrap │ └── bootstrap.min.css │ ├── firebase │ └── firebase-config.js │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff │ ├── header │ ├── header.css │ ├── header.jsx │ └── navigation.jsx │ ├── home │ └── index.jsx │ ├── index.js │ ├── login │ ├── login.css │ └── login.jsx │ ├── logout │ └── logout.jsx │ ├── profile-update │ └── profile-update.jsx │ ├── registerServiceWorker.js │ └── tickets-listing │ ├── ViewTickets.jsx │ └── ViewTickets.test.js ├── Chapter07 ├── cloud-functions.7z └── cloud-messaging.7z ├── Chapter08 └── chapter8.7z ├── LICENSE └── README.md /Chapter01/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Chapter 1 7 | 8 | 9 | 10 | 11 | 12 |

Hello world! This is HTML5 Boilerplate.

13 | 14 | 15 | 16 | 17 |

Messages

18 |

19 | 20 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Chapter02/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /Chapter02/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter02/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /Chapter02/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter02/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /Chapter02/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter02/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /Chapter02/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter02/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /Chapter02/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter02/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /Chapter02/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactJs and Firebase - Chapter 2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 24 | 25 | -------------------------------------------------------------------------------- /Chapter02/js/firebase-config.js: -------------------------------------------------------------------------------- 1 | // Initialize Firebase 2 | var config = { 3 | 4 | apiKey: "AIzaSyDO1VEnd5VmWd2OWQ9NQuh-ehNXcoPTy-w", 5 | authDomain: "demoproject-7cc0d.firebaseapp.com", 6 | databaseURL: "https://demoproject-7cc0d.firebaseio.com", 7 | projectId: "demoproject-7cc0d", 8 | storageBucket: "", 9 | messagingSenderId: "41428255556" 10 | 11 | }; 12 | 13 | firebase.initializeApp(config); 14 | 15 | var firebaseDb = firebase.database(); -------------------------------------------------------------------------------- /Chapter02/js/react-form.js: -------------------------------------------------------------------------------- 1 | class AddTicketForm extends React.Component { 2 | 3 | constructor() { 4 | super(); 5 | this.handleSubmitEvent = this.handleSubmitEvent.bind(this); 6 | } 7 | // Initialize Firebase 8 | 9 | handleSubmitEvent(event) { 10 | 11 | event.preventDefault(); 12 | 13 | console.log("Email--",this.refs.email.value.trim()); 14 | console.log("Issue Type--",this.refs.issueType.value.trim()); 15 | console.log("Department--",this.refs.department.value.trim()); 16 | console.log("Comments--",this.refs.comment.value.trim()); 17 | 18 | //React form data object 19 | var data = { 20 | date: Date(), 21 | email:this.refs.email.value.trim(), 22 | issueType:this.refs.issueType.value.trim(), 23 | department:this.refs.department.value.trim(), 24 | comments:this.refs.comment.value.trim() 25 | } 26 | 27 | firebaseDb.ref().child('helpdesk').child('tickets').push(data); 28 | firebaseDb.ref().on('child_added', function(snapshot) { 29 | var data = snapshot.val(); 30 | snapshot.forEach(function(childSnap) { 31 | console.log(childSnap.val()); 32 | this.refs.form.reset(); 33 | console.log("Ticket submitted successfully"); 34 | }); 35 | }); 36 | } 37 | render() { 38 | var style = {color: "#ffaaaa"}; 39 | return ( 40 |
41 |
42 |
43 | 44 | 46 |
47 |
48 | 49 | 59 |
60 |
61 | 63 | 70 |
71 |
72 | 73 | ( 200 characters max) 74 | 75 |
76 |
77 | 78 | 79 |
80 |
81 |
82 | 83 | ); 84 | } 85 | } 86 | ReactDOM.render( 87 | , 88 | document.getElementById('form') 89 | ); -------------------------------------------------------------------------------- /Chapter02/js/ticket-table.js: -------------------------------------------------------------------------------- 1 | class TicketTable extends React.Component { 2 | constructor(){ 3 | super(); 4 | this.state = { 5 | tickets:[] 6 | } 7 | } 8 | 9 | componentDidMount() { 10 | var itemsRef = firebaseDb.ref('/helpdesk/tickets/all/'); 11 | console.log(itemsRef,"test"); 12 | itemsRef.on('value', (snapshot) => { 13 | let tickets = snapshot.val(); 14 | console.log(tickets); 15 | let newState = []; 16 | for (let ticket in tickets) { 17 | newState.push({ 18 | id:tickets[ticket], 19 | email:tickets[ticket].email, 20 | issueType:tickets[ticket].issueType, 21 | department:tickets[ticket].department, 22 | comments:tickets[ticket].comments, 23 | date:tickets[ticket].date 24 | }); 25 | } 26 | this.setState({ 27 | tickets: newState 28 | }); 29 | 30 | }); 31 | } 32 | 33 | render() { 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | { 46 | this.state.tickets.map((ticket) => 47 | { return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | )}) 55 | } 56 | 57 |
EmailIssue TypeDepartmentCommentsDate
{ticket.email}{ticket.issueType}{ticket.department}{ticket.comments}{ticket.date}
58 | )} 59 | }; 60 | ReactDOM.render( 61 | , 62 | document.getElementById('table') 63 | ); -------------------------------------------------------------------------------- /Chapter02/reactForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactJs and Firebase - Chapter 2 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 32 |
33 |

Add Ticket

34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Chapter02/viewTickets.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactJs and Firebase - Chapter 2 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 32 |
33 |

View Tickets

34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Chapter03/_gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /Chapter03/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login-authentication", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "firebase": "^4.8.0", 7 | "react": "^16.2.0", 8 | "react-dom": "^16.2.0", 9 | "react-router-dom": "^4.2.2", 10 | "react-scripts": "1.0.17", 11 | "react-toastr-basic": "^1.1.14" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter03/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter03/public/favicon.ico -------------------------------------------------------------------------------- /Chapter03/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Chapter03/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Chapter03/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | BrowserRouter as Router, 4 | Route, 5 | } from 'react-router-dom' 6 | import Header from './header/header.jsx'; 7 | import Home from './home/index.jsx'; 8 | import Login from './login/login.jsx'; 9 | import Logout from './logout/logout.jsx'; 10 | import AddTicketForm from './add-ticket/AddTicketForm.jsx'; 11 | import ViewTicketTable from './tickets-listing/ViewTickets.jsx'; 12 | import firebase from './firebase/firebase-config'; 13 | import ToastrContainer from 'react-toastr-basic'; 14 | 15 | class App extends Component { 16 | constructor() { 17 | super(); 18 | this.state = { 19 | authenticated : false, 20 | data:'' 21 | } 22 | } 23 | componentWillMount() { 24 | this.removeAuthListener = firebase.auth().onAuthStateChanged((user) =>{ 25 | if(user){ 26 | console.log("App user",user); 27 | this.setState({ 28 | authenticated:true, 29 | data:user.providerData 30 | }) 31 | } 32 | else{ 33 | this.setState({ 34 | authenticated:false, 35 | data:'' 36 | }) 37 | } 38 | }) 39 | } 40 | componentWillUnmount(){ 41 | this.removeAuthListener(); 42 | } 43 | 44 | render() { 45 | return ( 46 | 47 |
48 | 49 | ()}/> 50 | { 51 | this.state.authenticated 52 | ? 53 | ( 54 | 55 |
56 | ()} /> 57 | 58 | 59 | 60 | ) 61 | : 62 | ( 63 | 64 |
65 | 66 | 67 | ) 68 | } 69 | 70 |
71 |
72 | ) 73 | } 74 | 75 | } 76 | 77 | export default App; 78 | -------------------------------------------------------------------------------- /Chapter03/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter03/src/add-ticket/AddTicketForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import firebase from '../firebase/firebase-config'; 3 | 4 | class AddTicketForm extends Component { 5 | 6 | constructor() { 7 | super(); 8 | this.handleSubmitEvent = this.handleSubmitEvent.bind(this); 9 | } 10 | 11 | handleSubmitEvent(e) { 12 | e.preventDefault(); 13 | 14 | //React form data object 15 | 16 | var data = { 17 | date: Date(), 18 | email:this.refs.email.value.trim(), 19 | issueType:this.refs.issueType.value.trim(), 20 | department:this.refs.department.value.trim(), 21 | comments:this.refs.comment.value.trim() 22 | } 23 | 24 | firebase.database().ref().child('helpdesk').child('tickets').push(data); 25 | 26 | firebase.database().ref().on('child_added', function(snapshot) { 27 | console.log("Ticket submitted successfully"); 28 | }); 29 | } 30 | render() { 31 | var style = {color: "#ffaaaa"}; 32 | return ( 33 |
34 |
35 | 36 | 38 |
39 |
40 | 41 | 51 |
52 |
53 | 55 | 62 |
63 |
64 | 65 | ( 200 characters left) 66 | 67 |
68 |
69 | 70 | 71 |
72 |
73 | ); 74 | } 75 | } 76 | 77 | export default AddTicketForm; 78 | -------------------------------------------------------------------------------- /Chapter03/src/add-ticket/AddTicketForm.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import AddTicketForm from './AddTicketForm'; 4 | 5 | it('Renders add ticket form component without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter03/src/firebase/firebase-config.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase'; 2 | 3 | const config = { 4 | apiKey: "AIzaSyDO1VEnd5VmWd2OWQ9NQuh-ehNXcoPTy-w", 5 | authDomain: "demoproject-7cc0d.firebaseapp.com", 6 | databaseURL: "https://demoproject-7cc0d.firebaseio.com", 7 | projectId: "demoproject-7cc0d", 8 | storageBucket: "demoproject-7cc0d.appspot.com", 9 | messagingSenderId: "41428255556" 10 | }; 11 | 12 | export const firebaseApp = firebase.initializeApp(config); 13 | 14 | export const googleProvider = new firebase.auth.GoogleAuthProvider(); 15 | export const facebookProvider = new firebase.auth.FacebookAuthProvider(); 16 | 17 | export default firebase; -------------------------------------------------------------------------------- /Chapter03/src/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter03/src/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /Chapter03/src/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /Chapter03/src/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter03/src/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /Chapter03/src/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter03/src/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /Chapter03/src/header/header.css: -------------------------------------------------------------------------------- 1 | .firebase-nav .navbar-nav.navbar-right:last-child { 2 | margin-right: 0px; 3 | } 4 | .toaster{ 5 | width: 350px; 6 | } -------------------------------------------------------------------------------- /Chapter03/src/header/header.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom' 3 | import './header.css'; 4 | 5 | class Header extends Component { 6 | 7 | render() { 8 | return ( 9 |
10 | { 11 | this.props.authenticated 12 | ? 13 | ( 14 | 15 |
    16 |
  • Home
  • 17 |
  • Tickets
  • 18 |
  • Add new ticket
  • 19 |
20 |
    21 |
  • Logout
  • 22 |
23 |
24 | ):( 25 | 26 |
    27 |
  • Register/Login
  • 28 |
29 |
30 | ) 31 | } 32 |
33 | 34 | ); 35 | } 36 | } 37 | 38 | export default Header; 39 | -------------------------------------------------------------------------------- /Chapter03/src/home/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Home extends Component { 4 | 5 | render() { 6 | var userPhoto = {width:"80px",height:"80px",margintop:"10px"}; 7 | return ( 8 |
9 | { 10 | this.props.userInfo.map((profile)=> { 11 | return ( 12 | 13 |

{ profile.displayName } - Welcome to Helpdesk Application

14 |
15 | user 16 |
17 | Eamil: {profile.email } 18 |
19 |
20 | )}) 21 | } 22 |
23 | ) 24 | } 25 | } 26 | 27 | export default Home; 28 | -------------------------------------------------------------------------------- /Chapter03/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App.jsx'; 4 | import './bootstrap/bootstrap.min.css'; 5 | 6 | import registerServiceWorker from './registerServiceWorker'; 7 | 8 | ReactDOM.render(, 9 | document.getElementById('root') 10 | ); 11 | registerServiceWorker(); 12 | -------------------------------------------------------------------------------- /Chapter03/src/login/login.css: -------------------------------------------------------------------------------- 1 | .form-signin .btn-facebook {background: #3b5998; border: 1px solid #294c91;} 2 | .form-signin .btn-google {background: #c32f10; border: 1px solid #b1270c;} 3 | 4 | .wrapper { 5 | margin-top: 80px; 6 | margin-bottom: 80px; 7 | } 8 | 9 | .form-signin { 10 | max-width: 380px; 11 | padding: 15px 35px 45px; 12 | margin: 0 auto; 13 | background-color: #fff; 14 | border: 1px solid rgba(0,0,0,0.1); 15 | box-shadow: 0px 0px 5px #ccc; 16 | } 17 | .form-signin-heading,.checkbox { 18 | margin-bottom: 30px; 19 | } 20 | .form-signin-heading{ 21 | font-family:Arial, Helvetica, sans-serif; 22 | font-size: 22px; 23 | border-bottom: 1px solid #ccc; 24 | padding-bottom: 10px; 25 | } 26 | .checkbox { 27 | font-weight: normal; 28 | font-size: 13px; 29 | } 30 | 31 | .form-control { 32 | position: relative; 33 | font-size: 16px; 34 | height: auto; 35 | padding: 10px; 36 | } 37 | 38 | input[type="email"] { 39 | margin-bottom: -1px; 40 | } 41 | 42 | input[type="password"] { 43 | margin-bottom: 20px; 44 | } 45 | 46 | .form-signin input[type="email"],.form-signin input[type="password"]{ 47 | margin-bottom: 10px; 48 | border-radius: 0px; 49 | font-size: 13px; 50 | } 51 | .form-signin button{ 52 | font-size: 13px; 53 | border-radius: 0px; 54 | font-weight: normal; 55 | } 56 | .form-signin button:hover{ 57 | opacity:0.9; 58 | } 59 | .form-signin button.btn-google:hover{ 60 | background: #c32f10; 61 | border: 1px solid #b1270c; 62 | } 63 | .form-signin button.btn-facebook:hover{ 64 | background: #3b5998; 65 | border: 1px solid #294c91; 66 | } 67 | .form-signin .btn-normal{ 68 | background: rgb(206, 153, 38); 69 | border: 1px solid #998115; 70 | } 71 | 72 | .form-signin .btn-normal:hover{ 73 | background: #ce9926; 74 | border: 1px solid #b6851a; 75 | } -------------------------------------------------------------------------------- /Chapter03/src/login/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Redirect } from 'react-router-dom' 3 | import { firebaseApp , facebookProvider, googleProvider } from '../firebase/firebase-config'; 4 | import { ToastDanger } from 'react-toastr-basic'; 5 | 6 | import './login.css'; 7 | 8 | class Login extends Component { 9 | constructor() { 10 | super(); 11 | this.authWithEmailPassword = this.authWithEmailPassword.bind(this); 12 | this.authWithFacebook = this.authWithFacebook.bind(this); 13 | this.authWithGoogle = this.authWithGoogle.bind(this); 14 | this.state = { 15 | redirect: false, 16 | data:null 17 | } 18 | } 19 | 20 | authWithEmailPassword(e){ 21 | e.preventDefault(); 22 | console.log(this.emailField.value); 23 | const email = this.emailField.value 24 | const password = this.passwordField.value; 25 | 26 | firebaseApp.auth().fetchProvidersForEmail(email).then((provider)=>{ 27 | if(provider.length === 0){ 28 | //Creating a new user 29 | firebaseApp.auth().setPersistence('session'); 30 | return firebaseApp.auth().createUserWithEmailAndPassword(email,password); 31 | } else if(provider.indexOf("password") === -1){ 32 | this.loginForm.reset(); 33 | ToastDanger('Wrong Password. Please try again!!') 34 | } else { 35 | //signin user 36 | return firebaseApp.auth().signInWithEmailAndPassword(email,password); 37 | } 38 | 39 | }).then((user) => { 40 | if(user && user.email){ 41 | this.loginForm.reset(); 42 | this.setState({redirect: true}); 43 | } 44 | }) 45 | .catch((error)=>{ 46 | console.log(error); 47 | ToastDanger(error.message); 48 | }) 49 | } 50 | 51 | authWithFacebook(){ 52 | console.log("facebook"); 53 | firebaseApp.auth().signInWithPopup(facebookProvider).then((result,error)=>{ 54 | if(error){ 55 | console.log("unable to sign in with facebook"); 56 | ToastDanger(error.message) 57 | } 58 | else{ 59 | this.setState({redirect:true,data:result.user}) 60 | } 61 | }).catch((error)=>{ 62 | ToastDanger(error.message); 63 | // if (error.code === 'auth/account-exists-with-different-credential') { 64 | // // Step 2. 65 | // var pendingCred = error.credential; 66 | // // The provider account's email address. 67 | // var email = error.email; 68 | // // Get registered providers for this email. 69 | // firebaseApp.auth().fetchProvidersForEmail(email).then(function(providers) { 70 | // // Step 3. 71 | // // If the user has several providers, 72 | // // the first provider in the list will be the "recommended" provider to use. 73 | // if (providers[0] === 'password') { 74 | // // Asks the user his password. 75 | // // In real scenario, you should handle this asynchronously. 76 | // var password = promptUserForPassword(); // TODO: implement promptUserForPassword. 77 | // firebaseApp.auth().signInWithEmailAndPassword(email, password).then(function(user) { 78 | // // Step 4a. 79 | // return user.link(pendingCred); 80 | // }).then(function() { 81 | // // Google account successfully linked to the existing Firebase user. 82 | 83 | // }); 84 | // } 85 | // }) 86 | // } 87 | }) 88 | } 89 | 90 | authWithGoogle(){ 91 | console.log("Google"); 92 | googleProvider.addScope('profile'); 93 | googleProvider.addScope('email'); 94 | firebaseApp.auth().signInWithPopup(googleProvider).then((result,error)=>{ 95 | if(error){ 96 | console.log("unable to sign in with google"); 97 | } 98 | else{ 99 | this.setState({redirect:true,data:result.user}) 100 | } 101 | }).catch((error)=>{ 102 | ToastDanger(error.message); 103 | }) 104 | } 105 | 106 | render() { 107 | if(this.state.redirect === true){ 108 | return 109 | } 110 | return ( 111 |
112 |
{this.authWithEmailPassword(event)}} ref={(form)=>{this.loginForm = form}}> 113 |

Login

114 | {this.emailField = input}} required /> 115 | {this.passwordField = input}} required /> 116 | 119 | 120 |
121 | 122 | 123 |
124 |
125 | 126 | ); 127 | } 128 | } 129 | 130 | export default Login; 131 | -------------------------------------------------------------------------------- /Chapter03/src/logout/logout.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Redirect } from 'react-router-dom' 3 | import { firebaseApp } from '../firebase/firebase-config'; 4 | 5 | class Logout extends Component { 6 | constructor(props) { 7 | super(); 8 | this.state = { 9 | redirect: props.authenticated, 10 | data:'' 11 | } 12 | } 13 | componentWillMount(){ 14 | firebaseApp.auth().signOut().then((user)=>{ 15 | this.setState({ 16 | redirect:true, 17 | data: null 18 | }) 19 | }) 20 | } 21 | 22 | 23 | render() { 24 | if(this.state.redirect === true){ 25 | return 26 | } 27 | return ( 28 |
29 |

Logging out...

30 |
31 | 32 | ); 33 | } 34 | } 35 | 36 | export default Logout; 37 | -------------------------------------------------------------------------------- /Chapter03/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | } else { 39 | // Is not local host. Just register service worker 40 | registerValidSW(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Chapter03/src/tickets-listing/ViewTickets.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import firebase from '../firebase/firebase-config'; 3 | 4 | class ViewTicketTable extends Component { 5 | constructor() { 6 | super(); 7 | this.state = { 8 | tickets: [] 9 | } 10 | } 11 | 12 | componentDidMount() { 13 | const itemsRef = firebase.database().ref('/helpdesk/tickets'); 14 | itemsRef.on('value', (snapshot) => { 15 | let tickets = snapshot.val(); 16 | let newState = []; 17 | for (let ticket in tickets) { 18 | newState.push({ 19 | id:ticket, 20 | email:tickets[ticket].email, 21 | issueType:tickets[ticket].issueType, 22 | department:tickets[ticket].department, 23 | comments:tickets[ticket].comments, 24 | date:tickets[ticket].date 25 | }); 26 | } 27 | this.setState({ 28 | tickets: newState 29 | }); 30 | console.log(this.state.tickets); 31 | }); 32 | } 33 | render() { 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {this.state.tickets.map((item) => { 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ) 56 | })} 57 | 58 |
EmailIssue TypeDepartmentCommentsDate
{item.email}{item.issueType}{item.department}{item.comments}{item.date}
59 | ); 60 | } 61 | } 62 | 63 | export default ViewTicketTable; 64 | -------------------------------------------------------------------------------- /Chapter03/src/tickets-listing/ViewTickets.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ViewTicketTable from './ViewTicketTable.jsx'; 4 | 5 | it('Renders view tickets component without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter04/seat-booking-app-final.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter04/seat-booking-app-final.7z -------------------------------------------------------------------------------- /Chapter05/_firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "demoproject-7cc0d" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Chapter05/_gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /Chapter05/database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "helpdesk": { 4 | "tickets": { 5 | "$ticket_id": { 6 | ".read": "auth != null && data.child('email').val() == auth.email", 7 | ".write": "auth != null" 8 | } 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter05/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "hosting": { 6 | "public": "build", 7 | "ignore": [ 8 | "firebase.json", 9 | "**/.*", 10 | "**/node_modules/**" 11 | ], 12 | "rewrites": [ 13 | { 14 | "source": "**", 15 | "destination": "/index.html" 16 | } 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter05/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login-authentication", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "firebase": "^4.8.0", 7 | "react": "^16.2.0", 8 | "react-dom": "^16.2.0", 9 | "react-router-dom": "^4.2.2", 10 | "react-scripts": "1.0.17", 11 | "react-toastr-basic": "^1.1.14" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter05/public/index.html: -------------------------------------------------------------------------------- 1 | React App
-------------------------------------------------------------------------------- /Chapter05/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | BrowserRouter as Router, 4 | Route 5 | } from 'react-router-dom' 6 | import firebase from './firebase/firebase-config'; 7 | import ToastrContainer from 'react-toastr-basic'; 8 | import { ToastDanger } from 'react-toastr-basic'; 9 | import Header from './header/header.jsx'; 10 | import Home from './home/index.jsx'; 11 | import Login from './login/login.jsx'; 12 | import Logout from './logout/logout.jsx'; 13 | import AddTicketForm from './add-ticket/AddTicketForm.jsx'; 14 | import ViewTicketTable from './tickets-listing/ViewTickets.jsx'; 15 | import ProfileUpdateForm from './profile-update/profile-update'; 16 | import AppUsers from './admin/getAllUsers'; 17 | import GetAllTickets from './admin/tickets'; 18 | import AddNewUserForm from './admin/newUserForm'; 19 | 20 | 21 | 22 | class App extends Component { 23 | constructor() { 24 | super(); 25 | this.state = { 26 | authenticated : false, 27 | data:'', 28 | userUid:'', 29 | role:{ 30 | admin:false, 31 | type:'' 32 | } 33 | } 34 | } 35 | getIdToken(user){ 36 | return user.getIdToken(true); 37 | } 38 | componentWillMount() { 39 | this.removeAuthListener = firebase.auth().onAuthStateChanged((user) =>{ 40 | if(user){ 41 | console.log(user,"information"); 42 | this.getIdToken(user).then((idToken)=>{ 43 | console.log(idToken); 44 | fetch('http://localhost:3000/setCustomClaims', { 45 | method: 'POST', // or 'PUT' 46 | body: JSON.stringify({idToken:idToken}), 47 | headers: new Headers({ 48 | 'Content-Type': 'application/json' 49 | }) 50 | }).then(res => res.json()) 51 | .catch(error => console.error('Error:', error)) 52 | .then(res => { 53 | console.log(res,"after token valid"); 54 | if(res.status === 'success' && res.role === 'admin'){ 55 | firebase.auth().currentUser.getIdToken(true); 56 | this.setState({ 57 | authenticated:true, 58 | data:user.providerData, 59 | userUid:user.uid, 60 | role:{ 61 | admin:true, 62 | type:'admin' 63 | } 64 | }) 65 | } 66 | else if (res.status === 'success' && res.role === 'employee'){ 67 | this.setState({ 68 | authenticated:true, 69 | data:user.providerData, 70 | userUid:user.uid, 71 | role:{ 72 | admin:false, 73 | type:'employee' 74 | } 75 | }) 76 | } 77 | else{ 78 | ToastDanger('Invalid Token !!') 79 | } 80 | }) 81 | }); 82 | 83 | } 84 | else{ 85 | this.setState({ 86 | authenticated:false, 87 | data:'', 88 | userUid:'', 89 | role:{ 90 | admin:false, 91 | type:'employee' 92 | } 93 | }) 94 | } 95 | }) 96 | } 97 | componentWillUnmount(){ 98 | this.removeAuthListener(); 99 | } 100 | 101 | render() { 102 | return ( 103 | 104 | 105 |
106 | 107 | ()}/> 108 | { 109 | this.state.authenticated 110 | ? ( 111 | 112 |
113 | ()} /> 114 | 115 | ):( 116 | 117 |
118 | 119 | 120 | ) 121 | }{ 122 | this.state.authenticated && !this.state.role.admin 123 | ? 124 | ( 125 | 126 | ( 127 | 128 | )}/> 129 | ( 130 | 131 | )}/> 132 | ( 133 | 134 | )}/> 135 | 136 | ) 137 | : 138 | ( 139 | 140 | 141 | 142 | 143 | 144 | ) 145 | } 146 | 147 |
148 |
149 | ) 150 | } 151 | 152 | } 153 | 154 | export default App; 155 | -------------------------------------------------------------------------------- /Chapter05/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter05/src/add-ticket/AddTicketForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import firebase from '../firebase/firebase-config'; 3 | import { ToastSuccess,ToastDanger } from 'react-toastr-basic'; 4 | 5 | class AddTicketForm extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.handleSubmitEvent = this.handleSubmitEvent.bind(this); 10 | this.handleChange = this.handleChange.bind(this); 11 | console.log(props.userInfo); 12 | 13 | this.state={ 14 | uId:props.userId, 15 | email:props.userInfo[0].email, 16 | issueType:"", 17 | department:"", 18 | comment:"" 19 | } 20 | } 21 | 22 | handleChange(event) { 23 | console.log(event.target.value); 24 | this.setState({ 25 | [event.target.id]: event.target.value 26 | }); 27 | } 28 | 29 | handleSubmitEvent(e) { 30 | e.preventDefault(); 31 | 32 | //React form data object 33 | const userId = this.state.uId; 34 | var data = { 35 | date: Date(), 36 | email:this.state.email, 37 | issueType:this.state.issueType, 38 | department:this.state.department, 39 | comments:this.state.comment, 40 | status:"progress" 41 | } 42 | 43 | console.log(data); 44 | 45 | var newTicketKey = firebase.database().ref('/helpdesk').child('tickets').push().key; 46 | // Write the new ticket data simultaneously in the tickets list and the user's ticket list. 47 | var updates = {}; 48 | updates['/helpdesk/tickets/' + userId + '/' + newTicketKey] = data; 49 | updates['/helpdesk/tickets/all/'+ newTicketKey] = data; 50 | 51 | return firebase.database().ref().update(updates).then(()=>{ 52 | ToastSuccess("Saved Successfully!!"); 53 | this.setState({ 54 | issueType:"", 55 | department:"", 56 | comment:"" 57 | }); 58 | }).catch((error)=>{ 59 | ToastDanger(error.message); 60 | }); 61 | 62 | } 63 | render() { 64 | var style = {color: "#ffaaaa"}; 65 | return ( 66 |
67 |
68 | 69 | 71 |
72 |
73 | 74 | 84 |
85 |
86 | 88 | 95 |
96 |
97 | 98 | ( 200 characters left) 99 | 100 |
101 |
102 | 103 | 104 |
105 |
106 | ); 107 | } 108 | } 109 | 110 | export default AddTicketForm; 111 | -------------------------------------------------------------------------------- /Chapter05/src/add-ticket/AddTicketForm.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import AddTicketForm from './AddTicketForm'; 4 | 5 | it('Renders add ticket form component without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter05/src/admin/getAllUsers.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | // import firebase from '../firebase/firebase-config'; 3 | 4 | class AppUsers extends Component { 5 | constructor() { 6 | super(); 7 | this.state = { 8 | users: [], 9 | search:'' 10 | } 11 | this.deleteUser = this.deleteUser.bind(this); 12 | this.viewProfile = this.viewProfile.bind(this); 13 | } 14 | deleteUser(uid){ 15 | console.log(uid,"delete user"); 16 | fetch('http://localhost:3000/deleteUser', { 17 | method: 'POST', // or 'PUT' 18 | body:JSON.stringify({uid:uid}), 19 | headers: new Headers({ 20 | 'Content-Type': 'application/json' 21 | }) 22 | }).then(res => res.json()) 23 | .catch(error => console.error('Error:', error)) 24 | } 25 | viewProfile(uid){ 26 | console.log(uid,"view profile"); 27 | fetch('http://localhost:3000/getUserProfile', { 28 | method: 'POST', // or 'PUT' 29 | body:JSON.stringify({uid:uid}), 30 | headers: new Headers({ 31 | 'Content-Type': 'application/json' 32 | }) 33 | }).then(res => res.json()) 34 | .catch(error => console.error('Error:', error)) 35 | .then(response => { 36 | console.log(response.data,"User Profile"); 37 | }) 38 | } 39 | componentDidMount() { 40 | fetch('http://localhost:3000/users', { 41 | method: 'GET', // or 'PUT' 42 | headers: new Headers({ 43 | 'Content-Type': 'application/json' 44 | }) 45 | }).then(res => res.json()) 46 | .catch(error => console.error('Error:', error)) 47 | .then(response => { 48 | console.log(response,"after token valid"); 49 | this.setState({ 50 | users:response 51 | }) 52 | console.log(this.state.users,'All Users'); 53 | }) 54 | } 55 | render() { 56 | var marginRight = {marginRight:'10px'}; 57 | return ( 58 |
59 |
60 |
61 | 63 |
64 | 68 | 69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | { 83 | this.state.users.length > 0 ? 84 | this.state.users.map((list,index) => { 85 | return ( 86 | 87 | 88 | 89 | 90 | 91 | 96 | 97 | ) 98 | }) : 99 | 100 | 101 | 102 | } 103 | 104 |
EmailNameLast Sign in TimeCreation TimeAction
{list.email}{list.displayName}{list.metadata.lastSignInTime}{list.metadata.creationTime} 92 | 93 | 94 | 95 |
No users found.
105 |
106 | ); 107 | } 108 | } 109 | 110 | export default AppUsers; 111 | -------------------------------------------------------------------------------- /Chapter05/src/admin/newUserForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | // import firebase from '../firebase/firebase-config'; 3 | import { ToastDanger,ToastSuccess } from 'react-toastr-basic'; 4 | 5 | class AddNewUserForm extends Component { 6 | constructor() { 7 | super(); 8 | this.state = { 9 | name:'', 10 | email:'', 11 | password:'', 12 | phoneNumber:'' 13 | } 14 | this.handleChange = this.handleChange.bind(this); 15 | this.handleSubmitEvent = this.handleSubmitEvent.bind(this); 16 | } 17 | handleChange(event) { 18 | console.log(event.target.value); 19 | this.setState({ 20 | [event.target.id]: event.target.value 21 | }); 22 | } 23 | 24 | handleSubmitEvent(e) { 25 | e.preventDefault(); 26 | //React form data object 27 | var data = { 28 | email:this.state.email, 29 | emailVerified: false, 30 | password:this.state.password, 31 | displayName:this.state.name, 32 | phoneNumber:this.state.phoneNumber, 33 | profilePhoto:this.fileInput.files[0], 34 | disabled: false 35 | } 36 | fetch('http://localhost:3000/createNewUser', { 37 | method: 'POST', // or 'PUT' 38 | body:JSON.stringify({data:data}), 39 | headers: new Headers({ 40 | 'Content-Type': 'application/json' 41 | }) 42 | }).then(res => res.json()) 43 | .catch(error => { 44 | ToastDanger(error) 45 | }) 46 | .then(response => { 47 | ToastSuccess(response.msg) 48 | this.setState({ 49 | name:'', 50 | email:'', 51 | password:'', 52 | phoneNumber:'' 53 | }); 54 | this.fileInput = ''; 55 | }); 56 | } 57 | render() { 58 | return ( 59 |
60 |
61 |
62 | 64 |
65 |
66 | 68 |
69 |
70 | 72 |
73 |
74 | 76 |
77 |
78 | { this.fileInput = input; }} /> 79 |
80 | 81 |
82 |
83 | ); 84 | } 85 | } 86 | 87 | export default AddNewUserForm; 88 | -------------------------------------------------------------------------------- /Chapter05/src/admin/tickets.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import firebase from '../firebase/firebase-config'; 3 | 4 | class GetAllTickets extends Component { 5 | constructor() { 6 | super(); 7 | this.state = { 8 | tickets: [] 9 | } 10 | this.handleChange = this.handleChange.bind(this); 11 | } 12 | handleChange(event) { 13 | console.log(event.target.value); 14 | this.setState({ 15 | [event.target.id]: event.target.value 16 | }); 17 | } 18 | componentWillMount() { 19 | console.log("sdfsdf"); 20 | const itemsRef = firebase.database().ref('/helpdesk/tickets').child('all') 21 | console.log(itemsRef); 22 | itemsRef.on('value', (snapshot) => { 23 | let tickets = snapshot.val(); 24 | console.log(tickets) 25 | if(tickets != null){ 26 | let ticketKeys = Object.keys(tickets); 27 | let newState = []; 28 | for (let ticket in tickets) { 29 | newState.push({ 30 | id:ticketKeys, 31 | email:tickets[ticket].email, 32 | issueType:tickets[ticket].issueType, 33 | department:tickets[ticket].department, 34 | comments:tickets[ticket].comments, 35 | status:tickets[ticket].status, 36 | date:tickets[ticket].date 37 | }); 38 | } 39 | this.setState({ 40 | tickets: newState 41 | }); 42 | } 43 | }); 44 | } 45 | render() { 46 | return ( 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | { 60 | this.state.tickets.length > 0 ? 61 | this.state.tickets.map((item,index) => { 62 | return ( 63 | 64 | 65 | 66 | 67 | 68 | 76 | 77 | 78 | ) 79 | }) : 80 | 81 | 82 | 83 | } 84 | 85 |
EmailIssue TypeDepartmentCommentsStatusCreation Date
{item.email}{item.issueType}{item.department}{item.comments} 69 | 75 | {item.date}
No tickets found.
86 | ); 87 | } 88 | } 89 | 90 | export default GetAllTickets; 91 | -------------------------------------------------------------------------------- /Chapter05/src/firebase/firebase-config.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase'; 2 | 3 | const config = { 4 | apiKey: "AIzaSyDO1VEnd5VmWd2OWQ9NQuh-ehNXcoPTy-w", 5 | authDomain: "demoproject-7cc0d.firebaseapp.com", 6 | databaseURL: "https://demoproject-7cc0d.firebaseio.com", 7 | projectId: "demoproject-7cc0d", 8 | storageBucket: "demoproject-7cc0d.appspot.com", 9 | messagingSenderId: "41428255556" 10 | }; 11 | 12 | export const firebaseApp = firebase.initializeApp(config); 13 | 14 | export const googleProvider = new firebase.auth.GoogleAuthProvider(); 15 | export const facebookProvider = new firebase.auth.FacebookAuthProvider(); 16 | 17 | export default firebase; -------------------------------------------------------------------------------- /Chapter05/src/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter05/src/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /Chapter05/src/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter05/src/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /Chapter05/src/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter05/src/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /Chapter05/src/header/header.css: -------------------------------------------------------------------------------- 1 | .firebase-nav .navbar-nav.navbar-right:last-child { 2 | margin-right: 0px; 3 | } 4 | .toaster{ 5 | width: 350px; 6 | } -------------------------------------------------------------------------------- /Chapter05/src/header/header.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom' 3 | import Navigation from './navigation'; 4 | import './header.css'; 5 | 6 | class Header extends Component { 7 | 8 | render() { 9 | return ( 10 |
11 | { 12 | this.props.authenticated 13 | ? 14 | ( 15 | 16 | ):( 17 | 18 |
    19 |
  • Register/Login
  • 20 |
21 |
22 | ) 23 | } 24 |
25 | 26 | ); 27 | } 28 | } 29 | 30 | export default Header; 31 | -------------------------------------------------------------------------------- /Chapter05/src/header/navigation.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom' 3 | 4 | class Navigation extends React.Component { 5 | render() { 6 | return ( 7 |
8 | { 9 | !this.props.role.admin 10 | ? 11 | ( 12 | 13 |
    14 |
  • Home
  • 15 |
  • Tickets
  • 16 |
  • Add new ticket
  • 17 |
  • Update Profile
  • 18 |
19 |
    20 |
  • Logout
  • 21 |
22 |
23 | ):( 24 | 25 |
    26 |
  • Application Users
  • 27 |
  • All Tickets
  • 28 |
  • Create New User
  • 29 |
30 |
    31 |
  • Logout
  • 32 |
33 |
34 | ) 35 | } 36 |
37 | 38 | ); 39 | } 40 | } 41 | 42 | export default Navigation; -------------------------------------------------------------------------------- /Chapter05/src/home/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Home extends Component { 4 | render() { 5 | var heading = {display: "inline-block", margin: "0px"}; 6 | var userPhoto = {float: "left", marginRight: "25px"} ; 7 | var marginBtm = {marginBottom:"15px"}; 8 | var admin = this.props.role.admin; 9 | return ( 10 |
11 | { 12 | this.props.userInfo.map((profile,index)=> { 13 | return ( 14 |
15 |

{ profile.displayName } - Welcome to Helpdesk Application {admin ? 'Admin' : ''}

16 | user 17 |
18 | Eamil: {profile.email } 19 |
20 | )}) 21 | } 22 |
23 | ) 24 | } 25 | } 26 | 27 | export default Home; 28 | -------------------------------------------------------------------------------- /Chapter05/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App.jsx'; 4 | import './bootstrap/bootstrap.min.css'; 5 | 6 | import registerServiceWorker from './registerServiceWorker'; 7 | 8 | ReactDOM.render(, 9 | document.getElementById('root') 10 | ); 11 | registerServiceWorker(); 12 | -------------------------------------------------------------------------------- /Chapter05/src/login/login.css: -------------------------------------------------------------------------------- 1 | .form-signin .btn-facebook {background: #3b5998; border: 1px solid #294c91;} 2 | .form-signin .btn-google {background: #c32f10; border: 1px solid #b1270c;} 3 | 4 | .wrapper { 5 | margin-top: 80px; 6 | margin-bottom: 80px; 7 | } 8 | 9 | .form-signin { 10 | max-width: 380px; 11 | padding: 15px 35px 45px; 12 | margin: 0 auto; 13 | background-color: #fff; 14 | border: 1px solid rgba(0,0,0,0.1); 15 | box-shadow: 0px 0px 5px #ccc; 16 | } 17 | .form-signin-heading,.checkbox { 18 | margin-bottom: 30px; 19 | } 20 | .form-signin-heading{ 21 | font-family:Arial, Helvetica, sans-serif; 22 | font-size: 22px; 23 | border-bottom: 1px solid #ccc; 24 | padding-bottom: 10px; 25 | } 26 | .checkbox { 27 | font-weight: normal; 28 | font-size: 13px; 29 | } 30 | 31 | .form-control { 32 | position: relative; 33 | font-size: 16px; 34 | height: auto; 35 | padding: 10px; 36 | } 37 | 38 | input[type="email"] { 39 | margin-bottom: -1px; 40 | } 41 | 42 | input[type="password"] { 43 | margin-bottom: 20px; 44 | } 45 | 46 | .form-signin input[type="email"],.form-signin input[type="password"]{ 47 | margin-bottom: 10px; 48 | border-radius: 0px; 49 | font-size: 13px; 50 | } 51 | .form-signin button{ 52 | font-size: 13px; 53 | border-radius: 0px; 54 | font-weight: normal; 55 | } 56 | .form-signin button:hover{ 57 | opacity:0.9; 58 | } 59 | .form-signin button.btn-google:hover{ 60 | background: #c32f10; 61 | border: 1px solid #b1270c; 62 | } 63 | .form-signin button.btn-facebook:hover{ 64 | background: #3b5998; 65 | border: 1px solid #294c91; 66 | } 67 | .form-signin .btn-normal{ 68 | background: rgb(206, 153, 38); 69 | border: 1px solid #998115; 70 | } 71 | 72 | .form-signin .btn-normal:hover{ 73 | background: #ce9926; 74 | border: 1px solid #b6851a; 75 | } -------------------------------------------------------------------------------- /Chapter05/src/login/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Redirect } from 'react-router-dom' 3 | import { firebaseApp , facebookProvider, googleProvider } from '../firebase/firebase-config'; 4 | import { ToastDanger } from 'react-toastr-basic'; 5 | 6 | import './login.css'; 7 | 8 | class Login extends Component { 9 | constructor() { 10 | super(); 11 | this.authWithEmailPassword = this.authWithEmailPassword.bind(this); 12 | this.authWithFacebook = this.authWithFacebook.bind(this); 13 | this.authWithGoogle = this.authWithGoogle.bind(this); 14 | this.state = { 15 | redirect: false, 16 | data:null 17 | } 18 | } 19 | 20 | authWithEmailPassword(e){ 21 | e.preventDefault(); 22 | console.log(this.emailField.value); 23 | const email = this.emailField.value 24 | const password = this.passwordField.value; 25 | 26 | firebaseApp.auth().fetchProvidersForEmail(email).then((provider)=>{ 27 | if(provider.length === 0){ 28 | //Creating a new user 29 | firebaseApp.auth().setPersistence('session'); 30 | //return firebaseApp.auth().createUserWithEmailAndPassword(email,password); 31 | } else if(provider.indexOf("password") === -1){ 32 | this.loginForm.reset(); 33 | ToastDanger('Wrong Password. Please try again!!') 34 | } else { 35 | //signin user 36 | return firebaseApp.auth().signInWithEmailAndPassword(email,password); 37 | } 38 | 39 | }).then((user) => { 40 | 41 | if(user && user.email){ 42 | this.loginForm.reset(); 43 | this.setState({redirect: true}); 44 | } 45 | }) 46 | .catch((error)=>{ 47 | console.log(error); 48 | ToastDanger(error.message); 49 | }) 50 | } 51 | 52 | authWithFacebook(){ 53 | console.log("facebook"); 54 | firebaseApp.auth().signInWithPopup(facebookProvider).then((result,error)=>{ 55 | if(error){ 56 | console.log("unable to sign in with facebook"); 57 | ToastDanger(error.message) 58 | } 59 | else{ 60 | this.setState({redirect:true,data:result.user}) 61 | } 62 | }).catch((error)=>{ 63 | ToastDanger(error.message); 64 | // if (error.code === 'auth/account-exists-with-different-credential') { 65 | // // Step 2. 66 | // var pendingCred = error.credential; 67 | // // The provider account's email address. 68 | // var email = error.email; 69 | // // Get registered providers for this email. 70 | // firebaseApp.auth().fetchProvidersForEmail(email).then(function(providers) { 71 | // // Step 3. 72 | // // If the user has several providers, 73 | // // the first provider in the list will be the "recommended" provider to use. 74 | // if (providers[0] === 'password') { 75 | // // Asks the user his password. 76 | // // In real scenario, you should handle this asynchronously. 77 | // var password = promptUserForPassword(); // TODO: implement promptUserForPassword. 78 | // firebaseApp.auth().signInWithEmailAndPassword(email, password).then(function(user) { 79 | // // Step 4a. 80 | // return user.link(pendingCred); 81 | // }).then(function() { 82 | // // Google account successfully linked to the existing Firebase user. 83 | 84 | // }); 85 | // } 86 | // }) 87 | // } 88 | }) 89 | } 90 | authWithGoogle(){ 91 | console.log("Google"); 92 | googleProvider.addScope('profile'); 93 | googleProvider.addScope('email'); 94 | firebaseApp.auth().signInWithPopup(googleProvider).then((result,error)=>{ 95 | if(error){ 96 | console.log("unable to sign in with google"); 97 | } 98 | else{ 99 | this.setState({redirect:true,data:result.user}) 100 | } 101 | }).catch((error)=>{ 102 | ToastDanger(error.message); 103 | }) 104 | } 105 | 106 | render() { 107 | if(this.state.redirect === true){ 108 | return 109 | } 110 | return ( 111 |
112 |
{this.authWithEmailPassword(event)}} ref={(form)=>{this.loginForm = form}}> 113 |

Login

114 | {this.emailField = input}} required /> 115 | {this.passwordField = input}} required /> 116 | 119 | 120 |
121 | 122 | 123 |
124 | 125 |
126 | 127 | ); 128 | } 129 | } 130 | 131 | export default Login; 132 | -------------------------------------------------------------------------------- /Chapter05/src/logout/logout.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Redirect } from 'react-router-dom' 3 | import { firebaseApp } from '../firebase/firebase-config'; 4 | 5 | class Logout extends Component { 6 | constructor(props) { 7 | super(); 8 | this.state = { 9 | redirect: props.authenticated, 10 | data:'' 11 | } 12 | } 13 | componentWillMount(){ 14 | firebaseApp.auth().signOut().then((user)=>{ 15 | this.setState({ 16 | redirect:true, 17 | data: null 18 | }) 19 | }) 20 | } 21 | 22 | 23 | render() { 24 | if(this.state.redirect === true){ 25 | return 26 | } 27 | return ( 28 |
29 |

Logging out...

30 |
31 | 32 | ); 33 | } 34 | } 35 | 36 | export default Logout; 37 | -------------------------------------------------------------------------------- /Chapter05/src/profile-update/profile-update.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | // import firebase from '../firebase/firebase-config'; 3 | import { ToastDanger,ToastSuccess } from 'react-toastr-basic'; 4 | 5 | class ProfileUpdateForm extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.handleSubmitEvent = this.handleSubmitEvent.bind(this); 10 | this.handleChange = this.handleChange.bind(this); 11 | console.log(props.userInfo); 12 | 13 | this.state={ 14 | uId:props.userId, 15 | name:props.displayName, 16 | email:props.email, 17 | password:props.password, 18 | phoneNumber:props.phoneNumber 19 | } 20 | } 21 | 22 | handleChange(event) { 23 | this.setState({ 24 | [event.target.id]: event.target.value 25 | }); 26 | } 27 | 28 | handleSubmitEvent(e) { 29 | e.preventDefault(); 30 | //React form data object 31 | const userId = this.state.uId; 32 | var data = { 33 | displayName: this.state.name, 34 | password: this.state.password, 35 | phoneNumber: this.state.phoneNumber, 36 | photoURL: this.fileInput.files[0] 37 | } 38 | console.log(data); 39 | 40 | fetch('http://localhost:3000/updateUserProfile', { 41 | method: 'POST', // or 'PUT' 42 | body: JSON.stringify({data:data,userId:userId}), 43 | headers: new Headers({ 44 | 'Content-Type': 'application/json' 45 | }) 46 | }).then(res => res.json()) 47 | .catch(error => { console.error('Error:', error); 48 | ToastDanger(error); 49 | }) 50 | .then(res => { 51 | console.log(res,"after token valid"); 52 | ToastSuccess(res.msg); 53 | }) 54 | } 55 | render() { 56 | var style = {color: "#ffaaaa"}; 57 | return ( 58 |
59 |
60 | 61 | 63 |
64 |
65 | 66 | 68 |
69 |
70 | 71 | 73 |
74 |
75 | 76 | 78 |
79 |
80 | 82 | 83 | { this.fileInput = input; }} /> 84 |
85 |
86 | 87 |
88 |
89 | ); 90 | } 91 | } 92 | 93 | export default ProfileUpdateForm; 94 | -------------------------------------------------------------------------------- /Chapter05/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | } else { 39 | // Is not local host. Just register service worker 40 | registerValidSW(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Chapter05/src/tickets-listing/ViewTickets.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import firebase from '../firebase/firebase-config'; 3 | 4 | class ViewTicketTable extends Component { 5 | constructor() { 6 | super(); 7 | this.state = { 8 | tickets: [] 9 | } 10 | } 11 | 12 | componentDidMount() { 13 | const itemsRef = firebase.database().ref('/helpdesk/tickets/'+this.props.userId); 14 | 15 | itemsRef.on('value', (snapshot) => { 16 | let tickets = snapshot.val(); 17 | if(tickets != null){ 18 | let ticketKeys = Object.keys(tickets); 19 | let newState = []; 20 | for (let ticket in tickets) { 21 | newState.push({ 22 | id:ticketKeys, 23 | email:tickets[ticket].email, 24 | issueType:tickets[ticket].issueType, 25 | department:tickets[ticket].department, 26 | comments:tickets[ticket].comments, 27 | status:tickets[ticket].status, 28 | date:tickets[ticket].date 29 | }); 30 | } 31 | this.setState({ 32 | tickets: newState 33 | }); 34 | } 35 | }); 36 | // firebase.auth().currentUser.getIdToken(true).then(function(idToken) { 37 | // // Send token to your backend via HTTPS 38 | // // ... 39 | // console.log(idToken); 40 | // }).catch(function(error) { 41 | // // Handle error 42 | // }); 43 | } 44 | render() { 45 | return ( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | { 59 | 60 | this.state.tickets.length > 0 ? 61 | this.state.tickets.map((item,index) => { 62 | return ( 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | ) 73 | }) : 74 | 75 | 76 | 77 | } 78 | 79 |
EmailIssue TypeDepartmentCommentsStatusDate
{item.email}{item.issueType}{item.department}{item.comments}{item.status === 'progress'?'In Progress':''}{item.date}
No tickets found.
80 | ); 81 | } 82 | } 83 | 84 | export default ViewTicketTable; 85 | -------------------------------------------------------------------------------- /Chapter05/src/tickets-listing/ViewTickets.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ViewTicketTable from './ViewTicketTable.jsx'; 4 | 5 | it('Renders view tickets component without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter07/cloud-functions.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter07/cloud-functions.7z -------------------------------------------------------------------------------- /Chapter07/cloud-messaging.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter07/cloud-messaging.7z -------------------------------------------------------------------------------- /Chapter08/chapter8.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Serverless-Web-Applications-with-React-and-Firebase/3b14ba3154988b8bf12c996edafa2133b033aa12/Chapter08/chapter8.7z -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Serverless Web Applications with React and Firebase 5 | This is the code repository for [Serverless Web Applications with React and Firebase](https://www.packtpub.com/web-development/serverless-web-applications-react-and-firebase?utm_source=github&utm_medium=repository&utm_campaign=9781788477413), published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the book from start to finish. 6 | ## About the Book 7 | Realtime applications have dominated the field of web applications for years. Real time isn't limited to only displaying data as soon as it's available; it shows its real power when used with interactive experiences, in which users and systems can instantly communicate with one another. With features such as virtual DOM and declarative views, React proves to be a better fit for such Realtime applications. Firebase makes building and rapid-prototyping these kinds of applications simpler by letting you focus on how the application should behave and look, without getting bogged down in the more tedious parts of Realtime development. 8 | 9 | This book will cover the Firebase features such as Cloud Storage, Cloud Function, Hosting, and Realtime Database integration with React to develop rich, collaborative, real-time applications using only client-side code. We can also see how we can secure our application using Firebase Authentication and Database Security Rules. We also leverage the power of Redux to organize the data in the frontend. Redux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can happen. Toward the end of the book, you will have improved your React skills by realizing the potential of Firebase to create real-time serverless web applications. 10 | 11 | This book provides more practical insights rather than just theoretical concepts and includes basic to advanced examples—from hello world to a realtime Web Application. 12 | ## Instructions and Navigation 13 | All of the code is organized into folders. Each folder starts with a number followed by the application name. For example, Chapter02. 14 | 15 | 16 | 17 | The code will look like the following: 18 | ``` 19 | constructor(props) { 20 | super(props); 21 | this.state = { 22 | value: props.initialValue 23 | }; 24 | } 25 | ``` 26 | 27 | You should have basic programming experience with React, HTML, CSS, and JavaScript to read this book profitably. It is assumed that you know already how Node Package Manager (npm) works to install any dependencies, and you have a basic idea about ES6 Syntax. 28 | 29 | ## Related Products 30 | * [React 16 Essentials - Second Edition](https://www.packtpub.com/web-development/react-16-essentials-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781787126046) 31 | 32 | * [React Native Blueprints](https://www.packtpub.com/web-development/react-native-blueprints?utm_source=github&utm_medium=repository&utm_campaign=9781787288096) 33 | 34 | * [Progressive Web Apps with React](https://www.packtpub.com/web-development/progressive-web-apps-react?utm_source=github&utm_medium=repository&utm_campaign=9781788297554) 35 | ### Download a free PDF 36 | 37 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
38 |

https://packt.link/free-ebook/9781788477413

--------------------------------------------------------------------------------