├── log └── .keep ├── storage └── .keep ├── tmp ├── .keep └── pids │ └── .keep ├── vendor └── .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 ├── .ruby-version ├── app ├── assets │ ├── builds │ │ └── .keep │ ├── images │ │ └── .keep │ ├── config │ │ └── manifest.js │ └── stylesheets │ │ ├── host │ │ └── listings.scss │ │ ├── static_pages.scss │ │ ├── users │ │ └── omniauth_callbacks.scss │ │ └── application.css ├── models │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ ├── notification.rb │ ├── photo.rb │ ├── message.rb │ ├── event.rb │ ├── bed.rb │ ├── room.rb │ ├── reservation.rb │ ├── calendar_event.rb │ ├── listing.rb │ ├── user.rb │ └── book_listing.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── application_controller.rb │ ├── static_pages_controller.rb │ ├── host │ │ ├── reservations_controller.rb │ │ ├── photos_controller.rb │ │ ├── rooms_controller.rb │ │ ├── merchant_settings_controller.rb │ │ └── listings_controller.rb │ ├── listings_controller.rb │ ├── webhooks_controller.rb │ ├── users │ │ └── omniauth_callbacks_controller.rb │ ├── messages_controller.rb │ └── reservations_controller.rb ├── views │ ├── layouts │ │ ├── mailer.text.erb │ │ ├── mailer.html.erb │ │ └── application.html.erb │ ├── message_mailer │ │ ├── new_message.html.erb │ │ └── new_message.text.erb │ ├── static_pages │ │ └── home.html.erb │ ├── host │ │ ├── merchant_settings │ │ │ ├── connected.html.erb │ │ │ └── index.html.erb │ │ ├── listings │ │ │ ├── show.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── new.html.erb │ │ │ └── index.html.erb │ │ ├── photos │ │ │ └── index.html.erb │ │ ├── reservations │ │ │ └── show.html.erb │ │ └── rooms │ │ │ ├── index.html.erb │ │ │ └── _form.html.erb │ ├── shared │ │ ├── _errors.html.erb │ │ └── _notices.html.erb │ ├── reservation_mailer │ │ ├── guest_booked.text.erb │ │ ├── host_booked.text.erb │ │ ├── guest_booked.html.erb │ │ └── host_booked.html.erb │ ├── devise │ │ ├── mailer │ │ │ ├── password_change.html.erb │ │ │ ├── confirmation_instructions.html.erb │ │ │ ├── unlock_instructions.html.erb │ │ │ ├── email_changed.html.erb │ │ │ └── reset_password_instructions.html.erb │ │ ├── shared │ │ │ ├── _error_messages.html.erb │ │ │ └── _links.html.erb │ │ ├── unlocks │ │ │ └── new.html.erb │ │ ├── passwords │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ ├── confirmations │ │ │ └── new.html.erb │ │ ├── registrations │ │ │ ├── edit.html.erb │ │ │ └── new.html.erb │ │ └── sessions │ │ │ └── new.html.erb │ ├── reservations │ │ ├── show.html.erb │ │ ├── new.html.erb │ │ └── index.html.erb │ ├── listings │ │ ├── show.html.erb │ │ └── index.html.erb │ └── messages │ │ └── index.html.erb ├── helpers │ ├── messages_helper.rb │ ├── webhooks_helper.rb │ ├── host │ │ ├── photos_helper.rb │ │ ├── rooms_helper.rb │ │ ├── listings_helper.rb │ │ ├── reservations_helper.rb │ │ └── merchant_settings_helper.rb │ ├── reservations_helper.rb │ ├── static_pages_helper.rb │ ├── listings_helper.rb │ ├── users │ │ └── omniauth_callbacks_helper.rb │ └── application_helper.rb ├── javascript │ ├── channels │ │ ├── index.js │ │ └── consumer.js │ ├── controllers │ │ ├── application.js │ │ ├── room_controller.js │ │ ├── menu_controller.js │ │ ├── index.js │ │ ├── listing_map_controller.js │ │ └── address_controller.js │ └── application.js ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── mailers │ ├── application_mailer.rb │ ├── message_mailer.rb │ └── reservation_mailer.rb ├── jobs │ ├── application_job.rb │ └── event_job.rb └── notifications │ ├── guest_reservation_booked_notification.rb │ ├── host_reservation_booked_notification.rb │ └── new_message.rb ├── .browserslistrc ├── .node-version ├── .DS_Store ├── Procfile.dev ├── config ├── initializers │ ├── stripe.rb │ ├── mime_types.rb │ ├── application_controller_renderer.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── permissions_policy.rb │ ├── assets.rb │ ├── wrap_parameters.rb │ ├── backtrace_silencers.rb │ ├── inflections.rb │ ├── content_security_policy.rb │ └── new_framework_defaults_7_0.rb ├── webpack │ ├── environment.js │ ├── test.js │ ├── production.js │ └── development.js ├── spring.rb ├── environment.rb ├── boot.rb ├── cable.yml ├── application.rb ├── credentials.yml.enc ├── locales │ ├── en.yml │ └── devise.en.yml ├── storage.yml ├── routes.rb ├── puma.rb ├── environments │ ├── test.rb │ ├── development.rb │ └── production.rb └── database.yml ├── bin ├── rake ├── rails ├── dev ├── webpack ├── webpack-dev-server ├── spring ├── yarn ├── setup └── bundle ├── README.md ├── test ├── jobs │ └── event_job_test.rb ├── models │ ├── message_test.rb │ ├── notification_test.rb │ ├── photo_test.rb │ ├── event_test.rb │ ├── calendar_event_test.rb │ └── reservation_test.rb ├── controllers │ ├── webhooks_controller_test.rb │ ├── host │ │ ├── merchant_settings_controller_test.rb │ │ ├── photos_controller_test.rb │ │ └── reservations_controller_test.rb │ ├── messages_controller_test.rb │ ├── listings_controller_test.rb │ └── reservations_controller_test.rb ├── fixtures │ ├── messages.yml │ ├── notifications.yml │ ├── photos.yml │ ├── events.yml │ ├── reservations.yml │ └── calendar_events.yml └── mailers │ ├── previews │ ├── message_mailer_preview.rb │ └── reservation_mailer_preview.rb │ ├── message_mailer_test.rb │ └── reservation_mailer_test.rb ├── db ├── migrate │ ├── 20210926182544_add_name_to_users.rb │ ├── 20220317141605_add_phone_number_to_users.rb │ ├── 20210926175111_create_users.rb │ ├── 20211127001249_add_stripe_customer_id_to_users.rb │ ├── 20211127001336_add_stripe_product_id_to_lisitngs.rb │ ├── 20211127013735_add_stripe_refund_id_to_reservations.rb │ ├── 20220317151520_add_identity_verified_to_users.rb │ ├── 20211126234514_add_price_to_listing.rb │ ├── 20211127012331_add_stripe_payment_intent_id_to_reservations.rb │ ├── 20211013230033_create_beds.rb │ ├── 20211013230015_create_rooms.rb │ ├── 20211126230105_create_photos.rb │ ├── 20210926182417_add_omniauth_columns_to_user.rb │ ├── 20211223203847_add_host_fields_to_users.rb │ ├── 20211127010215_create_events.rb │ ├── 20220315205850_create_notifications.rb │ ├── 20211126235956_create_reservations.rb │ ├── 20220309151733_create_calendar_events.rb │ ├── 20220316123857_create_messages.rb │ ├── 20210926184417_create_listings.rb │ ├── 20210926175127_add_devise_to_users.rb │ └── 20211126230823_create_active_storage_tables.active_storage.rb ├── seeds.rb └── schema.rb ├── config.ru ├── Rakefile ├── postcss.config.js ├── .gitattributes ├── package.json ├── .gitignore ├── babel.config.js ├── Gemfile └── Gemfile.lock /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/pids/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.2 2 | -------------------------------------------------------------------------------- /app/assets/builds/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 16.13.1 2 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/helpers/messages_helper.rb: -------------------------------------------------------------------------------- 1 | module MessagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/webhooks_helper.rb: -------------------------------------------------------------------------------- 1 | module WebhooksHelper 2 | end 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjavdev/clearbnb/HEAD/.DS_Store -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bin/rails server -p 3000 2 | js: yarn build --watch 3 | -------------------------------------------------------------------------------- /app/helpers/host/photos_helper.rb: -------------------------------------------------------------------------------- 1 | module Host::PhotosHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/host/rooms_helper.rb: -------------------------------------------------------------------------------- 1 | module Host::RoomsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/reservations_helper.rb: -------------------------------------------------------------------------------- 1 | module ReservationsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/static_pages_helper.rb: -------------------------------------------------------------------------------- 1 | module StaticPagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/host/listings_helper.rb: -------------------------------------------------------------------------------- 1 | module Host::ListingsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/host/reservations_helper.rb: -------------------------------------------------------------------------------- 1 | module Host::ReservationsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/host/merchant_settings_helper.rb: -------------------------------------------------------------------------------- 1 | module Host::MerchantSettingsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/listings_helper.rb: -------------------------------------------------------------------------------- 1 | module ListingsHelper 2 | include Pagy::Frontend 3 | end 4 | -------------------------------------------------------------------------------- /app/helpers/users/omniauth_callbacks_helper.rb: -------------------------------------------------------------------------------- 1 | module Users::OmniauthCallbacksHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /config/initializers/stripe.rb: -------------------------------------------------------------------------------- 1 | Stripe.api_key = Rails.application.credentials.dig(:stripe, :secret_key) 2 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/views/message_mailer/new_message.html.erb: -------------------------------------------------------------------------------- 1 |

New Message

2 | 3 |

4 | <%= @message.content %> 5 |

6 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | 3 | module.exports = environment 4 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link_tree ../builds 4 | -------------------------------------------------------------------------------- /app/views/static_pages/home.html.erb: -------------------------------------------------------------------------------- 1 |

StaticPages#home

2 |

Find me in app/views/static_pages/home.html.erb

3 | -------------------------------------------------------------------------------- /app/controllers/static_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class StaticPagesController < ApplicationController 2 | def home 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/javascript/channels/index.js: -------------------------------------------------------------------------------- 1 | import { createConsumer } from "@rails/actioncable" 2 | 3 | export default createConsumer() 4 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/views/host/merchant_settings/connected.html.erb: -------------------------------------------------------------------------------- 1 |

Connected

2 | 3 | Thanks so much! 4 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/notification.rb: -------------------------------------------------------------------------------- 1 | class Notification < ApplicationRecord 2 | include Noticed::Model 3 | belongs_to :recipient, polymorphic: true 4 | end 5 | -------------------------------------------------------------------------------- /app/views/message_mailer/new_message.text.erb: -------------------------------------------------------------------------------- 1 | Message#new_message 2 | 3 | <%= @greeting %>, find me in app/views/message_mailer/new_message.text.erb 4 | -------------------------------------------------------------------------------- /app/views/shared/_errors.html.erb: -------------------------------------------------------------------------------- 1 | <% if flash[:errors] %> 2 | <% flash[:errors].each do |error| %> 3 | <%= error %> 4 | <% end %> 5 | <% end %> 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clearbnb 2 | 3 | Want to see this thing built from scratch? 4 | [YouTube](https://www.youtube.com/playlist?list=PLS6F722u-R6LoD3UN0EE_cKtHVG2EWn0t) 5 | -------------------------------------------------------------------------------- /app/views/reservation_mailer/guest_booked.text.erb: -------------------------------------------------------------------------------- 1 | Reservation#guest_booked 2 | 3 | <%= @greeting %>, find me in app/views/reservation_mailer/guest_booked.text.erb 4 | -------------------------------------------------------------------------------- /app/views/reservation_mailer/host_booked.text.erb: -------------------------------------------------------------------------------- 1 | Reservation#host_booked 2 | 3 | <%= @greeting %>, find me in app/views/reservation_mailer/host_booked.text.erb 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/jobs/event_job_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class EventJobTest < ActiveJob::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/devise/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

We're contacting you to notify you that your password has been changed.

4 | -------------------------------------------------------------------------------- /test/models/message_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MessageTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20210926182544_add_name_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddNameToUsers < ActiveRecord::Migration[6.1] 2 | def change 3 | add_column :users, :name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/models/notification_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class NotificationTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /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 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /db/migrate/20220317141605_add_phone_number_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddPhoneNumberToUsers < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :phone_number, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! foreman version &> /dev/null 4 | then 5 | echo "Installing foreman..." 6 | gem install foreman 7 | fi 8 | 9 | foreman start -f Procfile.dev "$@" 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /db/migrate/20210926175111_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :users do |t| 4 | 5 | t.timestamps 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/controllers/webhooks_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class WebhooksControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/host/reservations_controller.rb: -------------------------------------------------------------------------------- 1 | class Host::ReservationsController < ApplicationController 2 | def show 3 | @reservation = current_user.host_reservations.find(params[:id]) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20211127001249_add_stripe_customer_id_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddStripeCustomerIdToUsers < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :stripe_customer_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20211127001336_add_stripe_product_id_to_lisitngs.rb: -------------------------------------------------------------------------------- 1 | class AddStripeProductIdToLisitngs < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :listings, :stripe_product_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/host/listings.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Host/Listings controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: https://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/static_pages.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the StaticPages controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: https://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /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 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /db/migrate/20211127013735_add_stripe_refund_id_to_reservations.rb: -------------------------------------------------------------------------------- 1 | class AddStripeRefundIdToReservations < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :reservations, :stripe_refund_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220317151520_add_identity_verified_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddIdentityVerifiedToUsers < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :identity_verified, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/host/merchant_settings_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class Host::MerchantSettingsControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20211126234514_add_price_to_listing.rb: -------------------------------------------------------------------------------- 1 | class AddPriceToListing < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :listings, :nightly_price, :integer 4 | add_column :listings, :cleaning_fee, :integer 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: clearbnb_production 11 | -------------------------------------------------------------------------------- /db/migrate/20211127012331_add_stripe_payment_intent_id_to_reservations.rb: -------------------------------------------------------------------------------- 1 | class AddStripePaymentIntentIdToReservations < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :reservations, :stripe_payment_intent_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/messages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MessagesControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get messages_index_url 6 | assert_response :success 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/users/omniauth_callbacks.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Users/OmniauthCallbacks controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: https://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /test/controllers/host/photos_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class Host::PhotosControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get host_photos_index_url 6 | assert_response :success 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/reservation_mailer/guest_booked.html.erb: -------------------------------------------------------------------------------- 1 |

Booking confirmed!

2 | 3 |

4 | You're all set to stay at <%= @reservation.listing.title %> 5 |

6 | 7 |

8 | <%= @reservation.start_date %> to <%= @reservation.end_date %> 9 |

10 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

6 | -------------------------------------------------------------------------------- /app/views/reservation_mailer/host_booked.html.erb: -------------------------------------------------------------------------------- 1 |

