├── 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 | Send message
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 |
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 | Email
38 | Issue Type
39 | Department
40 | Comments
41 | Date
42 |
43 |
44 |
45 | {
46 | this.state.tickets.map((ticket) =>
47 | { return (
48 |
49 | {ticket.email}
50 | {ticket.issueType}
51 | {ticket.department}
52 | {ticket.comments}
53 | {ticket.date}
54 | )})
55 | }
56 |
57 |
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 |
15 |
31 |
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 |
15 |
31 |
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 |
26 | You need to enable JavaScript to run this app.
27 |
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 |
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 |
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 |
23 |
24 | ):(
25 |
26 |
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 |
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 |
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 | Email
39 | Issue Type
40 | Department
41 | Comments
42 | Date
43 |
44 |
45 |
46 | {this.state.tickets.map((item) => {
47 | return (
48 |
49 | {item.email}
50 | {item.issueType}
51 | {item.department}
52 | {item.comments}
53 | {item.date}
54 |
55 | )
56 | })}
57 |
58 |
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 You need to enable JavaScript to run this 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 |
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 |
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 |
70 |
71 |
72 |
73 |
74 | Email
75 | Name
76 | Last Sign in Time
77 | Creation Time
78 | Action
79 |
80 |
81 |
82 | {
83 | this.state.users.length > 0 ?
84 | this.state.users.map((list,index) => {
85 | return (
86 |
87 | {list.email}
88 | {list.displayName}
89 | {list.metadata.lastSignInTime}
90 | {list.metadata.creationTime}
91 |
92 |
93 | {this.deleteUser(list.uid)}}>Delete User
94 | {this.viewProfile(list.uid)}}>View Profile
95 |
96 |
97 | )
98 | }) :
99 |
100 | No users found.
101 |
102 | }
103 |
104 |
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 |
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 | Email
51 | Issue Type
52 | Department
53 | Comments
54 | Status
55 | Creation Date
56 |
57 |
58 |
59 | {
60 | this.state.tickets.length > 0 ?
61 | this.state.tickets.map((item,index) => {
62 | return (
63 |
64 | {item.email}
65 | {item.issueType}
66 | {item.department}
67 | {item.comments}
68 |
69 |
70 | Select
71 | Reolved
72 | In Progress
73 | Closed
74 |
75 |
76 | {item.date}
77 |
78 | )
79 | }) :
80 |
81 | No tickets found.
82 |
83 | }
84 |
85 |
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 |
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 |
22 |
23 | ):(
24 |
25 |
26 | Application Users
27 | All Tickets
28 | Create New User
29 |
30 |
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 |
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 |
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 |
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 | Email
50 | Issue Type
51 | Department
52 | Comments
53 | Status
54 | Date
55 |
56 |
57 |
58 | {
59 |
60 | this.state.tickets.length > 0 ?
61 | this.state.tickets.map((item,index) => {
62 | return (
63 |
64 |
65 | {item.email}
66 | {item.issueType}
67 | {item.department}
68 | {item.comments}
69 | {item.status === 'progress'?'In Progress':''}
70 | {item.date}
71 |
72 | )
73 | }) :
74 |
75 | No tickets found.
76 |
77 | }
78 |
79 |
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
--------------------------------------------------------------------------------