├── .gitignore ├── README.md ├── bunker ├── .gitignore ├── README.md ├── package.json ├── public │ ├── 404.html │ ├── bunkertransparent.gif │ ├── bunkertransparent.png │ ├── favicon.ico │ ├── index.html │ ├── landingbackground.jpg │ └── manifest.json ├── semantic.json └── src │ ├── App.js │ ├── commonComponents │ ├── CheckInOutCalendar.js │ ├── Navigation │ │ └── Navigation.js │ ├── RoomQuantitySelect.js │ └── RoomTypeSelect.js │ ├── constants │ ├── roles.js │ └── routes.js │ ├── images │ ├── LandingBackground.jpg │ ├── accountBackground.jpeg │ ├── accountBackground.jpg │ ├── accountBackground1.jpg │ ├── bunker.png │ └── linkedin_banner_image_1.png │ ├── index.css │ ├── index.js │ ├── pages │ ├── Account │ │ └── Account.js │ ├── Admin │ │ └── Admin.js │ ├── Home │ │ ├── Home.js │ │ ├── components │ │ │ ├── FilterSort.js │ │ │ ├── HotelCard.js │ │ │ ├── ListingBase.js │ │ │ ├── Maps.js │ │ │ ├── MapsHotel.js │ │ │ └── SearchBar.js │ │ ├── dummydata.js │ │ └── sticky.css │ ├── Hotel │ │ ├── Carousel.js │ │ ├── CheckOut │ │ │ ├── CheckOut.js │ │ │ ├── CheckOutForm.js │ │ │ └── conflict.css │ │ └── Hotel.js │ ├── Landing │ │ └── Landing.js │ ├── PasswordChange │ │ └── PasswordChange.js │ ├── PasswordForget │ │ └── PasswordForget.js │ ├── Reservation │ │ ├── CancelReservation │ │ │ ├── CancelReservation.js │ │ │ └── CancelReservationForm.js │ │ ├── ChangeReservation │ │ │ ├── ChangeReservation.js │ │ │ ├── ChangeReservationForm.js │ │ │ ├── ChangeReservationForm_alt2.js │ │ │ └── ChangeReservation_alt.js │ │ └── Reservation.js │ ├── SignIn │ │ └── SignIn.js │ ├── SignUp │ │ └── SignUp.js │ └── Users │ │ ├── UserItem.js │ │ ├── UserList.js │ │ └── index.js │ ├── server │ ├── Firebase │ │ ├── context.js │ │ ├── firebase.js │ │ ├── firebase3.js │ │ └── index.js │ ├── Payment │ │ └── PayPalButton.js │ └── Session │ │ ├── context.js │ │ ├── index.js │ │ ├── withAuthentication.js │ │ ├── withAuthorization.js │ │ └── withEmailVerification.js │ └── serviceWorker.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Windows template 3 | # Windows thumbnail cache files 4 | Thumbs.db 5 | ehthumbs.db 6 | ehthumbs_vista.db 7 | 8 | # Dump file 9 | *.stackdump 10 | 11 | # Folder config file 12 | [Dd]esktop.ini 13 | 14 | # Recycle Bin used on file shares 15 | $RECYCLE.BIN/ 16 | 17 | # Windows Installer files 18 | *.cab 19 | *.msi 20 | *.msix 21 | *.msm 22 | *.msp 23 | 24 | # Windows shortcuts 25 | *.lnk 26 | ### JetBrains template 27 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 28 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 29 | 30 | # User-specific stuff 31 | .idea/**/workspace.xml 32 | .idea/**/tasks.xml 33 | .idea/**/dictionaries 34 | .idea/**/shelf 35 | 36 | # Sensitive or high-churn files 37 | .idea/**/dataSources/ 38 | .idea/**/dataSources.ids 39 | .idea/**/dataSources.local.xml 40 | .idea/**/sqlDataSources.xml 41 | .idea/**/dynamic.xml 42 | .idea/**/uiDesigner.xml 43 | .idea/**/dbnavigator.xml 44 | 45 | # Gradle 46 | .idea/**/gradle.xml 47 | .idea/**/libraries 48 | 49 | # CMake 50 | cmake-build-debug/ 51 | cmake-build-release/ 52 | 53 | # Mongo Explorer plugin 54 | .idea/**/mongoSettings.xml 55 | 56 | # File-based project format 57 | *.iws 58 | 59 | # IntelliJ 60 | out/ 61 | 62 | # mpeltonen/sbt-idea plugin 63 | .idea_modules/ 64 | 65 | # JIRA plugin 66 | atlassian-ide-plugin.xml 67 | 68 | # Cursive Clojure plugin 69 | .idea/replstate.xml 70 | 71 | # Crashlytics plugin (for Android Studio and IntelliJ) 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | 77 | # Editor-based Rest Client 78 | .idea/httpRequests 79 | ### macOS template 80 | # General 81 | .DS_Store 82 | .AppleDouble 83 | .LSOverride 84 | 85 | # Icon must end with two \r 86 | Icon 87 | 88 | # Thumbnails 89 | ._* 90 | 91 | # Files that might appear in the root of a volume 92 | .DocumentRevisions-V100 93 | .fseventsd 94 | .Spotlight-V100 95 | .TemporaryItems 96 | .Trashes 97 | .VolumeIcon.icns 98 | .com.apple.timemachine.donotpresent 99 | 100 | # Directories potentially created on remote AFP share 101 | .AppleDB 102 | .AppleDesktop 103 | Network Trash Folder 104 | Temporary Items 105 | .apdisk 106 | 107 | # dependencies 108 | /node_modules 109 | 110 | 111 | package-lock.json 112 | .idea 113 | .env 114 | server/firebase/config.js 115 | yarn.lock 116 | bunker/src/server/firebase/config.js 117 | bunker/yarn.lock 118 | bunker/.firebase/hosting.cHVibGlj.cache 119 | bunker/.firebase/hosting.YnVpbGQ.cache 120 | bunker/.firebaserc 121 | bunker/database.rules.json 122 | bunker/firebase.json 123 | bunker/firestore.indexes.json 124 | bunker/firestore.rules 125 | bunker/package.json 126 | bunker/src/server/firebase/test.js 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bunker 2 | ![image](https://drive.google.com/uc?export=view&id=1cjC-sJsV3Jly4q6k0e1F5HSo68_la5FA) 3 | 4 | ### Statement of Work 5 | LikeHome is a web application (or mobile application) which shares catalog of ideas. LikeHomeis a mini clone of the very successful product hotels.com. 6 | 7 | ### Scope of Work 8 | The scope of this project is to design and develop LikeHome, which can manifest as an iOS, Android, Windows application. LikeHome should minimally provide the following functions: 9 | * Authentication (login/sign up) 10 | * Search: 11 | * Filter result 12 | * Sort results 13 | * Reservation 14 | * New 15 | * Change 16 | * Cancel 17 | * No multiple books in the same date under the same ID. 18 | * Payment Cancelation charge 19 | * Mybooking(show all my reservation) 20 | * Reward point 21 | * Redeem points for free stay System Interfaces LikeHome interacts with various interfaces, including but not limited to a web interface. 22 | 23 | ### User Interface 24 | LikeHome will have user interfaces that must look the same on iOS, Android, Windows or the Web. 25 | 26 | ### Design 27 | LikeHome is an easy to use application. Usability good GUI design and user-friendness should be the design focuses. 28 | 29 | ## Setup 30 | For team members of this project, please run the following commands: 31 | 32 | ```git clone https://github.com/cmpe165spring2019/ReactApp.git``` 33 | 34 | ```cd bunker``` 35 | 36 | ```npm install``` 37 | 38 | ```npm start``` 39 | 40 | 41 | To keep updated and get the latest commits from master 42 | 43 | ```git fetch origin``` 44 | 45 | ```git rebase origin/master``` 46 | -------------------------------------------------------------------------------- /bunker/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | bunker/yarn.lock 25 | bunker/src/server/firebase/config.js 26 | -------------------------------------------------------------------------------- /bunker/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /bunker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bunker", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@wojtekmaj/react-daterange-picker": "^2.1.0", 7 | "bootstrap": "^4.3.1", 8 | "css-loader": "^2.1.1", 9 | "firebase": "^5.8.5", 10 | "firebase-tools": "^6.7.1", 11 | "google-maps-react": "^2.0.2", 12 | "google-map-react": "^1.1.4", 13 | "lodash": "^4.17.11", 14 | "moment": "^2.24.0", 15 | "nuka-carousel": "^4.5.3", 16 | "prop-types": "^15.7.2", 17 | "react": "^16.8.3", 18 | "react-async-script-loader": "^0.3.0", 19 | "react-bootstrap": "^1.0.0-beta.8", 20 | "react-dom": "^16.8.2", 21 | "react-input-slider": "^5.0.6", 22 | "react-router-dom": "^4.3.1", 23 | "react-scripts": "^2.1.8", 24 | "react-sticky": "^6.0.3", 25 | "recompose": "^0.30.0", 26 | "semantic-ui-calendar-react": "^0.14.2", 27 | "semantic-ui-carousel-react": "^1.0.3", 28 | "semantic-ui-css": "^2.4.1", 29 | "semantic-ui-react": "^0.85.0", 30 | "style-loader": "^0.23.1" 31 | }, 32 | "scripts": { 33 | "start": "react-scripts start", 34 | "build": "react-scripts build", 35 | "test": "react-scripts test", 36 | "eject": "react-scripts eject" 37 | }, 38 | "eslintConfig": { 39 | "extends": "react-app" 40 | }, 41 | "browserslist": [ 42 | ">0.2%", 43 | "not dead", 44 | "not ie <= 11", 45 | "not op_mini all" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /bunker/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Page Not Found 7 | 8 | 23 | 24 | 25 |
26 |