New Reservation

2 | 3 |

4 | <%= @reservation.guest.email %> booked your property "<%= @reservation.listing.title %>" 5 |

6 |

7 | <%= @reservation.start_date %> to <%= @reservation.end_date %> 8 |

9 | -------------------------------------------------------------------------------- /test/controllers/host/reservations_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class Host::ReservationsControllerTest < ActionDispatch::IntegrationTest 4 | test "should get show" do 5 | get host_reservations_show_url 6 | assert_response :success 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /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 | require 'resque/tasks' 6 | 7 | Rails.application.load_tasks 8 | -------------------------------------------------------------------------------- /app/views/shared/_notices.html.erb: -------------------------------------------------------------------------------- 1 | <% if notice %> 2 |

<%= notice %>

3 | <% end %> 4 | <% if alert %> 5 |

<%= alert %>

6 | <% end %> 7 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /db/migrate/20211013230033_create_beds.rb: -------------------------------------------------------------------------------- 1 | class CreateBeds < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :beds do |t| 4 | t.references :room, null: false, foreign_key: true 5 | t.integer :bed_size 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('postcss-flexbugs-fixes'), 5 | require('postcss-preset-env')({ 6 | autoprefixer: { 7 | flexbox: 'no-2009' 8 | }, 9 | stage: 3 10 | }) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20211013230015_create_rooms.rb: -------------------------------------------------------------------------------- 1 | class CreateRooms < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :rooms do |t| 4 | t.references :listing, null: false, foreign_key: true 5 | t.integer :room_type 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20211126230105_create_photos.rb: -------------------------------------------------------------------------------- 1 | class CreatePhotos < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :photos do |t| 4 | t.references :listing, null: false, foreign_key: true 5 | t.string :caption 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/listings_controller.rb: -------------------------------------------------------------------------------- 1 | class ListingsController < ApplicationController 2 | include Pagy::Backend 3 | 4 | def index 5 | @pagy, @listings = pagy(Listing.published, items: 6) 6 | end 7 | 8 | def show 9 | @listing = Listing.published.find(params[:id]) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/javascript/channels/consumer.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 `bin/rails generate channel` command. 3 | 4 | import { createConsumer } from "@rails/actioncable" 5 | 6 | export default createConsumer() 7 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [ 5 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 6 | ] 7 | -------------------------------------------------------------------------------- /db/migrate/20210926182417_add_omniauth_columns_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddOmniauthColumnsToUser < ActiveRecord::Migration[6.1] 2 | def change 3 | add_column :users, :provider, :string 4 | add_column :users, :uid, :string 5 | 6 | add_index :users, :uid 7 | add_index :users, [:provider, :uid] 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20211223203847_add_host_fields_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddHostFieldsToUsers < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :is_host, :boolean, default: false 4 | add_column :users, :stripe_account_id, :string 5 | add_column :users, :charges_enabled, :boolean, default: false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/messages.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | from_user: one 5 | to_user: one 6 | reservation: one 7 | content: MyString 8 | 9 | two: 10 | from_user: two 11 | to_user: two 12 | reservation: two 13 | content: MyString 14 | -------------------------------------------------------------------------------- /app/javascript/controllers/application.js: -------------------------------------------------------------------------------- 1 | import { Application } from "@hotwired/stimulus" 2 | 3 | const application = Application.start() 4 | 5 | // Configure Stimulus development experience 6 | application.warnings = true 7 | application.debug = false 8 | window.Stimulus = application 9 | 10 | export { application } 11 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /test/mailers/previews/message_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/message_mailer 2 | class MessageMailerPreview < ActionMailer::Preview 3 | 4 | # Preview this email at http://localhost:3000/rails/mailers/message_mailer/new_message 5 | def new_message 6 | MessageMailer.new_message 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /app/views/host/listings/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @listing.title %>

2 | 3 | <%= link_to "Edit details", edit_host_listing_path(@listing) %> 4 | <%= link_to "Edit rooms", host_listing_rooms_path(@listing) %> 5 | <%= link_to "Edit photos", host_listing_photos_path(@listing) %> 6 | 7 |
<%= JSON.pretty_generate(@listing.as_json) %>
8 | -------------------------------------------------------------------------------- /db/migrate/20211127010215_create_events.rb: -------------------------------------------------------------------------------- 1 | class CreateEvents < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :events do |t| 4 | t.text :request_body 5 | t.integer :status, default: 0 6 | t.text :error_message 7 | t.string :source 8 | t.string :event_type 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/controllers/listings_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ListingsControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get listings_index_url 6 | assert_response :success 7 | end 8 | 9 | test "should get show" do 10 | get listings_show_url 11 | assert_response :success 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/devise/mailer/email_changed.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @email %>!

2 | 3 | <% if @resource.try(:unconfirmed_email?) %> 4 |

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

5 | <% else %> 6 |

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

7 | <% end %> 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark the yarn lockfile as having been generated. 7 | yarn.lock linguist-generated 8 | 9 | # Mark any vendored files as having been vendored. 10 | vendor/* linguist-vendored 11 | -------------------------------------------------------------------------------- /app/controllers/webhooks_controller.rb: -------------------------------------------------------------------------------- 1 | class WebhooksController < ApplicationController 2 | skip_before_action :verify_authenticity_token 3 | 4 | def create 5 | event = Event.create!( 6 | request_body: request.raw_post, 7 | source: params[:source], 8 | ) 9 | EventJob.perform_later(event) 10 | render json: { message: "success" } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/controllers/reservations_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ReservationsControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get reservations_index_url 6 | assert_response :success 7 | end 8 | 9 | test "should get show" do 10 | get reservations_show_url 11 | assert_response :success 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/fixtures/notifications.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | recipient: one 5 | recipient_type: Recipient 6 | type: 7 | params: 8 | read_at: 2022-03-15 16:58:50 9 | 10 | two: 11 | recipient: two 12 | recipient_type: Recipient 13 | type: 14 | params: 15 | read_at: 2022-03-15 16:58:50 16 | -------------------------------------------------------------------------------- /test/mailers/message_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MessageMailerTest < ActionMailer::TestCase 4 | test "new_message" do 5 | mail = MessageMailer.new_message 6 | assert_equal "New message", mail.subject 7 | assert_equal ["to@example.org"], mail.to 8 | assert_equal ["from@example.com"], mail.from 9 | assert_match "Hi", mail.body.encoded 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /app/models/photo.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: photos 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # caption :string 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | class Photo < ApplicationRecord 12 | belongs_to :listing 13 | has_one_attached :photo 14 | end 15 | -------------------------------------------------------------------------------- /app/mailers/message_mailer.rb: -------------------------------------------------------------------------------- 1 | class MessageMailer < ApplicationMailer 2 | 3 | # Subject can be set in your I18n file at config/locales/en.yml 4 | # with the following lookup: 5 | # 6 | # en.message_mailer.new_message.subject 7 | # 8 | def new_message 9 | @message = params[:message] 10 | 11 | mail to: @message.to_user.email, subject: "New message from #{@message.from_user.email}" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20220315205850_create_notifications.rb: -------------------------------------------------------------------------------- 1 | class CreateNotifications < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :notifications do |t| 4 | t.references :recipient, polymorphic: true, null: false 5 | t.string :type, null: false 6 | t.jsonb :params 7 | t.datetime :read_at 8 | 9 | t.timestamps 10 | end 11 | add_index :notifications, :read_at 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20211126235956_create_reservations.rb: -------------------------------------------------------------------------------- 1 | class CreateReservations < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :reservations do |t| 4 | t.references :listing, null: false, foreign_key: true 5 | t.references :guest, null: false, foreign_key: { to_table: :users } 6 | t.string :session_id 7 | t.integer :status, default: 0 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/fixtures/photos.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: photos 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # caption :string 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | 12 | one: 13 | photo: MyString 14 | caption: MyString 15 | 16 | two: 17 | photo: MyString 18 | caption: MyString 19 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /test/models/photo_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: photos 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # caption :string 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | require "test_helper" 12 | 13 | class PhotoTest < ActiveSupport::TestCase 14 | # test "the truth" do 15 | # assert true 16 | # end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password. You can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /app/javascript/controllers/room_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | // Connects to data-controller="room" 4 | export default class extends Controller { 5 | static targets = [ 6 | 'bedFields', 7 | 'beds', 8 | ] 9 | 10 | connect() { 11 | } 12 | 13 | addBed(e) { 14 | e.preventDefault(); 15 | this.bedsTarget.insertAdjacentHTML( 16 | 'beforeend', 17 | this.bedFieldsTarget.innerHTML 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /db/migrate/20220309151733_create_calendar_events.rb: -------------------------------------------------------------------------------- 1 | class CreateCalendarEvents < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :calendar_events do |t| 4 | t.references :listing, null: false, foreign_key: true 5 | t.references :reservation, null: true, foreign_key: true 6 | t.integer :status, null: false 7 | t.date :start_date, null: false 8 | t.date :end_date, null: false 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Entry point for the build script in your package.json 2 | // import * as ActiveStorage from "@rails/activestorage" 3 | import "@hotwired/turbo-rails" 4 | import "./channels" 5 | import "./controllers" 6 | 7 | // ActiveStorage.start() 8 | console.log("here") 9 | window.initMap = () => { 10 | console.log('initMap was called'); 11 | const event = new Event("MapLoaded") 12 | event.initEvent("map-loaded", true, true); 13 | window.dispatchEvent(event) 14 | } 15 | -------------------------------------------------------------------------------- /db/migrate/20220316123857_create_messages.rb: -------------------------------------------------------------------------------- 1 | class CreateMessages < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :messages do |t| 4 | t.references :from_user, null: false, foreign_key: { to_table: :users } 5 | t.references :to_user, null: false, foreign_key: { to_table: :users } 6 | t.references :reservation, null: true, foreign_key: true 7 | t.string :content, null: false, default: '' 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/webpack_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::WebpackRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /app/models/message.rb: -------------------------------------------------------------------------------- 1 | class Message < ApplicationRecord 2 | belongs_to :from_user, class_name: 'User' 3 | belongs_to :to_user, class_name: 'User' 4 | belongs_to :reservation, optional: true 5 | has_noticed_notifications 6 | 7 | after_create_commit :notify_to_user 8 | 9 | def self.mark_as_read! 10 | all.map(&:notifications_as_message).flatten.each(&:mark_as_read!) 11 | end 12 | 13 | def notify_to_user 14 | NewMessage.with(message: self).deliver_later(to_user) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/devise/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.any? %> 2 |
3 |

4 | <%= I18n.t("errors.messages.not_saved", 5 | count: resource.errors.count, 6 | resource: resource.class.model_name.human.downcase) 7 | %> 8 |

9 | 14 |
15 | <% end %> 16 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /app/models/event.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: events 4 | # 5 | # id :bigint not null, primary key 6 | # request_body :text 7 | # status :integer default("pending") 8 | # error_message :text 9 | # source :string 10 | # event_type :string 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | class Event < ApplicationRecord 15 | enum status: {pending: 0, processed: 1, failed: 2} 16 | end 17 | -------------------------------------------------------------------------------- /app/views/reservations/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @reservation.listing.title %>

2 | 3 |
<%= JSON.pretty_generate(@reservation.as_json(include: :calendar_event)) %>
4 | 5 | <%= link_to "Messages", messages_path(reservation_id: @reservation.id) %> 6 | 7 |
8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /app/javascript/controllers/menu_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | import {toggle} from "el-transition"; 3 | 4 | // Connects to data-controller="menu" 5 | export default class extends Controller { 6 | static targets = [ "mobileMenu", "desktopMenu" ] 7 | connect() { 8 | console.log("menu controller connected"); 9 | } 10 | 11 | toggleDesktopMenu() { 12 | toggle(this.desktopMenuTarget); 13 | } 14 | 15 | toggleMobileMenu() { 16 | toggle(this.mobileMenuTarget); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/models/bed.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: beds 4 | # 5 | # id :bigint not null, primary key 6 | # room_id :bigint not null 7 | # bed_size :integer 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | class Bed < ApplicationRecord 12 | belongs_to :room 13 | 14 | enum bed_size: { 15 | twin: 0, 16 | twin_xl: 1, 17 | full: 2, 18 | queen: 3, 19 | king: 4, 20 | california_king: 5, 21 | } 22 | end 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clearbnb", 3 | "private": true, 4 | "dependencies": { 5 | "@hotwired/stimulus": "^3.0.1", 6 | "@hotwired/turbo-rails": "^7.1.3", 7 | "@rails/actioncable": "^7.0.0", 8 | "@rails/activestorage": "^7.0.0", 9 | "el-transition": "^0.0.7", 10 | "esbuild": "^0.14.49", 11 | "yarn": "^1.22.17" 12 | }, 13 | "version": "0.1.0", 14 | "scripts": { 15 | "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/views/host/photos/index.html.erb: -------------------------------------------------------------------------------- 1 |

Photos

2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /test/mailers/previews/reservation_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/reservation_mailer 2 | class ReservationMailerPreview < ActionMailer::Preview 3 | 4 | # Preview this email at http://localhost:3000/rails/mailers/reservation_mailer/host_booked 5 | def host_booked 6 | ReservationMailer.host_booked 7 | end 8 | 9 | # Preview this email at http://localhost:3000/rails/mailers/reservation_mailer/guest_booked 10 | def guest_booked 11 | ReservationMailer.guest_booked 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"]) 3 | gem "bundler" 4 | require "bundler" 5 | 6 | # Load Spring without loading other gems in the Gemfile, for speed. 7 | Bundler.locked_gems&.specs&.find { |spec| spec.name == "spring" }&.tap do |spring| 8 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 9 | gem "spring", spring.version 10 | require "spring/binstub" 11 | rescue Gem::LoadError 12 | # Ignore when Spring is not installed. 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/host/merchant_settings/index.html.erb: -------------------------------------------------------------------------------- 1 |

Merchant Settings

2 | 3 | <% if current_user.stripe_account_id.blank? %> 4 |

5 | You have not connected your Stripe account yet. 6 |

7 |

8 | Connect your Stripe account 9 |

10 | <% else %> 11 | Stripe Account: <%= current_user.stripe_account_id %> 12 | 13 | Charges enable: <%= current_user.charges_enabled? ? 'Yes' : 'No' %> 14 | <% end %> 15 | -------------------------------------------------------------------------------- /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 the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /test/models/event_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: events 4 | # 5 | # id :bigint not null, primary key 6 | # request_body :text 7 | # status :integer default("pending") 8 | # error_message :text 9 | # source :string 10 | # event_type :string 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | require "test_helper" 15 | 16 | class EventTest < ActiveSupport::TestCase 17 | # test "the truth" do 18 | # assert true 19 | # end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 9 |
10 | 11 |
12 | <%= f.submit "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 9 |
10 | 11 |
12 | <%= f.submit "Send me reset password instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /app/models/room.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: rooms 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # room_type :integer 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | class Room < ApplicationRecord 12 | belongs_to :listing 13 | has_many :beds, dependent: :destroy 14 | accepts_nested_attributes_for :beds 15 | 16 | enum room_type: { 17 | bedroom: 0, 18 | primary_bedroom: 1, 19 | living_room: 2, 20 | basement: 3, 21 | } 22 | end 23 | -------------------------------------------------------------------------------- /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| /my_noisy_library/.match?(line) } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 8 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] 9 | -------------------------------------------------------------------------------- /app/views/host/reservations/show.html.erb: -------------------------------------------------------------------------------- 1 |

