├── log
└── .keep
├── app
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── concerns
│ │ └── .keep
│ ├── review.rb
│ ├── booking.rb
│ ├── spot.rb
│ └── user.rb
├── assets
│ ├── images
│ │ ├── .keep
│ │ ├── bnb.png
│ │ ├── mainpage.png
│ │ ├── searchpage.png
│ │ ├── Logomakr_0gqPjJ.png
│ │ └── Logomakr_6djnyC.png
│ ├── stylesheets
│ │ ├── api
│ │ │ ├── reviews.scss
│ │ │ ├── spots.scss
│ │ │ ├── users.scss
│ │ │ ├── bookings.scss
│ │ │ └── sessions.scss
│ │ ├── static_pages.scss
│ │ ├── css_reset.scss
│ │ ├── CalendarMonthGrid.scss
│ │ ├── CalendarMonth.scss
│ │ ├── variables.scss
│ │ ├── DateRangePicker.scss
│ │ ├── footer.scss
│ │ ├── review.scss
│ │ ├── DateRangePickerInput.scss
│ │ ├── DateInput.scss
│ │ ├── DayPicker.scss
│ │ ├── DayPickerNavigation.scss
│ │ ├── search.scss
│ │ ├── booking.scss
│ │ ├── modal.scss
│ │ ├── CalendarDay.scss
│ │ ├── spots.scss
│ │ └── application.css.scss
│ └── javascripts
│ │ ├── api
│ │ ├── spots.coffee
│ │ ├── users.coffee
│ │ ├── bookings.coffee
│ │ ├── reviews.coffee
│ │ └── sessions.coffee
│ │ ├── static_pages.coffee
│ │ └── application.js
├── controllers
│ ├── concerns
│ │ └── .keep
│ ├── static_pages_controller.rb
│ ├── api
│ │ ├── users_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── reviews_controller.rb
│ │ ├── spots_controller.rb
│ │ └── bookings_controller.rb
│ └── application_controller.rb
├── helpers
│ ├── api
│ │ ├── spots_helper.rb
│ │ ├── users_helper.rb
│ │ ├── bookings_helper.rb
│ │ ├── reviews_helper.rb
│ │ └── sessions_helper.rb
│ ├── application_helper.rb
│ └── static_pages_helper.rb
└── views
│ ├── api
│ ├── users
│ │ ├── _user.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── sessions
│ │ ├── _user.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── reviews
│ │ ├── show.json.jbuilder
│ │ └── index.json.jbuilder
│ ├── bookings
│ │ ├── show.json.jbuilder
│ │ └── index.json.jbuilder
│ └── spots
│ │ ├── show.json.jbuilder
│ │ └── index.json.jbuilder
│ ├── static_pages
│ └── root.html.erb
│ └── layouts
│ └── application.html.erb
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── test
├── helpers
│ └── .keep
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── review_test.rb
│ ├── booking_test.rb
│ ├── user_test.rb
│ └── spot_test.rb
├── controllers
│ ├── .keep
│ ├── api
│ │ ├── spots_controller_test.rb
│ │ ├── users_controller_test.rb
│ │ ├── bookings_controller_test.rb
│ │ ├── reviews_controller_test.rb
│ │ └── sessions_controller_test.rb
│ └── static_pages_controller_test.rb
├── fixtures
│ ├── .keep
│ ├── reviews.yml
│ ├── users.yml
│ ├── bookings.yml
│ └── spots.yml
├── integration
│ └── .keep
└── test_helper.rb
├── vendor
└── assets
│ ├── javascripts
│ └── .keep
│ └── stylesheets
│ └── .keep
├── public
├── favicon.ico
├── robots.txt
├── 500.html
├── 422.html
└── 404.html
├── docs
├── wireframes
│ ├── Booking.png
│ ├── SpotShow.png
│ ├── SpotSearch.png
│ ├── LogIn Modal.png
│ ├── SIgnUp Modal.png
│ ├── IndividualHome.png
│ ├── FrontPage LoggedIn.png
│ └── FrontPage LoggedOut.png
├── api-endpoints.md
├── component-hierarchy.md
├── sample-state.md
├── README.md
└── schema.md
├── bin
├── bundle
├── rake
├── rails
├── spring
└── setup
├── config
├── boot.rb
├── initializers
│ ├── cookies_serializer.rb
│ ├── session_store.rb
│ ├── mime_types.rb
│ ├── filter_parameter_logging.rb
│ ├── backtrace_silencers.rb
│ ├── assets.rb
│ ├── to_time_preserves_timezone.rb
│ ├── wrap_parameters.rb
│ └── inflections.rb
├── environment.rb
├── routes.rb
├── locales
│ └── en.yml
├── secrets.yml
├── application.rb
├── environments
│ ├── development.rb
│ ├── test.rb
│ └── production.rb
└── database.yml
├── config.ru
├── db
├── migrate
│ ├── 20170428142247_add_rules_to_spots.rb
│ ├── 20170426132114_add_guest_limit_to_spots.rb
│ ├── 20170428142732_add_null_false_to_rules.rb
│ ├── 20170426132504_add_null_false_to_spot_guest_limit.rb
│ ├── 20170418223725_add_names_to_users.rb
│ ├── 20170418161431_create_users.rb
│ ├── 20170427141328_create_reviews.rb
│ ├── 20170426152955_create_bookings.rb
│ └── 20170420182132_create_spots.rb
├── schema.rb
└── seeds.rb
├── frontend
├── util
│ ├── search_api_util.js
│ ├── spot_api_util.js
│ ├── booking_api_util.js
│ ├── review_api_util.js
│ ├── session_api_util.js
│ └── marker_manager.js
├── components
│ ├── homepage
│ │ ├── homepage.jsx
│ │ └── homepage_container.js
│ ├── spots
│ │ ├── spot_show_container.js
│ │ ├── spot_index_container.js
│ │ ├── spot_index.jsx
│ │ ├── spot_index_item.jsx
│ │ └── spot_show.jsx
│ ├── greeting
│ │ ├── greeting_container.js
│ │ └── greeting.jsx
│ ├── carousel
│ │ ├── spot_carousel_container.js
│ │ └── spot_carousel.jsx
│ ├── review
│ │ ├── review_form_container.js
│ │ ├── review_index_container.js
│ │ ├── review_index_item.jsx
│ │ ├── review_index.jsx
│ │ └── review_form.jsx
│ ├── booking
│ │ ├── booking_index_container.js
│ │ ├── booking_container.js
│ │ ├── booking_index_item.jsx
│ │ ├── booking_index.jsx
│ │ └── booking.jsx
│ ├── app.jsx
│ ├── modal
│ │ ├── modal_container.js
│ │ └── modal.jsx
│ ├── navigation
│ │ ├── navigation_bar_container.js
│ │ ├── search_bar_container.js
│ │ ├── navigation_bar.jsx
│ │ └── search_bar.jsx
│ ├── search
│ │ ├── search_container.js
│ │ └── search.jsx
│ ├── login
│ │ ├── login_form_container.js
│ │ └── login_form.jsx
│ ├── signup
│ │ ├── signup_form_container.js
│ │ └── signup_form.jsx
│ ├── footer
│ │ └── footer.jsx
│ ├── root.jsx
│ └── map
│ │ └── map.jsx
├── actions
│ ├── modal_actions.js
│ ├── filter_actions.js
│ ├── review_actions.js
│ ├── spot_actions.js
│ ├── booking_actions.js
│ └── session_actions.js
├── reducers
│ ├── spot_reducer.js
│ ├── review_reducer.js
│ ├── root_reducer.js
│ ├── filters_reducer.js
│ ├── modal_reducer.js
│ ├── booking_reducer.js
│ └── session_reducer.js
├── store
│ └── store.js
└── entry.jsx
├── Rakefile
├── .gitignore
├── webpack.config.js
├── package.json
├── Gemfile
├── README.md
└── Gemfile.lock
/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/helpers/api/spots_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::SpotsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/users_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::UsersHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/bookings_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::BookingsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/reviews_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::ReviewsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/sessions_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::SessionsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/static_pages_helper.rb:
--------------------------------------------------------------------------------
1 | module StaticPagesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/views/api/users/_user.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! user, :fname, :lname
2 |
--------------------------------------------------------------------------------
/app/views/api/sessions/_user.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! user, :fname, :lname
2 |
--------------------------------------------------------------------------------
/app/views/api/sessions/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! @user, :fname, :lname
2 |
--------------------------------------------------------------------------------
/app/views/api/users/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'api/users/user', user: @user
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/app/views/api/reviews/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! @review, :id, :author, :spot, :rating, :body
2 |
--------------------------------------------------------------------------------
/app/assets/images/bnb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/app/assets/images/bnb.png
--------------------------------------------------------------------------------
/docs/wireframes/Booking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/docs/wireframes/Booking.png
--------------------------------------------------------------------------------
/docs/wireframes/SpotShow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/docs/wireframes/SpotShow.png
--------------------------------------------------------------------------------
/app/assets/images/mainpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/app/assets/images/mainpage.png
--------------------------------------------------------------------------------
/docs/wireframes/SpotSearch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/docs/wireframes/SpotSearch.png
--------------------------------------------------------------------------------
/app/assets/images/searchpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/app/assets/images/searchpage.png
--------------------------------------------------------------------------------
/docs/wireframes/LogIn Modal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/docs/wireframes/LogIn Modal.png
--------------------------------------------------------------------------------
/docs/wireframes/SIgnUp Modal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/docs/wireframes/SIgnUp Modal.png
--------------------------------------------------------------------------------
/app/views/api/bookings/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! @booking, :id, :user, :spot, :start_date, :end_date, :guest_number
2 |
--------------------------------------------------------------------------------
/docs/wireframes/IndividualHome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/docs/wireframes/IndividualHome.png
--------------------------------------------------------------------------------
/app/assets/images/Logomakr_0gqPjJ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/app/assets/images/Logomakr_0gqPjJ.png
--------------------------------------------------------------------------------
/app/assets/images/Logomakr_6djnyC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/app/assets/images/Logomakr_6djnyC.png
--------------------------------------------------------------------------------
/docs/wireframes/FrontPage LoggedIn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/docs/wireframes/FrontPage LoggedIn.png
--------------------------------------------------------------------------------
/app/controllers/static_pages_controller.rb:
--------------------------------------------------------------------------------
1 | class StaticPagesController < ApplicationController
2 | def root
3 |
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/docs/wireframes/FrontPage LoggedOut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojibuh/BillionairBnB/HEAD/docs/wireframes/FrontPage LoggedOut.png
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/app/views/api/spots/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! @spot, :id, :lat, :lng, :owner, :price, :location, :rules, :description
2 | json.image_url asset_path(@spot.image_url)
3 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/db/migrate/20170428142247_add_rules_to_spots.rb:
--------------------------------------------------------------------------------
1 | class AddRulesToSpots < ActiveRecord::Migration
2 | def change
3 | add_column :spots, :rules, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/api/reviews/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @reviews.each do |review|
2 | json.set! review.id do
3 | json.extract! review, :spot, :author, :rating, :body
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
4 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_BillionairBnB_session'
4 |
--------------------------------------------------------------------------------
/app/views/api/bookings/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @bookings.each do |booking|
2 | json.set! booking.id do
3 | json.extract! booking, :spot, :start_date, :end_date, :guest_number
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20170426132114_add_guest_limit_to_spots.rb:
--------------------------------------------------------------------------------
1 | class AddGuestLimitToSpots < ActiveRecord::Migration
2 | def change
3 | add_column :spots, :guest_limit, :integer
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20170428142732_add_null_false_to_rules.rb:
--------------------------------------------------------------------------------
1 | class AddNullFalseToRules < ActiveRecord::Migration
2 | def change
3 | change_column :spots, :rules, :string, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/test/controllers/api/spots_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::SpotsControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/users_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::UsersControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/bookings_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::BookingsControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/reviews_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::ReviewsControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/sessions_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::SessionsControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/static_pages_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class StaticPagesControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/reviews.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/reviews controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/spots.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/spots controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/users.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/users controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/db/migrate/20170426132504_add_null_false_to_spot_guest_limit.rb:
--------------------------------------------------------------------------------
1 | class AddNullFalseToSpotGuestLimit < ActiveRecord::Migration
2 | def change
3 | change_column :spots, :guest_limit, :integer, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/bookings.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/bookings controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/sessions.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/sessions controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/static_pages.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the StaticPages controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/views/api/spots/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @spots.each do |spot|
2 | json.set! spot.id do
3 | json.extract! spot, :id, :description, :lat, :lng, :location, :price
4 | json.image_url asset_path(spot.image_url)
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/db/migrate/20170418223725_add_names_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddNamesToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :fname, :string, null: false
4 | add_column :users, :lname, :string, null: false
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/spots.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/users.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/bookings.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/reviews.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/sessions.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/static_pages.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/frontend/util/search_api_util.js:
--------------------------------------------------------------------------------
1 | export const fetchBounds = (address) => {
2 | return $.ajax({
3 | method: 'GET',
4 | url: `https://maps.googleapis.com/maps/api/geocode/json?address=${address}&key=AIzaSyCeyaq4INBdNY82olkiTZT4o5RhTDTwLVs`
5 | });
6 | };
7 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../../config/application', __FILE__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/frontend/util/spot_api_util.js:
--------------------------------------------------------------------------------
1 | export const fetchSpots = (data) => {
2 | return $.ajax({
3 | method: 'GET',
4 | url: '/api/spots',
5 | data: data
6 | });
7 | };
8 |
9 | export const fetchSpot = (id) => {
10 | return $.ajax({
11 | method: 'GET',
12 | url: `/api/spots/${id}`,
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/frontend/util/booking_api_util.js:
--------------------------------------------------------------------------------
1 | export const createBooking = (booking) => {
2 | return $.ajax({
3 | method: 'POST',
4 | url: '/api/bookings',
5 | data: { booking }
6 | });
7 | };
8 |
9 | export const fetchBookings = () => {
10 | return $.ajax({
11 | method: 'GET',
12 | url: '/api/bookings'
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/frontend/util/review_api_util.js:
--------------------------------------------------------------------------------
1 | export const createReview = (review) => {
2 | return $.ajax({
3 | method: 'POST',
4 | url: '/api/reviews',
5 | data: { review }
6 | });
7 | };
8 |
9 | export const fetchReviews = (data) => {
10 | return $.ajax({
11 | method: 'GET',
12 | url: '/api/reviews',
13 | data: data
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require File.expand_path('../../config/environment', __FILE__)
3 | require 'rails/test_help'
4 |
5 | class ActiveSupport::TestCase
6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
7 | fixtures :all
8 |
9 | # Add more helper methods to be used by all tests here...
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20170418161431_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration
2 | def change
3 | create_table :users do |t|
4 | t.string :email, null: false
5 | t.string :password_digest, null: false
6 | t.string :session_token, null: false
7 |
8 | t.timestamps null: false
9 | end
10 |
11 | add_index :users, :email, unique: true
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/css_reset.scss:
--------------------------------------------------------------------------------
1 | html, body, section, article, h1, h2, p, button, input, div, ul, form, li, figure,
2 | img {
3 | margin: 0;
4 | border: 0;
5 | padding: 0;
6 | font: inherit;
7 | text-align: inherit;
8 | vertical-align: inherit;
9 | text-decoration: inherit;
10 | color: inherit;
11 | background: transparent;
12 | outline: none;
13 | list-style: none;
14 | }
15 |
--------------------------------------------------------------------------------
/app/controllers/api/users_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::UsersController < ApplicationController
2 |
3 | def create
4 | @user = User.new(user_params)
5 | if @user.save
6 | log_in(@user)
7 | render :show
8 | else
9 | render json: @user.errors , status: 422
10 | end
11 | end
12 |
13 | private
14 | def user_params
15 | params.require(:user).permit(:email, :fname, :lname, :password)
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/db/migrate/20170427141328_create_reviews.rb:
--------------------------------------------------------------------------------
1 | class CreateReviews < ActiveRecord::Migration
2 | def change
3 | create_table :reviews do |t|
4 | t.integer :author_id, null: false
5 | t.integer :spot_id, null: false
6 | t.integer :rating, null: false
7 | t.string :body, null: false
8 |
9 | t.timestamps null: false
10 | end
11 |
12 | add_index :reviews, :author_id
13 | add_index :reviews, :spot_id
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 |
3 | root to: 'static_pages#root'
4 |
5 | namespace :api, defaults: {format: :json} do
6 | resources :users, only: [:create]
7 | resource :session, only: [:create, :destroy]
8 | resources :spots, only: [:index, :create, :show, :destroy]
9 | resources :bookings, only: [:create, :index]
10 | resources :reviews, only: [:create, :index]
11 | end
12 |
13 | get '/*path', to: 'static_pages#root'
14 | end
15 |
--------------------------------------------------------------------------------
/frontend/util/session_api_util.js:
--------------------------------------------------------------------------------
1 | export const signup = (user) => {
2 | return $.ajax({
3 | method: 'POST',
4 | url: '/api/users',
5 | data: { user }
6 | });
7 | };
8 |
9 | export const login = (user) => {
10 | return $.ajax({
11 | method: 'POST',
12 | url: '/api/session',
13 | data: { user }
14 | });
15 | };
16 |
17 | export const logout = () => {
18 | return $.ajax({
19 | method: 'DELETE',
20 | url: '/api/session'
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/components/homepage/homepage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GreetingContainer from '../greeting/greeting_container';
3 | import SpotCarouselContainer from '../carousel/spot_carousel_container';
4 |
5 | class Homepage extends React.Component {
6 | render() {
7 | return (
8 |
this.mapNode = map }/>
64 | );
65 | }
66 | }
67 |
68 | export default withRouter(Map);
69 |
--------------------------------------------------------------------------------
/app/controllers/api/bookings_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::BookingsController < ApplicationController
2 |
3 | def create
4 | if start_date == nil
5 | render(
6 | json: ['Your booking must have a start date.'],
7 | status: 422
8 | )
9 | elsif end_date == nil
10 | render(
11 | json: ['Your booking must have an end date.'],
12 | status: 422
13 | )
14 | elsif guests == 0
15 | render(
16 | json: ['There must be at least one guest.'],
17 | status: 422
18 | )
19 | elsif start_date != '' && end_date != '' && guests > 0
20 | parsed_start = Booking.date_convert(start_date)
21 | parsed_end = Booking.date_convert(end_date)
22 | @booking = Booking.new({
23 | start_date: parsed_start,
24 | end_date: parsed_end,
25 | spot_id: spot_id,
26 | user_id: current_user.id,
27 | guest_number: guests
28 | })
29 |
30 | no_overlap = Booking.no_overlap?(parsed_start, parsed_end, spot_id)
31 | guest_limit = Booking.guest_limit(spot_id)
32 |
33 | if no_overlap && guest_limit >= guests && @booking.save
34 | render :show
35 | elsif no_overlap == false && guest_limit < guests
36 | render(
37 | json: [
38 | 'This booking conflicts with another. Try another date.',
39 | 'There are too many guests for this location.'
40 | ],
41 | status: 422
42 | )
43 | elsif no_overlap == false
44 | render(
45 | json: ['This booking conflicts with another. Try another date.'],
46 | status: 422
47 | )
48 | elsif guest_limit < guests
49 | render(
50 | json: ['There are too many guests for this location.'],
51 | status: 422
52 | )
53 | end
54 | end
55 |
56 | end
57 |
58 | def index
59 | @bookings = Booking.find_bookings(current_user.id)
60 | render :index
61 | end
62 |
63 | private
64 | def booking_params
65 | params.require(:booking).permit(:start_date, :end_date, :guest_number, :spot_id, :user_id)
66 | end
67 |
68 | def start_date
69 | params["booking"]["start_date"]
70 | end
71 |
72 | def end_date
73 | params["booking"]["end_date"]
74 | end
75 |
76 | def guests
77 | params["booking"]["guest_number"].to_i
78 | end
79 |
80 | def spot_id
81 | params["booking"]["spot_id"].to_i
82 | end
83 |
84 | end
85 |
--------------------------------------------------------------------------------
/frontend/components/navigation/navigation_bar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 | import SearchBarContainer from './search_bar_container';
4 |
5 | class NavigationBar extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | render() {
12 | const user = this.props.currentUser;
13 | if (user) {
14 | return (
15 |
40 | );
41 | } else {
42 | return (
43 |
69 | );
70 | }
71 | }
72 | }
73 |
74 | export default NavigationBar;
75 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/DayPickerNavigation.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .DayPickerNavigation__prev,
4 | .DayPickerNavigation__next {
5 | cursor: pointer;
6 | line-height: 0.78;
7 | -webkit-user-select: none; /* Chrome/Safari */
8 | -moz-user-select: none; /* Firefox */
9 | -ms-user-select: none; /* IE10+ */
10 | user-select: none;
11 | }
12 |
13 | .DayPickerNavigation__prev--default,
14 | .DayPickerNavigation__next--default {
15 | border: 1px solid $react-dates-color-border-light;
16 | background-color: $react-dates-color-white;
17 | color: $react-dates-color-placeholder-text;
18 |
19 | &:focus,
20 | &:hover {
21 | border: 1px solid $react-dates-color-border-medium;
22 | }
23 |
24 | &:active {
25 | background: darken($react-dates-color-white, 5%);
26 | }
27 | }
28 |
29 | .DayPickerNavigation--horizontal {
30 | position: relative;
31 |
32 | .DayPickerNavigation__prev,
33 | .DayPickerNavigation__next {
34 | border-radius: 3px;
35 | padding: 6px 9px;
36 | top: 18px;
37 | z-index: 2;
38 | position: absolute;
39 | }
40 |
41 | .DayPickerNavigation__prev {
42 | left: 22px;
43 | }
44 |
45 | .DayPickerNavigation__next {
46 | right: 22px;
47 | }
48 |
49 | .DayPickerNavigation__prev--default,
50 | .DayPickerNavigation__next--default {
51 | svg {
52 | height: 19px;
53 | width: 19px;
54 | fill: $react-dates-color-gray-light;
55 | }
56 | }
57 | }
58 |
59 | .DayPickerNavigation--vertical {
60 | background: $react-dates-color-white;
61 | box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1);
62 | position: absolute;
63 | bottom: 0;
64 | left: 0;
65 | height: 52px;
66 | width: 100%;
67 | z-index: 2;
68 |
69 | .DayPickerNavigation__prev,
70 | .DayPickerNavigation__next {
71 | display: inline-block;
72 | position: relative;
73 | height: 100%;
74 | width: 50%;
75 | }
76 |
77 | .DayPickerNavigation__next--default {
78 | border-left: 0;
79 | }
80 |
81 | .DayPickerNavigation__prev--default,
82 | .DayPickerNavigation__next--default {
83 | text-align: center;
84 | font-size: 2.5em;
85 | padding: 5px;
86 |
87 | svg {
88 | height: 42px;
89 | width: 42px;
90 | fill: $react-dates-color-text
91 | }
92 | }
93 | }
94 |
95 | .DayPickerNavigation--vertical-scrollable {
96 | position: relative;
97 |
98 | .DayPickerNavigation__next {
99 | width: 100%;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/search.scss:
--------------------------------------------------------------------------------
1 | .search-page {
2 | display: flex;
3 | flex-direction: row-reverse;
4 | justify-content: space-between;
5 | align-items: flex-start;
6 | min-width: 1100px;
7 | }
8 |
9 | .search-spots {
10 | width: 60%;
11 | height: 915px;
12 | overflow: auto;
13 | display: flex;
14 | flex-direction: row;
15 | justify-content: center;
16 | }
17 |
18 | .all-spots {
19 | width: 90%;
20 | display: flex;
21 | flex-flow: row wrap;
22 | justify-content: space-around;
23 | align-items: space-around;
24 | }
25 |
26 | .all-spots > div {
27 | height: 340px;
28 | }
29 |
30 | .search-spots figure {
31 | display: flex;
32 | flex-direction: column;
33 | align-items: flex-start;
34 | justify-content: space-around;
35 | padding-top: 10px;
36 | width: 420px;
37 | height: 310px;
38 | }
39 |
40 | .search-query input {
41 | text-align: center;
42 | }
43 |
44 | .search-spots img {
45 | width: 400px;
46 | height: 275px;
47 | }
48 |
49 | .search-bar {
50 | width: 950px;
51 | height: 50px;
52 | }
53 |
54 | input[type=number]::-webkit-inner-spin-button,
55 | input[type=number]::-webkit-outer-spin-button {
56 | -webkit-appearance: none;
57 | -moz-appearance: none;
58 | appearance: none;
59 | margin: 0;
60 | }
61 |
62 | .search-query {
63 | width: 82%;
64 | height: 100%;
65 | display: flex;
66 | flex-direction: row;
67 | justify-content: center;
68 | }
69 |
70 | .search-submit-button {
71 | visibility: hidden;
72 | width: 0px;
73 | height: 0px;
74 | }
75 |
76 | .where {
77 | width: 30%;
78 | border-top: 1px solid #DCDCDC;
79 | border-left: 1px solid #DCDCDC;
80 | border-bottom: 1px solid #DCDCDC;
81 | box-shadow: 0 4px 2px -2px whitesmoke;
82 | border-top-left-radius: 4px;
83 | border-bottom-left-radius: 4px;
84 | display: flex;
85 | flex-direction: row;
86 | align-items: center;
87 | transition: width 0.3s, border-color 0.5s;
88 | }
89 |
90 | .how-many {
91 | width: 30%;
92 | border-top: 1px solid #DCDCDC;
93 | border-right: 1px solid #DCDCDC;
94 | border-bottom: 1px solid #DCDCDC;
95 | box-shadow: 0 4px 2px -2px whitesmoke;
96 | border-top-right-radius: 4px;
97 | border-bottom-right-radius: 4px;
98 | display: flex;
99 | flex-direction: row;
100 | align-items: center;
101 | transition: border-color 0.5s;
102 | }
103 |
104 | .where:focus {
105 | border-bottom: 2px solid #008984;
106 | }
107 |
108 | .how-many:focus {
109 | border-bottom: 2px solid #008984;
110 | }
111 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/booking.scss:
--------------------------------------------------------------------------------
1 | .booking-container {
2 | width: 100%;
3 | height: 100%;
4 | }
5 |
6 | .booking-form {
7 | width: 100%;
8 | height: 100%;
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | justify-content: space-around;
13 | }
14 |
15 | .booking-price{
16 | font-size: 20px;
17 | }
18 |
19 | .calendar-label {
20 | width: 90%;
21 | display: flex;
22 | flex-direction: row;
23 | justify-content: space-around;
24 | }
25 |
26 | .numbers {
27 | width: 95%;
28 | display: flex;
29 | flex-direction: row;
30 | justify-content: center;
31 | }
32 |
33 | .guest-number {
34 | width: 90%;
35 | height: 45px;
36 | border: 1px solid #DCDCDC;
37 | text-align: center;
38 | }
39 |
40 | .booking-submit {
41 | background-color: #FF5A5F;
42 | color: #fff;
43 | font-weight: 300;
44 | padding: 20px 100px 20px 100px;
45 | border-radius: 5px;
46 | }
47 |
48 | .booking-submit:hover {
49 | cursor: pointer;
50 | }
51 |
52 | .booking-errors {
53 | width: 90%;
54 | }
55 |
56 | .booking-errors ul {
57 | color: red;
58 | display: flex;
59 | flex-direction: column;
60 | justify-content: center;
61 | align-items: center;
62 | }
63 |
64 | .bookings-page {
65 | width: 100%;
66 | display: flex;
67 | flex-direction: column;
68 | }
69 |
70 | .all-bookings {
71 | display: flex;
72 | flex-flow: row wrap;
73 | justify-content: space-around;
74 | }
75 |
76 | .bookings-page-title {
77 | font-size: 26px;
78 | color: #676767;
79 | font-weight: 600;
80 | padding: 10px 0px 10px 80px;
81 | }
82 |
83 | .individual-spot-container {
84 | display: flex;
85 | flex-direction: column;
86 | align-items: center;
87 | }
88 |
89 | .individual-spot {
90 | width: 100%;
91 | height: 100%;
92 | display: flex;
93 | flex-direction: column;
94 | align-items: center;
95 | margin: 10px;
96 | }
97 |
98 | .individual-spot img {
99 | width: 100%;
100 | height: 100%;
101 | }
102 |
103 | #book-link {
104 | font-size: 14px;
105 | font-weight: 400;
106 | text-decoration: none;
107 | outline: none;
108 | color: #676767;
109 | padding-bottom: 5.5px;
110 | border-bottom: 2px solid transparent;
111 | }
112 |
113 | #book-link:hover {
114 | border-bottom: 2px solid #676767;
115 | cursor: pointer;
116 | padding-bottom: 5.5px;
117 | }
118 |
119 | .booking-spot-image {
120 | width: 400px;
121 | height: 275px;
122 | }
123 |
124 | .spot-desc, .booking-range {
125 | font-size: 17px;
126 | color: #676767;
127 | font-weight: 400;
128 | }
129 |
--------------------------------------------------------------------------------
/frontend/components/carousel/spot_carousel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Slider from 'react-slick';
3 | import { Link } from 'react-router';
4 | import SpotIndexItem from '../spots/spot_index_item';
5 |
6 | class SpotCarousel extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | componentDidMount() {
13 | this.props.fetchSpots();
14 | }
15 |
16 | render() {
17 | //fix this so that featured homes is a separate query entirely
18 | const spots = Object.values(this.props.spots);
19 | const settings = {
20 | infinite: false,
21 | speed: 500,
22 | slidesToShow: 3,
23 | slidesToScroll: 1,
24 | arrows: true,
25 | draggable: false
26 | };
27 | if (spots.length > 0) {
28 | const featuredSpots = spots.slice(0, 6);
29 | const otherSpots = spots.slice(6, spots.length);
30 | if (featuredSpots.length === 6 && otherSpots.length === (spots.length - 6)) {
31 | return (
32 |
33 |
34 |
35 |
36 | Featured Homes
37 |
38 |
39 | See Map
40 |
41 |
42 |
43 |
44 | {featuredSpots.map((spot, idx) => {
45 | return
;
46 | })
47 | }
48 |
49 |
50 |
51 |
52 |
53 |
54 | More Homes
55 |
56 |
57 | See Map
58 |
59 |
60 |
61 |
62 | {otherSpots.map((spot, idx) => {
63 | return
;
64 | })
65 | }
66 |
67 |
68 |
69 |
70 | );
71 | } else {
72 | return (
73 |
74 | );
75 | }
76 | } else {
77 | return (
78 |
79 | );
80 | }
81 | }
82 | }
83 |
84 | export default SpotCarousel;
85 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # PostgreSQL. Versions 8.2 and up are supported.
2 | #
3 | # Install the pg driver:
4 | # gem install pg
5 | # On OS X with Homebrew:
6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config
7 | # On OS X with MacPorts:
8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
9 | # On Windows:
10 | # gem install pg
11 | # Choose the win32 build.
12 | # Install PostgreSQL and put its /bin directory on your path.
13 | #
14 | # Configure Using Gemfile
15 | # gem 'pg'
16 | #
17 | default: &default
18 | adapter: postgresql
19 | encoding: unicode
20 | # For details on connection pooling, see rails configuration guide
21 | # http://guides.rubyonrails.org/configuring.html#database-pooling
22 | pool: 5
23 |
24 | development:
25 | <<: *default
26 | database: BillionairBnB_development
27 |
28 | # The specified database role being used to connect to postgres.
29 | # To create additional roles in postgres see `$ createuser --help`.
30 | # When left blank, postgres will use the default role. This is
31 | # the same name as the operating system user that initialized the database.
32 | #username: BillionairBnB
33 |
34 | # The password associated with the postgres role (username).
35 | #password:
36 |
37 | # Connect on a TCP socket. Omitted by default since the client uses a
38 | # domain socket that doesn't need configuration. Windows does not have
39 | # domain sockets, so uncomment these lines.
40 | #host: localhost
41 |
42 | # The TCP port the server listens on. Defaults to 5432.
43 | # If your server runs on a different port number, change accordingly.
44 | #port: 5432
45 |
46 | # Schema search path. The server defaults to $user,public
47 | #schema_search_path: myapp,sharedapp,public
48 |
49 | # Minimum log levels, in increasing order:
50 | # debug5, debug4, debug3, debug2, debug1,
51 | # log, notice, warning, error, fatal, and panic
52 | # Defaults to warning.
53 | #min_messages: notice
54 |
55 | # Warning: The database defined as "test" will be erased and
56 | # re-generated from your development database when you run "rake".
57 | # Do not set this db to the same as development or production.
58 | test:
59 | <<: *default
60 | database: BillionairBnB_test
61 |
62 | # As with config/secrets.yml, you never want to store sensitive information,
63 | # like your database password, in your source code. If your source code is
64 | # ever seen by anyone, they now have access to your database.
65 | #
66 | # Instead, provide the password as a unix environment variable when you boot
67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
68 | # for a full rundown on how to provide these environment variables in a
69 | # production deployment.
70 | #
71 | # On Heroku and other platform providers, you may have a full connection URL
72 | # available as an environment variable. For example:
73 | #
74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
75 | #
76 | # You can use this database configuration with:
77 | #
78 | # production:
79 | # url: <%= ENV['DATABASE_URL'] %>
80 | #
81 | production:
82 | <<: *default
83 | database: BillionairBnB_production
84 | username: BillionairBnB
85 | password: <%= ENV['BILLIONAIRBNB_DATABASE_PASSWORD'] %>
86 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modal.scss:
--------------------------------------------------------------------------------
1 | .modal-screen {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | z-index: 999;
8 | background: rgba(0, 0, 0, 0.7);
9 | }
10 |
11 | .modal-content {
12 | position: absolute;
13 | z-index: 1000;
14 | top: 50%;
15 | left: 50%;
16 | width: 500px;
17 | max-width: 100%;
18 | padding: 50px;
19 | margin-left: -300px;
20 | transform: translateY(-50%);
21 | background: white;
22 | border-radius: 5px;
23 | }
24 |
25 | .signup-main, .login-main {
26 | display: flex;
27 | flex-direction: column;
28 | justify-content: space-around;
29 | align-items: center;
30 | }
31 |
32 | .signup-form, .login-form {
33 | width: 100%;
34 | height: 100%;
35 | }
36 |
37 | .signup-form input, .login-form input {
38 | text-align: center;
39 | }
40 |
41 | .signup-divider {
42 | width: 100%;
43 | color: #DCDCDC;
44 | }
45 |
46 | .signup-errors {
47 | color: red;
48 | }
49 |
50 | .login-errors {
51 | color: red;
52 | }
53 |
54 | .signup-password-errors {
55 | color: red;
56 | padding-bottom: 7px;
57 | list-style: none;
58 | }
59 |
60 | .login-submit, .signup-submit, .demo-login {
61 | width: 100%;
62 | background-color: #FF5A5F;
63 | color: white;
64 | padding-top: 10px;
65 | padding-bottom: 10px;
66 | text-align: center;
67 | border-radius: 4px;
68 | cursor: pointer;
69 | }
70 |
71 | .login-submit, .demo-login {
72 | margin-top: 8px;
73 | margin-bottom: 8px;
74 | }
75 |
76 | .signup{
77 | border: 1px solid #C0C0C0;
78 | margin-top: 8px;
79 | margin-bottom: 8px;
80 | }
81 |
82 | .login{
83 | border: 1px solid #C0C0C0;
84 | margin-top: 8px;
85 | margin-bottom: 8px;
86 | }
87 |
88 | .signup[type="text"] {
89 | padding-top: 10px;
90 | padding-bottom: 10px;
91 | width: 100%;
92 | }
93 |
94 | .login[type="text"] {
95 | padding-top: 10px;
96 | padding-bottom: 10px;
97 | width: 100%;
98 | }
99 |
100 | .signup[type="password"] {
101 | padding-top: 10px;
102 | padding-bottom: 10px;
103 | width: 100%;
104 | }
105 |
106 | .login[type="password"] {
107 | padding-top: 10px;
108 | padding-bottom: 10px;
109 | width: 100%;
110 | }
111 |
112 | .switch-to-signup {
113 | width: 100%;
114 | display: flex;
115 | flex-direction: row;
116 | justify-content: space-between;
117 | align-items: center;
118 | color: #676767;
119 | }
120 |
121 | .switch-to-login {
122 | width: 100%;
123 | display: flex;
124 | flex-direction: row;
125 | justify-content: space-between;
126 | color: #676767;
127 | align-items: center;
128 | }
129 |
130 | .switch-to-login button {
131 | padding: 8px 5px 8px 5px;
132 | border: 2px solid #008489;
133 | border-radius: 4px;
134 | color: #008489;
135 | font-weight: 400;
136 | text-align: center;
137 | cursor: pointer;
138 | }
139 |
140 | .switch-to-signup button {
141 | padding: 8px 5px 8px 5px;
142 | border: 2px solid #008489;
143 | border-radius: 4px;
144 | color: #008489;
145 | font-weight: 400;
146 | text-align: center;
147 | cursor: pointer;
148 | }
149 |
150 | .switch-to-signup p {
151 | color: #676767
152 | }
153 |
154 | .switch-to-login p {
155 | color: #676767
156 | }
157 |
158 | .switch {
159 | width: 90px;
160 | }
161 |
--------------------------------------------------------------------------------
/frontend/components/navigation/search_bar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DateRangePicker } from 'react-dates';
3 | import { fetchBounds } from '../../util/search_api_util';
4 | import { START_DATE, END_DATE } from 'react-dates/constants';
5 | import { withRouter } from 'react-router';
6 |
7 | class SearchBar extends React.Component {
8 |
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | address: '',
13 | startDate: this.props.startDate,
14 | endDate: this.props.endDate,
15 | guests: ''
16 | };
17 | this.handleSubmit = this.handleSubmit.bind(this);
18 | this.update = this.update.bind(this);
19 | }
20 |
21 | componentWillReceiveProps(newProps) {
22 | if (this.props.location.pathname !== newProps.location.pathname && newProps.location.pathname !== '/search') {
23 | this.setState({
24 | address: '',
25 | startDate: this.props.startDate,
26 | endDate: this.props.endDate,
27 | guests: ''
28 | });
29 | }
30 | }
31 |
32 | handleSubmit(e) {
33 | e.preventDefault();
34 | let address = this.state.address;
35 | let guests = this.state.guests === '' ? 0 : this.state.guests;
36 | let startDate = this.formatMoment(this.state.startDate);
37 | let endDate = this.formatMoment(this.state.endDate);
38 |
39 | fetchBounds(address).then(gmaps => {
40 | if (!!gmaps.results[0].geometry.viewport) {
41 | this.props.updateFilter([
42 | ['bounds', gmaps.results[0].geometry.viewport],
43 | ['guests', guests],
44 | ['startDate', startDate],
45 | ['endDate', endDate],
46 | ['address', gmaps.results[0].formatted_address]
47 | ]);
48 | }
49 | }).then(() => this.props.router.push('/search'));
50 | }
51 |
52 | update(key) {
53 | return ((e) => {
54 | this.setState({[key]: e.currentTarget.value});
55 | });
56 | }
57 |
58 | formatMoment(moment) {
59 | if (moment !== '') {
60 | let momentString = moment.format('YYYY,MM,DD');
61 | return momentString;
62 | }
63 | }
64 |
65 | render() {
66 | return(
67 |
68 |
91 |
92 | );
93 | }
94 | }
95 |
96 | export default withRouter(SearchBar);
97 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/CalendarDay.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | .CalendarDay {
4 | border: 1px solid lighten($react-dates-color-border-light, 3);
5 | padding: 0;
6 | box-sizing: border-box;
7 | color: $react-dates-color-gray;
8 | cursor: pointer;
9 | }
10 |
11 | .CalendarDay__button {
12 | position: relative;
13 | height: 100%;
14 | width: 100%;
15 | text-align: center;
16 | background: none;
17 | border: 0;
18 | margin: 0;
19 | padding: 0;
20 | color: inherit;
21 | font: inherit;
22 | line-height: normal;
23 | overflow: visible;
24 | cursor: pointer;
25 | box-sizing: border-box;
26 |
27 | &:active {
28 | background: darken($react-dates-color-white, 5%);
29 | outline: 0;
30 | }
31 | }
32 |
33 | // This order is important.
34 | .CalendarDay--highlighted-calendar {
35 | background: $react-dates-color-highlighted;
36 | color: $react-dates-color-gray;
37 | cursor: default;
38 |
39 | &:active {
40 | background: $react-dates-color-secondary;
41 | }
42 | }
43 |
44 | .CalendarDay--outside {
45 | border: 0;
46 | cursor: default;
47 |
48 | &:active {
49 | background: $react-dates-color-white;
50 | }
51 | }
52 |
53 | .CalendarDay--hovered {
54 | background: lighten($react-dates-color-border-light, 3);
55 | border: 1px double darken($react-dates-color-border-light, 3);
56 | color: inherit;
57 | }
58 |
59 | .CalendarDay--blocked-minimum-nights {
60 | color: $react-dates-color-gray-lighter;
61 | background: $react-dates-color-white;
62 | border: 1px solid lighten($react-dates-color-border-light, 3);
63 | cursor: default;
64 |
65 | &:active {
66 | background: $react-dates-color-white;
67 | }
68 | }
69 |
70 | .CalendarDay--selected-span {
71 | background: $react-dates-color-primary-shade-2;
72 | border: 1px double $react-dates-color-primary-shade-1;
73 | color: $react-dates-color-white;
74 |
75 | &.CalendarDay--hovered,
76 | &:active {
77 | background: $react-dates-color-primary-shade-1;
78 | border: 1px double $react-dates-color-primary;
79 | }
80 |
81 | &.CalendarDay--last-in-range {
82 | border-right: $react-dates-color-primary;
83 | }
84 | }
85 |
86 | .CalendarDay--hovered-span,
87 | .CalendarDay--after-hovered-start {
88 | background: $react-dates-color-primary-shade-4;
89 | border: 1px double $react-dates-color-primary-shade-3;
90 | color: $react-dates-color-secondary;
91 | }
92 |
93 | .CalendarDay--selected-start,
94 | .CalendarDay--selected-end,
95 | .CalendarDay--selected {
96 | background: $react-dates-color-primary;
97 | border: 1px double $react-dates-color-primary;
98 | color: $react-dates-color-white;
99 |
100 | &:active {
101 | background: $react-dates-color-primary;
102 | }
103 | }
104 |
105 | .CalendarDay--blocked-calendar {
106 | background: $react-dates-color-gray-lighter;
107 | color: $react-dates-color-gray-light;
108 | cursor: default;
109 |
110 | &:active {
111 | background: $react-dates-color-gray-lighter;
112 | }
113 | }
114 |
115 | .CalendarDay--blocked-out-of-range {
116 | color: $react-dates-color-gray-lighter;
117 | background: $react-dates-color-white;
118 | border: 1px solid lighten($react-dates-color-border-light, 3);
119 | cursor: default;
120 |
121 | &:active {
122 | background: $react-dates-color-white;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended that you check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(version: 20170428142732) do
15 |
16 | # These are extensions that must be enabled in order to support this database
17 | enable_extension "plpgsql"
18 |
19 | create_table "bookings", force: :cascade do |t|
20 | t.integer "user_id", null: false
21 | t.integer "spot_id", null: false
22 | t.date "start_date", null: false
23 | t.date "end_date", null: false
24 | t.integer "guest_number", null: false
25 | t.datetime "created_at", null: false
26 | t.datetime "updated_at", null: false
27 | end
28 |
29 | add_index "bookings", ["spot_id"], name: "index_bookings_on_spot_id", using: :btree
30 | add_index "bookings", ["user_id"], name: "index_bookings_on_user_id", using: :btree
31 |
32 | create_table "reviews", force: :cascade do |t|
33 | t.integer "author_id", null: false
34 | t.integer "spot_id", null: false
35 | t.integer "rating", null: false
36 | t.string "body", null: false
37 | t.datetime "created_at", null: false
38 | t.datetime "updated_at", null: false
39 | end
40 |
41 | add_index "reviews", ["author_id"], name: "index_reviews_on_author_id", using: :btree
42 | add_index "reviews", ["spot_id"], name: "index_reviews_on_spot_id", using: :btree
43 |
44 | create_table "spots", force: :cascade do |t|
45 | t.float "lat", null: false
46 | t.float "lng", null: false
47 | t.integer "owner_id", null: false
48 | t.integer "price", null: false
49 | t.string "location", null: false
50 | t.string "image_url", null: false
51 | t.text "description", null: false
52 | t.datetime "created_at", null: false
53 | t.datetime "updated_at", null: false
54 | t.integer "guest_limit", null: false
55 | t.string "rules", null: false
56 | end
57 |
58 | add_index "spots", ["image_url"], name: "index_spots_on_image_url", using: :btree
59 | add_index "spots", ["owner_id"], name: "index_spots_on_owner_id", using: :btree
60 | add_index "spots", ["price"], name: "index_spots_on_price", using: :btree
61 |
62 | create_table "users", force: :cascade do |t|
63 | t.string "email", null: false
64 | t.string "password_digest", null: false
65 | t.string "session_token", null: false
66 | t.datetime "created_at", null: false
67 | t.datetime "updated_at", null: false
68 | t.string "fname", null: false
69 | t.string "lname", null: false
70 | end
71 |
72 | add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
73 |
74 | end
75 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like
20 | # NGINX, varnish or squid.
21 | # config.action_dispatch.rack_cache = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
26 |
27 | # Compress JavaScripts and CSS.
28 | config.assets.js_compressor = :uglifier
29 | # config.assets.css_compressor = :sass
30 |
31 | # Do not fallback to assets pipeline if a precompiled asset is missed.
32 | config.assets.compile = false
33 |
34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35 | # yet still be able to expire them through the digest params.
36 | config.assets.digest = true
37 |
38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
39 |
40 | # Specifies the header that your server uses for sending files.
41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
43 |
44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
45 | config.force_ssl = true
46 |
47 | # Use the lowest log level to ensure availability of diagnostic information
48 | # when problems arise.
49 | config.log_level = :debug
50 |
51 | # Prepend all log lines with the following tags.
52 | # config.log_tags = [ :subdomain, :uuid ]
53 |
54 | # Use a different logger for distributed setups.
55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
61 | # config.action_controller.asset_host = 'http://assets.example.com'
62 |
63 | # Ignore bad email addresses and do not raise email delivery errors.
64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
65 | # config.action_mailer.raise_delivery_errors = false
66 |
67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
68 | # the I18n.default_locale when a translation cannot be found).
69 | config.i18n.fallbacks = true
70 |
71 | # Send deprecation notices to registered listeners.
72 | config.active_support.deprecation = :notify
73 |
74 | # Use default logging formatter so that PID and timestamp are not suppressed.
75 | config.log_formatter = ::Logger::Formatter.new
76 |
77 | # Do not dump schema after migrations.
78 | config.active_record.dump_schema_after_migration = false
79 | end
80 |
--------------------------------------------------------------------------------
/frontend/components/booking/booking.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DateRangePicker } from 'react-dates';
3 | import { withRouter } from 'react-router';
4 | import { START_DATE, END_DATE } from 'react-dates/constants';
5 |
6 | class Booking extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.handleSubmit = this.handleSubmit.bind(this);
11 | this.state = {
12 | startDate: '',
13 | endDate: '',
14 | guests: 0
15 | };
16 | }
17 |
18 | componentWillReceiveProps(newProps) {
19 | if(this.props.errors.length > 0) {
20 | this.props.clear();
21 | }
22 | }
23 |
24 | componentDidMount() {
25 | if(this.props.errors.length > 0) {
26 | this.props.clear();
27 | }
28 | }
29 |
30 | handleSubmit(e) {
31 | e.preventDefault();
32 | this.props.clear();
33 | let startDate = this.formatMoment(this.state.startDate);
34 | let endDate = this.formatMoment(this.state.endDate);
35 | if (this.props.currentUser) {
36 | this.props.createBooking({
37 | start_date: startDate,
38 | end_date: endDate,
39 | spot_id: this.props.spot.id,
40 | guest_number: parseInt(this.state.guests)
41 | }).then(() => this.props.router.push(`/bookings`));
42 | }
43 | }
44 |
45 | update(key) {
46 | return (event => this.setState({[key]: event.currentTarget.value}));
47 | }
48 |
49 | formatMoment(moment) {
50 | if (moment !== '') {
51 | let momentString = moment.format('YYYY,MM,DD');
52 | return momentString;
53 | }
54 | }
55 |
56 | dateRange(momentA, momentB) {
57 | if (momentA !== '' && momentB !== '') {
58 | return momentB.diff(momentA, 'days');
59 | } else {
60 | return 0;
61 | }
62 | }
63 |
64 | renderErrors() {
65 | if (!this.props.currentUser) {
66 | return (
Not Logged In
);
67 | } else if (this.props.errors) {
68 | return (this.props.errors.map((err, idx) => {
69 | return (
{ err });
70 | }));
71 | }
72 | }
73 |
74 | render() {
75 | const price = this.props.spot.price;
76 | const dateRange = this.dateRange(this.state.startDate, this.state.endDate);
77 | const guests = this.state.guests;
78 | return(
79 |
114 | );
115 | }
116 |
117 | }
118 |
119 | export default withRouter(Booking);
120 |
--------------------------------------------------------------------------------
/frontend/components/signup/signup_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 |
4 | class SignupForm extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | email: "",
9 | fname: "",
10 | lname: "",
11 | password: ""
12 | };
13 | this.handleSubmit = this.handleSubmit.bind(this);
14 | }
15 |
16 | handleSubmit(e) {
17 | e.preventDefault();
18 | const user = Object.assign({}, this.state);
19 | this.props.processForm(user).then(() => this.props.deactivate('signup'));
20 | }
21 |
22 | linkState(key) {
23 | return (event => this.setState({[key]: event.currentTarget.value}));
24 | }
25 |
26 | componentDidMount() {
27 | this.props.clear();
28 | }
29 |
30 | emailErrors() {
31 | let emailErrors = "";
32 | if (this.props.errors.email) {
33 | emailErrors = this.props.errors.email;
34 | emailErrors = emailErrors.map((err, idx) => {
35 | return (
{`Email ${err}`});
36 | });
37 | }
38 | return emailErrors;
39 | }
40 |
41 | fnameErrors() {
42 | let fnameErrors = '';
43 | if (this.props.errors.fname) {
44 | fnameErrors = this.props.errors.fname;
45 | fnameErrors = fnameErrors.map((err, idx) => {
46 | return (
{`First Name ${err}`});
47 | });
48 | }
49 | return fnameErrors;
50 | }
51 |
52 | lnameErrors() {
53 | let lnameErrors = '';
54 | if (this.props.errors.lname) {
55 | lnameErrors = this.props.errors.lname;
56 | lnameErrors = lnameErrors.map((err, idx) => {
57 | return (
{`Last Name ${err}`});
58 | });
59 | }
60 | return lnameErrors;
61 | }
62 |
63 | passwordErrors() {
64 | let passwordErrors = '';
65 | if (this.props.errors.password) {
66 | passwordErrors = this.props.errors.password;
67 | passwordErrors = passwordErrors.map((err, idx) => {
68 | return (
{`Password ${err}`});
69 | });
70 | }
71 | return passwordErrors;
72 | }
73 |
74 | render() {
75 | return(
76 |
77 |
104 |
105 |
106 |
Already have a BillionairBnB account?
107 |
108 |
109 |
110 | );
111 | }
112 | }
113 |
114 | export default withRouter(SignupForm);
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BillionairBnB
2 |
3 | Link to live site: [BillionairBnB](https://billionairbnb.herokuapp.com/)
4 |
5 | BillionairBnB is an application that allows its users to stay at luxury homes around the world. It is inspired by Airbnb, and was built using a Ruby on Rails backend, the React/Redux framework on the frontend, and a PostgreSQL database.
6 |
7 | 
8 |
9 | ## Features & Implementation
10 |
11 | ### Spot Search Utilizing Google Maps API
12 |
13 | BillionairBnB has search functionality that gives users the ability to search for homes based on where in the world they want to visit. When they input a place, an API request is sent to the Google Geocoder API, which responds with coordinates that the map can use to render the correct area. From there, the database filters spots based on which ones are within the bounds of the map. Since this filter also works when users move the map themselves, its dynamism enhances the user experience greatly.
14 |
15 | 
16 |
17 | ```JavaScript
18 | class Map extends React.Component {
19 | componentDidMount() {
20 | let mapOptions = {
21 | center: {lat: 40.757433, lng: -73.985807},
22 | zoom: 10
23 | };
24 | this.map = new google.maps.Map(this.mapNode, mapOptions);
25 |
26 | if (typeof this.props.bounds.northeast.lat === 'number') {
27 | let latLngBounds = new google.maps.LatLngBounds(new google.maps.LatLng({lat: this.props.bounds.southwest.lat, lng: this.props.bounds.southwest.lng }),
28 | new google.maps.LatLng({lat: this.props.bounds.northeast.lat, lng: this.props.bounds.northeast.lng }));
29 | this.map.fitBounds(latLngBounds);
30 | }
31 | this.MarkerManager = new MarkerManager(this.map, this.handleMarkerClick.bind(this));
32 | this.registerEventListeners();
33 | this.MarkerManager.updateMarkers(this.props.spots);
34 | }
35 | }
36 | ```
37 |
38 | In order to make this possible, the viewport coordinates are parsed from the Google Geocoder API response, and then used to reconfigure the map to the location that the user requested. Due to Google Maps' flexibility, BillionairBnB users can search anything from neighborhoods to countries. An initial problem I had while implementing this feature was that the Google Geocoder functions for moving the map, such as fitBounds, would set the bounds to be slightly wider than intended, which would be perceived as a difference. As a result, the map would endlessly try to fit itself to the new bounds. My solution was to focus on the address from the API response, since that always remained constant.
39 |
40 | ```JavaScript
41 | class Map extends React.Component {
42 | componentWillReceiveProps(newprops) {
43 |
44 | if (newprops.address && newprops.address !== this.props.address) {
45 | let latLngBounds = new google.maps.LatLngBounds(new google.maps.LatLng({lat: newprops.bounds.southwest.lat, lng: newprops.bounds.southwest.lng }),
46 | new google.maps.LatLng({lat: newprops.bounds.northeast.lat, lng: newprops.bounds.northeast.lng }));
47 |
48 | this.map.fitBounds(latLngBounds);
49 | }
50 |
51 | }
52 | }
53 | ```
54 |
55 | ### Bookings
56 |
57 | When a user wants to book a spot to stay in, they use the dynamic React Dates calendar, which is a library maintained by Airbnb. After inputting the start and end dates of their stay, there is a database check that makes sure that there are no conflicting bookings. If there is any conflict, an error is raised, and the user knows to modify the dates of their stay.
58 |
59 | ```Ruby
60 | class Booking < ActiveRecord::Base
61 | def self.no_overlap?(book_start_date, book_end_date, spot_id)
62 | spot_bookings = Spot.find_by(id: spot_id).bookings
63 | spot_bookings.none? do |booking|
64 | !((booking.start_date > book_end_date) || (book_start_date > booking.end_date))
65 | end
66 | end
67 | end
68 | ```
69 |
70 | ## Future Features to be Added
71 |
72 | ### User/Host Profiles
73 |
74 | Allow users to create profiles and customize settings, such as location and profile picture
75 |
76 | ### Spot Creation
77 |
78 | Allow users to become host by placing their own spot on the site that others can book
79 |
80 | ### Messaging
81 |
82 | Facilitate communication between user and host through the site
83 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/spots.scss:
--------------------------------------------------------------------------------
1 | .outer-carousel {
2 | width: 100%;
3 | height: 870px;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: space-around;
7 | align-items: center;
8 | }
9 |
10 | .featured-homes-carousel {
11 | display: flex;
12 | flex-direction: column;
13 | justify-content: center;
14 | align-items: center;
15 | }
16 |
17 | .other-homes-carousel {
18 | display: flex;
19 | flex-direction: column;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
24 | .carousel-header {
25 | height: 80px;
26 | width: 55.5%;
27 | min-width: 1065px;
28 | display: flex;
29 | flex-direction: row;
30 | justify-content: space-between;
31 | align-items: center;
32 | }
33 |
34 | .inner-carousel {
35 | display: flex;
36 | flex-direction: row;
37 | justify-content: center;
38 | align-items: center;
39 | width: 70%;
40 | }
41 |
42 | .searchlink {
43 | display: flex;
44 | flex-direction: row;
45 | justify-content: space-between;
46 | align-items: center;
47 | }
48 |
49 | .spots-link {
50 | font-size: 14px;
51 | font-weight: 500;
52 | text-decoration: none;
53 | outline: none;
54 | color: #676767;
55 | }
56 |
57 | .carousel-header-title {
58 | font-size: 26px;
59 | color: #676767;
60 | font-weight: 600;
61 | }
62 |
63 | .slick-list {
64 | width: 1080px;
65 | }
66 |
67 | .carousel figure {
68 | display: block;
69 | width: 100%;
70 | }
71 |
72 | .spot-item {
73 | padding-left: 6px;
74 | padding-right: 6px;
75 | width: 350px;
76 | height: 255px;
77 | display: flex;
78 | flex-direction: column;
79 | justify-content: space-between;
80 | }
81 |
82 | .spot-item img {
83 | width: 350px;
84 | height: 230px;
85 | margin: 0;
86 | }
87 |
88 | .slick-slide a, .all-spots a {
89 | text-decoration: none;
90 | }
91 |
92 | .spot-show-page {
93 | display: flex;
94 | flex-direction: column;
95 | align-items: center;
96 | padding-bottom: 20px;
97 | height: 90%;
98 | min-width: 1300px;
99 | }
100 |
101 | .spot-image {
102 | width: 1250px;
103 | height: 620px;
104 | }
105 |
106 | .spot-image img {
107 | width: 100%;
108 | height: 100%;
109 | }
110 |
111 | .spot-information {
112 | width: 1000px;
113 | display: flex;
114 | flex-direction: row;
115 | justify-content: space-between;
116 | }
117 |
118 | .spot-details {
119 | width: 665px;
120 | font-size: 30px;
121 | font-weight: bold;
122 | font-family: 'Roboto', sans-serif;
123 | color: #484848;
124 | display: flex;
125 | flex-direction: column;
126 | align-items: flex-start;
127 | }
128 |
129 | .spot-details p {
130 | font-size: 16px;
131 | }
132 |
133 | .spot-booking {
134 | width: 315px;
135 | height: 400px;
136 | border-bottom: 1px solid #C0C0C0;
137 | border-left: 1px solid #C0C0C0;
138 | border-right: 1px solid #C0C0C0;
139 | }
140 |
141 | .spot-owner-description {
142 | padding-top: 15px;
143 | padding-bottom: 20px;
144 | height: 120px;
145 | display: flex;
146 | flex-direction: column;
147 | justify-content: space-around;
148 | }
149 |
150 | .spot-rules {
151 | border-top: 1px solid #C0C0C0;
152 | width: 100%;
153 | height: 100px;
154 | display: flex;
155 | flex-direction: column;
156 | justify-content: space-around;
157 | align-items: flex-start;
158 | }
159 |
160 | .rules-header {
161 | margin: 0;
162 | align-self: flex-start;
163 | }
164 |
165 | .rules {
166 | font-size: 16px;
167 | font-weight: 300;
168 | }
169 |
170 | .description {
171 | font-size: 15px;
172 | font-family: 'Roboto', sans-serif;;
173 | color: #676767;
174 | font-weight: 400;
175 | }
176 |
177 | .price {
178 | font-size: 15px;
179 | padding-right: 5px;
180 | font-family: 'Roboto', sans-serif;;
181 | color: #676767;
182 | font-weight: 600;
183 | }
184 |
185 | .slick-disabled {
186 | visibility: hidden;
187 | }
188 |
189 | .slick-prev {
190 | padding-right: 7px;
191 | }
192 |
193 | .slick-next {
194 | padding-left: 7px;
195 | }
196 |
197 | .slick-arrow, .slick-prev, .slick-next {
198 | color: transparent;
199 | width: 24px;
200 | height: 24px;
201 | align-self: center;
202 | }
203 |
204 | .slick-prev:before {
205 | content: image-url('Logomakr_0gqPjJ.png');
206 | zoom: 8%;
207 | }
208 |
209 | .slick-next:before {
210 | content: image-url('Logomakr_6djnyC.png');
211 | zoom: 8%;
212 | }
213 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require_self
14 | */
15 | @import '_datepicker';
16 | @import 'CalendarDay';
17 | @import 'CalendarMonth';
18 | @import 'CalendarMonthGrid';
19 | @import 'DayPicker';
20 | @import 'DayPickerNavigation';
21 | @import 'DateInput';
22 | @import 'DateRangePicker';
23 | @import 'DateRangePickerInput';
24 | @import 'modal';
25 | @import 'css_reset';
26 | @import 'spots';
27 | @import 'search';
28 | @import 'booking';
29 | @import 'review';
30 | @import 'footer';
31 |
32 | body {
33 | font-family: 'Roboto', sans-serif;;
34 | }
35 |
36 | #map-container {
37 | width: 775px;
38 | height: 915px;
39 | }
40 |
41 | #logo {
42 | content: image-url('bnb.png');
43 | zoom: 22%;
44 | }
45 |
46 | .login-errors {
47 | list-style: none;
48 | }
49 |
50 | .signup-errors {
51 | list-style: none;
52 | }
53 |
54 | .homepage {
55 | min-width: 1200px;
56 | }
57 |
58 | .navbar {
59 | display: flex;
60 | flex-direction: row;
61 | align-items: flex-start;
62 | justify-content: center;
63 | padding-bottom: 10px;
64 | min-width: 1300px;
65 | }
66 |
67 | .navbar-left {
68 | padding-left: 26px;
69 | padding-top: 14px;
70 | }
71 |
72 | .navbar-middle-logged-out, .navbar-middle-logged-in {
73 | padding-top: 5px;
74 | align-self: center;
75 | display: flex;
76 | flex-direction: row;
77 | justify-content: flex-start;
78 | }
79 |
80 | .navbar-middle-logged-out {
81 | width: 88%;
82 | }
83 |
84 | .navbar-middle-logged-in {
85 | width: 80%;
86 | }
87 |
88 | .search-bar-shift {
89 | width: 100%;
90 | }
91 |
92 | .navbar-right-logged-out {
93 | width: 150px;
94 | padding: 18px 12px 18px 14px;
95 | display: flex;
96 | flex-direction: row;
97 | justify-content: space-around;
98 | }
99 |
100 | .navbar-right-logged-in {
101 | width: 320px;
102 | padding: 18px 12px 18px 14px;
103 | display: flex;
104 | flex-direction: row;
105 | justify-content: space-around;
106 | }
107 |
108 | .navbar-right-logged-out button, .navbar-right-logged-in button {
109 | color: #676767;
110 | width: 100%;
111 | height: 100%;
112 | text-decoration: none;
113 | background: none;
114 | border: none;
115 | padding: none;
116 | font-size: 14px;
117 | font-weight: 450;
118 | }
119 |
120 | .navbar-right-hover {
121 | height: 100%;
122 | padding: none;
123 | margin-bottom: 3px;
124 | border-bottom: 2px solid transparent;
125 | }
126 |
127 | .navbar-right button:focus {
128 | outline: none;
129 | }
130 |
131 | .navbar-right-hover:hover {
132 | border-bottom: 2px solid #676767;
133 | cursor: pointer;
134 | margin-bottom: 3px;
135 | }
136 |
137 | .greeting-1 {
138 | display: flex;
139 | flex-direction: row;
140 | justify-content: center;
141 | align-items: center;
142 | width: 100%;
143 | height: 180px;
144 | }
145 |
146 | .greeting-2 {
147 | width: 56%;
148 | min-width: 660px;
149 | height: 100%;
150 | display: flex;
151 | flex-direction: row;
152 | justify-content: flex-start;
153 | align-items: center;
154 | margin-top: 50px;
155 | }
156 |
157 | .greeting-3 {
158 | width: 60%;
159 | display: flex;
160 | flex-flow: row wrap;
161 | justify-content: flex-start;
162 | }
163 |
164 | .greeting-text-1 {
165 | color: #FF5A5F;
166 | font-size: 46px;
167 | font-weight: 600;
168 | padding-right: 5px;
169 | }
170 |
171 | .greeting-text-2 {
172 | color: #676767;
173 | font-size: 46px;
174 | font-weight: 200;
175 | }
176 |
177 | .user-greeting {
178 | font-size: 15px;
179 | color: #676767;
180 | }
181 |
182 | .footer-link {
183 | font-size: 16px;
184 | font-weight: 500;
185 | text-decoration: none;
186 | outline: none;
187 | color: #4c4747;
188 | cursor: pointer;
189 | }
190 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actionmailer (4.2.8)
5 | actionpack (= 4.2.8)
6 | actionview (= 5.2.2)
7 | activejob (= 4.2.11)
8 | mail (~> 2.5, >= 2.5.4)
9 | rails-dom-testing (~> 1.0, >= 1.0.5)
10 | actionpack (4.2.8)
11 | actionview (= 5.2.2)
12 | activesupport (= 4.2.11)
13 | rack (~> 1.6)
14 | rack-test (~> 0.6.2)
15 | rails-dom-testing (~> 1.0, >= 1.0.5)
16 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
17 | actionview (4.2.8)
18 | activesupport (= 4.2.11)
19 | builder (~> 3.1)
20 | erubis (~> 2.7.0)
21 | rails-dom-testing (~> 1.0, >= 1.0.5)
22 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
23 | activejob (4.2.11)
24 | activesupport (= 4.2.11)
25 | globalid (>= 0.3.0)
26 | activemodel (4.2.8)
27 | activesupport (= 4.2.11)
28 | builder (~> 3.1)
29 | activerecord (4.2.8)
30 | activemodel (= 4.2.8)
31 | activesupport (= 4.2.11)
32 | arel (~> 6.0)
33 | activesupport (4.2.11)
34 | i18n (~> 0.7)
35 | minitest (~> 5.1)
36 | thread_safe (~> 0.3, >= 0.3.4)
37 | tzinfo (~> 1.1)
38 | annotate (2.7.2)
39 | activerecord (>= 3.2, < 6.0)
40 | rake (>= 10.4, < 13.0)
41 | arel (6.0.4)
42 | bcrypt (3.1.11)
43 | better_errors (2.4.0)
44 | coderay (>= 1.0.0)
45 | erubi (>= 1.0.0)
46 | rack (>= 0.9.0)
47 | binding_of_caller (0.8.0)
48 | debug_inspector (>= 0.0.1)
49 | builder (3.2.3)
50 | byebug (10.0.1)
51 | coderay (1.1.2)
52 | coffee-rails (4.1.1)
53 | coffee-script (>= 2.2.0)
54 | railties (>= 4.0.0, < 5.1.x)
55 | coffee-script (2.4.1)
56 | coffee-script-source
57 | execjs
58 | coffee-script-source (1.12.2)
59 | concurrent-ruby (1.1.3)
60 | crass (1.0.4)
61 | debug_inspector (0.0.3)
62 | erubi (1.7.1)
63 | erubis (2.7.0)
64 | execjs (2.7.0)
65 | faker (1.8.7)
66 | i18n (>= 0.7)
67 | ffi (1.9.25)
68 | figaro (1.1.1)
69 | thor (~> 0.14)
70 | globalid (0.4.1)
71 | activesupport (>= 4.2.0)
72 | i18n (0.9.5)
73 | concurrent-ruby (~> 1.0)
74 | jbuilder (2.7.0)
75 | activesupport (>= 4.2.0)
76 | multi_json (>= 1.2)
77 | jquery-rails (4.3.1)
78 | rails-dom-testing (>= 1, < 3)
79 | railties (>= 4.2.0)
80 | thor (>= 0.14, < 2.0)
81 | json (1.8.6)
82 | loofah (2.2.3)
83 | crass (~> 1.0.2)
84 | nokogiri (>= 1.5.9)
85 | mail (2.7.0)
86 | mini_mime (>= 0.1.1)
87 | method_source (0.9.0)
88 | mini_mime (1.0.0)
89 | mini_portile2 (2.3.0)
90 | minitest (5.11.3)
91 | multi_json (1.13.1)
92 | nokogiri (1.8.5)
93 | mini_portile2 (~> 2.3.0)
94 | pg (0.21.0)
95 | pry (0.11.3)
96 | coderay (~> 1.1.0)
97 | method_source (~> 0.9.0)
98 | pry-rails (0.3.6)
99 | pry (>= 0.10.4)
100 | rack (1.6.11)
101 | rack-test (0.6.3)
102 | rack (>= 1.0)
103 | rails (4.2.8)
104 | actionmailer (= 4.2.8)
105 | actionpack (= 4.2.8)
106 | actionview (= 5.2.2)
107 | activejob (= 4.2.11)
108 | activemodel (= 4.2.8)
109 | activerecord (= 4.2.8)
110 | activesupport (= 4.2.11)
111 | bundler (>= 1.3.0, < 2.0)
112 | railties (= 4.2.8)
113 | sprockets-rails
114 | rails-deprecated_sanitizer (1.0.3)
115 | activesupport (>= 4.2.0.alpha)
116 | rails-dom-testing (1.0.9)
117 | activesupport (>= 4.2.0, < 5.0)
118 | nokogiri (~> 1.6)
119 | rails-deprecated_sanitizer (>= 1.0.1)
120 | rails-html-sanitizer (1.0.4)
121 | loofah (~> 2.2, >= 2.2.2)
122 | rails_12factor (0.0.3)
123 | rails_serve_static_assets
124 | rails_stdout_logging
125 | rails_serve_static_assets (0.0.5)
126 | rails_stdout_logging (0.0.5)
127 | railties (4.2.8)
128 | actionpack (= 4.2.8)
129 | activesupport (= 4.2.11)
130 | rake (>= 0.8.7)
131 | thor (>= 0.18.1, < 2.0)
132 | rake (12.3.1)
133 | rb-fsevent (0.10.3)
134 | rb-inotify (0.9.10)
135 | ffi (>= 0.5.0, < 2)
136 | rdoc (4.3.0)
137 | sass (3.5.6)
138 | sass-listen (~> 4.0.0)
139 | sass-listen (4.0.0)
140 | rb-fsevent (~> 0.9, >= 0.9.4)
141 | rb-inotify (~> 0.9, >= 0.9.7)
142 | sass-rails (5.0.7)
143 | railties (>= 4.0.0, < 6)
144 | sass (~> 3.1)
145 | sprockets (>= 2.8, < 4.0)
146 | sprockets-rails (>= 2.0, < 4.0)
147 | tilt (>= 1.1, < 3)
148 | sdoc (0.4.2)
149 | json (~> 1.7, >= 1.7.7)
150 | rdoc (~> 4.0)
151 | spring (2.0.2)
152 | activesupport (>= 4.2)
153 | sprockets (3.7.2)
154 | concurrent-ruby (~> 1.0)
155 | rack (> 1, < 3)
156 | sprockets-rails (3.2.1)
157 | actionpack (>= 4.0)
158 | activesupport (>= 4.0)
159 | sprockets (>= 3.0.0)
160 | thor (0.20.0)
161 | thread_safe (0.3.6)
162 | tilt (2.0.8)
163 | tzinfo (1.2.5)
164 | thread_safe (~> 0.1)
165 | uglifier (4.1.8)
166 | execjs (>= 0.3.0, < 3)
167 | validates_email_format_of (1.6.3)
168 | i18n
169 | web-console (2.3.0)
170 | activemodel (>= 4.0)
171 | binding_of_caller (>= 0.7.2)
172 | railties (>= 4.0)
173 | sprockets-rails (>= 2.0, < 4.0)
174 |
175 | PLATFORMS
176 | ruby
177 |
178 | DEPENDENCIES
179 | annotate
180 | bcrypt (~> 3.1.7)
181 | better_errors
182 | binding_of_caller
183 | byebug
184 | coffee-rails (~> 4.1.0)
185 | faker
186 | figaro
187 | jbuilder (~> 2.0)
188 | jquery-rails
189 | pg (~> 0.15)
190 | pry-rails
191 | rails (= 4.2.8)
192 | rails_12factor
193 | sass-rails (~> 5.0)
194 | sdoc (~> 0.4.0)
195 | spring
196 | uglifier (>= 1.3.0)
197 | validates_email_format_of
198 | web-console (~> 2.0)
199 |
200 | BUNDLED WITH
201 | 1.17.1
202 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7 | # Mayor.create(name: 'Emanuel', city: cities.first)
8 |
9 | # demo_user = User.create(email: 'moktar@jama.com', fname: 'Moktar', lname: 'Jama', password: 'password')
10 | # demo_user_2 = User.create(email: 'joktar@mama.com', fname: 'Joktar', lname: 'Mama', password: 'dassworp')
11 |
12 | new_york = Spot.create(
13 | description: 'Amazing TriBeCa penthouse',
14 | image_url: 'https://www.thepinnaclelist.com/wp-content/uploads/2014/10/01-48.0-Million-Sky-Lofts-Glasshouse-Penthouse-145-Hudson-Street-New-York-NY-920x700.jpg',
15 | price: 800,
16 | location: 'New York City, NY, USA',
17 | owner_id: 1,
18 | lat: 40.721695,
19 | lng: -74.011635,
20 | guest_limit: 5,
21 | rules: Faker::StarWars.quote
22 | )
23 |
24 | los_angeles = Spot.create(
25 | description: 'Great mansion in Bel-Air',
26 | image_url: 'https://s-media-cache-ak0.pinimg.com/originals/35/16/65/351665403bad01e7215aca7c2c6d67af.png',
27 | price: 1000,
28 | location: 'Los Angeles, CA, USA',
29 | owner_id: 1,
30 | lat: 34.099428,
31 | lng: -118.461763,
32 | guest_limit: 10,
33 | rules: "Your name can't be DJ Jazzy Jeff"
34 | )
35 |
36 | london = Spot.create(
37 | description: 'Absolutely regal palace',
38 | image_url: 'https://www.royal.uk/sites/default/files/images/feature/buckingham-palace.jpg',
39 | price: 1000,
40 | location: 'London, England, UK',
41 | owner_id: 1,
42 | lat: 51.501344,
43 | lng: 0.141890,
44 | guest_limit: 2,
45 | rules: Faker::Hipster.sentence
46 | )
47 |
48 | kanazawa = Spot.create(
49 | description: 'Historic and gorgeous castle',
50 | image_url: 'https://upload.wikimedia.org/wikipedia/commons/c/cd/Kanazawa-M-5935.jpg',
51 | price: 800,
52 | location: 'Kanazawa, Ishikawa, Japan',
53 | owner_id: 1,
54 | lat: 36.563950,
55 | lng: 136.659507,
56 | guest_limit: 40,
57 | rules: Faker::ChuckNorris.fact
58 | )
59 |
60 | dubai = Spot.create(
61 | description: 'man-made island for you and your friends to enjoy',
62 | image_url: 'https://ak3.picdn.net/shutterstock/videos/3730241/thumb/1.jpg?i10c=img.resize(height:72)',
63 | price: 800,
64 | location: 'Dubai, UAE',
65 | owner_id: 1,
66 | lat: 25.112953,
67 | lng: 55.138778,
68 | guest_limit: 30,
69 | rules: Faker::StarWars.quote
70 | )
71 |
72 | santorini = Spot.create(
73 | description: 'seashore villa with an amazing view',
74 | image_url: 'https://t-ec.bstatic.com/images/hotel/max1024x768/731/73116664.jpg',
75 | price: 700,
76 | location: 'Santorini, Greece',
77 | owner_id: 1,
78 | lat: 36.475422,
79 | lng: 25.413763,
80 | guest_limit: 7,
81 | rules: Faker::StarWars.quote
82 | )
83 |
84 | singapore = Spot.create(
85 | description: 'modern penthouse in a thriving Singaporean district',
86 | image_url: 'https://sg1-cdn.pgimgs.com/listing/18888255/UPHO.53580206.V800/New-Penthouse-Jurong-Amazing-Water-Views%21-Boon-Lay-Jurong-Tuas-Singapore.jpg',
87 | price: 800,
88 | location: 'Singapore, Singapore',
89 | owner_id: 1,
90 | lat: 1.305224,
91 | lng: 103.913765,
92 | guest_limit: 4,
93 | rules: Faker::ChuckNorris.fact
94 | )
95 |
96 | san_francisco = Spot.create(
97 | description: 'excellent getaway for tech billionaires',
98 | image_url: 'https://ap.rdcpix.com/1771840340/12a1ef9c744719a43a4890b2aee25f0fl-m0xd-w480_h480_q80.jpg',
99 | price: 1000,
100 | location: 'San Francisco, CA, USA',
101 | owner_id: 1,
102 | lat: 37.788497,
103 | lng: -122.457513,
104 | guest_limit: 11,
105 | rules: Faker::Hipster.sentence
106 | )
107 |
108 | hong_kong = Spot.create(
109 | description: 'an amazing getaway from the bustling metropolis',
110 | image_url: 'https://www.engelvoelkers.com/wp-content/uploads/2014/11/Capture.png',
111 | price: 1200,
112 | location: 'Hong Kong',
113 | owner_id: 1,
114 | lat: 22.246116,
115 | lng: 114.185732,
116 | guest_limit: 20,
117 | rules: Faker::StarWars.quote
118 | )
119 |
120 | paris = Spot.create(
121 | description: 'gorgeous apartment right on the Champs-Elysees',
122 | image_url: 'https://s-ec.bstatic.com/images/hotel/max1024x768/258/25889444.jpg',
123 | price: 800,
124 | location: 'Paris, France',
125 | owner_id: 1,
126 | lat: 48.870086,
127 | lng: 2.306588,
128 | guest_limit: 6,
129 | rules: Faker::StarWars.quote
130 | )
131 |
132 | miami = Spot.create(
133 | description: 'wonderful place right by the beach',
134 | image_url: 'https://cdn1.vox-cdn.com/uploads/chorus_asset/file/6569739/20160505_01_DSC_1862_HDR_LR.JPG',
135 | price: 1100,
136 | location: 'Miami, Florida, USA',
137 | owner_id: 1,
138 | lat: 25.785435,
139 | lng: -80.131368,
140 | guest_limit: 8,
141 | rules: Faker::ChuckNorris.fact
142 | )
143 |
144 | barcelona = Spot.create(
145 | description: 'gorgeous home located near the Sagrada Familia',
146 | image_url: 'https://69b0d6debb47579c9a280043-costadeltennis.netdna-ssl.com/wp-content/uploads/2017/04/playafels-barcelona-hotel6.jpg',
147 | price: 800,
148 | location: 'Barcelona, Spain',
149 | owner_id: 1,
150 | lat: 41.377032,
151 | lng: 2.188995,
152 | guest_limit: 10,
153 | rules: Faker::Hipster.sentence
154 | )
155 |
156 | milan = Spot.create(
157 | description: 'gorgeous house in the wealthiest Milanese district',
158 | image_url: 'https://www.milanmuseumguide.com/wp-content/uploads/Villa-Necchi-Campiglio-Milan-624x414.jpg',
159 | price: 1000,
160 | location: 'Milan, Italy',
161 | owner_id: 1,
162 | lat: 45.361689,
163 | lng: 9.162319,
164 | guest_limit: 6,
165 | rules: Faker::StarWars.quote
166 | )
167 |
168 | vienna = Spot.create(
169 | description: 'apartment centrally located in Innere Stadt, Vienna',
170 | image_url: 'https://aff.bstatic.com/images/hotel/840x460/137/13767864.jpg',
171 | price: 900,
172 | location: 'Vienna, Austria',
173 | owner_id: 1,
174 | lat: 48.210068,
175 | lng: 16.378979,
176 | guest_limit: 4,
177 | rules: Faker::ChuckNorris.fact
178 | )
179 |
180 | rio = Spot.create(
181 | description: 'wonderful beachside Leblon apartment',
182 | image_url: 'https://www.e-architect.co.uk/images/jpgs/sao_paulo/casa_6_m160310_1.jpg',
183 | price: 1300,
184 | location: 'Rio de Janeiro, Brazil',
185 | owner_id: 1,
186 | lat: -22.985907,
187 | lng: -43.224665,
188 | guest_limit: 15,
189 | rules: Faker::Hipster.sentence
190 | )
191 |
192 | cape_town = Spot.create(
193 | description: 'amazing place in the suburbs of Cape Town',
194 | image_url: 'https://businesstech.co.za/news/wp-content/uploads/2015/10/R200-million-house.png',
195 | price: 1100,
196 | location: 'Cape Town, South Africa',
197 | owner_id: 1,
198 | lat: -34.032869,
199 | lng: 18.474536,
200 | guest_limit: 10,
201 | rules: Faker::StarWars.quote
202 | )
203 |
--------------------------------------------------------------------------------