├── log └── .keep ├── tmp └── .keep ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── public ├── favicon.ico ├── apple-touch-icon.png ├── apple-touch-icon-precomposed.png ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── test ├── helpers │ └── .keep ├── mailers │ └── .keep ├── models │ └── .keep ├── controllers │ └── .keep ├── fixtures │ ├── .keep │ └── files │ │ └── .keep ├── integration │ └── .keep └── test_helper.rb ├── app ├── assets │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── channels │ │ │ └── .keep │ │ ├── cable.js │ │ └── application.js │ ├── config │ │ └── manifest.js │ └── stylesheets │ │ ├── api │ │ ├── booking_show.scss │ │ ├── modal.scss │ │ ├── booking.scss │ │ ├── header.scss │ │ ├── session.scss │ │ ├── host-show.scss │ │ ├── booking_form.scss │ │ ├── dashboard.scss │ │ ├── host-index.scss │ │ └── splash.scss │ │ └── application.css ├── models │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ ├── host.rb │ ├── booking.rb │ └── user.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── static_pages_controller.rb │ ├── api │ │ ├── sessions_controller.rb │ │ ├── hosts_controller.rb │ │ ├── users_controller.rb │ │ └── bookings_controller.rb │ └── application_controller.rb ├── views │ ├── layouts │ │ ├── mailer.text.erb │ │ ├── mailer.html.erb │ │ └── application.html.erb │ ├── api │ │ ├── hosts │ │ │ ├── show.json.jbuilder │ │ │ ├── index.json.jbuilder │ │ │ └── _host.json.jbuilder │ │ ├── users │ │ │ ├── show.json.jbuilder │ │ │ ├── index.json.jbuilder │ │ │ └── _user.json.jbuilder │ │ └── bookings │ │ │ ├── show.json.jbuilder │ │ │ ├── _booking.json.jbuilder │ │ │ └── index.json.jbuilder │ └── static_pages │ │ └── root.html.erb ├── helpers │ └── application_helper.rb ├── jobs │ └── application_job.rb ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb └── mailers │ └── application_mailer.rb ├── vendor └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep ├── .DS_Store ├── config ├── initializers │ ├── aws.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── application_controller_renderer.rb │ ├── filter_parameter_logging.rb │ ├── cookies_serializer.rb │ ├── backtrace_silencers.rb │ ├── assets.rb │ ├── wrap_parameters.rb │ ├── inflections.rb │ └── new_framework_defaults.rb ├── spring.rb ├── boot.rb ├── environment.rb ├── cable.yml ├── routes.rb ├── locales │ └── en.yml ├── application.rb ├── secrets.yml ├── environments │ ├── test.rb │ ├── development.rb │ └── production.rb ├── puma.rb └── database.yml ├── db ├── docs │ ├── wireframes │ │ ├── log_in.png │ │ ├── search_page.png │ │ ├── user_page.png │ │ ├── request_form.png │ │ └── create-account.png │ ├── api-endpoints.md │ ├── sample-state.md │ ├── README.md │ ├── component-heirarchy.md │ └── schema.md ├── migrate │ ├── 20170620181256_remove_columns_from_users.rb │ ├── 20170623160630_remove_status_from_users.rb │ ├── 20170626175301_add_index_to_owner_in_homes.rb │ ├── 20170628131943_add_default_to_status.rb │ ├── 20180411193735_add_latitude_and_longitude_to_users.rb │ ├── 20170620140631_remove_uniqueness_from_city.rb │ ├── 20170623134321_add_status_column_to_users.rb │ ├── 20180410012956_rename_columns_in_user.rb │ ├── 20180310003752_rename_columns_in_booking.rb │ ├── 20170620181444_change_null_on_columns_in_users.rb │ ├── 20170630132759_add_attachment_avatar_to_users.rb │ ├── 20170630145613_add_attachment_avatar_to_hosts.rb │ ├── 20170628133050_add_columns_to_hosts.rb │ ├── 20170728185708_change_type_of_column_in_bookings.rb │ ├── 20170630105944_change_value_to_date_in_bookings.rb │ ├── 20170626160102_create_homes.rb │ ├── 20170628111144_create_hosts.rb │ ├── 20170629042036_add_and_remove_columns_from_hosts.rb │ ├── 20180309233924_merge_host_to_user.rb │ ├── 20170625213818_create_bookings.rb │ └── 20170620132449_create_users.rb ├── schema.rb └── seeds.rb ├── bin ├── bundle ├── rake ├── rails ├── spring ├── update └── setup ├── config.ru ├── Rakefile ├── frontend ├── util │ ├── host_api_util.js │ ├── session_api_util.js │ ├── booking_api_util.js │ ├── route_util.jsx │ └── marker_manager.js ├── components │ ├── dashboard │ │ ├── dashboard_container.js │ │ └── dashboard.jsx │ ├── root.jsx │ ├── splash_screen │ │ ├── splash_screen_container.js │ │ └── splash_screen.jsx │ ├── hosts_map │ │ ├── hosts_map_container.js │ │ └── hosts_map.jsx │ ├── dropdown │ │ ├── dropdown.jsx │ │ └── dropdown_container.js │ ├── hosts │ │ ├── host_index_container.js │ │ ├── host_show_container.js │ │ ├── host_index_detail.jsx │ │ ├── host_show.jsx │ │ └── host_index.jsx │ ├── modal │ │ ├── modal_container.jsx │ │ └── modal.jsx │ ├── booking │ │ ├── bookings_show_container.js │ │ ├── bookings_container.js │ │ ├── bookings_show.jsx │ │ ├── bookings_form_container.js │ │ ├── bookings.jsx │ │ └── bookings_form.jsx │ ├── header │ │ ├── header_container.js │ │ └── header.jsx │ ├── session_form │ │ ├── session_form_container.js │ │ └── session_form.jsx │ └── App.jsx ├── actions │ ├── dropdown_actions.js │ ├── error_actions.js │ ├── modal_actions.js │ ├── host_actions.js │ ├── session_actions.js │ └── booking_actions.js ├── reducers │ ├── errors_reducer.js │ ├── hosts_reducer.js │ ├── filters_reducer.js │ ├── dropdown_reducer.js │ ├── root_reducer.js │ ├── bookings_reducer.js │ ├── session_reducer.js │ └── modal_reducer.js ├── store │ └── store.js └── futon_flying.jsx ├── README.md ├── .gitignore ├── webpack.config.js ├── package.json ├── Gemfile └── Gemfile.lock /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vo-eric/FutonFlying/HEAD/.DS_Store -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /config/initializers/aws.rb: -------------------------------------------------------------------------------- 1 | Aws::VERSION = Gem.loaded_specs["aws-sdk"].version 2 | -------------------------------------------------------------------------------- /app/views/api/hosts/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! 'api/hosts/host', host: @host 2 | -------------------------------------------------------------------------------- /db/docs/wireframes/log_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vo-eric/FutonFlying/HEAD/db/docs/wireframes/log_in.png -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /db/docs/wireframes/search_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vo-eric/FutonFlying/HEAD/db/docs/wireframes/search_page.png -------------------------------------------------------------------------------- /db/docs/wireframes/user_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vo-eric/FutonFlying/HEAD/db/docs/wireframes/user_page.png -------------------------------------------------------------------------------- /db/docs/wireframes/request_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vo-eric/FutonFlying/HEAD/db/docs/wireframes/request_form.png -------------------------------------------------------------------------------- /app/controllers/static_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class StaticPagesController < ApplicationController 2 | def root 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /db/docs/wireframes/create-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vo-eric/FutonFlying/HEAD/db/docs/wireframes/create-account.png -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/views/api/users/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! "api/users/user", user: @user 2 | 3 | # json.partial! "api/bookings/booking", booking: @booking -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /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/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /app/views/api/bookings/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! 'api/bookings/booking', :locals => {booking: @booking, foo: @current_user.id == @booking.guest_id} 2 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /app/views/api/hosts/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | @hosts.each do |host| 2 | json.set! host.id do 3 | json.partial! 'api/hosts/host', host: host 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/api/users/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | @users.each do |user| 2 | json.set! user.id do 3 | json.partial! 'api/users/user', user: user 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/booking_show.scss: -------------------------------------------------------------------------------- 1 | .booking-main { 2 | background-color: #fff; 3 | margin-top: 5%; 4 | 5 | .host-name { 6 | font-size: 2rem; 7 | } 8 | } -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | -------------------------------------------------------------------------------- /db/migrate/20170620181256_remove_columns_from_users.rb: -------------------------------------------------------------------------------- 1 | class RemoveColumnsFromUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :users, :city_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170623160630_remove_status_from_users.rb: -------------------------------------------------------------------------------- 1 | class RemoveStatusFromUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :users, :status 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170626175301_add_index_to_owner_in_homes.rb: -------------------------------------------------------------------------------- 1 | class AddIndexToOwnerInHomes < ActiveRecord::Migration[5.0] 2 | def change 3 | add_index :homes, :owner_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/api/hosts/_host.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! host, :id, :description, :lat, :lng, :accepting_guests, :fname, :lname, :city, :country 2 | 3 | json.avatar_url asset_path(host.avatar.url) 4 | -------------------------------------------------------------------------------- /app/views/api/users/_user.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! user, :id, :bio, :latitude, :longitude, :accepting_guests, :fname, :lname, :city, :country 2 | 3 | json.avatar_url asset_path(user.avatar.url) -------------------------------------------------------------------------------- /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: '_MyCouchSurfing_session' 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20170628131943_add_default_to_status.rb: -------------------------------------------------------------------------------- 1 | class AddDefaultToStatus < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :hosts, :accepting_gusts, :boolean, :default => true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180411193735_add_latitude_and_longitude_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddLatitudeAndLongitudeToUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :address, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170620140631_remove_uniqueness_from_city.rb: -------------------------------------------------------------------------------- 1 | class RemoveUniquenessFromCity < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_index :users, :city_id 4 | add_index :users, :city_id 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /db/migrate/20170623134321_add_status_column_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddStatusColumnToUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :status, :string, default: "Accepting Guests", null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180410012956_rename_columns_in_user.rb: -------------------------------------------------------------------------------- 1 | class RenameColumnsInUser < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column :users, :lat, :latitude 4 | rename_column :users, :lng, :longitude 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /db/migrate/20180310003752_rename_columns_in_booking.rb: -------------------------------------------------------------------------------- 1 | class RenameColumnsInBooking < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column :bookings, :endDate, :end_date 4 | rename_column :bookings, :startDate, :start_date 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20170620181444_change_null_on_columns_in_users.rb: -------------------------------------------------------------------------------- 1 | class ChangeNullOnColumnsInUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | change_column_null :users, :fname, :null => false 4 | change_column_null :users, :lname, :null => false 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/views/static_pages/root.html.erb: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /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', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /db/migrate/20170630132759_add_attachment_avatar_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAttachmentAvatarToUsers < ActiveRecord::Migration 2 | def self.up 3 | change_table :users do |t| 4 | t.attachment :avatar 5 | end 6 | end 7 | 8 | def self.down 9 | remove_attachment :users, :avatar 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20170630145613_add_attachment_avatar_to_hosts.rb: -------------------------------------------------------------------------------- 1 | class AddAttachmentAvatarToHosts < ActiveRecord::Migration 2 | def self.up 3 | change_table :hosts do |t| 4 | t.attachment :avatar 5 | end 6 | end 7 | 8 | def self.down 9 | remove_attachment :hosts, :avatar 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /frontend/util/host_api_util.js: -------------------------------------------------------------------------------- 1 | export const fetchHosts = (data) => { 2 | return $.ajax({ 3 | method: 'GET', 4 | url: 'api/users', 5 | data 6 | }); 7 | }; 8 | 9 | export const fetchSingleHost = (id) => { 10 | return $.ajax({ 11 | method: 'GET', 12 | url: `api/users/${id}`, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /db/migrate/20170628133050_add_columns_to_hosts.rb: -------------------------------------------------------------------------------- 1 | class AddColumnsToHosts < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :hosts, :fname, :string, null: false 4 | add_column :hosts, :lname, :string, null: false 5 | add_column :hosts, :city, :string 6 | add_column :hosts, :location, :string 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20170728185708_change_type_of_column_in_bookings.rb: -------------------------------------------------------------------------------- 1 | class ChangeTypeOfColumnInBookings < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :bookings, :startDate 4 | remove_column :bookings, :endDate 5 | add_column :bookings, :startDate, :date 6 | add_column :bookings, :endDate, :date 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /frontend/components/dashboard/dashboard_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Dashboard from './dashboard'; 3 | 4 | const mapStateToProps = (state) => { 5 | return { 6 | user: state.session.currentUser 7 | }; 8 | }; 9 | 10 | export default connect ( 11 | mapStateToProps, 12 | null 13 | )(Dashboard); 14 | -------------------------------------------------------------------------------- /frontend/actions/dropdown_actions.js: -------------------------------------------------------------------------------- 1 | export const OPEN_DROPDOWN = 'OPEN_DROPDOWN'; 2 | export const CLOSE_DROPDOWN = 'CLOSE_DROPDOWN'; 3 | 4 | export const openDropdown = () => { 5 | return { 6 | type: OPEN_DROPDOWN 7 | }; 8 | }; 9 | 10 | export const closeDropdown = () => { 11 | return { 12 | type: CLOSE_DROPDOWN 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /db/migrate/20170630105944_change_value_to_date_in_bookings.rb: -------------------------------------------------------------------------------- 1 | class ChangeValueToDateInBookings < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :bookings, :startDate 4 | remove_column :bookings, :endDate 5 | add_column :bookings, :startDate, :date, null: false 6 | add_column :bookings, :endDate, :date, null: false 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /frontend/actions/error_actions.js: -------------------------------------------------------------------------------- 1 | export const RECEIVE_ERRORS = 'RECEIVE_ERRORS'; 2 | export const CLEAR_ERRORS = 'CLEAR_ERRORS'; 3 | 4 | export const receiveErrors = errors => { 5 | return { 6 | type: RECEIVE_ERRORS, 7 | errors 8 | }; 9 | }; 10 | 11 | export const clearErrors = () => { 12 | return { 13 | type: CLEAR_ERRORS 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/20170626160102_create_homes.rb: -------------------------------------------------------------------------------- 1 | class CreateHomes < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :homes do |t| 4 | t.float :lng, null: false 5 | t.float :lat, null: false 6 | t.integer :owner_id, null: false 7 | t.boolean :accepting_guests, null: false 8 | t.string :description 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /frontend/components/root.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { HashRouter } from 'react-router-dom'; 4 | import App from './App'; 5 | 6 | const Root = ({ store }) => ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | 14 | export default Root; 15 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root "static_pages#root" 3 | 4 | namespace :api, defaults: {format: :json} do 5 | resources :users, except: [:new, :destroy] do 6 | resources :bookings, only: [:index, :show] 7 | end 8 | resources :bookings, only: [:index, :create, :show, :edit] 9 | resource :session, only: [:create, :destroy, :show] 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20170628111144_create_hosts.rb: -------------------------------------------------------------------------------- 1 | class CreateHosts < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :hosts do |t| 4 | t.float :lng, null: false 5 | t.float :lat, null: false 6 | t.integer :owner_id, null: false 7 | t.boolean :accepting_guests, null: false 8 | t.string :description 9 | end 10 | add_index :hosts, :owner_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20170629042036_add_and_remove_columns_from_hosts.rb: -------------------------------------------------------------------------------- 1 | class AddAndRemoveColumnsFromHosts < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :hosts, :owner_id 4 | remove_column :hosts, :accepting_gusts 5 | remove_column :hosts, :accepting_guests 6 | add_column :hosts, :accepting_guests, :boolean, :default => true 7 | rename_column :hosts, :location, :country 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20180309233924_merge_host_to_user.rb: -------------------------------------------------------------------------------- 1 | class MergeHostToUser < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :lat, :float, null: false 4 | add_column :users, :lng, :float, null: false 5 | add_column :users, :city, :string 6 | add_column :users, :country, :string 7 | add_column :users, :accepting_guests, :boolean, default: true 8 | 9 | drop_table :hosts 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /frontend/reducers/errors_reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_ERRORS, CLEAR_ERRORS } from '../actions/error_actions'; 2 | 3 | const ErrorsReducer = (state = [], action) => { 4 | Object.freeze(state); 5 | switch (action.type) { 6 | case RECEIVE_ERRORS: 7 | return action.errors; 8 | case CLEAR_ERRORS: 9 | return []; 10 | default: 11 | return state; 12 | } 13 | }; 14 | 15 | export default ErrorsReducer; 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the rails generate channel command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /frontend/components/splash_screen/splash_screen_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import SplashScreen from './splash_screen'; 3 | 4 | const mapStateToProps = state => { 5 | return { 6 | currentUser: state.session.currentUser 7 | } 8 | }; 9 | 10 | const mapDispatchToProps = (dispatch, ownProps) => ({ 11 | }); 12 | 13 | export default connect( 14 | mapStateToProps, 15 | mapDispatchToProps 16 | )(SplashScreen); 17 | -------------------------------------------------------------------------------- /app/views/api/bookings/_booking.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! booking, :start_date, :end_date, :num_guests 2 | 3 | if foo 4 | json.hosting foo 5 | json.fname booking.host.fname 6 | json.lname booking.host.lname 7 | json.avatar booking.host.avatar.url 8 | json.id booking.id 9 | else 10 | json.hosting foo 11 | json.fname booking.guest.fname 12 | json.lname booking.guest.lname 13 | json.avatar booking.guest.avatar.url 14 | json.id booking.id 15 | end -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/actions/modal_actions.js: -------------------------------------------------------------------------------- 1 | export const OPEN_MODAL = "OPEN_MODAL"; 2 | export const CLOSE_MODAL = "CLOSE_MODAL"; 3 | export const RECEIVE_COMPONENT = "RECEIVE_COMPONENT"; 4 | 5 | export const openModal = (component) => ({ 6 | type: OPEN_MODAL, 7 | component 8 | }); 9 | 10 | export const closeModal = (component) => ({ 11 | type: CLOSE_MODAL, 12 | component: null 13 | }); 14 | 15 | export const receiveComponent = (component) => ({ 16 | type: RECEIVE_COMPONENT, 17 | component 18 | }); 19 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/modal.scss: -------------------------------------------------------------------------------- 1 | .modal { 2 | height: 50%; 3 | width: 20%; 4 | background: #fff; 5 | z-index: 40; 6 | min-width: 400px; 7 | margin: 150px auto; 8 | border-radius: 7px; 9 | } 10 | 11 | .modal-background { 12 | -webkit-transition: .3s; 13 | -o-transition: .3s; 14 | transition: .3s; 15 | z-index: 10; 16 | background: rgba(0, 0, 0, 0.94); 17 | width: 100%; 18 | height: 100%; 19 | position: fixed; 20 | left: 0; 21 | top: 0; 22 | } 23 | -------------------------------------------------------------------------------- /db/migrate/20170625213818_create_bookings.rb: -------------------------------------------------------------------------------- 1 | class CreateBookings < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :bookings do |t| 4 | t.integer :startDate, null: false 5 | t.integer :endDate, null: false 6 | t.integer :host_id, null: false 7 | t.integer :guest_id, null: false 8 | t.integer :num_guests, default: 1, null: false 9 | 10 | t.timestamps 11 | end 12 | add_index :bookings, :host_id 13 | add_index :bookings, :guest_id 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /db/migrate/20170620132449_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :users do |t| 4 | t.string :fname, null: false 5 | t.string :lname, null: false 6 | t.string :username, null: false 7 | t.string :password_digest, null: false 8 | t.string :session_token, null: false 9 | t.text :bio 10 | t.string :city_id, null: false 11 | 12 | t.timestamps 13 | end 14 | add_index :users, :username, unique: true 15 | add_index :users, :city_id, unique: true 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /frontend/components/hosts_map/hosts_map_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import HostMap from './hosts_map'; 3 | 4 | import { fetchHosts } from '../../actions/host_actions'; 5 | 6 | const mapStateToProps = ({ hosts }) => { 7 | return { 8 | hosts: Object.values(hosts) 9 | }; 10 | }; 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | fetchHosts: (location) => dispatch(fetchHosts(location)) 15 | }; 16 | }; 17 | 18 | export default connect( 19 | mapStateToProps, 20 | mapDispatchToProps 21 | )(HostMap); 22 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /frontend/reducers/hosts_reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_HOSTS, RECEIVE_SINGLE_HOST } from '../actions/host_actions'; 2 | 3 | const initialState = {}; 4 | 5 | const HostsReducer = (state = initialState, action) => { 6 | Object.freeze(state); 7 | let newState; 8 | switch (action.type) { 9 | case RECEIVE_HOSTS: 10 | return action.hosts; 11 | case RECEIVE_SINGLE_HOST: 12 | newState = Object.assign({}, {[action.host.id]: action.host}); 13 | return newState; 14 | default: 15 | return state; 16 | } 17 | }; 18 | 19 | export default HostsReducer; 20 | -------------------------------------------------------------------------------- /frontend/reducers/filters_reducer.js: -------------------------------------------------------------------------------- 1 | // import merge from 'lodash/merge' 2 | // import { UPDATE_FILTER } from '../actions/filter_actions'; 3 | // 4 | // const defaultFilters = Object.freeze({ 5 | // bounds: {}, 6 | // }); 7 | // 8 | // const FiltersReducer = (state = defaultFilters, action) => { 9 | // Object.freeze(state) 10 | // if (action.type === UPDATE_FILTER) { 11 | // const newFilter = { 12 | // [action.filter]: action.value 13 | // }; 14 | // return merge({}, state, newFilter); 15 | // } else { 16 | // return state; 17 | // } 18 | // }; 19 | // 20 | // export default FiltersReducer; 21 | -------------------------------------------------------------------------------- /frontend/store/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import rootReducer from '../reducers/root_reducer'; 3 | import thunk from 'redux-thunk'; 4 | 5 | const middlewares = [thunk]; 6 | 7 | if (process.env.NODE_ENV !== 'production') { 8 | const { createLogger } = require('redux-logger'); 9 | middlewares.push(createLogger()); 10 | } 11 | 12 | const configureStore = (preloadedState = {}) => { 13 | return ( 14 | createStore( 15 | rootReducer, 16 | preloadedState, 17 | applyMiddleware(...middlewares) 18 | ) 19 | ); 20 | }; 21 | 22 | export default configureStore; 23 | -------------------------------------------------------------------------------- /frontend/components/dropdown/dropdown.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import BookingsFormContainer from '../booking/bookings_form_container'; 4 | 5 | class Dropdown extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render () { 11 | if (this.props.dropdownIsOpen) { 12 | return ( 13 |
14 | 15 |
16 | ); 17 | } else { 18 | return
; 19 | } 20 | } 21 | } 22 | 23 | export default Dropdown; 24 | -------------------------------------------------------------------------------- /frontend/components/hosts/host_index_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import HostIndex from './host_index'; 3 | import { fetchHosts, fetchSingleHost } from '../../actions/host_actions'; 4 | 5 | const mapStateToProps = (state) => { 6 | return { 7 | hosts: Object.keys(state.hosts).map(id => state.hosts[id]) 8 | }; 9 | }; 10 | 11 | const mapDispatchToProps = (dispatch, ownProps) => { 12 | return { 13 | fetchHosts: (hosts) => dispatch(fetchHosts(hosts)), 14 | fetchSingleHost: (id) => dispatch(fetchSingleHost(id)) 15 | }; 16 | }; 17 | 18 | export default connect( 19 | mapStateToProps, 20 | mapDispatchToProps 21 | )(HostIndex); 22 | -------------------------------------------------------------------------------- /frontend/components/modal/modal_container.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Modal from './modal'; 3 | import { closeModal, receiveComponent } from '../../actions/modal_actions'; 4 | 5 | const mapStateToProps = (state) => { 6 | return { 7 | modalIsOpen: state.modal.modalIsOpen, 8 | component: state.modal.component 9 | } 10 | }; 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | receiveComponent: component => dispatch(receiveComponent(component)), 15 | closeModal: () => dispatch(closeModal()) 16 | }; 17 | }; 18 | 19 | export default connect( 20 | mapStateToProps, 21 | mapDispatchToProps 22 | )(Modal); 23 | -------------------------------------------------------------------------------- /app/controllers/api/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::SessionsController < ApplicationController 2 | def create 3 | @user = User.find_by_credentials( 4 | params[:user][:username], 5 | params[:user][:password] 6 | ) 7 | 8 | if @user 9 | login(@user) 10 | render "api/users/show" 11 | else 12 | render json: ["Invalid username and/or password"], status: 401 13 | end 14 | end 15 | 16 | def destroy 17 | @user = current_user 18 | if @user 19 | logout 20 | render "api/users/show" 21 | else 22 | render json: ["You can't destroy what's not there"], status: 404 23 | end 24 | end 25 | 26 | end 27 | -------------------------------------------------------------------------------- /frontend/util/booking_api_util.js: -------------------------------------------------------------------------------- 1 | export const fetchBookings = (id) => { 2 | return $.ajax({ 3 | method: 'GET', 4 | url: 'api/users/' + id + '/bookings' 5 | }); 6 | }; 7 | 8 | export const fetchSingleBooking = (id) => { 9 | return $.ajax({ 10 | method: 'GET', 11 | url: `api/bookings/${id}`, 12 | }); 13 | }; 14 | 15 | export const createBooking = (booking) => { 16 | return $.ajax({ 17 | method: 'POST', 18 | url: `api/bookings`, 19 | data: { booking } 20 | }); 21 | }; 22 | 23 | export const deleteBooking = (bookingId) => { 24 | return $.ajax({ 25 | method: 'DELETE', 26 | url: `api/bookings/${bookingId}` 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### FutonFlying 2 | 3 | [FutonFlying] [link] 4 | 5 | [link]: https://futon-flying.herokuapp.com 6 | 7 | FutonFlying is a web application inspired by CouchSurfing built using Ruby on Rails as well as React/Redux. The basic premise is that users(futonfliers) can stay with other users or host fellow travelers at no cost. 8 | 9 | 10 | ### Still To Come 11 | 12 | ## Basic Features 13 | 14 | Ability to stay with hosts, search through the Google Maps API, and leave reviews 15 | 16 | ## Bonus Features 17 | 18 | FutonFlying will, with time permitted, also include: 19 | * Messaging 20 | * Local Events 21 | * Friends 22 | * A forum for locals to answer questions from travelers 23 | -------------------------------------------------------------------------------- /app/views/api/bookings/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.set! :hostings do 2 | if @bookings[:hostings] 3 | @bookings[:hostings].each do |booking| 4 | json.set! booking.id do 5 | json.partial! 'api/bookings/booking', :locals => {booking: booking, foo: @current_user.id == booking.guest_id} 6 | end 7 | end 8 | else 9 | nil 10 | end 11 | end 12 | 13 | json.set! :guestings do 14 | if @bookings[:guestings] 15 | @bookings[:guestings].each do |booking| 16 | json.set! booking.id do 17 | json.partial! 'api/bookings/booking', :locals => {booking: booking, foo: @current_user.id == booking.guest_id} 18 | end 19 | end 20 | else 21 | nil 22 | end 23 | end -------------------------------------------------------------------------------- /frontend/components/booking/bookings_show_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { withRouter } from 'react-router-dom'; 3 | import BookingsShow from './bookings_show'; 4 | import { fetchSingleBooking } from '../../actions/booking_actions'; 5 | 6 | const mapStateToProps = (state, ownProps) => { 7 | return { 8 | bookingDetails: state.bookings[ownProps.match.params.id] 9 | }; 10 | }; 11 | 12 | const mapDispatchToProps = (dispatch, ownProps) => { 13 | return { 14 | fetchSingleBooking: (id) => dispatch(fetchSingleBooking(id)), 15 | }; 16 | }; 17 | 18 | export default withRouter( 19 | connect( 20 | mapStateToProps, 21 | mapDispatchToProps 22 | )(BookingsShow)); 23 | -------------------------------------------------------------------------------- /frontend/reducers/dropdown_reducer.js: -------------------------------------------------------------------------------- 1 | import { OPEN_DROPDOWN, CLOSE_DROPDOWN} from '../actions/dropdown_actions'; 2 | 3 | const initialState = { 4 | dropdownIsOpen: false 5 | } 6 | 7 | const DropdownReducer = (state = initialState, action) => { 8 | Object.freeze(state); 9 | switch(action.type) { 10 | case OPEN_DROPDOWN: 11 | return Object.assign( 12 | {}, 13 | state, 14 | { dropdownIsOpen: true } 15 | ); 16 | case CLOSE_DROPDOWN: 17 | return Object.assign( 18 | {}, 19 | state, 20 | { dropdownIsOpen: false } 21 | ); 22 | default: 23 | return state; 24 | } 25 | }; 26 | 27 | export default DropdownReducer; 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore Byebug command history file. 17 | .byebug_history 18 | 19 | node_modules/ 20 | bundle.js 21 | bundle.js.map 22 | .byebug_history 23 | .DS_Store 24 | npm-debug.log 25 | 26 | # Ignore application configuration 27 | /config/application.yml 28 | -------------------------------------------------------------------------------- /frontend/components/dropdown/dropdown_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Dropdown from './dropdown'; 3 | import { fetchSingleHost } from '../../actions/host_actions'; 4 | import { openDropdown, closeDropdown } from '../../actions/dropdown_actions'; 5 | 6 | const mapStateToProps = (state) => { 7 | return { 8 | dropdownIsOpen: state.dropdown.dropdownIsOpen, 9 | state 10 | } 11 | }; 12 | 13 | const mapDispatchToProps = (dispatch, ownProps) => { 14 | return { 15 | fetchSingleHost: (id) => dispatch(fetchSingleHost(id)), 16 | closeDropdown: () => dispatch(closeDropdown()) 17 | } 18 | }; 19 | 20 | export default connect( 21 | mapStateToProps, 22 | mapDispatchToProps 23 | )(Dropdown); 24 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /frontend/reducers/root_reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import SessionReducer from './session_reducer'; 3 | import ModalReducer from './modal_reducer'; 4 | import HostsReducer from './hosts_reducer'; 5 | // import FiltersReducer from './filters_reducer'; 6 | import BookingsReducer from './bookings_reducer'; 7 | import DropdownReducer from './dropdown_reducer'; 8 | import ErrorsReducer from './errors_reducer'; 9 | 10 | const rootReducer = combineReducers({ 11 | session: SessionReducer, 12 | modal: ModalReducer, 13 | hosts: HostsReducer, 14 | bookings: BookingsReducer, 15 | dropdown: DropdownReducer, 16 | errors: ErrorsReducer 17 | // filters: FiltersReducer 18 | 19 | }); 20 | 21 | export default rootReducer; 22 | -------------------------------------------------------------------------------- /frontend/components/booking/bookings_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Bookings from './bookings'; 3 | import { fetchBookings, fetchSingleBooking } from '../../actions/booking_actions'; 4 | 5 | const mapStateToProps = (state, ownProps) => { 6 | return { 7 | bookings: Object.keys(state.bookings).map(id => state.bookings[id]), 8 | currentUser: state.session.currentUser, 9 | formType: ownProps.formType 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = (dispatch, ownProps) => { 14 | return { 15 | fetchBookings: id => dispatch(fetchBookings(id)), 16 | fetchSingleBooking: id => dispatch(fetchBookings(id)) 17 | }; 18 | }; 19 | 20 | export default connect( 21 | mapStateToProps, 22 | mapDispatchToProps 23 | )(Bookings); 24 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /frontend/reducers/bookings_reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | RECEIVE_BOOKINGS, 3 | RECEIVE_CURRENT_BOOKING, 4 | RECEIVE_ERRORS, 5 | REMOVE_BOOKING, 6 | CLEAR_ERRORS 7 | } from '../actions/booking_actions'; 8 | 9 | const BookingReducer = (state = {}, action) => { 10 | Object.freeze(state); 11 | switch(action.type) { 12 | case RECEIVE_BOOKINGS: 13 | return action.bookings; 14 | case RECEIVE_CURRENT_BOOKING: 15 | let currentBooking = {[action.booking.id]: action.booking}; 16 | return Object.assign({}, state, currentBooking); 17 | case REMOVE_BOOKING: 18 | let newState = Object.assign({}, state); 19 | delete newState[action.bookingId]; 20 | return newState; 21 | default: 22 | return state; 23 | } 24 | }; 25 | 26 | export default BookingReducer; 27 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | helper_method :current_user, :logged_in? 4 | 5 | private 6 | 7 | def current_user 8 | @current_user ||= User.find_by_session_token(session[:session_token]) 9 | end 10 | 11 | def login(user) 12 | session[:session_token] = user.reset_session_token 13 | @current_user = user 14 | end 15 | 16 | def logged_in? 17 | !!current_user 18 | end 19 | 20 | def logout 21 | current_user.reset_session_token 22 | session[:session_token] = nil 23 | @current_user = nil 24 | end 25 | 26 | def require_logged_in 27 | render json: { 28 | base: ['Invalid username and/or password']}, 29 | status: 401 if !current_user 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /frontend/components/hosts/host_show_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import HostShow from './host_show'; 3 | import { fetchSingleHost } from '../../actions/host_actions'; 4 | import { clearErrors } from '../../actions/error_actions'; 5 | import { openDropdown } from '../../actions/dropdown_actions'; 6 | 7 | const mapStateToProps = (state, ownProps) => { 8 | return { 9 | hostDetails: state.hosts[ownProps.match.params.id] 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = (dispatch, ownProps) => { 14 | return { 15 | fetchSingleHost: (id) => dispatch(fetchSingleHost(id)), 16 | clearErrors: () => dispatch(clearErrors()), 17 | openDropdown: () => dispatch(openDropdown()) 18 | }; 19 | }; 20 | 21 | export default connect( 22 | mapStateToProps, 23 | mapDispatchToProps 24 | )(HostShow); 25 | -------------------------------------------------------------------------------- /frontend/actions/host_actions.js: -------------------------------------------------------------------------------- 1 | import * as APIUtil from '../util/host_api_util'; 2 | import { receiveErrors } from './error_actions'; 3 | 4 | export const RECEIVE_HOSTS = 'RECEIVE_HOSTS'; 5 | export const RECEIVE_SINGLE_HOST = 'RECEIVE_SINGLE_HOST'; 6 | 7 | 8 | export const receiveHosts = hosts => { 9 | return { 10 | type: RECEIVE_HOSTS, 11 | hosts 12 | }; 13 | }; 14 | 15 | export const receiveSingleHost = host => { 16 | return { 17 | type: RECEIVE_SINGLE_HOST, 18 | host 19 | }; 20 | }; 21 | 22 | export const fetchHosts = location => dispatch => { 23 | return APIUtil.fetchHosts(location) 24 | .then((hosts) => dispatch(receiveHosts(hosts))); 25 | }; 26 | 27 | export const fetchSingleHost = id => dispatch => { 28 | return APIUtil.fetchSingleHost(id) 29 | .then((host) => dispatch(receiveSingleHost(host))); 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/components/header/header_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { logout, login } from '../../actions/session_actions'; 3 | import { openModal } from '../../actions/modal_actions'; 4 | import Header from './header'; 5 | import { clearErrors } from '../../actions/error_actions'; 6 | 7 | const mapStateToProps = (state) => { 8 | return { 9 | currentUser: state.session.currentUser, 10 | loggedIn: Boolean(state.session.currentUser) 11 | }; 12 | }; 13 | 14 | const mapDispatchToProps = (dispatch) => { 15 | return { 16 | loginGuest: (user) => dispatch(login(user)), 17 | logout: () => dispatch(logout()), 18 | clearErrors: () => dispatch(clearErrors()), 19 | openModal: (component) => dispatch(openModal(component)) 20 | }; 21 | }; 22 | 23 | export default connect( 24 | mapStateToProps, 25 | mapDispatchToProps 26 | )(Header); 27 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /frontend/components/modal/modal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import SessionFormContainer from '../session_form/session_form_container'; 4 | import ModalContainer from './modal_container'; 5 | 6 | class Modal extends React.Component { 7 | constructor (props) { 8 | super(props); 9 | } 10 | 11 | render () { 12 | if (this.props.modalIsOpen) { 13 | return ( 14 |
18 |
22 | 23 | 24 |
25 |
26 | ); 27 | } else { 28 | return ( 29 |
30 | ); 31 | } 32 | } 33 | } 34 | 35 | export default Modal; 36 | -------------------------------------------------------------------------------- /frontend/components/booking/bookings_show.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class BookingsShow extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | componentDidMount() { 9 | this.props.fetchSingleBooking(this.props.match.params.id); 10 | } 11 | 12 | render() { 13 | let booking = this.props.bookingDetails; 14 | if (!booking) { 15 | return null; 16 | } 17 | return ( 18 |
19 |
20 | {booking.fname} {booking.lname} 21 |
22 |
23 | {booking.start_date} TO 24 | {booking.end_date} 25 |
26 |
27 | HERES SOME FILLER TEST TEXT 28 |
29 |
30 | ); 31 | } 32 | 33 | } 34 | 35 | export default BookingsShow; 36 | -------------------------------------------------------------------------------- /frontend/reducers/session_reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_CURRENT_USER } from '../actions/session_actions'; 2 | import { RECEIVE_ERRORS, CLEAR_ERRORS } from '../actions/error_actions'; 3 | 4 | const nullUser = { 5 | currentUser: null, 6 | errors: [] 7 | }; 8 | 9 | const SessionReducer = (state = nullUser, action) => { 10 | Object.freeze(state); 11 | let errors; 12 | switch(action.type) { 13 | case RECEIVE_CURRENT_USER: 14 | const currentUser = action.currentUser; 15 | return Object.assign({}, nullUser, { currentUser }); 16 | case RECEIVE_ERRORS: 17 | const errors = action.errors; 18 | return Object.assign({}, state, { errors }); 19 | case CLEAR_ERRORS: 20 | const newState = Object.assign({}, state); 21 | newState.errors = []; 22 | return Object.assign({}, newState); 23 | default: 24 | return state; 25 | } 26 | }; 27 | 28 | export default SessionReducer; 29 | -------------------------------------------------------------------------------- /frontend/reducers/modal_reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | OPEN_MODAL, 3 | CLOSE_MODAL, 4 | RECEIVE_COMPONENT 5 | } from '../actions/modal_actions'; 6 | 7 | const initialState = { 8 | modalIsOpen: false, 9 | component: '' 10 | } 11 | 12 | const ModalReducer = (state = initialState, action) => { 13 | Object.freeze(state); 14 | switch(action.type) { 15 | case OPEN_MODAL: 16 | return Object.assign( 17 | {}, 18 | state, 19 | { component: action.component, modalIsOpen: true } 20 | ); 21 | case CLOSE_MODAL: 22 | return Object.assign( 23 | {}, 24 | state, 25 | { component: null, modalIsOpen: false} 26 | ); 27 | case RECEIVE_COMPONENT: 28 | return Object.assign( 29 | {}, 30 | state, 31 | { component: action.component } 32 | ); 33 | default: 34 | return state; 35 | } 36 | }; 37 | 38 | export default ModalReducer; 39 | -------------------------------------------------------------------------------- /db/docs/api-endpoints.md: -------------------------------------------------------------------------------- 1 | HTML API 2 | ---------------------------------------------------------------- 3 | 4 | ### Root 5 | * GET / 6 | 7 | JSON API 8 | ---------------------------------------------------------------------- 9 | 10 | ### Users 11 | * POST /api/users 12 | * PATCH /api/users 13 | 14 | ### Session 15 | * POST /api/session 16 | * DELETE /api/session 17 | 18 | ### Hostings 19 | * GET /api/hostings 20 | * POST /api/hostings 21 | * GET /api/hostings/:id 22 | * DELETE /api/hostings/:id 23 | * PATCH /api/hostings/:id 24 | 25 | ### Bookings 26 | * GET /api/bookings 27 | * POST /api/bookings 28 | * GET /api/bookings/:id 29 | * DELETE /api/bookings/:id 30 | 31 | ### Reviews 32 | * GET /api/hosts/:host_id/reviews 33 | * POST /api/hosts/:host_id/reviews 34 | * DELETE /api/hosts/:host_id/reviews/:review_id 35 | 36 | ### Profile 37 | * POST /api/profile 38 | * GET /api/profile/:id 39 | * PATCH /api/profile/:id 40 | * DELETE /api/profile/:id 41 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module MyCouchSurfing 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | config.paperclip_defaults = { 15 | :storage => :s3, 16 | :s3_credentials => { 17 | :bucket => ENV["s3_bucket"], 18 | :access_key_id => ENV["s3_access_key_id"], 19 | :secret_access_key => ENV["s3_secret_access_key"], 20 | s3_region: 'us-east-2', 21 | :s3_host_name => "s3-#{ENV["s3_region"]}.amazonaws.com", 22 | :url => ":s3_host_name" 23 | } 24 | } 25 | 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 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 other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | 17 | #map-container { 18 | width: 450px; 19 | height: 100%; 20 | transition: width .3s; 21 | } 22 | 23 | body { 24 | font-family: 'Lato', sans-serif; 25 | } 26 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FutonFlying 5 | 6 | 7 | 8 | > 9 | 10 | <%= javascript_include_tag "https://maps.googleapis.com/maps/api/js?key=AIzaSyDl3peYMfdxsHwzH-TMrgZFmwGNRIULxSw" %> 11 | <%= csrf_meta_tags %> 12 | <%= stylesheet_link_tag 'application', media: 'all' %> 13 | <%= javascript_include_tag 'application' %> 14 | 15 | 16 | 17 | <%= yield %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/components/booking/bookings_form_container.js: -------------------------------------------------------------------------------- 1 | import { 2 | connect 3 | } from 'react-redux'; 4 | import { 5 | withRouter 6 | } from 'react-router-dom'; 7 | import BookingsForm from './bookings_form'; 8 | import { 9 | closeDropdown 10 | } from '../../actions/dropdown_actions'; 11 | import { 12 | fetchBookings, 13 | fetchSingleBooking, 14 | createBooking, 15 | deleteBooking 16 | } 17 | from '../../actions/booking_actions'; 18 | 19 | const mapStateToProps = (state, ownProps) => { 20 | return { 21 | user: state.session.currentUser, 22 | host: state.hosts[ownProps.match.params.id], 23 | errors: state.errors 24 | 25 | }; 26 | }; 27 | 28 | const mapDispatchToProps = (dispatch, ownProps) => { 29 | return { 30 | fetchBookings: id => dispatch(fetchBookings(id)), 31 | createBooking: booking => dispatch(createBooking(booking)), 32 | closeDropdown: () => dispatch(closeDropdown()) 33 | }; 34 | }; 35 | 36 | export default withRouter(connect( 37 | mapStateToProps, 38 | mapDispatchToProps 39 | )(BookingsForm)); -------------------------------------------------------------------------------- /frontend/util/route_util.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Route, Redirect, withRouter } from 'react-router-dom'; 4 | 5 | const Auth = ({ component: Component, path, loggedIn }) => ( 6 | { 7 | return ( 8 | !loggedIn ? ( 9 | 10 | ) : ( 11 | 12 | ) 13 | ); 14 | } 15 | } /> 16 | ); 17 | 18 | const Protected = ({ component: Component, path, loggedIn }) => ( 19 | { 20 | return ( 21 | loggedIn ? ( 22 | 23 | ) : ( 24 | 25 | ) 26 | ) 27 | }} /> 28 | ); 29 | 30 | const mapStateToProps = state => { 31 | return {loggedIn: Boolean(state.session.currentUser)} 32 | }; 33 | 34 | export const AuthRoute = withRouter(connect(mapStateToProps, null)(Auth)); 35 | 36 | export const ProtectedRoute = withRouter(connect(mapStateToProps, null)(Protected)); 37 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 66817162339c45086cec067c0def15f03104f4f8473abe3fc135e288d4f23824b768aebd5658250fbb9808392b8536e45f33ed5032337a7c8b4802873550c7da 15 | 16 | test: 17 | secret_key_base: 8baf9459ddd5d0585e90e388e994575b4a1b952129c7c42517bc99bb979ec0f1d4cd70a13e431104032b52f520faf66d334329543acf40bfbec03b69ec077047 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # puts "\n== Copying sample files ==" 22 | # unless File.exist?('config/database.yml') 23 | # cp 'config/database.yml.sample', 'config/database.yml' 24 | # end 25 | 26 | puts "\n== Preparing database ==" 27 | system! 'bin/rails db:setup' 28 | 29 | puts "\n== Removing old logs and tempfiles ==" 30 | system! 'bin/rails log:clear tmp:clear' 31 | 32 | puts "\n== Restarting application server ==" 33 | system! 'bin/rails restart' 34 | end 35 | -------------------------------------------------------------------------------- /db/docs/sample-state.md: -------------------------------------------------------------------------------- 1 | ```js{ 2 | currentUser: { 3 | id: 1, 4 | first_name: "Trent", 5 | last_name: "Zaxford", 6 | image_url: "genericphotos.com/genericphoto" 7 | interests: { 8 | 1: { 9 | id: 1, 10 | interest: "Reading" 11 | } 12 | 2: { 13 | id: 2, 14 | interest: "Hiking" 15 | } 16 | } 17 | countries_visited: { 18 | 1: { 19 | id: 1, 20 | name: "Spain" 21 | } 22 | 2: { 23 | id: 5, 24 | name: "Hungary" 25 | } 26 | } 27 | hostings: { 28 | 1: { 29 | longitude: 48.228121, 30 | latitude: 16.293514, 31 | image_url: "genericphotos.com/genericFuton" 32 | } 33 | 2: { 34 | longitude: 48.228121, 35 | latitude: 16.293514, 36 | image_url: "genericphotos.com/genericFuton" 37 | } 38 | } 39 | bookings: { 40 | 1: { 41 | user_id: 1, 42 | futon_id: 1, 43 | start_date: 6-18-17, 44 | end_date: 6-20-17 45 | } 46 | } 47 | } 48 | }``` 49 | -------------------------------------------------------------------------------- /frontend/util/marker_manager.js: -------------------------------------------------------------------------------- 1 | export default class MarkerManager { 2 | constructor(map) { 3 | this.map = map; 4 | this.markers = {}; 5 | } 6 | 7 | updateMarkers(hosts) { 8 | // const hostsArr = Object.values(); 9 | // hosts.forEach(host => hostsObj[host.id] = host); 10 | // 11 | // hosts 12 | // .filter(host => !this.markers[host.id]) 13 | // .forEach((newHost) => this.createMarkerFromHost(newHost, this.handleClick)) 14 | // 15 | // Object.keys(this.markers) 16 | // .filter(hostId => !hostsObj[hostId]) 17 | // .forEach((hostId) => this.removeMarker(this.markers[hostId])) 18 | } 19 | 20 | createMarkerFromHost(host) { 21 | const pos = new google.maps.LatLng(host.latitude, host.longitude); 22 | const marker = new google.maps.Marker({ 23 | position: pos, 24 | map: this.map, 25 | hostId: host.id 26 | }); 27 | 28 | this.markers[marker.hostId] = marker; 29 | } 30 | 31 | removeMarker(marker) { 32 | this.markers[marker.hostId].setMap(null); 33 | delete this.markers[marker.hostId]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | 4 | var plugins = []; 5 | var devPlugins = []; 6 | 7 | var prodPlugins = [ 8 | new webpack.DefinePlugin({ 9 | 'process.env': { 10 | 'NODE_ENV': JSON.stringify('production') 11 | } 12 | }), 13 | new webpack.optimize.UglifyJsPlugin({ 14 | compress: { 15 | warnings: true 16 | } 17 | }) 18 | ]; 19 | 20 | plugins = plugins.concat( 21 | process.env.NODE_ENV === 'production' ? prodPlugins : devPlugins 22 | ) 23 | 24 | module.exports = { 25 | context: __dirname, 26 | entry: "./frontend/futon_flying.jsx", 27 | output: { 28 | path: path.resolve(__dirname, 'app', 'assets', 'javascripts'), 29 | filename: "bundle.js" 30 | }, 31 | plugins: plugins, 32 | module: { 33 | loaders: [ 34 | { 35 | test: [/\.jsx?$/, /\.js?$/], 36 | exclude: /node_modules/, 37 | loader: 'babel-loader', 38 | query: { 39 | presets: ['es2015', 'react'] 40 | } 41 | } 42 | ] 43 | }, 44 | devtool: 'source-maps', 45 | resolve: { 46 | extensions: [".js", ".jsx", "*"] 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /frontend/actions/session_actions.js: -------------------------------------------------------------------------------- 1 | import * as APIUtil from '../util/session_api_util'; 2 | import { receiveErrors, clearErrors } from './error_actions'; 3 | 4 | export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER'; 5 | 6 | export const receiveCurrentUser = currentUser => { 7 | return { 8 | type: RECEIVE_CURRENT_USER, 9 | currentUser 10 | }; 11 | }; 12 | 13 | export const login = user => dispatch => { 14 | return APIUtil.login(user) 15 | .then((user) => dispatch(receiveCurrentUser(user), 16 | (err) => { 17 | return dispatch(receiveErrors(err.responseJSON)); 18 | } 19 | ) 20 | ); 21 | }; 22 | 23 | export const logout = () => dispatch => { 24 | return APIUtil.logout() 25 | .then((user) => dispatch(receiveCurrentUser(null), 26 | (err) => { 27 | return dispatch(receiveErrors(err.responseJSON)); 28 | } 29 | ) 30 | ); 31 | }; 32 | 33 | export const signup = user => dispatch => { 34 | return APIUtil.signup(user) 35 | .then((user) => dispatch(receiveCurrentUser(user), 36 | (err) => { 37 | return dispatch(receiveErrors(err.responseJSON)); 38 | } 39 | ) 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /frontend/components/session_form/session_form_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { withRouter } from 'react-router-dom'; 3 | import { closeModal, openModal} from '../../actions/modal_actions'; 4 | import { login, signup, logout } from '../../actions/session_actions'; 5 | import SessionForm from './session_form'; 6 | import Header from '../header/header' 7 | import { clearErrors } from '../../actions/error_actions'; 8 | 9 | const mapStateToProps = state => { 10 | return { 11 | loggedIn: Boolean(state.session.currentUser), 12 | errors: state.session.errors, 13 | formType: state.modal.component.props.formType 14 | }; 15 | }; 16 | 17 | const mapDispatchToProps = (dispatch, ownProps) => { 18 | return { 19 | login: user => dispatch(login(user)), 20 | signup: user => dispatch(signup(user)), 21 | processForm: user => dispatch(processForm(user)), 22 | clearErrors: () => dispatch(clearErrors()), 23 | closeModal: () => dispatch(closeModal()), 24 | openModal: (component) => dispatch(openModal(component)) 25 | }; 26 | }; 27 | 28 | export default withRouter(connect( 29 | mapStateToProps, 30 | mapDispatchToProps 31 | )(SessionForm)); 32 | -------------------------------------------------------------------------------- /app/controllers/api/hosts_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::HostsController < ApplicationController 2 | 3 | def index 4 | # @hosts = Host.all 5 | @hosts = bounds ? Host.in_bounds(bounds) : Host.all 6 | render :index 7 | end 8 | 9 | def create 10 | @host = Host.new(host_params) 11 | if @host.save 12 | render 'api/hosts/show' 13 | else 14 | render json :@host.errors.full_messages, status: 422 15 | end 16 | 17 | end 18 | 19 | def show 20 | @host = Host.find(params[:id]) 21 | end 22 | 23 | def update 24 | @host = Host.find(params[:id]) 25 | if @host.update(host_params) 26 | render :show 27 | else 28 | render json: @host.errors.full_messages, status: 422 29 | end 30 | end 31 | 32 | def delete 33 | host = Host.find(params[:id]) 34 | host.delete 35 | render :index 36 | end 37 | 38 | private 39 | 40 | def host_params 41 | params.require(:host).permit( 42 | :lng, 43 | :lat, 44 | :fname, 45 | :lname, 46 | :city, 47 | :country, 48 | :description, 49 | :accepting_guests 50 | ) 51 | end 52 | 53 | def bounds 54 | params[:bounds] 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /app/models/host.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: hosts 4 | # 5 | # id :integer not null, primary key 6 | # lng :float not null 7 | # lat :float not null 8 | # description :string 9 | # fname :string not null 10 | # lname :string not null 11 | # city :string 12 | # country :string 13 | # accepting_guests :boolean default(TRUE) 14 | # avatar_file_name :string 15 | # avatar_content_type :string 16 | # avatar_file_size :integer 17 | # avatar_updated_at :datetime 18 | # 19 | 20 | class Host < ActiveRecord::Base 21 | validates :lng, :lat, :fname, :lname, presence: true 22 | 23 | has_attached_file :avatar, default_url: "generic-avatar.png" 24 | validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\Z/ 25 | 26 | has_many :reviews 27 | has_many :bookings 28 | 29 | def self.in_bounds(bounds) 30 | self.where("lat < ?", bounds[:northEast][:lat]) 31 | .where("lat > ?", bounds[:southWest][:lat]) 32 | .where("lng > ?", bounds[:southWest][:lng]) 33 | .where("lng < ?", bounds[:northEast][:lng]) 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /app/models/booking.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: bookings 4 | # 5 | # id :integer not null, primary key 6 | # host_id :integer not null 7 | # guest_id :integer not null 8 | # num_guests :integer default(1), not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # start_date :date 12 | # end_date :date 13 | # 14 | 15 | class Booking < ActiveRecord::Base 16 | validates :start_date, :end_date, :guest_id, :host_id, :num_guests, presence: true 17 | 18 | validate :is_after?, :valid_range?, :is_taken? 19 | 20 | belongs_to :guest, 21 | class_name: :User 22 | 23 | belongs_to :host, 24 | class_name: :User 25 | 26 | private 27 | def is_after? 28 | if start_date < Date.today 29 | self.errors.add(:start_date, "cannot be before today.") 30 | end 31 | end 32 | 33 | def valid_range? 34 | if start_date > end_date 35 | self.errors.add(:start_date, "cannot be after check in date.") 36 | end 37 | end 38 | 39 | def is_taken? 40 | if Booking.where('? < end_date and ? > start_date', self.start_date, self.end_date).any? 41 | errors.add(:date_conflict, 'These dates are already taken.') 42 | end 43 | end 44 | 45 | end -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 6 | 7 | Rails.application.config.raise_on_unfiltered_parameters = true 8 | 9 | # Enable per-form CSRF tokens. Previous versions had false. 10 | Rails.application.config.action_controller.per_form_csrf_tokens = true 11 | 12 | # Enable origin-checking CSRF mitigation. Previous versions had false. 13 | Rails.application.config.action_controller.forgery_protection_origin_check = true 14 | 15 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 16 | # Previous versions had false. 17 | ActiveSupport.to_time_preserves_timezone = true 18 | 19 | # Require `belongs_to` associations by default. Previous versions had false. 20 | Rails.application.config.active_record.belongs_to_required_by_default = true 21 | 22 | # Do not halt callback chains when a callback returns false. Previous versions had true. 23 | ActiveSupport.halt_callback_chains_on_return_false = false 24 | 25 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 26 | Rails.application.config.ssl_options = { hsts: { subdomains: true } } 27 | -------------------------------------------------------------------------------- /app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::UsersController < ApplicationController 2 | 3 | # need to add filtering to this as you don't want EVERY host 4 | def index 5 | @users = params[:lat] ? User.near([params[:lat], params[:lng]], 300).where.not(id: current_user.id) : User.all.where.not(id: current_user.id) 6 | render :index 7 | end 8 | 9 | def create 10 | @user = User.new(user_params) 11 | if @user.save 12 | login(@user) 13 | render "api/users/show" 14 | else 15 | render json: @user.errors.full_messages, status: 422 16 | end 17 | end 18 | 19 | def show 20 | @user = User.find(params[:id]) 21 | end 22 | 23 | def edit 24 | @user = User.find(params[:id]) 25 | end 26 | 27 | def update 28 | @user = User.find(params[:id]) 29 | if @user.update(user_params) 30 | render :show 31 | else 32 | render json: @user.errors.full_messages, status: 422 33 | end 34 | end 35 | 36 | private 37 | def user_params 38 | params.require(:user).permit( 39 | :fname, 40 | :lname, 41 | :username, 42 | :password, 43 | :bio, 44 | :city_id, 45 | :status, 46 | :longitude, 47 | :latitude, 48 | :city, 49 | :country, 50 | :accepting_guests 51 | ) 52 | end 53 | 54 | def bounds 55 | params[:bounds] 56 | end 57 | 58 | 59 | end 60 | -------------------------------------------------------------------------------- /frontend/futon_flying.jsx: -------------------------------------------------------------------------------- 1 | import configureStore from './store/store'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import Root from './components/root'; 5 | 6 | const getLocation = () => { 7 | var getPosition = function (options) { 8 | return new Promise(function (resolve, reject) { 9 | navigator.geolocation.getCurrentPosition(resolve, reject, options); 10 | }); 11 | }; 12 | getPosition() 13 | .then((position) => { 14 | window.localStorage['latitude'] = parseFloat(position.coords.latitude); 15 | window.localStorage['longitude'] = parseFloat(position.coords.longitude); 16 | }) 17 | .catch((err) => { 18 | return "Using the default coordinates"; 19 | }); 20 | }; 21 | 22 | document.addEventListener('DOMContentLoaded', () => { 23 | const root = document.getElementById('root'); 24 | let store; 25 | if (window.currentUser) { 26 | const preloadedState = { session: { currentUser: window.currentUser} }; 27 | store = configureStore(preloadedState); 28 | delete window.currentUser; 29 | } else { 30 | store = configureStore(); 31 | } 32 | if (!window.localStorage.latitude){ 33 | window.localStorage['latitude'] = 40.215273; 34 | window.localStorage['longitude'] = -74.129379; 35 | } 36 | getLocation(); 37 | 38 | ReactDOM.render(, root); 39 | }); 40 | -------------------------------------------------------------------------------- /db/docs/README.md: -------------------------------------------------------------------------------- 1 | .## Design Documents 2 | * [View Wireframes][wireframes] 3 | * [React Components][components] 4 | * [Sample State][sample-state] 5 | * [API Endpoints][api-endpoints] 6 | * [DB Schema][schema] 7 | 8 | [wireframes]: ./wireframes 9 | [components]: ./component-heirarchy.md 10 | [sample-state]: ./sample-state.md 11 | [api-endpoints]: ./api-endpoints.md 12 | [schema]: ./schema.md 13 | 14 | ## Implementation Timeline 15 | 16 | ### Phase 1: Backend setup and Front End User Authentication (1 day) 17 | * Allow new users to sign up 18 | * Allow current users to login & logout 19 | * Provide a demo user 20 | * Style front end auth 21 | 22 | 23 | ### Phase 2: Users and profiles (1 day) 24 | 25 | * Create users' dashboards 26 | * Allow users to edit their profiles 27 | 28 | ### Phase 3: Bookings and hostings (~2-3 days) 29 | 30 | * Allow users to create a listing 31 | * Allow users to find hosts 32 | * Allow users to host travelers 33 | * Create Date component 34 | * Allow users to find hosts through the Google Maps API 35 | * Allow users to leave reviews 36 | 37 | ### Phase 4: Search & Header (1 day) 38 | * Add Search component 39 | * Create Header 40 | 41 | ### Phase 5: Prettying up the site(1-3 days) 42 | * Style profiles, headers, dashboard, et al. 43 | 44 | ### Bonus Features 45 | - [ ] Messaging 46 | - [ ] Events 47 | - [ ] Friends 48 | - [ ] Basic forum for each location 49 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/booking.scss: -------------------------------------------------------------------------------- 1 | .booking { 2 | display: -webkit-box; 3 | display: -ms-flexbox; 4 | display: flex; 5 | -webkit-box-pack: justify; 6 | -ms-flex-pack: justify; 7 | justify-content: space-between; 8 | font-size: 1rem; 9 | padding: 10px; 10 | -webkit-transition: background-color .3s; 11 | -o-transition: background-color .3s; 12 | transition: background-color .3s; 13 | text-decoration: none; 14 | color: black; 15 | 16 | .booking__user { 17 | align-self: center; 18 | } 19 | 20 | .booking__image { 21 | width: 80px; 22 | height: 80px; 23 | border-radius: 50%; 24 | margin: 0; 25 | } 26 | 27 | .booking__name { 28 | margin: 0; 29 | } 30 | 31 | .booking__date { 32 | p { 33 | margin: 10px 0 5px 0; 34 | } 35 | 36 | hr { 37 | margin-top: 0; 38 | margin-bottom: 15px; 39 | border: .5px solid black; 40 | } 41 | } 42 | 43 | &:hover { 44 | background-color: darken(#fff, 10%); 45 | } 46 | 47 | &:first-of-type { 48 | padding-top: 5px; 49 | } 50 | 51 | &:last-of-type { 52 | padding-bottom: 5px; 53 | } 54 | } 55 | 56 | .booking__hr { 57 | color: black; 58 | margin: 0; 59 | 60 | &:last-of-type { 61 | display: none; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FutonFlying", 3 | "version": "1.0.0", 4 | "description": "Couchsurfing, but for futons!", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "engines": { 10 | "node": "v6.10.1", 11 | "npm": "v5.6.0" 12 | }, 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1", 15 | "postinstall": "webpack" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/vo-eric/FutonFlying" 20 | }, 21 | "keywords": [], 22 | "author": "", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/appacademy/BenchBnb/issues" 26 | }, 27 | "homepage": "https://github.com/vo-eric/FutonFlying#readme", 28 | "dependencies": { 29 | "babel-core": "^6.2.0", 30 | "babel-loader": "^6.2.0", 31 | "babel-preset-es2015": "^6.9.0", 32 | "babel-preset-react": "^6.1.18", 33 | "jquery": "^3.2.1", 34 | "lodash": "^4.15.0", 35 | "masonry-layout": "^4.2.0", 36 | "moment": "^2.18.1", 37 | "react": "^15.6.1", 38 | "react-addons-shallow-compare": "^15.6.0", 39 | "react-dates": "^12.3.0", 40 | "react-dom": "^15.6.1", 41 | "react-redux": "^4.4.5", 42 | "react-router-dom": "^4.1.1", 43 | "react-transition-group": "^1.2.0", 44 | "redux": "^3.5.2", 45 | "redux-thunk": "^2.1.0", 46 | "webpack": "^3.10.0" 47 | }, 48 | "devDependencies": { 49 | "redux-logger": "^3.0.6" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /db/docs/component-heirarchy.md: -------------------------------------------------------------------------------- 1 | Component Hierarchy 2 | ----------------------------------------------------------------------- 3 | 4 | ### AuthFormContainer 5 | * Sign Up 6 | * Log In 7 | 8 | ### DashboardContainer/HomeContainer 9 | * Header 10 | * User Details 11 | * Trips Index 12 | * Guests Index 13 | * Searchbar 14 | 15 | ### Header (persistent throughout all pages if signed in & not in dashboard) 16 | * Logo 17 | * Current User Photo 18 | * Messages 19 | * Search (not available if in dashboard) 20 | 21 | ### Search 22 | * Hosts Index 23 | * Hosts Index Item (showing X random hosts) 24 | * Visitors Index 25 | * Visitors Index Item (showing X random visitors without a host) 26 | * Booking Container 27 | * Map Container 28 | * Information Container 29 | 30 | ### UsersContainer 31 | * Booking Container 32 | * Date Component (if they are accepting guests) 33 | * Message Container 34 | 35 | ### BookingContainer 36 | * Date Component 37 | * Traveler Container 38 | 39 | ### HostingContainer 40 | * Date Component 41 | * Host Container 42 | 43 | ## Routes 44 | 45 | Path | Component 46 | ---------------------------|------------------- 47 | "/sign-up" | "AuthFormContainer" 48 | "/log-in" | "AuthFormContainer" 49 | "/futons/:futonId" | "HostsContainer" 50 | "/futons/:futonId/booking" | "BookingContainer" 51 | "/search" | "Search" 52 | "/home" | "DashboardContainer" 53 | -------------------------------------------------------------------------------- /frontend/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { Route, Switch, Link } from 'react-router-dom'; 4 | import { AuthRoute, ProtectedRoute } from '../util/route_util'; 5 | 6 | import SessionFormContainer from './session_form/session_form_container'; 7 | import HeaderContainer from './header/header_container'; 8 | import ModalContainer from './modal/modal_container'; 9 | import SplashScreenContainer from './splash_screen/splash_screen_container'; 10 | import HostIndexContainer from './hosts/host_index_container'; 11 | import HostShowContainer from './hosts/host_show_container'; 12 | import DashboardContainer from './dashboard/dashboard_container'; 13 | import BookingContainer from './booking/bookings_container'; 14 | import BookingsShowContainer from './booking/bookings_show_container'; 15 | 16 | const App = () => ( 17 |
18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | ); 33 | export default App; 34 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | position: fixed; 3 | width: 100%; 4 | left: 0; 5 | top: 0; 6 | z-index: 5; 7 | 8 | .subheader { 9 | -webkit-box-pack: justify; 10 | -ms-flex-pack: justify; 11 | justify-content: space-between; 12 | -webkit-box-align: center; 13 | -ms-flex-align: center; 14 | align-items: center; 15 | background: white; 16 | display: -webkit-box; 17 | display: -ms-flexbox; 18 | display: flex; 19 | height: 50px; 20 | border-bottom: 2px solid #bfcad2; 21 | } 22 | 23 | .wordmark { 24 | margin-left: 10px; 25 | font-size: 2rem; 26 | font-family: 'Delius', cursive; 27 | color: #D4490B; 28 | font-weight: bold; 29 | } 30 | 31 | button { 32 | font-family: 'Lato', sans-serif; 33 | line-height: 1.5; 34 | display: inline-block; 35 | cursor: pointer; 36 | padding: 8px 12px; 37 | max-width: 240px; 38 | font-size: .9rem; 39 | font-weight: bold; 40 | text-align: center; 41 | border-radius: 3px; 42 | outline: none; 43 | } 44 | 45 | .join-button, 46 | .sign-out { 47 | border: 1px solid #287fb8; 48 | background: #287fb8; 49 | color: white; 50 | margin: 5px; 51 | } 52 | 53 | .login-guest { 54 | border: 1px solid #D4490B; 55 | background: #D4490B; 56 | color: white; 57 | margin: 5px; 58 | } 59 | 60 | .login-button { 61 | border: 1px solid #287fb8; 62 | color: #287fb8; 63 | } 64 | } 65 | 66 | #main-content { 67 | margin-top: 4%; 68 | } 69 | -------------------------------------------------------------------------------- /frontend/components/hosts/host_index_detail.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | class HostIndexDetail extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.handleClick = this.handleClick.bind(this); 7 | } 8 | 9 | handleClick(e) { 10 | e.stopPropagation(); 11 | this.props.changeCenter({ 12 | lat: this.props.host.latitude, 13 | lng: this.props.host.longitude, 14 | }, this.props.host.id); 15 | } 16 | 17 | acceptingGuests() { 18 | if (this.props.host.accepting_guests) { 19 | return ( 20 |

21 | ACCEPTING GUESTS 22 |

23 | ); 24 | } else { 25 | return ( 26 |

27 | NOT ACCEPTING GUESTS 28 |

29 | ); 30 | } 31 | } 32 | 33 | render() { 34 | const { selected } = this.props; 35 | return ( 36 |
41 | 45 |
46 |
47 |
48 | {this.props.host.fname} {this.props.host.lname} 49 |
50 |
51 | {this.props.host.city}, {this.props.host.country} 52 |
53 |
54 | {this.acceptingGuests()} 55 |
56 |
57 | ); 58 | } 59 | } 60 | 61 | export default HostIndexDetail; 62 | -------------------------------------------------------------------------------- /app/controllers/api/bookings_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::BookingsController < ApplicationController 2 | before_action :require_logged_in 3 | 4 | def index 5 | # if params[:user_id] 6 | # @bookings = Booking.where(guest_id: params[:user_id]) 7 | # @hostings = Booking.where(host_id: params[:user_id]) 8 | # else 9 | # @bookings = Booking.where(guest_id: current_user.id) 10 | # @hostings = Booking.where(host_id: current_user.id) 11 | # end 12 | @bookings = @current_user.find_bookings 13 | render :index 14 | end 15 | 16 | def show 17 | @booking = Booking.find(params[:id]) 18 | end 19 | 20 | def create 21 | @booking = Booking.new(host_id: booking_params[:hostId], start_date: booking_params[:startDate], end_date: booking_params[:endDate], num_guests: booking_params[:numGuests]) 22 | @booking.guest_id = @current_user.id 23 | 24 | if @booking.save 25 | render :show 26 | else 27 | render json: @booking.errors.full_messages, status: 422 28 | end 29 | 30 | end 31 | # def update 32 | # @booking = Booking.find(params[:id]) 33 | # 34 | # if @booking.update(user_params) 35 | # render "api/bookings/show" 36 | # else 37 | # render json: @booking.errors.full_messages, status: 422 38 | # end 39 | # 40 | # end 41 | 42 | def destroy 43 | @booking = Booking.find(params[:id]) 44 | if @booking 45 | @booking.destroy 46 | render "api/bookings/index" 47 | else 48 | render json: ["There is no record of this booking"], status: 404 49 | end 50 | end 51 | 52 | private 53 | 54 | def booking_params 55 | params 56 | .require(:booking) 57 | .permit( 58 | :startDate, 59 | :endDate, 60 | :hostId, 61 | :numGuests 62 | ) 63 | end 64 | 65 | end 66 | -------------------------------------------------------------------------------- /frontend/actions/booking_actions.js: -------------------------------------------------------------------------------- 1 | import * as APIUtil from '../util/booking_api_util'; 2 | import { receiveErrors,clearErrors } from './error_actions'; 3 | 4 | export const RECEIVE_BOOKINGS = 'RECEIVE_BOOKINGS'; 5 | export const RECEIVE_CURRENT_BOOKING = 'RECEIVE_CURRENT_BOOKING'; 6 | export const CANCEL_BOOKING = 'CANCEL_BOOKING'; 7 | 8 | export const receiveBookings = bookings => { 9 | return { 10 | type: RECEIVE_BOOKINGS, 11 | bookings 12 | }; 13 | }; 14 | 15 | export const receiveCurrentBooking = booking => { 16 | return { 17 | type: RECEIVE_CURRENT_BOOKING, 18 | booking 19 | }; 20 | }; 21 | 22 | export const cancelBooking = bookingId => { 23 | return { 24 | type: CANCEL_BOOKING, 25 | bookingId 26 | }; 27 | }; 28 | 29 | export const fetchBookings = userId => dispatch => { 30 | return APIUtil.fetchBookings(userId) 31 | .then((bookings) => { 32 | dispatch(receiveBookings(bookings))}, 33 | (err) => { 34 | return dispatch(receiveErrors(err.responseJSON)); 35 | } 36 | ); 37 | }; 38 | 39 | export const fetchSingleBooking = id => dispatch => { 40 | return APIUtil.fetchSingleBooking(id) 41 | .then((booking) => dispatch(receiveCurrentBooking(booking), 42 | (err) => { 43 | return dispatch(receiveErrors(err.responseJSON)); 44 | } 45 | )); 46 | }; 47 | 48 | export const createBooking = booking => dispatch => ( 49 | APIUtil.createBooking(booking) 50 | .then(confirmation => dispatch(receiveCurrentBooking(confirmation)), 51 | (err) => dispatch(receiveErrors(err.responseJSON)))); 52 | 53 | export const deleteBooking = bookingId => dispatch => { 54 | return APIUtil.deleteBooking(bookingId) 55 | .then(() => dispatch(cancelBooking(bookingId), 56 | (err) => { 57 | return dispatch(receiveErrors(err.responseJSON)); 58 | } 59 | )); 60 | }; -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => 'public, max-age=3600' 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /db/docs/schema.md: -------------------------------------------------------------------------------- 1 | ## users 2 | column name | data type | details 3 | ----------------|-----------|----------------------- 4 | id | integer | not null, primary key 5 | first_name | string | not null 6 | last_name | string | not null 7 | image_url | string | not null 8 | email | string | not null, indexed, unique 9 | password_digest | string | not null 10 | session_token | string | not null, indexed, unique 11 | bio | string | 12 | city_id | integer | not null, foreign key 13 | language | string | not null 14 | interests | string | not null 15 | 16 | - has_many: reviews, bookings, hostings, languages, interests 17 | - belongs_to: city 18 | 19 | 20 | ## city 21 | column name | data type | details 22 | ------------|-----------|----------------------- 23 | id | integer | not null, primary key 24 | name | string | not null 25 | description | string | not null 26 | image_url | string | not null 27 | 28 | - has_many: hosts 29 | 30 | 31 | ## bookings 32 | column name | data type | details 33 | ------------|-----------|----------------------- 34 | id | integer | not null, primary key 35 | guest_id | integer | not null, foreign key, indexed 36 | host_id | integer | not null, foreign key, indexed 37 | start_date | date | not null 38 | end_date | date | not null 39 | num_travelers| integer | not null 40 | body | string | not null 41 | 42 | - belongs_to: host, guest 43 | 44 | ## reviews 45 | column name | data type | details 46 | ------------|-----------|----------------------- 47 | id | integer | not null, primary key 48 | reviewer_id | integer | not null, foreign key, indexed 49 | reviewee_id | integer | not null, foreign key, indexed 50 | body | string | not null 51 | rating | integer | not null 52 | 53 | -belongs_to: reviewer, reviewee 54 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | git_source(:github) do |repo_name| 4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 5 | "https://github.com/#{repo_name}.git" 6 | end 7 | 8 | 9 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 10 | gem 'rails', '~> 5.0.3' 11 | # Use postgresql as the database for Active Record 12 | gem 'pg', '~> 0.18' 13 | # Use Puma as the app server 14 | gem 'puma', '~> 3.0' 15 | # Use SCSS for stylesheets 16 | gem 'sass-rails', '~> 5.0' 17 | # Use Uglifier as compressor for JavaScript assets 18 | gem 'uglifier', '>= 1.3.0' 19 | # Use CoffeeScript for .coffee assets and views 20 | gem 'coffee-rails', '~> 4.2' 21 | # See https://github.com/rails/execjs#readme for more supported runtimes 22 | # gem 'therubyracer', platforms: :ruby 23 | 24 | # Use jquery as the JavaScript library 25 | gem 'jquery-rails' 26 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 27 | gem 'jbuilder', '~> 2.5' 28 | # Use Redis adapter to run Action Cable in production 29 | # gem 'redis', '~> 3.0' 30 | # Use ActiveModel has_secure_password 31 | gem 'paperclip', '5.0.0.beta1' 32 | gem 'bcrypt', '~> 3.1.7' 33 | gem 'annotate' 34 | gem 'faker' 35 | gem 'figaro' 36 | gem 'aws-sdk', '>=2.0' 37 | gem 'geocoder' 38 | gem 'seed_dump' 39 | 40 | # Use Capistrano for deployment 41 | # gem 'capistrano-rails', group: :development 42 | 43 | group :development, :test do 44 | # Call 'byebug' anywhere in the code to stop execution and get a console 45 | gem 'byebug', platform: :mri 46 | gem 'web-console', '>= 3.3.0' 47 | gem 'spring' 48 | 49 | end 50 | 51 | group :development do 52 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. 53 | gem 'listen', '~> 3.0.5' 54 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 55 | gem 'spring-watcher-listen', '~> 2.0.0' 56 | end 57 | 58 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 59 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 60 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => 'public, max-age=172800' 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | # Print deprecation notices to the Rails logger. 35 | config.active_support.deprecation = :log 36 | 37 | # Raise an error on page load if there are pending migrations. 38 | config.active_record.migration_error = :page_load 39 | 40 | # Debug mode disables concatenation and preprocessing of assets. 41 | # This option may cause significant delays in view rendering with a large 42 | # number of complex assets. 43 | config.assets.debug = true 44 | 45 | # Suppress logger output for asset requests. 46 | config.assets.quiet = true 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | 51 | # Use an evented file watcher to asynchronously detect changes in source code, 52 | # routes, locales, etc. This feature depends on the listen gem. 53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 54 | end 55 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum, this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # The code in the `on_worker_boot` will be called if you are using 36 | # clustered mode by specifying a number of `workers`. After each worker 37 | # process is booted this block will be run, if you are using `preload_app!` 38 | # option you will want to use this block to reconnect to any threads 39 | # or connections that may have been created at application boot, Ruby 40 | # cannot share connections between processes. 41 | # 42 | # on_worker_boot do 43 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 44 | # end 45 | 46 | # Allow puma to be restarted by `rails restart` command. 47 | plugin :tmp_restart 48 | -------------------------------------------------------------------------------- /frontend/components/dashboard/dashboard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import BookingsContainer from '../booking/bookings_container'; 4 | 5 | class Dashboard extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.showTrips = this.showTrips.bind(this); 9 | this.showGuests = this.showGuests.bind(this); 10 | } 11 | 12 | showTrips() { 13 | return ; 14 | } 15 | 16 | showGuests() { 17 | return ; 18 | } 19 | 20 | componentWillReceiveProps(nextProps) { 21 | if (!nextProps.user) { 22 | this.props.history.push('/'); 23 | } 24 | } 25 | 26 | render () { 27 | let user = this.props.user; 28 | return( 29 |
30 |
31 | 34 |