Reservation

2 | 3 |

<%= @reservation.listing.title %>

4 | 5 |

<%= @reservation.guest.email %>

6 | 7 |
<%= JSON.pretty_generate(@reservation.as_json(include: :calendar_event)) %>
8 | 9 | <%= link_to "Messages", messages_path(reservation_id: @reservation.id) %> 10 | 11 |
12 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR). 5 | select { |dir| File.expand_path(dir) != __dir__ }. 6 | product(["yarn", "yarn.cmd", "yarn.ps1"]). 7 | map { |dir, file| File.expand_path(file, dir) }. 8 | find { |file| File.executable?(file) } 9 | 10 | if yarn 11 | exec yarn, *ARGV 12 | else 13 | $stderr.puts "Yarn executable was not detected in the system." 14 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 15 | exit 1 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/models/calendar_event_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: calendar_events 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # reservation_id :bigint 8 | # status :integer not null 9 | # start_date :date not null 10 | # end_date :date not null 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | require "test_helper" 15 | 16 | class CalendarEventTest < ActiveSupport::TestCase 17 | # test "the truth" do 18 | # assert true 19 | # end 20 | end 21 | -------------------------------------------------------------------------------- /app/controllers/users/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::OmniauthCallbacksController < ApplicationController 2 | def google 3 | @user = User.from_omniauth(request.env["omniauth.auth"]) 4 | 5 | if @user.persisted? 6 | sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated 7 | set_flash_message(:notice, :success, kind: "Google") if is_navigational_format? 8 | else 9 | session["devise.google_data"] = request.env["omniauth.auth"].except(:extra) 10 | redirect_to new_user_registration_url 11 | end 12 | end 13 | 14 | def failure 15 | redirect_to root_path 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // This file is auto-generated by ./bin/rails stimulus:manifest:update 2 | // Run that command whenever you add a new controller 3 | 4 | import { application } from "./application" 5 | 6 | import AddressController from "./address_controller.js" 7 | application.register("address", AddressController) 8 | 9 | import ListingMapController from "./listing_map_controller.js" 10 | application.register("listing-map", ListingMapController) 11 | 12 | import MenuController from "./menu_controller.js" 13 | application.register("menu", MenuController) 14 | 15 | import RoomController from "./room_controller.js" 16 | application.register("room", RoomController) 17 | -------------------------------------------------------------------------------- /app/controllers/host/photos_controller.rb: -------------------------------------------------------------------------------- 1 | class Host::PhotosController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def index 5 | @listing = current_user.listings.find(params[:listing_id]) 6 | @photos = @listing.photos 7 | end 8 | 9 | def create 10 | @listing = current_user.listings.find(params[:listing_id]) 11 | @photo = @listing.photos.new(photo_params) 12 | if !@photo.save 13 | flash[:errors] = @photo.errors.full_messages 14 | end 15 | redirect_to host_listing_photos_path(@listing) 16 | end 17 | 18 | private 19 | 20 | def photo_params 21 | params.require(:photo).permit(:caption, :photo) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> 9 |
10 | 11 |
12 | <%= f.submit "Resend confirmation instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /test/fixtures/events.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: events 4 | # 5 | # id :bigint not null, primary key 6 | # request_body :text 7 | # status :integer default("pending") 8 | # error_message :text 9 | # source :string 10 | # event_type :string 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | 15 | one: 16 | request_body: MyText 17 | status: 1 18 | error_message: MyText 19 | source: MyString 20 | event_type: MyString 21 | 22 | two: 23 | request_body: MyText 24 | status: 1 25 | error_message: MyText 26 | source: MyString 27 | event_type: MyString 28 | -------------------------------------------------------------------------------- /test/mailers/reservation_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ReservationMailerTest < ActionMailer::TestCase 4 | test "host_booked" do 5 | mail = ReservationMailer.host_booked 6 | assert_equal "Host booked", mail.subject 7 | assert_equal ["to@example.org"], mail.to 8 | assert_equal ["from@example.com"], mail.from 9 | assert_match "Hi", mail.body.encoded 10 | end 11 | 12 | test "guest_booked" do 13 | mail = ReservationMailer.guest_booked 14 | assert_equal "Guest booked", mail.subject 15 | assert_equal ["to@example.org"], mail.to 16 | assert_equal ["from@example.com"], mail.from 17 | assert_match "Hi", mail.body.encoded 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20210926184417_create_listings.rb: -------------------------------------------------------------------------------- 1 | class CreateListings < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :listings do |t| 4 | t.references :host, null: false, foreign_key: { to_table: :users } 5 | t.string :title, null: false 6 | t.text :about 7 | t.integer :max_guests, default: 1 8 | t.string :address_line1 9 | t.string :address_line2 10 | t.string :city 11 | t.string :state 12 | t.string :postal_code 13 | t.string :country 14 | t.decimal :lat, precision: 10, scale: 6 15 | t.decimal :lng, precision: 10, scale: 6 16 | t.integer :status, default: 0 17 | 18 | t.timestamps 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/fixtures/reservations.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: reservations 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # guest_id :bigint not null 8 | # session_id :string 9 | # status :integer default("pending") 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # stripe_payment_intent_id :string 13 | # stripe_refund_id :string 14 | # 15 | 16 | one: 17 | session_id: MyString 18 | status: 1 19 | 20 | two: 21 | session_id: MyString 22 | status: 1 23 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def navigation_items 3 | [ 4 | { body: 'Listings', href: listings_path }, 5 | { body: 'Reservations', href: reservations_path }, 6 | { body: 'My Listings', href: host_listings_path }, 7 | ] 8 | end 9 | 10 | def navigation_class(path) 11 | if current_page?(path) 12 | "bg-gray-900 text-white px-3 py-2 rounded-md text-sm font-medium" 13 | else 14 | "text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium" 15 | end 16 | end 17 | 18 | def navigation_aria(path) 19 | if current_page?(path) 20 | "page" 21 | else 22 | "false" 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/fixtures/calendar_events.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: calendar_events 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # reservation_id :bigint 8 | # status :integer not null 9 | # start_date :date not null 10 | # end_date :date not null 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | 15 | one: 16 | listing: one 17 | status: 1 18 | start_date: 2022-03-09 19 | end_date: 2022-03-09 20 | reservation: one 21 | 22 | two: 23 | listing: two 24 | status: 1 25 | start_date: 2022-03-09 26 | end_date: 2022-03-09 27 | reservation: two 28 | -------------------------------------------------------------------------------- /test/models/reservation_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: reservations 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # guest_id :bigint not null 8 | # session_id :string 9 | # status :integer default("pending") 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # stripe_payment_intent_id :string 13 | # stripe_refund_id :string 14 | # 15 | require "test_helper" 16 | 17 | class ReservationTest < ActiveSupport::TestCase 18 | # test "the truth" do 19 | # assert true 20 | # end 21 | end 22 | -------------------------------------------------------------------------------- /app/mailers/reservation_mailer.rb: -------------------------------------------------------------------------------- 1 | class ReservationMailer < ApplicationMailer 2 | 3 | # Subject can be set in your I18n file at config/locales/en.yml 4 | # with the following lookup: 5 | # 6 | # en.reservation_mailer.host_booked.subject 7 | # 8 | def host_booked 9 | @reservation = params[:reservation] 10 | 11 | mail to: @reservation.host.email, subject: "You have a new reservation!" 12 | end 13 | 14 | # Subject can be set in your I18n file at config/locales/en.yml 15 | # with the following lookup: 16 | # 17 | # en.reservation_mailer.guest_booked.subject 18 | # 19 | def guest_booked 20 | @reservation = params[:reservation] 21 | 22 | mail to: @reservation.guest.email, subject: "Your reservation is confirmed!" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /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, or any plugin's 6 | * 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 | -------------------------------------------------------------------------------- /app/views/reservations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: 'shared/errors' %> 2 | 3 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /app/notifications/guest_reservation_booked_notification.rb: -------------------------------------------------------------------------------- 1 | # To deliver this notification: 2 | # 3 | # GuestReservationBookedNotification.with(post: @post).deliver_later(current_user) 4 | # GuestReservationBookedNotification.with(post: @post).deliver(current_user) 5 | 6 | class GuestReservationBookedNotification < Noticed::Base 7 | # Add your delivery methods 8 | # 9 | deliver_by :database 10 | deliver_by :email, mailer: "ReservationMailer", method: :guest_booked 11 | # deliver_by :slack 12 | # deliver_by :custom, class: "MyDeliveryMethod" 13 | 14 | # Add required params 15 | 16 | param :reservation 17 | 18 | # Define helper methods to make rendering easier. 19 | # 20 | # def message 21 | # t(".message") 22 | # end 23 | # 24 | # def url 25 | # post_path(params[:post]) 26 | # end 27 | end 28 | -------------------------------------------------------------------------------- /app/controllers/messages_controller.rb: -------------------------------------------------------------------------------- 1 | class MessagesController < ApplicationController 2 | def index 3 | @reservation = current_user.all_reservations.find(params[:reservation_id]) 4 | @messages = @reservation.messages 5 | @messages.where(to_user: current_user).mark_as_read! 6 | end 7 | 8 | def create 9 | @reservation = current_user.all_reservations.find(message_params[:reservation_id]) 10 | @message = current_user.sent_messages.new(message_params) 11 | @message.to_user = @reservation.guest == current_user ? @reservation.host : @reservation.guest 12 | if !@message.save 13 | flash[:errors] = @message.errors.full_messages 14 | end 15 | redirect_to messages_path(reservation_id: @reservation.id) 16 | end 17 | 18 | private 19 | 20 | def message_params 21 | params.require(:message).permit(:content, :reservation_id) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/notifications/host_reservation_booked_notification.rb: -------------------------------------------------------------------------------- 1 | # To deliver this notification: 2 | # 3 | # HostReservationBookedNotification.with(reservation: @reservation).deliver_later(current_user) 4 | # HostReservationBookedNotification.with(reservation: @reservation).deliver(current_user) 5 | 6 | class HostReservationBookedNotification < Noticed::Base 7 | # Add your delivery methods 8 | # 9 | deliver_by :database 10 | deliver_by :email, mailer: "ReservationMailer", method: :host_booked 11 | deliver_by :twilio 12 | 13 | # deliver_by :slack 14 | # deliver_by :custom, class: "MyDeliveryMethod" 15 | 16 | # Add required params 17 | # 18 | param :reservation 19 | param :message 20 | 21 | # Define helper methods to make rendering easier. 22 | # 23 | # def message 24 | # t(".message") 25 | # end 26 | # 27 | # def url 28 | # post_path(params[:post]) 29 | # end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/host/rooms_controller.rb: -------------------------------------------------------------------------------- 1 | class Host::RoomsController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def index 5 | @listing = current_user.listings.find(params[:listing_id]) 6 | @rooms = @listing.rooms.all 7 | end 8 | 9 | def create 10 | @listing = current_user.listings.find(params[:listing_id]) 11 | @room = @listing.rooms.new(room_params) 12 | if !@room.save 13 | flash[:errors] = @room.errors.full_messages 14 | end 15 | 16 | redirect_to host_listing_rooms_path(@listing) 17 | end 18 | 19 | def destroy 20 | @listing = current_user.listings.find(params[:listing_id]) 21 | @room = @listing.rooms.find(params[:id]) 22 | @room.destroy 23 | redirect_to host_listing_rooms_path(@listing) 24 | end 25 | 26 | private 27 | 28 | def room_params 29 | params.require(:room).permit(:room_type, beds_attributes: [:bed_size]) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /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 Clearbnb 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 6.1 13 | 14 | Rails.application.default_url_options = { host: 'localhost', port: 3000 } 15 | config.active_job.queue_adapter = :resque 16 | 17 | # Configuration for the application, engines, and railties goes here. 18 | # 19 | # These settings can be overridden in specific environments using the files 20 | # in config/environments, which are processed later. 21 | # 22 | # config.time_zone = "Central Time (US & Canada)" 23 | # config.eager_load_paths << Rails.root.join("extras") 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | 6qEEo5tAcHeivumvh5DtcJpSkbk3m/VtaGYjEAmKUg87J5czlDnkEQM+Y8XKXz6RHo8KkCfgowbKYH5b4Zv5nrxb6HTZJgbt9jG9AdJ+/5GTQuG42MTN3bAON67f5NipnnYyeonIJVTZzSNTZxrAFdf3z7jzWJlpJw7tUy1Q0fVaDFfKjzhyQvo6mndIDS9j/G1w1KFLjYBIS4CcYEjJMbHJ9A4zRNsIayjvhdLgu4cTAQqXgMqRTSnGk+DM0r4+KC23V0FVmV1DMsPARKrqQ1rZsXGOXTZf1TZQ69X+QelpRoz+B3ULmEUY0hHj9uOgLIGbus1mN2XPj5MYXSk2QUzqSUS1wETOvzDRt5XsVZW6HYjdXYZqtfL44Y2hVn2mJdlG29fNfjBJj41SBCnabw3vJI7ZmyVj306MeAwSy0vygkGKkkVYoq1emhHuIoWvZdHYWqADuG81GprAyXPX5DG87MfQTRlbY6tTU5z/4tZ5ah6gPVEsNKAkBEgZN3w0IIlmoDof6C1oPRpjxVITN8d2n1m5EQkjD4EZI/KBz7e4GS8yz3Lyb5GpzhTyALeghGtGG6zEdoPac7X5MunWEeLxnFtkV5m350KJKuYccG82P8/3nD0BF4hFX+SXjf32i0AfQDBb9PzEbo/VYJ/05w+EJy//c4sDhOVDkznRM+HzULBdPLKkpe/VW1ntf1u8BbcJE3kVdJLntfdDrCHC7HgAHi4j+fw7yGYUghmhIsIvOuPbYcl9hJ1h2c25uSNvM3f61JIss4MhtKGBfzothl2nLCrR//t8blCFoariGERWWGs51C3iecLGIXHU0ctiUtGYIB472urU89GlZK5SDaA5G1H8Z0Xl6Z697KhTC5lwqjfEriiHJNWsLWxCg5dqQX3y05PbI4PrLeDV9HBX4TnViJMTMLWqyFEtJ382Sbo=--eXul164VPzM+KgSC--eruUFj1DznV1Jyw5U+Irtg== -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | <%= f.hidden_field :reset_password_token %> 6 | 7 |
8 | <%= f.label :password, "New password" %>
9 | <% if @minimum_password_length %> 10 | (<%= @minimum_password_length %> characters minimum)
11 | <% end %> 12 | <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> 13 |
14 | 15 |
16 | <%= f.label :password_confirmation, "Confirm new password" %>
17 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 18 |
19 | 20 |
21 | <%= f.submit "Change my password" %> 22 |
23 | <% end %> 24 | 25 | <%= render "devise/shared/links" %> 26 | -------------------------------------------------------------------------------- /app/views/listings/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @listing.title %>

