├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── public
├── favicon.ico
├── apple-touch-icon.png
├── apple-touch-icon-precomposed.png
├── robots.txt
├── 500.html
├── 422.html
└── 404.html
├── test
├── helpers
│ └── .keep
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── comment_test.rb
│ ├── relationship_test.rb
│ ├── user_test.rb
│ └── path_test.rb
├── controllers
│ ├── .keep
│ ├── api
│ │ ├── paths_controller_test.rb
│ │ ├── users_controller_test.rb
│ │ ├── comments_controller_test.rb
│ │ ├── sessions_controller_test.rb
│ │ └── relationships_controller_test.rb
│ └── static_pages_controller_test.rb
├── fixtures
│ ├── .keep
│ ├── files
│ │ └── .keep
│ ├── comments.yml
│ ├── relationships.yml
│ ├── users.yml
│ └── paths.yml
├── integration
│ └── .keep
└── test_helper.rb
├── app
├── assets
│ ├── images
│ │ ├── .keep
│ │ ├── favicon.ico
│ │ ├── global_bg.png
│ │ ├── undoclear.png
│ │ ├── stop_marker.png
│ │ ├── default_avatar.png
│ │ ├── friends_page.png
│ │ ├── friends_search.png
│ │ ├── frontpage-min.jpg
│ │ ├── map_creation.png
│ │ ├── middle_marker.png
│ │ ├── path_show_page.png
│ │ ├── sample_avatar.jpg
│ │ └── start_marker.png
│ ├── javascripts
│ │ ├── channels
│ │ │ └── .keep
│ │ ├── api
│ │ │ ├── paths.coffee
│ │ │ ├── users.coffee
│ │ │ ├── comments.coffee
│ │ │ ├── sessions.coffee
│ │ │ └── relationships.coffee
│ │ ├── static_pages.coffee
│ │ ├── cable.js
│ │ └── application.js
│ ├── config
│ │ └── manifest.js
│ └── stylesheets
│ │ ├── homepage
│ │ ├── home.scss
│ │ ├── profile_box.scss
│ │ ├── profile_tabs.scss
│ │ ├── paths
│ │ │ ├── map.scss
│ │ │ ├── paths_index.scss
│ │ │ ├── path_update_form.scss
│ │ │ └── path_form.scss
│ │ ├── activity
│ │ │ └── activity.scss
│ │ └── dashboard
│ │ │ └── dashboard.scss
│ │ ├── api
│ │ ├── paths.scss
│ │ ├── users.scss
│ │ ├── comments.scss
│ │ ├── sessions.scss
│ │ └── relationships.scss
│ │ ├── static_pages.scss
│ │ ├── loading.scss
│ │ ├── reset.scss
│ │ ├── application.css
│ │ ├── friends
│ │ ├── friends_tab.scss
│ │ ├── my_friends.scss
│ │ └── friend_search.scss
│ │ ├── session_form
│ │ └── session_form.scss
│ │ ├── masthead
│ │ └── masthead.scss
│ │ └── frontpage
│ │ └── frontpage.scss
├── models
│ ├── concerns
│ │ └── .keep
│ ├── application_record.rb
│ ├── comment.rb
│ ├── relationship.rb
│ ├── path.rb
│ └── user.rb
├── controllers
│ ├── concerns
│ │ └── .keep
│ ├── static_pages_controller.rb
│ ├── api
│ │ ├── sessions_controller.rb
│ │ ├── comments_controller.rb
│ │ ├── users_controller.rb
│ │ ├── paths_controller.rb
│ │ └── relationships_controller.rb
│ └── application_controller.rb
├── views
│ ├── layouts
│ │ ├── mailer.text.erb
│ │ ├── mailer.html.erb
│ │ └── application.html.erb
│ ├── api
│ │ ├── paths
│ │ │ ├── show.json.jbuilder
│ │ │ ├── index.json.jbuilder
│ │ │ └── _path.json.jbuilder
│ │ ├── users
│ │ │ ├── show.json.jbuilder
│ │ │ ├── _user.json.jbuilder
│ │ │ └── index.json.jbuilder
│ │ ├── comments
│ │ │ ├── show.json.jbuilder
│ │ │ ├── index.json.jbuilder
│ │ │ └── _comment.json.jbuilder
│ │ └── relationships
│ │ │ └── index.json.jbuilder
│ └── static_pages
│ │ └── root.html.erb
├── helpers
│ ├── api
│ │ ├── paths_helper.rb
│ │ ├── users_helper.rb
│ │ ├── comments_helper.rb
│ │ ├── sessions_helper.rb
│ │ └── relationships_helper.rb
│ ├── application_helper.rb
│ └── static_pages_helper.rb
├── jobs
│ └── application_job.rb
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
└── mailers
│ └── application_mailer.rb
├── vendor
└── assets
│ ├── javascripts
│ └── .keep
│ └── stylesheets
│ └── .keep
├── docs
├── wireframes
│ ├── login.png
│ ├── signup.png
│ ├── homepage.png
│ ├── dashboard.png
│ ├── frontpage.png
│ ├── activity_feed.png
│ ├── create_routes.png
│ ├── find_friends.png
│ └── friends_page.png
├── api-endpoints.md
├── sample-state.md
├── component-hierarchy.md
├── README.MD
└── schema.md
├── bin
├── bundle
├── rake
├── rails
├── spring
├── update
└── setup
├── config
├── boot.rb
├── environment.rb
├── cable.yml
├── spring.rb
├── initializers
│ ├── session_store.rb
│ ├── mime_types.rb
│ ├── application_controller_renderer.rb
│ ├── filter_parameter_logging.rb
│ ├── cookies_serializer.rb
│ ├── backtrace_silencers.rb
│ ├── assets.rb
│ ├── wrap_parameters.rb
│ ├── inflections.rb
│ └── new_framework_defaults.rb
├── application.rb
├── routes.rb
├── locales
│ └── en.yml
├── secrets.yml
├── environments
│ ├── test.rb
│ ├── development.rb
│ └── production.rb
├── puma.rb
└── database.yml
├── config.ru
├── db
├── migrate
│ ├── 20170428052157_add_default_to_paths_table.rb
│ ├── 20170424015713_add_description_column_to_paths.rb
│ ├── 20170428052457_add_description_default_to_paths_table.rb
│ ├── 20170423044543_add_default_value_to_duration_to_paths_table.rb
│ ├── 20170425032643_add_default_img_url_to_users.rb
│ ├── 20170418174913_add_index_to_users.rb
│ ├── 20170425033644_create_comments.rb
│ ├── 20170418173523_create_users.rb
│ ├── 20170427175516_create_relationships.rb
│ └── 20170423005705_create_paths.rb
└── schema.rb
├── Rakefile
├── frontend
├── actions
│ ├── error_actions.js
│ ├── session_actions.js
│ ├── comment_actions.js
│ ├── path_actions.js
│ └── friend_actions.js
├── store
│ └── store.js
├── components
│ ├── app.jsx
│ ├── home
│ │ ├── paths
│ │ │ ├── paths_main.jsx
│ │ │ ├── paths_form
│ │ │ │ ├── path_form_container.js
│ │ │ │ ├── map_manager.js
│ │ │ │ └── map.jsx
│ │ │ ├── paths_index
│ │ │ │ ├── path_index_container.js
│ │ │ │ ├── path_index_item.jsx
│ │ │ │ └── path_index.jsx
│ │ │ └── path_show
│ │ │ │ ├── comment_index_item.jsx
│ │ │ │ ├── path_show_container.js
│ │ │ │ ├── comments.jsx
│ │ │ │ └── path_update_form.jsx
│ │ ├── profile_container.jsx
│ │ ├── dashboard
│ │ │ ├── dashboard_container.js
│ │ │ ├── pending_paths_index_item.jsx
│ │ │ ├── complete_paths_index_item.jsx
│ │ │ └── dashboard.jsx
│ │ ├── activity_feed
│ │ │ ├── activity_container.js
│ │ │ ├── activity.jsx
│ │ │ └── activity_index_item.jsx
│ │ └── home.jsx
│ ├── masthead
│ │ ├── masthead_container.js
│ │ └── masthead.jsx
│ ├── friends
│ │ ├── all_friends.jsx
│ │ ├── friend_search_container.js
│ │ ├── friends_index.jsx
│ │ ├── all_friends_container.js
│ │ ├── friends_tab.jsx
│ │ ├── friend_requests_index.jsx
│ │ └── friend_search.jsx
│ ├── session_form
│ │ └── session_form_container.js
│ ├── frontpage
│ │ └── frontpage.jsx
│ └── root.jsx
├── util
│ ├── session_api_util.js
│ ├── comments_api_util.js
│ ├── selector.js
│ ├── paths_api_util.js
│ ├── math_calculations.js
│ └── friends_api_util.js
├── reducers
│ ├── error_reducer.js
│ ├── session_reducer.js
│ ├── friend_requests_reducer.js
│ ├── friend_search_reducer.js
│ ├── friends_reducer.js
│ ├── root_reducer.js
│ ├── comments_reducer.js
│ └── paths_reducer.js
└── map_my_path.jsx
├── .gitignore
├── webpack.config.js
├── package.json
└── Gemfile
/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/helpers/api/paths_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::PathsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/users_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::UsersHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/comments_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::CommentsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/sessions_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::SessionsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/static_pages_helper.rb:
--------------------------------------------------------------------------------
1 | module StaticPagesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/relationships_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::RelationshipsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/views/api/paths/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'api/paths/path', path: @path
2 |
--------------------------------------------------------------------------------
/app/views/api/users/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'api/users/user', user: @user
2 |
--------------------------------------------------------------------------------
/app/views/api/comments/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'api/comments/comment', comment: @comment
2 |
--------------------------------------------------------------------------------
/docs/wireframes/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/docs/wireframes/login.png
--------------------------------------------------------------------------------
/docs/wireframes/signup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/docs/wireframes/signup.png
--------------------------------------------------------------------------------
/docs/wireframes/homepage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/docs/wireframes/homepage.png
--------------------------------------------------------------------------------
/app/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/favicon.ico
--------------------------------------------------------------------------------
/app/assets/images/global_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/global_bg.png
--------------------------------------------------------------------------------
/app/assets/images/undoclear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/undoclear.png
--------------------------------------------------------------------------------
/docs/wireframes/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/docs/wireframes/dashboard.png
--------------------------------------------------------------------------------
/docs/wireframes/frontpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/docs/wireframes/frontpage.png
--------------------------------------------------------------------------------
/app/assets/images/stop_marker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/stop_marker.png
--------------------------------------------------------------------------------
/docs/wireframes/activity_feed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/docs/wireframes/activity_feed.png
--------------------------------------------------------------------------------
/docs/wireframes/create_routes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/docs/wireframes/create_routes.png
--------------------------------------------------------------------------------
/docs/wireframes/find_friends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/docs/wireframes/find_friends.png
--------------------------------------------------------------------------------
/docs/wireframes/friends_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/docs/wireframes/friends_page.png
--------------------------------------------------------------------------------
/app/assets/images/default_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/default_avatar.png
--------------------------------------------------------------------------------
/app/assets/images/friends_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/friends_page.png
--------------------------------------------------------------------------------
/app/assets/images/friends_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/friends_search.png
--------------------------------------------------------------------------------
/app/assets/images/frontpage-min.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/frontpage-min.jpg
--------------------------------------------------------------------------------
/app/assets/images/map_creation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/map_creation.png
--------------------------------------------------------------------------------
/app/assets/images/middle_marker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/middle_marker.png
--------------------------------------------------------------------------------
/app/assets/images/path_show_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/path_show_page.png
--------------------------------------------------------------------------------
/app/assets/images/sample_avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/sample_avatar.jpg
--------------------------------------------------------------------------------
/app/assets/images/start_marker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulow/MapMyPath/HEAD/app/assets/images/start_marker.png
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/app/controllers/static_pages_controller.rb:
--------------------------------------------------------------------------------
1 | class StaticPagesController < ApplicationController
2 | def root
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/app/views/api/paths/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @paths.each do |path|
2 | json.set! path.id do
3 | json.partial! 'api/paths/path', path: path
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/app/views/api/relationships/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @friends.each do |friend|
2 | json.set! friend.id do
3 | json.partial! 'api/users/user', user: friend
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 |
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | config/application.yml
7 | ).each { |path| Spring.watch(path) }
8 |
--------------------------------------------------------------------------------
/app/views/api/comments/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @comments.each do |comment|
2 | json.set! comment.id do
3 | json.partial! 'api/comments/comment', comment: comment
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_MapMyPath_session'
4 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/db/migrate/20170428052157_add_default_to_paths_table.rb:
--------------------------------------------------------------------------------
1 | class AddDefaultToPathsTable < ActiveRecord::Migration[5.0]
2 | def change
3 | change_column_default :paths, :done_date, ''
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20170424015713_add_description_column_to_paths.rb:
--------------------------------------------------------------------------------
1 | class AddDescriptionColumnToPaths < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :paths, :description, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage/home.scss:
--------------------------------------------------------------------------------
1 | .body {
2 | background: image-url("global_bg.png");
3 | }
4 | .main-content-container {
5 | margin: 0 auto;
6 | max-width: 940px;
7 | padding-bottom: 100px;
8 | }
9 |
--------------------------------------------------------------------------------
/test/controllers/api/paths_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::PathsControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/users_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::UsersControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/comments_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::CommentsControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/sessions_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::SessionsControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/static_pages_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class StaticPagesControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/paths.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/Paths controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/users.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/Users controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/db/migrate/20170428052457_add_description_default_to_paths_table.rb:
--------------------------------------------------------------------------------
1 | class AddDescriptionDefaultToPathsTable < ActiveRecord::Migration[5.0]
2 | def change
3 | change_column_default :paths, :description, ''
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/test/controllers/api/relationships_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::RelationshipsControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/comments.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/comments controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/sessions.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/Sessions controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/static_pages.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the StaticPages controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/views/api/users/_user.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! user, :id, :first_name, :last_name, :img_url, :created_at
2 |
3 | # json.paths user.paths do |path|
4 | # json.extract! path, :name, :polyline, :distance, :created_at
5 | # end
6 |
--------------------------------------------------------------------------------
/db/migrate/20170423044543_add_default_value_to_duration_to_paths_table.rb:
--------------------------------------------------------------------------------
1 | class AddDefaultValueToDurationToPathsTable < ActiveRecord::Migration[5.0]
2 | def change
3 | change_column_default :paths, :duration, 0
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ApplicationController.renderer.defaults.merge!(
4 | # http_host: 'example.org',
5 | # https: false
6 | # )
7 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/relationships.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/relationships controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/paths.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/users.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/comments.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/sessions.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/static_pages.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/app/assets/javascripts/api/relationships.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/db/migrate/20170425032643_add_default_img_url_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddDefaultImgUrlToUsers < ActiveRecord::Migration[5.0]
2 | def change
3 | change_column_default :users, :img_url, 'https://s3.us-east-2.amazonaws.com/mapmyrun-dev/default_avatar.png'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/api/comments/_comment.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! comment, :id, :author_id, :path_id, :body, :created_at
2 |
3 | json.set! :author do
4 | json.id comment.author.id
5 | json.name "#{comment.author.first_name} #{comment.author.last_name}"
6 | json.img_url comment.author.img_url
7 | end
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/actions/error_actions.js:
--------------------------------------------------------------------------------
1 | export const CLEAR_ERRORS = "CLEAR_ERRORS";
2 | export const ADD_ERRORS = "ADD_ERRORS";
3 |
4 | export const clearErrors = () => ({
5 | type: CLEAR_ERRORS
6 | });
7 |
8 | export const addErrors = (errors) => ({
9 | type: ADD_ERRORS,
10 | errors
11 | });
12 |
--------------------------------------------------------------------------------
/db/migrate/20170418174913_add_index_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddIndexToUsers < ActiveRecord::Migration[5.0]
2 | def change
3 | add_index :users, :email, unique: true
4 | add_index :users, :session_token, unique: true
5 | add_index :users, :first_name
6 | add_index :users, :last_name
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/db/migrate/20170425033644_create_comments.rb:
--------------------------------------------------------------------------------
1 | class CreateComments < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :comments do |t|
4 | t.integer :author_id, null: false
5 | t.integer :path_id, null: false
6 | t.text :body, null: false
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/loading.scss:
--------------------------------------------------------------------------------
1 | #loading {
2 | border: 16px solid #f3f3f3;
3 | border-top: 16px solid #3498db;
4 | border-radius: 50%;
5 | width: 65px;
6 | height: 65px;
7 | animation: spin 1.5s linear infinite;
8 | }
9 |
10 | @keyframes spin {
11 | 0% { transform: rotate(0deg); }
12 | 100% { transform: rotate(360deg); }
13 | }
14 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require File.expand_path('../../config/environment', __FILE__)
3 | require 'rails/test_help'
4 |
5 | class ActiveSupport::TestCase
6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
7 | fixtures :all
8 |
9 | # Add more helper methods to be used by all tests here...
10 | end
11 |
--------------------------------------------------------------------------------
/frontend/store/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import logger from 'redux-logger';
4 | import rootReducer from '../reducers/root_reducer';
5 |
6 | const configureStore = (preloadedState = {}) => (
7 | createStore(
8 | rootReducer,
9 | preloadedState,
10 | applyMiddleware(thunk)
11 | )
12 | );
13 |
14 | export default configureStore;
15 | // logger
16 |
--------------------------------------------------------------------------------
/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the rails generate channel command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/frontend/components/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MastheadContainer from './masthead/masthead_container';
3 | import { IntlProvider } from 'react-intl';
4 |
5 | const App = ({ children, location }) => {
6 | return (
7 |
8 |
9 |
10 | {children}
11 |
12 |
13 | );
14 | };
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/db/migrate/20170418173523_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :users do |t|
4 | t.string :email, null: false
5 | t.string :first_name, null: false
6 | t.string :last_name, null: false
7 | t.string :password_digest, null: false
8 | t.string :session_token, null: false
9 | t.string :img_url
10 |
11 | t.timestamps
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/frontend/util/session_api_util.js:
--------------------------------------------------------------------------------
1 | export const signUp = (user) => (
2 | $.ajax({
3 | method: 'POST',
4 | url: 'api/users',
5 | data: { user }
6 | })
7 | );
8 |
9 | export const login = (user) => (
10 | $.ajax({
11 | method: 'POST',
12 | url: 'api/session',
13 | data: { user }
14 | })
15 | );
16 |
17 | export const signOut = () => (
18 | $.ajax({
19 | method: 'DELETE',
20 | url: 'api/session'
21 | })
22 | );
23 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| 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 |
--------------------------------------------------------------------------------
/app/views/static_pages/root.html.erb:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/db/migrate/20170427175516_create_relationships.rb:
--------------------------------------------------------------------------------
1 | class CreateRelationships < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :relationships do |t|
4 | t.integer :user_one_id, null: false
5 | t.integer :user_two_id, null: false
6 | t.integer :status, null: false
7 | t.integer :action_user_id, null: false
8 |
9 | t.timestamps
10 | end
11 |
12 | add_index :relationships, [:user_one_id, :user_two_id]
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/frontend/reducers/error_reducer.js:
--------------------------------------------------------------------------------
1 | import { CLEAR_ERRORS, ADD_ERRORS } from '../actions/error_actions';
2 |
3 | const defaultErrors = {};
4 |
5 | const ErrorReducer = (state = defaultErrors, action) => {
6 | Object.freeze(state);
7 | switch(action.type) {
8 | case ADD_ERRORS:
9 | return Object.assign({}, action.errors.responseJSON);
10 | case CLEAR_ERRORS:
11 | return {};
12 | default:
13 | return state;
14 | }
15 | };
16 |
17 | export default ErrorReducer;
18 |
--------------------------------------------------------------------------------
/frontend/reducers/session_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_CURRENT_USER } from '../actions/session_actions.js';
2 |
3 | const _nullUser = {
4 | currentUser: null
5 | };
6 |
7 | const SessionReducer = (state = _nullUser, action) => {
8 | Object.freeze(state);
9 | switch(action.type) {
10 | case RECEIVE_CURRENT_USER:
11 | const currentUser = action.currentUser;
12 | return Object.assign({}, { currentUser });
13 | default:
14 | return state;
15 | }
16 | };
17 |
18 | export default SessionReducer;
19 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/paths_main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PathFormContainer from './paths_form/path_form_container';
3 | import PathIndex from './paths_index/path_index_container';
4 |
5 | class PathsMain extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render () {
11 | return (
12 |
13 | {this.props.children}
14 |
15 | );
16 | }
17 | }
18 |
19 | //
20 | export default PathsMain;
21 |
--------------------------------------------------------------------------------
/test/models/comment_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: comments
4 | #
5 | # id :integer not null, primary key
6 | # author_id :integer not null
7 | # path_id :integer not null
8 | # body :text not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 |
13 | require 'test_helper'
14 |
15 | class CommentTest < ActiveSupport::TestCase
16 | # test "the truth" do
17 | # assert true
18 | # end
19 | end
20 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile += %w( search.js )
12 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/paths_form/path_form_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createPath } from '../../../../actions/path_actions';
3 |
4 | import PathForm from './path_form';
5 |
6 | const mapStateToProps = ({ errors }) => {
7 | return ({
8 | errors
9 | });
10 | };
11 |
12 | const mapDispatchToProps = (dispatch) => {
13 | return {
14 | createPath: path => dispatch(createPath(path))
15 | };
16 | };
17 |
18 | export default connect(
19 | mapStateToProps,
20 | mapDispatchToProps
21 | )(PathForm);
22 |
--------------------------------------------------------------------------------
/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 MapMyPath
10 | class Application < Rails::Application
11 | # Settings in config/environments/* take precedence over those specified here.
12 | # Application configuration should go into files in config/initializers
13 | # -- all .rb files in that directory are automatically loaded.
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/views/api/paths/_path.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! path, :id, :name, :polyline, :distance, :start_address, :end_address, :description, :duration, :done, :done_date, :created_at, :updated_at
2 |
3 | json.set! :user do
4 | json.id path.user.id
5 | json.name "#{path.user.first_name} #{path.user.last_name}"
6 | json.img_url path.user.img_url
7 | end
8 |
9 | # json.set! :comments do
10 | # json.array! path.comments do |comment|
11 | # json.id comment.id
12 | # json.author comment.author
13 | # json.body comment.body
14 | # end
15 | # end
16 |
--------------------------------------------------------------------------------
/frontend/components/masthead/masthead_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { signOut } from '../../actions/session_actions';
3 | import Masthead from './masthead';
4 |
5 | const mapStateToProps = ({ session }, { location }) => {
6 | return ({
7 | currentUser: session.currentUser,
8 | path: location.pathname
9 | });
10 | };
11 |
12 | const mapDispatchToProps = (dispatch) => ({
13 | signOut: () => dispatch(signOut())
14 | });
15 |
16 | export default connect(
17 | mapStateToProps,
18 | mapDispatchToProps
19 | )(Masthead);
20 |
--------------------------------------------------------------------------------
/frontend/util/comments_api_util.js:
--------------------------------------------------------------------------------
1 | export const getAllComments = (pathId) => {
2 | return $.ajax({
3 | method: 'GET',
4 | url: `api/paths/${pathId}/comments`
5 | });
6 | };
7 |
8 | export const createComment = (comment) => {
9 | return $.ajax({
10 | method: 'POST',
11 | url: `api/paths/${comment.path_id}/comments`,
12 | data: { comment }
13 | });
14 | };
15 |
16 | export const deleteComment = (comment) => {
17 | return $.ajax({
18 | method: 'DELETE',
19 | url: `api/paths/${comment.path_id}/comments/${comment.id}`,
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" }
12 | if spring
13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
14 | gem 'spring', spring.version
15 | require 'spring/binstub'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/db/migrate/20170423005705_create_paths.rb:
--------------------------------------------------------------------------------
1 | class CreatePaths < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :paths do |t|
4 | t.string :name, null: false
5 | t.text :polyline, null: false
6 | t.float :distance, null: false
7 | t.string :start_address, null: false
8 | t.string :end_address, null: false
9 | t.integer :duration
10 | t.boolean :done, default: false
11 | t.date :done_date
12 | t.integer :user_id, null: false
13 |
14 | t.timestamps
15 | end
16 |
17 | add_index :paths, :user_id
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/frontend/reducers/friend_requests_reducer.js:
--------------------------------------------------------------------------------
1 | import { REMOVE_REQUEST, RECEIVE_ALL_REQUESTS } from '../actions/friend_actions';
2 |
3 | const friendRequestReducer = (oldState = {}, action) => {
4 | Object.freeze(oldState);
5 | switch(action.type) {
6 | case RECEIVE_ALL_REQUESTS:
7 | return Object.assign({}, action.friends);
8 | case REMOVE_REQUEST:
9 | let newState = Object.assign({}, oldState);
10 | delete newState[action.friend.id];
11 | return newState;
12 | default:
13 | return oldState;
14 | }
15 | };
16 |
17 | export default friendRequestReducer;
18 |
--------------------------------------------------------------------------------
/app/views/api/users/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @friends.each do |friend|
2 | json.set! friend.id do
3 | json.partial! 'api/users/user', user: friend
4 |
5 | json.paths friend.paths do |path|
6 | json.extract! path, :name, :polyline, :distance, :updated_at, :id, :done
7 | json.user do
8 | json.name "#{friend.first_name} #{friend.last_name}"
9 | json.id friend.id
10 | json.img_url friend.img_url
11 | end
12 | end
13 | end
14 | end
15 |
16 | # json.array! friend.paths.each do |path|
17 | # json.extract! path, :id, :polyline, :distance, :updated_at
18 | # end
19 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
3 | root "static_pages#root"
4 |
5 | namespace :api, defaults: { format: :json } do
6 | resources :users, only: [:create, :update, :index]
7 | resource :session, only: [:create, :destroy]
8 | resources :paths, only: [:create, :update, :destroy, :index, :show] do
9 | resources :comments, only: [:index, :create, :destroy]
10 | end
11 | resources :relationships, only: [:index, :update, :create, :destroy, :show]
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/frontend/reducers/friend_search_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_ALL_POTENTIAL_FRIENDS, REMOVE_FROM_SEARCH } from '../actions/friend_actions';
2 |
3 | const friendSearchReducer = (oldState = {}, action) => {
4 | Object.freeze(oldState);
5 | switch(action.type) {
6 | case RECEIVE_ALL_POTENTIAL_FRIENDS:
7 | return Object.assign({}, action.friends);
8 | case REMOVE_FROM_SEARCH:
9 | let newState = Object.assign({}, oldState);
10 | delete newState[action.friend.id];
11 | return newState;
12 | default:
13 | return oldState;
14 | }
15 | };
16 |
17 | export default friendSearchReducer;
18 |
--------------------------------------------------------------------------------
/frontend/components/home/profile_container.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | const Profile = ({ currentUser }) => {
5 | if(currentUser) {
6 | return (
7 |
8 |

9 |
10 | Hello,
11 | {currentUser.first_name + ' ' + currentUser.last_name}
12 | Friends
13 |
14 |
15 | );
16 | } else {
17 | return ;
18 | }
19 | };
20 |
21 | export default Profile;
22 |
--------------------------------------------------------------------------------
/test/models/relationship_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: relationships
4 | #
5 | # id :integer not null, primary key
6 | # user_one_id :integer not null
7 | # user_two_id :integer not null
8 | # status :integer not null
9 | # action_user_id :integer not null
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | #
13 |
14 | require 'test_helper'
15 |
16 | class RelationshipTest < ActiveSupport::TestCase
17 | # test "the truth" do
18 | # assert true
19 | # end
20 | end
21 |
--------------------------------------------------------------------------------
/app/models/comment.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: comments
4 | #
5 | # id :integer not null, primary key
6 | # author_id :integer not null
7 | # path_id :integer not null
8 | # body :text not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 |
13 | class Comment < ApplicationRecord
14 | validates :author, :path, :body, presence: true
15 |
16 | belongs_to :author,
17 | class_name: 'User',
18 | primary_key: :id,
19 | foreign_key: :author_id
20 |
21 | belongs_to :path
22 |
23 | end
24 |
--------------------------------------------------------------------------------
/frontend/map_my_path.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 | import Modal from 'react-modal';
6 |
7 |
8 | document.addEventListener("DOMContentLoaded", () => {
9 | let store;
10 | if (window.currentUser) {
11 | const preloadedState = { session: { currentUser: window.currentUser } };
12 | store = configureStore(preloadedState);
13 | } else {
14 | store = configureStore();
15 | }
16 |
17 | Modal.setAppElement(document.body);
18 | const root = document.getElementById('root');
19 | ReactDOM.render(, root);
20 | });
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore all logfiles and tempfiles.
11 | /log/*
12 | /tmp/*
13 | !/log/.keep
14 | !/tmp/.keep
15 |
16 | # Ignore Byebug command history file.
17 | .byebug_history
18 |
19 | node_modules/
20 | bundle.js
21 | bundle.js.map
22 | .DS_Store
23 | npm-debug.log
24 |
25 | # Ignore application configuration
26 | /config/application.yml
27 |
--------------------------------------------------------------------------------
/frontend/util/selector.js:
--------------------------------------------------------------------------------
1 | export const completePaths = ( paths ) => {
2 | let completedPaths = [];
3 | for (const pathId in paths) {
4 | if(paths[pathId].done === true) {
5 | completedPaths.push(paths[pathId]);
6 | }
7 | }
8 | return completedPaths.sort(function(a, b) { return new Date(b.updated_at) - new Date(a.updated_at); });
9 | };
10 |
11 | export const pendingPaths = ( paths ) => {
12 | let incompletedPaths = [];
13 | for (const pathId in paths) {
14 | if(paths[pathId].done === false) {
15 | incompletedPaths.push(paths[pathId]);
16 | }
17 | }
18 | return incompletedPaths.sort(function(a, b) { return new Date(b.updated_at) - new Date(a.updated_at); });
19 | };
20 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/reset.scss:
--------------------------------------------------------------------------------
1 | html, body, header, nav, h1, h2, h3, h4, h5, a,
2 | ul, li, strong, main, button,
3 | section, img, div, p, form,
4 | fieldset, label, input, textarea,
5 | span, article, footer, time, small, dl, dt, dd {
6 | margin: 0;
7 | padding: 0;
8 | border: 0;
9 | outline: 0;
10 | font-family: 'Roboto','Open Sans', sans-serif;
11 | color: inherit;
12 | text-align: inherit;
13 | text-decoration: inherit;
14 | vertical-align: inherit;
15 | box-sizing: inherit;
16 | background: transparent;
17 | -webkit-font-smoothing: antialiased;
18 | list-style: none;
19 | }
20 |
21 | input:focus,
22 | select:focus,
23 | textarea:focus,
24 | button:focus {
25 | outline: none;
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/paths_index/path_index_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import PathIndex from './path_index';
3 |
4 | import { requestAllPaths, deletePath } from '../../../../actions/path_actions';
5 |
6 | const mapStateToProps = ({ paths }) => {
7 | return {
8 | paths: Object.keys(paths).map(id => paths[id])
9 | .sort(function(a, b) { return b.id - a.id; })
10 | };
11 | };
12 |
13 | const mapDispatchToProps = (dispatch) => {
14 | return {
15 | requestAllPaths: () => dispatch(requestAllPaths('index')),
16 | deletePath: (id) => dispatch(deletePath(id))
17 | };
18 | };
19 |
20 | export default connect(
21 | mapStateToProps,
22 | mapDispatchToProps
23 | )(PathIndex);
24 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/app/models/relationship.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: relationships
4 | #
5 | # id :integer not null, primary key
6 | # user_one_id :integer not null
7 | # user_two_id :integer not null
8 | # status :integer not null
9 | # action_user_id :integer not null
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | #
13 |
14 | class Relationship < ApplicationRecord
15 | validates :user_one_id, :user_two_id, :status, :action_user_id, presence: true
16 | validates :user_one_id, uniqueness: { scope: :user_two_id }
17 |
18 | # status: 0 pending friend request
19 | # status: 1 confirmed friend
20 |
21 |
22 | end
23 |
--------------------------------------------------------------------------------
/frontend/reducers/friends_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_ALL_FRIENDS, REMOVE_FRIEND, RECEIVE_NEW_FRIEND } from '../actions/friend_actions';
2 |
3 |
4 | const FriendsReducer = (oldState = {}, action) => {
5 | Object.freeze(oldState);
6 | switch(action.type) {
7 | case RECEIVE_ALL_FRIENDS:
8 | return Object.assign({}, action.friends);
9 | case RECEIVE_NEW_FRIEND:
10 | return Object.assign(
11 | {},
12 | oldState,
13 | { [action.friend.id]: action.friend}
14 | );
15 | case REMOVE_FRIEND:
16 | let newState = Object.assign({}, oldState);
17 | delete newState[action.id];
18 | return newState;
19 | default:
20 | return oldState;
21 | }
22 | };
23 |
24 | export default FriendsReducer;
25 |
--------------------------------------------------------------------------------
/app/controllers/api/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::SessionsController < ApplicationController
2 |
3 | def create
4 | @user = User.find_by_credentials(
5 | params[:user][:email],
6 | params[:user][:password]
7 | )
8 | if @user
9 | log_in(@user)
10 | render 'api/users/show'
11 | else
12 | render(
13 | json: { login: ["Incorrect email or password. Please try again"] },
14 | status: 401
15 | )
16 | end
17 | end
18 |
19 | def destroy
20 | @user = current_user
21 |
22 | if @user
23 | sign_out
24 | render 'api/users/show'
25 | else
26 | render(
27 | json: { signout: ["No user logged in"] },
28 | status: 404
29 | )
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :integer not null, primary key
6 | # email :string not null
7 | # first_name :string not null
8 | # last_name :string not null
9 | # password_digest :string not null
10 | # session_token :string not null
11 | # img_url :string default("https://s3.us-east-2.amazonaws.com/mapmyrun-dev/default_avatar.png")
12 | # created_at :datetime not null
13 | # updated_at :datetime not null
14 | #
15 |
16 | require 'test_helper'
17 |
18 | class UserTest < ActiveSupport::TestCase
19 | # test "the truth" do
20 | # assert true
21 | # end
22 | end
23 |
--------------------------------------------------------------------------------
/test/fixtures/comments.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: comments
4 | #
5 | # id :integer not null, primary key
6 | # author_id :integer not null
7 | # path_id :integer not null
8 | # body :text not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 |
13 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
14 |
15 | # This model initially had no columns defined. If you add columns to the
16 | # model remove the '{}' from the fixture names and add the columns immediately
17 | # below each fixture, per the syntax in the comments below
18 | #
19 | one: {}
20 | # column: value
21 | #
22 | two: {}
23 | # column: value
24 |
--------------------------------------------------------------------------------
/frontend/reducers/root_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import SessionReducer from './session_reducer';
4 | import ErrorReducer from './error_reducer';
5 | import PathsReducer from './paths_reducer';
6 | import CommentsReducer from './comments_reducer';
7 | import FriendsReducer from './friends_reducer';
8 | import FriendRequestReducer from './friend_requests_reducer';
9 | import FriendSearchReducer from './friend_search_reducer.js';
10 |
11 | const rootReducer = combineReducers({
12 | session: SessionReducer,
13 | errors: ErrorReducer,
14 | paths: PathsReducer,
15 | comments: CommentsReducer,
16 | friends: FriendsReducer,
17 | friendRequests: FriendRequestReducer,
18 | friendSearches: FriendSearchReducer
19 | });
20 |
21 | export default rootReducer;
22 |
--------------------------------------------------------------------------------
/frontend/reducers/comments_reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_ALL_COMMENTS,
3 | RECEIVE_NEW_COMMENT,
4 | REMOVE_COMMENT
5 | } from '../actions/comment_actions';
6 |
7 |
8 | const CommentsReducer = (oldState = {}, action) => {
9 | Object.freeze(oldState);
10 | switch(action.type) {
11 | case RECEIVE_ALL_COMMENTS:
12 | return Object.assign({}, action.comments);
13 | case RECEIVE_NEW_COMMENT:
14 | return Object.assign(
15 | {},
16 | oldState,
17 | { [action.comment.id]: action.comment }
18 | );
19 | case REMOVE_COMMENT:
20 | let newState = Object.assign({}, oldState);
21 | delete newState[action.id];
22 | return newState;
23 | default:
24 | return oldState;
25 | }
26 | };
27 |
28 | export default CommentsReducer;
29 |
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require_tree .
16 |
--------------------------------------------------------------------------------
/app/controllers/api/comments_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::CommentsController < ApplicationController
2 | before_action :require_logged_in!
3 |
4 | def index
5 | # double check
6 | @comments = Comment.where('path_id = ?', params[:path_id]).includes(:author)
7 | end
8 |
9 | def create
10 | @comment = current_user.comments.new(comment_params)
11 | @comment.author_id = current_user.id
12 |
13 | if @comment.save
14 | render :show
15 | else
16 | render json: @comment.errors.messages, status: 422
17 | end
18 | end
19 |
20 | def destroy
21 | @comment = Comment.find(params[:id])
22 | @comment.destroy
23 | render json: @comment
24 | end
25 |
26 | private
27 |
28 | def comment_params
29 | params.require(:comment).permit(:body, :path_id)
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery with: :exception
3 |
4 | helper_method :current_user, :logged_in?
5 |
6 | private
7 |
8 | def current_user
9 | return nil unless session[:session_token]
10 | @current_user ||= User.find_by_session_token(session[:session_token])
11 | end
12 |
13 | def logged_in?
14 | !!current_user
15 | end
16 |
17 | def log_in(user)
18 | @current_user = user
19 | session[:session_token] = user.reset_session_token!
20 | end
21 |
22 | def sign_out
23 | current_user.try(:reset_session_token!)
24 | session[:session_token] = nil
25 | end
26 |
27 | def require_logged_in!
28 | render json: { base: ['invalid credentials'] }, status: 401 if !current_user
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/frontend/components/home/dashboard/dashboard_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import Dashboard from './dashboard';
3 |
4 | import { requestAllPaths, clearPaths } from '../../../actions/path_actions';
5 |
6 | import { completePaths, pendingPaths } from '../../../util/selector.js';
7 |
8 | const mapStateToProps = ({ paths, session }) => {
9 | return {
10 | completePaths: completePaths(paths),
11 | pendingPaths: pendingPaths(paths),
12 | currentUser: session.currentUser
13 | };
14 | };
15 |
16 | const mapDispatchToProps = (dispatch) => {
17 | return {
18 | requestAllPaths: () => dispatch(requestAllPaths('index')),
19 | clearPaths: () => dispatch(clearPaths())
20 | };
21 | };
22 |
23 | export default connect(
24 | mapStateToProps,
25 | mapDispatchToProps
26 | )(Dashboard);
27 |
--------------------------------------------------------------------------------
/test/models/path_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: paths
4 | #
5 | # id :integer not null, primary key
6 | # name :string not null
7 | # polyline :text not null
8 | # distance :float not null
9 | # start_address :string not null
10 | # end_address :string not null
11 | # duration :integer default("0")
12 | # done :boolean default("false")
13 | # done_date :date
14 | # user_id :integer not null
15 | # created_at :datetime not null
16 | # updated_at :datetime not null
17 | # description :text
18 | #
19 |
20 | require 'test_helper'
21 |
22 | class PathTest < ActiveSupport::TestCase
23 | # test "the truth" do
24 | # assert true
25 | # end
26 | end
27 |
--------------------------------------------------------------------------------
/test/fixtures/relationships.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: relationships
4 | #
5 | # id :integer not null, primary key
6 | # user_one_id :integer not null
7 | # user_two_id :integer not null
8 | # status :integer not null
9 | # action_user_id :integer not null
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | #
13 |
14 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
15 |
16 | # This model initially had no columns defined. If you add columns to the
17 | # model remove the '{}' from the fixture names and add the columns immediately
18 | # below each fixture, per the syntax in the comments below
19 | #
20 | one: {}
21 | # column: value
22 | #
23 | two: {}
24 | # column: value
25 |
--------------------------------------------------------------------------------
/docs/api-endpoints.md:
--------------------------------------------------------------------------------
1 | # API endpoints
2 |
3 | ## HTML API
4 |
5 | ### RoutesContainer- `GET /` - loads React web app
6 |
7 | ## JSON API
8 |
9 | ### Users
10 |
11 | - `POST /api/users`
12 | - `PATCH /api/users`
13 |
14 | ### Session
15 |
16 | - `POST /api/session`
17 | - `DELETE /api/session`
18 |
19 | ### Routes
20 |
21 | - `GET /api/routes`
22 | - `POST /api/routes`
23 | - `GET /api/routes/:id`
24 | - `PATCH /api/routes/:id`
25 | - `DELETE /api/routes/:id`
26 |
27 | ### Friends
28 |
29 | - `GET /api/friends`
30 | - `DELETE /api/friends/:id`
31 |
32 | ### Friend Requests
33 |
34 | - `POST /api/friend_requests`
35 | - `DELETE /api/friend_requests/:id`
36 |
37 | ### Status
38 |
39 | - `GET /api/status`
40 | - `POST /api/status`
41 | - `DELETE /api/status/:id`
42 |
43 | ### Comments
44 |
45 | - `GET /api/comments`
46 | - `POST /api/comments`
47 | - `DELETE /api/comments/:id`
48 |
--------------------------------------------------------------------------------
/frontend/components/friends/all_friends.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FriendsIndex from './friends_index';
3 | import FriendRequestsIndex from './friend_requests_index';
4 |
5 | class AllFriends extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | componentDidMount() {
11 | this.props.requestAllRequests();
12 | this.props.requestAllFriends();
13 | }
14 |
15 | render () {
16 | return (
17 |
18 |
21 |
25 |
26 | );
27 | }
28 | }
29 |
30 | export default AllFriends;
31 |
--------------------------------------------------------------------------------
/frontend/components/friends/friend_search_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import FriendSearch from './friend_search';
3 |
4 | import {
5 | addRequest,
6 | requestAllPotentialFriends
7 | } from '../../actions/friend_actions';
8 |
9 | const mapStateToProps = ({ session, friendSearches }) => {
10 | return {
11 | currentUser: session.currentUser,
12 | potentialFriends: Object.keys(friendSearches).map(id => friendSearches[id])
13 | .sort(function(a, b) { return a.first_name - b.first_name; }),
14 | };
15 | };
16 |
17 | const mapDispatchToProps = (dispatch) => {
18 | return {
19 | addRequest: (id) => dispatch(addRequest(id)),
20 | requestAllPotentialFriends: (search) =>
21 | dispatch(requestAllPotentialFriends(search))
22 | };
23 | };
24 |
25 | export default connect(
26 | mapStateToProps,
27 | mapDispatchToProps
28 | )(FriendSearch);
29 |
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a way to update your development environment automatically.
15 | # Add necessary update steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | puts "\n== Updating database =="
22 | system! 'bin/rails db:migrate'
23 |
24 | puts "\n== Removing old logs and tempfiles =="
25 | system! 'bin/rails log:clear tmp:clear'
26 |
27 | puts "\n== Restarting application server =="
28 | system! 'bin/rails restart'
29 | end
30 |
--------------------------------------------------------------------------------
/app/models/path.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: paths
4 | #
5 | # id :integer not null, primary key
6 | # name :string not null
7 | # polyline :text not null
8 | # distance :float not null
9 | # start_address :string not null
10 | # end_address :string not null
11 | # duration :integer default("0")
12 | # done :boolean default("false")
13 | # done_date :date
14 | # user_id :integer not null
15 | # created_at :datetime not null
16 | # updated_at :datetime not null
17 | # description :text
18 | #
19 |
20 | class Path < ApplicationRecord
21 | validates :name, :polyline, :distance, :start_address, :end_address, :user_id, presence: true
22 |
23 | belongs_to :user
24 | has_many :comments, dependent: :destroy
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/frontend/components/friends/friends_index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | const FriendsIndex = ({ friends, deleteFriend }) => {
5 | const friendListItem = (friend) => {
6 | return (
7 |
8 |
9 |
10 |
{ friend.first_name + " " + friend.last_name }
11 |
12 |
13 |
14 | );
15 | };
16 |
17 |
18 | return (
19 |
20 |
21 | Friends
22 | Back to home page
23 |
24 |
25 | {friends.map(friend => friendListItem(friend))}
26 |
27 |
28 | );
29 | };
30 |
31 | export default FriendsIndex;
32 |
--------------------------------------------------------------------------------
/frontend/components/home/activity_feed/activity_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import Activity from './activity';
3 |
4 | import { combinePathsAndFriendsActivities } from '../../../util/selector.js';
5 |
6 | import { requestAllPaths, clearPaths } from '../../../actions/path_actions';
7 | import { requestAllFriends } from '../../../actions/friend_actions';
8 |
9 | const mapStateToProps = ({ session, paths }) => {
10 | return {
11 | currentUser: session.currentUser,
12 | paths: Object.keys(paths).map(id => paths[id])
13 | .sort(function(a, b) { return b.id - a.id; })
14 | };
15 | };
16 |
17 | const mapDispatchToProps = (dispatch) => {
18 | return {
19 | requestAllPaths: () => dispatch(requestAllPaths('activity')),
20 | clearPaths: () => dispatch(clearPaths())
21 | };
22 | };
23 |
24 | export default connect(
25 | mapStateToProps,
26 | mapDispatchToProps
27 | )(Activity);
28 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | context: __dirname,
6 | entry: './frontend/map_my_path.jsx',
7 | output: {
8 | path: path.resolve(__dirname, 'app', 'assets', 'javascripts'),
9 | filename: 'bundle.js'
10 | },
11 | resolve: {
12 | extensions: ['.js', '.jsx', '*']
13 | },
14 | module: {
15 | loaders: [
16 | {
17 | test: /\.jsx?$/,
18 | exclude: /(node_modules|bower_components)/,
19 | loader: 'babel-loader',
20 | query: {
21 | presets: ['react', 'es2015']
22 | }
23 | }
24 | ]
25 | },
26 | devtool: 'source-maps',
27 | // plugins: [
28 | // new webpack.DefinePlugin({
29 | // 'process.env': {
30 | // NODE_ENV: JSON.stringify('production')
31 | // }
32 | // }),
33 | // new webpack.optimize.UglifyJsPlugin()
34 | // ]
35 | };
36 |
--------------------------------------------------------------------------------
/frontend/util/paths_api_util.js:
--------------------------------------------------------------------------------
1 | export const getAllPaths = (type) => (
2 | $.ajax({
3 | method: 'GET',
4 | url: 'api/paths',
5 | data: { type }
6 | })
7 | );
8 |
9 | export const getSinglePath = (id) => (
10 | $.ajax({
11 | method: 'GET',
12 | url: `api/paths/${id}`
13 | })
14 | );
15 |
16 | export const createPath = (path) => (
17 | $.ajax({
18 | method: 'POST',
19 | url: 'api/paths',
20 | data: { path }
21 | })
22 | );
23 |
24 | export const updatePath = (path) => (
25 | $.ajax({
26 | method: 'PATCH',
27 | url: `api/paths/${path.id}`,
28 | data: { path }
29 | })
30 | );
31 |
32 | export const deletePath = (id) => (
33 | $.ajax({
34 | method: 'DELETE',
35 | url: `api/paths/${id}`
36 | })
37 | );
38 |
39 | // path = {
40 | // name: 'Sample',
41 | // polyline: 'Test',
42 | // distance: '5',
43 | // start_address: 'here',
44 | // end_address: 'there'
45 | // }
46 |
--------------------------------------------------------------------------------
/frontend/components/home/dashboard/pending_paths_index_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | const PendingPathsItem = ({ paths }) => {
5 | return (
6 |
7 | {
8 | paths.map(path => {
9 | const map = `https://maps.googleapis.com/maps/api/staticmap?size=135x125&path=weight:3%7Ccolor:red%7Cenc:${ path.polyline }&key=AIzaSyBygQhRnDSS9s1hu7jxsQMu3mwIU7Hd2N4`;
10 | return (
11 | -
12 |
13 |
14 |

15 |
16 |
17 |
{path.name}
18 |
{path.distance } mi
19 |
20 |
21 |
22 | );
23 | })
24 | }
25 |
26 | );
27 | };
28 |
29 | export default PendingPathsItem;
30 |
--------------------------------------------------------------------------------
/frontend/reducers/paths_reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_ALL_PATHS,
3 | RECEIVE_NEW_PATH,
4 | REMOVE_PATH,
5 | RECEIVE_SINGLE_PATH,
6 | CLEAR_PATHS
7 | } from '../actions/path_actions';
8 |
9 |
10 | const PathsReducer = (oldState = {}, action) => {
11 | Object.freeze(oldState);
12 | switch(action.type) {
13 | case RECEIVE_ALL_PATHS:
14 | return Object.assign({}, action.paths);
15 | case RECEIVE_NEW_PATH:
16 | return Object.assign(
17 | {},
18 | oldState,
19 | { [action.path.id]: action.path }
20 | );
21 | case RECEIVE_SINGLE_PATH:
22 | return Object.assign({}, { [action.path.id]: action.path });
23 | case REMOVE_PATH:
24 | let newState = Object.assign({}, oldState);
25 | delete newState[action.id];
26 | return newState;
27 | case CLEAR_PATHS:
28 | return {};
29 | default:
30 | return oldState;
31 | }
32 | };
33 |
34 | export default PathsReducer;
35 |
--------------------------------------------------------------------------------
/frontend/components/session_form/session_form_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { login, signOut, signUp } from '../../actions/session_actions';
3 | import { clearErrors } from '../../actions/error_actions';
4 | import SessionForm from './session_form';
5 |
6 | const mapStateToProps = ({ session, errors }) => {
7 | return ({
8 | signedIn: Boolean(session.currentUser),
9 | errors
10 | });
11 | };
12 |
13 | const mapDispatchToProps = (dispatch, { location }) => {
14 | const formType = location.pathname.slice(1);
15 | let processForm = signUp;
16 | if (formType === 'login') {
17 | processForm = login;
18 | }
19 | return {
20 | processForm: user => dispatch(processForm(user)),
21 | loginGuest: guest => dispatch(login(guest)),
22 | clearErrors: () => dispatch(clearErrors()),
23 | formType
24 | };
25 | };
26 |
27 | export default connect(
28 | mapStateToProps,
29 | mapDispatchToProps
30 | )(SessionForm);
31 |
--------------------------------------------------------------------------------
/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :integer not null, primary key
6 | # email :string not null
7 | # first_name :string not null
8 | # last_name :string not null
9 | # password_digest :string not null
10 | # session_token :string not null
11 | # img_url :string default("https://s3.us-east-2.amazonaws.com/mapmyrun-dev/default_avatar.png")
12 | # created_at :datetime not null
13 | # updated_at :datetime not null
14 | #
15 |
16 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
17 |
18 | # This model initially had no columns defined. If you add columns to the
19 | # model remove the '{}' from the fixture names and add the columns immediately
20 | # below each fixture, per the syntax in the comments below
21 | #
22 | one: {}
23 | # column: value
24 | #
25 | two: {}
26 | # column: value
27 |
--------------------------------------------------------------------------------
/frontend/components/home/activity_feed/activity.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Profile from '../profile_container';
3 |
4 | import ActivityIndexItem from './activity_index_item';
5 |
6 | class Activity extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | componentDidMount () {
12 | this.props.requestAllPaths();
13 | }
14 |
15 | componentWillUnmount () {
16 | this.props.clearPaths();
17 | }
18 |
19 | render () {
20 | return (
21 |
22 |
23 |
24 |
25 | recent activities
26 |
27 |
28 |
32 |
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | export default Activity;
40 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage/profile_box.scss:
--------------------------------------------------------------------------------
1 | .profile-box {
2 | display: flex;
3 | border: 1px solid #cdcdcd;
4 | background: #f8f8f8;
5 | border-radius: 3px;
6 | width: 230px;
7 | padding: 8px;
8 | box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
9 | margin: 15px 0 15px 30px;
10 | text-transform: uppercase;
11 | height: 100%;
12 |
13 | p:first-child {
14 | font-size: 12px;
15 | margin-bottom: 10px;
16 | letter-spacing: .8px;
17 | }
18 |
19 | .name {
20 | padding-bottom: 10px;
21 | border-bottom: 1px solid #cdcdcd;
22 | letter-spacing: .5px;
23 | overflow: hidden;
24 | text-overflow: ellipsis;
25 | width: 120px;
26 | }
27 |
28 | a {
29 | font-size: 13px;
30 | display: block;
31 | color:#2f98d9;
32 | margin-top: 10px;
33 | align-self: flex-end;
34 | }
35 |
36 | img {
37 | height: 100px;
38 | width: 100px;
39 | border: 1px solid #cdcdcd;
40 | background: #fff;
41 | }
42 |
43 | span {
44 | margin-left: 5px;
45 | padding: 5px;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/frontend/util/math_calculations.js:
--------------------------------------------------------------------------------
1 | export const convertSecondsToTime = (secNum) => {
2 | let hours = Math.floor(secNum / 3600);
3 | let minutes = Math.floor((secNum - (hours * 3600)) / 60);
4 | let seconds = secNum - (hours * 3600) - (minutes * 60);
5 |
6 | if (hours < 10) {hours = "0"+hours;}
7 | if (minutes < 10) {minutes = "0"+minutes;}
8 | if (seconds < 10) {seconds = "0"+seconds;}
9 | return (hours+':'+minutes+':'+seconds);
10 | };
11 |
12 | export const convertTimeToSeconds = (hours, minutes, seconds) => {
13 | const hoursSeconds = hours * 3600;
14 | const minutesSeconds = minutes * 60;
15 | return (hoursSeconds + minutesSeconds + seconds);
16 | };
17 |
18 | export const todaysDate = () => {
19 | const today = new Date();
20 | const year = today.getFullYear();
21 | let month = today.getMonth() + 1;
22 | let date = today.getDate();
23 | if (date < 10) {
24 | date = '0' + date;
25 | }
26 | if(month < 10){
27 | month = '0'+ month;
28 | }
29 |
30 | return (year + '-' + month + '-' + date);
31 | };
32 |
--------------------------------------------------------------------------------
/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: 782d6a54f6db812485cd79d59c06ffe2bec8e049511a3baf40e0aaaf98e8f730e69c44ede0f35f3a27c91a4387da30cae16cda64cf7f7ff1fdb5a9d03e1b301f
15 |
16 | test:
17 | secret_key_base: 6ad88cecf8526461bfe28b5b605ca479973844a10f1fe3b1d8478e6a733c1f037c58765dcf71a906743bc78b052d255c0b5419622d4c28ab086b9b7651de8170
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a starting point to setup your application.
15 | # Add necessary setup steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | # puts "\n== Copying sample files =="
22 | # unless File.exist?('config/database.yml')
23 | # cp 'config/database.yml.sample', 'config/database.yml'
24 | # end
25 |
26 | puts "\n== Preparing database =="
27 | system! 'bin/rails db:setup'
28 |
29 | puts "\n== Removing old logs and tempfiles =="
30 | system! 'bin/rails log:clear tmp:clear'
31 |
32 | puts "\n== Restarting application server =="
33 | system! 'bin/rails restart'
34 | end
35 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/paths_index/path_index_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 |
4 | const PathIndexItem = ({ path, deletePath, router }) => {
5 | const map = `https://maps.googleapis.com/maps/api/staticmap?size=80x80&path=weight:3%7Ccolor:red%7Cenc:${ path.polyline }&key=AIzaSyBygQhRnDSS9s1hu7jxsQMu3mwIU7Hd2N4`; const complete = (path.done) ? 'Yes' : 'No';
6 | return (
7 |
8 |  |
9 | {path.created_at.slice(0, 10)} |
10 | {path.distance} mi |
11 | {path.name} |
12 | {path.start_address} |
13 | {complete} |
14 |
15 |
16 |
17 | |
18 |
19 | );
20 | };
21 |
22 | export default withRouter(PathIndexItem);
23 |
--------------------------------------------------------------------------------
/app/controllers/api/users_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::UsersController < ApplicationController
2 | def index
3 | @friends = current_user.friends
4 | render :index
5 | end
6 |
7 | def create
8 | @user = User.new(user_params)
9 | if @user.save
10 | log_in(@user)
11 | render :show
12 | else
13 | render(
14 | json: @user.errors.messages,
15 | status: 422
16 | # will render a key value pair, e.g. first_name => ["can't be blank"]
17 | )
18 | end
19 | end
20 |
21 | def update
22 | @user = User.find_by(id: params[:id])
23 |
24 | if @user.update(user_params)
25 | render :show
26 | else
27 | render(
28 | json: @user.errors.messages,
29 | status: 422
30 | # will render a key value pair, e.g. first_name => ["can't be blank"]
31 | )
32 | end
33 | end
34 |
35 | private
36 |
37 | def user_params
38 | params.require(:user).permit(
39 | :first_name,
40 | :last_name,
41 | :email,
42 | :password,
43 | :img_url
44 | )
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/test/fixtures/paths.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: paths
4 | #
5 | # id :integer not null, primary key
6 | # name :string not null
7 | # polyline :text not null
8 | # distance :float not null
9 | # start_address :string not null
10 | # end_address :string not null
11 | # duration :integer default("0")
12 | # done :boolean default("false")
13 | # done_date :date
14 | # user_id :integer not null
15 | # created_at :datetime not null
16 | # updated_at :datetime not null
17 | # description :text
18 | #
19 |
20 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
21 |
22 | # This model initially had no columns defined. If you add columns to the
23 | # model remove the '{}' from the fixture names and add the columns immediately
24 | # below each fixture, per the syntax in the comments below
25 | #
26 | one: {}
27 | # column: value
28 | #
29 | two: {}
30 | # column: value
31 |
--------------------------------------------------------------------------------
/frontend/util/friends_api_util.js:
--------------------------------------------------------------------------------
1 | export const getAllFriends = () => {
2 | return $.ajax({
3 | method: 'GET',
4 | url: 'api/users'
5 | });
6 | };
7 |
8 | export const getPendingFriends = () => {
9 | return $.ajax({
10 | method: 'GET',
11 | url: 'api/relationships'
12 | });
13 | };
14 |
15 | export const addFriend = (friend_id) => (
16 | $.ajax({
17 | method: 'PATCH',
18 | url: `api/relationships/${friend_id}`,
19 | })
20 | );
21 |
22 | export const deleteFriend = (friend_id) => (
23 | $.ajax({
24 | method: 'DELETE',
25 | url: `api/relationships/${friend_id}`
26 | })
27 | );
28 |
29 | export const addRequest = (friend_id) => (
30 | $.ajax({
31 | method: 'POST',
32 | url: `api/relationships/`,
33 | data: { friend_id }
34 | })
35 | );
36 |
37 | export const deleteRequest = (friend_id) => (
38 | $.ajax({
39 | method: 'DELETE',
40 | url: `api/relationships/${friend_id}`
41 | })
42 | );
43 |
44 | export const getPotentialFriends = (search) => (
45 | $.ajax({
46 | method: 'GET',
47 | url: 'api/relationships/search',
48 | data: { search }
49 | })
50 | );
51 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage/profile_tabs.scss:
--------------------------------------------------------------------------------
1 | .profile-tabs-container {
2 |
3 | .profile-tabs {
4 | display: flex;
5 | padding: 35px 0 20px 0;
6 | color: #046AB2;
7 |
8 | a {
9 | font-size: 14px;
10 | text-transform: uppercase;
11 | padding: 12px 23px;
12 | width: 700px;
13 | text-align: center;
14 | background: #f6f6f6;
15 | border-top: 1px solid #d1d0cf;
16 | border-bottom: 1px solid #d1d0cf;
17 | border-right: 1px solid #d1d0cf;
18 | font-weight: 700;
19 |
20 |
21 | &:hover {
22 | color: #92BFDD;
23 | background: #fff;
24 | }
25 |
26 | &:first-child {
27 | border-left: 1px solid #dddddd;
28 | }
29 | }
30 |
31 | .selected {
32 | border-top: 3.5px solid #2BA5EF;
33 | color: #444444;
34 | background: #fff;
35 | border-bottom: 0;
36 | cursor: default;
37 |
38 | &:hover {
39 | color: #444444;
40 | background: #fff;
41 | }
42 | }
43 |
44 | .end-border {
45 | border-bottom: 1px solid #d1d0cf;
46 | width: 100%;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/path_show/comment_index_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedRelative } from 'react-intl';
3 |
4 | const CommentIndexItem = ({ comment, deleteComment, path, currentUser }) => {
5 | const deleteButton = () => {
6 | if (comment.author_id === currentUser.id || path.user.id === currentUser.id) {
7 | return ;
8 | } else {
9 | return "";
10 | }
11 | };
12 | return (
13 |
14 |
15 |

16 |
17 |
18 |
19 | { comment.author.name }
20 |
21 |
22 | { deleteButton() }
23 |
24 |
25 |
26 | { comment.body }
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | export default CommentIndexItem;
34 |
--------------------------------------------------------------------------------
/frontend/components/friends/all_friends_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import AllFriends from './all_friends';
3 |
4 | import {
5 | requestAllFriends,
6 | deleteFriend,
7 | deleteRequest,
8 | addFriend,
9 | requestAllRequests,
10 | } from '../../actions/friend_actions';
11 |
12 | const mapStateToProps = ({ session, friends, friendRequests }) => {
13 | return {
14 | currentUser: session.currentUser,
15 | friends: Object.keys(friends).map(id => friends[id])
16 | .sort(function(a, b) { return b.name - a.name; }),
17 | friendRequests: Object.keys(friendRequests).map(id => friendRequests[id])
18 | .sort(function(a, b) { return b.name - a.name; })
19 | };
20 | };
21 |
22 | const mapDispatchToProps = (dispatch) => {
23 | return {
24 | requestAllFriends: () => dispatch(requestAllFriends()),
25 | deleteFriend: (id) => dispatch(deleteFriend(id)),
26 | deleteRequest: (id) => dispatch(deleteRequest(id)),
27 | addFriend: (id) => dispatch(addFriend(id)),
28 | requestAllRequests: () => dispatch(requestAllRequests())
29 | };
30 | };
31 |
32 | export default connect(
33 | mapStateToProps,
34 | mapDispatchToProps
35 | )(AllFriends);
36 |
--------------------------------------------------------------------------------
/config/initializers/new_framework_defaults.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains migration options to ease your Rails 5.0 upgrade.
4 | #
5 | # Read the Guide for Upgrading Ruby on Rails for more info on each option.
6 |
7 | # Enable per-form CSRF tokens. Previous versions had false.
8 | Rails.application.config.action_controller.per_form_csrf_tokens = true
9 |
10 | # Enable origin-checking CSRF mitigation. Previous versions had false.
11 | Rails.application.config.action_controller.forgery_protection_origin_check = true
12 |
13 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
14 | # Previous versions had false.
15 | ActiveSupport.to_time_preserves_timezone = true
16 |
17 | # Require `belongs_to` associations by default. Previous versions had false.
18 | Rails.application.config.active_record.belongs_to_required_by_default = true
19 |
20 | # Do not halt callback chains when a callback returns false. Previous versions had true.
21 | ActiveSupport.halt_callback_chains_on_return_false = false
22 |
23 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false.
24 | Rails.application.config.ssl_options = { hsts: { subdomains: true } }
25 |
--------------------------------------------------------------------------------
/docs/sample-state.md:
--------------------------------------------------------------------------------
1 | ```js
2 | {
3 | session: {
4 | currentUser: {
5 | id: 1,
6 | name: "Eugene",
7 | img_url: "url"
8 | }
9 | },
10 |
11 | errors: {
12 | email: "cannot be blank",
13 | first_name: "cannot be blank",
14 | password: "is too short (minimum is 6 characters)"
15 | },
16 |
17 | paths: {
18 | 1: {
19 | user_id: 1,
20 | name: "Sample path",
21 | polyline: "",
22 | distance: "5 miles",
23 | done: false,
24 | map_url: "",
25 | duration: null
26 | },
27 | 2: {
28 | user_id: 2,
29 | name: "Sample path 2",
30 | polyline: "",
31 | distance: "5 miles",
32 | done: true,
33 | map_url: "",
34 | duration: 60
35 | }
36 | },
37 |
38 | friends: {
39 | 2: {
40 | id: 2,
41 | name: "Jennifer",
42 | img_url: "url"
43 | },
44 | 3: {
45 | id: 3,
46 | name: "Kevin",
47 | img_url: "url"
48 | },
49 | 4: {
50 | id: 4,
51 | name: "Josh",
52 | img_url: "url"
53 | }
54 | },
55 |
56 | comments: {
57 | 1: {
58 | id: 1,
59 | pathId: 1,
60 | authorId: 2,
61 | body: "good job!"
62 | }
63 | }
64 | }
65 | ```
66 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/friends/friends_tab.scss:
--------------------------------------------------------------------------------
1 | .friends-content-container {
2 | margin: 0 auto;
3 | max-width: 940px;
4 | padding-bottom: 100px;
5 | }
6 |
7 | .friends-tabs-container {
8 |
9 | .friends-tabs {
10 | display: flex;
11 | padding: 35px 0 20px 0;
12 | color: #046AB2;
13 |
14 | a {
15 | font-size: 14px;
16 | text-transform: uppercase;
17 | padding: 12px 23px;
18 | width: 500px;
19 | text-align: center;
20 | background: #f6f6f6;
21 | border-top: 1px solid #d1d0cf;
22 | border-bottom: 1px solid #d1d0cf;
23 | border-right: 1px solid #d1d0cf;
24 | font-weight: 700;
25 |
26 |
27 | &:hover {
28 | color: #92BFDD;
29 | background: #fff;
30 | }
31 |
32 | &:first-child {
33 | border-left: 1px solid #dddddd;
34 | }
35 | }
36 |
37 | .selected {
38 | border-top: 3.5px solid #2BA5EF;
39 | color: #444444;
40 | background: #fff;
41 | border-bottom: 0;
42 | cursor: default;
43 |
44 | &:hover {
45 | color: #444444;
46 | background: #fff;
47 | }
48 | }
49 |
50 | .end-border {
51 | border-bottom: 1px solid #d1d0cf;
52 | width: 100%;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MapMyPath
6 | <%= csrf_meta_tags %>
7 |
8 |
10 |
20 |
21 | <%= stylesheet_link_tag 'application', media: 'all' %>
22 | <%= javascript_include_tag 'application' %>
23 | <%= favicon_link_tag image_path("favicon.ico") %>
24 |
25 |
26 |
27 |
28 |
29 | <%= yield %>
30 |
31 |
32 |
--------------------------------------------------------------------------------
/frontend/components/home/dashboard/complete_paths_index_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 | import { convertSecondsToTime } from '../../../util/math_calculations';
4 |
5 | const CompletePathsItem = ({ paths }) => {
6 | return (
7 |
8 | {
9 | paths.map(path => {
10 | const map = `https://maps.googleapis.com/maps/api/staticmap?size=120x100&path=weight:3%7Ccolor:red%7Cenc:${ path.polyline }&key=AIzaSyBygQhRnDSS9s1hu7jxsQMu3mwIU7Hd2N4`;
11 | return (
12 | -
13 |
14 |

15 |
16 |
17 |
Distance
18 |
{path.distance} mi
19 |
20 |
21 |
Duration
22 |
{convertSecondsToTime(path.duration)}
23 |
24 |
25 |
Date
26 |
{path.done_date || '--'}
27 |
28 |
29 | );
30 | })
31 | }
32 |
33 | );
34 | };
35 |
36 | export default CompletePathsItem;
37 |
--------------------------------------------------------------------------------
/frontend/components/friends/friends_tab.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | class FriendsTab extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.selectedTab = this.selectedTab.bind(this);
8 | }
9 |
10 | selectedTab(tab) {
11 | if (this.props.location.pathname.includes(tab)) {
12 | return "selected";
13 | }
14 | return "unselected";
15 | }
16 |
17 | render () {
18 | return (
19 |
20 |
21 |
22 |
23 |
26 | My Friends
27 |
28 |
31 | Find Friends
32 |
33 |
34 |
35 |
36 |
37 |
38 | {this.props.children}
39 |
40 | );
41 | }
42 | }
43 |
44 | export default FriendsTab;
45 |
--------------------------------------------------------------------------------
/frontend/components/friends/friend_requests_index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class FriendRequestsIndex extends React.Component {
4 | constructor(props) {
5 | super(props);
6 |
7 | this.friendListItem = this.friendListItem.bind(this);
8 | }
9 |
10 | friendListItem (friend) {
11 | return (
12 |
13 |
14 |
15 |
{ friend.first_name + " " + friend.last_name }
16 |
20 |
21 |
22 | );
23 | }
24 |
25 | render () {
26 | if (this.props.friendRequests.length > 0) {
27 | return (
28 |
29 |
30 | Friend Requests
31 |
32 |
33 | {this.props.friendRequests.map(friend => this.friendListItem(friend))}
34 |
35 |
36 | );
37 | } else {
38 | return ;
39 | }
40 | }
41 | }
42 |
43 | // return (
44 | // hi
45 | // );
46 | export default FriendRequestsIndex ;
47 |
--------------------------------------------------------------------------------
/docs/component-hierarchy.md:
--------------------------------------------------------------------------------
1 | ## Component Hierarchy
2 |
3 |
4 | **NavigationContainer**
5 | - AuthFormContainer
6 | + AuthForm
7 |
8 | - HomePageContainer
9 | + ActivityFeedContainer
10 | - ActivityIndexContainer
11 | + ActivityIndexItem
12 | - RouteDetail
13 | + Comment
14 | + UserContainer
15 |
16 | + DashboardContainer
17 | - RoutesSummary
18 |
19 | + UserContainer
20 |
21 | + RoutesContainer
22 | - RoutesIndexContainer
23 | - RoutesIndexItems
24 | - NewRouteRouteForm
25 |
26 | - FriendsContainer
27 | + FriendsIndexContainer
28 | - FriendsIndexItem
29 | - PendingFriendsIndex
30 | - PendingFriendsIndexItem
31 | + FindFriendsSearchIndex
32 | - FindFriendsIndexItem
33 |
34 |
35 |
36 | ## Routes
37 |
38 | # Routes
39 | |Path | Component |
40 | | --------------------- | ------------- |
41 | | /signup | AuthFormContainer |
42 | | /login | AuthFormContainer |
43 | | /homepage/routes/ | RoutesIndexContainer |
44 | | /homepage/routes/new | NewRoutesForm |
45 | | /homepage/routes/:id | RouteShowContainer |
46 | | /homepage/dashboard/ | RoutesSummary |
47 | | /homepage/activity | ActivityFeedContainer |
48 | | /friends | FriendsContainer |
49 | | /friends/my-friends | FriendsIndexContainer |
50 | | /friends/find-friends | FindFriendsContainer |
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MapMyPath",
3 | "version": "1.0.0",
4 | "description": "This README would normally document whatever steps are necessary to get the application up and running.",
5 | "main": "index.js",
6 | "directories": {
7 | "doc": "docs",
8 | "test": "test"
9 | },
10 | "scripts": {
11 | "test": "echo \"Error: no test specified\" && exit 1",
12 | "postinstall": "webpack",
13 | "webpack": "./node_modules/.bin/webpack"
14 | },
15 | "engines": {
16 | "node": "6.7.0",
17 | "npm": "3.10.7"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/eulow/MapMyPath.git"
22 | },
23 | "keywords": [],
24 | "author": "",
25 | "license": "ISC",
26 | "bugs": {
27 | "url": "https://github.com/eulow/MapMyPath/issues"
28 | },
29 | "homepage": "https://github.com/eulow/MapMyPath#readme",
30 | "dependencies": {
31 | "babel-core": "^6.24.1",
32 | "babel-loader": "^6.4.1",
33 | "babel-preset-es2015": "^6.24.1",
34 | "babel-preset-react": "^6.24.1",
35 | "react": "^15.5.4",
36 | "react-dom": "^15.5.4",
37 | "react-intl": "^2.2.3",
38 | "react-modal": "^1.7.7",
39 | "react-redux": "^5.0.4",
40 | "react-router": "^3.0.5",
41 | "redux": "^3.6.0",
42 | "redux-logger": "^3.0.1",
43 | "redux-thunk": "^2.2.0"
44 | },
45 | "devDependencies": {
46 | "webpack": "^2.4.1"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/controllers/api/paths_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::PathsController < ApplicationController
2 | before_action :require_logged_in!
3 |
4 | def index
5 | if params[:type] == 'index'
6 | @paths = current_user.paths
7 | elsif params[:type] == 'activity'
8 | @paths = current_user.recent_activities
9 | end
10 | render :index
11 | end
12 |
13 | def create
14 | @path = current_user.paths.new(path_params)
15 |
16 | if @path.save
17 | render :show
18 | else
19 | render(
20 | json: @path.errors.messages,
21 | status: 422
22 | )
23 | end
24 | end
25 |
26 | def show
27 | @path = Path.includes(:comments).find(params[:id])
28 | render :show
29 | end
30 |
31 | def destroy
32 | @path = Path.find(params[:id])
33 | @path.destroy
34 | render json: @path
35 | end
36 |
37 | def update
38 | @path = Path.find(params[:id])
39 |
40 | if @path.update(path_params)
41 | render :show
42 | else
43 | render(
44 | json: @path.errors.messages,
45 | status: 422
46 | )
47 | end
48 | end
49 |
50 | private
51 |
52 | def path_params
53 | params.require(:path).permit(
54 | :name,
55 | :polyline,
56 | :distance,
57 | :start_address,
58 | :end_address,
59 | :duration,
60 | :done,
61 | :done_date,
62 | :description
63 | )
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/frontend/components/home/home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | class Home extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.selectedTab = this.selectedTab.bind(this);
8 | }
9 |
10 | selectedTab(tab) {
11 | if (this.props.location.pathname.includes(tab)) {
12 | return "selected";
13 | }
14 | return "unselected";
15 | }
16 |
17 | render () {
18 | return (
19 |
20 |
21 |
22 |
23 |
26 | Activity Feed
27 |
28 |
31 | My Dashboard
32 |
33 |
35 | My Paths
36 |
37 |
38 |
39 |
40 |
41 |
42 | {this.props.children}
43 |
44 | );
45 | }
46 | }
47 |
48 | export default Home;
49 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/path_show/path_show_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import PathShow from './path_show';
3 |
4 | import {
5 | createComment,
6 | deleteComment,
7 | requestAllComments
8 | } from '../../../../actions/comment_actions';
9 |
10 | import {
11 | requestSinglePath,
12 | deletePath,
13 | updatePath
14 | } from '../../../../actions/path_actions';
15 |
16 | import { clearErrors } from '../../../../actions/error_actions';
17 |
18 | const mapStateToProps = ({ session, paths, comments, errors }) => {
19 | const path = paths[Object.keys(paths)];
20 | return {
21 | path,
22 | errors,
23 | comments: Object.keys(comments).map(id => comments[id])
24 | .sort(function(a, b) { return b.id - a.id; }),
25 | currentUser: session.currentUser
26 | };
27 | };
28 |
29 | const mapDispatchToProps = (dispatch) => {
30 | return {
31 | requestSinglePath: (id) => dispatch(requestSinglePath(id)),
32 | updatePath: (path) => dispatch(updatePath(path)),
33 | deletePath: (id) => dispatch(deletePath(id)),
34 | requestAllComments: (pathId) => dispatch(requestAllComments(pathId)),
35 | createComment: (commentId, pathId) =>
36 | dispatch(createComment(commentId, pathId)),
37 | deleteComment: (commentId, pathId) =>
38 | dispatch(deleteComment(commentId, pathId)),
39 | clearErrors: () => dispatch(clearErrors())
40 | };
41 | };
42 |
43 | export default connect(
44 | mapStateToProps,
45 | mapDispatchToProps
46 | )(PathShow);
47 |
--------------------------------------------------------------------------------
/frontend/actions/session_actions.js:
--------------------------------------------------------------------------------
1 | import * as SessionAPIUtil from '../util/session_api_util';
2 | import { clearErrors, addErrors } from './error_actions';
3 |
4 | export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER';
5 |
6 | export const receiveCurrentUser = (currentUser) => ({
7 | type: RECEIVE_CURRENT_USER,
8 | currentUser
9 | });
10 |
11 | export const login = (user) => dispatch => {
12 | return (
13 | SessionAPIUtil.login(user)
14 | .then(
15 | (currentUser) => {
16 | dispatch(receiveCurrentUser(currentUser));
17 | dispatch(clearErrors());
18 |
19 | // dispatches receiveCurrentUser session action and clear error action
20 | },
21 | (errors) => {
22 | dispatch(addErrors(errors));
23 | // dispatches error action to error reducer
24 | }
25 | )
26 | );
27 | };
28 |
29 | export const signOut = () => dispatch => {
30 | return (
31 | SessionAPIUtil.signOut()
32 | .then((currentUser) => {
33 | dispatch(receiveCurrentUser(null));
34 | })
35 | );
36 | };
37 |
38 |
39 | export const signUp = (user) => dispatch => (
40 | SessionAPIUtil.signUp(user)
41 | .then(
42 | (currentUser) => {
43 | dispatch(receiveCurrentUser(currentUser));
44 | dispatch(clearErrors());
45 |
46 | // dispatches receiveCurrentUser session action and clear error action
47 | },
48 | (errors) => {
49 | dispatch(addErrors(errors));
50 | // dispatches error action to error reducer
51 | }
52 | )
53 | );
54 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/paths_index/path_index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 | import PathIndexItem from './path_index_item';
4 |
5 | class PathIndex extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | componentDidMount () {
11 | this.props.requestAllPaths();
12 | }
13 |
14 | render () {
15 |
16 | return (
17 |
18 |
19 | My Paths
20 | Create a path
24 |
25 |
26 |
27 |
28 |
29 | | Path |
30 | Created |
31 | Distance |
32 | Name |
33 | Start Location |
34 | Complete |
35 | Options |
36 |
37 |
38 |
39 | {
40 | this.props.paths.map(path => {
41 | return (
42 |
46 | );
47 | })
48 | }
49 |
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | export default PathIndex;
57 |
--------------------------------------------------------------------------------
/frontend/actions/comment_actions.js:
--------------------------------------------------------------------------------
1 | import * as CommentsAPIUtil from '../util/comments_api_util';
2 | import { clearErrors, addErrors } from './error_actions';
3 |
4 | export const RECEIVE_ALL_COMMENTS = 'RECEIVE_ALL_COMMENTS';
5 | export const RECEIVE_NEW_COMMENT = 'RECEIVE_NEW_COMMENT';
6 | export const REMOVE_COMMENT = 'DELETE_COMMENT';
7 |
8 | export const receiveAllComments = (comments) => ({
9 | type: RECEIVE_ALL_COMMENTS,
10 | comments
11 | });
12 |
13 | export const receiveNewComment = (comment) => ({
14 | type: RECEIVE_NEW_COMMENT,
15 | comment
16 | });
17 |
18 | export const removeComment = (id) => ({
19 | type: REMOVE_COMMENT,
20 | id
21 | });
22 |
23 |
24 | export const requestAllComments = (pathId) => (dispatch) => {
25 | return (
26 | CommentsAPIUtil.getAllComments(pathId)
27 | .then(
28 | (comments) => {
29 | dispatch(receiveAllComments(comments));
30 | }
31 | )
32 | );
33 | };
34 |
35 | export const createComment = (comment) => (dispatch) => {
36 | return (
37 | CommentsAPIUtil.createComment(comment)
38 | .then(
39 | (newComment) => {
40 | dispatch(receiveNewComment(newComment));
41 | dispatch(clearErrors());
42 | },
43 | (errors) => {
44 | dispatch(addErrors(errors));
45 | }
46 | )
47 | );
48 | };
49 |
50 | export const deleteComment = (comment) => (dispatch) => {
51 | return (
52 | CommentsAPIUtil.deleteComment(comment)
53 | .then(
54 | (deletedComment) => {
55 | dispatch(removeComment(deletedComment.id));
56 | },
57 | (errors) => {
58 | dispatch(addErrors(errors));
59 | }
60 | )
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage/paths/map.scss:
--------------------------------------------------------------------------------
1 | #map-container {
2 | border: 1px solid black;
3 | position: relative;
4 |
5 | .map-buttons {
6 | position: absolute;
7 | z-index: 1;
8 | top: 10px;
9 | left: 100px;
10 | border-radius: 2px;
11 | font-family: Roboto, Arial, sans-serif;
12 | background-color: rgb(255, 255, 255);
13 | color: rgb(86, 86, 86);
14 |
15 | button {
16 | font-size: 11px;
17 | text-align: center;
18 | cursor: pointer;
19 | border-radius: 2px;
20 | padding: 8px;
21 | box-shadow: rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px;
22 | min-width: 30px;
23 |
24 | &:hover {
25 | background: -webkit-gradient(
26 | linear, left top, left bottom,
27 | from(#ededed),
28 | to(#e0e0e0));
29 | }
30 | }
31 | }
32 |
33 | #map {
34 | display: flex;
35 | flex-direction: column;
36 | justify-content: center;
37 | align-items: center;
38 | width: 690px;
39 | height: 620px;
40 | }
41 | }
42 |
43 | // .ReactModal__Body--open {
44 | // overflow: hidden;
45 | // }
46 |
47 | .map-modal-overlay {
48 | position: fixed;
49 | top: 0;
50 | bottom: 0;
51 | left: 0;
52 | right: 0;
53 | background-color: #dddddd;
54 | z-index: 3;
55 | opacity: .9;
56 | }
57 |
58 | .map-modal-content {
59 | border: 1px solid black;
60 | position: absolute;
61 | width: 300px;
62 | height: 300px;
63 | top: 26%;
64 | left: 40%;
65 | right: 50%;
66 | bottom: 50%;
67 | background: #fff;
68 | border-radius: 4px;
69 | overflow: hidden;
70 |
71 | div {
72 | margin: auto;
73 | }
74 |
75 | p{
76 | text-align: center;
77 | padding: 40px;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/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/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => 'public, max-age=3600'
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 | config.action_mailer.perform_caching = false
31 |
32 | # Tell Action Mailer not to deliver emails to the real world.
33 | # The :test delivery method accumulates sent emails in the
34 | # ActionMailer::Base.deliveries array.
35 | config.action_mailer.delivery_method = :test
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/friends/my_friends.scss:
--------------------------------------------------------------------------------
1 | .friends-main {
2 | display: flex;
3 | flex-direction: column;
4 | color: #444444;
5 |
6 |
7 | .friends, .friend-requests {
8 | header {
9 | margin-top: 8px;
10 | padding-bottom: 27px;
11 | border-bottom: 1px solid #d1d0cf;
12 | text-transform: uppercase;
13 | display: flex;
14 | text-transform: uppercase;
15 | justify-content: space-between;
16 | align-items: center;
17 | letter-spacing: 1px;
18 |
19 | h2 {
20 | font-weight: lighter;
21 | margin-left: 5px;
22 | font-weight: lighter;
23 | }
24 |
25 | .back-button {
26 | font-size: 12px;
27 | color: white;
28 | padding: 15px 18px;
29 | transition: .6s;
30 | border-radius: 2px;
31 | font-weight: bold;
32 | background: -webkit-gradient(
33 | linear, left top, left bottom,
34 | from(#4499db),
35 | to(#177ed7));
36 | &:hover {
37 | background: -webkit-gradient(
38 | linear, left top, left bottom,
39 | from(#3f92c6),
40 | to(#0074bc))
41 | }
42 | }
43 | }
44 |
45 | ul {
46 | display: flex;
47 | flex-wrap: wrap;
48 | margin: 30px 0;
49 |
50 | li {
51 | display: flex;
52 | width: 300px;
53 | height: 90px;
54 | align-items: center;
55 | box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
56 | background: #f8f8f8;
57 | margin: 0 10px 10px 0;
58 |
59 | img {
60 | height: 70px;
61 | width: 70px;
62 | margin: 10px;
63 | display: block;
64 | background: #fff;
65 | }
66 |
67 | button {
68 | color: #0076c0;
69 | font-size: 12px;
70 | cursor: pointer;
71 | padding: 2px 0;
72 | margin: 8px 20px 0 0;
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage/paths/paths_index.scss:
--------------------------------------------------------------------------------
1 | .paths-index {
2 | color: #444444;
3 | box-sizing:border-box;
4 |
5 | header {
6 | display: flex;
7 | text-transform: uppercase;
8 | justify-content: space-between;
9 | align-items: center;
10 | padding-bottom: 20px;
11 | border-bottom: 1px solid #d1d0cf;
12 | letter-spacing: 1px;
13 |
14 | .create-button {
15 | font-size: 12px;
16 | color: white;
17 | padding: 15px 18px;
18 | transition: .6s;
19 | border-radius: 2px;
20 | font-weight: bold;
21 | background: -webkit-gradient(
22 | linear, left top, left bottom,
23 | from(#ff9628),
24 | to(#ff8714));
25 | &:hover {
26 | background: -webkit-gradient(
27 | linear, left top, left bottom,
28 | from(#f08a15),
29 | to(#e56e06))
30 | }
31 | }
32 | .path-index-header {
33 | margin-left: 5px;
34 | font-weight: lighter;
35 | }
36 | }
37 |
38 | table {
39 | margin-top: 30px;
40 | width: 100%;
41 | font-size: 12px;
42 | border-collapse: collapse;
43 |
44 | th {
45 | padding: 15px 10px;
46 | text-align: left;
47 | border: 1px solid #d1d0cf;
48 | background-color: #f6f6f6;
49 | }
50 | td {
51 | padding: 10px 15px;
52 | border: 1px solid #d1d0cf;
53 | }
54 |
55 | td:first-child {
56 | padding: 10px 5px;
57 |
58 | img {
59 | display: block;
60 | margin: auto;
61 | }
62 | }
63 |
64 | tr:nth-child(odd) {
65 | background: #FFF
66 | }
67 |
68 | tr:nth-child(even) {
69 | background-color: #f6f6f6;
70 | }
71 |
72 | button {
73 | margin: auto;
74 | display: block;
75 | color: #0076c0;
76 | font-size: 12px;
77 | cursor: pointer;
78 | padding: 2px 0;
79 | }
80 |
81 | tbody tr:hover {
82 | background: #ececec;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/frontend/components/masthead/masthead.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 |
4 | class Masthead extends React.Component {
5 | constructor(props) {
6 | super(props);
7 |
8 | this.userInfo = this.userInfo.bind(this);
9 | this.signOut = this.signOut.bind(this);
10 | }
11 |
12 | sessionLinks () {
13 | return (
14 |
18 | );
19 | }
20 |
21 | signOut(e) {
22 | e.preventDefault();
23 | this.props.signOut().then(
24 | () => {
25 | this.props.router.push('/');
26 | }
27 | );
28 | }
29 |
30 | userInfo () {
31 | return (
32 |
43 | );
44 | }
45 |
46 |
47 | render () {
48 | let render = this.props.currentUser ? this.userInfo() : this.sessionLinks();
49 | if (this.props.path === '/login' || this.props.path ==='/signup') {
50 | render = ;
51 | }
52 |
53 | return (
54 |
55 |
56 |
57 |
58 |
mapmypath
59 |
60 |
61 | {render}
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | export default withRouter(Masthead);
69 |
--------------------------------------------------------------------------------
/docs/README.MD:
--------------------------------------------------------------------------------
1 | # MapMyPath
2 |
3 | [Heroku Link][heroku]
4 | [Trello Link][trello]
5 |
6 | [heroku]: https://mapmypath.herokuapp.com/
7 | [trello]: https://trello.com/b/CvrGYg5n
8 |
9 | ## Minimum Viable Product
10 |
11 | MapMyPath is a web application inspired by MapMyRun used for mapping
12 | your moving activities built using Ruby on Rails for the backend and
13 | React/Redux for the front end. By the end of week 9, this app will
14 | have the following complete allowing for a smooth, bug-free navigation with
15 | adequate seed data and CSS styling:
16 |
17 | - [ ] Hosting on Heroku
18 | - [ ] New account creation, login and guest login
19 | - [ ] Making running routes using a map
20 | - [ ] Friending other users
21 | - [ ] Commenting on your friends' runs
22 | - [ ] Dashboard that tracks all your pending/completed runs
23 |
24 |
25 |
26 | ## Design Docs
27 |
28 | * [View Wireframes][wireframes]
29 | * [React Components][components]
30 | * [API endpoints][api-points]
31 | * [DB Schema][schema]
32 | * [Sample State][sample-state]
33 |
34 | [wireframes]: wireframes
35 | [components]: component-hierarchy.md
36 | [api-points]: api-endpoints.md
37 | [schema]: schema.md
38 | [sample-state]: sample-state.md
39 |
40 | ## Implementation Timeline
41 |
42 | ### Phase 1: Backend setup and frontend user authentication (2 days)
43 |
44 | **Objective:** Functioning rail project with front-end authentication
45 |
46 | ### Phase 2: Dashboard (2 days)
47 |
48 | **Objective:** Dashboard that connects all 3 models together into one usable page, along with appropriate styling
49 |
50 | ### Phase 3: Maps model, API and components (2 days)
51 |
52 | **Objective:** Maps model that can be created, read, edited and destroyed through API, along with appropriate styling
53 |
54 | ### Phase 4: Friends model (2 days)
55 |
56 | **Objective:** Friends that belong to users that can be created, read, edited and destroyed, along with appropriate styling
57 |
58 | ### Phase 5: Comments model and Status model (1 day)
59 |
60 | **Objective:** Comments that belong to Users and Routes that can be created, read, edited and destroyed.
61 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | if Rails.root.join('tmp/caching-dev.txt').exist?
17 | config.action_controller.perform_caching = true
18 |
19 | config.cache_store = :memory_store
20 | config.public_file_server.headers = {
21 | 'Cache-Control' => 'public, max-age=172800'
22 | }
23 | else
24 | config.action_controller.perform_caching = false
25 |
26 | config.cache_store = :null_store
27 | end
28 |
29 | # Don't care if the mailer can't send.
30 | config.action_mailer.raise_delivery_errors = false
31 |
32 | config.action_mailer.perform_caching = false
33 |
34 | # Print deprecation notices to the Rails logger.
35 | config.active_support.deprecation = :log
36 |
37 | # Raise an error on page load if there are pending migrations.
38 | config.active_record.migration_error = :page_load
39 |
40 | # Debug mode disables concatenation and preprocessing of assets.
41 | # This option may cause significant delays in view rendering with a large
42 | # number of complex assets.
43 | config.assets.debug = true
44 |
45 | # Suppress logger output for asset requests.
46 | config.assets.quiet = true
47 |
48 | # Raises error for missing translations
49 | # config.action_view.raise_on_missing_translations = true
50 |
51 | # Use an evented file watcher to asynchronously detect changes in source code,
52 | # routes, locales, etc. This feature depends on the listen gem.
53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
54 | end
55 |
--------------------------------------------------------------------------------
/app/controllers/api/relationships_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::RelationshipsController < ApplicationController
2 | before_action :require_logged_in!
3 |
4 | def index
5 | @friends = current_user.pending_friends
6 | render :index
7 | end
8 |
9 | def create
10 | @relationship = Relationship.new
11 | @relationship.action_user_id = current_user.id
12 |
13 | users = [current_user.id, params[:friend_id].to_i].sort
14 |
15 | @relationship.user_one_id = users[0]
16 | @relationship.user_two_id = users[1]
17 | @relationship.status = 0
18 | if @relationship.save
19 | @user = User.find(params[:friend_id])
20 | render 'api/users/show'
21 | else
22 | render @relationship.errors.messages
23 | end
24 | end
25 |
26 | def show
27 | @friends = current_user.potential_friends(params[:search])
28 | render :index
29 | end
30 |
31 | def update
32 | relationship = [current_user.id, params[:id].to_i].sort
33 | @relationship = Relationship
34 | .where(user_one_id: relationship[0])
35 | .where(user_two_id: relationship[1])
36 | .first
37 |
38 | @relationship.action_user_id = current_user.id
39 | @relationship.status = 1
40 |
41 | if @relationship.save
42 | friend_id = relationship.reject { |user| user == current_user.id }
43 | @user = User.find_by(id: friend_id.first)
44 | render 'api/users/show'
45 | else
46 | render json: @relationship.errors.messages
47 | end
48 | end
49 |
50 | def destroy
51 | friendship = [current_user.id, params[:id].to_i].sort
52 | @relationship = Relationship
53 | .where(user_one_id: friendship[0])
54 | .where(user_two_id: friendship[1])
55 | .first
56 |
57 | if @relationship.destroy
58 | friend_id = friendship.reject { |user| user == current_user.id }
59 | @user = User.find_by(id: friend_id.first)
60 | render 'api/users/show'
61 | else
62 | render json: @relationship.errors.messages
63 | end
64 | end
65 |
66 | private
67 |
68 | def relationship_params
69 | params.require(:relationship).permit(:friend_id)
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | git_source(:github) do |repo_name|
4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
5 | "https://github.com/#{repo_name}.git"
6 | end
7 |
8 | gem 'faker'
9 |
10 | gem 'bcrypt'
11 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
12 | gem 'figaro'
13 |
14 | gem 'rails', '~> 5.0.2'
15 | # Use postgresql as the database for Active Record
16 | gem 'pg', '~> 0.18'
17 | # Use Puma as the app server
18 | gem 'puma', '~> 3.0'
19 | # Use SCSS for stylesheets
20 | gem 'sass-rails', '~> 5.0'
21 | # Use Uglifier as compressor for JavaScript assets
22 | gem 'uglifier', '>= 1.3.0'
23 | # Use CoffeeScript for .coffee assets and views
24 | gem 'coffee-rails', '~> 4.2'
25 | # See https://github.com/rails/execjs#readme for more supported runtimes
26 | # gem 'therubyracer', platforms: :ruby
27 |
28 | # Use jquery as the JavaScript library
29 | gem 'jquery-rails'
30 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
31 | gem 'jbuilder', '~> 2.5'
32 | # Use Redis adapter to run Action Cable in production
33 | # gem 'redis', '~> 3.0'
34 | # Use ActiveModel has_secure_password
35 | # gem 'bcrypt', '~> 3.1.7'
36 |
37 | # Use Capistrano for deployment
38 | # gem 'capistrano-rails', group: :development
39 |
40 | group :development, :test do
41 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
42 | gem 'annotate'
43 | gem 'better_errors'
44 | gem 'binding_of_caller'
45 | gem 'byebug', platform: :mri
46 | gem 'pry-rails'
47 | end
48 |
49 | group :development do
50 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
51 | gem 'listen', '~> 3.0.5'
52 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
53 | gem 'spring'
54 | gem 'spring-watcher-listen', '~> 2.0.0'
55 | gem 'web-console', '>= 3.3.0'
56 | end
57 |
58 | group :production do
59 | gem 'rails_12factor'
60 | end
61 |
62 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
63 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
64 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum, this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory. If you use this option
30 | # you need to make sure to reconnect any threads in the `on_worker_boot`
31 | # block.
32 | #
33 | # preload_app!
34 |
35 | # The code in the `on_worker_boot` will be called if you are using
36 | # clustered mode by specifying a number of `workers`. After each worker
37 | # process is booted this block will be run, if you are using `preload_app!`
38 | # option you will want to use this block to reconnect to any threads
39 | # or connections that may have been created at application boot, Ruby
40 | # cannot share connections between processes.
41 | #
42 | # on_worker_boot do
43 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
44 | # end
45 |
46 | # Allow puma to be restarted by `rails restart` command.
47 | plugin :tmp_restart
48 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/friends/friend_search.scss:
--------------------------------------------------------------------------------
1 | .friends-search {
2 | color: #444444;
3 |
4 | // * {
5 | // border: 1px solid black;
6 | // }
7 |
8 | header {
9 | margin-top: 8px;
10 | padding-bottom: 27px;
11 | border-bottom: 1px solid #d1d0cf;
12 | // vertical-align: middle;
13 |
14 | h2 {
15 | text-transform: uppercase;
16 | letter-spacing: 1px;
17 | font-weight: lighter;
18 | margin-left: 5px;
19 | }
20 |
21 | form {
22 | margin-top: 10px;
23 |
24 | input[type='text'] {
25 | border: 1px solid #d1d0cf;
26 | width: 500px;
27 | margin: 10px 0 0 5px;
28 | font-size: 14px;
29 | padding: 10px;;
30 | }
31 |
32 | input[type='submit'] {
33 | margin-left: 20px;
34 | text-transform: uppercase;
35 | font-size: 12px;
36 | color: white;
37 | padding: 15px 60px;
38 | transition: .6s;
39 | border-radius: 2px;
40 | font-weight: bold;
41 | letter-spacing: .5px;
42 | background: -webkit-gradient(
43 | linear, left top, left bottom,
44 | from(#ff9628),
45 | to(#ff8714));
46 | cursor: pointer;
47 | &:hover {
48 | background: -webkit-gradient(
49 | linear, left top, left bottom,
50 | from(#f08a15),
51 | to(#e56e06))
52 | }
53 | }
54 | }
55 | }
56 |
57 | .unable-to-find {
58 | margin: 30px 0 0 10px;
59 | }
60 |
61 | ul {
62 | display: flex;
63 | flex-wrap: wrap;
64 | margin: 30px 0;
65 |
66 | li {
67 | display: flex;
68 | width: 300px;
69 | height: 90px;
70 | align-items: center;
71 | box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
72 | background: #f8f8f8;
73 | margin: 0 10px 10px 0;
74 |
75 | img {
76 | height: 70px;
77 | width: 70px;
78 | margin: 10px;
79 | display: block;
80 | background: #fff;
81 | }
82 |
83 | button {
84 | color: #0076c0;
85 | font-size: 12px;
86 | cursor: pointer;
87 | padding: 2px 0;
88 | margin: 8px 20px 0 0;
89 | }
90 | }
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/frontend/components/friends/friend_search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class FriendSearch extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.search = '';
7 |
8 | this.state = {
9 | search: ''
10 | };
11 |
12 | this.handleSubmit = this.handleSubmit.bind(this);
13 | this.renderSearchResults = this.renderSearchResults.bind(this);
14 | }
15 |
16 | handleSubmit(e) {
17 | e.preventDefault();
18 | this.search = this.state.search;
19 |
20 | this.props.requestAllPotentialFriends(this.state.search);
21 | }
22 |
23 | update(field) {
24 | return e => this.setState({
25 | [field]: e.currentTarget.value
26 | });
27 | }
28 |
29 | renderSearchResults() {
30 | if (this.props.potentialFriends.length > 0) {
31 | return (
32 |
33 | { this.props.potentialFriends.map((friend => {
34 | return (
35 | -
36 |
37 |
38 |
{ friend.first_name + " " + friend.last_name }
39 |
40 |
41 |
42 | );
43 | }))}
44 |
45 | );
46 | } else if (this.props.potentialFriends.length === 0 && this.search) {
47 | return We couldn't find anything for {this.search}.
;
48 | }
49 | }
50 |
51 | render () {
52 |
53 | return (
54 |
55 |
56 | Find MapMyPath friends by first name, last name, or email:
57 |
66 |
67 | {this.renderSearchResults()}
68 |
69 | );
70 | }
71 | }
72 |
73 | export default FriendSearch;
74 |
--------------------------------------------------------------------------------
/docs/schema.md:
--------------------------------------------------------------------------------
1 | # Schema Information
2 |
3 | ## users
4 |
5 | | column name | data type | details |
6 | | ------------- | ------------- | ------------- |
7 | | id | integer | not null, primary key |
8 | | email | string | not null, indexed, unique |
9 | | first_name | string | not null, indexed |
10 | | last_name | string | not null, indexed |
11 | | password_digest | string | not null |
12 | | session_token | string | not null, indexed, unique |
13 | | img_url | string | |
14 | | created_at | timestamp | not null |
15 | | updated_at | timestamp | not null |
16 |
17 | ## routes
18 |
19 | | column name | data type | details |
20 | | ------------- | ---------- | ------------- |
21 | | id | integer | not null, primary key |
22 | | user_id | integer | not null, foreign key |
23 | | name | string | not null |
24 | | polyline | string | not null |
25 | | start_address | string | not null |
26 | | end_address | string | not null |
27 | | distance | float | not null |
28 | | done | boolean | not null, default: false |
29 | | created_at | timestamp | not null |
30 | | updated_at | timestamp | not null |
31 |
32 | ## relationships
33 |
34 | | column name | data type | details |
35 | | ------------- | ----------| ------------- |
36 | | id | integer | not null, primary key |
37 | | user_one_id | integer | not null, foreign key |
38 | | user_two_id | integer | not null, foreign key |
39 | | status | integer | not null |
40 | | action_user_id | integer | not null, foreign key |
41 | | created_at | timestamp | not null |
42 | | updated_at | timestamp | not null |
43 |
44 | ## comments
45 |
46 | | column name | data type | details |
47 | | ------------- | ----------- | ------------- |
48 | | id | integer | not null, primary key |
49 | | author_id | integer | not null, foreign key |
50 | | path_id | integer | not null, foreign key |
51 | | body | text | not null |
52 | | created_at | timestamp | not null |
53 | | updated_at | timestamp | not null |
54 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/path_show/comments.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CommentIndexItem from './comment_index_item';
3 |
4 | class Comments extends React.Component {
5 | constructor(props) {
6 | super(props);
7 |
8 | this.handleSubmit = this.handleSubmit.bind(this);
9 |
10 | this.state = {
11 | body: ""
12 | };
13 | }
14 |
15 | update(field) {
16 | return (e) => {
17 | this.setState({
18 | [field]: e.currentTarget.value
19 | });
20 | };
21 | }
22 |
23 | handleSubmit(e) {
24 | e.preventDefault();
25 | const comment = Object.assign({}, this.state, { path_id: this.props.path.id });
26 | this.setState(
27 | { body: "" }
28 | );
29 | this.props.createComment(comment);
30 | }
31 |
32 | renderError({ body }) {
33 | if(body) {
34 | return (
35 | Comment {body}.
36 | );
37 | }
38 | }
39 |
40 | render () {
41 | const {
42 | comments,
43 | createComment,
44 | deleteComment,
45 | currentUser,
46 | errors,
47 | path
48 | } = this.props;
49 |
50 | return (
51 |
52 |
Comments
53 |
66 | {this.renderError(errors)}
67 |
68 | {
69 | comments.map(comment => {
70 | return (
71 |
77 | );
78 | })
79 | }
80 |
81 |
82 | );
83 | }
84 | }
85 |
86 | export default Comments;
87 |
--------------------------------------------------------------------------------
/frontend/actions/path_actions.js:
--------------------------------------------------------------------------------
1 | import * as PathsAPIUtil from '../util/paths_api_util';
2 | import { clearErrors, addErrors } from './error_actions';
3 |
4 | export const RECEIVE_ALL_PATHS = 'RECEIVE_ALL_PATHS';
5 | export const RECEIVE_SINGLE_PATH = 'RECEIVE_SINGLE_PATH';
6 | export const RECEIVE_NEW_PATH = 'RECEIVE_NEW_PATH';
7 | export const REMOVE_PATH = 'REMOVE_PATH';
8 | export const CLEAR_PATHS = 'CLEAR_PATHS';
9 |
10 | export const receiveAllPaths = (paths) => ({
11 | type: RECEIVE_ALL_PATHS,
12 | paths
13 | });
14 |
15 | export const clearPaths = () => ({
16 | type: CLEAR_PATHS
17 | });
18 |
19 | export const receiveSinglePath = (path) => ({
20 | type: RECEIVE_SINGLE_PATH,
21 | path
22 | });
23 |
24 | export const receiveNewPath = (path) => ({
25 | type: RECEIVE_NEW_PATH,
26 | path
27 | });
28 |
29 | export const removePath = (id) => ({
30 | type: REMOVE_PATH,
31 | id
32 | });
33 |
34 | export const requestAllPaths = (type) => (dispatch) => {
35 | return (
36 | PathsAPIUtil.getAllPaths(type)
37 | .then(
38 | (paths) => {
39 | dispatch(receiveAllPaths(paths));
40 | }
41 | )
42 | );
43 | };
44 |
45 | export const requestSinglePath = (id) => (dispatch) => {
46 | return (
47 | PathsAPIUtil.getSinglePath(id)
48 | .then(
49 | (path) => {
50 | dispatch(receiveSinglePath(path));
51 | }
52 | )
53 | );
54 | };
55 |
56 |
57 | export const createPath = (path) => (dispatch) => {
58 | // dispatch(receiveNewPath(newPath));
59 | // not necessary since it will redirect to new page,
60 | // thus loading new component
61 |
62 | return (
63 | PathsAPIUtil.createPath(path)
64 | .then(
65 | (newPath) => {
66 | dispatch(clearErrors());
67 | return newPath;
68 | },
69 | (errors) => {
70 | dispatch(addErrors(errors));
71 | }
72 | )
73 | );
74 | };
75 |
76 | export const updatePath = (path) => (dispatch) => {
77 | return (
78 | PathsAPIUtil.updatePath(path)
79 | .then(
80 | (updatedPath) => {
81 | dispatch(receiveSinglePath(updatedPath));
82 | dispatch(clearErrors());
83 | },
84 | (errors) => {
85 | dispatch(addErrors(errors));
86 | }
87 | )
88 | );
89 | };
90 |
91 | export const deletePath = (id) => (dispatch) => {
92 | return (
93 | PathsAPIUtil.deletePath(id)
94 | .then(
95 | (deletedPath) => {
96 | dispatch(removePath(deletedPath.id));
97 | },
98 | (errors) => {
99 | dispatch(addErrors(errors));
100 | }
101 | )
102 | );
103 | };
104 |
--------------------------------------------------------------------------------
/frontend/components/frontpage/frontpage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Link } from 'react-router';
4 | import { login } from '../../actions/session_actions';
5 |
6 | class Frontpage extends React.Component {
7 | constructor(props) {
8 | super(props);
9 |
10 | this.loginGuest = this.loginGuest.bind(this);
11 | }
12 |
13 | loginGuest () {
14 | const guest = {
15 | email: "Guest",
16 | password: "Password"
17 | };
18 | this.props.login(guest).then(() => this.props.router.push('/home'));
19 | }
20 |
21 | render () {
22 | return(
23 |
24 |
25 |
26 | Make every mile count, join free today
27 | You pound the pavement, we provide the motivation.
28 | Plan each stride and learn from every route with MapMyPath.
29 |
30 |
37 |
38 |
Already a member?
39 |
Log in
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
Map your route
48 |
Know where you're going, see where you've been. Be bold and create your own runs.
49 |
50 |
51 |
52 |
Track your activity
53 |
Record your activity, track your speed, track your runs.
54 |
55 |
56 |
57 |
Share with friends
58 |
Add a social twist to your exercise routine. Get extra encourgement, cheer on your buddies.
59 |
60 |
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | const mapDispatchToProps = (dispatch) => {
69 | return {
70 | login: (guest) => dispatch(login(guest))
71 | };
72 | };
73 |
74 | export default connect(
75 | null,
76 | mapDispatchToProps
77 | )(Frontpage);
78 |
--------------------------------------------------------------------------------
/frontend/components/home/activity_feed/activity_index_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 | import { FormattedRelative } from 'react-intl';
4 |
5 | const ActivityIndexItem = ({ paths }) => {
6 | const activityRender = (activity) => {
7 | const map = `https://maps.googleapis.com/maps/api/staticmap?size=278x160&path=weight:3%7Ccolor:red%7Cenc:${ activity.polyline }&key=AIzaSyBygQhRnDSS9s1hu7jxsQMu3mwIU7Hd2N4`; if (activity.done) {
8 | return (
9 |
10 |
11 |
12 |
13 | { activity.user.name} ran { activity.distance } miles
14 |
15 |
16 |
17 |
18 |

19 |
20 |
21 |
22 |
distance
23 |
{ activity.distance } mi
24 |
25 |
26 |
27 |
28 |
29 | );
30 | } else {
31 | return (
32 |
33 |
34 |
35 |
36 | { activity.user.name} created the { activity.name } path
37 |
38 |
39 |
40 |
41 |
42 |
distance
43 |
{ activity.distance }
mi
44 |
45 |
46 |

47 |
48 |
49 |
50 |
51 |
52 | );
53 | }
54 | };
55 |
56 | return (
57 |
58 | {
59 | paths.map(activity => {
60 | return (activityRender(activity));
61 | })
62 | }
63 |
64 | );
65 | };
66 |
67 |
68 | export default ActivityIndexItem;
69 |
--------------------------------------------------------------------------------
/frontend/components/home/dashboard/dashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PendingPathsItem from './pending_paths_index_item';
3 | import CompletePathsItem from './complete_paths_index_item';
4 | import Profile from '../profile_container';
5 | import { convertSecondsToTime } from '../../../util/math_calculations';
6 |
7 | class Dashboard extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.totalDistance = this.totalDistance.bind(this);
12 | this.totalTime = this.totalTime.bind(this);
13 | }
14 |
15 | componentWillMount () {
16 | this.props.requestAllPaths();
17 | }
18 |
19 | componentWillUnmount () {
20 | this.props.clearPaths();
21 | }
22 |
23 | totalDistance () {
24 | if (this.props.completePaths.length > 0) {
25 | let result = 0;
26 | for (let i = 0; i < this.props.completePaths.length; i++) {
27 | result += this.props.completePaths[i].distance;
28 | }
29 | return result.toFixed(2);
30 | }
31 |
32 | return 0;
33 | }
34 |
35 | totalTime () {
36 | if (this.props.completePaths.length > 0) {
37 | let result = 0;
38 | for (let i = 0; i < this.props.completePaths.length; i++) {
39 | result += this.props.completePaths[i].duration;
40 | }
41 | return convertSecondsToTime(result);
42 | }
43 | return "00:00:00";
44 | }
45 |
46 | render () {
47 | return (
48 |
49 |
50 |
51 |
52 | life time stats
53 |
54 |
55 |
56 |
57 | Distance
58 | { this.totalDistance() }
59 | miles
60 |
61 |
62 | Time
63 | { this.totalTime() }
64 | hh : mm : ss
65 |
66 |
67 | Runs
68 | { this.props.completePaths.length }
69 | completed
70 |
71 |
72 |
73 |
Pending runs
74 |
75 |
76 |
77 |
Completed runs
78 |
79 |
80 |
81 |
82 |
83 | );
84 | }
85 | }
86 |
87 | //
88 | //
89 |
90 | export default Dashboard;
91 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/session_form/session_form.scss:
--------------------------------------------------------------------------------
1 | .auth-form-container {
2 | display: flex;
3 | justify-content: center;
4 | background: #f0f0f0;
5 | // height: 600px;
6 | border-bottom: 1px solid #d6d6d6;
7 |
8 | // * {
9 | // border: 1px solid black;
10 | // }
11 | .errors {
12 | font-size: 12px;
13 | color: rgba(255, 0, 0, 0.69);
14 | padding: 5px 0 3px 8px;
15 | }
16 |
17 | form {
18 | display: flex;
19 | flex-direction: column;
20 | position: relative;
21 | top: 5%;
22 | // height: 600px;
23 | width: 320px;
24 | margin: 40px 0 100px;
25 |
26 | }
27 |
28 | a.toggle-button {
29 | position: relative;
30 | padding: 10px 0;
31 | margin: 10px 0 0 0;
32 | top: 2px;
33 | text-transform: uppercase;
34 | font-family: sans-serif;
35 | font-size: 12px;
36 | color: #9297a3;
37 | font-weight: 500;
38 | text-align: right;
39 | transition-duration: .4s;
40 |
41 | &:hover {
42 | color: #6d717a;
43 | font-weight: 500
44 | }
45 | }
46 |
47 |
48 | .or-bar {
49 | display: flex;
50 | align-items: center;
51 | margin: 15px 0;
52 |
53 | .bar {
54 | display: block;
55 | height: 1px;
56 | flex: 1;
57 | border-bottom: 1px solid #c8c7cc
58 | }
59 |
60 | .or {
61 | text-transform: uppercase;
62 | font-size: 12px;
63 | padding: 0 15px;
64 | color: #c8c7cc;
65 | }
66 | }
67 |
68 | input[type="submit"], .guest-login {
69 | display: flex;
70 | justify-content: center;
71 | align-items: center;
72 | background: #97bf11;
73 | color: #fff;
74 | width: 100%;
75 | height: 24px;
76 | cursor: pointer;
77 | transition-duration: .4s;
78 | text-transform: uppercase;
79 | margin: 15px 0 5px 0;
80 | padding: 10px 0;
81 | font-size: 12px;
82 | letter-spacing: .8px;
83 | border-radius: 6px;
84 | font-weight: 600;
85 | &:hover {
86 | background-color: #94bb0f
87 | }
88 | }
89 |
90 | .guest-login {
91 | background-color: #3b5998;
92 | &:hover {
93 | background-color: #274178;
94 | }
95 | }
96 |
97 | .input-container {
98 | display: block;
99 | margin-bottom: 4px;
100 |
101 | height: 40px;
102 | margin: 6px 0;
103 | }
104 |
105 | .input-box {
106 | border: 1px solid #c8c7cc;
107 | display: block;
108 | width: 100%;
109 | padding: 20px 16px;
110 | height: 44px;
111 | box-sizing: border-box;
112 | font-size: 12px;
113 | font-weight: 100;
114 | &::-webkit-input-placeholder {
115 | color: #c8c7cc;
116 | }
117 | &:focus {
118 | box-shadow: 0px 0px 4px 1px rgba(64,128,255,1);
119 | background: #fff;
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage/activity/activity.scss:
--------------------------------------------------------------------------------
1 | .activities {
2 | display: flex;
3 | color: #444444;
4 |
5 | .activities-body {
6 | min-width: 664px;
7 |
8 | header {
9 | margin-top: 8px;
10 | padding-bottom: 27px;
11 | border-bottom: 1px solid #d1d0cf;
12 |
13 | h2 {
14 | text-transform: uppercase;
15 | letter-spacing: 1px;
16 | font-weight: lighter;
17 | margin-left: 5px;
18 | font-weight: lighter;
19 | }
20 | }
21 | }
22 |
23 | li {
24 | display: flex;
25 | margin-top: 30px;
26 |
27 | .avatar {
28 | width: 65px;
29 | height: 60px;
30 | background: #fff;
31 | border: 1px solid #cdcdcd;
32 | box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
33 | }
34 |
35 | .activity-body {
36 | width: 575px;
37 | margin-left: 20px;
38 | border: 1px solid #cdcdcd;
39 | font-weight: lighter;
40 | box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
41 |
42 | span {
43 | display: flex;
44 | justify-content: space-between;
45 | align-items: center;
46 | padding: 10px 18px;
47 | background: #fff;
48 |
49 | p {
50 | letter-spacing: .6px;
51 | font-size: 14px;
52 | font-weight: lighter;
53 | }
54 | .fa-activity {
55 | color: #e6e6e6;
56 | }
57 | }
58 |
59 | a {
60 | display: flex;
61 | justify-content: space-between;
62 | background: #f8f8f8;
63 | border-top: 1px solid #cdcdcd;
64 | border-bottom: 1px solid #cdcdcd;
65 |
66 | div:first-child{
67 | border-right: 1px solid #cdcdcd;
68 | }
69 |
70 | img {
71 | display: block;
72 | }
73 |
74 | .activity-details {
75 | width: 100%;
76 | text-align: center;
77 | margin: auto;
78 | padding: 26px 0;
79 | }
80 |
81 | .fa-road {
82 | color: #07b1c1;
83 | font-size: 25px;
84 | }
85 |
86 | p {
87 | text-transform: uppercase;
88 | font-size: 14px;
89 | margin-top: 10px;
90 | color: #a7a7a7;
91 | }
92 |
93 | .distance {
94 | font-size: 24px;
95 | color: #666666;
96 | }
97 | }
98 |
99 | .bottom {
100 | display: flex;
101 | align-items: center;
102 | justify-content: flex-end;
103 | color: #9d9d9d;
104 | font-size: 10px;
105 | height: 35px;
106 | background: #f8f8f8;
107 |
108 | span {
109 | margin: 0;
110 | padding:10px;
111 | background: #f8f8f8;
112 | }
113 |
114 | .fa-user-o {
115 | padding-right: 10px;
116 | border-right: 1px solid #9d9d9d;
117 | }
118 |
119 | }
120 | }
121 |
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :integer not null, primary key
6 | # email :string not null
7 | # first_name :string not null
8 | # last_name :string not null
9 | # password_digest :string not null
10 | # session_token :string not null
11 | # img_url :string default("https://s3.us-east-2.amazonaws.com/mapmyrun-dev/default_avatar.png")
12 | # created_at :datetime not null
13 | # updated_at :datetime not null
14 | #
15 |
16 | class User < ApplicationRecord
17 | validates :email, :session_token, presence: true, uniqueness: true
18 | validates :first_name, :last_name, presence: true
19 | validates :password, length: { minimum: 6, allow_nil: true }
20 |
21 | attr_reader :password
22 |
23 | after_initialize :ensure_session_token
24 |
25 | has_many :paths, dependent: :destroy
26 | has_many :comments,
27 | class_name: 'Comment',
28 | primary_key: :id,
29 | foreign_key: :author_id,
30 | dependent: :destroy
31 |
32 | def friends
33 | relationship_where_status(1)
34 | end
35 |
36 | def pending_friends
37 | relationship_where_status(0).where('relationships.action_user_id != :id', id: self.id)
38 | end
39 |
40 | def potential_friends(search)
41 | not_potential_friends = Relationship.where('user_one_id = :id or user_two_id = :id', id: self.id)
42 | ids = not_potential_friends.pluck(:user_one_id).concat(not_potential_friends.pluck(:user_two_id)).uniq
43 | return User.where.not(id: ids).where('first_name ILIKE :search OR last_name ILIKE :search OR email ILIKE :search', search: "%#{search}%")
44 | end
45 |
46 | def recent_activities
47 | ids = self.friends.ids
48 | ids.push(self.id).uniq!
49 | Path.where(user_id: ids).includes(:comments)
50 | end
51 |
52 | def self.find_by_credentials(email, password)
53 | user = User.find_by(email: email)
54 | return nil unless user && user.valid_password?(password)
55 | user
56 | end
57 |
58 | def password=(password)
59 | @password = password
60 | self.password_digest = BCrypt::Password.create(password)
61 | end
62 |
63 | def valid_password?(password)
64 | BCrypt::Password.new(self.password_digest).is_password?(password)
65 | end
66 |
67 | def reset_session_token!
68 | self.session_token = SecureRandom.urlsafe_base64(16)
69 | self.save!
70 | self.session_token
71 | end
72 |
73 | private
74 |
75 | def ensure_session_token
76 | self.session_token ||= SecureRandom.urlsafe_base64(16)
77 | end
78 |
79 | def relationship_where_status(status)
80 | User
81 | .joins('JOIN relationships ON relationships.user_one_id = users.id OR relationships.user_two_id = users.id')
82 | .where("relationships.status = :status", status: status)
83 | .where('relationships.user_one_id = :id OR relationships.user_two_id = :id', id: self.id)
84 | .where('users.id != :id', id: self.id)
85 | end
86 |
87 | end
88 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/masthead/masthead.scss:
--------------------------------------------------------------------------------
1 | .masthead-container {
2 | margin: 0 auto;
3 | border-bottom: 1px solid #d6d6d6;
4 | justify-content: center;
5 | background: #fff;
6 |
7 | .masthead {
8 | margin: auto;
9 | display: flex;
10 | height: 70px;
11 | justify-content: space-between;
12 | max-width: 950px;
13 | // padding: 0 50px;
14 | // padding-right: 100px;
15 | // left: 50%;
16 |
17 | .logo {
18 | display: flex;
19 | align-items: center;
20 | margin-bottom: 4px;
21 |
22 | .title {
23 | font-size: 26px;
24 | padding: 5px;
25 | font-weight: 700;
26 | color: #2f98d9;
27 | // letter-spacing: 1px;
28 |
29 | &:before {
30 | position: relative;
31 | top: 2px;
32 | padding: 0 15px 0 0;
33 | content: "∆";
34 | font-size: 30px;
35 | font-weight: 400;
36 | }
37 | }
38 | }
39 |
40 | .user-info {
41 | position: relative;
42 | .avatar {
43 | position: relative;
44 | height: 42px;
45 | width: 45px;
46 | border: .5px solid #d1d0cf;
47 | margin: 0 8px 0 0;
48 | }
49 |
50 | &:hover #drop-down-menu {
51 | display: flex;
52 | }
53 |
54 | #drop-down-menu {
55 | display: none;
56 | flex-direction: column;
57 | position: absolute;
58 | border: 1px solid #d3d6dc;
59 | border-top: 0;
60 | color: #9297a3;
61 | box-shadow: 0 2px 4px 0 rgba(34, 38, 40, 0.2);
62 | background-color: #fff;
63 | top: 70px;
64 | right: 6px;
65 | z-index: 100;
66 |
67 | .drop-down-button {
68 | text-align: center;
69 | padding: 16px 20px;
70 | font-size: 12px;
71 | border-top: 1px solid #d3d6dc;
72 | cursor: pointer;
73 | font-weight: 500;
74 | white-space: nowrap;
75 |
76 | &:hover {
77 | color: black;
78 | }
79 | }
80 | }
81 | }
82 |
83 |
84 | nav {
85 | display: flex;
86 | align-items: center;
87 |
88 | .login-button, .signup-button, .signout-button {
89 | position: relative;
90 | padding: 10px;
91 | margin: 5px;
92 | text-transform: uppercase;
93 | font-size: 13px;
94 | color: #9297a3;
95 | transition-duration: .4s;
96 | }
97 |
98 | .signout-button {
99 | margin: 0 0 0 10px;
100 | cursor: pointer;
101 | color: #fff;
102 | border-radius: 5px;
103 | background-color: #3b5998;
104 |
105 | &:hover {
106 | background-color: #274178;
107 | }
108 | }
109 |
110 | a.signup-button {
111 | background: #97bf11;
112 | color: #fff;
113 | border-radius: 5px;
114 | }
115 |
116 | a.signup-button:hover {
117 | background: #94bb0f;
118 | }
119 |
120 | a.login-button:hover {
121 | color: #6d717a;
122 | font-weight: 500
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/frontend/actions/friend_actions.js:
--------------------------------------------------------------------------------
1 | import * as FriendsAPIUtil from '../util/friends_api_util';
2 |
3 | export const RECEIVE_ALL_FRIENDS = 'RECEIVE_ALL_FRIENDS';
4 | export const RECEIVE_ALL_REQUESTS = 'RECEIVE_ALL_REQUESTS';
5 | export const RECEIVE_NEW_FRIEND = 'RECEIVE_NEW_FRIEND';
6 | export const REMOVE_FRIEND = 'REMOVE_FRIEND';
7 | export const REMOVE_REQUEST = 'DENY_REQUEST';
8 | export const RECEIVE_ALL_POTENTIAL_FRIENDS = 'RECEIVE_ALL_POTENTIAL_FRIENDS';
9 | export const REMOVE_FROM_SEARCH = 'REMOVE_FROM_SEARCH';
10 |
11 | export const receiveAllFriends = (friends) => ({
12 | type: RECEIVE_ALL_FRIENDS,
13 | friends
14 | });
15 |
16 | export const receiveAllRequests = (friends) => ({
17 | type: RECEIVE_ALL_REQUESTS,
18 | friends
19 | });
20 |
21 | export const receiveNewFriend = (friend) => ({
22 | type: RECEIVE_NEW_FRIEND,
23 | friend
24 | });
25 |
26 | export const removeFriend = (id) => ({
27 | type: REMOVE_FRIEND,
28 | id
29 | });
30 |
31 | export const removeRequest = (friend) => ({
32 | type: REMOVE_REQUEST,
33 | friend
34 | });
35 |
36 | export const receiveAllPotentialFriends = (friends) => ({
37 | type: RECEIVE_ALL_POTENTIAL_FRIENDS,
38 | friends
39 | });
40 |
41 | export const removeFromSearch = (friend) => ({
42 | type: REMOVE_FROM_SEARCH,
43 | friend
44 | });
45 |
46 | export const requestAllFriends = () => (dispatch) => {
47 | return (
48 | FriendsAPIUtil.getAllFriends()
49 | .then(
50 | (friends) => {
51 | dispatch(receiveAllFriends(friends));
52 | }
53 | )
54 | );
55 | };
56 |
57 | export const requestAllRequests = () => (dispatch) => {
58 | return (
59 | FriendsAPIUtil.getPendingFriends()
60 | .then(
61 | (friends) => {
62 | dispatch(receiveAllRequests(friends));
63 | }
64 | )
65 | );
66 | };
67 |
68 | export const addFriend = (id) => (dispatch) => {
69 | return (
70 | FriendsAPIUtil.addFriend(id)
71 | .then(
72 | (friend) => {
73 | dispatch(receiveNewFriend(friend));
74 | dispatch(removeRequest(friend));
75 | }
76 | )
77 | );
78 | };
79 |
80 | export const deleteFriend = (friendId) => (dispatch) => {
81 | return (
82 | FriendsAPIUtil.deleteFriend(friendId)
83 | .then(
84 | (deletedFriend) => {
85 | dispatch(removeFriend(deletedFriend.id));
86 | }
87 | )
88 | );
89 | };
90 |
91 | export const deleteRequest = (friendId) => (dispatch) => {
92 | return (
93 | FriendsAPIUtil.deleteRequest(friendId)
94 | .then(
95 | (deletedFriend) => {
96 | dispatch(removeRequest(deletedFriend));
97 | }
98 | )
99 | );
100 | };
101 |
102 | export const addRequest = (friendId) => (dispatch) => {
103 | return (
104 | FriendsAPIUtil.addRequest(friendId)
105 | .then(
106 | (friend) => {
107 | dispatch(removeFromSearch(friend));
108 | }
109 | )
110 | );
111 | };
112 |
113 | export const requestAllPotentialFriends = (search) => (dispatch) => {
114 | return (
115 | FriendsAPIUtil.getPotentialFriends(search)
116 | .then(
117 | (potentialFriends) => {
118 | dispatch(receiveAllPotentialFriends(potentialFriends));
119 | }
120 | )
121 | );
122 | };
123 |
--------------------------------------------------------------------------------
/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: MapMyPath_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: MapMyPath
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: MapMyPath_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: MapMyPath_production
84 | username: MapMyPath
85 | password: <%= ENV['MAPMYPATH_DATABASE_PASSWORD'] %>
86 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/paths_form/map_manager.js:
--------------------------------------------------------------------------------
1 | export default class MapManager {
2 | constructor(map, updateState) {
3 | this.map = map;
4 | this.updateState = updateState;
5 | this.pathMarkers = [];
6 |
7 | this.directionsService = new google.maps.DirectionsService;
8 | this.directionsRenderer = new google.maps.DirectionsRenderer(
9 | {
10 | map: this.map,
11 | preserveViewport: true,
12 | }
13 | );
14 |
15 | this.getDirections = this.getDirections.bind(this);
16 | this.renderDirections = this.renderDirections.bind(this);
17 | this.addMarker = this.addMarker.bind(this);
18 | this.clearDirections = this.clearDirections.bind(this);
19 | }
20 |
21 | addMarker(position) {
22 | const marker = new google.maps.Marker({
23 | position,
24 | map: this.map,
25 | label: 'A'
26 | });
27 |
28 | this.pathMarkers.push(marker);
29 |
30 | const markers = this.pathMarkers;
31 |
32 | if (markers.length > 1) {
33 | markers[0].setMap(null);
34 | marker.setMap(null);
35 | this.getDirections(markers);
36 | }
37 | }
38 |
39 | clearDirections() {
40 | this.directionsRenderer.setMap(null);
41 | this.directionsRenderer = new google.maps.DirectionsRenderer(
42 | {
43 | map: this.map,
44 | preserveViewport: true,
45 | }
46 | );
47 | this.updateState({
48 | distance: 0,
49 | polyline: '',
50 | start_address: 'N/A',
51 | end_address: 'N/A'
52 | });
53 | }
54 |
55 | undo() {
56 | let markers = this.pathMarkers;
57 |
58 | if (markers.length > 2) {
59 | this.pathMarkers.pop();
60 | this.getDirections(this.pathMarkers);
61 | } else if (markers.length === 2) {
62 | let marker = markers[0];
63 | // this.createMap(this.map.getCenter(), this.map.getZoom());
64 | this.clearDirections();
65 | this.pathMarkers = [];
66 | this.addMarker(marker.position);
67 | } else if (markers.length === 1) {
68 | this.clearDirections();
69 | this.pathMarkers[0].setMap(null);
70 | this.pathMarkers = [];
71 | }
72 | }
73 |
74 | getDirections(markers) {
75 | const waypoints = markers
76 | .slice(1, markers.length - 1)
77 | .map(marker => {
78 | return {
79 | location: marker.position,
80 | stopover: false
81 | };
82 | });
83 |
84 | this.directionsService.route(
85 | {
86 | origin: markers[0].position,
87 | destination: markers[markers.length - 1].position,
88 | waypoints: waypoints,
89 | optimizeWaypoints: true,
90 | travelMode: "WALKING"
91 | }, (response) => {
92 | this.renderDirections(response);
93 | }
94 | );
95 | }
96 |
97 | renderDirections(response) {
98 | const route = response.routes[0];
99 | const distanceInMiles = (route.legs[0].distance.value * .000622).toFixed(2);
100 | this.updateState({
101 | distance: distanceInMiles,
102 | polyline: route.overview_polyline,
103 | start_address: route.legs[0].start_address,
104 | end_address: route.legs[0].end_address
105 | });
106 |
107 | this.directionsRenderer.setDirections(response);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/frontend/components/root.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 |
4 |
5 | // react router
6 | import { Router, Route, IndexRoute, hashHistory, IndexRedirect } from 'react-router';
7 |
8 | // react components
9 | import App from './app';
10 | import SessionFormContainer from './session_form/session_form_container';
11 | import Frontpage from './frontpage/frontpage';
12 | import Home from './home/home';
13 |
14 | // paths
15 | import PathsMain from './home/paths/paths_main';
16 | import PathFormContainer from './home/paths/paths_form/path_form_container';
17 | import PathIndexContainer from './home/paths/paths_index/path_index_container';
18 | import PathShowContainer from './home/paths/path_show/path_show_container';
19 |
20 | // dashboard
21 | import DashboardContainer from './home/dashboard/dashboard_container';
22 |
23 | // activities
24 | import ActivityContainer from './home/activity_feed/activity_container';
25 |
26 | // friends
27 | import FriendsTabs from './friends/friends_tab';
28 | import AllFriendsContainer from './friends/all_friends_container';
29 | import FriendSearchContainer from './friends/friend_search_container';
30 |
31 | const Root = ({ store }) => {
32 | const _ensureLoggedIn = (nextState, replace) => {
33 | const currentUser = store.getState().session.currentUser;
34 | if (!currentUser) {
35 | replace('/');
36 | }
37 | };
38 |
39 | const _redirectIfLoggedIn = (nextState, replace) => {
40 | const currentUser = store.getState().session.currentUser;
41 | if (currentUser) {
42 | replace('/home');
43 | }
44 | };
45 |
46 | return (
47 |
48 |
49 |
50 |
53 |
54 |
58 |
62 |
63 |
67 |
68 |
69 |
72 |
73 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | );
97 | };
98 |
99 | export default Root;
100 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage/paths/path_update_form.scss:
--------------------------------------------------------------------------------
1 | .path-update-overlay {
2 | position: fixed;
3 | top: 0;
4 | bottom: 0;
5 | left: 0;
6 | right: 0;
7 | background-color: rgba(99, 99, 99, 0.8);
8 | z-index: 3;
9 |
10 | }
11 |
12 | .path-update-content {
13 | box-shadow: 0px 0px 8px 1px rgba(0,0,0,1);
14 | position: absolute;
15 | width: 350px;
16 | height: 450px;
17 | top: 15%;
18 | left: 40%;
19 | right: 50%;
20 | bottom: 50%;
21 | background-color: #fff;
22 |
23 | .path-update-form {
24 | width: 240px;
25 | height: 400px;
26 | margin: auto;
27 |
28 | form {
29 | margin-top: 25px;
30 |
31 | div {
32 | margin: 10px 0;
33 | }
34 | .duration-container {
35 | padding-bottom: 20px;
36 | border-bottom: 1px solid #d1d0cf;
37 | }
38 | }
39 |
40 | h3 {
41 | font-size: 26px;
42 | padding: 25px 0;
43 | border-bottom: 1px solid #d1d0cf;
44 | color: #444444;
45 | font-weight: 400;
46 | letter-spacing: 1px;
47 | text-align: center;
48 | }
49 |
50 | label {
51 | display: block;
52 | font-size: 12px;
53 | color: #8c8c8c;
54 | margin-bottom: 10px;
55 | font-weight: 600;
56 | }
57 |
58 | input[type='date'] {
59 | color: #108cc9;
60 | padding: 3px 0;
61 | font-size: 13px;
62 | transition: .2s;
63 | border: 1px solid #c3c2c2;
64 | border-radius: 3px;
65 | // width: 60px;
66 | &:focus {
67 | box-shadow: 0px 0px 4px 1px rgba(64,128,255,1);
68 | background: #fff;
69 | }
70 | }
71 |
72 | input[type='text'] {
73 | border: 1px solid #c3c2c2;
74 | border-radius: 3px;
75 | font-size: 12px;
76 | padding: 5px;
77 | width: 140px;
78 | height: 20px;
79 | &:focus {
80 | box-shadow: 0px 0px 4px 1px rgba(64,128,255,1);
81 | background: #fff;
82 | }
83 | }
84 |
85 | input[type='tel'] {
86 | margin: 2px;
87 | border: 1px solid #c3c2c2;
88 | padding: 5px;
89 | font-size: 12px;
90 | width: 25px;
91 | border-radius: 3px;
92 | transition: .2s;
93 | &:focus {
94 | box-shadow: 0px 0px 4px 1px rgba(64,128,255,1);
95 | background: #fff;
96 | }
97 | }
98 |
99 |
100 | input[name='hours'] {
101 | margin-left: 0;
102 | }
103 |
104 | .path-update-form-buttons {
105 | display: flex;
106 | align-items: center;
107 | margin-top: 20px;;
108 | // border: 1px solid black;
109 |
110 | input[type='submit'] {
111 | background: -webkit-gradient(
112 | linear, left top, left bottom,
113 | from(#b6b6b6),
114 | to(#a9a6a6));
115 | text-align: center;
116 | color: #fff;
117 | height: 20px;
118 | cursor: pointer;
119 | text-transform: uppercase;
120 | padding: 5px 10px;
121 | font-size: 11px;
122 | font-weight: 700;
123 | letter-spacing: 1.5px;
124 | border-radius: 5px;
125 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
126 |
127 | &:hover {
128 | background: -webkit-gradient(
129 | linear, left top, left bottom,
130 | from(#878585),
131 | to(#5e5e5e));
132 | }
133 | }
134 |
135 | p {
136 | font-size: 12px;
137 | font-weight: lighter;
138 | padding: 0 10px;
139 | }
140 |
141 |
142 | .cancel-button {
143 | text-align: center;
144 | font-size: 12px;
145 | color: #0076c0;
146 | cursor: pointer;
147 | }
148 |
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/path_show/path_update_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | convertTimeToSeconds,
4 | todaysDate
5 | } from '../../../../util/math_calculations';
6 |
7 | class PathUpdateForm extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.handleSubmit = this.handleSubmit.bind(this);
12 | this.setDuration = this.setDuration.bind(this);
13 |
14 | this.state = {
15 | description: "",
16 | done_date: ""
17 | };
18 |
19 | this.hours = 0;
20 | this.minutes = 0;
21 | this.seconds = 0;
22 | }
23 |
24 | componentDidMount () {
25 | this.setState(this.props.path);
26 | }
27 |
28 | handleSubmit(e) {
29 | e.preventDefault();
30 | const duration = convertTimeToSeconds(this.hours, this.minutes, this.seconds);
31 | const path = Object.assign({}, this.state, { duration }, { done: true });
32 | this.props.closeModal();
33 | this.props.updatePath(path);
34 | }
35 |
36 | setDuration(e) {
37 | const name = e.currentTarget.name;
38 | switch(name) {
39 | case 'hours':
40 | this.hours = parseInt(e.currentTarget.value);
41 | break;
42 | case 'minutes':
43 | this.minutes = parseInt(e.currentTarget.value);
44 | break;
45 | case 'seconds':
46 | this.seconds = parseInt(e.currentTarget.value);
47 | break;
48 | }
49 | }
50 |
51 | update(field) {
52 | return (e) => {
53 | this.setState({
54 | [field]: e.currentTarget.value
55 | });
56 | };
57 | }
58 |
59 | render () {
60 | return (
61 |
62 |
{this.props.path.name}
63 |
119 |
120 | );
121 | }
122 |
123 | }
124 |
125 | export default PathUpdateForm;
126 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage/dashboard/dashboard.scss:
--------------------------------------------------------------------------------
1 | .dashboard {
2 | display: flex;
3 | color: #444444;
4 |
5 |
6 | .dashboard-body {
7 | min-width: 664px;
8 |
9 | header {
10 | margin-top: 8px;
11 | padding-bottom: 27px;
12 | border-bottom: 1px solid #d1d0cf;
13 |
14 | h2 {
15 | text-transform: uppercase;
16 | letter-spacing: 1px;
17 | font-weight: lighter;
18 | margin-left: 5px;
19 | font-weight: lighter;
20 | }
21 | }
22 |
23 | .run-stats {
24 | display: flex;
25 | background: #f7f7f7;
26 | margin-top: 28px;
27 |
28 | span {
29 | text-align: center;
30 | padding: 10px 15px;
31 | border: 1px solid #c3c2c2;
32 | width: 100%;
33 | min-width: 40px;
34 |
35 | &:first-child {
36 | border-right: 0;
37 | }
38 |
39 | &:last-child {
40 | border-left: 0;
41 | }
42 |
43 | h3 {
44 | text-transform: uppercase;
45 | font-size: 14px;
46 | color: #adabab;
47 | font-weight: lighter;
48 | margin-top: 5px;
49 | }
50 |
51 | p {
52 | color: #616060;
53 | font-size: 20px;
54 | padding: 6px 0;
55 | }
56 |
57 | h5 {
58 | text-transform: lowercase;
59 | font-size: 14px;
60 | color: #adabab;
61 | font-weight: lighter;
62 | }
63 | }
64 | }
65 |
66 | h3 {
67 | margin-top: 25px;
68 | padding-bottom: 15px;
69 | border-bottom: 1px solid #d1d0cf;
70 | text-transform: uppercase;
71 | letter-spacing: 1px;
72 | font-weight: lighter;
73 | margin-left: 5px;
74 | font-weight: lighter;
75 | font-size: 22px;
76 | }
77 |
78 | .pending-runs {
79 |
80 | ul {
81 | display: flex;
82 | flex-wrap: wrap;
83 |
84 | li {
85 | position: relative;
86 | margin: 15px 12px;
87 | img {
88 | border: 1px solid #d1d0cf;
89 | display: block;
90 | }
91 |
92 | .map-details {
93 | width: 100px;
94 | background: #fff;
95 | font-size: 13px;
96 | position: absolute;
97 | top: 80px;
98 | left: 5px;
99 | opacity: .9;
100 | padding: 5px 8px;
101 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
102 |
103 | &:hover {
104 | opacity: 1;
105 | color: #2f98d9;
106 | }
107 |
108 | p {
109 | white-space: nowrap;
110 | overflow: hidden;
111 | text-overflow: ellipsis;
112 | }
113 | }
114 | }
115 | }
116 | }
117 |
118 | .complete-runs {
119 |
120 | li {
121 | padding: 10px;
122 | border-bottom: 1px solid #d1d0cf;
123 | display: flex;
124 | align-items: center;
125 | margin: 15px 0 10 0;
126 | img {
127 | border: 1px solid #d1d0cf;
128 | display: block;
129 | }
130 |
131 | .duration {
132 | border-left: 1px solid #d1d0cf;
133 | border-right: 1px solid #d1d0cf;
134 | }
135 |
136 | .stats {
137 | min-width: 130px;
138 | padding: 0 20px;
139 |
140 | h4 {
141 | font-weight: lighter;
142 | padding-bottom: 15px;
143 | color: #8e8e8e;
144 | }
145 | p {
146 | font-size: 22px;
147 | font-weight: 100;
148 | color: #696969
149 | }
150 | }
151 | }
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Disable serving static files from the `/public` folder by default since
18 | # Apache or NGINX already handles this.
19 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
20 |
21 | # Compress JavaScripts and CSS.
22 | config.assets.js_compressor = :uglifier
23 | # config.assets.css_compressor = :sass
24 |
25 | # Do not fallback to assets pipeline if a precompiled asset is missed.
26 | config.assets.compile = false
27 |
28 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
29 |
30 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
31 | # config.action_controller.asset_host = 'http://assets.example.com'
32 |
33 | # Specifies the header that your server uses for sending files.
34 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
35 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
36 |
37 | # Mount Action Cable outside main process or domain
38 | # config.action_cable.mount_path = nil
39 | # config.action_cable.url = 'wss://example.com/cable'
40 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
41 |
42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
43 | # config.force_ssl = true
44 |
45 | # Use the lowest log level to ensure availability of diagnostic information
46 | # when problems arise.
47 | config.log_level = :debug
48 |
49 | # Prepend all log lines with the following tags.
50 | config.log_tags = [ :request_id ]
51 |
52 | # Use a different cache store in production.
53 | # config.cache_store = :mem_cache_store
54 |
55 | # Use a real queuing backend for Active Job (and separate queues per environment)
56 | # config.active_job.queue_adapter = :resque
57 | # config.active_job.queue_name_prefix = "MapMyPath_#{Rails.env}"
58 | config.action_mailer.perform_caching = false
59 |
60 | # Ignore bad email addresses and do not raise email delivery errors.
61 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
62 | # config.action_mailer.raise_delivery_errors = false
63 |
64 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
65 | # the I18n.default_locale when a translation cannot be found).
66 | config.i18n.fallbacks = true
67 |
68 | # Send deprecation notices to registered listeners.
69 | config.active_support.deprecation = :notify
70 |
71 | # Use default logging formatter so that PID and timestamp are not suppressed.
72 | config.log_formatter = ::Logger::Formatter.new
73 |
74 | # Use a different logger for distributed setups.
75 | # require 'syslog/logger'
76 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
77 |
78 | if ENV["RAILS_LOG_TO_STDOUT"].present?
79 | logger = ActiveSupport::Logger.new(STDOUT)
80 | logger.formatter = config.log_formatter
81 | config.logger = ActiveSupport::TaggedLogging.new(logger)
82 | end
83 |
84 | # Do not dump schema after migrations.
85 | config.active_record.dump_schema_after_migration = false
86 | end
87 |
--------------------------------------------------------------------------------
/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: 20170428052457) do
14 |
15 | # These are extensions that must be enabled in order to support this database
16 | enable_extension "plpgsql"
17 |
18 | create_table "comments", force: :cascade do |t|
19 | t.integer "author_id", null: false
20 | t.integer "path_id", null: false
21 | t.text "body", null: false
22 | t.datetime "created_at", null: false
23 | t.datetime "updated_at", null: false
24 | end
25 |
26 | create_table "friends", force: :cascade do |t|
27 | t.integer "user_one", null: false
28 | t.integer "user_two", null: false
29 | t.datetime "created_at", null: false
30 | t.datetime "updated_at", null: false
31 | end
32 |
33 | create_table "paths", force: :cascade do |t|
34 | t.string "name", null: false
35 | t.text "polyline", null: false
36 | t.float "distance", null: false
37 | t.string "start_address", null: false
38 | t.string "end_address", null: false
39 | t.integer "duration", default: 0
40 | t.boolean "done", default: false
41 | t.date "done_date"
42 | t.integer "user_id", null: false
43 | t.datetime "created_at", null: false
44 | t.datetime "updated_at", null: false
45 | t.text "description", default: ""
46 | t.index ["user_id"], name: "index_paths_on_user_id", using: :btree
47 | end
48 |
49 | create_table "relationships", force: :cascade do |t|
50 | t.integer "user_one_id", null: false
51 | t.integer "user_two_id", null: false
52 | t.integer "status", null: false
53 | t.integer "action_user_id", null: false
54 | t.datetime "created_at", null: false
55 | t.datetime "updated_at", null: false
56 | t.index ["user_one_id", "user_two_id"], name: "index_relationships_on_user_one_id_and_user_two_id", using: :btree
57 | end
58 |
59 | create_table "users", force: :cascade do |t|
60 | t.string "email", null: false
61 | t.string "first_name", null: false
62 | t.string "last_name", null: false
63 | t.string "password_digest", null: false
64 | t.string "session_token", null: false
65 | t.string "img_url", default: "https://s3.us-east-2.amazonaws.com/mapmyrun-dev/default_avatar.png"
66 | t.datetime "created_at", null: false
67 | t.datetime "updated_at", null: false
68 | t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
69 | t.index ["first_name"], name: "index_users_on_first_name", using: :btree
70 | t.index ["last_name"], name: "index_users_on_last_name", using: :btree
71 | t.index ["session_token"], name: "index_users_on_session_token", unique: true, using: :btree
72 | end
73 |
74 | end
75 |
--------------------------------------------------------------------------------
/frontend/components/home/paths/paths_form/map.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MapManager from './map_manager';
3 | import Modal from 'react-modal';
4 |
5 | class Map extends React.Component {
6 | constructor(props) {
7 | super(props);
8 |
9 | this.state = {
10 | modalIsOpen: true
11 | };
12 |
13 | this.createMap = this.createMap.bind(this);
14 | this.clearMap = this.clearMap.bind(this);
15 | this.undoMap = this.undoMap.bind(this);
16 | }
17 |
18 | componentDidMount () {
19 | const initialPosition = { lat: 40.745209, lng: -73.993957 };
20 |
21 | navigator.geolocation.getCurrentPosition(
22 | (position) => {
23 | this.createMap(
24 | { lat: position.coords.latitude, lng: position.coords.longitude }
25 | );
26 | }, (error) => {
27 | this.createMap(initialPosition);
28 | }
29 | );
30 | }
31 |
32 | createMap (initialPosition, zoom) {
33 | zoom = zoom || 15;
34 |
35 | this.map = new google.maps.Map(this.refs.map, {
36 | center: initialPosition,
37 | zoom,
38 | rotateControl: true,
39 | mapTypeControlOptions:
40 | {
41 | style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
42 | },
43 | draggableCursor: 'crosshair',
44 | styles: [
45 | {
46 | "featureType": "administrative",
47 | "stylers": [
48 | {
49 | "visibility": "on"
50 | }
51 | ]
52 | },
53 | {
54 | "featureType": "landscape.man_made",
55 | "stylers": [
56 | {
57 | "visibility": "on"
58 | }
59 | ]
60 | },
61 | {
62 | "featureType": "landscape.natural",
63 | "stylers": [
64 | {
65 | "visibility": "on"
66 | }
67 | ]
68 | },
69 | {
70 | "featureType": "poi",
71 | "stylers": [
72 | {
73 | "saturation": -35
74 | },
75 | {
76 | "lightness": -15
77 | },
78 | {
79 | "visibility": "off"
80 | }
81 | ]
82 | },
83 | {
84 | "featureType": "poi.medical",
85 | "stylers": [
86 | {
87 | "visibility": "on"
88 | }
89 | ]
90 | },
91 | {
92 | "featureType": "poi.park",
93 | "stylers": [
94 | {
95 | "visibility": "simplified"
96 | }
97 | ]
98 | },
99 | {
100 | "featureType": "poi.school",
101 | "stylers": [
102 | {
103 | "visibility": "on"
104 | }
105 | ]
106 | },
107 | {
108 | "featureType": "transit",
109 | "stylers": [
110 | {
111 | "visibility": "off"
112 | }
113 | ]
114 | },
115 | {
116 | "featureType": "water",
117 | "stylers": [
118 | {
119 | "visibility": "on"
120 | }
121 | ]
122 | }
123 | ]
124 | });
125 |
126 | this.MapManager = new MapManager(this.map, this.props.setState);
127 |
128 | this.map.addListener('click', (e) => {
129 | this.MapManager.addMarker(e.latLng);
130 | });
131 |
132 | this.map.addListener('idle', (e) => {
133 | this.setState({ modalIsOpen: false });
134 | });
135 | }
136 |
137 | clearMap (e) {
138 | e.preventDefault();
139 |
140 | if (this.MapManager.pathMarkers.length > 0) {
141 | this.MapManager.clearDirections();
142 | this.MapManager.pathMarkers[0].setMap(null);
143 | this.MapManager.pathMarkers = [];
144 | }
145 | }
146 |
147 | undoMap (e) {
148 | e.preventDefault();
149 |
150 | this.MapManager.undo();
151 | }
152 |
153 | render () {
154 | return (
155 |
156 |
162 |
163 |
Loading mapping tools
164 |
165 |
Please wait
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | );
177 | }
178 | }
179 |
180 | export default Map;
181 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/frontpage/frontpage.scss:
--------------------------------------------------------------------------------
1 | .front-page {
2 |
3 | .frontpage-content {
4 | display: flex;
5 | justify-content: flex-end;
6 | min-width: 930px;
7 | background: image-url("frontpage-min.jpg");
8 | background-size: cover;
9 | padding: 55px;
10 | background-position-y: -164px;
11 | background-repeat: no-repeat;
12 |
13 | section {
14 | width: 490px;
15 | height: 380px;
16 | display: flex;
17 | flex-direction: column;
18 | position: relative;
19 | right: 15%;
20 | color: white;
21 | text-shadow: 1px 1px 7px rgba(150, 150, 150, 0.94);
22 |
23 | h1 {
24 | text-transform: uppercase;
25 | letter-spacing: .5px;
26 | font-size: 36px;
27 | line-height: 135%;
28 | font-weight: 600;
29 |
30 | }
31 |
32 | h3 {
33 | font-size: 15px;
34 | font-weight: 400;
35 | text-shadow: 2px 1px 9px rgba(150, 150, 150, 0.94);
36 | margin: 10px 0 0 5px;
37 | }
38 |
39 | .login-signup {
40 | display: flex;
41 | margin: 20px 0 0 0;
42 | font-family: sans-serif;
43 |
44 | .or {
45 | font-size: 15px;
46 | padding: 0 15px;
47 | color: white;
48 | text-shadow: 2px 1px 9px rgba(150, 150, 150, 0.94);
49 | display: flex;
50 | align-items: center;
51 | font-weight: lighter;
52 | }
53 |
54 | .guest-login, .signup-button {
55 | display: flex;
56 | align-items: center;
57 | color: #fff;
58 | height: 20px;
59 | cursor: pointer;
60 | transition-duration: .4s;
61 | text-transform: uppercase;
62 | padding: 10px 20px;
63 | font-size: 14px;
64 | font-weight: 700;
65 | letter-spacing: 1.5px;
66 | border-radius: 10px;
67 | box-shadow: -4px 6px 31px -8px rgba(0,0,0,0.75)
68 | }
69 |
70 | .guest-login {
71 | background: -webkit-gradient(
72 | linear, left top, left bottom,
73 | from(#4499db),
74 | to(#177ed7));
75 | &:hover {
76 | background: -webkit-gradient(
77 | linear, left top, left bottom,
78 | from(#3f92c6),
79 | to(#0074bc))
80 | }
81 | }
82 |
83 | .signup-button {
84 | background: -webkit-gradient(
85 | linear, left top, left bottom,
86 | from(#ff9628),
87 | to(#ff8714));
88 | &:hover {
89 | background: -webkit-gradient(
90 | linear, left top, left bottom,
91 | from(#f08a15),
92 | to(#e56e06))
93 | }
94 | }
95 | }
96 |
97 | .login {
98 | display: flex;
99 | justify-content: flex-end;
100 | position: relative;
101 | top: 180px;
102 | align-items: center;
103 | margin-right: 25px;
104 |
105 |
106 | p {
107 | font-weight: 600;
108 | font-size: 15px;
109 | letter-spacing: .5px;
110 | margin: 0 8px;
111 | }
112 |
113 | .login-button {
114 | display: flex;
115 | align-items: center;
116 | // justify-content: center;
117 | color: #fff;
118 | height: 14px;
119 | cursor: pointer;
120 | transition-duration: .4s;
121 | text-transform: uppercase;
122 | padding: 8px 20px;
123 | font-size: 12px;
124 | font-weight: 700;
125 | letter-spacing: .8px;
126 | border-radius: 5px;
127 | box-shadow: -4px 6px 31px -8px rgba(0,0,0,0.75);
128 |
129 | background: -webkit-gradient(
130 | linear, left top, left bottom,
131 | from(#5b5b5b),
132 | to(#454545));
133 |
134 | &:hover {
135 | background: -webkit-gradient(
136 | linear, left top, left bottom,
137 | from(#4f4f4f),
138 | to(#222222))
139 | }
140 | }
141 | }
142 | }
143 | }
144 |
145 | .footer {
146 | display: flex;
147 | justify-content: center;
148 | background: #f9f7f6;
149 | width: 100%;
150 |
151 | // * {
152 | // border: 1px solid black;
153 | // }
154 |
155 | .features {
156 | display: flex;
157 | // width: 800px;
158 | margin: 30px 0 40px 0;
159 | color: #999;
160 |
161 | &>div {
162 | display: flex;
163 | flex-direction: column;
164 | align-items: center;
165 | width: 220px;
166 | margin: 0 28px;
167 |
168 | .fa {
169 | color: #67b1ea;
170 | margin-bottom: 25px;
171 | }
172 |
173 | h3 {
174 | text-transform: uppercase;
175 | letter-spacing: .5px;
176 | font-weight: 800;
177 | }
178 |
179 | p {
180 | text-align: center;
181 | font-size: 13px;
182 | }
183 | }
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage/paths/path_form.scss:
--------------------------------------------------------------------------------
1 | .create-path {
2 | // color: #444444;
3 |
4 | header {
5 | display: flex;
6 | text-transform: uppercase;
7 | justify-content: space-between;
8 | align-items: center;
9 | padding-bottom: 20px;
10 | border-bottom: 1px solid #d1d0cf;
11 | letter-spacing: 1px;
12 |
13 | .back-button {
14 | font-size: 12px;
15 | color: white;
16 | padding: 15px 18px;
17 | transition: .6s;
18 | border-radius: 2px;
19 | font-weight: bold;
20 | background: -webkit-gradient(
21 | linear, left top, left bottom,
22 | from(#4499db),
23 | to(#177ed7));
24 | &:hover {
25 | background: -webkit-gradient(
26 | linear, left top, left bottom,
27 | from(#3f92c6),
28 | to(#0074bc))
29 | }
30 | }
31 | .path-form-header {
32 | margin-left: 5px;
33 | font-weight: lighter;
34 | }
35 | }
36 | }
37 |
38 | .path-main {
39 | color: #444444;
40 | display: flex;
41 | margin-top: 20px;
42 |
43 | .errors {
44 | font-size: 11px;
45 | color: rgba(255, 0, 0, 0.69);
46 | width: 180px;
47 | }
48 |
49 | .path-form {
50 |
51 | .path-details {
52 | padding: 10px 0px 0 0;
53 | display: flex;
54 | flex-direction: column;
55 | align-items: center;
56 |
57 | h3 {
58 | text-transform: uppercase;
59 | margin: 8px 0;
60 | padding-bottom: 4px;
61 | border-bottom: 1px solid gray;
62 | font-size: 18px;
63 | }
64 |
65 |
66 | li {
67 | margin: 5px;
68 |
69 | &:last-child {
70 | padding-bottom: 5px;
71 | border-bottom: 1px solid gray;
72 | }
73 | }
74 |
75 | p {
76 | width: 240px;
77 | vertical-align: middle;
78 | }
79 |
80 | .address {
81 | height: 38px;
82 | }
83 |
84 | h4 {
85 | margin: 8px 0;
86 | font-size: 14px;
87 | text-align: center;
88 | }
89 |
90 | p {
91 | text-align: center;
92 | font-size: 14px;
93 | }
94 | }
95 |
96 | .path-form-input {
97 | padding: 10px 0px 0 0;
98 | display: flex;
99 | flex-direction: column;
100 | align-items: center;
101 |
102 |
103 | h3 {
104 | text-transform: uppercase;
105 | margin: 8px 0;
106 | padding-bottom: 4px;
107 | border-bottom: 1px solid gray;
108 | font-size: 18px;
109 | }
110 |
111 | .name-input, .description-input {
112 | margin: 5px 0;
113 | border: 1px solid #c8c7cc;
114 | padding: 10px;
115 | font-size: 12px;
116 | width: 180px;
117 | border-radius: 3px;
118 | transition: .2s;
119 | &:focus {
120 | box-shadow: 0px 0px 4px 1px rgba(64,128,255,1);
121 | background: #fff;
122 | }
123 | }
124 |
125 | .select-container {
126 | border: 1px solid #c8c7cc;
127 | border-radius: 2px;
128 | width: 200px;
129 | margin: 5px 0;
130 |
131 | select {
132 | background: transparent;
133 | border: none;
134 | font-size: 12px;
135 | height: 35px;
136 | padding: 5px;
137 | width: 200px;
138 | // transition: .2s;
139 | &:focus {
140 | box-shadow: 0px 0px 4px 1px rgba(64,128,255,1);
141 | background: #fff;
142 | }
143 | }
144 | }
145 |
146 | input[type='date'] {
147 | width: 100%;
148 | padding: 8px 15px;
149 | font-size: 12px;
150 | text-transform: uppercase;
151 | transition: .2s;
152 | &:focus {
153 | box-shadow: 0px 0px 4px 1px rgba(64,128,255,1);
154 | background: #fff;
155 | }
156 | }
157 |
158 | #date-container {
159 | border: 1px solid #c8c7cc;
160 | border-radius: 3px;
161 | width: 200px;
162 | margin: 5px 0;
163 | }
164 |
165 | #duration-container {
166 | width: 200px;
167 | justify-content: space-between;
168 | align-items: center;
169 |
170 | input[type='tel'] {
171 | margin: 5px 0;
172 | border: 1px solid #c8c7cc;
173 | padding: 10px;
174 | font-size: 12px;
175 | width: 38px;
176 | border-radius: 3px;
177 | transition: .2s;
178 | &:focus {
179 | box-shadow: 0px 0px 4px 1px rgba(64,128,255,1);
180 | background: #fff;
181 | }
182 | }
183 | }
184 |
185 | input[type='submit'] {
186 | background: -webkit-gradient(
187 | linear, left top, left bottom,
188 | from(#ff9628),
189 | to(#ff8714));
190 | width: 160px;
191 | text-align: center;
192 | color: #fff;
193 | height: 20px;
194 | cursor: pointer;
195 | text-transform: uppercase;
196 | padding: 5px 20px;
197 | font-size: 13px;
198 | font-weight: 700;
199 | letter-spacing: 1.5px;
200 | border-radius: 5px;
201 | margin-top: 10px;
202 |
203 | &:hover {
204 | background: -webkit-gradient(
205 | linear, left top, left bottom,
206 | from(#f08a15),
207 | to(#e56e06))
208 | }
209 | }
210 |
211 | .false {
212 | display: none;
213 | }
214 |
215 | .true {
216 | display: flex;
217 | }
218 | }
219 |
220 | }
221 | }
222 |
--------------------------------------------------------------------------------