404

27 |

Page Not Found

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /bunker/public/bunkertransparent.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmpe165spring2019/ReactApp/04c8604998d86d7e92afde4d95c532d5ac32bb95/bunker/public/bunkertransparent.gif -------------------------------------------------------------------------------- /bunker/public/bunkertransparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmpe165spring2019/ReactApp/04c8604998d86d7e92afde4d95c532d5ac32bb95/bunker/public/bunkertransparent.png -------------------------------------------------------------------------------- /bunker/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmpe165spring2019/ReactApp/04c8604998d86d7e92afde4d95c532d5ac32bb95/bunker/public/favicon.ico -------------------------------------------------------------------------------- /bunker/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 17 | 26 | 27 | 40 | 41 | 74 | 75 | 81 | 82 | Bunker 83 | 84 | 85 | 86 | 87 | 88 |
89 | Loading... 90 |
91 | 92 |
93 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /bunker/public/landingbackground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmpe165spring2019/ReactApp/04c8604998d86d7e92afde4d95c532d5ac32bb95/bunker/public/landingbackground.jpg -------------------------------------------------------------------------------- /bunker/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": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /bunker/semantic.json: -------------------------------------------------------------------------------- 1 | { 2 | "base": "semantic/", 3 | "paths": { 4 | "source": { 5 | "config": "src/theme.config", 6 | "definitions": "src/definitions/", 7 | "site": "src/site/", 8 | "themes": "src/themes/" 9 | }, 10 | "output": { 11 | "packaged": "/out/", 12 | "uncompressed": "/out/components/", 13 | "compressed": "/out/components/", 14 | "themes": "/out/themes/" 15 | }, 16 | "clean": "dist/" 17 | }, 18 | "permission": false, 19 | "autoInstall": false, 20 | "rtl": false, 21 | "components": ["reset", "site", "button", "container", "divider", "flag", "header", "icon", "image", "input", "label", "list", "loader", "placeholder", "rail", "reveal", "segment", "step", "breadcrumb", "form", "grid", "menu", "message", "table", "ad", "card", "comment", "feed", "item", "statistic", "accordion", "checkbox", "dimmer", "dropdown", "embed", "modal", "nag", "popup", "progress", "rating", "search", "shape", "sidebar", "sticky", "tab", "transition", "api", "form", "state", "visibility"], 22 | "version": "2.4.2" 23 | } -------------------------------------------------------------------------------- /bunker/src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {BrowserRouter as Router, Route} from "react-router-dom"; 3 | import {PayPalButton} from "./server/Payment/PayPalButton"; 4 | 5 | import Navigation from './commonComponents/Navigation/Navigation'; 6 | 7 | import LandingPage from './pages/Landing/Landing'; 8 | import SignUpPage from './pages/SignUp/SignUp'; 9 | import SignInPage from './pages/SignIn/SignIn'; 10 | import PasswordForgetPage from './pages/PasswordForget/PasswordForget'; 11 | import HomePage from './pages/Home/Home'; 12 | import AccountPage from './pages/Account/Account'; 13 | import AdminPage from './pages/Admin/Admin'; 14 | import HotelPage from './pages/Hotel/Hotel'; 15 | import ReservationPage from './pages/Reservation/Reservation'; 16 | 17 | import * as ROUTES from './constants/routes'; 18 | import { withAuthentication } from './server/Session'; 19 | 20 | 21 | const App = () => ( 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 | ); 37 | 38 | export default withAuthentication(App); 39 | -------------------------------------------------------------------------------- /bunker/src/commonComponents/CheckInOutCalendar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { DatesRangeInput } from "semantic-ui-calendar-react"; 4 | import * as moment from 'moment'; 5 | 6 | 7 | const today=moment().format('MM-DD-YYYY'); 8 | const aWeekFromToday = moment().add(5, 'days').format('MM-DD-YYYY'); 9 | const defaultDateRangeArray = [today, aWeekFromToday]; 10 | const defaultDateRange = defaultDateRangeArray.join(" - "); 11 | 12 | const CheckInOutCalendar = (props) => ( 13 | 23 | ) 24 | 25 | export default CheckInOutCalendar; 26 | -------------------------------------------------------------------------------- /bunker/src/commonComponents/Navigation/Navigation.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | 3 | // Backend functionality 4 | import {AuthUserContext} from "../../server/Session"; 5 | import * as ROUTES from "../../constants/routes"; 6 | import * as ROLES from "../../constants/roles"; 7 | import {withFirebase} from "../../server/Firebase/index"; 8 | 9 | // Components 10 | import {Link} from "react-router-dom"; 11 | import {Menu, Button, Dropdown, Image, Icon} from "semantic-ui-react"; 12 | import BunkerImage from "../../images/bunker.png"; 13 | 14 | const user = JSON.parse(localStorage.getItem("authUser")); 15 | 16 | class Navigation extends Component { 17 | constructor(props) { 18 | super(props); 19 | 20 | this.state = { 21 | firebase: null, 22 | location: "", 23 | activeItem: "" 24 | }; 25 | } 26 | 27 | componentDidMount() { 28 | const authUser = this.props.authUser || {}; 29 | this.setState({ 30 | reward_points: authUser.reward_points || 0 31 | }); 32 | } 33 | 34 | handleItemClick = (e, {name}) => { 35 | this.setState({activeItem: name}); 36 | if (name === "signout") { 37 | this.signOut(); 38 | } 39 | }; 40 | 41 | signOut = () => { 42 | this.props.firebase.doSignOut(); 43 | }; 44 | 45 | render() { 46 | const {activeItem} = this.state.activeItem; 47 | 48 | const NavigationAuth = ({authUser}) => { 49 | const [rewardPoints, setRewardPoints] = React.useState(0); 50 | React.useEffect( 51 | () => { 52 | setRewardPoints(authUser.reward_points); 53 | }, 54 | [authUser] 55 | ); 56 | return ( 57 |
58 | 59 | 60 | 65 | {/* */} 66 | 67 | Bunker 68 | 69 | 70 | 71 | 76 | 77 | Book Hotels 78 | 79 | 80 | 81 | 86 | 87 | Account 88 | 89 | 90 | {authUser.roles.includes(ROLES.ADMIN) && ( 91 | 92 | 97 | 98 | )} 99 | 100 | 101 | 102 | 103 | Reward Points: {rewardPoints} 104 | 105 | 106 | 111 | 112 | My Reservations 113 | 114 | 115 | 116 | 121 | 122 | Sign Out 123 | 124 | 125 | 126 |
127 | ); 128 | }; 129 | 130 | const NavigationNonAuth = () => ( 131 |
132 | 133 | 134 | 139 | {/* */} 140 | 141 | Bunker 142 | 143 | 144 | 145 | 150 | 151 | Book Hotels 152 | 153 | 154 | 155 | 156 | 157 | 162 | 163 | Sign In/Sign Up 164 | 165 | 166 | 167 | 168 |
169 | ); 170 | 171 | return ( 172 | 173 | {authUser => 174 | authUser ? ( 175 | 176 | ) : ( 177 | 178 | ) 179 | } 180 | 181 | ); 182 | } 183 | } 184 | 185 | export default withFirebase(Navigation); 186 | -------------------------------------------------------------------------------- /bunker/src/commonComponents/RoomQuantitySelect.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Dropdown } from 'semantic-ui-react'; 4 | 5 | import * as util from 'util' // has no default export 6 | 7 | 8 | const RoomQuantitySelect = (props) => { 9 | // console.log('props: ' + typeof(Integer(props.defaultValue))); 10 | 11 | let roomQuantityOptions = []; 12 | for(let i = 1; i < 17; i++){ 13 | let obj = { 14 | key: i, 15 | text: i, 16 | value: i 17 | }; 18 | roomQuantityOptions.push(obj); 19 | } 20 | 21 | return( 22 | 31 | ) 32 | } 33 | 34 | export default RoomQuantitySelect; 35 | -------------------------------------------------------------------------------- /bunker/src/commonComponents/RoomTypeSelect.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Select } from 'semantic-ui-react'; 4 | 5 | const roomTypeOptions = [ 6 | { 7 | key: 'single', 8 | text: 'Single-Person', 9 | value: 'single' 10 | }, 11 | { 12 | key: 'double', 13 | text: 'Double-Person', 14 | value: 'double' 15 | }, 16 | { 17 | key: 'multiple', 18 | text: 'Multiple-Person', 19 | value: 'multiple' 20 | } 21 | ]; 22 | 23 | const RoomTypeSelect = (props) => ( 24 | 85 | 86 | 87 | 88 | 89 |
90 | Sort By: 91 |
92 | 198 | ); 199 | } 200 | const bunkerStyle = { 201 | margin: "auto", 202 | width: "300px", 203 | height: "15%", 204 | }; 205 | const introDiv = { 206 | margin:"20px auto 0 auto ", 207 | width:"360px", 208 | height: "70px", 209 | // color: 'white' 210 | } 211 | // const introD = { 212 | // width:"360px", 213 | // height: "70px", 214 | // fontcolor:"grey", 215 | // } 216 | const Place = { 217 | margin:"0px auto 0 auto ", 218 | width:"360px", 219 | }; 220 | const InOutDiv = { 221 | width:"360px", 222 | margin:"20px auto 0 auto ", 223 | height: "60px", 224 | } 225 | const CheckIn = { 226 | float:'left', 227 | width:"180px", 228 | }; 229 | const CheckOut = { 230 | float:'left', 231 | width:"180px", 232 | }; 233 | const Guests = { 234 | margin:"20px auto 0 auto ", 235 | width:"360px", 236 | }; 237 | const buttonDiv = { 238 | margin:"-46px 0px 0 340px ", 239 | width:"360px" 240 | }; 241 | 242 | const image = { 243 | display: "block", 244 | margin: '50px auto 0 auto', 245 | width: "50%" 246 | 247 | } 248 | 249 | const boxStyle = { 250 | margin: "15px auto 0px auto", 251 | // padding-left:'auto' 252 | // padding-right:'auto' 253 | border: '5px solid white', 254 | borderRadius:"5px", 255 | width: '500px', 256 | height: '450px', 257 | backgroundColor: 'white', 258 | backgroundRepeat:'', 259 | position:'center', 260 | opacity: '0.95' 261 | }; 262 | 263 | const backgroundStyle = { 264 | // width: "100%", 265 | height: "100vh", 266 | backgroundImage: `url(${Background})`, 267 | backgroundRepeat: "null", 268 | backgroundSize: 'cover', 269 | overflow: 'hidden', 270 | 271 | }; 272 | 273 | export default withFirebase(Landing); 274 | -------------------------------------------------------------------------------- /bunker/src/pages/PasswordChange/PasswordChange.js: -------------------------------------------------------------------------------- 1 | //will push to Github 03/24/19 2 | import React, { Component } from 'react'; 3 | 4 | import { withFirebase } from '../../server/Firebase'; 5 | 6 | import { 7 | Container, 8 | Button, 9 | Form, 10 | Grid, 11 | Header, 12 | Segment, 13 | Divider, 14 | Message, 15 | Icon, 16 | Input, 17 | }from 'semantic-ui-react'; 18 | 19 | const INITIAL_STATE = { 20 | passwordOne: '', 21 | passwordTwo: '', 22 | error: null, 23 | }; 24 | 25 | class PasswordChangeForm extends Component { 26 | constructor(props) { 27 | super(props); 28 | 29 | this.state = { ...INITIAL_STATE }; 30 | } 31 | 32 | onSubmit = event => { 33 | const { passwordOne } = this.state; 34 | 35 | this.props.firebase 36 | .doPasswordUpdate(passwordOne) 37 | .then(() => { 38 | this.setState({ ...INITIAL_STATE }); 39 | }) 40 | .catch(error => { 41 | this.setState({ error }); 42 | }); 43 | 44 | event.preventDefault(); 45 | }; 46 | 47 | onChange = event => { 48 | this.setState({ [event.target.name]: event.target.value }); 49 | }; 50 | 51 | render() { 52 | const { passwordOne, passwordTwo, error } = this.state; 53 | 54 | const isInvalid = 55 | passwordOne !== passwordTwo || passwordOne === ''; 56 | 57 | return ( 58 | 59 | 60 | {/**/} 61 | 62 | 63 |
64 |
65 | 66 | 67 | Account Settings 68 |
69 | 70 |

Reset Password

71 | 79 | 86 | 89 | 90 | {error &&

{error.message}

} 91 | 92 |
93 |
94 | {/**/} 95 | 96 |
97 | ); 98 | } 99 | } 100 | 101 | export default withFirebase(PasswordChangeForm); 102 | -------------------------------------------------------------------------------- /bunker/src/pages/PasswordForget/PasswordForget.js: -------------------------------------------------------------------------------- 1 | // will push to Github 03/24/29 2 | import React, { Component } from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | import { withFirebase } from '../../server/Firebase'; 6 | import * as ROUTES from '../../constants/routes'; 7 | 8 | import { 9 | Button, 10 | Form, 11 | Grid, 12 | Header, 13 | Segment, 14 | Message, 15 | } from 'semantic-ui-react'; 16 | 17 | const PasswordForgetPage = () => ( 18 | 19 | 20 |
21 |

