├── .DS_Store
├── .gitignore
├── .ruby-version
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
├── .DS_Store
├── assets
│ ├── .DS_Store
│ ├── config
│ │ └── manifest.js
│ ├── images
│ │ ├── .keep
│ │ ├── Tent-Mono.png
│ │ ├── Wordmark-Mono.png
│ │ ├── favicon.ico
│ │ ├── foot-mountains.png
│ │ ├── logo.png
│ │ ├── more_smores.png
│ │ ├── splash_background.jpeg
│ │ └── splash_background_2.jpg
│ ├── javascripts
│ │ ├── application.js
│ │ ├── bundle.js
│ │ ├── cable.js
│ │ └── channels
│ │ │ └── .keep
│ └── stylesheets
│ │ ├── application.scss
│ │ ├── booking
│ │ ├── booking_form.scss
│ │ └── booking_index.scss
│ │ ├── footer.scss
│ │ ├── search.scss
│ │ ├── session_form
│ │ └── session_form.scss
│ │ └── sites
│ │ ├── sites_detail.scss
│ │ ├── sites_form.scss
│ │ └── sites_show.scss
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
├── controllers
│ ├── api
│ │ ├── bookings_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── sites_controller.rb
│ │ └── users_controller.rb
│ ├── application_controller.rb
│ ├── concerns
│ │ └── .keep
│ └── static_pages_controller.rb
├── helpers
│ └── application_helper.rb
├── jobs
│ └── application_job.rb
├── mailers
│ └── application_mailer.rb
├── models
│ ├── application_record.rb
│ ├── booking.rb
│ ├── concerns
│ │ └── .keep
│ ├── site.rb
│ └── user.rb
└── views
│ ├── api
│ ├── bookings
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── sites
│ │ ├── _sites.json.jbuilder
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ └── users
│ │ ├── _user.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── layouts
│ ├── .DS_Store
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
│ └── static_pages
│ └── root.html.erb
├── bin
├── bundle
├── rails
├── rake
├── setup
├── spring
├── update
└── yarn
├── bookings_screen.png
├── config.ru
├── config
├── application.rb
├── boot.rb
├── cable.yml
├── credentials.yml.enc
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── application_controller_renderer.rb
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── content_security_policy.rb
│ ├── cookies_serializer.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ └── wrap_parameters.rb
├── locales
│ └── en.yml
├── puma.rb
├── routes.rb
├── spring.rb
└── storage.yml
├── db
├── migrate
│ ├── 20190430202022_create_users.rb
│ ├── 20190430202540_create_sites.rb
│ ├── 20190502165308_drop_sites.rb
│ ├── 20190502165400_recreate_sites.rb
│ ├── 20190503180949_create_active_storage_tables.active_storage.rb
│ ├── 20190503181527_remove_sites.rb
│ ├── 20190503181602_build_sites.rb
│ ├── 20190503183154_update_rv_allowed.rb
│ ├── 20190503183332_add_sites_table.rb
│ ├── 20190504204954_add_site_description.rb
│ ├── 20190507165220_add_state_city_sites.rb
│ ├── 20190507172411_add_price.rb
│ ├── 20190508171716_bookings.rb
│ └── 20190508175230_add_price_bookings.rb
├── schema.rb
└── seeds.rb
├── frontend
├── .DS_Store
├── actions
│ ├── bookings_actions.js
│ ├── modal_actions.js
│ ├── session_actions.js
│ └── site_actions.js
├── assets
│ ├── .DS_Store
│ └── Tent-Mono.png
├── components
│ ├── .DS_Store
│ ├── App.jsx
│ ├── bookings
│ │ ├── booking_container.js
│ │ ├── booking_detail.jsx
│ │ ├── booking_form.jsx
│ │ ├── booking_index.jsx
│ │ └── booking_index_container.js
│ ├── footer.jsx
│ ├── greeting
│ │ ├── .DS_Store
│ │ ├── greeting.jsx
│ │ └── greeting_container.js
│ ├── modal.jsx
│ ├── not_found.jsx
│ ├── root.jsx
│ ├── search
│ │ └── search_splash.jsx
│ ├── session_form
│ │ ├── create_user_form.jsx
│ │ ├── login_form_container.js
│ │ ├── session_form.jsx
│ │ └── signup_form_container.js
│ ├── sites
│ │ ├── site_create
│ │ │ ├── site_activities.jsx
│ │ │ ├── site_form.jsx
│ │ │ ├── site_location.jsx
│ │ │ └── site_photo.jsx
│ │ ├── site_edit
│ │ │ ├── site_edit_activity.jsx
│ │ │ ├── site_edit_form.jsx
│ │ │ ├── site_edit_location.jsx
│ │ │ └── site_edit_photo.jsx
│ │ ├── site_form.jsx
│ │ ├── site_map.jsx
│ │ ├── site_show.jsx
│ │ ├── site_show_container.js
│ │ ├── sites_detail.jsx
│ │ ├── sites_detail_container.js
│ │ ├── sites_form_container.js
│ │ ├── sites_index.jsx
│ │ ├── sites_index_container.js
│ │ ├── sites_search.jsx
│ │ └── sites_search_container.js
│ └── splash.jsx
├── hip_camp.jsx
├── reducers
│ ├── bookings_reducer.js
│ ├── create_reducer.js
│ ├── entities_reducer.js
│ ├── errors_reducer.js
│ ├── modal_reducer.js
│ ├── root_reducer.js
│ ├── selectors.js
│ ├── session_errors_reducer.js
│ ├── session_reducer.js
│ ├── sites_reducer.js
│ ├── ui_reducer.js
│ └── users_reducer.js
├── store
│ └── store.js
└── util
│ ├── bookings_api_util.js
│ ├── marker_manager.js
│ ├── route_util.js
│ ├── route_util.jsx
│ ├── session_api_util.js
│ └── site_api_util.js
├── index_page.png
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── log
└── .keep
├── package-lock.json
├── package.json
├── public
├── 404.html
├── 422.html
├── 500.html
├── apple-touch-icon-precomposed.png
├── apple-touch-icon.png
├── favicon.ico
└── robots.txt
├── search_feature.gif
├── site_show.png
├── storage
└── .keep
├── test
├── application_system_test_case.rb
├── controllers
│ └── .keep
├── fixtures
│ ├── .keep
│ ├── create_users.yml
│ ├── files
│ │ └── .keep
│ ├── sites.yml
│ └── users.yml
├── helpers
│ └── .keep
├── integration
│ └── .keep
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── create_user_test.rb
│ ├── site_test.rb
│ └── user_test.rb
├── system
│ └── .keep
└── test_helper.rb
├── tmp
└── .keep
├── vendor
└── .keep
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/.DS_Store
--------------------------------------------------------------------------------
/.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 | bundle.js*
10 |
11 | # Ignore all logfiles and tempfiles.
12 | /log/*
13 | /tmp/*
14 | !/log/.keep
15 | !/tmp/.keep
16 |
17 | # Ignore uploaded files in development
18 | /storage/*
19 | !/storage/.keep
20 | /node_modules
21 | /yarn-error.log
22 |
23 | /public/assets
24 | .byebug_history
25 |
26 | # Ignore master key for decrypting credentials and more.
27 | /config/master.key
28 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.5.1
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | ruby '2.5.1'
5 |
6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
7 | gem 'rails', '~> 5.2.3'
8 | # Use postgresql as the database for Active Record
9 | gem 'pg', '>= 0.18', '< 2.0'
10 | # Use Puma as the app server
11 | gem 'puma', '~> 3.11'
12 | # Use SCSS for stylesheets
13 | gem 'sass-rails', '~> 5.0'
14 | # Use Uglifier as compressor for JavaScript assets
15 | gem 'uglifier', '>= 1.3.0'
16 |
17 | gem "aws-sdk-s3"
18 | # See https://github.com/rails/execjs#readme for more supported runtimes
19 | # gem 'mini_racer', platforms: :ruby
20 |
21 | # Use CoffeeScript for .coffee assets and views
22 | gem 'coffee-rails', '~> 4.2'
23 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
24 | gem 'jbuilder', '~> 2.5'
25 | # Use Redis adapter to run Action Cable in production
26 | # gem 'redis', '~> 4.0'
27 | # Use ActiveModel has_secure_password
28 | gem 'bcrypt', '~> 3.1.7'
29 | gem 'activestorage'
30 | gem 'jquery-rails'
31 |
32 |
33 | # Use ActiveStorage variant
34 | # gem 'mini_magick', '~> 4.8'
35 |
36 | # Use Capistrano for deployment
37 | # gem 'capistrano-rails', group: :development
38 |
39 | # Reduces boot times through caching; required in config/boot.rb
40 | gem 'bootsnap', '>= 1.1.0', require: false
41 |
42 | group :development, :test do
43 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
44 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
45 | end
46 |
47 | group :development do
48 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
49 | gem 'web-console', '>= 3.3.0'
50 | gem 'listen', '>= 3.0.5', '< 3.2'
51 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
52 | gem 'spring'
53 | gem 'spring-watcher-listen', '~> 2.0.0'
54 | gem 'annotate'
55 | end
56 |
57 | group :test do
58 | # Adds support for Capybara system testing and selenium driver
59 | gem 'capybara', '>= 2.15'
60 | gem 'selenium-webdriver'
61 | # Easy installation and use of chromedriver to run system tests with Chrome
62 | gem 'chromedriver-helper'
63 | end
64 |
65 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
66 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## [MoreS'mores](https://moresmores.herokuapp.com/#/)
2 |
3 | * [MoreS'mores](https://moresmores.herokuapp.com/#/) is a clone of the camping app HipCamp. More S'mores allows users to find camp and reserve camping sites. Additionally users can place their own property as camp sites and control reservations to their camp sites. Every site lists their various amenities such as allowing pets or to park an RV, enabling users to search and find camp sites by their preference.
4 | * Built using Ruby on Rails, React, Redux, Google Maps API, AWS S3 for image storage
5 |
6 | ## Install
7 | ``` bash
8 | npm install
9 | bundle install
10 | ````
11 |
12 | Create and initialize database
13 | ``` bash
14 | bundle exec rails db:setup
15 | ````
16 |
17 | Launch site
18 | ``` bash
19 | npm start
20 | bundle exec rails server
21 | ```
22 |
23 | ## Viewing Sites
24 | * Users can view site listings and select individual sites to see additional details related to a specific site
25 |
26 | 
27 |
28 | 
29 |
30 | ## Booking a Site
31 | * Should a user find a site of their liking they are able to reserve the site, additionally users can view their placed bookings from the bookings section of their profile.
32 |
33 | 
34 |
35 | ## Search Implementation
36 | * Implementing search was done through an update to Rails backend controller when reuqesting all sites. Including an alternative when a search condition is added in params allows for fetching sites where only the name matches with the search condition.
37 | ``` ruby
38 | def index
39 | if (params[:search]!=nil)
40 | @sites = Site.where("lower(name) like ?", "%#{params[:search].downcase}%")
41 | else
42 | @sites = Site.all
43 | end
44 | end
45 | ```
46 | 
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/app/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/.DS_Store
--------------------------------------------------------------------------------
/app/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/.DS_Store
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/images/.keep
--------------------------------------------------------------------------------
/app/assets/images/Tent-Mono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/images/Tent-Mono.png
--------------------------------------------------------------------------------
/app/assets/images/Wordmark-Mono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/images/Wordmark-Mono.png
--------------------------------------------------------------------------------
/app/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/images/favicon.ico
--------------------------------------------------------------------------------
/app/assets/images/foot-mountains.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/images/foot-mountains.png
--------------------------------------------------------------------------------
/app/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/images/logo.png
--------------------------------------------------------------------------------
/app/assets/images/more_smores.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/images/more_smores.png
--------------------------------------------------------------------------------
/app/assets/images/splash_background.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/images/splash_background.jpeg
--------------------------------------------------------------------------------
/app/assets/images/splash_background_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/images/splash_background_2.jpg
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
5 | // vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require rails-ujs
14 | //= require activestorage
15 | //= require jquery
16 | //= require jquery_ujs
17 | //= require_tree .
18 |
--------------------------------------------------------------------------------
/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/assets/javascripts/channels/.keep
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | @import "./session_form/session_form";
2 | @import "./sites/sites_detail";
3 | @import "./sites/sites_form";
4 | @import "./sites/sites_show";
5 | @import "./footer";
6 | @import "./search";
7 | @import "./booking/booking_form";
8 | @import "./booking/booking_index";
9 |
10 |
11 | * {
12 | font-family: 'Cabin', sans-serif;
13 | }
14 |
15 | .header {
16 | width: 85%;
17 | display: flex;
18 | margin: 0 auto;
19 | justify-content: space-between;
20 | min-width: 1000px;
21 | }
22 |
23 | .logo {
24 | width: 140px;
25 | padding-right: 300px;
26 | margin-top: -5px;
27 | text-decoration: none;
28 | font-family: 'Cabin', sans-serif;
29 | color: black;
30 | }
31 |
32 | .logo:hover {
33 | text-decoration: none;
34 | }
35 |
36 | .not-found {
37 | margin: 0 auto;
38 | }
39 | .header-nav {
40 | display: flex;
41 | font-size: 17px;
42 | justify-content: space-evenly;
43 | ul{
44 | display: flex;
45 | list-style: none;
46 | li {
47 | display: flex;
48 | font-size: 13px;
49 | margin-left: 15px;
50 | }
51 | }
52 | }
53 |
54 | .header-link {
55 | display: inline-block;
56 | padding-right: 5px;
57 | text-decoration: none;
58 | margin: 0 auto;
59 | color: black;
60 | border-style: none;
61 | button {
62 | border-style: none;
63 | }
64 | }
65 |
66 | .header-link:hover, .dropdown-content:hover {
67 | text-decoration: underline;
68 | text-decoration-color: green;
69 | }
70 |
71 | .modal-background {
72 | position: fixed;
73 | top: 0;
74 | bottom: 0;
75 | right: 0;
76 | left: 0;
77 | background: rgba(255, 255, 255,0.7);
78 | z-index: 10;
79 | }
80 |
81 | .modal-child {
82 | position: absolute;
83 | top: 50%;
84 | left: 50%;
85 | border-radius: 3px;
86 | transform: translate(-50%,-50%);
87 | width: auto;
88 | margin: 0 auto;
89 | margin-top: 50px;
90 | text-align: center;
91 | background: white;
92 | height: auto;
93 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
94 | }
95 |
96 | .splash-full {
97 | background-image: asset_url('splash_background_2.jpeg');
98 | background-size: 100% 100%;
99 | min-width: 1000px;
100 | }
101 |
102 | .splash-content {
103 | width: 85%;
104 | margin-left: 10%;
105 | padding-top: 5px;
106 | padding-bottom: 20px;
107 | }
108 |
109 | .splash-title {
110 | h2 {
111 | font-size: 35px;
112 | }
113 | }
114 |
115 | .splash-body {
116 | width: 60%;
117 | font-weight: 400;
118 | p {
119 | margin-top: -20px;
120 | font-weight: 500;
121 | font-size: 1.4rem;
122 | }
123 | }
124 |
125 | .dropdown > .header-link:hover {
126 | .dropdown-content {
127 | display:block;
128 | }
129 | }
130 |
131 | .dropdown-content {
132 | display: none;
133 | position: absolute;
134 | width: 100px;
135 | text-decoration: none;
136 | margin: 0 auto;
137 | color: black;
138 | padding-top:15px;
139 | }
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/booking/booking_form.scss:
--------------------------------------------------------------------------------
1 | .booking-form {
2 | // width: 100;
3 | height: 100%;
4 | text-align: center;
5 | }
6 |
7 | .booking-head {
8 | margin: 0 auto;
9 | text-align: center;
10 | }
11 |
12 | .booking-content {
13 | margin: 0 auto;
14 | text-align: center;
15 | display: flex;
16 | flex-direction: column;
17 | width: 85%;
18 | .submit-button {
19 | margin: 0 auto;
20 | margin-top: 15px;
21 | margin-bottom: 15px;
22 | }
23 | }
24 | .booking-inputs {
25 | display: flex;
26 | flex-direction: row;
27 | justify-content: space-evenly;
28 | margin: 0 auto;
29 | padding-left: 15px;
30 | padding-right: 15px;
31 | }
32 |
33 | .booking-input {
34 | padding-top: 15px;
35 | padding-bottom: 15px;
36 | padding-right: 10px;
37 | }
38 |
39 | .input-field {
40 | margin-left: 10px;
41 | padding-top: 15px;
42 | padding-bottom: 15px;
43 | font-weight: bolder;
44 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/booking/booking_index.scss:
--------------------------------------------------------------------------------
1 | .bookings {
2 | text-align: center;
3 | margin-bottom: 100px;
4 | }
5 |
6 | .no-bookings {
7 | text-align: center;
8 | margin-bottom: 600px;
9 | }
10 |
11 | .booking-detail {
12 | // border-style: solid;
13 | background: #f2f2f2;
14 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
15 | padding-bottom: 10px;
16 | list-style: none;
17 | height: 430px;
18 | width: 700px;
19 | margin: 0 auto;
20 | margin-bottom: 20px;
21 | padding-bottom: 15px;
22 | }
23 |
24 | .booking-info {
25 | display: flex;
26 | flex-direction: row;
27 | justify-content: left;
28 | }
29 |
30 | .booking-image {
31 | width: 400px;
32 | height: 400px;
33 | margin-top: 15px;
34 | margin-bottom: 15px;
35 | margin-left: 15px;
36 | }
37 |
38 | .booking-dates {
39 | margin-left: 50px;
40 | text-align: left;
41 | }
42 |
43 | .booking-location {
44 | text-decoration: none;
45 | color: black;
46 | }
47 |
48 | .booking-reminder {
49 | color: black;
50 | text-decoration: none;
51 | }
52 |
53 | .booking-reminder:hover {
54 | color: #00e6ac;
55 | }
56 |
57 | .delete-button {
58 | width: 85%;
59 | border-style: solid;
60 | padding-top: 20px;
61 | padding-bottom: 20px;
62 | background-color: #00e6ac;
63 | font-size: 1.2rem;
64 | color: white;
65 | margin: 0 auto;
66 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/footer.scss:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | height: 100%;
5 | min-width: 1300px;
6 | }
7 |
8 | .footer {
9 | height: 350px;
10 | margin-top: 30px;
11 | background-color: #412F5A;
12 | background-image: asset_url('foot-mountains.png');
13 | background-repeat: repeat-x;
14 | background-position: bottom;
15 | display: flex;
16 | flex-direction: row-reverse;
17 | align-items: center;
18 | padding-right: 125px;
19 | padding-left: 125px;
20 | }
21 |
22 | .footer-main {
23 | position: top;
24 | width: 100%;
25 | display: flex;
26 | flex-direction: column;
27 | justify-content: space-around;
28 | align-items: left;
29 | // margin-top: 20px;
30 | margin-bottom: 180px;
31 | }
32 |
33 | .footer-heading {
34 | width: 500px;
35 | }
36 |
37 | .footer-title {
38 | color: #7f6b9b;
39 | font-size: 20px;
40 | }
41 |
42 | .footer-body {
43 | position: bottom;
44 | display: flex;
45 | align-items: flex-start;
46 | width: 450px;
47 | color: white;
48 | font-size: 15px;
49 | line-height: 1.2;
50 | }
51 |
52 | .list-links {
53 | display: flex;
54 | flex-direction: column;
55 | justify-content: space-around;
56 | width: 100px;
57 | height: auto;
58 | }
59 |
60 | .list-links a {
61 | color: white;
62 | font-size: 15px;
63 | line-height: 1.5;
64 | text-decoration: none;
65 | }
66 |
67 | .list-links a:hover {
68 | color: #7f6b9b;
69 | }
70 |
71 |
72 | .footer-list {
73 | margin-bottom: 180px;
74 | margin-right: 50px;
75 | }
76 |
77 | .social {
78 | color: #7f6b9b;
79 | font-size: 18px;
80 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/search.scss:
--------------------------------------------------------------------------------
1 | .search-content{
2 | height: 400px;
3 | width: 85%;
4 | min-width: 1000px;
5 | margin: 0 auto;
6 | }
7 |
8 | .search-box {
9 | display: flex;
10 | text-align: center;
11 | flex-direction: column;
12 | margin-top: 10px;
13 | padding: 20px 10px;
14 | background: white;
15 | border-radius: 15px;
16 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
17 | }
18 |
19 | .search-bar {
20 | width: 95%;
21 | margin: 0 auto;
22 | padding-top: 30px;
23 | padding-bottom: 30px;
24 | padding-left: 20px;
25 | font-size: 1.3rem;
26 | border-radius: 15px;
27 | border-width: 1px;
28 |
29 |
30 | }
31 |
32 | .search-buttons {
33 | display: flex;
34 | margin: 0 auto;
35 | justify-content: space-between;
36 | margin-top: 10px;
37 | width: 95%;
38 | }
39 |
40 | .search-button {
41 | border-style: solid;
42 | border-color: grey;
43 | border-radius: 15px;
44 | background: white;
45 | border-width: 1px;
46 | padding: 20px 60px 20px 60px;
47 | text-align: center;
48 | }
49 |
50 | .search-button:hover, .search-submit:hover {
51 | cursor: pointer;
52 | }
53 |
54 | .search-button-active {
55 | border-style: solid;
56 | border-color: grey;
57 | border-width: 1px;
58 | padding: 20px 60px 20px 60px;
59 | text-align: center;
60 | background: #00e6ac;
61 | border-radius: 15px;
62 | color: white;
63 | }
64 |
65 | .search-submit {
66 | border-width: 1px;
67 | padding: 20px 80px 20px 80px;
68 | background: #00e6ac;
69 | color: white;
70 | font-size: 1.1rem;
71 | border-radius: 15px;
72 | }
73 |
74 |
75 | .search-submit:hover {
76 | background-color: #00b386;
77 | }
78 |
79 | .search-button:hover {
80 | background-color: #00b386;
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/session_form/session_form.scss:
--------------------------------------------------------------------------------
1 | .form-input {
2 | width: 100%;
3 | margin-bottom: 5px;
4 | padding-top: 20px;
5 | padding-bottom: 20px;
6 | height: 20px;
7 | text-align: center;
8 | border-color: black;
9 | border-width: 1px;
10 | }
11 |
12 | .auth-form {
13 | padding-left: 20px;
14 | padding-right: 20px;
15 | }
16 |
17 | .form-errors-list {
18 | text-align: center;
19 | width: 85%;
20 | margin: 0 auto;
21 | margin-bottom: 15px;
22 | list-style: none;
23 | // list-style-image: url('https://unicons.iconscout.com/release/v0.0.3/svg/exclamation-triangle.svg');
24 | }
25 |
26 | .form-input::placeholder {
27 | font-weight: bolder;
28 | color: black;
29 | text-align: left;
30 | padding-left: 10px;
31 | }
32 |
33 | .submit-button {
34 | width: 85%;
35 | padding-top: 15px;
36 | padding-bottom: 15px;
37 | margin-top: 5px;
38 | background-color: #00e6ac;
39 | color: white;
40 | font-size: 1.3rem;
41 | }
42 |
43 | .demo-button {
44 | width: 100%;
45 | display: block;
46 | padding-top: 15px;
47 | padding-bottom: 15px;
48 | margin-bottom: 10px;
49 | margin-top:10px;
50 | background-color: #00e6ac;
51 | color: white;
52 | font-size: 1.3rem;
53 | }
54 |
55 | .submit-button:hover, .demo-button:hover {
56 | background-color: #00b386
57 | }
58 |
59 | .alternative {
60 | margin: 0 auto;
61 | margin-top: 10px;
62 | }
63 |
64 |
65 | .state-input {
66 | width: 85%;
67 | margin-top: 15px;
68 | margin-bottom: 15px;
69 | text-align:center;
70 | }
71 |
72 | .state-list {
73 | margin-left: 5px;
74 | margin-top: 10px;
75 | margin-bottom: 10px;
76 | text-align:center;
77 | background: white;
78 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/sites/sites_detail.scss:
--------------------------------------------------------------------------------
1 | .index-body {
2 | width: 85%;
3 | margin: 0 auto;
4 | h3 {
5 | margin-left: 95px;
6 | }
7 | }
8 |
9 | .index-container {
10 | width: 100%;
11 | height: 100%;
12 | display: flex;
13 | justify-content: space-evenly;
14 | margin: 0 auto;
15 | }
16 |
17 | .sites-list {
18 | display: flex;
19 | flex-wrap: wrap;
20 | justify-content: space-around;
21 | min-width: 700px;
22 | width: 80%;
23 | }
24 |
25 | .site-item {
26 | list-style: none;
27 | width: 340px;
28 | height: 250px;
29 | display: block;
30 | position: relative;
31 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
32 | margin-right: 10px;
33 | margin-top: 10px;
34 | a {
35 | text-decoration: none;
36 | }
37 | li {
38 | height: 100%;
39 | }
40 | }
41 |
42 | .site-details {
43 | height: 100%;
44 | display: block;
45 | .site-content {
46 | color: black;
47 | display: flex;
48 | width: 95%;
49 | height: 80px;
50 | margin-left: auto;
51 | margin-right: auto;
52 | margin-top: 10px;
53 | }
54 | .site-heading {
55 | height: 25px;
56 | margin-top: 10px;
57 | margin-left: auto;
58 | margin-right: auto;
59 | width: 95%;
60 | }
61 | .site-title {
62 | font-size: 25px;
63 | font-weight: 400;
64 | margin-left: auto;
65 | margin-right: auto;
66 | margin-top: 1px;
67 | color: black;
68 | }
69 | .site-subtitle {
70 | font-size: 15px;
71 | font-weight: 400;
72 | margin: 0 auto;
73 | }
74 | .site-price {
75 | font-size: 15px;
76 | font-weight: 400;
77 | margin: 0 auto;
78 | color: black;
79 | }
80 | }
81 |
82 | .site-image {
83 | img {
84 | width: 340px;
85 | height: 130px;
86 | margin: 0 auto;
87 | object-fit: cover;
88 | }
89 | }
90 |
91 | .sites-map {
92 | display: inline-block;
93 | width: 400px;
94 | height: 600px;
95 | }
96 | .content-left {
97 | width: 50%;
98 | height: 100%;
99 | }
100 |
101 | .content-right {
102 | width: 50%;
103 | height: 100%;
104 | text-align: right;
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/sites/sites_form.scss:
--------------------------------------------------------------------------------
1 | .site-form {
2 | text-align: center;
3 | width: 600px;
4 | height: 550px;
5 | margin-left: auto;
6 | margin-right: auto;
7 | margin-top: 50px;
8 | background: #f2f2f2;
9 | padding-top: 15px;
10 | padding-bottom: 15px;
11 | display: flex;
12 | justify-content: space-around;
13 | flex-direction: column;
14 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
15 | }
16 |
17 | .site-nums {
18 | display: flex;
19 | flex-direction: row;
20 |
21 | }
22 |
23 | .site-input {
24 | width: 85%;
25 | margin-top: 10px;
26 | border-color: black;
27 | border-width: 1px;
28 | padding-top: 20px;
29 | padding-bottom: 20px;
30 | padding-left: 10px;
31 | margin-left: auto;
32 | margin-right: auto;
33 | font-weight: bolder;
34 | color: black;
35 | }
36 |
37 | .site-input::placeholder {
38 | font-weight: bolder;
39 | color: black;
40 | text-align: left;
41 | }
42 |
43 | .site-cap {
44 | width: 35%;
45 | margin-top: 10px;
46 | border-color: black;
47 | border-width: 1px;
48 | padding-top: 15px;
49 | padding-bottom: 15px;
50 | margin-left: auto;
51 | margin-right: auto;
52 | text-align: center;
53 | font-weight: bolder;
54 | color: black;
55 | }
56 |
57 | .site-cap::placeholder {
58 | font-weight: bolder;
59 | color: black;
60 | }
61 |
62 | .form-price {
63 | display: flex;
64 | flex-direction: row;
65 | width: 85%;
66 | margin: 0 auto;
67 | padding-top: 5px;
68 | padding-bottom: 5px;
69 | // width: 65%;
70 | }
71 |
72 | .price-mark {
73 | background-color: grey;
74 | height: 100%;
75 | }
76 |
77 | .site-button-incomplete {
78 | width: 85%;
79 | margin-bottom: 10px;
80 | background-color: #008060;
81 | color: white;
82 | padding-top: 15px;
83 | padding-bottom: 15px;
84 | font-size: 1.3rem;
85 | margin: 0 auto;
86 | }
87 |
88 | .site-button-complete {
89 | width: 85%;
90 | margin-bottom: 10px;
91 | background-color: #00e6ac;
92 | color: white;
93 | padding-top: 15px;
94 | padding-bottom: 15px;
95 | font-size: 1.3rem;
96 | margin: 0 auto;
97 | }
98 |
99 | .site-file {
100 | margin: 0 auto;
101 | margin-bottom: 15px;
102 | }
103 |
104 | .site-header {
105 | margin-left: 10px;
106 | margin-right: 10px;
107 | }
108 |
109 | .site-options {
110 | display: flex;
111 | justify-content: space-evenly;
112 | flex-wrap: wrap;
113 | margin-bottom: 15px;
114 | }
115 |
116 | .site-activity-green {
117 | width: 150px;
118 | margin-right: 10px;
119 | margin-left: 10px;
120 | margin-top: 10px;
121 | margin-bottom: 10px;
122 | height: 50px;
123 | text-align: center;
124 | background-color: #00e6ac;
125 | }
126 |
127 | .site-activity-grey {
128 | width: 150px;
129 | margin-right: 10px;
130 | margin-left: 10px;
131 | margin-top: 10px;
132 | margin-bottom: 10px;
133 | height: 50px;
134 | text-align: center;
135 | background-color: #00b386;
136 | }
137 |
138 | .photo-prev {
139 | img{
140 | max-width: 55%;
141 | // height: auto;
142 | }
143 | }
144 |
145 | .fire-icon {
146 | font-size: 40px;
147 | }
148 |
149 | .act-heading {
150 | font-size: 15px;
151 | }
152 |
153 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/sites/sites_show.scss:
--------------------------------------------------------------------------------
1 | .site-show{
2 | width: 85%;
3 | margin: 0 auto;
4 | text-align: center;
5 | img {
6 | width: 60%;
7 | max-height: 600px;
8 | object-fit: contain;
9 | }
10 | }
11 |
12 |
13 | .site-info {
14 | width: 90%;
15 | margin-left: 50px;
16 | text-align: left;
17 | }
18 |
19 | .site-heading {
20 | width: 55%;
21 | }
22 |
23 | .owner-options {
24 | display: flex;
25 | justify-content: space-evenly;
26 | text-decoration: none;
27 | margin-top: 25px;
28 | a {
29 | text-decoration: none;
30 | color: white;
31 | }
32 | .edit-button {
33 | text-decoration: none;
34 | }
35 | }
36 |
37 | .site-list {
38 | list-style: none;
39 | text-align: left;
40 | margin-left: 10px;
41 | margin-right: 10px;
42 | li {
43 | margin-left: -30px;
44 | }
45 | }
46 |
47 | .site-category{
48 | text-align: center;
49 | margin-top: 25px;
50 | width: 200px;
51 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
52 | }
53 |
54 | .site-lists {
55 | display: flex;
56 | justify-content: space-evenly;
57 | margin-bottom: 50px;
58 | }
59 |
60 | .act-false {
61 | color: grey;
62 | }
63 |
64 | .booking-box {
65 | width: 400px;
66 | height: 200px;
67 | margin-top: -90px;
68 | border-style: solid;
69 | border-color: grey;
70 | text-align: center;
71 | .site-pricing {
72 | margin-left: 15px;
73 | margin-right: 15px;
74 | line-height: 15%;
75 | p {
76 | color: grey;
77 | }
78 | }
79 | }
80 |
81 |
82 | .booking-button {
83 | width: 85%;
84 | border-style: solid;
85 | padding-top: 20px;
86 | padding-bottom: 20px;
87 | background-color: #00e6ac;
88 | font-size: 1.3rem;
89 | color: white;
90 | margin: 0 auto;
91 | }
92 |
93 | .edit-button {
94 | width: 35%;
95 | border-style: solid;
96 | padding-top: 5px;
97 | padding-bottom: 5px;
98 | background-color: #00e6ac;
99 | font-size: 1.1rem;
100 | color: white;
101 | }
102 |
103 |
104 | .line-break {
105 | width: 100%;
106 | }
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/api/bookings_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::BookingsController < ApplicationController
2 |
3 | def create
4 | @booking = Booking.new(booking_params)
5 | @booking.user_id = current_user.id
6 | if @booking.save
7 | render "api/bookings/show"
8 | else
9 | render json: @booking.errors.full_messages, status: 422
10 | end
11 | end
12 |
13 | def index
14 | @bookings = current_user.bookings.order(start_date: :desc)
15 | end
16 |
17 | def update
18 | @booking = Booking.find(params[:id])
19 | if @booking.update(booking_params)
20 | render "api/bookings/show"
21 | else
22 | render json: @booking.errors.full_messages, status: 422
23 | end
24 | end
25 |
26 | def destroy
27 | @booking = Booking.find(params[:id])
28 | if @booking
29 | @booking.destroy
30 | render "api/bookings/show"
31 | else
32 | render json:["Unable to delete booking"], status:401
33 | end
34 | end
35 |
36 | private
37 | def booking_params
38 | params.require(:booking).permit(
39 | :site_id,
40 | :group_size,
41 | :start_date,
42 | :end_date,
43 | :approved,
44 | :reviewed
45 | )
46 | end
47 | end
--------------------------------------------------------------------------------
/app/controllers/api/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::SessionsController < ApplicationController
2 | def create
3 | @user = User.find_by_credentials(
4 | params[:user][:email],
5 | params[:user][:password]
6 | )
7 |
8 | if @user
9 | login(@user)
10 | render "api/users/show"
11 | else
12 | render json: ["Invalid username/password combination"], status: 401
13 | end
14 | end
15 |
16 | def destroy
17 | @user = current_user
18 | if @user
19 | logout!
20 | render "api/users/show"
21 | else
22 | render json: ["Nobody signed in"], status: 404
23 | end
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/app/controllers/api/sites_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::SitesController < ApplicationController
2 | def index
3 | if (params[:search]!=nil)
4 | @sites = Site.where("lower(name) like ?", "%#{params[:search].downcase}%")
5 | else
6 | @sites = Site.all
7 | end
8 | end
9 |
10 | def show
11 | @site = Site.find(params[:id])
12 | end
13 |
14 | def create
15 | @site = Site.new(site_params)
16 | @site.user_id = current_user.id
17 | if @site.save
18 | render "api/sites/show"
19 | else
20 | render json: @site.errors.full_messages, status: 422
21 | end
22 | end
23 |
24 | def update
25 | @site = Site.find(params[:id])
26 | if @site.update(site_params)
27 | render "api/sites/show"
28 | else
29 | render json: @site.errors.full_messages, status: 422
30 | end
31 | end
32 |
33 | def destroy
34 | @site = Site.find(params[:id])
35 | if current_user.id == @site.user_id
36 | @site.destroy
37 | end
38 | render "api/sites/show"
39 | end
40 |
41 | private
42 | def site_params
43 | params.require(:site).permit(
44 | :name,
45 | :capacity,
46 | :fire_allowed,
47 | :rv_allowed,
48 | :pet_allowed,
49 | :bike_activity,
50 | :hike_activity,
51 | :latitude,
52 | :longitude,
53 | :state,
54 | :description,
55 | :photo,
56 | :city,
57 | :price
58 | )
59 | end
60 | end
--------------------------------------------------------------------------------
/app/controllers/api/users_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::UsersController < ApplicationController
2 | def create
3 | @user = User.new(user_params)
4 | if @user.save
5 | login(@user)
6 | render "api/users/show"
7 | else
8 | render json: @user.errors.full_messages, status: 422
9 | end
10 | end
11 |
12 | private
13 |
14 | def user_params
15 | params.require(:user).permit(:first_name, :last_name, :email, :zip_code, :state, :password)
16 | end
17 | end
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 |
3 | helper_method :current_user, :logged_in?
4 |
5 | def current_user
6 | return nil if !session[:session_token]
7 | @current_user ||= User.find_by_session_token(session[:session_token])
8 | end
9 |
10 | def login(user)
11 | session[:session_token]=user.session_token
12 | end
13 |
14 | def logout!
15 | current_user.reset_session_token!
16 | session[:session_token] = nil
17 | end
18 |
19 | def logged_in?
20 | !!current_user
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/controllers/static_pages_controller.rb:
--------------------------------------------------------------------------------
1 | class StaticPagesController < ApplicationController
2 | def root; end
3 | end
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/booking.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: bookings
4 | #
5 | # id :bigint not null, primary key
6 | # user_id :integer not null
7 | # site_id :integer not null
8 | # group_size :integer not null
9 | # start_date :date not null
10 | # end_date :date not null
11 | # approved :boolean default(FALSE)
12 | # reviewed :boolean default(FALSE)
13 | # total_price :integer not null
14 | #
15 |
16 | class Booking < ApplicationRecord
17 | validates :user_id, :site_id, :group_size, :start_date, :end_date, presence: true
18 | after_initialize :total_price
19 |
20 | belongs_to :user
21 | belongs_to :site
22 |
23 | def total_days
24 | @total_days = end_date - start_date
25 | @total_days.to_int
26 | end
27 |
28 | def total_price
29 | priceTotal = (self.site.price * total_days)
30 | priceTotal.to_int
31 | self.total_price = priceTotal
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/models/site.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: sites
4 | #
5 | # id :bigint not null, primary key
6 | # user_id :integer not null
7 | # name :string not null
8 | # capacity :integer not null
9 | # fire_allowed :boolean default(FALSE)
10 | # rv_allowed :boolean default(FALSE)
11 | # pet_allowed :boolean default(FALSE)
12 | # bike_activity :boolean default(FALSE)
13 | # hike_activity :boolean default(FALSE)
14 | # latitude :float not null
15 | # longitude :float not null
16 | # state :string not null
17 | # created_at :datetime not null
18 | # updated_at :datetime not null
19 | # description :string default(""), not null
20 | # city :string default(""), not null
21 | # price :integer default(0), not null
22 | #
23 |
24 | class Site < ApplicationRecord
25 | validates :user_id, :name, :capacity, :latitude, :longitude, :state, :city, :price, presence: true
26 |
27 | belongs_to :user
28 | has_one_attached :photo
29 | has_many :bookings
30 |
31 | end
32 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :bigint not null, primary key
6 | # first_name :string not null
7 | # last_name :string not null
8 | # email :string not null
9 | # zip_code :integer not null
10 | # state :string not null
11 | # password_digest :string not null
12 | # session_token :string not null
13 | # created_at :datetime not null
14 | # updated_at :datetime not null
15 | #
16 |
17 | class User < ApplicationRecord
18 | validates :first_name, :last_name, :email, :zip_code, :state, :session_token, presence: true
19 | validates :email, :session_token, uniqueness: true
20 | validates :password, length: { minimum: 6 }, allow_nil: true
21 |
22 | attr_reader :password
23 | after_initialize :ensure_session_token!
24 |
25 | has_many :sites
26 | has_many :bookings
27 |
28 | def self.find_by_credentials(email, password)
29 | user = User.find_by(email: email)
30 | return nil unless user
31 | user.is_password?(password) ? user : nil
32 | end
33 |
34 | def password=(password)
35 | @password = password
36 | self.password_digest = BCrypt::Password.create(password)
37 | end
38 |
39 | def is_password?(password)
40 | BCrypt::Password.new(self.password_digest).is_password?(password)
41 | end
42 |
43 | def reset_session_token!
44 | generate_session_token
45 | save!
46 | self.session_token
47 | end
48 |
49 | private
50 | def ensure_session_token!
51 | generate_session_token unless self.session_token
52 | end
53 |
54 | def new_session_token
55 | SecureRandom.urlsafe_base64
56 | end
57 |
58 | def generate_session_token
59 | self.session_token = new_session_token
60 | while User.find_by(session_token: self.session_token)
61 | self.session_token = new_session_token
62 | end
63 | self.session_token
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/app/views/api/bookings/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @bookings.each do |booking|
2 | json.set! booking.id do
3 | json.extract! booking, :id, :user_id, :site_id, :group_size, :start_date, :end_date, :approved, :reviewed, :total_price
4 | json.extract! booking.site, :name
5 | json.photoUrl url_for(booking.site.photo)
6 | end
7 | end
--------------------------------------------------------------------------------
/app/views/api/bookings/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.set! @booking.id do
2 | json.extract! @booking, :id, :user_id, :site_id, :group_size, :start_date, :end_date, :approved, :reviewed, :total_price
3 | end
--------------------------------------------------------------------------------
/app/views/api/sites/_sites.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! site, :id, :user_id, :name, :description, :capacity, :fire_allowed, :rv_allowed, :pet_allowed, :bike_activity, :hike_activity, :latitude, :longitude, :state, :city, :price
2 | json.photoUrl url_for(site.photo)
--------------------------------------------------------------------------------
/app/views/api/sites/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @sites.each do |site|
2 | json.set! site.id do
3 | json.partial! "api/sites/sites", site: site
4 | json.photoUrl url_for(site.photo)
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/views/api/sites/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.set! @site.id do
2 | json.partial! "api/sites/sites", site: @site
3 | end
4 |
5 |
--------------------------------------------------------------------------------
/app/views/api/users/_user.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! user, :id, :email, :first_name, :last_name
--------------------------------------------------------------------------------
/app/views/api/users/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "api/users/user", user: @user
--------------------------------------------------------------------------------
/app/views/layouts/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/app/views/layouts/.DS_Store
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | MoreS'mores
5 | <%= csrf_meta_tags %>
6 | <%= csp_meta_tag %>
7 |
8 |
9 |
10 | <%= stylesheet_link_tag 'application', media: 'all' %>
11 | <%= javascript_include_tag 'application' %>
12 | <%= javascript_include_tag Rails.application.credentials.google %>
13 |
14 |
15 |
16 | <%= yield %>
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/views/static_pages/root.html.erb:
--------------------------------------------------------------------------------
1 | <% if logged_in? %>
2 |
5 | <% end %>
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a starting point to setup your application.
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 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:setup'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" }
12 | if spring
13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
14 | gem 'spring', spring.version
15 | require 'spring/binstub'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a way to update your development environment automatically.
14 | # Add necessary update steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | puts "\n== Updating database =="
24 | system! 'bin/rails db:migrate'
25 |
26 | puts "\n== Removing old logs and tempfiles =="
27 | system! 'bin/rails log:clear tmp:clear'
28 |
29 | puts "\n== Restarting application server =="
30 | system! 'bin/rails restart'
31 | end
32 |
--------------------------------------------------------------------------------
/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path('..', __dir__)
3 | Dir.chdir(APP_ROOT) do
4 | begin
5 | exec "yarnpkg", *ARGV
6 | rescue Errno::ENOENT
7 | $stderr.puts "Yarn executable was not detected in the system."
8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9 | exit 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/bookings_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/bookings_screen.png
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/config/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 HipCamp
10 | class Application < Rails::Application
11 | # Initialize configuration defaults for originally generated Rails version.
12 | config.load_defaults 5.2
13 |
14 | # Settings in config/environments/* take precedence over those specified here.
15 | # Application configuration can go into files in config/initializers
16 | # -- all .rb files in that directory are automatically loaded after loading
17 | # the framework and any gems in your application.
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: HipCamp_production
11 |
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | ZrbqFGpjyXd44CGia3oM+XTwZvXHkTLJGbNF2axew1D5ViYm5VpOnEKniC4pGVHt9pdso2eQRMNWJYVs4/UGQyF7Iy56r+f1819XegcWaYGrSQDVW9BfXzllq/PZoTX4RkQ7q81LWJWDlZW1E9cCHHUPn8hm1E6P/6J+9RkpexM4RhBaak4OMD1Q9zisLueIb11s1B9Ez4NmPYLGKVZv6rfgMeKWmCmGXiK6ooYv8SpUeZelCyrfMHB38bLALvyUGkEAQlfWAo+1xa9GSCwufdLtdiZ1XxTxMs2Bi1P53fAwzCpOx6PEs70YHTGfwSVWQHDgjIKHb08gSuAh08ThdG2ZkEFf5HIEYnyDbE0onIO8QNHRP53CHcAxaknUVGeJZC57xR/jByAjloL7KKK6tEkQRITVLSIkK9JT+Hyrzxn9S5yx2tMFklNazXwfZoXAMG2E7P7IHRWF252Ud8cB3ioTvu/EdOJxyI6OSbd9uePGk3/OCYU9VNg9MNbabO6RY23q+lqxls7aTVSU09eWZw36m8aZul4bos41UVcourvQNm44IigpVPsyWW7Cm18rBqVGtCcVRbtcMCwZ+aZjwA8nLCCyfof/yYIo8eneDABziOPju02UYwsipySen1P9V7I1a41luJRJadj2iyrmlt4RNQK4o0ma7YX9tQB708rh44+7QOHyk95Np9te0/QBvinCTbdy/ysw/wZrbLJv/U9l35w1z8g+j8U=--yXWmfnYTVDeNI6kb--24kQOdH18ForvG1KnWKKLw==
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # PostgreSQL. Versions 9.1 and up are supported.
2 | #
3 | # Install the pg driver:
4 | # gem install pg
5 | # On OS X with Homebrew:
6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config
7 | # On OS X with MacPorts:
8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
9 | # On Windows:
10 | # gem install pg
11 | # Choose the win32 build.
12 | # Install PostgreSQL and put its /bin directory on your path.
13 | #
14 | # Configure Using Gemfile
15 | # gem 'pg'
16 | #
17 | default: &default
18 | adapter: postgresql
19 | encoding: unicode
20 | # For details on connection pooling, see Rails configuration guide
21 | # http://guides.rubyonrails.org/configuring.html#database-pooling
22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
23 |
24 | development:
25 | <<: *default
26 | database: HipCamp_development
27 |
28 | # The specified database role being used to connect to postgres.
29 | # To create additional roles in postgres see `$ createuser --help`.
30 | # When left blank, postgres will use the default role. This is
31 | # the same name as the operating system user that initialized the database.
32 | #username: HipCamp
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: HipCamp_test
61 |
62 | # As with config/secrets.yml, you never want to store sensitive information,
63 | # like your database password, in your source code. If your source code is
64 | # ever seen by anyone, they now have access to your database.
65 | #
66 | # Instead, provide the password as a unix environment variable when you boot
67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
68 | # for a full rundown on how to provide these environment variables in a
69 | # production deployment.
70 | #
71 | # On Heroku and other platform providers, you may have a full connection URL
72 | # available as an environment variable. For example:
73 | #
74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
75 | #
76 | # You can use this database configuration with:
77 | #
78 | # production:
79 | # url: <%= ENV['DATABASE_URL'] %>
80 | #
81 | production:
82 | <<: *default
83 | database: HipCamp_production
84 | username: HipCamp
85 | password: <%= ENV['HIPCAMP_DATABASE_PASSWORD'] %>
86 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | # Run rails dev:cache to toggle caching.
17 | if Rails.root.join('tmp', 'caching-dev.txt').exist?
18 | config.action_controller.perform_caching = true
19 |
20 | config.cache_store = :memory_store
21 | config.public_file_server.headers = {
22 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
23 | }
24 | else
25 | config.action_controller.perform_caching = false
26 |
27 | config.cache_store = :null_store
28 | end
29 |
30 | # Store uploaded files on the local file system (see config/storage.yml for options)
31 | config.active_storage.service = :amazon_dev
32 |
33 | # Don't care if the mailer can't send.
34 | config.action_mailer.raise_delivery_errors = false
35 |
36 | config.action_mailer.perform_caching = false
37 |
38 | # Print deprecation notices to the Rails logger.
39 | config.active_support.deprecation = :log
40 |
41 | # Raise an error on page load if there are pending migrations.
42 | config.active_record.migration_error = :page_load
43 |
44 | # Highlight code that triggered database queries in logs.
45 | config.active_record.verbose_query_logs = true
46 |
47 | # Debug mode disables concatenation and preprocessing of assets.
48 | # This option may cause significant delays in view rendering with a large
49 | # number of complex assets.
50 | config.assets.debug = true
51 |
52 | # Suppress logger output for asset requests.
53 | config.assets.quiet = true
54 |
55 | # Raises error for missing translations
56 | # config.action_view.raise_on_missing_translations = true
57 |
58 | # Use an evented file watcher to asynchronously detect changes in source code,
59 | # routes, locales, etc. This feature depends on the listen gem.
60 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
61 | end
62 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
19 | # config.require_master_key = true
20 |
21 | # Disable serving static files from the `/public` folder by default since
22 | # Apache or NGINX already handles this.
23 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
24 |
25 | # Compress JavaScripts and CSS.
26 | config.assets.js_compressor = :uglifier
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fallback to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
33 |
34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
35 | # config.action_controller.asset_host = 'http://assets.example.com'
36 |
37 | # Specifies the header that your server uses for sending files.
38 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
40 |
41 | # Store uploaded files on the local file system (see config/storage.yml for options)
42 | config.active_storage.service = :amazon_prod
43 |
44 | # Mount Action Cable outside main process or domain
45 | # config.action_cable.mount_path = nil
46 | # config.action_cable.url = 'wss://example.com/cable'
47 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
48 |
49 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
50 | # config.force_ssl = true
51 |
52 | # Use the lowest log level to ensure availability of diagnostic information
53 | # when problems arise.
54 | config.log_level = :debug
55 |
56 | # Prepend all log lines with the following tags.
57 | config.log_tags = [ :request_id ]
58 |
59 | # Use a different cache store in production.
60 | # config.cache_store = :mem_cache_store
61 |
62 | # Use a real queuing backend for Active Job (and separate queues per environment)
63 | # config.active_job.queue_adapter = :resque
64 | # config.active_job.queue_name_prefix = "HipCamp_#{Rails.env}"
65 |
66 | config.action_mailer.perform_caching = false
67 |
68 | # Ignore bad email addresses and do not raise email delivery errors.
69 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
70 | # config.action_mailer.raise_delivery_errors = false
71 |
72 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
73 | # the I18n.default_locale when a translation cannot be found).
74 | config.i18n.fallbacks = true
75 |
76 | # Send deprecation notices to registered listeners.
77 | config.active_support.deprecation = :notify
78 |
79 | # Use default logging formatter so that PID and timestamp are not suppressed.
80 | config.log_formatter = ::Logger::Formatter.new
81 |
82 | # Use a different logger for distributed setups.
83 | # require 'syslog/logger'
84 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
85 |
86 | if ENV["RAILS_LOG_TO_STDOUT"].present?
87 | logger = ActiveSupport::Logger.new(STDOUT)
88 | logger.formatter = config.log_formatter
89 | config.logger = ActiveSupport::TaggedLogging.new(logger)
90 | end
91 |
92 | # Do not dump schema after migrations.
93 | config.active_record.dump_schema_after_migration = false
94 | end
95 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 |
31 | # Store uploaded files on the local file system in a temporary directory
32 | config.active_storage.service = :test
33 |
34 | config.action_mailer.perform_caching = false
35 |
36 | # Tell Action Mailer not to deliver emails to the real world.
37 | # The :test delivery method accumulates sent emails in the
38 | # ActionMailer::Base.deliveries array.
39 | config.action_mailer.delivery_method = :test
40 |
41 | # Print deprecation notices to the stderr.
42 | config.active_support.deprecation = :stderr
43 |
44 | # Raises error for missing translations
45 | # config.action_view.raise_on_missing_translations = true
46 | end
47 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | # Add Yarn node_modules folder to the asset load path.
9 | Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/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 |
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 |
19 | # If you are using UJS then enable automatic nonce generation
20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
21 |
22 | # Report CSP violations to a specified URI
23 | # For further information see the following documentation:
24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
25 | # Rails.application.config.content_security_policy_report_only = true
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/config/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/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 |
--------------------------------------------------------------------------------
/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 http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory.
30 | #
31 | # preload_app!
32 |
33 | # Allow puma to be restarted by `rails restart` command.
34 | plugin :tmp_restart
35 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 |
3 | root to: 'static_pages#root'
4 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
5 |
6 | namespace :api, defaults: {format: :json} do
7 | resources :bookings, only: [:index, :destroy]
8 | resources :users, only: [:create, :show]
9 | resources :sites, only: [:index, :show, :create, :update, :destroy] do
10 | resources :bookings, only: [:create, :update]
11 | end
12 | resource :session, only: [:create,:destroy,:show]
13 | end
14 | end
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w[
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ].each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/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_dev:
11 | service: S3
12 | access_key_id: <%= Rails.application.credentials.aws[:access_key_id] %>
13 | secret_access_key: <%= Rails.application.credentials.aws[:secret_access_key] %>
14 | region: <%= Rails.application.credentials.aws[:region] %>
15 | bucket: <%= Rails.application.credentials.aws[:dev][:bucket] %>
16 |
17 |
18 | amazon_prod:
19 | service: S3
20 | access_key_id: <%= Rails.application.credentials.aws[:access_key_id] %>
21 | secret_access_key: <%= Rails.application.credentials.aws[:secret_access_key] %>
22 | region: <%= Rails.application.credentials.aws[:region] %>
23 | bucket: <%= Rails.application.credentials.aws[:dev][:bucket] %>
24 |
25 | # Remember not to checkin your GCS keyfile to a repository
26 | # google:
27 | # service: GCS
28 | # project: your_project
29 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
30 | # bucket: your_own_bucket
31 |
32 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
33 | # microsoft:
34 | # service: AzureStorage
35 | # storage_account_name: your_account_name
36 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
37 | # container: your_container_name
38 |
39 | # mirror:
40 | # service: Mirror
41 | # primary: local
42 | # mirrors: [ amazon, google, microsoft ]
43 |
--------------------------------------------------------------------------------
/db/migrate/20190430202022_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :users do |t|
4 | t.string :first_name, null: false
5 | t.string :last_name, null: false
6 | t.string :email, null: false, unique: true
7 | t.integer :zip_code, null: false
8 | t.string :state, null: false
9 | t.string :password_digest, null: false
10 | t.string :session_token, null: false, unique: true
11 | t.timestamps
12 | end
13 | add_index :users, :session_token
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/db/migrate/20190430202540_create_sites.rb:
--------------------------------------------------------------------------------
1 | class CreateSites < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :sites do |t|
4 | t.integer :host_id, null: false
5 | t.string :name, null: false
6 | t.boolean :rv_allowed, default: false
7 | t.boolean :pet_allowed, default: false
8 | t.boolean :bike_activity, default: false
9 | t.boolean :hike_activity, default: false
10 | t.float :latitude, null: false
11 | t.float :longitude, null: false
12 | t.string :state, null: false
13 | t.string :profile_photo_url, null: false
14 | t.timestamps
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/db/migrate/20190502165308_drop_sites.rb:
--------------------------------------------------------------------------------
1 | class DropSites < ActiveRecord::Migration[5.2]
2 | def change
3 | drop_table :sites
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20190502165400_recreate_sites.rb:
--------------------------------------------------------------------------------
1 | class RecreateSites < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :sites do |t|
4 | t.integer :host_id, null: false
5 | t.string :name, null: false
6 | t.integer :capacity, null: false
7 | t.boolean :rv_allowed, default: false
8 | t.boolean :pet_allowed, default: false
9 | t.boolean :bike_activity, default: false
10 | t.boolean :hike_activity, default: false
11 | t.float :latitude, null: false
12 | t.float :longitude, null: false
13 | t.string :state, null: false
14 | t.string :profile_photo_url, null: false
15 | t.timestamps
16 | end
17 | add_index :sites, :host_id
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/db/migrate/20190503180949_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 | create_table :active_storage_blobs do |t|
5 | t.string :key, null: false
6 | t.string :filename, null: false
7 | t.string :content_type
8 | t.text :metadata
9 | t.bigint :byte_size, null: false
10 | t.string :checksum, null: false
11 | t.datetime :created_at, null: false
12 |
13 | t.index [ :key ], unique: true
14 | end
15 |
16 | create_table :active_storage_attachments do |t|
17 | t.string :name, null: false
18 | t.references :record, null: false, polymorphic: true, index: false
19 | t.references :blob, null: false
20 |
21 | t.datetime :created_at, null: false
22 |
23 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
24 | t.foreign_key :active_storage_blobs, column: :blob_id
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/db/migrate/20190503181527_remove_sites.rb:
--------------------------------------------------------------------------------
1 | class RemoveSites < ActiveRecord::Migration[5.2]
2 | def change
3 | drop_table :sites
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20190503181602_build_sites.rb:
--------------------------------------------------------------------------------
1 | class BuildSites < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :sites do |t|
4 | t.integer :user_id, null: false
5 | t.string :name, null: false
6 | t.integer :capacity, null: false
7 | t.boolean :fire_allowed, default: false
8 | t.boolean :rv_allowed, default: false
9 | t.boolean :pet_allowed, default: false
10 | t.boolean :bike_activity, default: false
11 | t.boolean :hike_activity, default: false
12 | t.float :latitude, null: false
13 | t.float :longitude, null: false
14 | t.string :state, null: false
15 | t.timestamps
16 | end
17 | add_index :sites, :user_id
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/db/migrate/20190503183154_update_rv_allowed.rb:
--------------------------------------------------------------------------------
1 | class UpdateRvAllowed < ActiveRecord::Migration[5.2]
2 | def change
3 | drop_table :sites
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20190503183332_add_sites_table.rb:
--------------------------------------------------------------------------------
1 | class AddSitesTable < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :sites do |t|
4 | t.integer :user_id, null: false
5 | t.string :name, null: false
6 | t.integer :capacity, null: false
7 | t.boolean :fire_allowed, default: false
8 | t.boolean :rv_allowed, default: false
9 | t.boolean :pet_allowed, default: false
10 | t.boolean :bike_activity, default: false
11 | t.boolean :hike_activity, default: false
12 | t.float :latitude, null: false
13 | t.float :longitude, null: false
14 | t.string :state, null: false
15 | t.timestamps
16 | end
17 | add_index :sites, :user_id
18 | end
19 | end
--------------------------------------------------------------------------------
/db/migrate/20190504204954_add_site_description.rb:
--------------------------------------------------------------------------------
1 | class AddSiteDescription < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :sites, :description, :string, null: false, default: ""
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20190507165220_add_state_city_sites.rb:
--------------------------------------------------------------------------------
1 | class AddStateCitySites < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :sites, :city, :string, null: false, default: ""
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20190507172411_add_price.rb:
--------------------------------------------------------------------------------
1 | class AddPrice < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :sites, :price, :integer, null: false, default: 0
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20190508171716_bookings.rb:
--------------------------------------------------------------------------------
1 | class Bookings < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :bookings do |t|
4 | t.integer :user_id, null: false
5 | t.integer :site_id, null: false
6 | t.integer :group_size, null: false
7 | t.date :start_date, null: false
8 | t.date :end_date, null: false
9 | t.boolean :approved, default: false
10 | t.boolean :reviewed, default: false
11 | end
12 | add_index :bookings, :user_id
13 | add_index :bookings, :site_id
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/db/migrate/20190508175230_add_price_bookings.rb:
--------------------------------------------------------------------------------
1 | class AddPriceBookings < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :bookings, :total_price, :integer, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 2019_05_08_175230) 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", 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.bigint "byte_size", null: false
34 | t.string "checksum", null: false
35 | t.datetime "created_at", null: false
36 | t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
37 | end
38 |
39 | create_table "bookings", force: :cascade do |t|
40 | t.integer "user_id", null: false
41 | t.integer "site_id", null: false
42 | t.integer "group_size", null: false
43 | t.date "start_date", null: false
44 | t.date "end_date", null: false
45 | t.boolean "approved", default: false
46 | t.boolean "reviewed", default: false
47 | t.integer "total_price", null: false
48 | t.index ["site_id"], name: "index_bookings_on_site_id"
49 | t.index ["user_id"], name: "index_bookings_on_user_id"
50 | end
51 |
52 | create_table "sites", force: :cascade do |t|
53 | t.integer "user_id", null: false
54 | t.string "name", null: false
55 | t.integer "capacity", null: false
56 | t.boolean "fire_allowed", default: false
57 | t.boolean "rv_allowed", default: false
58 | t.boolean "pet_allowed", default: false
59 | t.boolean "bike_activity", default: false
60 | t.boolean "hike_activity", default: false
61 | t.float "latitude", null: false
62 | t.float "longitude", null: false
63 | t.string "state", null: false
64 | t.datetime "created_at", null: false
65 | t.datetime "updated_at", null: false
66 | t.string "description", default: "", null: false
67 | t.string "city", default: "", null: false
68 | t.integer "price", default: 0, null: false
69 | t.index ["user_id"], name: "index_sites_on_user_id"
70 | end
71 |
72 | create_table "users", force: :cascade do |t|
73 | t.string "first_name", null: false
74 | t.string "last_name", null: false
75 | t.string "email", null: false
76 | t.integer "zip_code", null: false
77 | t.string "state", null: false
78 | t.string "password_digest", null: false
79 | t.string "session_token", null: false
80 | t.datetime "created_at", null: false
81 | t.datetime "updated_at", null: false
82 | t.index ["session_token"], name: "index_users_on_session_token"
83 | end
84 |
85 | add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
86 | end
87 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
--------------------------------------------------------------------------------
/frontend/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/frontend/.DS_Store
--------------------------------------------------------------------------------
/frontend/actions/bookings_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIUtil from '../util/bookings_api_util';
2 |
3 | export const RECEIVE_BOOKING = "RECEIVE_BOOKING";
4 | export const RECEIVE_ALL_BOOKINGS = "RECEIVE_ALL_BOOKINGS";
5 | export const CREATE_BOOKING = "CREATE_BOOKING";
6 | export const REMOVE_BOOKING = "REMOVE_BOOKING";
7 |
8 | export const receiveBooking = booking => ({
9 | type: RECEIVE_BOOKING,
10 | booking
11 | });
12 |
13 | export const createBooking = booking => dispatch => (
14 | APIUtil.createBooking(booking)
15 | .then(booking => dispatch(receiveBooking(booking)))
16 | )
17 |
18 | export const removeBooking = booking => ({
19 | type: REMOVE_BOOKING,
20 | booking
21 | })
22 |
23 | export const deleteBooking = booking => dispatch => (
24 | APIUtil.deleteBooking(booking)
25 | .then(booking => dispatch(removeBooking(booking)))
26 | )
27 |
28 | export const receiveAllBookings = bookings => ({
29 | bookings: bookings,
30 | type: RECEIVE_ALL_BOOKINGS
31 | });
32 |
33 | export const requestAllBookings = () => dispatch => (
34 | APIUtil.fetchBookings()
35 | .then(bookings => dispatch(receiveAllBookings(bookings)))
36 | );
37 |
38 |
--------------------------------------------------------------------------------
/frontend/actions/modal_actions.js:
--------------------------------------------------------------------------------
1 | export const OPEN_MODAL = "OPEN_MODAL";
2 | export const CLOSE_MODAL = "CLOSE_MODAL";
3 |
4 | export const openModal = modal => {
5 | return {
6 | type: OPEN_MODAL,
7 | modal
8 | };
9 | };
10 |
11 | export const closeModal = () => {
12 | return {
13 | type: CLOSE_MODAL
14 | };
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/actions/session_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIUtil from '../util/session_api_util';
2 |
3 | export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER';
4 | export const LOGOUT_CURRENT_USER = 'LOGOUT_CURRENT_USER';
5 | export const RECEIVE_SESSION_ERRORS = 'RECEIVE_SESSION_ERRORS';
6 | export const CLEAR_SESSION_ERRORS = 'CLEAR_SESSION_ERRORS';
7 |
8 | export const receiveCurrentUser = currentUser => ({
9 | type: RECEIVE_CURRENT_USER,
10 | currentUser
11 | });
12 |
13 | export const logoutCurrentUser = () => ({
14 | type: LOGOUT_CURRENT_USER
15 | });
16 |
17 | export const receiveErrors = errors => ({
18 | type: RECEIVE_SESSION_ERRORS,
19 | errors
20 | });
21 |
22 | export const signup = user => dispatch => (
23 | APIUtil.signup(user).then(user => (
24 | dispatch(receiveCurrentUser(user))
25 | ), err => (dispatch(receiveErrors(err.responseJSON)))
26 | )
27 | );
28 |
29 | export const clearSessionErrors = () => ({
30 | type: CLEAR_SESSION_ERRORS
31 | })
32 |
33 | export const login = user => dispatch => (
34 | APIUtil.login(user).then(user => (
35 | dispatch(receiveCurrentUser(user))
36 | ), err => (dispatch(receiveErrors(err.responseJSON)))
37 | )
38 | );
39 |
40 | export const logout = () => dispatch => (
41 | APIUtil.logout().then( () => (
42 | dispatch(logoutCurrentUser())
43 | ))
44 | );
45 |
46 |
47 |
--------------------------------------------------------------------------------
/frontend/actions/site_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIUtil from '../util/site_api_util';
2 |
3 | export const RECEIVE_ALL_SITES = "RECEIVE_ALL_SITES";
4 | export const RECEIVE_SITE = "RECEIVE_SITE";
5 | export const UPDATE_SITE_STATE = "UPDATE_SITE_STATE";
6 | export const REMOVE_SITE = "REMOVE_SITE";
7 | export const RECEIVE_SEARCH_SITES = "RECEIVE_SEARCH_SITES";
8 |
9 | const receiveSites = (sites) => ({
10 | sites: sites,
11 | type: RECEIVE_ALL_SITES
12 | })
13 |
14 | export const fetchSites = () => (dispatch) => (
15 | APIUtil.fetchSites()
16 | .then(sites => dispatch(receiveSites(sites)))
17 | )
18 |
19 | const receiveSite = (site) => ({
20 | site: site,
21 | type: RECEIVE_SITE
22 | })
23 |
24 | export const fetchSite = (id) => (dispatch) => (
25 | APIUtil.fetchSite(id)
26 | .then(site => dispatch(receiveSite(site)))
27 | )
28 |
29 | export const createSite = (site) => dispatch => (
30 | APIUtil.createSite(site)
31 | .then( site => dispatch(receiveSite(site)))
32 | )
33 |
34 | const updateSiteState = (key, value) => {
35 | return {
36 | type: UPDATE_SITE_STATE,
37 | key,
38 | value
39 | }
40 | }
41 |
42 | export const updateSite = (key, value) => dispatch => {
43 | return dispatch(updateSiteState(key, value))
44 | };
45 |
46 | const removeSite = (site) => ({
47 | site: site.id,
48 | type: REMOVE_SITE
49 | })
50 |
51 | export const deleteSite = site => dispatch => {
52 | return (
53 | APIUtil.destroySite(site).then(site => dispatch(removeSite(site)))
54 | )
55 | }
56 |
57 | export const updateExistSite = (site) => dispatch => (
58 | APIUtil.updateSite(site).then(site => dispatch(receiveSite(site)))
59 | );
60 |
61 | const receiveResults = listings => ({
62 | type: RECEIVE_SEARCH_SITES,
63 | listings
64 | });
65 |
66 | export const searchSites = condition => dispatch => (
67 | APIUtil.searchListings(condition)
68 | .then(listings => dispatch(receiveResults(listings)))
69 | );
--------------------------------------------------------------------------------
/frontend/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/frontend/assets/.DS_Store
--------------------------------------------------------------------------------
/frontend/assets/Tent-Mono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/frontend/assets/Tent-Mono.png
--------------------------------------------------------------------------------
/frontend/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/frontend/components/.DS_Store
--------------------------------------------------------------------------------
/frontend/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Route,
4 | Redirect,
5 | Switch,
6 | Link,
7 | HashRouter
8 | } from 'react-router-dom';
9 | import { library } from "@fortawesome/fontawesome-svg-core";
10 | import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
11 | library.add(faExclamationTriangle);
12 |
13 | import GreetingContainer from './greeting/greeting_container';
14 | import SitesIndexContainer from './sites/sites_index_container';
15 | import SitesSearchContainer from './sites/sites_search_container';
16 | import Footer from './footer';
17 |
18 | // Create Site Form
19 | import SiteForm from './sites/site_create/site_form';
20 | import SiteLocation from './sites/site_create/site_location';
21 | import SiteActivities from './sites/site_create/site_activities';
22 | import SitePhoto from './sites/site_create/site_photo';
23 |
24 | //Edit Site Form
25 | import SiteEdit from "./sites/site_edit/site_edit_form";
26 | import SiteEditLocation from "./sites/site_edit/site_edit_location";
27 | import SiteEditActivity from "./sites/site_edit/site_edit_activity";
28 |
29 | //Booking Form
30 | import BookingContainer from './bookings/booking_container';
31 | import BookingIndexContainer from './bookings/booking_index_container';
32 | import SiteShowContainer from './sites/site_show_container';
33 | import Splash from './splash';
34 |
35 | import { AuthRoute, ProtectedRoute } from '../util/route_util';
36 |
37 | const App = () => (
38 |
39 |
40 |
41 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | );
112 |
113 | export default App;
--------------------------------------------------------------------------------
/frontend/components/bookings/booking_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createBooking, deleteBooking } from '../../actions/bookings_actions';
3 | import { fetchSite } from '../../actions/site_actions';
4 | import BookingForm from './booking_form';
5 | import { closeModal } from '../../actions/modal_actions';
6 |
7 |
8 | const mapStateToProps = (state, ownProps) => {
9 | return {
10 | bookings: state.entities.bookings,
11 | sessionId: state.session.id,
12 | sites: state.entities.sites
13 | }
14 | };
15 |
16 | const mapDispatchToProps = dispatch => {
17 | return {
18 | createBooking: booking => dispatch(createBooking(booking)),
19 | deleteBooking: id => dispatch(deleteBooking(id)),
20 | fetchSite: id => dispatch(fetchSite(id)),
21 | closeModal: () => dispatch(closeModal())
22 | }
23 | };
24 |
25 | export default connect(mapStateToProps, mapDispatchToProps)(BookingForm);
--------------------------------------------------------------------------------
/frontend/components/bookings/booking_detail.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router-dom';
3 |
4 |
5 | class BookingDetail extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
14 |
18 |
19 |
23 |
{this.props.booking.name}
24 |
25 |
Start Date: {this.props.booking.start_date}
26 |
End Date: {this.props.booking.end_date}
27 |
this.props.deleteBooking(this.props.booking.id)
30 | .then(this.props.history.push('/'))}
31 | >Delete Booking
32 |
33 |
34 |
35 | )
36 | }
37 | }
38 |
39 | export default withRouter(BookingDetail);
--------------------------------------------------------------------------------
/frontend/components/bookings/booking_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router-dom';
3 |
4 | class BookingForm extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | start_date: "",
9 | user_id: props.sessionId,
10 | site_id: props.location.pathname.split("/").pop(),
11 | end_date: "",
12 | total_price: 0,
13 | group_size: 1,
14 | approved: false,
15 | reviewed: false
16 | };
17 | this.handleSubmit = this.handleSubmit.bind(this);
18 | }
19 |
20 | update(field) {
21 | return e => this.setState({
22 | [field]: e.currentTarget.value
23 | });
24 | }
25 |
26 | handleSubmit(e) {
27 | e.preventDefault();
28 | const booking = Object.assign({}, this.state);
29 | this.props.createBooking(booking)
30 | .then(() => this.props.closeModal())
31 | .then(this.props.history.push(`/bookings/${this.props.user_id}`));
32 | }
33 |
34 | handleDateChange(type) {
35 | this.setState({[type]: [e.currentTarget.value]})
36 | }
37 |
38 | render() {
39 | const currSite = this.props.sites[this.state.site_id];
40 | const currentDate = new Date().toString();
41 |
42 | return (
43 |
83 | );
84 | }
85 | }
86 |
87 | export default withRouter(BookingForm);
--------------------------------------------------------------------------------
/frontend/components/bookings/booking_index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BookingDetail from './booking_detail';
3 | import { Link } from 'react-router-dom';
4 |
5 |
6 | class BookingIndex extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | // this.state = {
10 | // bookings: props.bookings,
11 | // sites: props.sites
12 | // }
13 | }
14 |
15 | componentDidMount() {
16 | this.props.requestAllBookings();
17 | }
18 |
19 |
20 |
21 | render() {
22 | const bookingItems = this.props.bookings.map(booking => (
23 |
28 | ))
29 |
30 | const yesItems = () => {
31 | return (
32 |
33 |
Your Bookings
34 |
35 |
36 | )
37 | };
38 |
39 | const noItems = () => {
40 | return (
41 |
42 |
You Have No Bookings!
43 |
47 | Find a Campsite to book!
48 |
49 |
50 | );
51 | };
52 |
53 | return this.props.bookings.length > 0 ? yesItems() : noItems();
54 | }
55 | }
56 |
57 | export default BookingIndex;
--------------------------------------------------------------------------------
/frontend/components/bookings/booking_index_container.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'react-redux';
2 | import { requestAllBookings, deleteBooking } from '../../actions/bookings_actions';
3 | import BookingIndex from './booking_index';
4 |
5 | const mapStateToProps = (state) => {
6 | return {
7 | bookings: Object.values(state.entities.bookings),
8 | sites: state.entities.sites
9 | // currentUser: state.users[state.session.id]
10 | }
11 | }
12 |
13 | const mapDispatchToProps = dispatch => {
14 | return {
15 | requestAllBookings: () => dispatch(requestAllBookings()),
16 | deleteBooking: booking => dispatch(deleteBooking(booking))
17 | }
18 | };
19 |
20 | export default connect(mapStateToProps, mapDispatchToProps)(BookingIndex);
21 |
22 |
--------------------------------------------------------------------------------
/frontend/components/footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // icons
4 | import { library } from "@fortawesome/fontawesome-svg-core";
5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6 | import { faCampground } from "@fortawesome/free-solid-svg-icons";
7 | import { faLinkedin, faGithub} from "@fortawesome/free-brands-svg-icons"
8 |
9 | library.add(faLinkedin);
10 | library.add(faGithub);
11 | library.add(faCampground);
12 |
13 | class Footer extends React.Component {
14 | render() {
15 | return (
16 | <>
17 |
49 | >
50 | );
51 | }
52 | }
53 |
54 | export default Footer;
--------------------------------------------------------------------------------
/frontend/components/greeting/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/frontend/components/greeting/.DS_Store
--------------------------------------------------------------------------------
/frontend/components/greeting/greeting.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import Modal from '../modal';
4 |
5 | const Greeting = ({ currentUser, logout, openModal }) => {
6 | const sessionLinks = () => (
7 | <>
8 |
9 |
10 |
11 |
12 | More S'mores
13 |
14 |
15 |
16 |
17 |
18 | Camp
22 |
23 |
24 | openModal('login')}
26 | className="header-link"
27 | >Host
28 |
29 |
30 | openModal('login')}
32 | className="header-link"
33 | >Login
34 |
35 |
36 | openModal('signup')}
38 | className="header-link"
39 | >Sign Up
40 |
41 |
42 |
43 |
44 | >
45 | )
46 |
47 | const personalGreeting = () => (
48 | <>
49 |
50 |
51 |
52 |
53 | More S'mores
54 |
55 |
56 |
57 |
58 |
59 | Camp
63 |
64 |
65 | Host
69 |
70 |
71 | Bookings
75 |
76 |
77 |
78 | {currentUser.first_name.charAt(0).toUpperCase() + currentUser.first_name.slice(1)}
79 |
80 | Sign Out
81 |
82 |
83 |
84 |
85 |
86 |
87 | >
88 | );
89 |
90 | return currentUser ? personalGreeting() : sessionLinks();
91 | }
92 |
93 | export default Greeting;
--------------------------------------------------------------------------------
/frontend/components/greeting/greeting_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { openModal } from '../../actions/modal_actions';
3 | import { logout } from '../../actions/session_actions';
4 | import Greeting from './greeting';
5 |
6 | const mapStateToProps = ({ session, errors, entities: { users }}) => {
7 | return {
8 | errors: errors.session,
9 | currentUser: users[session.id]
10 | };
11 | };
12 |
13 | const mapDispatchToProps = dispatch => ({
14 | logout: () => dispatch(logout()),
15 | openModal: modal => dispatch(openModal(modal))
16 | });
17 |
18 | export default connect(mapStateToProps, mapDispatchToProps)(Greeting);
--------------------------------------------------------------------------------
/frontend/components/modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { closeModal } from '../actions/modal_actions';
3 | import { connect } from 'react-redux';
4 | import LogInFormContainer from './session_form/login_form_container';
5 | import SignUpFormContainer from './session_form/signup_form_container';
6 | import BookingContainer from './bookings/booking_container';
7 |
8 | function Modal({modal, closeModal}) {
9 | if (!modal) {
10 | return null;
11 | }
12 | let component;
13 | switch (modal) {
14 | case 'login':
15 | component = ;
16 | break;
17 | case 'signup':
18 | component =
19 | break;
20 | case 'booking':
21 | component =
22 | break;
23 | default:
24 | return null;
25 | }
26 | return (
27 |
28 |
e.stopPropagation()}>
29 | { component }
30 |
31 |
32 | );
33 | }
34 |
35 | const mapStateToProps = state => {
36 | return {
37 | modal: state.ui.modal
38 | };
39 | };
40 |
41 | const mapDispatchToProps = dispatch => {
42 | return {
43 | closeModal: () => dispatch(closeModal())
44 | };
45 | };
46 |
47 | export default connect(mapStateToProps, mapDispatchToProps)(Modal);
--------------------------------------------------------------------------------
/frontend/components/not_found.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | const NotFound = () => (
5 |
6 |
You've ventured too far, there is no camp here!
7 |
8 | )
--------------------------------------------------------------------------------
/frontend/components/root.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import { HashRouter } from 'react-router-dom';
4 | import App from './App';
5 |
6 | const Root = ({ store }) => (
7 |
8 |
9 |
10 |
11 |
12 | );
13 |
14 | export default Root;
--------------------------------------------------------------------------------
/frontend/components/search/search_splash.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router-dom';
3 | import { library } from '@fortawesome/fontawesome-svg-core'
4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5 | import { faCampground, faBus, faDog, faCalendarAlt } from '@fortawesome/free-solid-svg-icons';
6 | library.add(faBus);
7 | library.add(faDog);
8 | library.add(faCampground);
9 | library.add(faCalendarAlt);
10 |
11 |
12 | class SplashSearch extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | searchValue: ""
17 | };
18 | this.submitSearch = this.submitSearch.bind(this);
19 | }
20 |
21 | flipCalendar() {
22 | if (document.getElementById("anytime").className === "search-button") {
23 | document.getElementById("anytime").className = "search-button-active"
24 | } else {
25 | document.getElementById("anytime").className = "search-button"
26 | }
27 | };
28 |
29 | flipCamp() {
30 | if (document.getElementById("campsite").className === "search-button") {
31 | document.getElementById("campsite").className = "search-button-active"
32 | } else {
33 | document.getElementById("campsite").className = "search-button"
34 | }
35 | };
36 |
37 | flipLodging() {
38 | if (document.getElementById("lodging").className === "search-button") {
39 | document.getElementById("lodging").className = "search-button-active"
40 | } else {
41 | document.getElementById("lodging").className = "search-button"
42 | }
43 | };
44 |
45 | flipRv() {
46 | if (document.getElementById("rv").className === "search-button") {
47 | document.getElementById("rv").className = "search-button-active";
48 | } else {
49 | document.getElementById("rv").className = "search-button";
50 | }
51 | };
52 |
53 | submitSearch(e) {
54 | e.preventDefault();
55 | this.props.history.push(`/search/${this.state.searchValue}`);
56 | }
57 |
58 | update(field) {
59 | return e =>
60 | this.setState({
61 | [field]: e.currentTarget.value
62 | });
63 | }
64 |
65 | render() {
66 | return (
67 |
95 | );
96 | }
97 | }
98 |
99 | export default withRouter(SplashSearch);
--------------------------------------------------------------------------------
/frontend/components/session_form/create_user_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3 |
4 |
5 | class AccountForm extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | email: "",
10 | password: ''
11 | };
12 | this.handleSubmit = this.handleSubmit.bind(this);
13 | this.states = ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY'];
14 | }
15 |
16 | update(field) {
17 | return e => this.setState({
18 | [field]: e.currentTarget.value
19 | });
20 | }
21 |
22 | handleSubmit(e) {
23 | e.preventDefault();
24 | const user = Object.assign({}, this.state);
25 | this.props.processForm(user)
26 | // .then(() => this.props.login(user))
27 | .then(() => this.props.closeModal()) ;
28 | }
29 |
30 | renderStateItem(state) {
31 | return (
32 | {state}
33 | )
34 | }
35 |
36 | renderErrors() {
37 | return (
38 |
39 | {this.props.errors.map((error, i) => (
40 | {error}
41 | ))}
42 |
43 | );
44 | }
45 |
46 | componentWillUnmount() {
47 | this.props.clearErrors();
48 | }
49 |
50 | render() {
51 | return (
52 |
53 |
Join More S'Mores!
54 |
102 |
103 | );
104 | }
105 | }
106 |
107 | export default AccountForm;
--------------------------------------------------------------------------------
/frontend/components/session_form/login_form_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import React from 'react';
3 | import { Link } from 'react-router-dom';
4 | import { login, clearSessionErrors } from '../../actions/session_actions';
5 | import { openModal, closeModal } from '../../actions/modal_actions';
6 | import SessionForm from './session_form';
7 |
8 | const mapStateToProps = ({errors}) => {
9 | return {
10 | errors: errors.session,
11 | formType: 'Login',
12 | navLink: Sign Up Instead
13 | };
14 | };
15 |
16 | const mapDispatchToProps = dispatch => {
17 | return {
18 | processForm: (user) => dispatch(login(user)),
19 | demoLogin: (user) => dispatch(login(user)),
20 | otherForm: (
21 | dispatch(openModal('signup'))}>Signup!
22 | ),
23 | closeModal: () => dispatch(closeModal()),
24 | clearErrors: () => dispatch(clearSessionErrors())
25 | };
26 | };
27 |
28 | export default connect(mapStateToProps, mapDispatchToProps)(SessionForm);
29 |
--------------------------------------------------------------------------------
/frontend/components/session_form/session_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import { Link } from 'react-router-dom';
3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4 |
5 | class SessionForm extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | email: "",
10 | password: ''
11 | };
12 | this.handleSubmit = this.handleSubmit.bind(this);
13 | this.demoLogin = this.demoLogin.bind(this);
14 | }
15 |
16 | update(field) {
17 | return e => this.setState({
18 | [field]: e.currentTarget.value
19 | });
20 | }
21 |
22 | handleSubmit(e) {
23 | e.preventDefault();
24 | const user = Object.assign({}, this.state);
25 | this.props.processForm(user).then(() => this.props.closeModal());
26 | }
27 |
28 | demoLogin(e) {
29 | e.preventDefault();
30 | this.props.processForm({
31 | email: "smores@moresmores.com",
32 | password: "moresmores"
33 | });
34 | this.props.closeModal();
35 | }
36 |
37 | renderErrors() {
38 | return (
39 |
40 | {this.props.errors.map((error, i) => (
41 |
42 |
46 | {error}
47 |
48 | ))}
49 |
50 | );
51 | }
52 |
53 | componentWillUnmount() {
54 | this.props.clearErrors();
55 | }
56 |
57 | render() {
58 | return (
59 |
60 |
Welcome back!
61 |
62 |
87 |
88 | );
89 | }
90 | }
91 |
92 | export default SessionForm;
--------------------------------------------------------------------------------
/frontend/components/session_form/signup_form_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import React from 'react';
3 | import { Link } from 'react-router-dom';
4 | import { signup, login, clearSessionErrors } from '../../actions/session_actions';
5 | import AccountForm from './create_user_form';
6 | import { openModal, closeModal } from '../../actions/modal_actions';
7 |
8 | const mapStateToProps = ({errors}) => {
9 | return {
10 | errors: errors.session,
11 | formType: 'Signup',
12 | navLink: Log In Instead
13 | };
14 | };
15 |
16 | const mapDispatchToProps = dispatch => {
17 | return {
18 | processForm: (user) => dispatch(signup(user)),
19 | login: (user) => dispatch(login(user)),
20 | otherForm: (
21 | dispatch(openModal('login'))}>Login
22 | ),
23 | closeModal: () => dispatch(closeModal()),
24 | clearErrors: () => dispatch(clearSessionErrors())
25 | };
26 | };
27 |
28 | export default connect(mapStateToProps,mapDispatchToProps)(AccountForm);
--------------------------------------------------------------------------------
/frontend/components/sites/site_create/site_activities.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { updateSite } from '../../../actions/site_actions';
3 | import { connect } from 'react-redux';
4 |
5 | import { library } from '@fortawesome/fontawesome-svg-core'
6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7 | import { faFire, faHiking, faBicycle, faBus, faDog } from '@fortawesome/free-solid-svg-icons';
8 | library.add(faFire);
9 | library.add(faHiking);
10 | library.add(faBicycle);
11 | library.add(faBus);
12 | library.add(faDog);
13 |
14 | class SiteActivities extends React.Component {
15 | constructor(props) {
16 | super(props);
17 | this.state = {arr: [
18 | {type:"hike_activity", active: false, icon:"hiking", title: "Hiking"},
19 | {type:"bike_activity", active: false, icon:"bicycle", title: "Biking"},
20 | {type:"fire_allowed", active: false, icon:"fire", title: "Campfire"},
21 | {type:"rv_allowed", active: false, icon:"bus", title: "RV Parking"},
22 | {type:"pet_allowed", active: false, icon: "dog", title: "Pets"}
23 | ]
24 | }
25 | this.nextForm = this.nextForm.bind(this);
26 | }
27 |
28 | nextForm() {
29 | this.state.arr.map(act => {
30 | this.props.updateSite(act.type, act.active);
31 | })
32 | location.href = "/#/site_create/photo"
33 | }
34 |
35 | update(type) {
36 | return e => this.setState({
37 | [type]: !this.state[type]
38 | })
39 | }
40 |
41 | toggle(index) {
42 | let arr = this.state.arr;
43 | arr[index].active = !arr[index].active;
44 | this.setState({arr: arr});
45 | }
46 |
47 | render() {
48 | let next = (
49 |
53 | Next
54 |
55 | );
56 |
57 | // clean this function to read easier!
58 | let activities = this.state.arr.map( (act, i) =>
59 | this.toggle(i)}>{act.title}
60 | )
61 |
62 | return (
63 |
121 | );
122 |
123 | }
124 | }
125 |
126 | const mapStateToProps = ({entities: {create}}) => {
127 | return {
128 | name: create.name,
129 | description: create.description,
130 | capacity: create.capacity,
131 | price: create.price,
132 | latitude: create.latitude,
133 | longitude: create.longitude,
134 | state: create.state,
135 | city: create.city
136 | };
137 | };
138 |
139 |
140 | const mapDispatchToProps = dispatch => ({
141 | updateSite: (key, value) => dispatch(updateSite(key, value))
142 | });
143 |
144 | export default connect(mapStateToProps, mapDispatchToProps)(SiteActivities);
--------------------------------------------------------------------------------
/frontend/components/sites/site_create/site_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { updateSite } from '../../../actions/site_actions';
4 |
5 |
6 | class SiteForm extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | name: this.props.name,
11 | description: this.props.description,
12 | capacity: this.props.capacity,
13 | price: this.props.price
14 | }
15 | this.nextForm = this.nextForm.bind(this)
16 | }
17 |
18 | nextForm() {
19 | this.props.updateSite("name", this.state.name)
20 | this.props.updateSite("description", this.state.description)
21 | this.props.updateSite("capacity", this.state.capacity)
22 | this.props.updateSite("price", this.state.price)
23 | location.href = "/#/site_create/location"
24 | }
25 |
26 | update(type) {
27 | return e => this.setState({
28 | [type]: e.currentTarget.value
29 | })
30 | }
31 |
32 | render() {
33 | let next;
34 | if (this.state.name === "" || this.state.description === "" || this.state.capacity <= 0 || this.state.price <= 0) {
35 | next = Not Done
36 | } else {
37 | next = Next
38 | }
39 |
40 | return (
41 |
76 | );
77 | }
78 | }
79 |
80 | const mapStateToProps = (state) => {
81 | return {
82 | name: "",
83 | description: "",
84 | capacity: 0,
85 | price: 0
86 | };
87 | };
88 |
89 | const mapDispatchToProps = dispatch => ({
90 | updateSite: (key, value) => dispatch(updateSite(key, value))
91 | });
92 |
93 | export default connect(mapStateToProps,mapDispatchToProps)(SiteForm);
--------------------------------------------------------------------------------
/frontend/components/sites/site_create/site_location.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { updateSite } from '../../../actions/site_actions';
3 | import {connect} from 'react-redux';
4 |
5 | class SiteLocation extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = { latitude: "", longitude: "", state: "", city: "" };
9 | this.onClick = this.onClick.bind(this);
10 | this.states = ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY'];
11 | }
12 |
13 | onClick() {
14 | this.props.updateSite("latitude", this.state.latitude)
15 | this.props.updateSite("longitude", this.state.longitude)
16 | this.props.updateSite("state", this.state.state)
17 | this.props.updateSite("city", this.state.city)
18 | location.href = "/#/site_create/activities"
19 | }
20 |
21 | update(type) {
22 | return e => this.setState({
23 | [type]: e.currentTarget.value
24 | })
25 | }
26 |
27 | renderStateItem(state) {
28 | return (
29 | {state}
30 | )
31 | }
32 |
33 | render() {
34 | let next;
35 | if (this.state.latitude === "" || this.state.longitude === "" || this.state.state === "" || this.state.city === "") {
36 | next = (
37 |
38 | Not Done
39 |
40 | );
41 | } else {
42 | next = (
43 |
47 | Next
48 |
49 | );
50 | }
51 |
52 | return (
53 |
54 |
Where is your site?
55 |
56 | State:
57 |
61 | {this.states.map(state => this.renderStateItem(state))}
62 |
63 |
64 |
71 |
72 |
79 |
80 |
87 |
88 | {next}
89 |
90 | );
91 |
92 | }
93 | }
94 |
95 | const mapStateToProps = ({entities: {create}}) => {
96 | return {
97 | name: create.name,
98 | description: create.description,
99 | capacity: create.capacity,
100 | price: create.price
101 | };
102 | };
103 |
104 |
105 | const mapDispatchToProps = dispatch => ({
106 | updateSite: (key,value) => dispatch(updateSite(key,value))
107 | });
108 |
109 | export default connect(mapStateToProps,mapDispatchToProps)(SiteLocation);
--------------------------------------------------------------------------------
/frontend/components/sites/site_create/site_photo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { updateSite } from '../../../actions/site_actions';
3 | import { connect } from 'react-redux';
4 | import { createSite } from '../../../actions/site_actions';
5 |
6 | class SitePhoto extends React.Component {
7 |
8 | constructor(props) {
9 |
10 | super(props);
11 | this.state = { photo: "" };
12 | this.nextForm = this.nextForm.bind(this);
13 | this.handleFile = this.handleFile.bind(this);
14 | }
15 |
16 | handleFile(e) {
17 | const file = e.currentTarget.files[0];
18 | const fileReader = new FileReader();
19 | fileReader.onloadend = () => {
20 | this.setState({ photoFile: file, photoUrl: fileReader.result });
21 | };
22 | if (file) {
23 | fileReader.readAsDataURL(file);
24 | }
25 | }
26 |
27 | nextForm() {
28 | this.props.updateSite("photo", this.state.photo)
29 | const formData = new FormData();
30 | formData.append('site[name]', this.props.name);
31 | formData.append('site[description]', this.props.description);
32 | formData.append('site[capacity]', this.props.capacity);
33 | formData.append('site[price]', this.props.price);
34 | formData.append('site[fire_allowed]', this.props.fire_allowed);
35 | formData.append('site[rv_allowed]', this.props.rv_allowed);
36 | formData.append('site[pet_allowed]', this.props.pet_allowed);
37 | formData.append('site[bike_activity]', this.props.bike_activity);
38 | formData.append('site[hike_activity]', this.props.hike_activity);
39 | formData.append('site[capacity]', this.props.capacity);
40 | formData.append('site[latitude]', this.props.latitude);
41 | formData.append('site[longitude]', this.props.longitude);
42 | formData.append('site[state]', this.props.state);
43 | formData.append('site[city]', this.props.city);
44 | formData.append('site[photo]', this.state.photoFile);
45 | this.props.createSite(formData)
46 | .then(this.props.history.push('/sites'));
47 | }
48 |
49 | update(type) {
50 | return e => this.setState({
51 | [type]: e.currentTarget.value
52 | })
53 | }
54 |
55 |
56 | render() {
57 | let next = (
58 |
62 | Submit Site
63 |
64 | );
65 |
66 | const photoPreview = (
67 |
68 |
Photo Preview
69 |
70 |
71 | );
72 |
73 | const preview = this.state.photoUrl ? photoPreview : Photo Required!
;
74 |
75 | return (
76 |
77 |
Add a photo to complete your site!
78 | {preview}
79 |
80 |
81 |
82 | {next}
83 |
84 | )
85 |
86 | }
87 | }
88 |
89 | const mapStateToProps = ({entities: {create}}) => {
90 | return {
91 | name: create.name,
92 | description: create.description,
93 | capacity: create.capacity,
94 | price: create.price,
95 | latitude: create.latitude,
96 | longitude: create.longitude,
97 | state: create.state,
98 | city: create.city,
99 | bike_activity: create.bike_activity,
100 | fire_allowed: create.fire_allowed,
101 | hike_activity: create.hike_activity,
102 | pet_allowed: create.pet_allowed,
103 | rv_allowed: create.rv_allowed
104 | };
105 | };
106 |
107 | const mapDispatchToProps = dispatch => ({
108 | updateSite: (key, value) => dispatch(updateSite(key, value)),
109 | createSite: (site) => dispatch(createSite(site))
110 | });
111 |
112 | export default connect(mapStateToProps, mapDispatchToProps)(SitePhoto);
--------------------------------------------------------------------------------
/frontend/components/sites/site_edit/site_edit_activity.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { fetchSite, updateExistSite } from '../../../actions/site_actions';
4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5 |
6 | class SiteEditActivity extends React.Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = this.props.site
10 | this.nextForm = this.nextForm.bind(this)
11 | this.finishEdit = this.finishEdit.bind(this)
12 | this.state = {
13 | site: this.props.site,
14 | arr: [
15 | { type: "hike_activity", active: this.props.site.hike_activity, icon: "hiking", title: "Hiking" },
16 | { type: "bike_activity", active: this.props.site.bike_activity, icon: "bicycle", title: "Biking" },
17 | { type: "fire_allowed", active: this.props.site.fire_allowed, icon: "fire", title: "Campfire" },
18 | { type: "rv_allowed", active: this.props.site.rv_allowed, icon: "bus", title: "RV Parking" },
19 | { type: "pet_allowed", active: this.props.site.pet_allowed, icon: "dog", title: "Pets" }
20 | ]
21 | }
22 | }
23 |
24 | finishEdit() {
25 | let newSite = this.state.site;
26 | this.state.arr.map(act => {
27 | newSite[act.type] = act.active
28 | })
29 | this.props.updateSite(newSite)
30 | location.href = `/#/sites/${this.state.site.id}`
31 | }
32 |
33 | nextForm() {
34 | let newSite = this.state.site;
35 | this.state.arr.map(act => {
36 | newSite[act.type] = act.active
37 | })
38 | this.props.updateSite(newSite)
39 | }
40 |
41 | update(type) {
42 | return e => this.setState({
43 | [type]: !this.state[type]
44 | })
45 | }
46 |
47 | toggle(index) {
48 | let arr = this.state.arr;
49 | arr[index].active = !arr[index].active;
50 | this.setState({ arr: arr });
51 | }
52 |
53 | componentDidMount() {
54 | this.props.fetchSite(this.props.match.params.siteId);
55 | }
56 |
57 | render() {
58 |
59 | let next;
60 | if (this.state.name === "" || this.state.description === "" || this.state.capacity <= 0 || this.state.price <= 0) {
61 | next = Not Done
62 | } else {
63 | next =
64 | <>
65 | Finish Editing
66 | {/* this next line should route to edit the photo for a site */}
67 | {/* Finish Editing */}
68 | >
69 | }
70 |
71 | let activities = this.state.arr.map((act, i) =>
72 | this.toggle(i)}>{act.title}
73 | )
74 |
75 | return (
76 |
77 |
What amentities are closeby?
78 |
79 | {activities}
80 |
81 | {next}
82 |
83 | )
84 | }
85 | }
86 |
87 | const mapStateToProps = (state, ownProps) => {
88 | return {
89 | site: state.entities.sites[ownProps.match.params.siteId]
90 | }
91 | }
92 |
93 | const mapDispatchToProps = dispatch => ({
94 | updateSite: (site) => dispatch(updateExistSite(site)),
95 | fetchSite: (id) => dispatch(fetchSite(id))
96 | });
97 |
98 | export default connect(mapStateToProps,mapDispatchToProps)(SiteEditActivity);
--------------------------------------------------------------------------------
/frontend/components/sites/site_edit/site_edit_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { fetchSite, updateExistSite } from '../../../actions/site_actions';
4 |
5 | class SiteEdit extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | // this.state = {name: this.props.site.name, description: this.props.site.description, capacity: this.props.site.capacity, price: this.props.site.price }
9 | this.state = this.props.site
10 | this.nextForm = this.nextForm.bind(this)
11 | this.finishEdit = this.finishEdit.bind(this)
12 | }
13 |
14 | finishEdit() {
15 | this.props.updateSite(this.state)
16 | location.href=`/#/sites/${this.state.id}`
17 | }
18 |
19 | nextForm() {
20 | this.props.updateSite(this.state)
21 | location.href=`/#/site_edit_location/${this.state.id}`
22 | }
23 |
24 | update(type) {
25 | return e => this.setState({
26 | [type]: e.currentTarget.value
27 | })
28 | }
29 |
30 | componentDidMount() {
31 | this.props.fetchSite(this.props.match.params.siteId);
32 | }
33 |
34 |
35 | render() {
36 | let next;
37 | if (this.state.name === "" || this.state.description === "" || this.state.capacity <= 0 || this.state.price <= 0) {
38 | next = Not Done
39 | } else {
40 | next =
41 | <>
42 | Finish Editing
43 | Continue Editing
44 | >
45 | }
46 | return (
47 |
83 | );
84 | }
85 | }
86 |
87 | const mapStateToProps = (state, ownProps) => {
88 | return {
89 | site: state.entities.sites[ownProps.match.params.siteId]
90 | }
91 | }
92 |
93 | const mapDispatchToProps = dispatch => ({
94 | updateSite: (site) => dispatch(updateExistSite(site)),
95 | fetchSite: (id) => dispatch(fetchSite(id))
96 | });
97 |
98 | export default connect(mapStateToProps, mapDispatchToProps)(SiteEdit);
--------------------------------------------------------------------------------
/frontend/components/sites/site_edit/site_edit_location.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { fetchSite, updateExistSite } from '../../../actions/site_actions';
4 |
5 | class SiteEditLocation extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = this.props.site
9 | this.nextForm = this.nextForm.bind(this)
10 | this.finishEdit = this.finishEdit.bind(this)
11 | this.states = ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY'];
12 | }
13 |
14 | finishEdit() {
15 | this.props.updateSite(this.state)
16 | location.href = `/#/sites/${this.state.id}`
17 | }
18 |
19 | nextForm() {
20 | this.props.updateSite(this.state)
21 | location.href = `/#/site_edit_activity/${this.state.id}`
22 | }
23 |
24 | update(type) {
25 | return e => this.setState({
26 | [type]: e.currentTarget.value
27 | })
28 | }
29 |
30 | renderStateItem(state) {
31 | if (state === this.state.state ) {
32 | return ({state} )
33 | } else {
34 | return ({state} )
35 | }
36 | }
37 |
38 | componentDidMount() {
39 | this.props.fetchSite(this.props.match.params.siteId);
40 | }
41 |
42 | render() {
43 | let next;
44 | if (this.state.latitude === "" || this.state.longitude === "" || this.state.state === "" || this.state.city === "") {
45 | next = (
46 |
47 | No Fields Can Be Blank
48 |
49 | );
50 | } else {
51 | next =
52 | <>
53 | Finish Editing
54 | Continue Editing
55 | >
56 | }
57 | return (
58 |
59 |
Where is your site?
60 |
61 | State:
62 |
66 | {this.states.map(state => this.renderStateItem(state))}
67 |
68 |
69 |
76 |
77 |
84 |
85 |
92 |
93 | {next}
94 |
95 | )
96 | }
97 | }
98 |
99 | const mapStateToProps = (state,ownProps) => {
100 | return {
101 | site: state.entities.sites[ownProps.match.params.siteId]
102 | }
103 | }
104 |
105 | const mapDispatchToProps = dispatch => ({
106 | updateSite: (site) => dispatch(updateExistSite(site)),
107 | fetchSite: (id) => dispatch(fetchSite(id))
108 | });
109 |
110 | export default connect(mapStateToProps,mapDispatchToProps)(SiteEditLocation)
--------------------------------------------------------------------------------
/frontend/components/sites/site_edit/site_edit_photo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { fetchSite, updateExistSite } from '../../../actions/site_actions';
4 |
5 |
6 | class SiteEditPhoto extends React.Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = this.props.site;
10 | this.finishEdit = this.finishEdit.bind(this);
11 | this.handleFile = this.handleFile.bind(this);
12 | }
13 |
14 | handleFile(e) {
15 | const file = e.currentTarget.files[0];
16 | const fileReader = new FileReader();
17 | fileReader.onloadend = () => {
18 | this.setState({ photoFile: file, photoUrl: fileReader.result });
19 | };
20 | if (file) {
21 | fileReader.readAsDataURL(file);
22 | }
23 | }
24 |
25 |
26 | }
--------------------------------------------------------------------------------
/frontend/components/sites/site_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class SiteForm extends React.Component {
4 | constructor(props) {
5 | super(props)
6 | this.state = this.props.site;
7 | this.handleSubmit = this.handleSubmit.bind(this);
8 | this.handleFile = this.handleFile.bind(this);
9 | this.states = ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY'];
10 | }
11 |
12 | update(field) {
13 | return e => this.setState({
14 | [field]: e.target.value
15 | })
16 | }
17 |
18 | handleSubmit(e) {
19 | e.preventDefault();
20 | const formData = new FormData();
21 | formData.append('site[name]',this.state.name);
22 | formData.append('site[description]', this.state.description);
23 | formData.append('site[capacity]', this.state.capacity);
24 | formData.append('site[fire_allowed]', this.state.fire_allowed);
25 | formData.append('site[rv_allowed]', this.state.rv_allowed);
26 | formData.append('site[pet_allowed]', this.state.pet_allowed);
27 | formData.append('site[bike_activity]', this.state.bike_activity);
28 | formData.append('site[hike_activity]', this.state.hike_activity);
29 | formData.append('site[capacity]', this.state.capacity);
30 | formData.append('site[latitude]', this.state.latitude);
31 | formData.append('site[longitude]', this.state.longitude);
32 | formData.append('site[state]', this.state.state);
33 | formData.append('site[photo]', this.state.photoFile);
34 | this.props.createSite(formData)
35 | .then(this.props.history.push('/sites'));
36 | }
37 |
38 | handleFile(e) {
39 | const file = e.currentTarget.files[0];
40 | const fileReader = new FileReader();
41 | fileReader.onloadend = () => {
42 | this.setState({ photoFile: file, photoUrl: fileReader.result });
43 | };
44 | if (file) {
45 | fileReader.readAsDataURL(file);
46 | }
47 | }
48 |
49 | renderStateItem(state) {
50 | return (
51 | {state}
52 | )
53 | }
54 |
55 | render() {
56 |
57 | const photoPreview = (
58 | <>
59 | Photo Preview
60 |
61 | >
62 | );
63 |
64 | const preview = this.state.photoUrl ? photoPreview : Add a photo! ;
65 | return(
66 |
67 |
120 |
121 | )
122 | };
123 |
124 | }
125 |
126 | export default SiteForm;
--------------------------------------------------------------------------------
/frontend/components/sites/site_map.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import ReactDOM from 'react-dom';
3 | // import { withRouter } from 'react-router-dom';
4 |
5 | // import MarkerManager from "../../util/marker_manager";
6 |
7 |
8 | class SiteMap extends React.Component {
9 | // componentDidMount() {
10 | // const mapOptions = {
11 | // center: {lat: 37.7758, lng:-122.435},
12 | // zoom: 13
13 | // };
14 |
15 | // this.map = new google.maps.Map(this.mapNode, mapOptions);
16 | // this.MarkerManager = new MarkerManager(this.map, this.handleClick.bind(this));
17 | // // this.addListeners = this.addListeners.bind(this);
18 |
19 | // this.MarkerManager.updateMarkers(this.state.sites);
20 | // // this.addListeners();
21 | // }
22 |
23 | // componentDidUpdate() {
24 | // this.MarkerManager.updateMarkers(this.props.sites);
25 | // }
26 |
27 | // addListeners() {
28 | // google.maps.event.addListener(this.map, 'idle', () => {
29 | // const { north, south, east, west } = this.map.getBounds().toJSON();
30 | // const bounds = {
31 | // northEast: { lat: north, lng: east },
32 | // southWest: { lat: south, lng: west } };
33 | // this.props.updateFilter('bounds', bounds);
34 | // });
35 | // }
36 |
37 | // handleClick(site) {
38 | // this.props.history.push(`/#/sites/${site.id}`);
39 | // }
40 |
41 | render() {
42 | return (
43 | this.mapNode = map }>
44 | );
45 | }
46 |
47 | }
48 |
49 |
50 | export default SiteMap;
--------------------------------------------------------------------------------
/frontend/components/sites/site_show.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router-dom';
3 | import Modal from '../modal';
4 |
5 | // icons
6 | import { library } from "@fortawesome/fontawesome-svg-core";
7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
8 | import {
9 | faFire,
10 | faHiking,
11 | faBicycle,
12 | faBus,
13 | faDog,
14 | faCampground,
15 | faMapMarkerAlt
16 | } from "@fortawesome/free-solid-svg-icons";
17 | library.add(faFire);
18 | library.add(faHiking);
19 | library.add(faCampground);
20 | library.add(faMapMarkerAlt);
21 |
22 | class SiteShow extends React.Component {
23 | constructor(props) {
24 | super(props)
25 | }
26 |
27 | componentDidMount() {
28 | this.props.fetchSite(this.props.match.params.id);
29 | }
30 |
31 | render() {
32 | const editButton = (
33 |
34 |
35 | Edit Site!
36 |
37 | this.props.deleteSite(site.id)
40 | .then(this.props.history.push('/sites'))}
41 | >Delete Site
42 |
43 | );
44 |
45 | const bookButton = (
46 |
47 | this.props.openModal("booking")}
49 | className="booking-button"
50 | >
51 | Book Now!
52 |
53 |
54 | )
55 |
56 | const unauthbookButton = (
57 |
58 | this.props.openModal("login")}
60 | className="booking-button"
61 | >
62 | Book Now!
63 |
64 |
65 | )
66 |
67 |
68 | let site = this.props.site;
69 | const edit = (site.user_id === this.props.sessionId) ? editButton : <>>
70 | const book = (this.props.sessionId===null) ? unauthbookButton: bookButton
71 | return (
72 |
73 |
74 |
75 |
76 |
77 |
78 |
{site.name}
79 |
{site.description}
80 |
81 |
82 |
83 |
Essentials
84 |
85 |
86 | Bring Your
87 | Own Tent
88 |
89 |
90 | {" "}
91 | {site.capacity} Spots
92 |
93 |
94 |
95 |
96 |
Activities
97 |
98 |
99 |
104 | Campfire Allowed
105 |
106 |
107 |
108 |
113 | RV Parking
114 |
115 |
116 |
117 |
122 | Pets Allowed
123 |
124 |
125 |
126 |
131 | Biking Nearby
132 |
133 |
134 |
135 |
140 | Hiking Nearby
141 |
142 |
143 |
144 |
145 |
146 |
147 |
${site.price}
148 |
Per Night
149 |
150 | {/*
151 | this.props.openModal("booking")}
153 | className="booking-button"
154 | >
155 | Book Now!
156 |
157 |
*/}
158 | {book}
159 |
160 | {edit}
161 |
162 |
163 |
164 |
165 | );
166 | }
167 | }
168 |
169 | export default withRouter(SiteShow);
170 |
171 |
172 |
--------------------------------------------------------------------------------
/frontend/components/sites/site_show_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { fetchSite, deleteSite } from '../../actions/site_actions';
3 | import SiteShow from './site_show';
4 | import { openModal } from '../../actions/modal_actions';
5 |
6 | const mapStateToProps = (state, ownProps) => {
7 | return {
8 | site: state.entities.sites[ownProps.match.params.id] || {},
9 | sessionId: state.session.id
10 | }
11 | };
12 |
13 | const mapDispatchToProps = dispatch => ({
14 | fetchSite: id => dispatch(fetchSite(id)),
15 | deleteSite: site => dispatch(deleteSite(site)),
16 | openModal: modal => dispatch(openModal(modal))
17 | });
18 |
19 | export default connect(mapStateToProps,mapDispatchToProps)(SiteShow);
--------------------------------------------------------------------------------
/frontend/components/sites/sites_detail.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter, Link} from 'react-router-dom';
3 |
4 | class SiteDetail extends React.Component {
5 | constructor (props) {
6 | super(props)
7 | }
8 |
9 | render () {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
{this.props.site.name}
20 |
21 |
22 |
23 |
{this.props.site.city}, {this.props.site.state}
24 |
25 |
26 |
${this.props.site.price}/Night
27 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 | }
36 |
37 | export default withRouter(SiteDetail);
--------------------------------------------------------------------------------
/frontend/components/sites/sites_detail_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { fetchSite } from '../../actions/site_actions';
3 | import SiteDetail from './sites_detail';
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | site: state.entities.sites[ownProps.match.params.siteId]
8 | }
9 | };
10 |
11 | const mapDispatchToProps = dispatch => ({
12 | fetchSite: (id) => dispatch(fetchSite(id))
13 | });
14 |
15 | export default connect(mapStateToProps,mapDispatchToProps)(SiteDetail);
--------------------------------------------------------------------------------
/frontend/components/sites/sites_form_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import SiteForm from './site_form';
3 | import { createSite } from '../../actions/site_actions';
4 |
5 | const mapStateToProps = state => {
6 | return({
7 | site: {
8 | name: "",
9 | capacity: 0,
10 | fire_allowed: true,
11 | rv_allowed: false,
12 | pet_allowed: false,
13 | bike_activity: false,
14 | hike_activity: false,
15 | latitude: "",
16 | longitude: "",
17 | state: "",
18 | description: "",
19 | photo: ""
20 | },
21 | formType: "Create Site"
22 | })
23 | }
24 |
25 | const mapDispatchToProps = dispatch => {
26 | return {
27 | createSite: (site) => dispatch(createSite(site))
28 | };
29 | };
30 |
31 | export default connect(mapStateToProps,mapDispatchToProps)(SiteForm)
32 |
--------------------------------------------------------------------------------
/frontend/components/sites/sites_index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SiteDetail from './sites_detail';
3 | import MarkerManager from '../../util/marker_manager';
4 |
5 | class SitesIndex extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | componentDidMount () {
11 | this.props.fetchSites();
12 | }
13 |
14 | componentDidUpdate() {
15 | const mapOptions = {
16 | center: { lat: 37.7758, lng: -122.435 },
17 | zoom: 9
18 | };
19 | const infoWindow = new google.maps.InfoWindow();
20 | this.map = new google.maps.Map(this.mapNode, mapOptions);
21 | this.MarkerManager = new MarkerManager(this.map);
22 | this.MarkerManager.updateMarkers(this.props.sites);
23 | // this.MarkerManager.markers.forEach(marker, () => console.log(marker.site));
24 | }
25 |
26 | render () {
27 | const siteItems = this.props.sites.map(site => ( ))
28 | return (
29 |
30 |
The best camping around!
31 |
32 |
37 |
38 |
this.mapNode = map }>
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | export default SitesIndex;
--------------------------------------------------------------------------------
/frontend/components/sites/sites_index_container.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'react-redux';
2 | import selectAllSites from '../../reducers/selectors';
3 | import {fetchSites} from '../../actions/site_actions';
4 | import SitesIndex from './sites_index';
5 |
6 | const mapStateToProps = state => ({
7 | sites: selectAllSites(state)
8 | })
9 |
10 | const mapDispatchToProps = dispatch => {
11 | return {
12 | fetchSites: () => dispatch(fetchSites())
13 | }
14 | };
15 |
16 | export default connect(mapStateToProps,mapDispatchToProps)(SitesIndex);
--------------------------------------------------------------------------------
/frontend/components/sites/sites_search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SiteDetail from './sites_detail';
3 | import MarkerManager from '../../util/marker_manager';
4 |
5 | class SiteSearchIndex extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | searchResults: this.props.searchSites(this.props.match.params.search)
10 | }
11 | }
12 |
13 | componentDidMount() {
14 | this.props.searchSites(this.props.match.params.search);
15 | }
16 |
17 | componentDidUpdate() {
18 | const mapOptions = {
19 | center: { lat: 37.7758, lng: -122.435 },
20 | zoom: 10
21 | };
22 | this.map = new google.maps.Map(this.mapNode, mapOptions);
23 | this.MarkerManager = new MarkerManager(this.map);
24 | this.MarkerManager.updateMarkers(this.props.sites);
25 | }
26 |
27 | render() {
28 | const siteItems = this.props.sites.map(site => ( ))
29 |
30 | const noItems = () => {
31 | return (
32 |
33 |
No Sites Matched Your Search!
34 |
35 |
39 |
40 |
this.mapNode = map}>
41 |
42 |
43 |
44 | );
45 | }
46 |
47 |
48 | const yesItems = () => {
49 | return (
50 |
51 |
The best camping around!
52 |
53 |
58 |
59 |
this.mapNode = map}>
60 |
61 |
62 |
63 | );
64 | }
65 |
66 | return this.props.sites.length > 0 ? yesItems() : noItems();
67 | // return (
68 | // this.props.sites && (
69 | //
70 | //
The best camping around!
71 | //
72 | //
77 | //
78 | //
this.mapNode = map}>
79 | //
80 | //
81 | //
82 |
83 |
84 | // )
85 | // );
86 | }
87 | }
88 |
89 | export default SiteSearchIndex;
--------------------------------------------------------------------------------
/frontend/components/sites/sites_search_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import selectAllSites from '../../reducers/selectors';
3 | import { searchSites } from '../../actions/site_actions';
4 | import SiteSearchIndex from './sites_search';
5 |
6 | const mapStateToProps = state => ({
7 | sites: selectAllSites(state)
8 | })
9 |
10 | const mapDispatchToProps = dispatch => {
11 | return {
12 | searchSites: condition => dispatch(searchSites(condition))
13 | }
14 | };
15 |
16 | export default connect(mapStateToProps,mapDispatchToProps)(SiteSearchIndex);
--------------------------------------------------------------------------------
/frontend/components/splash.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SplashSearch from "./search/search_splash";
3 |
4 | const Splash = () => {
5 | const splash_title = [
6 | "Find yourself outside.",
7 | "Everywhere you want to camp.",
8 | "It was in-tents.",
9 | "A perfect day would be to get into the car, drive out to Yosemite, and go camping.",
10 | "The woods are lovely, dark and deep.",
11 | "If people sat outside and looked at the stars each night, I'll bet they'd live a lot differently",
12 | "My wish is to stay always like this, living quietly in a corner of nature.",
13 | "Look deep into nature, and then you will understand everything better.",
14 | "I go to nature to be soothed and healed, and to have my sense put in order.",
15 | "Real freedom lies in wildness, not in civilization.",
16 | "Roses are red, mud is brown, the wood are better than any night on the town.",
17 | "The wilderness holds answers to questions we have not yet learned to ask.",
18 | "The mountains are calling and I must go.",
19 | "A bad day camping is still better than a good day working.",
20 | "Wilderness is not a luxury but necessity of the human spirit.",
21 | "We can never have enough of nature."
22 | ];
23 | const random_splash = splash_title[Math.floor(Math.random() * splash_title.length)];
24 |
25 | return (
26 |
27 |
28 |
29 | {random_splash}
30 |
31 |
32 |
33 | Book unique camping experiences on over{" "}
34 | 300,000 campsites, ranches, vineyards, public
35 | parks and more.
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 |
46 | export default Splash;
--------------------------------------------------------------------------------
/frontend/hip_camp.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import configureStore from './store/store';
4 | import Root from './components/root';
5 |
6 |
7 | document.addEventListener('DOMContentLoaded', () => {
8 | let root = document.getElementById('root');
9 | let store = configureStore();
10 |
11 | // remove after testing
12 | // window.getState = store.getState;
13 | // window.dispatch = store.dispatch;
14 | // end of testing
15 |
16 | if (window.currentUser) {
17 | const preloadedState = {
18 | session: {id: window.currentUser.id},
19 | entities: {
20 | users: { [window.currentUser.id]: window.currentUser }
21 | }
22 | };
23 | store = configureStore(preloadedState);
24 | delete window.currentUser;
25 | } else {
26 | store = configureStore();
27 | }
28 |
29 |
30 | ReactDOM.render( , root);
31 | })
--------------------------------------------------------------------------------
/frontend/reducers/bookings_reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_ALL_BOOKINGS,
3 | RECEIVE_BOOKING,
4 | CREATE_BOOKING,
5 | REMOVE_BOOKING
6 | } from '../actions/bookings_actions';
7 |
8 | import {merge} from 'lodash';
9 |
10 | const bookingsReducer = (state={}, action) => {
11 | Object.freeze(state);
12 | switch (action.type) {
13 | case RECEIVE_ALL_BOOKINGS:
14 | return action.bookings || {};
15 | case RECEIVE_BOOKING:
16 | let newState = merge({}, state, action.booking);
17 | return newState;
18 | case REMOVE_BOOKING:
19 | newState = merge({}, state);
20 | delete newState[action.bookingId];
21 | return newState;
22 | case CREATE_BOOKING:
23 | return merge({}, state, {booking: action.booking})
24 | default:
25 | return state;
26 | }
27 | }
28 |
29 | export default bookingsReducer;
--------------------------------------------------------------------------------
/frontend/reducers/create_reducer.js:
--------------------------------------------------------------------------------
1 | import {UPDATE_SITE_STATE} from '../actions/site_actions';
2 | import {merge} from 'lodash';
3 |
4 |
5 | const createReducer = (state = {}, action) => {
6 | Object.freeze(state);
7 | switch(action.type) {
8 | case UPDATE_SITE_STATE:
9 | const newSite = { [action.key]: action.value }
10 | return merge({},state,newSite);
11 | default:
12 | return state;
13 | }
14 | }
15 |
16 | export default createReducer;
--------------------------------------------------------------------------------
/frontend/reducers/entities_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import usersReducer from './users_reducer';
4 | import sitesReducer from './sites_reducer';
5 | import createReducer from './create_reducer';
6 | import bookingReducer from './bookings_reducer';
7 |
8 |
9 | export default combineReducers({
10 | users: usersReducer,
11 | sites: sitesReducer,
12 | create: createReducer,
13 | bookings: bookingReducer
14 | });
--------------------------------------------------------------------------------
/frontend/reducers/errors_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import sessionErrorsReducer from './session_errors_reducer';
4 |
5 | const errorsReducer = combineReducers({
6 | session: sessionErrorsReducer
7 | });
8 |
9 | export default errorsReducer;
10 |
--------------------------------------------------------------------------------
/frontend/reducers/modal_reducer.js:
--------------------------------------------------------------------------------
1 | import { OPEN_MODAL, CLOSE_MODAL } from '../actions/modal_actions';
2 |
3 | export default function modalReducer(state = null, action) {
4 | switch (action.type) {
5 | case OPEN_MODAL:
6 | return action.modal
7 | case CLOSE_MODAL:
8 | return null;
9 | default:
10 | return state;
11 | }
12 | }
--------------------------------------------------------------------------------
/frontend/reducers/root_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import entities from './entities_reducer';
4 | import bookings from './bookings_reducer';
5 | import session from './session_reducer';
6 | import sites from './sites_reducer';
7 | import users from './users_reducer';
8 | import errors from './errors_reducer';
9 | import ui from './ui_reducer';
10 |
11 | const rootReducer = combineReducers({
12 | entities,
13 | session,
14 | sites,
15 | users,
16 | bookings,
17 | ui,
18 | errors
19 | });
20 |
21 | export default rootReducer;
--------------------------------------------------------------------------------
/frontend/reducers/selectors.js:
--------------------------------------------------------------------------------
1 | const selectAllSites = (state) => {
2 | return Object.values(state.entities.sites);
3 | };
4 |
5 | export default selectAllSites;
--------------------------------------------------------------------------------
/frontend/reducers/session_errors_reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_SESSION_ERRORS,
3 | RECEIVE_CURRENT_USER,
4 | CLEAR_SESSION_ERRORS
5 | } from '../actions/session_actions';
6 | import { CLOSE_MODAL } from '../actions/modal_actions';
7 |
8 | export default (state = [], action) => {
9 | Object.freeze(state);
10 | switch (action.type) {
11 | case RECEIVE_SESSION_ERRORS:
12 | return action.errors;
13 | case RECEIVE_CURRENT_USER:
14 | return [];
15 | case CLOSE_MODAL:
16 | return [];
17 | case CLEAR_SESSION_ERRORS:
18 | return [];
19 | default:
20 | return state;
21 | }
22 | };
--------------------------------------------------------------------------------
/frontend/reducers/session_reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_CURRENT_USER,
3 | LOGOUT_CURRENT_USER
4 | } from '../actions/session_actions';
5 |
6 | const _nullUser = Object.freeze({
7 | id: null
8 | });
9 |
10 | const sessionReducer = (state = _nullUser, action) => {
11 | Object.freeze(state);
12 | switch(action.type) {
13 | case RECEIVE_CURRENT_USER:
14 | return { id: action.currentUser.id };
15 | case LOGOUT_CURRENT_USER:
16 | return _nullUser;
17 | default:
18 | return state;
19 | }
20 | };
21 |
22 | export default sessionReducer;
--------------------------------------------------------------------------------
/frontend/reducers/sites_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_ALL_SITES, RECEIVE_SITE, RECEIVE_SEARCH_SITES, UPDATE_SITE_STATE, REMOVE_SITE} from '../actions/site_actions';
2 | import {merge} from 'lodash';
3 |
4 | const siteReducer = (state = {}, action) => {
5 | Object.freeze(state);
6 | switch(action.type) {
7 | case RECEIVE_ALL_SITES:
8 | return action.sites
9 | case RECEIVE_SITE:
10 | let newState = merge({},state,action.site);
11 | return newState;
12 | case REMOVE_SITE:
13 | newState = merge({}, state);
14 | delete newState[action.site];
15 | return newState;
16 | case RECEIVE_SEARCH_SITES:
17 | return action.listings;
18 | default:
19 | return state;
20 | }
21 | };
22 |
23 | export default siteReducer;
--------------------------------------------------------------------------------
/frontend/reducers/ui_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import modal from './modal_reducer';
4 |
5 | export default combineReducers({
6 | modal
7 | });
--------------------------------------------------------------------------------
/frontend/reducers/users_reducer.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash/merge';
2 |
3 | import { RECEIVE_CURRENT_USER } from '../actions/session_actions';
4 |
5 | const usersReducer = (state={}, action) => {
6 | Object.freeze(state);
7 | switch(action.type) {
8 | case RECEIVE_CURRENT_USER:
9 | return merge({},state, {[action.currentUser.id]: action.currentUser });
10 | default:
11 | return state;
12 | }
13 | }
14 |
15 | export default usersReducer;
16 |
--------------------------------------------------------------------------------
/frontend/store/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import logger from 'redux-logger';
4 |
5 | import rootReducer from '../reducers/root_reducer';
6 |
7 | const configureStore = (preloadedState = {}) => {
8 | let middleWare = [thunk];
9 | if (process.env.NODE_ENV !== 'production') {
10 | middleWare = [...middleWare];
11 | // middleWare = [...middleWare, logger];
12 | }
13 | return createStore(
14 | rootReducer,
15 | preloadedState,
16 | applyMiddleware(...middleWare)
17 | );
18 | };
19 |
20 | export default configureStore;
--------------------------------------------------------------------------------
/frontend/util/bookings_api_util.js:
--------------------------------------------------------------------------------
1 | export const fetchBookings = () => {
2 | return $.ajax({
3 | method: "GET",
4 | url: "/api/bookings"
5 | })
6 | };
7 |
8 | export const createBooking = booking => {
9 | return $.ajax({
10 | method: 'POST',
11 | url: `/api/sites/${booking.site_id}/bookings`,
12 | data: {booking}
13 | })
14 | }
15 |
16 | export const deleteBooking = id => {
17 | return $.ajax({
18 | method: 'DELETE',
19 | url: `/api/bookings/${id}`
20 | })
21 | }
--------------------------------------------------------------------------------
/frontend/util/marker_manager.js:
--------------------------------------------------------------------------------
1 | class MarkerManager {
2 | constructor(map, handleClick) {
3 | this.map = map;
4 | // this.handleClick = handleClick;
5 | this.markers = {};
6 | }
7 |
8 | updateMarkers(sites) {
9 | const sitesObj = {};
10 | sites.forEach(site => sitesObj[site.id] = site);
11 |
12 | sites
13 | .filter(site => !this.markers[site.id])
14 | // .forEach(newSite => this.createMarkerFromSite(newSite, this.handleClick))
15 | .forEach(newSite => this.createMarkerFromSite(newSite))
16 |
17 | Object.keys(this.markers)
18 | .filter(siteId => !sitesObj[siteId])
19 | .forEach((siteId) => this.removeMarker(this.markers[siteId]))
20 | }
21 |
22 | createMarkerFromSite(site) {
23 | const position = new google.maps.LatLng(site.latitude, site.longitude);
24 | const marker = new google.maps.Marker({
25 | position,
26 | // label: site.name,
27 | map: this.map,
28 | siteId: site.id
29 | });
30 | this.markers[marker.siteId] = marker;
31 | }
32 |
33 | removeMarker(marker) {
34 | this.markers[marker.siteId].setMap(null);
35 | delete this.markers[marker.siteId];
36 | }
37 | }
38 |
39 | export default MarkerManager;
--------------------------------------------------------------------------------
/frontend/util/route_util.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Route,
4 | Redirect,
5 | withRouter
6 | } from 'react-router-dom';
7 |
8 |
9 | const Auth = ({component: Component, path, loggedIn, exact}) => (
10 | (
11 | !loggedIn? (
12 |
13 | ) : (
14 |
15 | )
16 | )} />
17 | );
18 |
19 | const Protected = ({ component: Component, path, loggedIn, exact }) => (
20 | (
21 | loggedIn? (
22 |
23 | ) : (
24 |
25 | )
26 | )} />
27 | );
28 |
29 | const mapStateToProps = state => (
30 | {loggedIn: Boolean(state.session.id)}
31 | );
32 |
33 | export const AuthRoute = withRouter(connect(mapStateToProps)(Auth));
34 | export const ProtectedRoute = withRouter(connect(mapStateToProps)(Protected));
--------------------------------------------------------------------------------
/frontend/util/route_util.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Route,
4 | Redirect,
5 | withRouter
6 | } from 'react-router-dom';
7 |
8 |
9 | const Auth = ({component: Component, path, loggedIn, exact}) => (
10 | (
11 | !loggedIn? (
12 |
13 | ) : (
14 |
15 | )
16 | )} />
17 | );
18 |
19 | const Protected = ({ component: Component, path, loggedIn, exact }) => (
20 | (
21 | loggedIn? (
22 |
23 | ) : (
24 |
25 | )
26 | )} />
27 | );
28 |
29 | const mapStateToProps = state => (
30 | {loggedIn: Boolean(state.session.id)}
31 | );
32 |
33 | export const AuthRoute = withRouter(connect(mapStateToProps)(Auth));
34 | export const ProtectedRoute = withRouter(connect(mapStateToProps)(Protected));
--------------------------------------------------------------------------------
/frontend/util/session_api_util.js:
--------------------------------------------------------------------------------
1 | export const login = user => (
2 | $.ajax({
3 | method: 'POST',
4 | url: '/api/session',
5 | data: { user }
6 | })
7 | );
8 |
9 | export const signup = user => (
10 | $.ajax({
11 | method: 'POST',
12 | url: '/api/users',
13 | data: { user }
14 | })
15 | );
16 |
17 | export const logout = () => (
18 | $.ajax({
19 | method: 'DELETE',
20 | url: '/api/session'
21 | })
22 | );
--------------------------------------------------------------------------------
/frontend/util/site_api_util.js:
--------------------------------------------------------------------------------
1 | export const fetchSites = () => (
2 | $.ajax({
3 | method: "GET",
4 | url: "/api/sites"
5 | })
6 | );
7 |
8 | export const fetchSite = (id) => (
9 | $.ajax({
10 | method: "GET",
11 | url: `/api/sites/${id}`
12 | })
13 | );
14 |
15 | export const createSite = site => (
16 | $.ajax({
17 | method: "POST",
18 | url: '/api/sites',
19 | data: site,
20 | contentType: false,
21 | processData: false
22 | })
23 | );
24 |
25 | export const updateSite = site => (
26 | $.ajax({
27 | method: 'PATCH',
28 | url: `/api/sites/${site.id}`,
29 | data: { site }
30 | })
31 | );
32 |
33 | export const destroySite = id => {
34 | return ($.ajax({
35 | method: 'DELETE',
36 | url: `/api/sites/${id}`
37 | }))
38 | };
39 |
40 | export const searchListings = condition => {
41 | return(
42 | $.ajax({
43 | method: "GET",
44 | url: `/api/sites?search=${condition}`
45 | }))
46 | };
--------------------------------------------------------------------------------
/index_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/index_page.png
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/lib/tasks/.keep
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/log/.keep
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "moresmores",
3 | "private": true,
4 | "dependencies": {
5 | "@babel/core": "^7.4.4",
6 | "@babel/preset-env": "^7.4.4",
7 | "@babel/preset-react": "^7.0.0",
8 | "@fortawesome/fontawesome-svg-core": "^1.2.17",
9 | "@fortawesome/free-brands-svg-icons": "^5.8.2",
10 | "@fortawesome/free-solid-svg-icons": "^5.8.1",
11 | "@fortawesome/react-fontawesome": "^0.1.4",
12 | "babel-loader": "^8.0.5",
13 | "i": "^0.3.6",
14 | "jquery": "^3.4.0",
15 | "npm": "^6.9.0",
16 | "react": "^16.8.6",
17 | "react-dom": "^16.8.6",
18 | "react-redux": "^7.0.3",
19 | "react-router-dom": "^5.0.0",
20 | "redux": "^4.0.1",
21 | "redux-logger": "^3.0.6",
22 | "redux-thunk": "^2.3.0",
23 | "webpack": "^4.30.0",
24 | "webpack-cli": "^3.3.1"
25 | },
26 | "description": "This README would normally document whatever steps are necessary to get the application up and running.",
27 | "version": "1.0.0",
28 | "main": "index.js",
29 | "directories": {
30 | "lib": "lib",
31 | "test": "test"
32 | },
33 | "engines": {
34 | "node": "10.14.2",
35 | "npm": "6.1.0"
36 | },
37 | "scripts": {
38 | "test": "echo \"Error: no test specified\" && exit 1",
39 | "webpack": "webpack --watch --mode=development",
40 | "postinstall": "webpack"
41 | },
42 | "keywords": [],
43 | "author": "",
44 | "license": "ISC"
45 | }
46 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/search_feature.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/search_feature.gif
--------------------------------------------------------------------------------
/site_show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/site_show.png
--------------------------------------------------------------------------------
/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/storage/.keep
--------------------------------------------------------------------------------
/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/test/controllers/.keep
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/test/fixtures/.keep
--------------------------------------------------------------------------------
/test/fixtures/create_users.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | # This model initially had no columns defined. If you add columns to the
4 | # model remove the '{}' from the fixture names and add the columns immediately
5 | # below each fixture, per the syntax in the comments below
6 | #
7 | one: {}
8 | # column: value
9 | #
10 | two: {}
11 | # column: value
12 |
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/test/fixtures/sites.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: sites
4 | #
5 | # id :bigint not null, primary key
6 | # user_id :integer not null
7 | # name :string not null
8 | # capacity :integer not null
9 | # fire_allowed :boolean default(FALSE)
10 | # rv_allowed :boolean default(FALSE)
11 | # pet_allowed :boolean default(FALSE)
12 | # bike_activity :boolean default(FALSE)
13 | # hike_activity :boolean default(FALSE)
14 | # latitude :float not null
15 | # longitude :float not null
16 | # state :string not null
17 | # created_at :datetime not null
18 | # updated_at :datetime not null
19 | # description :string default(""), not null
20 | # city :string default(""), not null
21 | # price :integer default(0), not null
22 | #
23 |
24 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
25 |
26 | # This model initially had no columns defined. If you add columns to the
27 | # model remove the '{}' from the fixture names and add the columns immediately
28 | # below each fixture, per the syntax in the comments below
29 | #
30 | one: {}
31 | # column: value
32 | #
33 | two: {}
34 | # column: value
35 |
--------------------------------------------------------------------------------
/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :bigint not null, primary key
6 | # first_name :string not null
7 | # last_name :string not null
8 | # email :string not null
9 | # zip_code :integer not null
10 | # state :string not null
11 | # password_digest :string not null
12 | # session_token :string not null
13 | # created_at :datetime not null
14 | # updated_at :datetime not null
15 | #
16 |
17 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
18 |
19 | # This model initially had no columns defined. If you add columns to the
20 | # model remove the '{}' from the fixture names and add the columns immediately
21 | # below each fixture, per the syntax in the comments below
22 | #
23 | one: {}
24 | # column: value
25 | #
26 | two: {}
27 | # column: value
28 |
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/test/helpers/.keep
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/test/integration/.keep
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/test/mailers/.keep
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/test/models/.keep
--------------------------------------------------------------------------------
/test/models/create_user_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class CreateUserTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/site_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: sites
4 | #
5 | # id :bigint not null, primary key
6 | # user_id :integer not null
7 | # name :string not null
8 | # capacity :integer not null
9 | # fire_allowed :boolean default(FALSE)
10 | # rv_allowed :boolean default(FALSE)
11 | # pet_allowed :boolean default(FALSE)
12 | # bike_activity :boolean default(FALSE)
13 | # hike_activity :boolean default(FALSE)
14 | # latitude :float not null
15 | # longitude :float not null
16 | # state :string not null
17 | # created_at :datetime not null
18 | # updated_at :datetime not null
19 | # description :string default(""), not null
20 | # city :string default(""), not null
21 | # price :integer default(0), not null
22 | #
23 |
24 | require 'test_helper'
25 |
26 | class SiteTest < ActiveSupport::TestCase
27 | # test "the truth" do
28 | # assert true
29 | # end
30 | end
31 |
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :bigint not null, primary key
6 | # first_name :string not null
7 | # last_name :string not null
8 | # email :string not null
9 | # zip_code :integer not null
10 | # state :string not null
11 | # password_digest :string not null
12 | # session_token :string not null
13 | # created_at :datetime not null
14 | # updated_at :datetime not null
15 | #
16 |
17 | require 'test_helper'
18 |
19 | class UserTest < ActiveSupport::TestCase
20 | # test "the truth" do
21 | # assert true
22 | # end
23 | end
24 |
--------------------------------------------------------------------------------
/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/test/system/.keep
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require_relative '../config/environment'
3 | require 'rails/test_help'
4 |
5 | class ActiveSupport::TestCase
6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
7 | fixtures :all
8 |
9 | # Add more helper methods to be used by all tests here...
10 | end
11 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/tmp/.keep
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jkern-dev/MoreSmores/0ccf41ae9f79717c6594b40917b7c4a9ce951e72/vendor/.keep
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: './frontend/hip_camp.jsx',
6 | output: {
7 | path: path.join(__dirname, 'app', 'assets', 'javascripts'),
8 | filename: 'bundle.js'
9 | },
10 | resolve: {
11 | extensions: ['.js', '.jsx', "*"]
12 | },
13 | devtool: 'inline-source-map',
14 | module: {
15 | rules: [
16 | {
17 | test: /\.jsx?$/,
18 | exclude: /(node_modules|bower_components)/,
19 | loader: 'babel-loader',
20 | query: {
21 | presets: ['@babel/env', '@babel/react']
22 | }
23 | },
24 | {
25 | test: /\.node$/,
26 | loader: 'node-loader'
27 | }
28 | ]
29 | }
30 | };
31 |
--------------------------------------------------------------------------------