35 | {user.fname} {user.lname} 36 |

37 |

38 | {user.city}, {user.country} 39 |

40 |
41 | 42 |
43 |
44 |

45 | Explore The World 46 |

47 | 48 | 49 | 52 | Browse all Hosts 53 | 54 |
55 | 56 |
57 |
58 | My Travel Plans 59 |
60 | 61 |
62 | {this.showTrips()} 63 |
64 |
65 | 66 |
67 |
68 | My Upcoming Guests 69 |
70 | 71 |
72 | {this.showGuests()} 73 |
74 |
75 | 76 |
77 | 78 |
79 | ); 80 | } 81 | } 82 | 83 | export default Dashboard; 84 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/session.scss: -------------------------------------------------------------------------------- 1 | // Header of session modals 2 | .modal-exit { 3 | cursor: pointer; 4 | } 5 | 6 | .modal-head { 7 | display: -webkit-box; 8 | display: -ms-flexbox; 9 | display: flex; 10 | -webkit-box-pack: justify; 11 | -ms-flex-pack: justify; 12 | justify-content: space-between; 13 | -webkit-box-align: center; 14 | -ms-flex-align: center; 15 | align-items: center; 16 | padding: 15px 20px 5px 20px; 17 | } 18 | 19 | .modal-head-text { 20 | font: 2rem; 21 | color: #27374c; 22 | font-weight: bold; 23 | } 24 | 25 | // First & Last Name of User 26 | .input-name { 27 | display: -webkit-box; 28 | display: -ms-flexbox; 29 | display: flex; 30 | -webkit-box-pack: justify; 31 | -ms-flex-pack: justify; 32 | justify-content: space-between; 33 | font-size: .9rem; 34 | margin: 0 15px; 35 | 36 | input { 37 | padding-left: 5px; 38 | border-radius: 4px; 39 | height: 30px; 40 | width: 100%; 41 | border: 1px solid #aeb5ba; 42 | font-size: inherit; 43 | } 44 | 45 | .input-last-name { 46 | margin-left: 10px; 47 | } 48 | } 49 | 50 | //Username, Password, and Submit button 51 | .auth-form-username, 52 | .auth-form-password { 53 | padding-left: 5px; 54 | border-radius: 5px; 55 | height: 30px; 56 | width: 100%; 57 | border: 1px solid #aeb5ba; 58 | font-size: .9rem; 59 | margin-bottom: 15px; 60 | } 61 | 62 | .auth-form { 63 | margin: 15px 22px 0 15px; 64 | } 65 | 66 | .alt-text { 67 | font-size: 1rem; 68 | text-align: center; 69 | } 70 | 71 | .submit-button { 72 | color: white; 73 | text-align: center; 74 | border-radius: 5px; 75 | font-size: .9rem; 76 | letter-spacing: 1px; 77 | height: 35px; 78 | width: 102%; 79 | background: #287fb8; 80 | border: 1px solid #287fb8; 81 | margin-bottom: 10px; 82 | } 83 | 84 | //Modal toggle 85 | 86 | .form-toggle__text { 87 | font-size: 1.25rem; 88 | text-align: center; 89 | } 90 | 91 | .form-toggle__button { 92 | display: block; 93 | margin: 10px auto 0 auto; 94 | width: 30%; 95 | height: 30px; 96 | font-size: .9rem; 97 | border: 1px solid #287fb8; 98 | color: #287fb8; 99 | border-radius: 5px; 100 | outline: none; 101 | cursor: pointer; 102 | } 103 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/host-show.scss: -------------------------------------------------------------------------------- 1 | .host-show-main { 2 | margin: 0 auto; 3 | width: 60%; 4 | display: -webkit-box; 5 | display: -ms-flexbox; 6 | display: flex; 7 | } 8 | 9 | .about-me { 10 | margin-top: 10px; 11 | height: 100%; 12 | background: white; 13 | border-bottom: 2px solid #bfcad2; 14 | } 15 | 16 | .about-me-header { 17 | padding: 10px; 18 | font-size: 1.2rem; 19 | border-bottom: 1px solid #ecf0f1; 20 | } 21 | 22 | .about-me-body { 23 | margin: 5px 10px 0 10px; 24 | padding: 2px; 25 | line-height: 1.5; 26 | font-size: .9rem; 27 | } 28 | 29 | .host-show-photo { 30 | margin-top: 5px; 31 | width: 150px; 32 | height: 150px; 33 | border-radius: 50%; 34 | } 35 | 36 | .host-show-user { 37 | text-align: center; 38 | background: white; 39 | min-width: 20%; 40 | height: 300px; 41 | border-bottom: 2px solid #bfcad2; 42 | padding-top: 15px; 43 | } 44 | 45 | .host-show-name { 46 | -ms-flex-wrap: wrap; 47 | flex-wrap: wrap; 48 | margin-top: 10px; 49 | font-size: 1.8rem; 50 | text-align: center; 51 | } 52 | 53 | .host-show-location { 54 | margin-top: 5px; 55 | font-size: .8rem; 56 | text-align: center; 57 | } 58 | 59 | .host-show-about { 60 | width: 80%; 61 | height: auto; 62 | margin-left: 10px; 63 | } 64 | 65 | .host-booking { 66 | display: -webkit-box; 67 | display: -ms-flexbox; 68 | display: flex; 69 | background: white; 70 | border-bottom: 2px solid #bfcad2; 71 | -webkit-box-pack: justify; 72 | -ms-flex-pack: justify; 73 | justify-content: space-between; 74 | padding: 15px 8px; 75 | } 76 | 77 | .accepting-guests { 78 | color: #41b866; 79 | font-size: 1.4rem; 80 | -ms-flex-item-align: center; 81 | -ms-grid-row-align: center; 82 | align-self: center; 83 | } 84 | 85 | .not-accepting-guests { 86 | color: #ff3f2e; 87 | font-size: 1.4rem; 88 | -ms-flex-item-align: center; 89 | -ms-grid-row-align: center; 90 | align-self: center; 91 | } 92 | 93 | .request-button { 94 | background-color: #287fb8; 95 | border: none; 96 | font-size: 1rem; 97 | color: white; 98 | padding: 8px 12px; 99 | text-align: center; 100 | border-radius: 3px; 101 | margin-right: 5px; 102 | outline: none; 103 | -webkit-transition: background-color .3s; 104 | -o-transition: background-color .3s; 105 | transition: background-color .3s; 106 | 107 | &:hover { 108 | background-color: darken(#287fb8, 10%); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20180411193735) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "bookings", force: :cascade do |t| 19 | t.integer "host_id", null: false 20 | t.integer "guest_id", null: false 21 | t.integer "num_guests", default: 1, null: false 22 | t.datetime "created_at", null: false 23 | t.datetime "updated_at", null: false 24 | t.date "start_date" 25 | t.date "end_date" 26 | t.index ["guest_id"], name: "index_bookings_on_guest_id", using: :btree 27 | t.index ["host_id"], name: "index_bookings_on_host_id", using: :btree 28 | end 29 | 30 | create_table "homes", force: :cascade do |t| 31 | t.float "lng", null: false 32 | t.float "lat", null: false 33 | t.integer "owner_id", null: false 34 | t.boolean "accepting_guests", null: false 35 | t.string "description" 36 | t.index ["owner_id"], name: "index_homes_on_owner_id", using: :btree 37 | end 38 | 39 | create_table "users", force: :cascade do |t| 40 | t.string "fname" 41 | t.string "lname" 42 | t.string "username", null: false 43 | t.string "password_digest", null: false 44 | t.string "session_token", null: false 45 | t.text "bio" 46 | t.datetime "created_at", null: false 47 | t.datetime "updated_at", null: false 48 | t.string "avatar_file_name" 49 | t.string "avatar_content_type" 50 | t.integer "avatar_file_size" 51 | t.datetime "avatar_updated_at" 52 | t.float "latitude", null: false 53 | t.float "longitude", null: false 54 | t.string "city" 55 | t.string "country" 56 | t.boolean "accepting_guests", default: true 57 | t.string "address" 58 | t.index ["username"], name: "index_users_on_username", unique: true, using: :btree 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/booking_form.scss: -------------------------------------------------------------------------------- 1 | .dropdown { 2 | -webkit-transition: height .5s; 3 | -o-transition: height .5s; 4 | transition: height .5s; 5 | overflow: hidden; 6 | background: #fff; 7 | border-bottom: 2px solid #bfcad2; 8 | margin-top: 10px; 9 | padding: 0 10px; 10 | } 11 | 12 | .active { 13 | height: 550px; 14 | } 15 | 16 | .inactive { 17 | height: 0; 18 | border-bottom: none; 19 | } 20 | 21 | .booking-form { 22 | .booking-form-header { 23 | text-transform: uppercase; 24 | padding-bottom: 10px; 25 | font-size: .9rem; 26 | border-bottom: 1px solid #ecf0f1; 27 | } 28 | 29 | .booking-form__dates { 30 | margin-top: 10px; 31 | display: -webkit-box; 32 | display: -ms-flexbox; 33 | display: flex; 34 | } 35 | 36 | .booking-errors { 37 | color: rgb(253, 69, 69); 38 | margin-bottom: 0; 39 | 40 | p { 41 | margin-top: 0; 42 | margin-bottom: 0; 43 | } 44 | } 45 | 46 | .booking-form__date, 47 | .booking-form__message { 48 | display: -webkit-box; 49 | display: -ms-flexbox; 50 | display: flex; 51 | -webkit-box-orient: vertical; 52 | -webkit-box-direction: normal; 53 | -ms-flex-direction: column; 54 | flex-direction: column; 55 | } 56 | 57 | .booking-form__date { 58 | span { 59 | font-size: .9rem; 60 | } 61 | 62 | &:first-of-type { 63 | margin-right: 20px; 64 | } 65 | } 66 | 67 | .booking-form__date-input { 68 | padding: 8px; 69 | margin-top: 10px; 70 | height: 10px; 71 | border-radius: 5px; 72 | outline: none; 73 | } 74 | 75 | .booking-form__message { 76 | span { 77 | margin: 30px 0 10px 0; 78 | font-size: 1rem; 79 | } 80 | 81 | textarea { 82 | height: 200px; 83 | padding: 10px; 84 | font-size: 1rem; 85 | outline: none; 86 | margin-bottom: 20px; 87 | } 88 | } 89 | 90 | .booking-form__buttons { 91 | text-align: right; 92 | 93 | .booking-form-button { 94 | border: none; 95 | border-radius: 3px; 96 | cursor: pointer; 97 | font-size: .9rem; 98 | padding: 8px 12px; 99 | max-width: 240px; 100 | outline: none; 101 | border: 1px solid #287fb8; 102 | } 103 | 104 | .booking-form-button:first-of-type { 105 | background: #fff; 106 | color: #287fb8; 107 | margin-right: 10px; 108 | } 109 | 110 | .booking-form-button:last-of-type { 111 | background: #287fb8; 112 | color: #fff; 113 | margin-right: 0; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /frontend/components/hosts/host_show.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import BookingsFormContainer from '../booking/bookings_form_container'; 3 | import DropdownContainer from '../dropdown/dropdown_container'; 4 | 5 | class HostShow extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.openDropdown = this.openDropdown.bind(this); 9 | this.renderBio = this.renderBio.bind(this); 10 | } 11 | 12 | openDropdown() { 13 | this.props.openDropdown(); 14 | } 15 | 16 | componentDidMount() { 17 | this.props.fetchSingleHost(this.props.match.params.id); 18 | } 19 | 20 | acceptingGuests() { 21 | if (this.props.hostDetails.accepting_guests) { 22 | return ( 23 |
24 | Accepting Guests 25 |
26 | ); 27 | } else { 28 | return( 29 |
30 | Not Accepting Guests 31 |
32 | ); 33 | } 34 | } 35 | 36 | renderBio() { 37 | if (!this.props.hostDetails.bio) { 38 | return `${this.props.hostDetails.fname} has not added a bio yet.`; 39 | } else { 40 | return this.props.hostDetails.bio; 41 | } 42 | } 43 | 44 | render () { 45 | if (!this.props.hostDetails) { 46 | return ( 47 |
Loading...
48 | ); 49 | } 50 | let buttonStyle = !this.props.hostDetails.accepting_guests ? {cursor: 'not-allowed'} : {cursor: 'pointer'}; 51 | return ( 52 |
53 |
54 |
55 | 59 |
60 | 61 |
62 | {this.props.hostDetails.fname} {this.props.hostDetails.lname} 63 |
64 | 65 |
66 | {this.props.hostDetails.city}, {this.props.hostDetails.country} 67 |
68 |
69 | 70 |
71 |
72 | { this.acceptingGuests() } 73 |
74 | 82 |
83 |
84 | 85 |
86 |
87 | About Me 88 |
89 | 90 |
91 | {this.renderBio()} 92 |
93 |
94 |
95 |
96 | ); 97 | } 98 | } 99 | 100 | export default HostShow; 101 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.1 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: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | development: 25 | <<: *default 26 | database: MyCouchSurfing_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: MyCouchSurfing 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: MyCouchSurfing_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: MyCouchSurfing_production 84 | username: MyCouchSurfing 85 | password: <%= ENV['MYCOUCHSURFING_DATABASE_PASSWORD'] %> 86 | -------------------------------------------------------------------------------- /frontend/components/booking/bookings.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import BookingsShowContainer from './bookings_show_container'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | class Bookings extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.renderBookingsTemplate = this.renderBookingsTemplate.bind(this); 9 | this.handleClick = this.handleClick.bind(this); 10 | } 11 | 12 | componentWillMount() { 13 | this.props.fetchBookings(this.props.currentUser.id); 14 | } 15 | 16 | handleClick() { 17 | return ( 18 |
19 | ; 20 |
21 | ); 22 | } 23 | 24 | renderBookingsTemplate(booking) { 25 | let bookings; 26 | return ([ 27 |
28 | {booking.fname} 32 |

33 | {booking.fname} {booking.lname} 34 |

35 |
, 36 | 37 |
38 |

Arriving At

39 |
40 | {booking.start_date} 41 |
, 42 | 43 |
44 |

Leaving At

45 |
46 | {booking.end_date} 47 |
48 | ]); 49 | } 50 | 51 | render() { 52 | if (this.props.formType === "bookings") { 53 | let bookings; 54 | if (this.props.bookings.length !== 0) { 55 | bookings = ( 56 |
57 | {Object.values(this.props.bookings[0]).map((booking) => { 58 | return ([ 59 |
this.handleClick()}> 62 | {this.renderBookingsTemplate(booking)} 63 |
, 64 |
65 | ]); 66 | } 67 | )} 68 |
69 | ); 70 | } else { 71 | bookings = ( 72 |
73 | You don't have any bookings 74 |
75 | ); 76 | } 77 | return bookings; 78 | } else if (this.props.formType === "hostings") { 79 | let hostings; 80 | if (this.props.bookings[1]) { 81 | debugger 82 | hostings = ( 83 |
84 | {Object.values(this.props.bookings[1]).map((hosting) => { 85 | return ([ 86 |
87 | {this.renderBookingsTemplate(hosting)} 88 |
, 89 |
90 | ]); 91 | } 92 | )} 93 |
94 | ); 95 | } else { 96 | hostings = ( 97 |
98 | You don't have any hostings 99 |
100 | ); 101 | } 102 | return hostings; 103 | } 104 | } 105 | } 106 | 107 | export default Bookings; -------------------------------------------------------------------------------- /app/assets/stylesheets/api/dashboard.scss: -------------------------------------------------------------------------------- 1 | .dashboard { 2 | margin: 0 auto; 3 | font-family: 'Lato', sans-serif; 4 | display: -webkit-box; 5 | display: -ms-flexbox; 6 | display: flex; 7 | width: 80%; 8 | } 9 | 10 | .user-module__photo { 11 | width: 120px; 12 | height: 120px; 13 | border-radius: 60px; 14 | margin: 0 auto; 15 | -o-object-fit: cover; 16 | object-fit: cover; 17 | } 18 | 19 | .dashboard-user { 20 | text-align: center; 21 | padding: 10px 10px 20px 10px; 22 | height: 20%; 23 | background: white; 24 | border-bottom: 2px solid #bfcad2; 25 | min-width: 150px; 26 | margin-right: 10px; 27 | 28 | .user-module__name { 29 | font-size: 1.5rem; 30 | text-align: center; 31 | margin: 10px 0 5px 0; 32 | } 33 | 34 | .user-module__location { 35 | font-size: .9rem; 36 | margin: 0; 37 | } 38 | } 39 | 40 | .dashboard-main { 41 | width: 90%; 42 | 43 | .search-module { 44 | background: white; 45 | display: -webkit-box; 46 | display: -ms-flexbox; 47 | display: flex; 48 | -webkit-box-orient: vertical; 49 | -webkit-box-direction: normal; 50 | -ms-flex-direction: column; 51 | flex-direction: column; 52 | 53 | .teaser-text { 54 | font-size: 1rem; 55 | margin-top: 0; 56 | padding: 15px 0 20px 10px; 57 | border-bottom: 1px solid #ecf0f1; 58 | } 59 | } 60 | 61 | .city-images { 62 | margin-top: 20px; 63 | padding: 0 10px 15px 10px; 64 | display: -webkit-box; 65 | display: -ms-flexbox; 66 | display: flex; 67 | -ms-flex-wrap: wrap; 68 | flex-wrap: wrap; 69 | -webkit-box-pack: justify; 70 | -ms-flex-pack: justify; 71 | justify-content: space-between; 72 | } 73 | 74 | .city-image { 75 | max-width: 250px; 76 | min-width: 150px; 77 | min-height: 150px; 78 | max-height: 250px; 79 | } 80 | 81 | .city-image img { 82 | width: 100%; 83 | height: 100%; 84 | position: relative; 85 | vertical-align: top; 86 | } 87 | 88 | .browse-all-text { 89 | text-align: center; 90 | color: black; 91 | text-decoration: none; 92 | margin: 30px 0 10px 0; 93 | } 94 | 95 | //Travel Plans Module 96 | .upcoming-guests, 97 | .travel, 98 | .random-locations { 99 | background: white; 100 | height: auto; 101 | margin-top: 7px; 102 | } 103 | 104 | .dash-text { 105 | padding: 15px 20px 20px 20px; 106 | font-size: 12px; 107 | font-weight: bold; 108 | border-bottom: 1px solid #ecf0f1; 109 | line-height: 1.5em; 110 | text-transform: uppercase; 111 | } 112 | 113 | .icons { 114 | padding-right: 10px; 115 | } 116 | 117 | .travel-plans { 118 | height: 50%; 119 | } 120 | 121 | .inside-text { 122 | text-align: center; 123 | background: #fbfcfc; 124 | border-bottom: 2px solid #bfcad2; 125 | 126 | .hostings-list-none, 127 | .bookings-list-none { 128 | padding: 20px; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /frontend/components/header/header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, Route, Redirect, withRouter } from 'react-router-dom'; 3 | import SessionFormContainer from '../session_form/session_form_container'; 4 | import ModalContainer from '../modal/modal_container'; 5 | import { login, logout } from '../../actions/session_actions'; 6 | import { receiveErrors, clearErrors } from '../../actions/error_actions'; 7 | 8 | class Header extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | this.handleClick = this.handleClick.bind(this); 12 | this.clearErrorsOpenModal = this.clearErrorsOpenModal.bind(this); 13 | this.handleDemoClick = this.handleDemoClick.bind(this); 14 | } 15 | 16 | handleClick(e) { 17 | this.props.logout() 18 | } 19 | 20 | handleDemoClick(e) { 21 | this.props.loginGuest({user: {username: 'cthanhvo', password: 'password'}}); 22 | } 23 | 24 | clearErrorsOpenModal(component) { 25 | this.props.clearErrors(); 26 | this.props.openModal(component); 27 | } 28 | 29 | isLoggedIn() { 30 | if (this.props.currentUser) { 31 | return ( 32 |
33 |
34 | 38 | futon flying 39 | 40 |
41 | 45 |
46 |
47 |
48 | //want this to redirect to the homepage 49 | ); 50 | } 51 | } 52 | 53 | notLoggedIn() { 54 | if (!this.props.currentUser) { 55 | return ( 56 |
57 |
58 | 62 | futon flying 63 | 64 |
65 | 71 | 72 | 78 | 79 | 85 |
86 |
87 |
88 | ); 89 | } 90 | } 91 | 92 | render() { 93 | return ( 94 |
95 | {this.isLoggedIn()} 96 | {this.notLoggedIn()} 97 |
98 | ); 99 | } 100 | } 101 | 102 | export default withRouter(Header); 103 | -------------------------------------------------------------------------------- /frontend/components/booking/bookings_form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { clearErrors } from '../../actions/error_actions'; 3 | 4 | class BookingsForm extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | formInfo: { 9 | hostId: this.props.host.id, 10 | startDate: null, 11 | endDate: null, 12 | numGuests: 1 13 | } 14 | }; 15 | this.closeDropdown = this.closeDropdown.bind(this); 16 | this.update = this.update.bind(this); 17 | this.handleSubmit = this.handleSubmit.bind(this); 18 | this.renderErrors = this.renderErrors.bind(this); 19 | } 20 | 21 | closeDropdown() { 22 | this.props.closeDropdown(); 23 | } 24 | 25 | update(e, property) { 26 | const {formInfo} = this.state; 27 | formInfo[property] = e.currentTarget.value; 28 | this.setState({ 29 | formInfo 30 | }); 31 | } 32 | 33 | handleSubmit(e) { 34 | e.preventDefault(); 35 | let booking = this.state.formInfo; 36 | this.props.createBooking(booking).then(() => { 37 | if (!this.state.errors) { 38 | this.props.history.push('/'); 39 | } 40 | }); 41 | } 42 | 43 | renderErrors() { 44 | const {errors} = this.props; 45 | if (errors.length){ 46 | return ( 47 |

48 | {errors.map(error =>

{error}

)} 49 |

50 | ); 51 | } else { 52 | return null; 53 | } 54 | } 55 | 56 | render() { 57 | return ( 58 |
59 |

60 | Request to stay 61 |

62 | 63 |
64 |
65 |
66 | Arrival Date 67 | this.update(e, "startDate")} /> 71 |
72 | 73 |
74 | Departure Date 75 | this.update(e, "endDate")} /> 80 |
81 |
82 | 83 | {this.renderErrors()} 84 | 85 | 86 |
87 | Message 88 | 92 |
93 |
94 | 95 |
96 | 101 | 102 | 107 |
108 |
109 | ); 110 | } 111 | } 112 | 113 | export default BookingsForm; 114 | -------------------------------------------------------------------------------- /frontend/components/hosts/host_index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, Route } from 'react-router-dom'; 3 | import HostIndexDetail from './host_index_detail'; 4 | import HostMapContainer from '../hosts_map/hosts_map_container'; 5 | class HostIndex extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | center: { 10 | lat: window.localStorage.latitude, 11 | lng: window.localStorage.longitude 12 | }, 13 | style: { width: "70vw" }, 14 | mapInFocus: false, 15 | selected: null 16 | }; 17 | this.changeCenter = this.changeCenter.bind(this); 18 | this.getLocation = this.getLocation.bind(this); 19 | } 20 | 21 | componentDidMount() { 22 | this.getLocation(); 23 | this.props.fetchHosts(this.state.center); 24 | 25 | } 26 | 27 | getLocation() { 28 | var getPosition = function (options) { 29 | return new Promise(function (resolve, reject) { 30 | navigator.geolocation.getCurrentPosition(resolve, reject, options); 31 | }); 32 | }; 33 | getPosition() 34 | .then((position) => { 35 | window.localStorage['latitude'] = parseFloat(position.coords.latitude); 36 | window.localStorage['longitude'] = parseFloat(position.coords.longitude); 37 | this.setState({ 38 | center: { 39 | 40 | lat: parseFloat(position.coords.latitude), 41 | lng: parseFloat(position.coords.longitude) 42 | } 43 | }); 44 | }) 45 | .catch((err) => { 46 | return "Using the default coordinates"; 47 | }); 48 | } 49 | 50 | changeCenter(center, selected) { 51 | this.setState({ center, selected }); 52 | } 53 | 54 | searchMap() { 55 | const { center, style } = this.state; 56 | return ( 57 |
58 | 62 |
63 | ); 64 | } 65 | 66 | render() { 67 | const { selected } = this.state; 68 | if (!this.props.hosts) { 69 | return ( 70 |
71 |
72 |

73 | There are no rooms available at this location 74 |

75 |
76 | {this.searchMap()} 77 |
78 | ); 79 | } else { 80 | let hostsList = ( 81 |
82 | {this.props.hosts.map((host) => ( 83 | 91 | ) 92 | )} 93 |
94 | ); 95 | return ( 96 |
97 |
98 |
99 | Available Hosts 100 |
101 | {hostsList} 102 |
103 | {this.searchMap()} 104 |
105 | ); 106 | } 107 | } 108 | } 109 | 110 | export default HostIndex; 111 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # fname :string 7 | # lname :string 8 | # username :string not null 9 | # password_digest :string not null 10 | # session_token :string not null 11 | # bio :text 12 | # created_at :datetime not null 13 | # updated_at :datetime not null 14 | # avatar_file_name :string 15 | # avatar_content_type :string 16 | # avatar_file_size :integer 17 | # avatar_updated_at :datetime 18 | # latitude :float not null 19 | # longitude :float not null 20 | # city :string 21 | # country :string 22 | # accepting_guests :boolean default(TRUE) 23 | # 24 | 25 | class User < ActiveRecord::Base 26 | validates :username, :password_digest, :session_token, presence: true 27 | validates :username, uniqueness: true 28 | validates :longitude, :latitude, :fname, :lname, presence: true 29 | validates :password, length: { minimum: 6, allow_nil: true } 30 | 31 | has_attached_file :avatar, default_url: "https://s3.us-east-2.amazonaws.com/futon-flying-pro/generic-avatar.png" 32 | validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\Z/ 33 | 34 | before_validation :ensure_session_token_uniqueness 35 | after_initialize :ensure_session_token 36 | 37 | 38 | reverse_geocoded_by :latitude, :longitude do |obj,results| 39 | if geo = results.first 40 | obj.city = geo.city 41 | obj.country = geo.country 42 | end 43 | end 44 | after_validation :reverse_geocode 45 | 46 | 47 | has_many :reviews 48 | has_many :bookings 49 | 50 | attr_reader :password 51 | 52 | def self.find_by_credentials(username, password) 53 | @user = User.find_by(username: username) 54 | @user && @user.is_password?(password) ? @user : nil 55 | end 56 | 57 | def password=(password) 58 | @password = password 59 | self.password_digest = BCrypt::Password.create(password) 60 | end 61 | 62 | def is_password?(password) 63 | BCrypt::Password.new(self.password_digest).is_password?(password) 64 | end 65 | 66 | def reset_session_token 67 | self.session_token = SecureRandom.urlsafe_base64(16) 68 | ensure_session_token_uniqueness 69 | self.save 70 | self.session_token 71 | end 72 | 73 | def self.in_bounds(bounds) 74 | self.where("lat < ?", bounds[:northEast][:latitude]) 75 | .where("lat > ?", bounds[:southWest][:latitude]) 76 | .where("lng > ?", bounds[:southWest][:longitude]) 77 | .where("lng < ?", bounds[:northEast][:longitude]) 78 | end 79 | 80 | def user_bookings 81 | Booking.where("guest_id = ? AND end_date >= ?", self.id, Date.today) 82 | end 83 | 84 | def user_hostings 85 | Booking.where("host_id = ? AND end_date >= ?", self.id, Date.today) 86 | end 87 | 88 | def find_bookings 89 | hostings = user_bookings() 90 | guestings = user_hostings() 91 | 92 | return { 93 | hostings: hostings, 94 | guestings: guestings 95 | } 96 | end 97 | 98 | 99 | private 100 | 101 | def ensure_session_token 102 | self.session_token ||= SecureRandom.urlsafe_base64(16) 103 | end 104 | 105 | def ensure_session_token_uniqueness 106 | while User.find_by(session_token: self.session_token) 107 | self.session_token = SecureRandom.urlsafe_base64(16) 108 | end 109 | end 110 | 111 | 112 | # def address= 113 | # self.latitude.to_s + " " + self.longitude.to_s 114 | # end 115 | end 116 | -------------------------------------------------------------------------------- /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 | # Disable serving static files from the `/public` folder by default since 18 | # Apache or NGINX already handles this. 19 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 20 | 21 | # Compress JavaScripts and CSS. 22 | config.assets.js_compressor = :uglifier 23 | # config.assets.css_compressor = :sass 24 | 25 | # Do not fallback to assets pipeline if a precompiled asset is missed. 26 | config.assets.compile = false 27 | 28 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 29 | 30 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 31 | # config.action_controller.asset_host = 'http://assets.example.com' 32 | 33 | # Specifies the header that your server uses for sending files. 34 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 35 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 36 | 37 | # Mount Action Cable outside main process or domain 38 | # config.action_cable.mount_path = nil 39 | # config.action_cable.url = 'wss://example.com/cable' 40 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Use the lowest log level to ensure availability of diagnostic information 46 | # when problems arise. 47 | config.log_level = :debug 48 | 49 | # Prepend all log lines with the following tags. 50 | config.log_tags = [ :request_id ] 51 | 52 | # Use a different cache store in production. 53 | # config.cache_store = :mem_cache_store 54 | 55 | # Use a real queuing backend for Active Job (and separate queues per environment) 56 | # config.active_job.queue_adapter = :resque 57 | # config.active_job.queue_name_prefix = "MyCouchSurfing_#{Rails.env}" 58 | config.action_mailer.perform_caching = false 59 | 60 | # Ignore bad email addresses and do not raise email delivery errors. 61 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 62 | # config.action_mailer.raise_delivery_errors = false 63 | 64 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 65 | # the I18n.default_locale when a translation cannot be found). 66 | config.i18n.fallbacks = true 67 | 68 | # Send deprecation notices to registered listeners. 69 | config.active_support.deprecation = :notify 70 | 71 | # Use default logging formatter so that PID and timestamp are not suppressed. 72 | config.log_formatter = ::Logger::Formatter.new 73 | 74 | # Use a different logger for distributed setups. 75 | # require 'syslog/logger' 76 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 77 | 78 | if ENV["RAILS_LOG_TO_STDOUT"].present? 79 | logger = ActiveSupport::Logger.new(STDOUT) 80 | logger.formatter = config.log_formatter 81 | config.logger = ActiveSupport::TaggedLogging.new(logger) 82 | end 83 | 84 | # Do not dump schema after migrations. 85 | config.active_record.dump_schema_after_migration = false 86 | end 87 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/host-index.scss: -------------------------------------------------------------------------------- 1 | .hosts-search { 2 | width: 100%; 3 | max-height: 90vh; 4 | display: -webkit-box; 5 | display: -ms-flexbox; 6 | display: flex; 7 | -webkit-box-pack: center; 8 | -ms-flex-pack: center; 9 | justify-content: center; 10 | } 11 | 12 | .img-placeholder { 13 | color: black; 14 | margin-top: auto; 15 | margin-bottom: auto; 16 | font-size: 2.5em; 17 | padding-right: 10px; 18 | } 19 | 20 | .host-index-photo { 21 | width: 75px; 22 | height: 75px; 23 | -o-object-fit: cover; 24 | object-fit: cover; 25 | border-radius: 50%; 26 | } 27 | 28 | .hosts-index { 29 | width: 70%; 30 | background: white; 31 | margin-right: 3px; 32 | overflow: scroll; 33 | height: 89vh; 34 | } 35 | 36 | .no-hosts { 37 | width: 55%; 38 | background: white; 39 | margin-right: 3px; 40 | } 41 | 42 | .google-map { 43 | background: white; 44 | padding: 5px; 45 | } 46 | 47 | .hosts-available { 48 | margin: 10px 5px; 49 | text-align: center; 50 | } 51 | 52 | .host-index-module { 53 | display: -webkit-box; 54 | display: -ms-flexbox; 55 | display: flex; 56 | padding: 5px 10px; 57 | border-bottom: 1px solid #dbddde; 58 | 59 | &:hover { 60 | -webkit-transition: .3s; 61 | -o-transition: .3s; 62 | transition: .3s; 63 | background-color: darken(white, 5); 64 | } 65 | } 66 | 67 | .host-index-details { 68 | display: -webkit-box; 69 | display: -ms-flexbox; 70 | display: flex; 71 | width: 100%; 72 | -webkit-box-pack: justify; 73 | -ms-flex-pack: justify; 74 | justify-content: space-between; 75 | cursor: pointer; 76 | } 77 | 78 | .host-index-info { 79 | padding: 10px 5px; 80 | } 81 | 82 | .host-index-name { 83 | padding-top: 10px; 84 | font-size: 14px; 85 | font-weight: bold; 86 | } 87 | 88 | .host-index-location { 89 | font-size: 12px; 90 | } 91 | 92 | .index-accepting-guests { 93 | padding-top: 20px; 94 | text-align: center; 95 | font-size: .9rem; 96 | font-weight: bold; 97 | line-height: 1.25em; 98 | right: 0; 99 | } 100 | 101 | .guest-indicator { 102 | width: 70px; 103 | -ms-flex-item-align: center; 104 | -ms-grid-row-align: center; 105 | align-self: center; 106 | text-align: left; 107 | font-size: .9rem; 108 | font-weight: bold; 109 | margin: 0 20px; 110 | } 111 | 112 | .accepting { 113 | color: #41b866; 114 | } 115 | 116 | .not-accepting { 117 | color: #ff3f2e; 118 | } 119 | 120 | // This is for the blurbs 121 | #map-canvas { 122 | margin: 0; 123 | padding: 0; 124 | height: 400px; 125 | max-width: none; 126 | } 127 | 128 | #map-canvas img { 129 | max-width: none !important; 130 | } 131 | 132 | #iw-container .iw-title { 133 | font-family: 'Open Sans Condensed', sans-serif; 134 | font-size: 22px; 135 | font-weight: 400; 136 | padding: 10px 10px 0 10px; 137 | color: white; 138 | margin: 0; 139 | border-radius: 2px 2px 0 0; 140 | } 141 | 142 | #iw-container .iw-content { 143 | font-size: 13px; 144 | line-height: 18px; 145 | font-weight: 400; 146 | margin-right: 1px; 147 | padding: 15px 5px 20px 15px; 148 | max-height: 140px; 149 | overflow-y: auto; 150 | overflow-x: hidden; 151 | } 152 | 153 | .iw-content img { 154 | float: right; 155 | margin: 0 5px 5px 10px; 156 | } 157 | 158 | .iw-subTitle { 159 | font-size: 16px; 160 | font-weight: 700; 161 | padding: 5px 0; 162 | } 163 | 164 | .iw-name-link { 165 | text-decoration: none; 166 | color: black; 167 | } 168 | 169 | .iw-title hr { 170 | margin-top: 5px; 171 | border: 1px solid #f1efea; 172 | } 173 | 174 | .fa-comment-alt { 175 | font-size: 1.5rem; 176 | } 177 | 178 | .accepting { 179 | color: green; 180 | } 181 | 182 | .not-accepting { 183 | color: red; 184 | } 185 | -------------------------------------------------------------------------------- /frontend/components/hosts_map/hosts_map.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { withRouter } from 'react-router-dom'; 4 | import MarkerManager from '../../util/marker_manager'; 5 | 6 | const getCoordsObj = latLng => { 7 | return { 8 | latitude: latLng.lat(), 9 | longitude: latLng.lng() 10 | }; 11 | }; 12 | 13 | const mapOptions = { 14 | center: { 15 | lat: parseFloat(window.localStorage.latitude), 16 | lng: parseFloat(window.localStorage.longitude) 17 | }, 18 | zoom: 12, 19 | scrollwheel: true 20 | }; 21 | 22 | class HostMap extends React.Component { 23 | constructor(props) { 24 | super(props); 25 | this.state = { 26 | hosts: this.props.hosts, 27 | style: this.props.style, 28 | previousMarker: null, 29 | location: mapOptions.center 30 | }; 31 | this.renderHosts = this.renderHosts.bind(this); 32 | this.renderChatOption = this.renderChatOption.bind(this); 33 | } 34 | 35 | componentDidMount(props) { 36 | if (this.props.center) { 37 | mapOptions.center = this.props.center; 38 | } 39 | if (window.localStorage.latitude) { 40 | mapOptions.center = {lat: parseFloat(window.localStorage.latitude),lng: parseFloat(window.localStorage.longitude)}; 41 | } 42 | this.map = new google.maps.Map(this.mapNode, mapOptions); 43 | this.map.addListener('idle',() => { 44 | let location = { lat: this.map.getCenter().lat(), lng: this.map.getCenter().lng() }; 45 | if (this.state.location.lat != location.lat){ 46 | this.setState({ location }); 47 | } 48 | }); 49 | } 50 | 51 | renderChatOption(host) { 52 | if (host.accepting_guests) { 53 | return ''; 54 | } else { 55 | return ''; 56 | } 57 | } 58 | 59 | renderHosts(hosts, map) { 60 | if (hosts.length) { 61 | hosts.forEach(host => { 62 | let hostBio = host.bio; 63 | if (!hostBio) { 64 | hostBio = `${host.fname} has not added a bio yet!`; 65 | } 66 | let contentString = `
67 | 74 | 75 |
76 |
77 | About Me 78 |
79 | ${host.fname}_picture height= 80 |