22 |

PasswordForget

23 | 24 |

No worry, please enter your registered email

25 | 26 |
27 |
28 |
29 | ); 30 | 31 | const INITIAL_STATE = { 32 | email: '', 33 | error: null, 34 | }; 35 | 36 | class PasswordForgetFormBase extends Component { 37 | constructor(props) { 38 | super(props); 39 | 40 | this.state = { ...INITIAL_STATE }; 41 | } 42 | 43 | onSubmit = event => { 44 | const { email } = this.state; 45 | 46 | this.props.firebase 47 | .doPasswordReset(email) 48 | .then(() => { 49 | this.setState({ ...INITIAL_STATE }); 50 | }) 51 | .catch(error => { 52 | this.setState({ error }); 53 | }); 54 | 55 | event.preventDefault(); 56 | }; 57 | 58 | onChange = event => { 59 | this.setState({ [event.target.name]: event.target.value }); 60 | }; 61 | 62 | render() { 63 | const { email, error } = this.state; 64 | 65 | const isInvalid = email === ''; 66 | 67 | return ( 68 | 69 | 70 | 71 | 72 |
73 |

74 | 75 | 83 | 86 | 87 | 88 | 89 | {error &&

****{error.message}****

} 90 | 91 |
92 | 93 | 94 | 95 |
96 | 97 | ); 98 | } 99 | } 100 | 101 | const PasswordForgetLink = () => ( 102 |

103 | Forgot Password? 104 |

105 | ); 106 | 107 | export default PasswordForgetPage; 108 | 109 | const PasswordForgetForm = withFirebase(PasswordForgetFormBase); 110 | 111 | export { PasswordForgetForm, PasswordForgetLink }; 112 | -------------------------------------------------------------------------------- /bunker/src/pages/Reservation/CancelReservation/CancelReservation.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Modal, 4 | Icon, 5 | Button, 6 | Message, 7 | Segment, 8 | Image, 9 | Confirm 10 | } from "semantic-ui-react"; 11 | import {withFirebase} from "../../../server/Firebase"; 12 | import CancelReservationForm from "./CancelReservationForm"; 13 | 14 | const CancelReservation = props => { 15 | 16 | const [openModal, setOpenModal] = React.useState(false); 17 | const [isConfirmOpen, setIsConfirmOpen] = React.useState(false); 18 | const [isError, setIsError] = React.useState(false); 19 | const [isOpen, setIsOpen] = React.useState(false); 20 | 21 | //fake data 22 | 23 | const {reservation, hotel} = props; 24 | const user = JSON.parse(localStorage.getItem("authUser")); 25 | 26 | const handleDeleteReservation = () => { 27 | console.log("handle Delete for: " + reservation.id); 28 | 29 | props.firebase 30 | .deleteReservationFromDB(reservation.id, user.uid, reservation.data.price) 31 | .catch(error => { 32 | setIsError(true); 33 | console.log(error); 34 | }); 35 | }; 36 | 37 | return ( 38 | setOpenModal(true)}> 42 | 43 | Delete this Reservation 44 | 45 | } 46 | open={openModal} 47 | onClose={()=>setOpenModal(false)} 48 | > 49 | Cancel Reservation 50 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 55 | } 56 | open={openModal} 57 | onClose={()=>setOpenModal(false)} 58 | > 59 | Edit Reservation 60 | 61 | 62 | 72 | 73 | 74 | 75 | 76 | 180 | 181 | {error && 182 | 183 | Oh snap! You got an error! 184 |

{error.message}

185 |
} 186 | 187 | ); 188 | } 189 | } 190 | 191 | const SignUpLink = () => ( 192 |

193 | Don't have an account? Sign Up 194 |

195 | ); 196 | 197 | const SignUpForm = compose( 198 | withRouter, 199 | withFirebase 200 | )(SignUpFormBase); 201 | 202 | export default SignUpPage; 203 | 204 | export {SignUpForm, SignUpLink}; 205 | -------------------------------------------------------------------------------- /bunker/src/pages/Users/UserItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { withFirebase } from '../../server/Firebase'; 4 | 5 | class UserItem extends Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.state = { 10 | loading: false, 11 | user: null, 12 | ...props.location.state, 13 | }; 14 | } 15 | 16 | componentDidMount() { 17 | if (this.state.user) { 18 | return; 19 | } 20 | 21 | this.setState({ loading: true }); 22 | 23 | this.props.firebase 24 | .user(this.props.match.params.id) 25 | .on('value', snapshot => { 26 | this.setState({ 27 | user: snapshot.val(), 28 | loading: false, 29 | }); 30 | }); 31 | } 32 | 33 | componentWillUnmount() { 34 | this.props.firebase.user(this.props.match.params.id).off(); 35 | } 36 | 37 | onSendPasswordResetEmail = () => { 38 | this.props.firebase.doPasswordReset(this.state.user.email); 39 | }; 40 | 41 | render() { 42 | const { user, loading } = this.state; 43 | 44 | return ( 45 |
46 |

User ({this.props.match.params.id})

47 | {loading &&
Loading ...
} 48 | 49 | {user && ( 50 |
51 | 52 | ID: {user.uid} 53 | 54 | 55 | E-Mail: {user.email} 56 | 57 | 58 | Username: {user.username} 59 | 60 | 61 | 67 | 68 |
69 | )} 70 |
71 | ); 72 | } 73 | } 74 | 75 | export default withFirebase(UserItem); 76 | -------------------------------------------------------------------------------- /bunker/src/pages/Users/UserList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | import { withFirebase } from '../../server/Firebase'; 5 | import * as ROUTES from '../../constants/routes'; 6 | 7 | class UserList extends Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | loading: false, 13 | users: [], 14 | }; 15 | } 16 | 17 | componentDidMount() { 18 | this.setState({ loading: true }); 19 | 20 | this.props.firebase.users().on('value', snapshot => { 21 | const usersObject = snapshot.val(); 22 | 23 | const usersList = Object.keys(usersObject).map(key => ({ 24 | ...usersObject[key], 25 | uid: key, 26 | })); 27 | 28 | this.setState({ 29 | users: usersList, 30 | loading: false, 31 | }); 32 | }); 33 | } 34 | 35 | componentWillUnmount() { 36 | this.props.firebase.users().off(); 37 | } 38 | 39 | render() { 40 | const { users, loading } = this.state; 41 | 42 | return ( 43 |
44 |

Users

45 | {loading &&
Loading ...
} 46 |
    47 | {users.map(user => ( 48 |
  • 49 | 50 | ID: {user.uid} 51 | 52 | 53 | E-Mail: {user.email} 54 | 55 | 56 | Username: {user.username} 57 | 58 | 59 | 65 | Details 66 | 67 | 68 |
  • 69 | ))} 70 |
71 |
72 | ); 73 | } 74 | } 75 | 76 | export default withFirebase(UserList); 77 | -------------------------------------------------------------------------------- /bunker/src/pages/Users/index.js: -------------------------------------------------------------------------------- 1 | import UserList from './UserList'; 2 | import UserItem from './UserItem'; 3 | 4 | export { UserList, UserItem }; 5 | -------------------------------------------------------------------------------- /bunker/src/server/Firebase/context.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const FirebaseContext = React.createContext(null); 4 | 5 | export const withFirebase = Component => props => ( 6 | 7 | {firebase => } 8 | 9 | ); 10 | 11 | export default FirebaseContext; 12 | -------------------------------------------------------------------------------- /bunker/src/server/Firebase/firebase.js: -------------------------------------------------------------------------------- 1 | import app from "firebase/app"; 2 | import "firebase/auth"; 3 | import "firebase/database"; 4 | import "firebase/firestore"; 5 | import _ from "lodash"; 6 | 7 | import Config from "./config"; 8 | 9 | const config = Config; 10 | class Firebase { 11 | constructor() { 12 | app.initializeApp(config); 13 | 14 | /* Helper */ 15 | 16 | this.serverValue = app.database.ServerValue; 17 | this.emailAuthProvider = app.auth.EmailAuthProvider; 18 | 19 | /* Firebase APIs */ 20 | 21 | this.auth = app.auth(); 22 | //this.db = app.database(); 23 | this.database = app.firestore(); 24 | 25 | this.FieldValue = app.firestore.FieldValue; 26 | 27 | /* Social Sign In Method Provider */ 28 | 29 | this.googleProvider = new app.auth.GoogleAuthProvider(); 30 | this.facebookProvider = new app.auth.FacebookAuthProvider(); 31 | this.twitterProvider = new app.auth.TwitterAuthProvider(); 32 | } 33 | 34 | // *** Auth API *** 35 | 36 | doCreateUserWithEmailAndPassword = (email, password) => 37 | this.auth.createUserWithEmailAndPassword(email, password); 38 | 39 | doSignInWithEmailAndPassword = (email, password) => 40 | this.auth.signInWithEmailAndPassword(email, password); 41 | 42 | doSignInWithGoogle = () => this.auth.signInWithPopup(this.googleProvider); 43 | 44 | doSignInWithFacebook = () => this.auth.signInWithPopup(this.facebookProvider); 45 | 46 | doSignInWithTwitter = () => this.auth.signInWithPopup(this.twitterProvider); 47 | 48 | doSignOut = () => this.auth.signOut(); 49 | 50 | doPasswordReset = email => this.auth.sendPasswordResetEmail(email); 51 | 52 | doSendEmailVerification = () => { 53 | this.auth.currentUser 54 | .sendEmailVerification({ 55 | url: config.url 56 | }) 57 | .then(() => console.log("Verification email sent.")) 58 | .catch(error => error); 59 | }; 60 | doPasswordUpdate = password => this.auth.currentUser.updatePassword(password); 61 | //Base API call 62 | user = uid => this.database.collection("users").doc(uid); 63 | 64 | hotelRef = uid => this.database.collection("hotels").doc(uid); 65 | 66 | locationRef = uid => this.database.collection("locations").doc(uid).get(); 67 | 68 | reservationRef = uid => this.database.collection("reservations").doc(uid); 69 | reservationsRef = () => this.database.collection("reservations"); 70 | 71 | // *** Merge Auth and DB User API *** // 72 | 73 | onAuthUserListener = (next, fallback) => 74 | this.auth.onAuthStateChanged(authUser => { 75 | let subscribe; 76 | if (authUser) { 77 | subscribe = this.user(authUser.uid) 78 | .onSnapshot( snapshot => { 79 | console.log(1); 80 | const dbUser = snapshot.data(); 81 | 82 | // default empty roles 83 | if (!dbUser.roles) { 84 | dbUser.roles = []; 85 | } 86 | 87 | // merge auth and db user 88 | authUser = { 89 | uid: authUser.uid, 90 | email: authUser.email, 91 | emailVerified: authUser.emailVerified, 92 | providerData: authUser.providerData, 93 | ...dbUser 94 | }; 95 | 96 | next(authUser); 97 | } 98 | ) 99 | 100 | } else { 101 | fallback(); 102 | } 103 | }); 104 | 105 | // *** Database API *** // 106 | 107 | getAllHotels = () => 108 | this.database 109 | .collection("hotels") 110 | .get() 111 | .then(hotels => { 112 | let result = []; 113 | hotels.forEach(snapshot => { 114 | const obj = { 115 | id: snapshot.id, 116 | data: {...snapshot.data()} 117 | }; 118 | result.push(obj); 119 | }); 120 | return result; 121 | }); 122 | 123 | addGoogleUserToDB = socialAuthUser => { 124 | return this.user(socialAuthUser.user.uid).set( 125 | { 126 | username: socialAuthUser.user.displayName, 127 | email: socialAuthUser.user.email, 128 | reservations: this.FieldValue.arrayUnion(""), 129 | reward_points: this.FieldValue.increment(0) 130 | }, 131 | {merge: true} 132 | ); 133 | }; 134 | 135 | addUserToDB = (authUser, email, username) => { 136 | const data = { 137 | user_id: authUser.user.uid, 138 | username: username, 139 | email: email, 140 | reservations: [], 141 | reward_points: 0 142 | }; 143 | 144 | return this.user(data.user_id) 145 | .set(data) 146 | .then(() => { 147 | console.log("Successfully created account."); 148 | return this.doSendEmailVerification(); 149 | }) 150 | .catch(error => error); 151 | }; 152 | 153 | editUserAccount = (user_id, data) => { 154 | return this.user(user_id) 155 | .update(data) 156 | .then(() => { 157 | console.log("User data was successfully changed"); 158 | return true; 159 | }) 160 | .catch(error => error); 161 | }; 162 | 163 | checkForConflictWithDates = (new_start, new_end, user_id) => { 164 | return this.reservationsRef() 165 | .where("user_id", "==", user_id) 166 | .get() 167 | .then(snapshots => { 168 | let reservations = []; 169 | snapshots.forEach(snapshot => { 170 | reservations.push({ 171 | data: snapshot.data() 172 | }); 173 | }); 174 | return reservations.every(res => { 175 | const check = 176 | new_end < res.data.start_date || new_start > res.data.end_date; 177 | console.log( 178 | new_start, 179 | res.data.start_date, 180 | new_end, 181 | res.data.end_date, 182 | check 183 | ); 184 | return check; 185 | }); 186 | }); 187 | }; 188 | 189 | addReservationToDB = (user_id, data, isUseReward, usedReward) => { 190 | return this.checkForConflictWithDates( 191 | data.start_date, 192 | data.end_date, 193 | user_id 194 | ).then(check => { 195 | if (check) { 196 | this.reservationsRef() 197 | .add(data) 198 | .then(res_doc => { 199 | if (isUseReward) { 200 | this.editUserAccount(user_id, { 201 | reservations: this.FieldValue.arrayUnion(res_doc.id), 202 | reward_points: this.FieldValue.increment(-usedReward) 203 | }); 204 | return true; 205 | } else { 206 | this.editUserAccount(user_id, { 207 | reservations: this.FieldValue.arrayUnion(res_doc.id), 208 | reward_points: this.FieldValue.increment( 209 | Math.floor((data.price || 0) / 10) 210 | ) 211 | }); 212 | return true; 213 | } 214 | }) 215 | .catch(error => console.log("Failed to add res " + error)); 216 | } else { 217 | console.log("Cant have multiple booking"); 218 | return false; 219 | } 220 | }); 221 | }; 222 | 223 | //edit reservation data 224 | editReservationInfo = (reservation_id, data, user_id) => { 225 | return this.reservationRef(reservation_id) 226 | .update(data) 227 | .then(() => { 228 | console.log("Reservation data was successfully changed"); 229 | return true; 230 | }) 231 | .catch(error => { 232 | console.error("Error editing document: ", error); 233 | return error; 234 | }); 235 | }; 236 | 237 | //Delete reservation 238 | deleteReservationFromDB = (reservation_id, user_id, price = 0) => { 239 | return this.user(user_id) 240 | .update({ 241 | reservations: this.FieldValue.arrayRemove(reservation_id), 242 | reward_points: this.FieldValue.increment(-Math.floor(price / 10)) 243 | }) 244 | .then(() => { 245 | return this.reservationRef(reservation_id) 246 | .delete() 247 | .then(() => { 248 | console.log("Done delete reservation"); 249 | }) 250 | .catch(err => { 251 | console.log("Error in delete reservation", err); 252 | return err; 253 | }); 254 | }) 255 | .catch(err => { 256 | console.log( 257 | "Error in remove reservations id or decrease reward_points" 258 | ); 259 | return err; 260 | }); 261 | }; 262 | 263 | //location Search function 264 | getCities = next => 265 | this.database 266 | .collection("locations") 267 | .get() 268 | .then(snapshot => { 269 | let cities = []; 270 | snapshot.forEach(city => { 271 | const obj = { 272 | id: city.id, 273 | data: {...city.data()} 274 | }; 275 | cities.push(obj); 276 | }); 277 | 278 | return cities; 279 | }); 280 | subscribeUserReward = (userID, doChange, doError) => { 281 | return this.user(userID).onSnapshot( 282 | snapshot => { 283 | doChange(snapshot.data().reward_points); 284 | }, 285 | error => { 286 | console.log(error); 287 | } 288 | ); 289 | }; 290 | 291 | subscribeReservations = ( 292 | userID, 293 | // start_date, 294 | doChange, 295 | doError 296 | ) => { 297 | return this.reservationsRef() 298 | .where("user_id", "==", userID) 299 | .orderBy("start_date") 300 | .onSnapshot( 301 | snapshot => { 302 | let reservations = []; 303 | snapshot.forEach(doc => 304 | reservations.push({id: doc.id, data: doc.data()}) 305 | ); 306 | doChange(reservations); 307 | }, 308 | error => { 309 | console.log(error); 310 | doError(error); 311 | } 312 | ); 313 | }; 314 | 315 | getReservations = reservationIDs => { 316 | let result = []; 317 | let promise = []; 318 | reservationIDs.forEach(reservationID => 319 | promise.push(this.reservationRef(reservationID).get()) 320 | ); 321 | return Promise.all(promise).then(snapshots => { 322 | snapshots.forEach(snapshot => { 323 | const obj = { 324 | id: snapshot.id, 325 | data: snapshot.data() 326 | }; 327 | result.push(obj); 328 | }); 329 | return result; 330 | }); 331 | }; 332 | 333 | getHotels = hotelIDs => { 334 | let promise = hotelIDs.map(hotelID => this.hotelRef(hotelID).get()); 335 | return Promise.all(promise).then(snapshots => { 336 | let result = snapshots.map(snapshot => ({ 337 | id: snapshot.id, 338 | data: snapshot.data() 339 | })); 340 | return result; 341 | }); 342 | }; 343 | 344 | //Data Retrive and filter 345 | getLocationHotel = location => { 346 | const promises = []; 347 | location.data.hotels.forEach(hotelRef => { 348 | promises.push(hotelRef.get()); 349 | }); 350 | return Promise.all(promises).then(snapshots =>{ 351 | let result = snapshots.map(snapshot => ({ 352 | id: snapshot.id, 353 | data: snapshot.data() 354 | })) 355 | return result; 356 | }) 357 | }; 358 | 359 | getHotelsRoomTypeSearch = (hotels, room_types) => { 360 | let result = []; 361 | hotels.forEach(hotel => { 362 | const check = room_types.every(room_type => 363 | hotel.data.room_types.includes(room_type) 364 | ); 365 | if (check) result.push(hotel); 366 | }); 367 | return result; 368 | }; 369 | 370 | getHotelRoomAvailableDate = (hotels, date_start, date_end) => { 371 | let result = []; 372 | hotels.forEach(hotel => { 373 | const isAvailable = hotel.data.rooms.some(room => 374 | room.unavailable_dates.every(dateRange => { 375 | const roomCheck = 376 | date_end < dateRange.startDate || date_start > dateRange.endDate; 377 | if (!roomCheck) hotel.data.rooms.pop(room); 378 | return roomCheck; 379 | }) 380 | ); 381 | if (isAvailable) { 382 | result.push(hotel); 383 | } 384 | }); 385 | return result; 386 | }; 387 | 388 | updateUnavailableDatetoRoom = (hotel, date_start, date_end) => { 389 | let availableRoom = hotel[0].data.rooms[0]; 390 | const hotelRef = this.hotelRef(hotel.id); 391 | //remove the current available room 392 | hotelRef 393 | .update({ 394 | rooms: this.firebase.firestore.FieldValue.arrayRemove(availableRoom) 395 | }) 396 | .then(() => console.log("Successfully Remove available room")) 397 | .catch(error => console.log(error)); 398 | //Add edited available room 399 | availableRoom.unavailable_dates.push({startDate: 8, endDate: 8}); 400 | this.hotelRef(hotel.id) 401 | .update({ 402 | rooms: this.firebase.firestore.FieldValue.arrayUnion(availableRoom) 403 | }) 404 | .then(() => console.log("Successfully add edited available room")) 405 | .catch(error => console.log(error)); 406 | }; 407 | /** 408 | * filter hotels arrays 409 | * @param {array} hotels hotels object arrays 410 | * @param {array string} field the deep of the filter object. Ex: ['room', 'price'] will the{ hotel: { room: { price: 1}}} 411 | * Ex2: ['price'] will filter the {hotel: { price}} 412 | * @param {function} compareFunction function to compare. Ex: (a,b) => a < b 413 | * @param {any} compareValue value that will be compare to: filter everything larger smaller than 2, compareFunction = (a, b) => a < b, compareValue = 2 414 | * @return {array} array of hotels 415 | */ 416 | filterHotels = (hotels, field, compareFunction, compareValue) => { 417 | return hotels.filter(hotel => { 418 | return compareFunction(_.get(hotel, field), compareValue); 419 | }); 420 | }; 421 | /** 422 | * sorted hotels arrays 423 | * @param {array} hotels hotels object arrays 424 | * @param {array string} field the deep of the filter object. Ex: ['room', 'price'] will the{ hotel: { room: { price: 1}}} 425 | * Ex2: ['price'] will filter the {hotel: { price}} 426 | * @param {boolean} isAscending if true, sort Asccending, if false sort descending 427 | * @return {array} array of sorted hotels 428 | */ 429 | sortHotels = (hotels, field, isAscending = true) => { 430 | let type = typeof _.get(hotels[0], field); 431 | let compareFunction; 432 | if (type === "string") { 433 | compareFunction = (a, b) => { 434 | var stringA = _.get(a, field).toUpperCase(); // ignore upper and lowercase 435 | var stringB = _.get(b, field).toUpperCase(); // ignore upper and lowercase 436 | if (stringA < stringB) { 437 | return -1; 438 | } 439 | if (stringA > stringB) { 440 | return 1; 441 | } 442 | // names must be equal 443 | return 0; 444 | }; 445 | } else { 446 | compareFunction = (a, b) => _.get(a, field) - _.get(b, field); 447 | } 448 | if (isAscending) { 449 | return hotels.sort(compareFunction); 450 | } else return hotels.sort(compareFunction).reverse(); 451 | }; 452 | } 453 | 454 | export default Firebase; 455 | -------------------------------------------------------------------------------- /bunker/src/server/Firebase/firebase3.js: -------------------------------------------------------------------------------- 1 | import app from "firebase/app"; 2 | import "firebase/auth"; 3 | import "firebase/firestore"; 4 | import "firebase/database"; 5 | 6 | import Config from "./config"; 7 | 8 | const config = Config; 9 | 10 | class Firebase { 11 | constructor() { 12 | app.initializeApp(config); 13 | //Initialize firebase authentication 14 | this.auth = app.auth(); 15 | //Initialize firebase database 16 | this.database = app.firestore(); 17 | //Initialize Google Authentication 18 | this.googleProvider = new app.auth.GoogleAuthProvider(); 19 | } 20 | 21 | //Auth API 22 | //Google SignIn 23 | googleSignIn = () => { 24 | this.auth 25 | .signInWithPopup(this.googleProvider) 26 | .then(result => { 27 | console.log(result); 28 | console.log("Google Account Linked"); 29 | }) 30 | .catch(err => { 31 | console.log(err); 32 | console.log("Failed to link."); 33 | }); 34 | }; 35 | 36 | signIn = (email, password) => { 37 | this.auth.signInWithEmailAndPassword(email, password).catch(error => { 38 | return error; 39 | }); 40 | }; 41 | 42 | //Google Logout 43 | signOut = () => { 44 | this.auth.signOut().catch(error => { 45 | return error; 46 | }); 47 | }; 48 | 49 | createAccount = (email, password, data) => { 50 | this.auth 51 | .createUserWithEmailAndPassword(email, password) 52 | .then(authUser => { 53 | data.user_id = authUser.user.uid; 54 | data.reservations = []; 55 | data.reward_points = 0; 56 | this.database 57 | .collection("users") 58 | .doc(data.user_id) 59 | .set(data) 60 | .then(console.log("Successfully created account.")) 61 | .catch(error => error); 62 | }) 63 | .catch(error => { 64 | return error; 65 | }); 66 | }; 67 | 68 | // ******User API********** 69 | //edit user data 70 | editUser = (user_id, data) => { 71 | this.database 72 | .collection("users") 73 | .doc(user_id) 74 | .update(data) 75 | .then(() => { 76 | console.log("User data was successfully changed"); 77 | return true; 78 | }) 79 | .catch(error => { 80 | console.error("Error editing document: ", error); 81 | return false; 82 | }); 83 | }; 84 | 85 | getCities = () => 86 | this.firestore 87 | .collection("locations") 88 | .get() 89 | .then(snapshot => { 90 | let cities = []; 91 | snapshot.forEach(city => { 92 | const obj = { 93 | id: city.id, 94 | data: {...city.data()} 95 | }; 96 | cities.push(obj); 97 | }); 98 | 99 | return cities; 100 | }); 101 | getLocationHotel = location => { 102 | let hotels = []; 103 | location.data.hotels.forEach(hotelRef => { 104 | hotelRef.get().then(hotel => { 105 | const obj = { 106 | id: hotel.id, 107 | data: {...hotel.data()} 108 | }; 109 | hotels.push(obj); 110 | }); 111 | }); 112 | return hotels; 113 | }; 114 | 115 | getHotelsRoomTypeSearch = (hotels, room_types) => { 116 | let result = []; 117 | hotels.forEach(hotel => { 118 | const check = room_types.every(room_type => 119 | hotel.data.room_types.includes(room_type) 120 | ); 121 | if (check) result.push(hotel); 122 | }); 123 | return result; 124 | }; 125 | 126 | getHotelRoomAvailableDate = (hotels, date_start, date_end) => { 127 | let result = []; 128 | hotels.forEach(hotel => { 129 | const isAvaliable = hotel.data.rooms.some(room => 130 | room.unavailable_dates.every(dateRange => { 131 | const roomCheck = 132 | date_end < dateRange.startDate || date_start > dateRange.endDate; 133 | if (!roomCheck) hotel.data.rooms.pop(room); 134 | return roomCheck; 135 | }) 136 | ); 137 | if (isAvaliable) { 138 | result.push(hotel); 139 | } 140 | }); 141 | return result; 142 | }; 143 | 144 | updateUnavailableDatetoRoom = (hotel, date_start, date_end) => { 145 | let availableRoom = hotel[0].data.rooms[0]; 146 | const hotelRef = this.database.collection("hotels").doc(hotel.id); 147 | //remove the current available room 148 | hotelRef 149 | .update({ 150 | rooms: this.firebase.firestore.FieldValue.arrayRemove(availableRoom) 151 | }) 152 | .then(() => console.log("Successfully Remove available room")) 153 | .catch(error => console.log(error)); 154 | //Add edited available room 155 | availableRoom.unavailable_dates.push({startDate: 8, endDate: 8}); 156 | this.database 157 | .collection("hotels") 158 | .doc(hotel.id) 159 | .update({ 160 | rooms: this.firebase.firestore.FieldValue.arrayUnion(availableRoom) 161 | }) 162 | .then(() => console.log("Successfully add edited available room")) 163 | .catch(error => console.log(error)); 164 | }; 165 | 166 | //*****Reservation API********* 167 | //add reservation data 168 | addReservation = (user_id, data) => { 169 | //Check that data has valid properties 170 | //get user's document 171 | let user; 172 | this.database 173 | .collection("users") 174 | .doc(user_id) 175 | .get() 176 | .then(snapshot => { 177 | user = { 178 | id: snapshot.id, 179 | data: snapshot.data() 180 | }; 181 | }) 182 | .catch(error => error("Users not exits")); 183 | if ( 184 | data.hasOwnProperty("user_id") && 185 | data.hasOwnProperty("hotel_id") && 186 | data.hasOwnProperty("room_id") && 187 | data.hasOwnProperty("price") && 188 | data.hasOwnProperty("start_date") && 189 | data.hasOwnProperty("end_date") 190 | ) { 191 | //Create new reservation document 192 | this.database 193 | .collection("reservations") 194 | .add(data) 195 | .then(res_doc => { 196 | //Add reservation_id to reservation document 197 | data.reservation_id = res_doc.id; 198 | this.editReservation(res_doc.id, data); 199 | let new_res = user.data.reservations; 200 | new_res.push(data.reservation_id); 201 | this.editUser(user_id, {reservation: new_res}); 202 | }) 203 | .catch(err => { 204 | console.log("Failed to add new reservation. " + err); 205 | return false; 206 | }); 207 | return true; 208 | } else return false; 209 | }; 210 | 211 | //edit reservation data 212 | editReservation = (reservation_id, data) => { 213 | this.database 214 | .collection("reservations") 215 | .doc(reservation_id) 216 | .update(data) 217 | .then(() => { 218 | console.log("Reservation data was successfully changed"); 219 | return true; 220 | }) 221 | .catch(error => { 222 | console.error("Error editing document: ", error); 223 | return false; 224 | }); 225 | }; 226 | 227 | //Delete reservation 228 | deleteReservation = (reservation_id, user_id) => { 229 | //delete from reservations collection 230 | this.database 231 | .collection("reservations") 232 | .doc(reservation_id) 233 | .delete() 234 | .then(() => { 235 | console.log( 236 | "Successfully deleted reservation from reservation collection." 237 | ); 238 | //delete reservation from users reservations 239 | this.database 240 | .collection("users") 241 | .doc(user_id) 242 | .get() 243 | .then(doc => { 244 | let user_res = doc.data().reservations; //array of users reservation_id's 245 | //Update user's reservations 246 | if (user_res.indexOf(reservation_id) >= 0) { 247 | user_res.splice(user_res.indexOf(reservation_id), 1); //remove reservation_id from array 248 | this.editUser(user_id, {reservations: user_res}); 249 | } else { 250 | console.log("Reservation was not present."); 251 | return false; 252 | } 253 | }) 254 | .catch(err => { 255 | console.log("Failed to delete reservation from user. " + err); 256 | return false; 257 | }); 258 | }) 259 | .catch(err => { 260 | console.log( 261 | "Failed to delete reservation from reservation collection. " + err 262 | ); 263 | return false; 264 | }); 265 | return true; 266 | }; 267 | } 268 | 269 | export default Firebase; 270 | -------------------------------------------------------------------------------- /bunker/src/server/Firebase/index.js: -------------------------------------------------------------------------------- 1 | import FirebaseContext, { withFirebase } from './context'; 2 | import Firebase from './firebase'; 3 | 4 | export default Firebase; 5 | 6 | export { FirebaseContext, withFirebase }; 7 | -------------------------------------------------------------------------------- /bunker/src/server/Payment/PayPalButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import scriptLoader from "react-async-script-loader"; 4 | import {paypalConfig} from "../Firebase/config"; 5 | import {withFirebase} from "../Firebase"; 6 | const CLIENT = paypalConfig; 7 | 8 | class PayPalButton extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | showButton: false 14 | }; 15 | 16 | window.React = React; 17 | window.ReactDOM = ReactDOM; 18 | } 19 | 20 | componentDidMount() { 21 | const {isScriptLoaded, isScriptLoadSucceed} = this.props; 22 | 23 | if (isScriptLoaded && isScriptLoadSucceed) { 24 | this.setState({showButton: true}); 25 | } 26 | } 27 | 28 | componentWillReceiveProps(nextProps) { 29 | const {isScriptLoaded, isScriptLoadSucceed} = nextProps; 30 | 31 | const isLoadedButWasntLoadedBefore = 32 | !this.state.showButton && !this.props.isScriptLoaded && isScriptLoaded; 33 | 34 | if (isLoadedButWasntLoadedBefore) { 35 | if (isScriptLoadSucceed) { 36 | this.setState({showButton: true}); 37 | } 38 | } 39 | } 40 | 41 | render() { 42 | const {total, currency, commit, onSuccess, onError, onCancel, new_start, new_end, user_id} = this.props; 43 | const env = "sandbox"; 44 | const client = CLIENT; 45 | const {showButton} = this.state; 46 | 47 | const paypal = window.PAYPAL; 48 | 49 | const payment = () => 50 | paypal.rest.payment.create(env, client, { 51 | transactions: [ 52 | { 53 | amount: { 54 | total, 55 | currency 56 | } 57 | } 58 | ] 59 | }); 60 | 61 | const onAuthorize = (data, actions) =>{ 62 | this.props.firebase.checkForConflictWithDates(new_start,new_end,user_id).then( check => { 63 | if(check){ 64 | actions.payment.execute().then(() => { 65 | const payment = { 66 | paid: true, 67 | cancelled: false, 68 | payerID: data.payerID, 69 | paymentID: data.paymentID, 70 | paymentToken: data.paymentToken, 71 | returnUrl: data.returnUrl 72 | }; 73 | onSuccess(payment); 74 | }); 75 | } 76 | else{ 77 | onError(new Error("MultipleBookingError")) 78 | } 79 | }) 80 | } 81 | const style = { 82 | size: 'medium', 83 | color: 'blue', 84 | shape: 'rect', 85 | } 86 | 87 | return ( 88 |
89 | {showButton && ( 90 | 100 | )} 101 |
102 | ); 103 | } 104 | } 105 | 106 | export default scriptLoader("https://www.paypalobjects.com/api/checkout.js")( 107 | withFirebase(PayPalButton) 108 | ); 109 | -------------------------------------------------------------------------------- /bunker/src/server/Session/context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const AuthUserContext = React.createContext(null); 4 | 5 | export default AuthUserContext; 6 | -------------------------------------------------------------------------------- /bunker/src/server/Session/index.js: -------------------------------------------------------------------------------- 1 | import AuthUserContext from './context'; 2 | import withAuthentication from './withAuthentication'; 3 | import withAuthorization from './withAuthorization'; 4 | import withEmailVerification from './withEmailVerification'; 5 | 6 | export { 7 | AuthUserContext, 8 | withAuthentication, 9 | withAuthorization, 10 | withEmailVerification, 11 | }; 12 | -------------------------------------------------------------------------------- /bunker/src/server/Session/withAuthentication.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import AuthUserContext from './context'; 4 | import { withFirebase } from '../Firebase/index'; 5 | 6 | const withAuthentication = Component => { 7 | class WithAuthentication extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | authUser: JSON.parse(localStorage.getItem('authUser')), 13 | }; 14 | } 15 | 16 | componentDidMount() { 17 | this.listener = this.props.firebase.onAuthUserListener( 18 | authUser => { 19 | localStorage.setItem('authUser', JSON.stringify(authUser)); 20 | this.setState({ authUser }); 21 | }, 22 | () => { 23 | localStorage.removeItem('authUser'); 24 | this.setState({ authUser: null }); 25 | }, 26 | ); 27 | } 28 | 29 | componentWillUnmount() { 30 | this.listener(); 31 | } 32 | 33 | render() { 34 | return ( 35 | 36 | 37 | 38 | ); 39 | } 40 | } 41 | 42 | return withFirebase(WithAuthentication); 43 | }; 44 | 45 | export default withAuthentication; 46 | -------------------------------------------------------------------------------- /bunker/src/server/Session/withAuthorization.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | import { compose } from 'recompose'; 4 | 5 | import AuthUserContext from './context'; 6 | import { withFirebase } from '../Firebase/index'; 7 | import * as ROUTES from '../../constants/routes'; 8 | 9 | const withAuthorization = condition => Component => { 10 | class WithAuthorization extends React.Component { 11 | componentDidMount() { 12 | this.listener = this.props.firebase.onAuthUserListener( 13 | authUser => { 14 | if (!condition(authUser)) { 15 | this.props.history.push(ROUTES.SIGN_IN); 16 | } 17 | }, 18 | () => this.props.history.push(ROUTES.SIGN_IN), 19 | ); 20 | } 21 | 22 | componentWillUnmount() { 23 | this.listener(); 24 | } 25 | 26 | render() { 27 | return ( 28 | 29 | {authUser => 30 | condition(authUser) ? : null 31 | } 32 | 33 | ); 34 | } 35 | } 36 | 37 | return compose( 38 | withRouter, 39 | withFirebase, 40 | )(WithAuthorization); 41 | }; 42 | 43 | export default withAuthorization; 44 | -------------------------------------------------------------------------------- /bunker/src/server/Session/withEmailVerification.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import AuthUserContext from './context'; 4 | import { withFirebase } from '../Firebase/index'; 5 | 6 | const needsEmailVerification = authUser => 7 | authUser && 8 | !authUser.emailVerified && 9 | authUser.providerData 10 | .map(provider => provider.providerId) 11 | .includes('password'); 12 | 13 | const withEmailVerification = Component => { 14 | class WithEmailVerification extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | 18 | this.state = { isSent: false }; 19 | } 20 | 21 | onSendEmailVerification = () => { 22 | this.props.firebase 23 | .doSendEmailVerification() 24 | .then(() => this.setState({ isSent: true })); 25 | }; 26 | 27 | render() { 28 | return ( 29 | 30 | {authUser => 31 | needsEmailVerification(authUser) ? ( 32 |
33 | {this.state.isSent ? ( 34 |

35 | E-Mail confirmation sent: Check your E-Mails (Spam 36 | folder included) for a confirmation E-Mail. 37 | Refresh this page once you confirmed your E-Mail. 38 |

39 | ) : ( 40 |

41 | Verify your E-Mail: Check your E-Mails (Spam folder 42 | included) for a confirmation E-Mail or send 43 | another confirmation E-Mail. 44 |

45 | )} 46 | 47 | 54 |
55 | ) : ( 56 | 57 | ) 58 | } 59 |
60 | ); 61 | } 62 | } 63 | 64 | return withFirebase(WithEmailVerification); 65 | }; 66 | 67 | export default withEmailVerification; 68 | -------------------------------------------------------------------------------- /bunker/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "se165-backend", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "lodash": "^4.17.11", 6 | "react": "^16.8.3" 7 | }, 8 | "devDependencies": { 9 | "firebase": "^5.8.3" 10 | } 11 | } 12 | --------------------------------------------------------------------------------