2 | 3 |
4 | Per night <%= number_to_currency(@listing.nightly_price / 100, precision: 0) %> 5 |
6 |
7 | Cleaning fee <%= number_to_currency(@listing.cleaning_fee / 100, precision: 0) %> 8 |
9 | 10 | 11 | <%= link_to "Reserve", new_reservation_path(listing_id: @listing.id) %> 12 | 13 | <%= @listing.city %>, <%= @listing.state %> 14 | 15 |

16 | <%= @listing.about %> 17 |

18 | 19 |

20 | <%= @listing.max_guests %> 21 |

22 | 23 | 24 | <% if @listing.photos.exists? %> 25 | <% @listing.photos.each do |photo| %> 26 | <%= image_tag photo.photo.variant(resize_to_limit: [100, 100]) %> 27 | <% end %> 28 | <% end %> 29 | 30 | 35 | 36 | 37 |
38 |

Host

39 | <%= @listing.host.email %> 40 |
41 | -------------------------------------------------------------------------------- /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 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /.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 pidfiles, but keep the directory. 17 | /tmp/pids/* 18 | !/tmp/pids/ 19 | !/tmp/pids/.keep 20 | 21 | # Ignore uploaded files in development. 22 | /storage/* 23 | !/storage/.keep 24 | 25 | /public/assets 26 | .byebug_history 27 | 28 | # Ignore master key for decrypting credentials and more. 29 | /config/master.key 30 | 31 | /public/packs 32 | /public/packs-test 33 | /node_modules 34 | /yarn-error.log 35 | yarn-debug.log* 36 | .yarn-integrity 37 | dump.rdb 38 | 39 | /app/assets/builds/* 40 | !/app/assets/builds/.keep 41 | -------------------------------------------------------------------------------- /app/notifications/new_message.rb: -------------------------------------------------------------------------------- 1 | # To deliver this notification: 2 | # 3 | # NewMessage.with(message: @message).deliver_later(current_user) 4 | # NewMessage.with(message: @message).deliver(current_user) 5 | 6 | class NewMessage < Noticed::Base 7 | # Add your delivery methods 8 | # 9 | deliver_by :database 10 | deliver_by :email, mailer: "MessageMailer", method: :new_message 11 | deliver_by :twilio, format: :format_for_twilio 12 | 13 | # deliver_by :slack 14 | # deliver_by :custom, class: "MyDeliveryMethod" 15 | 16 | # Add required params 17 | # 18 | param :message 19 | 20 | def format_for_twilio 21 | { 22 | Body: params[:message].content, 23 | From: Rails.application.credentials.twilio[:phone_number], 24 | To: recipient.phone_number 25 | } 26 | end 27 | 28 | # Define helper methods to make rendering easier. 29 | # 30 | # def message 31 | # t(".message") 32 | # end 33 | # 34 | # def url 35 | # post_path(params[:post]) 36 | # end 37 | end 38 | -------------------------------------------------------------------------------- /app/models/reservation.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: reservations 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # guest_id :bigint not null 8 | # session_id :string 9 | # status :integer default("pending") 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # stripe_payment_intent_id :string 13 | # stripe_refund_id :string 14 | # 15 | class Reservation < ApplicationRecord 16 | belongs_to :listing 17 | belongs_to :guest, class_name: 'User' 18 | has_one :host, through: :listing 19 | enum status: {pending: 0, booked: 1, canceling: 3, cancelled: 2, expired: 4} 20 | has_one :calendar_event, dependent: :destroy, required: true 21 | has_many :messages 22 | 23 | delegate :start_date, to: :calendar_event 24 | delegate :end_date, to: :calendar_event 25 | delegate :nights, to: :calendar_event 26 | end 27 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /app/javascript/controllers/listing_map_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | // Connects to data-controller="listing_map" 4 | export default class extends Controller { 5 | static targets = ["map", "listings"] 6 | connect() { 7 | console.log("ListingMapController connected") 8 | 9 | if(window.google) { 10 | this.initGoogle(); 11 | } 12 | } 13 | 14 | initGoogle() { 15 | console.log('init google') 16 | const myLatLng = { lat: -25.363, lng: 131.044 }; 17 | const map = new google.maps.Map(this.mapTarget, { 18 | zoom: 4, 19 | center: myLatLng, 20 | }); 21 | this.addMarkers(map); 22 | } 23 | 24 | addMarkers(map) { 25 | console.log('adding markers') 26 | Array.from(this.listingsTarget.children).forEach((listing) => { 27 | if(listing.dataset.lat) { 28 | new google.maps.Marker({ 29 | position: { 30 | lat: parseFloat(listing.dataset.lat), 31 | lng: parseFloat(listing.dataset.lng), 32 | }, 33 | map, 34 | title: listing.dataset.title, 35 | }); 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /app/controllers/host/merchant_settings_controller.rb: -------------------------------------------------------------------------------- 1 | class Host::MerchantSettingsController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def index 5 | end 6 | 7 | def connect 8 | if !current_user.stripe_account_id.present? 9 | account = Stripe::Account.create( 10 | type: 'express', 11 | country: 'US', 12 | email: current_user.email, 13 | capabilities: { 14 | card_payments: {requested: true}, 15 | transfers: {requested: true}, 16 | tax_reporting_us_1099_k: {requested: true}, 17 | }, 18 | business_type: 'individual', 19 | individual: { 20 | email: current_user.email, 21 | } 22 | ) 23 | current_user.update( 24 | is_host: true, 25 | stripe_account_id: account.id, 26 | ) 27 | end 28 | 29 | link = Stripe::AccountLink.create( 30 | account: current_user.stripe_account_id, 31 | refresh_url: connect_host_merchant_settings_url, 32 | return_url: connected_host_merchant_settings_url, 33 | type: 'account_onboarding', 34 | ) 35 | redirect_to link.url, status: :see_other, allow_other_hosts: true 36 | end 37 | 38 | def connected 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/views/host/rooms/index.html.erb: -------------------------------------------------------------------------------- 1 |

Rooms

2 | 3 | <%= link_to "Back to #{@listing.title}", host_listing_path(@listing) %> 4 | 5 | 25 | 26 | <%= render partial: 'form', locals: { listing: @listing } %> 27 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | require 'resque/server' 2 | Rails.application.routes.draw do 3 | root to: 'static_pages#home' 4 | 5 | resources :listings, only: [:index, :show] 6 | resources :messages, only: [:index, :create] 7 | resources :reservations do 8 | member do 9 | post '/cancel' => 'reservations#cancel' 10 | get '/expire' => 'reservations#expire' 11 | end 12 | end 13 | post '/webhooks/:source' => 'webhooks#create' 14 | 15 | namespace :host do 16 | resources :merchant_settings do 17 | collection do 18 | get :connect, to: 'merchant_settings#connect' 19 | get :connected, to: 'merchant_settings#connected' 20 | end 21 | end 22 | resources :listings do 23 | resources :photos, only: [:index, :create, :destroy] 24 | resources :rooms, only: [:index, :create, :destroy] 25 | end 26 | resources :reservations, only: [:show] 27 | end 28 | 29 | devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' } 30 | 31 | resque_web_constraint = lambda do |request| 32 | current_user = request.env['warden'].user 33 | current_user.id == 1 34 | end 35 | constraints resque_web_constraint do 36 | mount Resque::Server, at: '/jobs' 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name) %>
3 | <% end %> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end %> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end %> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end %> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end %> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %>
24 | <% end %> 25 | <% end %> 26 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | me = User.find_or_create_by!( 2 | email: 'wave@cjav.dev', 3 | ) do |u| 4 | u.password = 'password' 5 | end 6 | 7 | 10.times do 8 | listing = Listing.create( 9 | host: me, 10 | title: Faker::Lorem.words.join(" "), 11 | about: Faker::Lorem.paragraphs.join("\n"), 12 | max_guests: (1...15).to_a.sample, 13 | address_line1: Faker::Address.street_address, 14 | city: Faker::Address.city, 15 | state: Faker::Address.state, 16 | country: 'US', 17 | status: [:draft, :published].sample, 18 | nightly_price: 12000, 19 | cleaning_fee: 5000, 20 | ) 21 | puts "New listing: #{listing.id}" 22 | end 23 | 24 | 25 | 10.times do 26 | host = User.create!( 27 | email: Faker::Internet.email, 28 | password: Faker::Internet.password 29 | ) 30 | 31 | 10.times do 32 | listing = Listing.create( 33 | host: host, 34 | title: Faker::Lorem.words.join(" "), 35 | about: Faker::Lorem.paragraphs.join("\n"), 36 | max_guests: (1...15).to_a.sample, 37 | address_line1: Faker::Address.street_address, 38 | city: Faker::Address.city, 39 | state: Faker::Address.state, 40 | country: 'US', 41 | status: [:draft, :published].sample, 42 | nightly_price: 12000, 43 | cleaning_fee: 5000, 44 | ) 45 | end 46 | end 47 | 48 | 49 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | # # Specify URI for violation reports 15 | # # policy.report_uri "/csp-violation-report-endpoint" 16 | # end 17 | 18 | # If you are using UJS then enable automatic nonce generation 19 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 20 | 21 | # Set the nonce only to specific directives 22 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src) 23 | 24 | # Report CSP violations to a specified URI 25 | # For further information see the following documentation: 26 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 27 | # Rails.application.config.content_security_policy_report_only = true 28 | -------------------------------------------------------------------------------- /app/views/host/rooms/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 22 | 23 |
24 | Beds 25 |
26 | 27 | add bed 28 | 29 | 30 |
31 | 32 |
33 | -------------------------------------------------------------------------------- /app/views/host/listings/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= @listing.title %>

2 | 3 | <%= render partial: "shared/errors" %> 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /app/models/calendar_event.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: calendar_events 4 | # 5 | # id :bigint not null, primary key 6 | # listing_id :bigint not null 7 | # reservation_id :bigint 8 | # status :integer not null 9 | # start_date :date not null 10 | # end_date :date not null 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | class CalendarEvent < ApplicationRecord 15 | belongs_to :listing 16 | belongs_to :reservation, optional: true 17 | validates :status, presence: true 18 | enum status: { reserved: 0, blocked: 1 } # blocked means manually held by the host 19 | 20 | validates :start_date, presence: true 21 | validates :end_date, presence: true 22 | # Start date cannot fall after end date 23 | validates :start_date, comparison: { less_than: :end_date } 24 | # Dates must be in the future 25 | validates :start_date, inclusion: { 26 | in: (Date.today..Date.today + 365 + 365), 27 | message: "must be in the future" 28 | } 29 | # Dates don't overlap with other calendar events for this listing 30 | validates :start_date, :end_date, overlap: { 31 | exclude_edges: ['start_date', 'end_date'], 32 | scope: :listing_id, 33 | query_options: { 34 | joins: 'LEFT OUTER JOIN reservations r on r.id = calendar_events.reservation_id', 35 | where: 'r.status in (0, 1) or calendar_events.status = 1' 36 | }, 37 | message_content: 'is already booked for this date range' 38 | } 39 | 40 | def nights 41 | (start_date...end_date).count 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 9 |
10 | 11 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 12 |
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
13 | <% end %> 14 | 15 |
16 | <%= f.label :password %> (leave blank if you don't want to change it)
17 | <%= f.password_field :password, autocomplete: "new-password" %> 18 | <% if @minimum_password_length %> 19 |
20 | <%= @minimum_password_length %> characters minimum 21 | <% end %> 22 |
23 | 24 |
25 | <%= f.label :password_confirmation %>
26 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 27 |
28 | 29 |
30 | <%= f.label :current_password %> (we need your current password to confirm your changes)
31 | <%= f.password_field :current_password, autocomplete: "current-password" %> 32 |
33 | 34 |
35 | <%= f.submit "Update" %> 36 |
37 | <% end %> 38 | 39 |

Cancel my account

40 | 41 |

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

42 | 43 | <%= link_to "Back", :back %> 44 | -------------------------------------------------------------------------------- /app/views/listings/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listings

2 | 3 |
4 | <%== pagy_nav(@pagy) %> 5 |
6 | 7 |
8 | 22 |
23 |
24 |
25 | 26 |
27 | <%== pagy_nav(@pagy) %> 28 |
29 | -------------------------------------------------------------------------------- /app/controllers/host/listings_controller.rb: -------------------------------------------------------------------------------- 1 | class Host::ListingsController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def new 5 | @listing = Listing.new 6 | end 7 | 8 | def create 9 | @listing = current_user.listings.new(listing_create_params) 10 | if @listing.save 11 | redirect_to host_listing_path(@listing) 12 | else 13 | flash.now[:errors] = @listing.errors.full_messages 14 | render :new 15 | end 16 | end 17 | 18 | def edit 19 | @listing = current_user.listings.find(params[:id]) 20 | end 21 | 22 | def update 23 | @listing = current_user.listings.find(params[:id]) 24 | if @listing.update(listing_update_params) 25 | redirect_to host_listing_path(@listing) 26 | else 27 | flash.now[:errors] = @listing.errors.full_messages 28 | render :edit 29 | end 30 | end 31 | 32 | def index 33 | @listings = current_user.listings.all 34 | end 35 | 36 | def show 37 | @listing = current_user.listings.find(params[:id]) 38 | end 39 | 40 | def destroy 41 | @listing = current_user.listings.find(params[:id]) 42 | @listing.update(status: :archived) 43 | redirect_to host_listings_path 44 | end 45 | 46 | private 47 | 48 | def listing_create_params 49 | params.require(:listing).permit( 50 | :title, 51 | :about, 52 | :max_guests, 53 | :address_line1, 54 | :address_line2, 55 | :city, 56 | :state, 57 | :postal_code, 58 | :country, 59 | :lat, 60 | :lng, 61 | :nightly_price, 62 | :cleaning_fee 63 | ) 64 | end 65 | 66 | def listing_update_params 67 | params.require(:listing).permit( 68 | :title, 69 | :about, 70 | :max_guests, 71 | :status, 72 | :nightly_price, 73 | :cleaning_fee 74 | ) 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /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/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 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 12 | # terminating a worker in development environments. 13 | # 14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 15 | 16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 17 | # 18 | port ENV.fetch("PORT") { 3000 } 19 | 20 | # Specifies the `environment` that Puma will run in. 21 | # 22 | environment ENV.fetch("RAILS_ENV") { "development" } 23 | 24 | # Specifies the `pidfile` that Puma will use. 25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 26 | 27 | # Specifies the number of `workers` to boot in clustered mode. 28 | # Workers are forked web server processes. If using threads and workers together 29 | # the concurrency of the application would be max `threads` * `workers`. 30 | # Workers do not work on JRuby or Windows (both of which do not support 31 | # processes). 32 | # 33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 34 | 35 | # Use the `preload_app!` method when specifying a `workers` number. 36 | # This directive tells Puma to first boot the application and load code 37 | # before forking the application. This takes advantage of Copy On Write 38 | # process behavior so workers use less memory. 39 | # 40 | # preload_app! 41 | 42 | # Allow puma to be restarted by `rails restart` command. 43 | plugin :tmp_restart 44 | -------------------------------------------------------------------------------- /db/migrate/20210926175127_add_devise_to_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddDeviseToUsers < ActiveRecord::Migration[6.1] 4 | def self.up 5 | change_table :users do |t| 6 | ## Database authenticatable 7 | t.string :email, null: false, default: "" 8 | t.string :encrypted_password, null: false, default: "" 9 | 10 | ## Recoverable 11 | t.string :reset_password_token 12 | t.datetime :reset_password_sent_at 13 | 14 | ## Rememberable 15 | t.datetime :remember_created_at 16 | 17 | ## Trackable 18 | t.integer :sign_in_count, default: 0, null: false 19 | t.datetime :current_sign_in_at 20 | t.datetime :last_sign_in_at 21 | t.string :current_sign_in_ip 22 | t.string :last_sign_in_ip 23 | 24 | ## Confirmable 25 | t.string :confirmation_token 26 | t.datetime :confirmed_at 27 | t.datetime :confirmation_sent_at 28 | t.string :unconfirmed_email # Only if using reconfirmable 29 | 30 | ## Lockable 31 | t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 32 | t.string :unlock_token # Only if unlock strategy is :email or :both 33 | t.datetime :locked_at 34 | 35 | # Uncomment below if timestamps were not included in your original model. 36 | # t.timestamps null: false 37 | end 38 | 39 | add_index :users, :email, unique: true 40 | add_index :users, :reset_password_token, unique: true 41 | add_index :users, :confirmation_token, unique: true 42 | add_index :users, :unlock_token, unique: true 43 | end 44 | 45 | def self.down 46 | # By default, we don't want to make any assumption about how to roll back a migration when your 47 | # model already existed. Please edit below which fields you would like to remove in this migration. 48 | raise ActiveRecord::IrreversibleMigration 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /app/models/listing.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: listings 4 | # 5 | # id :bigint not null, primary key 6 | # host_id :bigint not null 7 | # title :string not null 8 | # about :text 9 | # max_guests :integer default(1) 10 | # address_line1 :string 11 | # address_line2 :string 12 | # city :string 13 | # state :string 14 | # postal_code :string 15 | # country :string 16 | # lat :decimal(10, 6) 17 | # lng :decimal(10, 6) 18 | # status :integer default("draft") 19 | # created_at :datetime not null 20 | # updated_at :datetime not null 21 | # nightly_price :integer 22 | # cleaning_fee :integer 23 | # stripe_product_id :string 24 | # 25 | class Listing < ApplicationRecord 26 | validates :nightly_price, numericality: { greater_than: 0 } 27 | validates :cleaning_fee, numericality: { greater_than: 0 } 28 | 29 | validates :title, presence: true 30 | validates :max_guests, numericality: { greater_than: 0, less_than_or_equal_to: 100 } 31 | belongs_to :host, class_name: 'User' 32 | enum status: {draft: 0, published: 1, archived: 2} 33 | has_many :rooms 34 | has_many :photos 35 | has_many :calendar_events 36 | has_many :reservations 37 | scope :published, -> { where(status: :published) } 38 | 39 | after_commit :maybe_create_stripe_product, on: [:create, :update] 40 | 41 | def maybe_create_stripe_product 42 | return if !stripe_product_id.blank? 43 | 44 | product = Stripe::Product.create( 45 | name: title, 46 | url: Rails.application.routes.url_helpers.url_for(self), 47 | metadata: { 48 | clearbnb_id: id, 49 | } 50 | ) 51 | update(stripe_product_id: product.id) 52 | end 53 | 54 | def address 55 | "#{address_line1} #{address_line2} #{city} #{state}" 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/controllers/reservations_controller.rb: -------------------------------------------------------------------------------- 1 | class ReservationsController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def index 5 | @reservations = current_user.reservations 6 | @host_reservations = current_user.host_reservations 7 | end 8 | 9 | def expire 10 | Stripe::Checkout::Session.expire(params[:session_id]) 11 | redirect_to listings_path 12 | end 13 | 14 | def cancel 15 | @reservation = current_user.reservations.find(params[:id]) 16 | # make api call to refund payment 17 | refund = Stripe::Refund.create({ 18 | payment_intent: @reservation.stripe_payment_intent_id, 19 | }) 20 | @reservation.update(status: :canceling, stripe_refund_id: refund.id) 21 | redirect_to reservation_path(@reservation) 22 | end 23 | 24 | def new 25 | @listing = Listing.find(params[:listing_id]) 26 | @calendar_events = @listing.calendar_events 27 | end 28 | 29 | def create 30 | # check to see if the user's identity is verified 31 | if !current_user.identity_verified 32 | # Rdirect through the identity verification flow 33 | verification_session = Stripe::Identity::VerificationSession.create({ 34 | type: 'document', 35 | metadata: { 36 | user_id: current_user.id, 37 | }, 38 | return_url: new_reservation_url(listing_id: reservation_params[:listing_id]), 39 | }) 40 | redirect_to verification_session.url, allow_other_host: true, status: :see_other 41 | return 42 | end 43 | 44 | @booking = BookListing.new(current_user, reservation_params) 45 | 46 | if @booking.save 47 | redirect_to @booking.checkout_url, allow_other_hosts: true, status: :see_other 48 | else 49 | flash.now[:errors] = @booking.errors 50 | @listing = @booking.listing 51 | @calendar_events = @listing.calendar_events 52 | render :new 53 | end 54 | end 55 | 56 | def show 57 | @reservation = current_user.reservations.find(params[:id]) 58 | end 59 | 60 | private 61 | 62 | def reservation_params 63 | params.require(:reservation).permit(:listing_id, :start_date, :end_date) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | var validEnv = ['development', 'test', 'production'] 3 | var currentEnv = api.env() 4 | var isDevelopmentEnv = api.env('development') 5 | var isProductionEnv = api.env('production') 6 | var isTestEnv = api.env('test') 7 | 8 | if (!validEnv.includes(currentEnv)) { 9 | throw new Error( 10 | 'Please specify a valid `NODE_ENV` or ' + 11 | '`BABEL_ENV` environment variables. Valid values are "development", ' + 12 | '"test", and "production". Instead, received: ' + 13 | JSON.stringify(currentEnv) + 14 | '.' 15 | ) 16 | } 17 | 18 | return { 19 | presets: [ 20 | isTestEnv && [ 21 | '@babel/preset-env', 22 | { 23 | targets: { 24 | node: 'current' 25 | } 26 | } 27 | ], 28 | (isProductionEnv || isDevelopmentEnv) && [ 29 | '@babel/preset-env', 30 | { 31 | forceAllTransforms: true, 32 | useBuiltIns: 'entry', 33 | corejs: 3, 34 | modules: false, 35 | exclude: ['transform-typeof-symbol'] 36 | } 37 | ] 38 | ].filter(Boolean), 39 | plugins: [ 40 | 'babel-plugin-macros', 41 | '@babel/plugin-syntax-dynamic-import', 42 | isTestEnv && 'babel-plugin-dynamic-import-node', 43 | '@babel/plugin-transform-destructuring', 44 | [ 45 | '@babel/plugin-proposal-class-properties', 46 | { 47 | loose: true 48 | } 49 | ], 50 | [ 51 | '@babel/plugin-proposal-object-rest-spread', 52 | { 53 | useBuiltIns: true 54 | } 55 | ], 56 | [ 57 | '@babel/plugin-proposal-private-methods', 58 | { 59 | loose: true 60 | } 61 | ], 62 | [ 63 | '@babel/plugin-proposal-private-property-in-object', 64 | { 65 | loose: true 66 | } 67 | ], 68 | [ 69 | '@babel/plugin-transform-runtime', 70 | { 71 | helpers: false 72 | } 73 | ], 74 | [ 75 | '@babel/plugin-transform-regenerator', 76 | { 77 | async: false 78 | } 79 | ] 80 | ].filter(Boolean) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '3.1.2' 5 | 6 | gem 'rails', github: 'rails/rails', branch: 'main' 7 | 8 | gem 'sprockets-rails' 9 | 10 | # Use postgresql as the database for Active Record 11 | gem 'pg', '~> 1.1' 12 | 13 | # Use Puma as the app server 14 | gem 'puma', '~> 5.0' 15 | 16 | gem "jsbundling-rails" 17 | 18 | gem "turbo-rails" 19 | 20 | gem "stimulus-rails" 21 | 22 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 23 | gem 'jbuilder', '~> 2.7' 24 | 25 | # Use SCSS for stylesheets 26 | gem 'sass-rails', '>= 6' 27 | 28 | 29 | 30 | # Use Redis adapter to run Action Cable in production 31 | # gem 'redis', '~> 4.0' 32 | # Use Active Model has_secure_password 33 | # gem 'bcrypt', '~> 3.1.7' 34 | 35 | # Use Active Storage variant 36 | # gem 'image_processing', '~> 1.2' 37 | 38 | # Reduces boot times through caching; required in config/boot.rb 39 | gem 'bootsnap', require: false 40 | 41 | # Authentication 42 | gem 'devise' 43 | gem 'omniauth-google-oauth2' 44 | gem 'omniauth-rails_csrf_protection' 45 | 46 | group :development, :test do 47 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 48 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] 49 | gem "pry-rails", "~> 0.3.9" 50 | gem 'faker' 51 | end 52 | 53 | group :development do 54 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 55 | gem 'web-console', '>= 4.1.0' 56 | # Display performance information such as SQL time and flame graphs for each request in your browser. 57 | # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md 58 | gem 'rack-mini-profiler', '~> 2.0' 59 | gem 'listen', '~> 3.3' 60 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 61 | gem 'spring' 62 | gem 'letter_opener' 63 | gem 'annotate' 64 | end 65 | 66 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 67 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 68 | 69 | gem "tailwindcss-rails", "~> 0.4.3" 70 | gem 'image_processing', '~> 1.2' 71 | gem 'stripe' 72 | gem 'resque' 73 | gem "validates_overlap", "~> 1.0" 74 | gem "pagy", "~> 5.10" 75 | gem "noticed", "~> 1.5" 76 | -------------------------------------------------------------------------------- /db/migrate/20211126230823_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20170806125915) 2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2] 3 | def change 4 | # Use Active Record's configured type for primary and foreign keys 5 | primary_key_type, foreign_key_type = primary_and_foreign_key_types 6 | 7 | create_table :active_storage_blobs, id: primary_key_type do |t| 8 | t.string :key, null: false 9 | t.string :filename, null: false 10 | t.string :content_type 11 | t.text :metadata 12 | t.string :service_name, null: false 13 | t.bigint :byte_size, null: false 14 | t.string :checksum, null: false 15 | 16 | if connection.supports_datetime_with_precision? 17 | t.datetime :created_at, precision: 6, null: false 18 | else 19 | t.datetime :created_at, null: false 20 | end 21 | 22 | t.index [ :key ], unique: true 23 | end 24 | 25 | create_table :active_storage_attachments, id: primary_key_type do |t| 26 | t.string :name, null: false 27 | t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type 28 | t.references :blob, null: false, type: foreign_key_type 29 | 30 | if connection.supports_datetime_with_precision? 31 | t.datetime :created_at, precision: 6, null: false 32 | else 33 | t.datetime :created_at, null: false 34 | end 35 | 36 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true 37 | t.foreign_key :active_storage_blobs, column: :blob_id 38 | end 39 | 40 | create_table :active_storage_variant_records, id: primary_key_type do |t| 41 | t.belongs_to :blob, null: false, index: false, type: foreign_key_type 42 | t.string :variation_digest, null: false 43 | 44 | t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true 45 | t.foreign_key :active_storage_blobs, column: :blob_id 46 | end 47 | end 48 | 49 | private 50 | def primary_and_foreign_key_types 51 | config = Rails.configuration.generators 52 | setting = config.options[config.orm][:primary_key_type] 53 | primary_key_type = setting || :primary_key 54 | foreign_key_type = setting || :bigint 55 | [primary_key_type, foreign_key_type] 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # Turn false under Spring and add config.action_view.cache_template_loading = true 12 | config.cache_classes = true 13 | 14 | # Do not eager load code on boot. This avoids loading your whole application 15 | # just for the purpose of running a single test. If you are using a tool that 16 | # preloads Rails for running tests, you may have to set it to true. 17 | config.eager_load = false 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = false 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Store uploaded files on the local file system in a temporary directory. 37 | config.active_storage.service = :test 38 | 39 | config.action_mailer.perform_caching = false 40 | 41 | # Tell Action Mailer not to deliver emails to the real world. 42 | # The :test delivery method accumulates sent emails in the 43 | # ActionMailer::Base.deliveries array. 44 | config.action_mailer.delivery_method = :test 45 | 46 | # Print deprecation notices to the stderr. 47 | config.active_support.deprecation = :stderr 48 | 49 | # Raise exceptions for disallowed deprecations. 50 | config.active_support.disallowed_deprecation = :raise 51 | 52 | # Tell Active Support which deprecation messages to disallow. 53 | config.active_support.disallowed_deprecation_warnings = [] 54 | 55 | # Raises error for missing translations. 56 | # config.i18n.raise_on_missing_translations = true 57 | 58 | # Annotate rendered view with file names. 59 | # config.action_view.annotate_rendered_view_with_filenames = true 60 | end 61 | -------------------------------------------------------------------------------- /app/javascript/controllers/address_controller.js: -------------------------------------------------------------------------------- 1 | import {Controller} from '@hotwired/stimulus' 2 | 3 | export default class extends Controller { 4 | static targets = [ 5 | 'input', 6 | 'line1', 7 | 'line2', 8 | 'city', 9 | 'state', 10 | 'postal_code', 11 | 'country', 12 | 'lat', 13 | 'lng', 14 | ] 15 | 16 | connect() { 17 | console.log("Address controller is connected"); 18 | if(window.google) { 19 | this.initGoogle(); 20 | } 21 | } 22 | 23 | initGoogle() { 24 | // setup autocomplete 25 | console.log(`Google maps is initialized and the address controller knows about it`) 26 | console.log(google); 27 | this.autocomplete = new google.maps.places.Autocomplete(this.inputTarget, { 28 | fields: ["address_components", "geometry"], 29 | types: ["address"], 30 | }) 31 | this.autocomplete.addListener('place_changed', this.placeSelected.bind(this)) 32 | } 33 | 34 | placeSelected() { 35 | // use values from autocomplete 36 | const place = this.autocomplete.getPlace(); 37 | 38 | if(!place.geometry) { 39 | return; 40 | } 41 | 42 | this.latTarget.value = place.geometry.location.lat(); 43 | this.lngTarget.value = place.geometry.location.lng(); 44 | 45 | 46 | // Get each component of the address from the place details, 47 | // and then fill-in the corresponding field on the form. 48 | // place.address_components are google.maps.GeocoderAddressComponent objects 49 | // which are documented at http://goo.gle/3l5i5Mr 50 | for (const component of place.address_components) { 51 | const componentType = component.types[0]; 52 | 53 | switch (componentType) { 54 | case "street_number": { 55 | this.line1Target.value = `${component.long_name} ${this.line1Target.value}`; 56 | break; 57 | } 58 | 59 | case "route": { 60 | this.line1Target.value += component.short_name; 61 | break; 62 | } 63 | 64 | case "postal_code": { 65 | this.postal_codeTarget.value = `${component.long_name}${this.postal_codeTarget.value}`; 66 | break; 67 | } 68 | 69 | case "postal_code_suffix": { 70 | this.postal_codeTarget.value = `${this.postal_codeTarget.value}-${component.long_name}`; 71 | break; 72 | } 73 | case "locality": 74 | this.cityTarget.value = component.long_name; 75 | break; 76 | case "administrative_area_level_1": { 77 | this.stateTarget.value = component.short_name; 78 | break; 79 | } 80 | case "country": 81 | this.countryTarget.value = component.short_name; 82 | break; 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join("tmp/caching-dev.txt").exist? 20 | config.action_controller.perform_caching = true 21 | config.action_controller.enable_fragment_cache_logging = true 22 | 23 | config.cache_store = :memory_store 24 | config.public_file_server.headers = { 25 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 26 | } 27 | else 28 | config.action_controller.perform_caching = false 29 | 30 | config.cache_store = :null_store 31 | end 32 | 33 | # Store uploaded files on the local file system (see config/storage.yml for options). 34 | config.active_storage.service = :local 35 | 36 | # Don't care if the mailer can't send. 37 | config.action_mailer.raise_delivery_errors = false 38 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 39 | config.action_mailer.delivery_method = :letter_opener 40 | config.action_mailer.perform_deliveries = true 41 | 42 | config.action_mailer.perform_caching = false 43 | 44 | # Print deprecation notices to the Rails logger. 45 | config.active_support.deprecation = :log 46 | 47 | # Raise exceptions for disallowed deprecations. 48 | config.active_support.disallowed_deprecation = :raise 49 | 50 | # Tell Active Support which deprecation messages to disallow. 51 | config.active_support.disallowed_deprecation_warnings = [] 52 | 53 | # Raise an error on page load if there are pending migrations. 54 | config.active_record.migration_error = :page_load 55 | 56 | # Highlight code that triggered database queries in logs. 57 | config.active_record.verbose_query_logs = true 58 | 59 | # Suppress logger output for asset requests. 60 | config.assets.quiet = true 61 | 62 | # Raises error for missing translations. 63 | # config.i18n.raise_on_missing_translations = true 64 | 65 | # Annotate rendered view with file names. 66 | # config.action_view.annotate_rendered_view_with_filenames = true 67 | 68 | # Uncomment if you wish to allow Action Cable access from any origin. 69 | # config.action_cable.disable_request_forgery_protection = true 70 | end 71 | -------------------------------------------------------------------------------- /app/views/messages/index.html.erb: -------------------------------------------------------------------------------- 1 |

Messages

2 | 3 |
4 | 44 |
45 | 46 |
47 | 48 | 49 |
50 | 51 |
52 | 53 |
54 |
55 |
56 | 57 |
58 |
59 | -------------------------------------------------------------------------------- /app/jobs/event_job.rb: -------------------------------------------------------------------------------- 1 | class EventJob < ApplicationJob 2 | queue_as :default 3 | 4 | def perform(event) 5 | case event.source 6 | when "stripe" 7 | stripe_event = Stripe::Event.construct_from( 8 | JSON.parse(event.request_body, symbolize_names: true) 9 | ) 10 | begin 11 | handle_stripe(stripe_event) 12 | event.update( 13 | event_type: stripe_event.type, 14 | error_message: '', 15 | status: :processed 16 | ) 17 | rescue => e 18 | event.update( 19 | error_message: e.message, 20 | status: :failed 21 | ) 22 | end 23 | else 24 | event.update( 25 | error_message: "Unknown source #{event.source}", 26 | status: :failed 27 | ) 28 | end 29 | end 30 | 31 | def handle_stripe(event) 32 | case event.type 33 | when "account.updated" 34 | account = event.data.object 35 | user = User.find_by(stripe_account_id: account.id) 36 | user.update(charges_enabled: account.charges_enabled) 37 | when "checkout.session.completed" 38 | # do something with the checkout session and the reservation here. 39 | checkout_session = event.data.object 40 | reservation = Reservation.find_by(session_id: checkout_session.id) 41 | if reservation.nil? 42 | raise "No reservation found with Checkout Session ID #{checkout_session.id}" 43 | end 44 | reservation.update(status: :booked, stripe_payment_intent_id: checkout_session.payment_intent) 45 | HostReservationBookedNotification.with(reservation: reservation, message: "New Booking! #{reservation.guest.email} is coming #{reservation.start_date}").deliver_later(reservation.host) 46 | GuestReservationBookedNotification.with(reservation: reservation).deliver_later(reservation.guest) 47 | when "checkout.session.expired" 48 | checkout_session = event.data.object 49 | reservation = Reservation.find_by(session_id: checkout_session.id) 50 | if reservation.nil? 51 | raise "No reservation found with Checkout Session ID #{checkout_session.id}" 52 | end 53 | reservation.update(status: :expired) 54 | when "charge.refunded" 55 | charge = event.data.object 56 | reservation = Reservation.find_by(stripe_payment_intent_id: charge.payment_intent) 57 | if reservation.nil? 58 | raise "No reservation found with Payment Intent ID #{charge.payment_intent}" 59 | end 60 | reservation.update(status: :cancelled) 61 | when "identity.verification_session.verified" 62 | session = event.data.object 63 | user = User.find_by(id: session.metadata.user_id) 64 | if user.nil? 65 | raise "No user found with ID #{session.metadata.user_id}" 66 | end 67 | if session.status == "verified" 68 | user.update(identity_verified: true) 69 | else 70 | user.update(identity_verified: false) 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :bigint not null, primary key 6 | # created_at :datetime not null 7 | # updated_at :datetime not null 8 | # email :string default(""), not null 9 | # encrypted_password :string default(""), not null 10 | # reset_password_token :string 11 | # reset_password_sent_at :datetime 12 | # remember_created_at :datetime 13 | # sign_in_count :integer default(0), not null 14 | # current_sign_in_at :datetime 15 | # last_sign_in_at :datetime 16 | # current_sign_in_ip :string 17 | # last_sign_in_ip :string 18 | # confirmation_token :string 19 | # confirmed_at :datetime 20 | # confirmation_sent_at :datetime 21 | # unconfirmed_email :string 22 | # failed_attempts :integer default(0), not null 23 | # unlock_token :string 24 | # locked_at :datetime 25 | # provider :string 26 | # uid :string 27 | # name :string 28 | # stripe_customer_id :string 29 | # is_host :boolean default(FALSE) 30 | # stripe_account_id :string 31 | # charges_enabled :boolean default(FALSE) 32 | # 33 | class User < ApplicationRecord 34 | devise :database_authenticatable, :registerable, 35 | :recoverable, :rememberable, :validatable, 36 | :confirmable, :lockable, :timeoutable, :trackable, 37 | :omniauthable, omniauth_providers: [:google] 38 | has_many :listings, foreign_key: :host_id 39 | has_many :reservations, foreign_key: :guest_id 40 | has_many :host_reservations, class_name: 'Reservation', through: :listings, source: :reservations 41 | has_many :notifications, as: :recipient 42 | has_many :sent_messages, class_name: 'Message', foreign_key: 'from_user_id' 43 | has_many :received_messages, class_name: 'Message', foreign_key: 'to_user_id' 44 | 45 | after_commit :maybe_create_stripe_customer, on: [:create, :update] 46 | 47 | def all_reservations 48 | Reservation.where(guest: self).or(Reservation.where(listing: listings)) 49 | end 50 | 51 | def maybe_create_stripe_customer 52 | return if !stripe_customer_id.blank? 53 | 54 | customer = Stripe::Customer.create( 55 | email: email, 56 | name: name, 57 | metadata: { 58 | clearbnb_id: id 59 | } 60 | ) 61 | update(stripe_customer_id: customer.id) 62 | end 63 | 64 | def self.from_omniauth(auth) 65 | where(provider: auth.provider, uid: auth.uid).first_or_create do |user| 66 | user.email = auth.info.email 67 | user.password = Devise.friendly_token[0, 20] 68 | user.name = auth.info.name # assuming the user model has a name 69 | # user.image = auth.info.image # assuming the user model has an image 70 | # If you are using confirmable and the provider(s) you use validate emails, 71 | # uncomment the line below to skip the confirmation emails. 72 | user.skip_confirmation! 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /app/views/host/listings/new.html.erb: -------------------------------------------------------------------------------- 1 |

List your property

2 | 3 | <%= render partial: "shared/errors" %> 4 | 5 |
6 | 7 | 8 |
9 | 12 |
13 | 14 |
15 |
16 | 17 |
18 | 21 | 22 |
23 | 24 |
25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | -------------------------------------------------------------------------------- /app/models/book_listing.rb: -------------------------------------------------------------------------------- 1 | class BookListing 2 | include Rails.application.routes.url_helpers 3 | 4 | attr_reader :current_user, :params 5 | 6 | def initialize(current_user, params) 7 | @current_user = current_user 8 | @params = params 9 | end 10 | 11 | def save 12 | if valid? 13 | calendar_event.save && reservation.save 14 | end 15 | end 16 | 17 | def valid? 18 | calendar_event.valid? || reservation.valid? 19 | end 20 | 21 | def errors 22 | calendar_event.errors.full_messages + reservation.errors.full_messages 23 | end 24 | 25 | def checkout_url 26 | checkout_session.url 27 | end 28 | 29 | def checkout_session 30 | return @checkout_session if @checkout_session 31 | 32 | # Create a Stripe Checkout session 33 | @checkout_session = Stripe::Checkout::Session.create( 34 | success_url: reservation_url(reservation), 35 | cancel_url: expire_reservation_url(reservation) + "?session_id={CHECKOUT_SESSION_ID}", 36 | customer: current_user.stripe_customer_id, 37 | mode: 'payment', 38 | allow_promotion_codes: true, 39 | expires_at: 1.hour.from_now.to_i, 40 | submit_type: 'book', 41 | line_items: [{ 42 | price_data: { 43 | unit_amount: listing.nightly_price, 44 | currency: 'usd', 45 | product: listing.stripe_product_id, 46 | }, 47 | quantity: reservation.nights, # Num nights 48 | }, { 49 | price_data: { 50 | unit_amount: listing.cleaning_fee, 51 | currency: 'usd', 52 | product: 'prod_KfZvqsikciZVFr', # Cleaning fee product ID 53 | }, 54 | quantity: 1 55 | }], 56 | metadata: { 57 | listing_id: listing.id, 58 | reservation_id: reservation.id, 59 | guest_id: current_user.id, 60 | start_date: start_date, 61 | end_date: end_date, 62 | }, 63 | payment_intent_data: { 64 | application_fee_amount: ((listing.cleaning_fee + listing.nightly_price) * 0.10).to_i, 65 | transfer_data: { 66 | destination: listing.host.stripe_account_id 67 | }, 68 | metadata: { 69 | reservation_id: reservation.id, 70 | listing_id: listing.id, 71 | reservation_id: reservation.id, 72 | guest_id: current_user.id, 73 | start_date: start_date, 74 | end_date: end_date, 75 | } 76 | } 77 | ) 78 | reservation.update(session_id: @checkout_session.id) 79 | checkout_session 80 | end 81 | 82 | def calendar_event 83 | @calendar_event ||= CalendarEvent.new( 84 | listing: listing, 85 | reservation: reservation, 86 | status: :reserved, 87 | start_date: start_date, 88 | end_date: end_date, 89 | ) 90 | end 91 | 92 | def reservation 93 | @reservation ||= Reservation.new( 94 | listing: listing, 95 | guest: current_user, 96 | ) 97 | end 98 | 99 | def listing 100 | @listing ||= Listing.find(listing_id) 101 | end 102 | 103 | private 104 | 105 | def start_date 106 | params[:start_date] 107 | end 108 | 109 | def end_date 110 | params[:end_date] 111 | end 112 | 113 | def listing_id 114 | params[:listing_id] 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.3 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On macOS with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On macOS 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 | # https://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | development: 25 | <<: *default 26 | database: clearbnb_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 running Rails. 32 | #username: clearbnb 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: clearbnb_test 61 | 62 | # As with config/credentials.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 or a full connection URL as an environment 67 | # variable when you boot the app. For example: 68 | # 69 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 70 | # 71 | # If the connection URL is provided in the special DATABASE_URL environment 72 | # variable, Rails will automatically merge its configuration values on top of 73 | # the values provided in this file. Alternatively, you can specify a connection 74 | # URL environment variable explicitly: 75 | # 76 | # production: 77 | # url: <%= ENV['MY_APP_DATABASE_URL'] %> 78 | # 79 | # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database 80 | # for a full overview on how database connection configuration can be specified. 81 | # 82 | production: 83 | <<: *default 84 | database: clearbnb_production 85 | username: clearbnb 86 | password: <%= ENV['CLEARBNB_DATABASE_PASSWORD'] %> 87 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= 65 | env_var_version || cli_arg_version || 66 | lockfile_version 67 | end 68 | 69 | def bundler_requirement 70 | return "#{Gem::Requirement.default}.a" unless bundler_version 71 | 72 | bundler_gem_version = Gem::Version.new(bundler_version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /app/views/host/listings/index.html.erb: -------------------------------------------------------------------------------- 1 |

Your listings

2 | 3 | 4 | Add a new listing 5 | 6 | 7 | 8 |
9 | 67 |
68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/heartcombo/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | email_changed: 27 | subject: "Email Changed" 28 | password_change: 29 | subject: "Password Changed" 30 | omniauth_callbacks: 31 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 32 | success: "Successfully authenticated from %{kind} account." 33 | passwords: 34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 35 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 36 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 37 | updated: "Your password has been changed successfully. You are now signed in." 38 | updated_not_active: "Your password has been changed successfully." 39 | registrations: 40 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 41 | signed_up: "Welcome! You have signed up successfully." 42 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 43 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 44 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." 46 | updated: "Your account has been updated successfully." 47 | updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again." 48 | sessions: 49 | signed_in: "Signed in successfully." 50 | signed_out: "Signed out successfully." 51 | already_signed_out: "Signed out successfully." 52 | unlocks: 53 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 54 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 55 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 56 | errors: 57 | messages: 58 | already_confirmed: "was already confirmed, please try signing in" 59 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 60 | expired: "has expired, please request a new one" 61 | not_found: "not found" 62 | not_locked: "was not locked" 63 | not_saved: 64 | one: "1 error prohibited this %{resource} from being saved:" 65 | other: "%{count} errors prohibited this %{resource} from being saved:" 66 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults_7_0.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file eases your Rails 7.0 framework defaults upgrade. 4 | # 5 | # Uncomment each configuration one by one to switch to the new default. 6 | # Once your application is ready to run with all new defaults, you can remove 7 | # this file and set the `config.load_defaults` to `7.0`. 8 | # 9 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 10 | # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html 11 | 12 | # Raise an error when trying to use forgery protection without a working 13 | # session. 14 | # Rails.application.config.action_controller.silence_disabled_session_errors = false 15 | 16 | # `button_to` view helper will render ` 77 | 78 | 79 |
80 | 81 | Forgot your password? 82 | 83 | 84 | 85 | Already registered? 86 | 87 | 88 | Didn't receive confirmation instructions? 89 | 90 | 91 | Didn't receive unlock instructions? 92 | 93 |
94 | 95 | 96 | 97 | 98 | 99 | 102 | 103 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | Workflow 6 |

7 | Sign in to your account 8 |

9 | 10 | <%= render "devise/shared/error_messages", resource: resource %> 11 | <%= render "shared/notices" %> 12 |
13 | 14 | 15 |
16 |
17 |
18 |

19 | Sign in with 20 |

21 | 22 | 30 |
31 | 32 |
33 | 36 |
37 | 38 | Or continue with 39 | 40 |
41 |
42 |
43 | 44 |
45 |
46 | 47 |
48 | 51 |
52 | 53 |
54 |
55 | 56 |
57 | 60 |
61 | 62 |
63 |
64 | 65 |
66 | <% if devise_mapping.rememberable? %> 67 |
68 | 69 | 72 |
73 | <% end %> 74 |
75 | 76 |
77 | 80 |
81 | 82 | 96 | 97 |
98 |
99 |
100 |
101 |
102 | 105 |
106 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 26 | 27 | # Compress CSS using a preprocessor. 28 | # config.assets.css_compressor = :sass 29 | 30 | # Do not fallback to assets pipeline if a precompiled asset is missed. 31 | config.assets.compile = false 32 | 33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 34 | # config.asset_host = "http://assets.example.com" 35 | 36 | # Specifies the header that your server uses for sending files. 37 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 38 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 39 | 40 | # Store uploaded files on the local file system (see config/storage.yml for options). 41 | config.active_storage.service = :local 42 | 43 | # Mount Action Cable outside main process or domain. 44 | # config.action_cable.mount_path = nil 45 | # config.action_cable.url = "wss://example.com/cable" 46 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] 47 | 48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 49 | # config.force_ssl = true 50 | 51 | # Include generic and useful information about system operation, but avoid logging too much 52 | # information to avoid inadvertent exposure of personally identifiable information (PII). 53 | config.log_level = :info 54 | 55 | # Prepend all log lines with the following tags. 56 | config.log_tags = [ :request_id ] 57 | 58 | # Use a different cache store in production. 59 | # config.cache_store = :mem_cache_store 60 | 61 | # Use a real queuing backend for Active Job (and separate queues per environment). 62 | # config.active_job.queue_adapter = :resque 63 | # config.active_job.queue_name_prefix = "clearbnb_production" 64 | 65 | config.action_mailer.perform_caching = false 66 | 67 | # Ignore bad email addresses and do not raise email delivery errors. 68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 69 | # config.action_mailer.raise_delivery_errors = false 70 | 71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 72 | # the I18n.default_locale when a translation cannot be found). 73 | config.i18n.fallbacks = true 74 | 75 | # Don't log any deprecations. 76 | config.active_support.report_deprecations = false 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | 81 | # Use a different logger for distributed setups. 82 | # require "syslog/logger" 83 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") 84 | 85 | if ENV["RAILS_LOG_TO_STDOUT"].present? 86 | logger = ActiveSupport::Logger.new(STDOUT) 87 | logger.formatter = config.log_formatter 88 | config.logger = ActiveSupport::TaggedLogging.new(logger) 89 | end 90 | 91 | # Do not dump schema after migrations. 92 | config.active_record.dump_schema_after_migration = false 93 | 94 | # Inserts middleware to perform automatic connection switching. 95 | # The `database_selector` hash is used to pass options to the DatabaseSelector 96 | # middleware. The `delay` is used to determine how long to wait after a write 97 | # to send a subsequent read to the primary. 98 | # 99 | # The `database_resolver` class is used by the middleware to determine which 100 | # database is appropriate to use based on the time delay. 101 | # 102 | # The `database_resolver_context` class is used by the middleware to set 103 | # timestamps for the last write to the primary. The resolver uses the context 104 | # class timestamps to determine how long to wait before reading from the 105 | # replica. 106 | # 107 | # By default Rails will store a last write timestamp in the session. The 108 | # DatabaseSelector middleware is designed as such you can define your own 109 | # strategy for connection switching and pass that into the middleware through 110 | # these configuration options. 111 | # config.active_record.database_selector = { delay: 2.seconds } 112 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver 113 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session 114 | end 115 | -------------------------------------------------------------------------------- /app/views/reservations/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Your upcoming trips

5 |
6 |
7 |
8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | <% @reservations.each do |reservation| %> 25 | 26 | 29 | 32 | 35 | 38 | 41 | 42 | <% end %> 43 | 44 | 45 | 46 |
ListingCheck inCheck outStatus 19 | View 20 |
27 | <%= link_to reservation.listing.title, reservation_path(reservation) %> 28 | 30 | <%= reservation.start_date %> 31 | 33 | <%= reservation.end_date %> 34 | 36 | <%= reservation.status %> 37 | 39 | View, Lindsay Walton 40 |
47 |
48 |
49 |
50 |
51 |
52 | 53 | <% if !@host_reservations.empty? %> 54 |
55 |
56 |
57 |

You are hosting these guests

58 |
59 |
60 |
61 |
62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | <% @host_reservations.each do |reservation| %> 79 | 80 | 83 | 86 | 89 | 92 | 95 | 98 | 99 | <% end %> 100 | 101 |
ListingGuestCheck inCheck outStatus 73 | View 74 |
81 | <%= link_to reservation.listing.title, host_reservation_path(reservation) %> 82 | 84 | <%= reservation.guest.email %> 85 | 87 | <%= reservation.start_date %> 88 | 90 | <%= reservation.end_date %> 91 | 93 | <%= reservation.status %> 94 | 96 | View, Lindsay Walton 97 |
102 |
103 |
104 |
105 |
106 |
107 | <% end %> 108 | -------------------------------------------------------------------------------- /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 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2022_03_17_151520) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "active_storage_attachments", force: :cascade do |t| 19 | t.string "name", null: false 20 | t.string "record_type", null: false 21 | t.bigint "record_id", null: false 22 | t.bigint "blob_id", null: false 23 | t.datetime "created_at", precision: 6, null: false 24 | t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" 25 | t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true 26 | end 27 | 28 | create_table "active_storage_blobs", force: :cascade do |t| 29 | t.string "key", null: false 30 | t.string "filename", null: false 31 | t.string "content_type" 32 | t.text "metadata" 33 | t.string "service_name", null: false 34 | t.bigint "byte_size", null: false 35 | t.string "checksum", null: false 36 | t.datetime "created_at", precision: 6, null: false 37 | t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true 38 | end 39 | 40 | create_table "active_storage_variant_records", force: :cascade do |t| 41 | t.bigint "blob_id", null: false 42 | t.string "variation_digest", null: false 43 | t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true 44 | end 45 | 46 | create_table "beds", force: :cascade do |t| 47 | t.bigint "room_id", null: false 48 | t.integer "bed_size" 49 | t.datetime "created_at", precision: 6, null: false 50 | t.datetime "updated_at", precision: 6, null: false 51 | t.index ["room_id"], name: "index_beds_on_room_id" 52 | end 53 | 54 | create_table "calendar_events", force: :cascade do |t| 55 | t.bigint "listing_id", null: false 56 | t.bigint "reservation_id" 57 | t.integer "status", null: false 58 | t.date "start_date", null: false 59 | t.date "end_date", null: false 60 | t.datetime "created_at", precision: 6, null: false 61 | t.datetime "updated_at", precision: 6, null: false 62 | t.index ["listing_id"], name: "index_calendar_events_on_listing_id" 63 | t.index ["reservation_id"], name: "index_calendar_events_on_reservation_id" 64 | end 65 | 66 | create_table "events", force: :cascade do |t| 67 | t.text "request_body" 68 | t.integer "status", default: 0 69 | t.text "error_message" 70 | t.string "source" 71 | t.string "event_type" 72 | t.datetime "created_at", precision: 6, null: false 73 | t.datetime "updated_at", precision: 6, null: false 74 | end 75 | 76 | create_table "listings", force: :cascade do |t| 77 | t.bigint "host_id", null: false 78 | t.string "title", null: false 79 | t.text "about" 80 | t.integer "max_guests", default: 1 81 | t.string "address_line1" 82 | t.string "address_line2" 83 | t.string "city" 84 | t.string "state" 85 | t.string "postal_code" 86 | t.string "country" 87 | t.decimal "lat", precision: 10, scale: 6 88 | t.decimal "lng", precision: 10, scale: 6 89 | t.integer "status", default: 0 90 | t.datetime "created_at", precision: 6, null: false 91 | t.datetime "updated_at", precision: 6, null: false 92 | t.integer "nightly_price" 93 | t.integer "cleaning_fee" 94 | t.string "stripe_product_id" 95 | t.index ["host_id"], name: "index_listings_on_host_id" 96 | end 97 | 98 | create_table "messages", force: :cascade do |t| 99 | t.bigint "from_user_id", null: false 100 | t.bigint "to_user_id", null: false 101 | t.bigint "reservation_id" 102 | t.string "content", default: "", null: false 103 | t.datetime "created_at", precision: 6, null: false 104 | t.datetime "updated_at", precision: 6, null: false 105 | t.index ["from_user_id"], name: "index_messages_on_from_user_id" 106 | t.index ["reservation_id"], name: "index_messages_on_reservation_id" 107 | t.index ["to_user_id"], name: "index_messages_on_to_user_id" 108 | end 109 | 110 | create_table "notifications", force: :cascade do |t| 111 | t.string "recipient_type", null: false 112 | t.bigint "recipient_id", null: false 113 | t.string "type", null: false 114 | t.jsonb "params" 115 | t.datetime "read_at", precision: 6 116 | t.datetime "created_at", precision: 6, null: false 117 | t.datetime "updated_at", precision: 6, null: false 118 | t.index ["read_at"], name: "index_notifications_on_read_at" 119 | t.index ["recipient_type", "recipient_id"], name: "index_notifications_on_recipient" 120 | end 121 | 122 | create_table "photos", force: :cascade do |t| 123 | t.bigint "listing_id", null: false 124 | t.string "caption" 125 | t.datetime "created_at", precision: 6, null: false 126 | t.datetime "updated_at", precision: 6, null: false 127 | t.index ["listing_id"], name: "index_photos_on_listing_id" 128 | end 129 | 130 | create_table "reservations", force: :cascade do |t| 131 | t.bigint "listing_id", null: false 132 | t.bigint "guest_id", null: false 133 | t.string "session_id" 134 | t.integer "status", default: 0 135 | t.datetime "created_at", precision: 6, null: false 136 | t.datetime "updated_at", precision: 6, null: false 137 | t.string "stripe_payment_intent_id" 138 | t.string "stripe_refund_id" 139 | t.index ["guest_id"], name: "index_reservations_on_guest_id" 140 | t.index ["listing_id"], name: "index_reservations_on_listing_id" 141 | end 142 | 143 | create_table "rooms", force: :cascade do |t| 144 | t.bigint "listing_id", null: false 145 | t.integer "room_type" 146 | t.datetime "created_at", precision: 6, null: false 147 | t.datetime "updated_at", precision: 6, null: false 148 | t.index ["listing_id"], name: "index_rooms_on_listing_id" 149 | end 150 | 151 | create_table "users", force: :cascade do |t| 152 | t.datetime "created_at", precision: 6, null: false 153 | t.datetime "updated_at", precision: 6, null: false 154 | t.string "email", default: "", null: false 155 | t.string "encrypted_password", default: "", null: false 156 | t.string "reset_password_token" 157 | t.datetime "reset_password_sent_at", precision: 6 158 | t.datetime "remember_created_at", precision: 6 159 | t.integer "sign_in_count", default: 0, null: false 160 | t.datetime "current_sign_in_at", precision: 6 161 | t.datetime "last_sign_in_at", precision: 6 162 | t.string "current_sign_in_ip" 163 | t.string "last_sign_in_ip" 164 | t.string "confirmation_token" 165 | t.datetime "confirmed_at", precision: 6 166 | t.datetime "confirmation_sent_at", precision: 6 167 | t.string "unconfirmed_email" 168 | t.integer "failed_attempts", default: 0, null: false 169 | t.string "unlock_token" 170 | t.datetime "locked_at", precision: 6 171 | t.string "provider" 172 | t.string "uid" 173 | t.string "name" 174 | t.string "stripe_customer_id" 175 | t.boolean "is_host", default: false 176 | t.string "stripe_account_id" 177 | t.boolean "charges_enabled", default: false 178 | t.string "phone_number" 179 | t.boolean "identity_verified", default: false 180 | t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true 181 | t.index ["email"], name: "index_users_on_email", unique: true 182 | t.index ["provider", "uid"], name: "index_users_on_provider_and_uid" 183 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 184 | t.index ["uid"], name: "index_users_on_uid" 185 | t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true 186 | end 187 | 188 | add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" 189 | add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" 190 | add_foreign_key "beds", "rooms" 191 | add_foreign_key "calendar_events", "listings" 192 | add_foreign_key "calendar_events", "reservations" 193 | add_foreign_key "listings", "users", column: "host_id" 194 | add_foreign_key "messages", "reservations" 195 | add_foreign_key "messages", "users", column: "from_user_id" 196 | add_foreign_key "messages", "users", column: "to_user_id" 197 | add_foreign_key "photos", "listings" 198 | add_foreign_key "reservations", "listings" 199 | add_foreign_key "reservations", "users", column: "guest_id" 200 | add_foreign_key "rooms", "listings" 201 | end 202 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/rails/rails.git 3 | revision: 4f818c7202ee26b8d33f8817c6064ba94e386596 4 | branch: main 5 | specs: 6 | actioncable (7.1.0.alpha) 7 | actionpack (= 7.1.0.alpha) 8 | activesupport (= 7.1.0.alpha) 9 | nio4r (~> 2.0) 10 | websocket-driver (>= 0.6.1) 11 | zeitwerk (~> 2.6) 12 | actionmailbox (7.1.0.alpha) 13 | actionpack (= 7.1.0.alpha) 14 | activejob (= 7.1.0.alpha) 15 | activerecord (= 7.1.0.alpha) 16 | activestorage (= 7.1.0.alpha) 17 | activesupport (= 7.1.0.alpha) 18 | mail (>= 2.7.1) 19 | net-imap 20 | net-pop 21 | net-smtp 22 | actionmailer (7.1.0.alpha) 23 | actionpack (= 7.1.0.alpha) 24 | actionview (= 7.1.0.alpha) 25 | activejob (= 7.1.0.alpha) 26 | activesupport (= 7.1.0.alpha) 27 | mail (~> 2.5, >= 2.5.4) 28 | net-imap 29 | net-pop 30 | net-smtp 31 | rails-dom-testing (~> 2.0) 32 | actionpack (7.1.0.alpha) 33 | actionview (= 7.1.0.alpha) 34 | activesupport (= 7.1.0.alpha) 35 | rack (~> 2.0, >= 2.2.0) 36 | rack-test (>= 0.6.3) 37 | rails-dom-testing (~> 2.0) 38 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 39 | actiontext (7.1.0.alpha) 40 | actionpack (= 7.1.0.alpha) 41 | activerecord (= 7.1.0.alpha) 42 | activestorage (= 7.1.0.alpha) 43 | activesupport (= 7.1.0.alpha) 44 | globalid (>= 0.6.0) 45 | nokogiri (>= 1.8.5) 46 | actionview (7.1.0.alpha) 47 | activesupport (= 7.1.0.alpha) 48 | builder (~> 3.1) 49 | erubi (~> 1.4) 50 | rails-dom-testing (~> 2.0) 51 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 52 | activejob (7.1.0.alpha) 53 | activesupport (= 7.1.0.alpha) 54 | globalid (>= 0.3.6) 55 | activemodel (7.1.0.alpha) 56 | activesupport (= 7.1.0.alpha) 57 | activerecord (7.1.0.alpha) 58 | activemodel (= 7.1.0.alpha) 59 | activesupport (= 7.1.0.alpha) 60 | activestorage (7.1.0.alpha) 61 | actionpack (= 7.1.0.alpha) 62 | activejob (= 7.1.0.alpha) 63 | activerecord (= 7.1.0.alpha) 64 | activesupport (= 7.1.0.alpha) 65 | marcel (~> 1.0) 66 | mini_mime (>= 1.1.0) 67 | activesupport (7.1.0.alpha) 68 | concurrent-ruby (~> 1.0, >= 1.0.2) 69 | connection_pool (>= 2.2.5) 70 | i18n (>= 1.6, < 2) 71 | minitest (>= 5.1) 72 | tzinfo (~> 2.0) 73 | rails (7.1.0.alpha) 74 | actioncable (= 7.1.0.alpha) 75 | actionmailbox (= 7.1.0.alpha) 76 | actionmailer (= 7.1.0.alpha) 77 | actionpack (= 7.1.0.alpha) 78 | actiontext (= 7.1.0.alpha) 79 | actionview (= 7.1.0.alpha) 80 | activejob (= 7.1.0.alpha) 81 | activemodel (= 7.1.0.alpha) 82 | activerecord (= 7.1.0.alpha) 83 | activestorage (= 7.1.0.alpha) 84 | activesupport (= 7.1.0.alpha) 85 | bundler (>= 1.15.0) 86 | railties (= 7.1.0.alpha) 87 | railties (7.1.0.alpha) 88 | actionpack (= 7.1.0.alpha) 89 | activesupport (= 7.1.0.alpha) 90 | method_source 91 | rake (>= 12.2) 92 | thor (~> 1.0) 93 | zeitwerk (~> 2.6) 94 | 95 | GEM 96 | remote: https://rubygems.org/ 97 | specs: 98 | addressable (2.8.0) 99 | public_suffix (>= 2.0.2, < 5.0) 100 | annotate (3.2.0) 101 | activerecord (>= 3.2, < 8.0) 102 | rake (>= 10.4, < 14.0) 103 | bcrypt (3.1.18) 104 | bindex (0.8.1) 105 | bootsnap (1.12.0) 106 | msgpack (~> 1.2) 107 | builder (3.2.4) 108 | byebug (11.1.3) 109 | coderay (1.1.3) 110 | concurrent-ruby (1.1.10) 111 | connection_pool (2.2.5) 112 | crass (1.0.6) 113 | devise (4.8.1) 114 | bcrypt (~> 3.0) 115 | orm_adapter (~> 0.1) 116 | railties (>= 4.1.0) 117 | responders 118 | warden (~> 1.2.3) 119 | digest (3.1.0) 120 | domain_name (0.5.20190701) 121 | unf (>= 0.0.5, < 1.0.0) 122 | erubi (1.10.0) 123 | faker (2.21.0) 124 | i18n (>= 1.8.11, < 2) 125 | faraday (2.3.0) 126 | faraday-net_http (~> 2.0) 127 | ruby2_keywords (>= 0.0.4) 128 | faraday-net_http (2.0.3) 129 | ffi (1.15.5) 130 | ffi-compiler (1.0.1) 131 | ffi (>= 1.0.0) 132 | rake 133 | globalid (1.0.0) 134 | activesupport (>= 5.0) 135 | hashie (5.0.0) 136 | http (5.1.0) 137 | addressable (~> 2.8) 138 | http-cookie (~> 1.0) 139 | http-form_data (~> 2.2) 140 | llhttp-ffi (~> 0.4.0) 141 | http-cookie (1.0.5) 142 | domain_name (~> 0.5) 143 | http-form_data (2.3.0) 144 | i18n (1.12.0) 145 | concurrent-ruby (~> 1.0) 146 | image_processing (1.12.2) 147 | mini_magick (>= 4.9.5, < 5) 148 | ruby-vips (>= 2.0.17, < 3) 149 | jbuilder (2.11.5) 150 | actionview (>= 5.0.0) 151 | activesupport (>= 5.0.0) 152 | jsbundling-rails (1.0.3) 153 | railties (>= 6.0.0) 154 | jwt (2.4.1) 155 | launchy (2.5.0) 156 | addressable (~> 2.7) 157 | letter_opener (1.8.1) 158 | launchy (>= 2.2, < 3) 159 | listen (3.7.1) 160 | rb-fsevent (~> 0.10, >= 0.10.3) 161 | rb-inotify (~> 0.9, >= 0.9.10) 162 | llhttp-ffi (0.4.0) 163 | ffi-compiler (~> 1.0) 164 | rake (~> 13.0) 165 | loofah (2.18.0) 166 | crass (~> 1.0.2) 167 | nokogiri (>= 1.5.9) 168 | mail (2.7.1) 169 | mini_mime (>= 0.1.1) 170 | marcel (1.0.2) 171 | method_source (1.0.0) 172 | mini_magick (4.11.0) 173 | mini_mime (1.1.2) 174 | mini_portile2 (2.8.0) 175 | minitest (5.16.2) 176 | mono_logger (1.1.1) 177 | msgpack (1.5.3) 178 | multi_json (1.15.0) 179 | multi_xml (0.6.0) 180 | mustermann (1.1.1) 181 | ruby2_keywords (~> 0.0.1) 182 | net-imap (0.2.3) 183 | digest 184 | net-protocol 185 | strscan 186 | net-pop (0.1.1) 187 | digest 188 | net-protocol 189 | timeout 190 | net-protocol (0.1.3) 191 | timeout 192 | net-smtp (0.3.1) 193 | digest 194 | net-protocol 195 | timeout 196 | nio4r (2.5.8) 197 | nokogiri (1.13.7) 198 | mini_portile2 (~> 2.8.0) 199 | racc (~> 1.4) 200 | noticed (1.5.9) 201 | http (>= 4.0.0) 202 | rails (>= 5.2.0) 203 | oauth2 (1.4.10) 204 | faraday (>= 0.17.3, < 3.0) 205 | jwt (>= 1.0, < 3.0) 206 | multi_json (~> 1.3) 207 | multi_xml (~> 0.5) 208 | rack (>= 1.2, < 3) 209 | omniauth (2.1.0) 210 | hashie (>= 3.4.6) 211 | rack (>= 2.2.3) 212 | rack-protection 213 | omniauth-google-oauth2 (1.0.1) 214 | jwt (>= 2.0) 215 | oauth2 (~> 1.1) 216 | omniauth (~> 2.0) 217 | omniauth-oauth2 (~> 1.7.1) 218 | omniauth-oauth2 (1.7.3) 219 | oauth2 (>= 1.4, < 3) 220 | omniauth (>= 1.9, < 3) 221 | omniauth-rails_csrf_protection (1.0.1) 222 | actionpack (>= 4.2) 223 | omniauth (~> 2.0) 224 | orm_adapter (0.5.0) 225 | pagy (5.10.1) 226 | activesupport 227 | pg (1.4.1) 228 | pry (0.14.1) 229 | coderay (~> 1.1) 230 | method_source (~> 1.0) 231 | pry-rails (0.3.9) 232 | pry (>= 0.10.4) 233 | public_suffix (4.0.7) 234 | puma (5.6.4) 235 | nio4r (~> 2.0) 236 | racc (1.6.0) 237 | rack (2.2.4) 238 | rack-mini-profiler (2.3.4) 239 | rack (>= 1.2.0) 240 | rack-protection (2.2.0) 241 | rack 242 | rack-test (2.0.2) 243 | rack (>= 1.3) 244 | rails-dom-testing (2.0.3) 245 | activesupport (>= 4.2.0) 246 | nokogiri (>= 1.6) 247 | rails-html-sanitizer (1.4.3) 248 | loofah (~> 2.3) 249 | rake (13.0.6) 250 | rb-fsevent (0.11.1) 251 | rb-inotify (0.10.1) 252 | ffi (~> 1.0) 253 | redis (4.7.1) 254 | redis-namespace (1.8.2) 255 | redis (>= 3.0.4) 256 | responders (3.0.1) 257 | actionpack (>= 5.0) 258 | railties (>= 5.0) 259 | resque (2.2.1) 260 | mono_logger (~> 1.0) 261 | multi_json (~> 1.0) 262 | redis-namespace (~> 1.6) 263 | sinatra (>= 0.9.2) 264 | ruby-vips (2.1.4) 265 | ffi (~> 1.12) 266 | ruby2_keywords (0.0.5) 267 | sass-rails (6.0.0) 268 | sassc-rails (~> 2.1, >= 2.1.1) 269 | sassc (2.4.0) 270 | ffi (~> 1.9) 271 | sassc-rails (2.1.2) 272 | railties (>= 4.0.0) 273 | sassc (>= 2.0) 274 | sprockets (> 3.0) 275 | sprockets-rails 276 | tilt 277 | sinatra (2.2.0) 278 | mustermann (~> 1.0) 279 | rack (~> 2.2) 280 | rack-protection (= 2.2.0) 281 | tilt (~> 2.0) 282 | spring (4.0.0) 283 | sprockets (4.1.1) 284 | concurrent-ruby (~> 1.0) 285 | rack (> 1, < 3) 286 | sprockets-rails (3.4.2) 287 | actionpack (>= 5.2) 288 | activesupport (>= 5.2) 289 | sprockets (>= 3.0.0) 290 | stimulus-rails (1.0.4) 291 | railties (>= 6.0.0) 292 | stripe (6.5.0) 293 | strscan (3.0.3) 294 | tailwindcss-rails (0.4.3) 295 | rails (>= 6.0.0) 296 | thor (1.2.1) 297 | tilt (2.0.10) 298 | timeout (0.3.0) 299 | turbo-rails (1.1.1) 300 | actionpack (>= 6.0.0) 301 | activejob (>= 6.0.0) 302 | railties (>= 6.0.0) 303 | tzinfo (2.0.4) 304 | concurrent-ruby (~> 1.0) 305 | unf (0.1.4) 306 | unf_ext 307 | unf_ext (0.0.8.2) 308 | validates_overlap (1.0.0) 309 | rails (>= 6.0.0) 310 | warden (1.2.9) 311 | rack (>= 2.0.9) 312 | web-console (4.2.0) 313 | actionview (>= 6.0.0) 314 | activemodel (>= 6.0.0) 315 | bindex (>= 0.4.0) 316 | railties (>= 6.0.0) 317 | websocket-driver (0.7.5) 318 | websocket-extensions (>= 0.1.0) 319 | websocket-extensions (0.1.5) 320 | zeitwerk (2.6.0) 321 | 322 | PLATFORMS 323 | ruby 324 | 325 | DEPENDENCIES 326 | annotate 327 | bootsnap 328 | byebug 329 | devise 330 | faker 331 | image_processing (~> 1.2) 332 | jbuilder (~> 2.7) 333 | jsbundling-rails 334 | letter_opener 335 | listen (~> 3.3) 336 | noticed (~> 1.5) 337 | omniauth-google-oauth2 338 | omniauth-rails_csrf_protection 339 | pagy (~> 5.10) 340 | pg (~> 1.1) 341 | pry-rails (~> 0.3.9) 342 | puma (~> 5.0) 343 | rack-mini-profiler (~> 2.0) 344 | rails! 345 | resque 346 | sass-rails (>= 6) 347 | spring 348 | sprockets-rails 349 | stimulus-rails 350 | stripe 351 | tailwindcss-rails (~> 0.4.3) 352 | turbo-rails 353 | tzinfo-data 354 | validates_overlap (~> 1.0) 355 | web-console (>= 4.1.0) 356 | 357 | RUBY VERSION 358 | ruby 3.1.2p20 359 | 360 | BUNDLED WITH 361 | 2.3.17 362 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Clearbnb 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | <%= stylesheet_link_tag "inter-font", "data-turbo-track": "reload" %> 10 | <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> 11 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 12 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 155 |
156 | 157 |
158 |
159 | 160 |
161 | <%= yield %> 162 |
163 | 164 |
165 |
166 |
167 | 168 | 169 | 170 | --------------------------------------------------------------------------------