${hostBio}

81 |
`; 82 | const { latitude, longitude } = host; 83 | 84 | let marker = new google.maps.Marker({ 85 | position: { lat: latitude, lng: longitude }, 86 | map, 87 | icon: "https://i.imgur.com/1kFqqW8.png" 88 | }); 89 | let infowindow = new google.maps.InfoWindow({ 90 | content: contentString, 91 | title: host.city 92 | }); 93 | marker.addListener('click', function () { 94 | if (this.state.previousMarker) { 95 | this.state.previousMarker.close(); 96 | } 97 | infowindow.open(map, marker); 98 | this.setState({ previousMarker: infowindow }); 99 | }.bind(this)); 100 | 101 | google.maps.event.addListener(map, 'click', function () { 102 | infowindow.close(); 103 | }); 104 | }); 105 | } 106 | } 107 | componentWillUpdate(nextProps, nextState) { 108 | if (this.props.center !== nextProps.center) { 109 | this.map.setZoom(12); 110 | this.map.panTo(nextProps.center); 111 | this.props.fetchHosts(nextProps.center); 112 | if (this.state.hosts !== nextProps.hosts) { 113 | this.renderHosts(nextProps.hosts, this.map); 114 | } 115 | } 116 | if (this.state.location !== nextState.location) { 117 | this.map.panTo(nextState.location); 118 | this.props.fetchHosts(nextState.location); 119 | if (this.state.hosts !== nextProps.hosts) { 120 | this.renderHosts(nextProps.hosts, this.map); 121 | } 122 | } 123 | if (this.state.style !== nextProps.style) { 124 | this.setState({ style: nextProps.style }); 125 | } 126 | } 127 | render() { 128 | return ( 129 |
this.mapNode = map} 132 | style={this.state.style} /> 133 | ); 134 | } 135 | } 136 | export default withRouter(HostMap); 137 | -------------------------------------------------------------------------------- /frontend/components/splash_screen/splash_screen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Link } from 'react-router-dom'; 3 | 4 | class SplashScreen extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | render () { 10 | return ( 11 |
12 |
13 |

14 | Stay With Locals and Meet Fellow Travelers 15 |

16 |

17 | Share your experiences. 18 |

19 |
20 | 21 |
22 |

23 | How it Works 24 |

25 | 26 |
27 | 28 |
29 |
30 | 31 |

Meet Amazing People

32 |
33 |

34 | Futonfliers open their homes and share their lives. Be connected. 35 |

36 |
37 | 38 |
39 | 40 |

Find a Host

41 | 42 |
43 | 44 |

45 | Connect with hosts and confirm your stay through FutonFlying. Your stay with hosts is free! 46 |

47 |
48 | 49 |
50 |
51 |

Top Futonflying Destinations

52 | 53 |
54 |
55 | Los Angeles 58 |
59 | Los Angeles 60 |
61 |
62 | 63 |
64 | Copenhagen 68 |
69 | Copenhagen 70 |
71 |
72 | 73 |
74 | Vienna 78 |
79 | Vienna 80 |
81 |
82 | 83 |
84 | Lyon 88 |
89 | Lyon 90 |
91 |
92 | 93 | 94 |
95 | Ha Long Bay 99 |
100 | Ha Long Bay 101 |
102 |
103 |
104 | London 108 |
109 | London 110 |
111 |
112 | 113 |
114 | New York 118 |
119 | New York 120 |
121 |
122 | 123 |
124 | Berlin 128 |
129 | Berlin 130 |
131 |
132 | 133 |
134 | Porto 138 |
139 | Porto 140 |
141 |
142 | 143 |
144 | Rome 148 |
149 | Rome 150 |
151 |
152 | 153 |
154 | Budapest 158 |
159 | Budapest 160 |
161 |
162 | 163 |
164 |
165 | ); 166 | } 167 | } 168 | 169 | export default SplashScreen; 170 | 171 | 172 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/splash.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f1efea; 3 | margin: 0; 4 | } 5 | 6 | .welcome-overlay { 7 | background: -webkit-gradient(linear, left top, left bottom, color-stop(30%, rgba(5, 5, 2, 0.7)), to(transparent)), url("https://s3.us-east-2.amazonaws.com/futon-flying-pro/welcome.jpg") (center / cover); 8 | background: -webkit-linear-gradient(rgba(5, 5, 2, 0.7) 30%, transparent), url("https://s3.us-east-2.amazonaws.com/futon-flying-pro/welcome.jpg") (center / cover); 9 | background: -o-linear-gradient(rgba(5, 5, 2, 0.7) 30%, transparent), url("https://s3.us-east-2.amazonaws.com/futon-flying-pro/welcome.jpg") (center / cover); 10 | background: linear-gradient(rgba(5, 5, 2, 0.7) 30%, transparent), url("https://s3.us-east-2.amazonaws.com/futon-flying-pro/welcome.jpg") (center / cover); 11 | height: 85vh; 12 | width: 100%; 13 | 14 | p { 15 | color: white; 16 | text-align: center; 17 | } 18 | 19 | .overlay-text { 20 | font-size: 3rem; 21 | padding-top: 300px; 22 | font-weight: 300; 23 | margin: 0; 24 | } 25 | 26 | .overlay-subtext { 27 | font-size: 2rem; 28 | font-weight: 100; 29 | margin-top: 0px; 30 | } 31 | } 32 | 33 | .how-it-works { 34 | margin: 10px 0; 35 | padding: 50px 0; 36 | height: 70vh; 37 | background: #ed6504; 38 | 39 | p { 40 | color: white; 41 | text-align: center; 42 | padding: 0 5px; 43 | font-size: .9rem; 44 | line-height: 1.3rem; 45 | } 46 | 47 | .hiw-text { 48 | font-size: 3rem; 49 | font-weight: 100; 50 | margin-bottom: 7px; 51 | } 52 | 53 | .hiw-info { 54 | display: -webkit-box; 55 | display: -ms-flexbox; 56 | display: flex; 57 | padding-top: 30px; 58 | width: 40%; 59 | margin: 0 auto; 60 | } 61 | 62 | .hiw-discover, 63 | .hiw-find { 64 | height: 200px; 65 | background: #ff855c; 66 | margin: 5px; 67 | max-width: 400px; 68 | min-height: 300px; 69 | } 70 | 71 | .hiw-discover img, 72 | .hiw-find img { 73 | height: 40%; 74 | margin: 0 auto; 75 | padding-top: 5px; 76 | display: block; 77 | } 78 | 79 | .short-underline { 80 | padding-top: 10px; 81 | margin: 0 auto; 82 | width: 50px; 83 | border-bottom: 2px solid white; 84 | } 85 | 86 | .shorter-underline { 87 | padding-top: 5px; 88 | margin: 0 auto; 89 | width: 30px; 90 | border-bottom: 2px solid white; 91 | } 92 | } 93 | 94 | .tgp-header { 95 | color: #34495e; 96 | text-align: center; 97 | font-size: 2.5rem; 98 | font-weight: 300; 99 | margin: 20px 0; 100 | } 101 | 102 | .the-grand-piece { 103 | margin: 0 auto; 104 | display: -ms-grid; 105 | display: grid; 106 | -ms-grid-columns: auto [4]; 107 | grid-template-columns: repeat(4, auto); 108 | -ms-grid-rows: 175px [4]; 109 | grid-template-rows: repeat(4, 175px); 110 | grid-gap: 5px; 111 | height: 80vh; 112 | width: 80vw; 113 | grid-template-areas: "los-angeles lyon ha-long ha-long" "los-angeles copenhagen berlin london" "porto porto rome london" "vienna budapest rome new-york"; 114 | 115 | img { 116 | width: 100%; 117 | height: 100%; 118 | } 119 | 120 | .tgp-city { 121 | display: block; 122 | position: relative; 123 | overflow: hidden; 124 | 125 | img { 126 | display: block; 127 | -webkit-transition: all .3s; 128 | -o-transition: all .3s; 129 | transition: all .3s; 130 | } 131 | 132 | .tgp-text { 133 | -webkit-transition: all .3s; 134 | -o-transition: all .3s; 135 | transition: all .3s; 136 | opacity: 0; 137 | position: absolute; 138 | top: 50%; 139 | width: 100%; 140 | text-align: center; 141 | letter-spacing: 1px; 142 | font-size: 1.5rem; 143 | color: white; 144 | } 145 | 146 | &:hover { 147 | -webkit-filter: grayscale(1); 148 | filter: grayscale(1); 149 | 150 | img { 151 | -webkit-transform: scale(1.1); 152 | -ms-transform: scale(1.1); 153 | transform: scale(1.1); 154 | } 155 | 156 | .tgp-text { 157 | opacity: 1; 158 | } 159 | } 160 | } 161 | 162 | .p1 { 163 | -ms-grid-row: 1; 164 | -ms-grid-row-span: 2; 165 | -ms-grid-column: 1; 166 | grid-area: los-angeles; 167 | } 168 | 169 | .p2 { 170 | -ms-grid-row: 2; 171 | -ms-grid-column: 2; 172 | grid-area: copenhagen; 173 | } 174 | 175 | .p3 { 176 | -ms-grid-row: 4; 177 | -ms-grid-column: 1; 178 | grid-area: vienna; 179 | } 180 | 181 | .p4 { 182 | -ms-grid-row: 1; 183 | -ms-grid-column: 2; 184 | grid-area: lyon; 185 | } 186 | 187 | .p5 { 188 | -ms-grid-row: 1; 189 | -ms-grid-column: 3; 190 | -ms-grid-column-span: 2; 191 | grid-area: ha-long; 192 | } 193 | 194 | .p6 { 195 | -ms-grid-row: 2; 196 | -ms-grid-row-span: 2; 197 | -ms-grid-column: 4; 198 | grid-area: london; 199 | } 200 | 201 | .p7 { 202 | -ms-grid-row: 4; 203 | -ms-grid-column: 4; 204 | grid-area: new-york; 205 | } 206 | 207 | .p8 { 208 | -ms-grid-row: 2; 209 | -ms-grid-column: 3; 210 | grid-area: berlin; 211 | } 212 | 213 | .p9 { 214 | -ms-grid-row: 3; 215 | -ms-grid-column: 1; 216 | -ms-grid-column-span: 2; 217 | grid-area: porto; 218 | } 219 | 220 | .p10 { 221 | -ms-grid-row: 3; 222 | -ms-grid-row-span: 2; 223 | -ms-grid-column: 3; 224 | grid-area: rome; 225 | } 226 | 227 | .p11 { 228 | -ms-grid-row: 4; 229 | -ms-grid-column: 2; 230 | grid-area: budapest; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /frontend/components/session_form/session_form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from '../header/header'; 3 | import SessionFormContainer from './session_form_container'; 4 | import ModalContainer from '../modal/modal_container'; 5 | import { clearErrors } from '../../actions/error_actions'; 6 | 7 | class SessionForm extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | username: '', 12 | password: '', 13 | latitude: window.localStorage['latitude'], 14 | longitude: window.localStorage['longitude'] 15 | }; 16 | this.handleSubmit = this.handleSubmit.bind(this); 17 | this.clearErrorsOpenModal = this.clearErrorsOpenModal.bind(this); 18 | this.handleClick = this.handleClick.bind(this); 19 | } 20 | 21 | handleClick(e) { 22 | this.props.closeModal(); 23 | } 24 | 25 | stopPropagation(e) { 26 | e.stopPropagation(); 27 | } 28 | 29 | update(property) { 30 | return e => this.setState({ 31 | [property]: e.currentTarget.value 32 | }); 33 | } 34 | 35 | clearErrorsOpenModal(component) { 36 | this.props.clearErrors(); 37 | this.props.openModal(component); 38 | } 39 | 40 | handleSubmit(e) { 41 | e.preventDefault(); 42 | const user = Object.assign({},this.state); 43 | if (this.props.formType === 'login') { 44 | this.props.login({ user }).then(this.props.closeModal); 45 | } else if (this.props.formType === 'signup') { 46 | this.props.signup({ user }).then(this.props.closeModal); 47 | } 48 | this.setState({username: '', password: '', fname: '', lname: ''}) 49 | } 50 | 51 | handleName() { 52 | if (this.props.formType === 'signup') { 53 | return ( 54 |
55 | 62 | 63 | 70 |
71 | ); 72 | } 73 | } 74 | 75 | renderErrors() { 76 | return ( 77 |
    78 | {this.props.errors.map((error, i) => ( 79 |
  • 80 | {error} 81 |
  • 82 | ))} 83 |
84 | ); 85 | } 86 | 87 | modalToRender() { 88 | if (this.props.formType === 'login') { 89 | return( 90 |
91 |
92 |
93 | Log in to Futon Flying 94 |
95 | X 96 |
97 |
98 |
99 | ); 100 | } else if (this.props.formType === 'signup'){ 101 | return( 102 |
103 |
104 |
105 | Join Futonflying for free 106 |
107 | X 108 |
109 |
110 |
111 | ) 112 | } 113 | } 114 | 115 | renderButton() { 116 | if (this.props.formType === 'signup') { 117 | return ( 118 | 123 | ); 124 | } else if (this.props.formType === 'login'){ 125 | return ( 126 | 131 | ); 132 | } 133 | } 134 | 135 | switchForm() { 136 | if (this.props.formType === 'signup') { 137 | return ( 138 |
139 |
140 | Already a member? 141 | {/*
*/} 142 | 149 | {/*
*/} 150 |
151 |
152 | ); 153 | } else if (this.props.formType === 'login') { 154 | return ( 155 |
156 |
157 | Don't have an account? 158 | {/*
*/} 159 | 166 | {/*
*/} 167 |
168 |
169 | ) 170 | } 171 | } 172 | 173 | render() { 174 | return ( 175 |
176 |
177 |
178 | {this.modalToRender()} 179 | {this.renderErrors()} 180 | {this.handleName()} 181 |
182 | 189 | 196 | {this.renderButton()} 197 | {this.switchForm()} 198 |
199 |
200 |
201 |
202 | ); 203 | } 204 | } 205 | 206 | export default SessionForm; 207 | -------------------------------------------------------------------------------- /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 rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | 9 | require 'faker' 10 | 11 | men = File.read('db/men.json') 12 | ladies = File.read('db/ladies.json') 13 | unified = File.read('db/unified.json') 14 | men_hash = JSON.parse(men)["results"] 15 | ladies_hash = JSON.parse(ladies)["results"] 16 | unified_hash = JSON.parse(unified)["results"] 17 | 18 | user1 = User.create!( 19 | username: "cthanhvo", 20 | password: "password", 21 | fname: "Can", 22 | lname: "Vo", 23 | latitude: 10.11427, 24 | longitude: 106.22552, 25 | bio: "This is for my homeboy Wadah", 26 | avatar: "https://s3.us-east-2.amazonaws.com/futon-flying-pro/ctvo.jpg" 27 | ) 28 | 29 | def get_location(entry) 30 | street = entry['location']['street'].split(" ").join("+") 31 | city = entry['location']['city'].split(" ").join("+") 32 | state = entry['location']['state'].split(" ").join("+") 33 | address = [ActiveSupport::Inflector.transliterate(city), ActiveSupport::Inflector.transliterate(state)].join(",") 34 | url = "https://maps.googleapis.com/maps/api/geocode/json?address=#{address}&key=#{ENV["google_key"]}" 35 | data = JSON.parse(open(url).read) 36 | return [] if data['status'] == 'ZERO_RESULTS' 37 | begin 38 | location = { 39 | latitude: data['results'][0]['geometry']['location']['lat'], 40 | longitude: data['results'][0]['geometry']['location']['lng'], 41 | country: data['results'][0]['address_components'][-2]['long_name'], 42 | city: data['results'][0]['address_components'][2]['long_name'] 43 | } 44 | rescue 45 | puts "error adding user" 46 | end 47 | 48 | end 49 | 50 | unified_hash[0..50].each do |user| 51 | location = get_location(user) 52 | next if location.empty? 53 | puts location['latitude'] 54 | puts location['longitude'] 55 | User.create!( 56 | latitude: location[:latitude], 57 | longitude: location[:longitude], 58 | fname: user['name']['first'].capitalize, 59 | lname: user['name']['last'].capitalize, 60 | city: location[:city], 61 | country: location[:country], 62 | accepting_guests: Faker::Boolean.boolean, 63 | avatar: user['picture']["large"], 64 | bio: Faker::Lorem.paragraph, 65 | username: Faker::Internet.user_name, 66 | password_digest: Faker::Internet.password(8) 67 | ) 68 | 69 | end 70 | 71 | user8 = User.create!( 72 | latitude: 40.418639, 73 | longitude: -3.704602, 74 | password: Faker::Internet.password(8), 75 | username: Faker::Internet.user_name, 76 | fname: "Paulina", 77 | lname: "Rutkowska", 78 | city: "Madrid", 79 | country: "Spain", 80 | accepting_guests: true, 81 | avatar: ladies_hash[0]['picture']["large"], 82 | bio: "En este 2017 con mi compañera y amor decidimos salir de las sierras de Córdoba Argentina, para recorrer el continente Europeo y Norte Africano. Ambos somos trabajadores del arte y la cultura, este viaje es una especie de peregrinación en que cada pasa es un enriquecimiento sobre cultura, paisaje e historia. Estamos produciendo obra y compartiendo. Personalmente me dedico mas al audiovisual y Lucía a la caracterización de personajes para obras de opera y para dibujo." 83 | ) 84 | 85 | user2 = User.create!( 86 | latitude: 5.092167, 87 | longitude: 1.317893, 88 | password: Faker::Internet.password(8), 89 | username: Faker::Internet.user_name, 90 | fname: "Robbie", 91 | lname: "Miles", 92 | city: "Woodbridge", 93 | country: "United Kingdom", 94 | accepting_guests: true, 95 | avatar: 'https://s3.us-east-2.amazonaws.com/futon-flying-pro/robbie.jpg', 96 | bio: "Ñaaaaaaaaaaaa!!!! Heeeello everyone!!! My name is Robbie and im 25. I'm from Spain but I currently live in streatham hill, London. I'm working as a registered nurse in a hospital making smile, helping and providing loveeee to people haha!!! I'm actually studing a degree in law at the same time for change the change in the world and try to help people in a bigger way! I'm a very happy, funny, positive, optimistic and social guy that always keeps his smile very high!!! :) I definitelly can not spend any second of my life doing nothing or not sharing a good moment with someone. I'm a very friendlyyy dude, lets just met and hang out so you can see whaaaat I am taaaalking abooouuuut! Love is always the answer!" 97 | ) 98 | 99 | user3 = User.create!( 100 | latitude: 48.178217, 101 | longitude: 16.326814, 102 | password: Faker::Internet.password(8), 103 | username: Faker::Internet.user_name, 104 | fname: 'Hannah', 105 | lname: 'Beck', 106 | city: 'Vienna', 107 | country: 'Austria', 108 | accepting_guests: true, 109 | avatar: ladies_hash[1]['picture']["large"], 110 | bio: "I'm and Austro-American who grew up in Austria. I spent my High School years in Tirana Albania where I also graduated, and met the love of my life. We both live in Rome, Italy together and I'm now starting to study Performing Arts in October. I love traveling, so when I heard about CouchSurfing I wanted to do my part to make traveling on low budget easier for everyone. We hope to meet lots of friendly new faces and be able to call as many as possible our friends. We'd be glad to hang out around town, go to museums, listen to live music, or just crack open a beer. Flexibility is important.I'm and Austro-American who grew up in Austria. I spent my High School years in Tirana Albania where I also graduated, and met the love of my life. We both live in Rome, Italy together and I'm now starting to study Performing Arts in October. I love traveling, so when I heard about CouchSurfing I wanted to do my part to make traveling on low budget easier for everyone. We hope to meet lots of friendly new faces and be able to call as many as possible our friends. We'd be glad to hang out around town, go to museums, listen to live music, or just crack open a beer. Flexibility is important." 111 | ) 112 | 113 | user4 = User.create!( 114 | latitude: 55.679553, 115 | longitude: 12.585516, 116 | password: Faker::Internet.password(8), 117 | username: Faker::Internet.user_name, 118 | fname: 'Wen Bo', 119 | lname: 'Xie', 120 | city: 'Copenhagen', 121 | country: 'Denmark', 122 | accepting_guests: false, 123 | avatar: 'https://s3.us-east-2.amazonaws.com/futon-flying-pro/wenbo.jpg', 124 | bio: "I'm Wen Bo" 125 | ) 126 | 127 | user5 = User.create!( 128 | latitude: 43.124499, 129 | longitude: -78.799183, 130 | password: Faker::Internet.password(8), 131 | username: Faker::Internet.user_name, 132 | fname: 'Kevin', 133 | lname: 'Garvey', 134 | city: 'Mapleton', 135 | country: 'New York', 136 | accepting_guests: true, 137 | avatar: men_hash[0]['picture']["large"], 138 | 139 | bio: "I'm and Austro-American who grew up in Austria. I spent my High School years in Tirana Albania where I also graduated, and met the love of my life. We both live in Rome, Italy together and I'm now starting to study Performing Arts in October. I love traveling, so when I heard about CouchSurfing I wanted to do my part to make traveling on low budget easier for everyone. We hope to meet lots of friendly new faces and be able to call as many as possible our friends. We'd be glad to hang out around town, go to museums, listen to live music, or just crack open a beer. Flexibility is important.I'm and Austro-American who grew up in Austria. I spent my High School years in Tirana Albania where I also graduated, and met the love of my life. We both live in Rome, Italy together and I'm now starting to study Performing Arts in October. I love traveling, so when I heard about CouchSurfing I wanted to do my part to make traveling on low budget easier for everyone. We hope to meet lots of friendly new faces and be able to call as many as possible our friends. We'd be glad to hang out around town, go to museums, listen to live music, or just crack open a beer. Flexibility is important." 140 | ) 141 | 142 | user6 = User.create!( 143 | latitude: 43.703215, 144 | longitude: -79.403278, 145 | password: Faker::Internet.password(8), 146 | username: Faker::Internet.user_name, 147 | fname: 'James', 148 | lname: 'Somers', 149 | city: 'Toronto', 150 | country: 'Canada', 151 | accepting_guests: true, 152 | avatar: men_hash[1]['picture']["large"], 153 | 154 | bio: "We live on a farm in the Napa Valley. We have sheep, chickens, rabbits, turkeys, Australian Cattle Dogs, cats and one goofy Labradoodle. We have a large vegetable garden, fruit and nut trees and a Cabernet Sauvignon vineyard. We sell produce from the farm directly and give away any oversupply to the local food bank. We welcome day visitors to the farm and love sharing this beautiful area with cityfolk. Kirsten (thats's me)is a massage thearpist which allows her time to do lots of other things in life like farming and volunteering on the board of the Napa Valley Marathon. Ron has recently relocated to Napa from Reno. He has a business background and is now trying to figure out what he wants to be in his new life. 155 | Our dogs are our children although we enjoy visits from our nieces and any other children who would like to experience the farm." 156 | ) 157 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.0.6) 5 | actionpack (= 5.0.6) 6 | nio4r (>= 1.2, < 3.0) 7 | websocket-driver (~> 0.6.1) 8 | actionmailer (5.0.6) 9 | actionpack (= 5.0.6) 10 | actionview (= 5.0.6) 11 | activejob (= 5.0.6) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.0.6) 15 | actionview (= 5.0.6) 16 | activesupport (= 5.0.6) 17 | rack (~> 2.0) 18 | rack-test (~> 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.0.6) 22 | activesupport (= 5.0.6) 23 | builder (~> 3.1) 24 | erubis (~> 2.7.0) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.0.6) 28 | activesupport (= 5.0.6) 29 | globalid (>= 0.3.6) 30 | activemodel (5.0.6) 31 | activesupport (= 5.0.6) 32 | activerecord (5.0.6) 33 | activemodel (= 5.0.6) 34 | activesupport (= 5.0.6) 35 | arel (~> 7.0) 36 | activesupport (5.0.6) 37 | concurrent-ruby (~> 1.0, >= 1.0.2) 38 | i18n (~> 0.7) 39 | minitest (~> 5.1) 40 | tzinfo (~> 1.1) 41 | annotate (2.7.2) 42 | activerecord (>= 3.2, < 6.0) 43 | rake (>= 10.4, < 13.0) 44 | arel (7.1.4) 45 | aws-partitions (1.68.0) 46 | aws-sdk (3.0.1) 47 | aws-sdk-resources (~> 3) 48 | aws-sdk-acm (1.3.0) 49 | aws-sdk-core (~> 3) 50 | aws-sigv4 (~> 1.0) 51 | aws-sdk-alexaforbusiness (1.1.0) 52 | aws-sdk-core (~> 3) 53 | aws-sigv4 (~> 1.0) 54 | aws-sdk-apigateway (1.9.0) 55 | aws-sdk-core (~> 3) 56 | aws-sigv4 (~> 1.0) 57 | aws-sdk-applicationautoscaling (1.8.0) 58 | aws-sdk-core (~> 3) 59 | aws-sigv4 (~> 1.0) 60 | aws-sdk-applicationdiscoveryservice (1.1.0) 61 | aws-sdk-core (~> 3) 62 | aws-sigv4 (~> 1.0) 63 | aws-sdk-appstream (1.6.0) 64 | aws-sdk-core (~> 3) 65 | aws-sigv4 (~> 1.0) 66 | aws-sdk-appsync (1.1.0) 67 | aws-sdk-core (~> 3) 68 | aws-sigv4 (~> 1.0) 69 | aws-sdk-athena (1.0.1) 70 | aws-sdk-core (~> 3) 71 | aws-sigv4 (~> 1.0) 72 | aws-sdk-autoscaling (1.5.0) 73 | aws-sdk-core (~> 3) 74 | aws-sigv4 (~> 1.0) 75 | aws-sdk-autoscalingplans (1.1.0) 76 | aws-sdk-core (~> 3) 77 | aws-sigv4 (~> 1.0) 78 | aws-sdk-batch (1.3.0) 79 | aws-sdk-core (~> 3) 80 | aws-sigv4 (~> 1.0) 81 | aws-sdk-budgets (1.5.0) 82 | aws-sdk-core (~> 3) 83 | aws-sigv4 (~> 1.0) 84 | aws-sdk-cloud9 (1.1.0) 85 | aws-sdk-core (~> 3) 86 | aws-sigv4 (~> 1.0) 87 | aws-sdk-clouddirectory (1.1.0) 88 | aws-sdk-core (~> 3) 89 | aws-sigv4 (~> 1.0) 90 | aws-sdk-cloudformation (1.3.0) 91 | aws-sdk-core (~> 3) 92 | aws-sigv4 (~> 1.0) 93 | aws-sdk-cloudfront (1.1.0) 94 | aws-sdk-core (~> 3) 95 | aws-sigv4 (~> 1.0) 96 | aws-sdk-cloudhsm (1.3.0) 97 | aws-sdk-core (~> 3) 98 | aws-sigv4 (~> 1.0) 99 | aws-sdk-cloudhsmv2 (1.1.0) 100 | aws-sdk-core (~> 3) 101 | aws-sigv4 (~> 1.0) 102 | aws-sdk-cloudsearch (1.0.1) 103 | aws-sdk-core (~> 3) 104 | aws-sigv4 (~> 1.0) 105 | aws-sdk-cloudsearchdomain (1.0.1) 106 | aws-sdk-core (~> 3) 107 | aws-sigv4 (~> 1.0) 108 | aws-sdk-cloudtrail (1.0.1) 109 | aws-sdk-core (~> 3) 110 | aws-sigv4 (~> 1.0) 111 | aws-sdk-cloudwatch (1.4.0) 112 | aws-sdk-core (~> 3) 113 | aws-sigv4 (~> 1.0) 114 | aws-sdk-cloudwatchevents (1.1.0) 115 | aws-sdk-core (~> 3) 116 | aws-sigv4 (~> 1.0) 117 | aws-sdk-cloudwatchlogs (1.2.0) 118 | aws-sdk-core (~> 3) 119 | aws-sigv4 (~> 1.0) 120 | aws-sdk-codebuild (1.6.0) 121 | aws-sdk-core (~> 3) 122 | aws-sigv4 (~> 1.0) 123 | aws-sdk-codecommit (1.3.0) 124 | aws-sdk-core (~> 3) 125 | aws-sigv4 (~> 1.0) 126 | aws-sdk-codedeploy (1.3.0) 127 | aws-sdk-core (~> 3) 128 | aws-sigv4 (~> 1.0) 129 | aws-sdk-codepipeline (1.1.0) 130 | aws-sdk-core (~> 3) 131 | aws-sigv4 (~> 1.0) 132 | aws-sdk-codestar (1.1.0) 133 | aws-sdk-core (~> 3) 134 | aws-sigv4 (~> 1.0) 135 | aws-sdk-cognitoidentity (1.0.1) 136 | aws-sdk-core (~> 3) 137 | aws-sigv4 (~> 1.0) 138 | aws-sdk-cognitoidentityprovider (1.3.0) 139 | aws-sdk-core (~> 3) 140 | aws-sigv4 (~> 1.0) 141 | aws-sdk-cognitosync (1.0.1) 142 | aws-sdk-core (~> 3) 143 | aws-sigv4 (~> 1.0) 144 | aws-sdk-comprehend (1.0.0) 145 | aws-sdk-core (~> 3) 146 | aws-sigv4 (~> 1.0) 147 | aws-sdk-configservice (1.6.0) 148 | aws-sdk-core (~> 3) 149 | aws-sigv4 (~> 1.0) 150 | aws-sdk-core (3.17.0) 151 | aws-partitions (~> 1.0) 152 | aws-sigv4 (~> 1.0) 153 | jmespath (~> 1.0) 154 | aws-sdk-costandusagereportservice (1.0.1) 155 | aws-sdk-core (~> 3) 156 | aws-sigv4 (~> 1.0) 157 | aws-sdk-costexplorer (1.1.0) 158 | aws-sdk-core (~> 3) 159 | aws-sigv4 (~> 1.0) 160 | aws-sdk-databasemigrationservice (1.4.0) 161 | aws-sdk-core (~> 3) 162 | aws-sigv4 (~> 1.0) 163 | aws-sdk-datapipeline (1.0.1) 164 | aws-sdk-core (~> 3) 165 | aws-sigv4 (~> 1.0) 166 | aws-sdk-dax (1.0.1) 167 | aws-sdk-core (~> 3) 168 | aws-sigv4 (~> 1.0) 169 | aws-sdk-devicefarm (1.3.0) 170 | aws-sdk-core (~> 3) 171 | aws-sigv4 (~> 1.0) 172 | aws-sdk-directconnect (1.1.0) 173 | aws-sdk-core (~> 3) 174 | aws-sigv4 (~> 1.0) 175 | aws-sdk-directoryservice (1.1.0) 176 | aws-sdk-core (~> 3) 177 | aws-sigv4 (~> 1.0) 178 | aws-sdk-dynamodb (1.4.0) 179 | aws-sdk-core (~> 3) 180 | aws-sigv4 (~> 1.0) 181 | aws-sdk-dynamodbstreams (1.0.1) 182 | aws-sdk-core (~> 3) 183 | aws-sigv4 (~> 1.0) 184 | aws-sdk-ec2 (1.28.0) 185 | aws-sdk-core (~> 3) 186 | aws-sigv4 (~> 1.0) 187 | aws-sdk-ecr (1.3.0) 188 | aws-sdk-core (~> 3) 189 | aws-sigv4 (~> 1.0) 190 | aws-sdk-ecs (1.8.0) 191 | aws-sdk-core (~> 3) 192 | aws-sigv4 (~> 1.0) 193 | aws-sdk-efs (1.0.1) 194 | aws-sdk-core (~> 3) 195 | aws-sigv4 (~> 1.0) 196 | aws-sdk-elasticache (1.3.0) 197 | aws-sdk-core (~> 3) 198 | aws-sigv4 (~> 1.0) 199 | aws-sdk-elasticbeanstalk (1.3.0) 200 | aws-sdk-core (~> 3) 201 | aws-sigv4 (~> 1.0) 202 | aws-sdk-elasticloadbalancing (1.2.0) 203 | aws-sdk-core (~> 3) 204 | aws-sigv4 (~> 1.0) 205 | aws-sdk-elasticloadbalancingv2 (1.8.0) 206 | aws-sdk-core (~> 3) 207 | aws-sigv4 (~> 1.0) 208 | aws-sdk-elasticsearchservice (1.3.0) 209 | aws-sdk-core (~> 3) 210 | aws-sigv4 (~> 1.0) 211 | aws-sdk-elastictranscoder (1.0.1) 212 | aws-sdk-core (~> 3) 213 | aws-sigv4 (~> 1.0) 214 | aws-sdk-emr (1.1.0) 215 | aws-sdk-core (~> 3) 216 | aws-sigv4 (~> 1.0) 217 | aws-sdk-firehose (1.1.0) 218 | aws-sdk-core (~> 3) 219 | aws-sigv4 (~> 1.0) 220 | aws-sdk-gamelift (1.3.0) 221 | aws-sdk-core (~> 3) 222 | aws-sigv4 (~> 1.0) 223 | aws-sdk-glacier (1.6.0) 224 | aws-sdk-core (~> 3) 225 | aws-sigv4 (~> 1.0) 226 | aws-sdk-glue (1.5.0) 227 | aws-sdk-core (~> 3) 228 | aws-sigv4 (~> 1.0) 229 | aws-sdk-greengrass (1.2.0) 230 | aws-sdk-core (~> 3) 231 | aws-sigv4 (~> 1.0) 232 | aws-sdk-guardduty (1.2.0) 233 | aws-sdk-core (~> 3) 234 | aws-sigv4 (~> 1.0) 235 | aws-sdk-health (1.0.1) 236 | aws-sdk-core (~> 3) 237 | aws-sigv4 (~> 1.0) 238 | aws-sdk-iam (1.3.0) 239 | aws-sdk-core (~> 3) 240 | aws-sigv4 (~> 1.0) 241 | aws-sdk-importexport (1.0.1) 242 | aws-sdk-core (~> 3) 243 | aws-sigv2 (~> 1.0) 244 | aws-sdk-inspector (1.3.0) 245 | aws-sdk-core (~> 3) 246 | aws-sigv4 (~> 1.0) 247 | aws-sdk-iot (1.3.0) 248 | aws-sdk-core (~> 3) 249 | aws-sigv4 (~> 1.0) 250 | aws-sdk-iotdataplane (1.0.1) 251 | aws-sdk-core (~> 3) 252 | aws-sigv4 (~> 1.0) 253 | aws-sdk-iotjobsdataplane (1.0.0) 254 | aws-sdk-core (~> 3) 255 | aws-sigv4 (~> 1.0) 256 | aws-sdk-kinesis (1.2.0) 257 | aws-sdk-core (~> 3) 258 | aws-sigv4 (~> 1.0) 259 | aws-sdk-kinesisanalytics (1.2.0) 260 | aws-sdk-core (~> 3) 261 | aws-sigv4 (~> 1.0) 262 | aws-sdk-kinesisvideo (1.0.0) 263 | aws-sdk-core (~> 3) 264 | aws-sigv4 (~> 1.0) 265 | aws-sdk-kinesisvideoarchivedmedia (1.0.0) 266 | aws-sdk-core (~> 3) 267 | aws-sigv4 (~> 1.0) 268 | aws-sdk-kinesisvideomedia (1.0.0) 269 | aws-sdk-core (~> 3) 270 | aws-sigv4 (~> 1.0) 271 | aws-sdk-kms (1.5.0) 272 | aws-sdk-core (~> 3) 273 | aws-sigv4 (~> 1.0) 274 | aws-sdk-lambda (1.4.0) 275 | aws-sdk-core (~> 3) 276 | aws-sigv4 (~> 1.0) 277 | aws-sdk-lambdapreview (1.0.1) 278 | aws-sdk-core (~> 3) 279 | aws-sigv4 (~> 1.0) 280 | aws-sdk-lex (1.3.0) 281 | aws-sdk-core (~> 3) 282 | aws-sigv4 (~> 1.0) 283 | aws-sdk-lexmodelbuildingservice (1.5.0) 284 | aws-sdk-core (~> 3) 285 | aws-sigv4 (~> 1.0) 286 | aws-sdk-lightsail (1.3.0) 287 | aws-sdk-core (~> 3) 288 | aws-sigv4 (~> 1.0) 289 | aws-sdk-machinelearning (1.0.1) 290 | aws-sdk-core (~> 3) 291 | aws-sigv4 (~> 1.0) 292 | aws-sdk-marketplacecommerceanalytics (1.0.1) 293 | aws-sdk-core (~> 3) 294 | aws-sigv4 (~> 1.0) 295 | aws-sdk-marketplaceentitlementservice (1.0.1) 296 | aws-sdk-core (~> 3) 297 | aws-sigv4 (~> 1.0) 298 | aws-sdk-marketplacemetering (1.0.1) 299 | aws-sdk-core (~> 3) 300 | aws-sigv4 (~> 1.0) 301 | aws-sdk-mediaconvert (1.1.0) 302 | aws-sdk-core (~> 3) 303 | aws-sigv4 (~> 1.0) 304 | aws-sdk-medialive (1.2.0) 305 | aws-sdk-core (~> 3) 306 | aws-sigv4 (~> 1.0) 307 | aws-sdk-mediapackage (1.0.0) 308 | aws-sdk-core (~> 3) 309 | aws-sigv4 (~> 1.0) 310 | aws-sdk-mediastore (1.1.0) 311 | aws-sdk-core (~> 3) 312 | aws-sigv4 (~> 1.0) 313 | aws-sdk-mediastoredata (1.1.0) 314 | aws-sdk-core (~> 3) 315 | aws-sigv4 (~> 1.0) 316 | aws-sdk-migrationhub (1.0.1) 317 | aws-sdk-core (~> 3) 318 | aws-sigv4 (~> 1.0) 319 | aws-sdk-mobile (1.0.0) 320 | aws-sdk-core (~> 3) 321 | aws-sigv4 (~> 1.0) 322 | aws-sdk-mq (1.0.0) 323 | aws-sdk-core (~> 3) 324 | aws-sigv4 (~> 1.0) 325 | aws-sdk-mturk (1.2.0) 326 | aws-sdk-core (~> 3) 327 | aws-sigv4 (~> 1.0) 328 | aws-sdk-opsworks (1.2.0) 329 | aws-sdk-core (~> 3) 330 | aws-sigv4 (~> 1.0) 331 | aws-sdk-opsworkscm (1.2.0) 332 | aws-sdk-core (~> 3) 333 | aws-sigv4 (~> 1.0) 334 | aws-sdk-organizations (1.7.0) 335 | aws-sdk-core (~> 3) 336 | aws-sigv4 (~> 1.0) 337 | aws-sdk-pinpoint (1.2.0) 338 | aws-sdk-core (~> 3) 339 | aws-sigv4 (~> 1.0) 340 | aws-sdk-polly (1.4.0) 341 | aws-sdk-core (~> 3) 342 | aws-sigv4 (~> 1.0) 343 | aws-sdk-pricing (1.0.0) 344 | aws-sdk-core (~> 3) 345 | aws-sigv4 (~> 1.0) 346 | aws-sdk-rds (1.13.0) 347 | aws-sdk-core (~> 3) 348 | aws-sigv4 (~> 1.0) 349 | aws-sdk-redshift (1.1.0) 350 | aws-sdk-core (~> 3) 351 | aws-sigv4 (~> 1.0) 352 | aws-sdk-rekognition (1.2.0) 353 | aws-sdk-core (~> 3) 354 | aws-sigv4 (~> 1.0) 355 | aws-sdk-resourcegroups (1.0.0) 356 | aws-sdk-core (~> 3) 357 | aws-sigv4 (~> 1.0) 358 | aws-sdk-resourcegroupstaggingapi (1.0.1) 359 | aws-sdk-core (~> 3) 360 | aws-sigv4 (~> 1.0) 361 | aws-sdk-resources (3.12.0) 362 | aws-sdk-acm (~> 1) 363 | aws-sdk-alexaforbusiness (~> 1) 364 | aws-sdk-apigateway (~> 1) 365 | aws-sdk-applicationautoscaling (~> 1) 366 | aws-sdk-applicationdiscoveryservice (~> 1) 367 | aws-sdk-appstream (~> 1) 368 | aws-sdk-appsync (~> 1) 369 | aws-sdk-athena (~> 1) 370 | aws-sdk-autoscaling (~> 1) 371 | aws-sdk-autoscalingplans (~> 1) 372 | aws-sdk-batch (~> 1) 373 | aws-sdk-budgets (~> 1) 374 | aws-sdk-cloud9 (~> 1) 375 | aws-sdk-clouddirectory (~> 1) 376 | aws-sdk-cloudformation (~> 1) 377 | aws-sdk-cloudfront (~> 1) 378 | aws-sdk-cloudhsm (~> 1) 379 | aws-sdk-cloudhsmv2 (~> 1) 380 | aws-sdk-cloudsearch (~> 1) 381 | aws-sdk-cloudsearchdomain (~> 1) 382 | aws-sdk-cloudtrail (~> 1) 383 | aws-sdk-cloudwatch (~> 1) 384 | aws-sdk-cloudwatchevents (~> 1) 385 | aws-sdk-cloudwatchlogs (~> 1) 386 | aws-sdk-codebuild (~> 1) 387 | aws-sdk-codecommit (~> 1) 388 | aws-sdk-codedeploy (~> 1) 389 | aws-sdk-codepipeline (~> 1) 390 | aws-sdk-codestar (~> 1) 391 | aws-sdk-cognitoidentity (~> 1) 392 | aws-sdk-cognitoidentityprovider (~> 1) 393 | aws-sdk-cognitosync (~> 1) 394 | aws-sdk-comprehend (~> 1) 395 | aws-sdk-configservice (~> 1) 396 | aws-sdk-costandusagereportservice (~> 1) 397 | aws-sdk-costexplorer (~> 1) 398 | aws-sdk-databasemigrationservice (~> 1) 399 | aws-sdk-datapipeline (~> 1) 400 | aws-sdk-dax (~> 1) 401 | aws-sdk-devicefarm (~> 1) 402 | aws-sdk-directconnect (~> 1) 403 | aws-sdk-directoryservice (~> 1) 404 | aws-sdk-dynamodb (~> 1) 405 | aws-sdk-dynamodbstreams (~> 1) 406 | aws-sdk-ec2 (~> 1) 407 | aws-sdk-ecr (~> 1) 408 | aws-sdk-ecs (~> 1) 409 | aws-sdk-efs (~> 1) 410 | aws-sdk-elasticache (~> 1) 411 | aws-sdk-elasticbeanstalk (~> 1) 412 | aws-sdk-elasticloadbalancing (~> 1) 413 | aws-sdk-elasticloadbalancingv2 (~> 1) 414 | aws-sdk-elasticsearchservice (~> 1) 415 | aws-sdk-elastictranscoder (~> 1) 416 | aws-sdk-emr (~> 1) 417 | aws-sdk-firehose (~> 1) 418 | aws-sdk-gamelift (~> 1) 419 | aws-sdk-glacier (~> 1) 420 | aws-sdk-glue (~> 1) 421 | aws-sdk-greengrass (~> 1) 422 | aws-sdk-guardduty (~> 1) 423 | aws-sdk-health (~> 1) 424 | aws-sdk-iam (~> 1) 425 | aws-sdk-importexport (~> 1) 426 | aws-sdk-inspector (~> 1) 427 | aws-sdk-iot (~> 1) 428 | aws-sdk-iotdataplane (~> 1) 429 | aws-sdk-iotjobsdataplane (~> 1) 430 | aws-sdk-kinesis (~> 1) 431 | aws-sdk-kinesisanalytics (~> 1) 432 | aws-sdk-kinesisvideo (~> 1) 433 | aws-sdk-kinesisvideoarchivedmedia (~> 1) 434 | aws-sdk-kinesisvideomedia (~> 1) 435 | aws-sdk-kms (~> 1) 436 | aws-sdk-lambda (~> 1) 437 | aws-sdk-lambdapreview (~> 1) 438 | aws-sdk-lex (~> 1) 439 | aws-sdk-lexmodelbuildingservice (~> 1) 440 | aws-sdk-lightsail (~> 1) 441 | aws-sdk-machinelearning (~> 1) 442 | aws-sdk-marketplacecommerceanalytics (~> 1) 443 | aws-sdk-marketplaceentitlementservice (~> 1) 444 | aws-sdk-marketplacemetering (~> 1) 445 | aws-sdk-mediaconvert (~> 1) 446 | aws-sdk-medialive (~> 1) 447 | aws-sdk-mediapackage (~> 1) 448 | aws-sdk-mediastore (~> 1) 449 | aws-sdk-mediastoredata (~> 1) 450 | aws-sdk-migrationhub (~> 1) 451 | aws-sdk-mobile (~> 1) 452 | aws-sdk-mq (~> 1) 453 | aws-sdk-mturk (~> 1) 454 | aws-sdk-opsworks (~> 1) 455 | aws-sdk-opsworkscm (~> 1) 456 | aws-sdk-organizations (~> 1) 457 | aws-sdk-pinpoint (~> 1) 458 | aws-sdk-polly (~> 1) 459 | aws-sdk-pricing (~> 1) 460 | aws-sdk-rds (~> 1) 461 | aws-sdk-redshift (~> 1) 462 | aws-sdk-rekognition (~> 1) 463 | aws-sdk-resourcegroups (~> 1) 464 | aws-sdk-resourcegroupstaggingapi (~> 1) 465 | aws-sdk-route53 (~> 1) 466 | aws-sdk-route53domains (~> 1) 467 | aws-sdk-s3 (~> 1) 468 | aws-sdk-sagemaker (~> 1) 469 | aws-sdk-sagemakerruntime (~> 1) 470 | aws-sdk-serverlessapplicationrepository (~> 1) 471 | aws-sdk-servicecatalog (~> 1) 472 | aws-sdk-servicediscovery (~> 1) 473 | aws-sdk-ses (~> 1) 474 | aws-sdk-shield (~> 1) 475 | aws-sdk-simpledb (~> 1) 476 | aws-sdk-sms (~> 1) 477 | aws-sdk-snowball (~> 1) 478 | aws-sdk-sns (~> 1) 479 | aws-sdk-sqs (~> 1) 480 | aws-sdk-ssm (~> 1) 481 | aws-sdk-states (~> 1) 482 | aws-sdk-storagegateway (~> 1) 483 | aws-sdk-support (~> 1) 484 | aws-sdk-swf (~> 1) 485 | aws-sdk-transcribeservice (~> 1) 486 | aws-sdk-translate (~> 1) 487 | aws-sdk-waf (~> 1) 488 | aws-sdk-wafregional (~> 1) 489 | aws-sdk-workdocs (~> 1) 490 | aws-sdk-workmail (~> 1) 491 | aws-sdk-workspaces (~> 1) 492 | aws-sdk-xray (~> 1) 493 | aws-sdk-route53 (1.9.0) 494 | aws-sdk-core (~> 3) 495 | aws-sigv4 (~> 1.0) 496 | aws-sdk-route53domains (1.1.0) 497 | aws-sdk-core (~> 3) 498 | aws-sigv4 (~> 1.0) 499 | aws-sdk-s3 (1.8.2) 500 | aws-sdk-core (~> 3) 501 | aws-sdk-kms (~> 1) 502 | aws-sigv4 (~> 1.0) 503 | aws-sdk-sagemaker (1.5.0) 504 | aws-sdk-core (~> 3) 505 | aws-sigv4 (~> 1.0) 506 | aws-sdk-sagemakerruntime (1.0.0) 507 | aws-sdk-core (~> 3) 508 | aws-sigv4 (~> 1.0) 509 | aws-sdk-serverlessapplicationrepository (1.1.0) 510 | aws-sdk-core (~> 3) 511 | aws-sigv4 (~> 1.0) 512 | aws-sdk-servicecatalog (1.3.0) 513 | aws-sdk-core (~> 3) 514 | aws-sigv4 (~> 1.0) 515 | aws-sdk-servicediscovery (1.1.0) 516 | aws-sdk-core (~> 3) 517 | aws-sigv4 (~> 1.0) 518 | aws-sdk-ses (1.6.0) 519 | aws-sdk-core (~> 3) 520 | aws-sigv4 (~> 1.0) 521 | aws-sdk-shield (1.1.0) 522 | aws-sdk-core (~> 3) 523 | aws-sigv4 (~> 1.0) 524 | aws-sdk-simpledb (1.0.1) 525 | aws-sdk-core (~> 3) 526 | aws-sigv2 (~> 1.0) 527 | aws-sdk-sms (1.0.1) 528 | aws-sdk-core (~> 3) 529 | aws-sigv4 (~> 1.0) 530 | aws-sdk-snowball (1.2.0) 531 | aws-sdk-core (~> 3) 532 | aws-sigv4 (~> 1.0) 533 | aws-sdk-sns (1.1.0) 534 | aws-sdk-core (~> 3) 535 | aws-sigv4 (~> 1.0) 536 | aws-sdk-sqs (1.3.0) 537 | aws-sdk-core (~> 3) 538 | aws-sigv4 (~> 1.0) 539 | aws-sdk-ssm (1.7.0) 540 | aws-sdk-core (~> 3) 541 | aws-sigv4 (~> 1.0) 542 | aws-sdk-states (1.2.0) 543 | aws-sdk-core (~> 3) 544 | aws-sigv4 (~> 1.0) 545 | aws-sdk-storagegateway (1.2.0) 546 | aws-sdk-core (~> 3) 547 | aws-sigv4 (~> 1.0) 548 | aws-sdk-support (1.0.1) 549 | aws-sdk-core (~> 3) 550 | aws-sigv4 (~> 1.0) 551 | aws-sdk-swf (1.0.1) 552 | aws-sdk-core (~> 3) 553 | aws-sigv4 (~> 1.0) 554 | aws-sdk-transcribeservice (1.0.0) 555 | aws-sdk-core (~> 3) 556 | aws-sigv4 (~> 1.0) 557 | aws-sdk-translate (1.0.0) 558 | aws-sdk-core (~> 3) 559 | aws-sigv4 (~> 1.0) 560 | aws-sdk-waf (1.4.0) 561 | aws-sdk-core (~> 3) 562 | aws-sigv4 (~> 1.0) 563 | aws-sdk-wafregional (1.4.0) 564 | aws-sdk-core (~> 3) 565 | aws-sigv4 (~> 1.0) 566 | aws-sdk-workdocs (1.1.0) 567 | aws-sdk-core (~> 3) 568 | aws-sigv4 (~> 1.0) 569 | aws-sdk-workmail (1.0.0) 570 | aws-sdk-core (~> 3) 571 | aws-sigv4 (~> 1.0) 572 | aws-sdk-workspaces (1.1.0) 573 | aws-sdk-core (~> 3) 574 | aws-sigv4 (~> 1.0) 575 | aws-sdk-xray (1.1.0) 576 | aws-sdk-core (~> 3) 577 | aws-sigv4 (~> 1.0) 578 | aws-sigv2 (1.0.1) 579 | aws-sigv4 (1.0.2) 580 | bcrypt (3.1.11) 581 | bindex (0.5.0) 582 | builder (3.2.3) 583 | byebug (10.0.0) 584 | climate_control (0.2.0) 585 | cocaine (0.5.8) 586 | climate_control (>= 0.0.3, < 1.0) 587 | coffee-rails (4.2.2) 588 | coffee-script (>= 2.2.0) 589 | railties (>= 4.0.0) 590 | coffee-script (2.4.1) 591 | coffee-script-source 592 | execjs 593 | coffee-script-source (1.12.2) 594 | concurrent-ruby (1.0.5) 595 | crass (1.0.3) 596 | erubis (2.7.0) 597 | execjs (2.7.0) 598 | faker (1.8.7) 599 | i18n (>= 0.7) 600 | ffi (1.9.23) 601 | figaro (1.1.1) 602 | thor (~> 0.14) 603 | geocoder (1.4.7) 604 | globalid (0.4.1) 605 | activesupport (>= 4.2.0) 606 | i18n (0.9.5) 607 | concurrent-ruby (~> 1.0) 608 | jbuilder (2.7.0) 609 | activesupport (>= 4.2.0) 610 | multi_json (>= 1.2) 611 | jmespath (1.3.1) 612 | jquery-rails (4.3.1) 613 | rails-dom-testing (>= 1, < 3) 614 | railties (>= 4.2.0) 615 | thor (>= 0.14, < 2.0) 616 | listen (3.0.8) 617 | rb-fsevent (~> 0.9, >= 0.9.4) 618 | rb-inotify (~> 0.9, >= 0.9.7) 619 | loofah (2.2.0) 620 | crass (~> 1.0.2) 621 | nokogiri (>= 1.5.9) 622 | mail (2.7.0) 623 | mini_mime (>= 0.1.1) 624 | method_source (0.9.0) 625 | mime-types (3.1) 626 | mime-types-data (~> 3.2015) 627 | mime-types-data (3.2016.0521) 628 | mimemagic (0.3.2) 629 | mini_mime (1.0.0) 630 | mini_portile2 (2.3.0) 631 | minitest (5.11.3) 632 | multi_json (1.13.1) 633 | nio4r (2.2.0) 634 | nokogiri (1.8.2) 635 | mini_portile2 (~> 2.3.0) 636 | paperclip (5.0.0.beta1) 637 | activemodel (>= 4.2.0) 638 | activesupport (>= 4.2.0) 639 | cocaine (~> 0.5.5) 640 | mime-types 641 | mimemagic (~> 0.3.0) 642 | pg (0.21.0) 643 | puma (3.11.2) 644 | rack (2.0.4) 645 | rack-test (0.6.3) 646 | rack (>= 1.0) 647 | rails (5.0.6) 648 | actioncable (= 5.0.6) 649 | actionmailer (= 5.0.6) 650 | actionpack (= 5.0.6) 651 | actionview (= 5.0.6) 652 | activejob (= 5.0.6) 653 | activemodel (= 5.0.6) 654 | activerecord (= 5.0.6) 655 | activesupport (= 5.0.6) 656 | bundler (>= 1.3.0) 657 | railties (= 5.0.6) 658 | sprockets-rails (>= 2.0.0) 659 | rails-dom-testing (2.0.3) 660 | activesupport (>= 4.2.0) 661 | nokogiri (>= 1.6) 662 | rails-html-sanitizer (1.0.3) 663 | loofah (~> 2.0) 664 | railties (5.0.6) 665 | actionpack (= 5.0.6) 666 | activesupport (= 5.0.6) 667 | method_source 668 | rake (>= 0.8.7) 669 | thor (>= 0.18.1, < 2.0) 670 | rake (12.3.0) 671 | rb-fsevent (0.10.2) 672 | rb-inotify (0.9.10) 673 | ffi (>= 0.5.0, < 2) 674 | sass (3.5.5) 675 | sass-listen (~> 4.0.0) 676 | sass-listen (4.0.0) 677 | rb-fsevent (~> 0.9, >= 0.9.4) 678 | rb-inotify (~> 0.9, >= 0.9.7) 679 | sass-rails (5.0.7) 680 | railties (>= 4.0.0, < 6) 681 | sass (~> 3.1) 682 | sprockets (>= 2.8, < 4.0) 683 | sprockets-rails (>= 2.0, < 4.0) 684 | tilt (>= 1.1, < 3) 685 | seed_dump (3.3.1) 686 | activerecord (>= 4) 687 | activesupport (>= 4) 688 | spring (2.0.2) 689 | activesupport (>= 4.2) 690 | spring-watcher-listen (2.0.1) 691 | listen (>= 2.7, < 4.0) 692 | spring (>= 1.2, < 3.0) 693 | sprockets (3.7.1) 694 | concurrent-ruby (~> 1.0) 695 | rack (> 1, < 3) 696 | sprockets-rails (3.2.1) 697 | actionpack (>= 4.0) 698 | activesupport (>= 4.0) 699 | sprockets (>= 3.0.0) 700 | thor (0.20.0) 701 | thread_safe (0.3.6) 702 | tilt (2.0.8) 703 | tzinfo (1.2.5) 704 | thread_safe (~> 0.1) 705 | uglifier (4.1.6) 706 | execjs (>= 0.3.0, < 3) 707 | web-console (3.5.1) 708 | actionview (>= 5.0) 709 | activemodel (>= 5.0) 710 | bindex (>= 0.4.0) 711 | railties (>= 5.0) 712 | websocket-driver (0.6.5) 713 | websocket-extensions (>= 0.1.0) 714 | websocket-extensions (0.1.3) 715 | 716 | PLATFORMS 717 | ruby 718 | 719 | DEPENDENCIES 720 | annotate 721 | aws-sdk (>= 2.0) 722 | bcrypt (~> 3.1.7) 723 | byebug 724 | coffee-rails (~> 4.2) 725 | faker 726 | figaro 727 | geocoder 728 | jbuilder (~> 2.5) 729 | jquery-rails 730 | listen (~> 3.0.5) 731 | paperclip (= 5.0.0.beta1) 732 | pg (~> 0.18) 733 | puma (~> 3.0) 734 | rails (~> 5.0.3) 735 | sass-rails (~> 5.0) 736 | seed_dump 737 | spring 738 | spring-watcher-listen (~> 2.0.0) 739 | tzinfo-data 740 | uglifier (>= 1.3.0) 741 | web-console (>= 3.3.0) 742 | 743 | BUNDLED WITH 744 | 1.16.1 745 | --------------------------------------------------------------------------------