├── log
└── .keep
├── tmp
└── .keep
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── public
├── favicon.ico
├── apple-touch-icon.png
├── apple-touch-icon-precomposed.png
├── images
│ └── favicon.ico
├── system
│ └── users
│ │ └── images
│ │ └── 000
│ │ └── 000
│ │ └── 004
│ │ └── original
│ │ └── girl-avatar.jpg
├── robots.txt
├── 500.html
├── 422.html
└── 404.html
├── test
├── helpers
│ └── .keep
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── like_test.rb
│ ├── follow_test.rb
│ ├── comment_test.rb
│ ├── post_test.rb
│ └── user_test.rb
├── controllers
│ ├── .keep
│ ├── users_controller_test.rb
│ ├── sessions_controller_test.rb
│ ├── api
│ │ ├── likes_controller_test.rb
│ │ ├── posts_controller_test.rb
│ │ ├── users_controller_test.rb
│ │ ├── comments_controller_test.rb
│ │ ├── essions_controller_test.rb
│ │ ├── follows_controller_test.rb
│ │ ├── sessions_controller_test.rb
│ │ └── search_results_controller_test.rb
│ ├── static_pages_controller_test.rb
│ └── static_pages_controller_controller_test.rb
├── fixtures
│ ├── .keep
│ ├── files
│ │ └── .keep
│ ├── likes.yml
│ ├── comments.yml
│ ├── follows.yml
│ ├── posts.yml
│ └── users.yml
├── integration
│ └── .keep
└── test_helper.rb
├── app
├── assets
│ ├── images
│ │ ├── .keep
│ │ ├── ny.jpg
│ │ ├── beach.jpg
│ │ ├── began.JPG
│ │ ├── budva.jpg
│ │ ├── dream.JPG
│ │ ├── ghost.jpg
│ │ ├── roma.JPG
│ │ ├── tutin.jpg
│ │ ├── bbridge.jpg
│ │ ├── bearded.jpg
│ │ ├── cactus.JPG
│ │ ├── coffee.JPG
│ │ ├── cordoba.JPG
│ │ ├── darkest.JPG
│ │ ├── gg-park.jpg
│ │ ├── giralda.JPG
│ │ ├── granada.JPG
│ │ ├── grand-c.JPG
│ │ ├── imagine.JPG
│ │ ├── jamaica.JPG
│ │ ├── m-woods.JPG
│ │ ├── search.png
│ │ ├── sedona.JPG
│ │ ├── seville.JPG
│ │ ├── spy-eye.JPG
│ │ ├── sunset.JPG
│ │ ├── IMG_2278.JPG
│ │ ├── al-hambra.jpg
│ │ ├── antelope.jpg
│ │ ├── avatar-1.png
│ │ ├── avatar-2.png
│ │ ├── avatar-3.png
│ │ ├── avatar-4.png
│ │ ├── avatar-5.png
│ │ ├── avatar-g2.png
│ │ ├── avatar-g3.png
│ │ ├── avatar-g5.png
│ │ ├── avatar-g6.png
│ │ ├── colorado.jpg
│ │ ├── desert-veg.JPG
│ │ ├── grand-c2.JPG
│ │ ├── homepage.png
│ │ ├── malaga-cat.JPG
│ │ ├── san-fran.jpg
│ │ ├── girl-avatar.jpg
│ │ ├── long-island.jpg
│ │ ├── malaga_ruins.JPG
│ │ ├── plaza-de-esp.JPG
│ │ ├── spring-bloom.JPG
│ │ └── readme
│ │ │ ├── follows.png
│ │ │ ├── search.png
│ │ │ ├── posts-feed.png
│ │ │ └── user-profile.png
│ ├── javascripts
│ │ ├── channels
│ │ │ └── .keep
│ │ ├── users.coffee
│ │ ├── api
│ │ │ ├── likes.coffee
│ │ │ ├── posts.coffee
│ │ │ ├── users.coffee
│ │ │ ├── comments.coffee
│ │ │ ├── essions.coffee
│ │ │ ├── follows.coffee
│ │ │ ├── sessions.coffee
│ │ │ └── search_results.coffee
│ │ ├── sessions.coffee
│ │ ├── static_pages.coffee
│ │ ├── static_pages_controller.coffee
│ │ ├── cable.js
│ │ └── application.js
│ ├── stylesheets
│ │ ├── base
│ │ │ ├── fonts.scss
│ │ │ ├── colors.scss
│ │ │ ├── gallery.scss
│ │ │ ├── reset.scss
│ │ │ └── layout.scss
│ │ ├── custom-fonts
│ │ │ ├── billabong.eot
│ │ │ └── billabong.ttf
│ │ ├── font-awesome
│ │ │ ├── fonts
│ │ │ │ ├── FontAwesome.otf
│ │ │ │ ├── fontawesome-webfont.eot
│ │ │ │ ├── fontawesome-webfont.ttf
│ │ │ │ ├── fontawesome-webfont.woff
│ │ │ │ └── fontawesome-webfont.woff2
│ │ │ ├── less
│ │ │ │ ├── screen-reader.less
│ │ │ │ ├── fixed-width.less
│ │ │ │ ├── larger.less
│ │ │ │ ├── list.less
│ │ │ │ ├── core.less
│ │ │ │ ├── stacked.less
│ │ │ │ ├── font-awesome.less
│ │ │ │ ├── bordered-pulled.less
│ │ │ │ ├── rotated-flipped.less
│ │ │ │ ├── path.less
│ │ │ │ ├── animated.less
│ │ │ │ └── mixins.less
│ │ │ ├── scss
│ │ │ │ ├── _fixed-width.scss
│ │ │ │ ├── _screen-reader.scss
│ │ │ │ ├── _larger.scss
│ │ │ │ ├── _list.scss
│ │ │ │ ├── _core.scss
│ │ │ │ ├── font-awesome.scss
│ │ │ │ ├── _stacked.scss
│ │ │ │ ├── _bordered-pulled.scss
│ │ │ │ ├── _rotated-flipped.scss
│ │ │ │ ├── _path.scss
│ │ │ │ ├── _animated.scss
│ │ │ │ └── _mixins.scss
│ │ │ └── HELP-US-OUT.txt
│ │ ├── users.scss
│ │ ├── sessions.scss
│ │ ├── api
│ │ │ ├── follows.scss
│ │ │ ├── likes.scss
│ │ │ ├── posts.scss
│ │ │ ├── users.scss
│ │ │ ├── comments.scss
│ │ │ ├── sessions.scss
│ │ │ └── search_results.scss
│ │ ├── static_pages.scss
│ │ ├── components
│ │ │ ├── _footer.scss
│ │ │ ├── _upload_post.scss
│ │ │ ├── _modal_post_item.scss
│ │ │ ├── _search.scss
│ │ │ ├── _main_content.scss
│ │ │ ├── _landing_page.scss
│ │ │ └── _edit_profile_page.scss
│ │ └── application.scss
│ └── config
│ │ └── manifest.js
├── models
│ ├── concerns
│ │ └── .keep
│ ├── application_record.rb
│ ├── like.rb
│ ├── comment.rb
│ ├── follow.rb
│ ├── post.rb
│ └── user.rb
├── controllers
│ ├── concerns
│ │ └── .keep
│ ├── static_pages_controller.rb
│ ├── api
│ │ ├── search_results_controller.rb
│ │ ├── likes_controller.rb
│ │ ├── comments_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── follows_controller.rb
│ │ ├── users_controller.rb
│ │ └── posts_controller.rb
│ └── application_controller.rb
├── views
│ ├── layouts
│ │ ├── mailer.text.erb
│ │ ├── mailer.html.erb
│ │ └── application.html.erb
│ ├── api
│ │ ├── likes
│ │ │ ├── show.json.jbuilder
│ │ │ └── _like.json.jbuilder
│ │ ├── posts
│ │ │ ├── edit.json.jbuilder
│ │ │ ├── show.json.jbuilder
│ │ │ ├── index.json.jbuilder
│ │ │ └── _post.json.jbuilder
│ │ ├── comments
│ │ │ ├── show.json.jbuilder
│ │ │ └── _comment.json.jbuilder
│ │ ├── follows
│ │ │ ├── show.json.jbuilder
│ │ │ └── _follow.json.jbuilder
│ │ ├── users
│ │ │ ├── show.json.jbuilder
│ │ │ ├── index.json.jbuilder
│ │ │ └── _user.json.jbuilder
│ │ └── search_results
│ │ │ └── index.json.jbuilder
│ └── static_pages
│ │ └── root.html.erb
├── helpers
│ ├── users_helper.rb
│ ├── api
│ │ ├── likes_helper.rb
│ │ ├── posts_helper.rb
│ │ ├── users_helper.rb
│ │ ├── comments_helper.rb
│ │ ├── essions_helper.rb
│ │ ├── follows_helper.rb
│ │ ├── sessions_helper.rb
│ │ └── search_results_helper.rb
│ ├── sessions_helper.rb
│ ├── application_helper.rb
│ ├── static_pages_helper.rb
│ └── static_pages_controller_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
│ ├── Singin.png
│ ├── PhotoFeed.png
│ ├── PhotoView.png
│ └── UserProfile.png
├── api-endpoints.md
├── component-hierarchy.md
├── sample-state.md
├── schema.md
└── README.md
├── bin
├── bundle
├── rake
├── rails
├── spring
├── update
└── setup
├── config
├── spring.rb
├── boot.rb
├── environment.rb
├── cable.yml
├── 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
├── locales
│ └── en.yml
├── routes.rb
├── application.rb
├── secrets.yml
├── environments
│ ├── test.rb
│ └── development.rb
└── puma.rb
├── config.ru
├── frontend
├── util
│ ├── search_api_util.js
│ ├── comments_api_util.js
│ ├── likes_api_util.js
│ ├── util.js
│ ├── follows_api_util.js
│ ├── session_api_util.js
│ ├── users_api_util.js
│ ├── posts_api_util.js
│ └── modal_style.js
├── components
│ ├── shared
│ │ ├── spinner.jsx
│ │ ├── error_list.jsx
│ │ └── footer.jsx
│ ├── app.jsx
│ ├── posts
│ │ ├── posts.jsx
│ │ ├── upload_post_container.js
│ │ ├── modal
│ │ │ ├── post_item_modal_container.js
│ │ │ └── post_item_modal.jsx
│ │ ├── comment_item.jsx
│ │ ├── posts_feed_container.js
│ │ ├── posts_feed.jsx
│ │ ├── add_comment_form.jsx
│ │ └── upload_post.jsx
│ ├── user_profile
│ │ ├── user_profile_post_item.jsx
│ │ ├── edit_profile_container.js
│ │ ├── modal
│ │ │ └── follows_modal_container.js
│ │ ├── user_profile_posts.jsx
│ │ ├── user_profile_container.js
│ │ └── user_profile.jsx
│ ├── greeting
│ │ ├── greeting_container.js
│ │ └── greeting.jsx
│ ├── navigation
│ │ ├── nav_links_container.js
│ │ ├── nav_bar.jsx
│ │ └── nav_links.jsx
│ ├── follow
│ │ ├── follow_container.js
│ │ └── follow.jsx
│ ├── search
│ │ ├── search_container.js
│ │ ├── search_result_item.jsx
│ │ └── search.jsx
│ ├── session
│ │ └── auth_form_container.js
│ └── root.jsx
├── reducers
│ ├── selectors.js
│ ├── fetching_reducer.js
│ ├── search_reducer.js
│ ├── root_reducer.js
│ ├── session_reducer.js
│ ├── users_reducer.js
│ └── posts_reducer.js
├── store
│ └── store.js
├── travelgram.jsx
└── actions
│ ├── likes_actions.js
│ ├── search_actions.js
│ ├── comments_actions.js
│ ├── follows_actions.js
│ ├── users_actions.js
│ ├── session_actions.js
│ └── posts_actions.js
├── db
├── migrate
│ ├── 20170424000824_remove_post_url_constraint.rb
│ ├── 20170428020005_add_bio_and_website_to_user.rb
│ ├── 20170424151949_create_likes.rb
│ ├── 20170426151824_create_follows.rb
│ ├── 20170425173221_create_comments.rb
│ ├── 20170423172049_add_attachment_image_to_users.rb
│ ├── 20170423212138_add_attachment_image_to_posts.rb
│ ├── 20170420154504_create_posts.rb
│ └── 20170418161629_create_users.rb
└── schema.rb
├── Rakefile
├── .gitignore
├── webpack.config.js
├── package.json
├── Guardfile
├── Gemfile
└── README.md
/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/helpers/users_helper.rb:
--------------------------------------------------------------------------------
1 | module UsersHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/likes_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::LikesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/posts_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::PostsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/users_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::UsersHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/sessions_helper.rb:
--------------------------------------------------------------------------------
1 | module SessionsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/comments_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::CommentsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/essions_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::EssionsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/follows_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::FollowsHelper
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/views/api/likes/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'like', like: @like
2 |
--------------------------------------------------------------------------------
/app/views/api/posts/edit.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'post', post: @post
2 |
--------------------------------------------------------------------------------
/app/views/api/posts/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'post', post: @post
2 |
--------------------------------------------------------------------------------
/app/helpers/api/search_results_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::SearchResultsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/views/api/comments/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'comment', comment: @comment
2 |
--------------------------------------------------------------------------------
/app/views/api/follows/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'follow', follow: @follow
2 |
--------------------------------------------------------------------------------
/app/views/api/users/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "api/users/user", user: @user
2 |
--------------------------------------------------------------------------------
/app/helpers/static_pages_controller_helper.rb:
--------------------------------------------------------------------------------
1 | module StaticPagesControllerHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/assets/images/ny.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/ny.jpg
--------------------------------------------------------------------------------
/app/assets/images/beach.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/beach.jpg
--------------------------------------------------------------------------------
/app/assets/images/began.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/began.JPG
--------------------------------------------------------------------------------
/app/assets/images/budva.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/budva.jpg
--------------------------------------------------------------------------------
/app/assets/images/dream.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/dream.JPG
--------------------------------------------------------------------------------
/app/assets/images/ghost.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/ghost.jpg
--------------------------------------------------------------------------------
/app/assets/images/roma.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/roma.JPG
--------------------------------------------------------------------------------
/app/assets/images/tutin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/tutin.jpg
--------------------------------------------------------------------------------
/docs/wireframes/Login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/docs/wireframes/Login.png
--------------------------------------------------------------------------------
/docs/wireframes/Singin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/docs/wireframes/Singin.png
--------------------------------------------------------------------------------
/public/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/public/images/favicon.ico
--------------------------------------------------------------------------------
/app/assets/images/bbridge.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/bbridge.jpg
--------------------------------------------------------------------------------
/app/assets/images/bearded.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/bearded.jpg
--------------------------------------------------------------------------------
/app/assets/images/cactus.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/cactus.JPG
--------------------------------------------------------------------------------
/app/assets/images/coffee.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/coffee.JPG
--------------------------------------------------------------------------------
/app/assets/images/cordoba.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/cordoba.JPG
--------------------------------------------------------------------------------
/app/assets/images/darkest.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/darkest.JPG
--------------------------------------------------------------------------------
/app/assets/images/gg-park.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/gg-park.jpg
--------------------------------------------------------------------------------
/app/assets/images/giralda.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/giralda.JPG
--------------------------------------------------------------------------------
/app/assets/images/granada.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/granada.JPG
--------------------------------------------------------------------------------
/app/assets/images/grand-c.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/grand-c.JPG
--------------------------------------------------------------------------------
/app/assets/images/imagine.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/imagine.JPG
--------------------------------------------------------------------------------
/app/assets/images/jamaica.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/jamaica.JPG
--------------------------------------------------------------------------------
/app/assets/images/m-woods.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/m-woods.JPG
--------------------------------------------------------------------------------
/app/assets/images/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/search.png
--------------------------------------------------------------------------------
/app/assets/images/sedona.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/sedona.JPG
--------------------------------------------------------------------------------
/app/assets/images/seville.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/seville.JPG
--------------------------------------------------------------------------------
/app/assets/images/spy-eye.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/spy-eye.JPG
--------------------------------------------------------------------------------
/app/assets/images/sunset.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/sunset.JPG
--------------------------------------------------------------------------------
/docs/wireframes/PhotoFeed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/docs/wireframes/PhotoFeed.png
--------------------------------------------------------------------------------
/docs/wireframes/PhotoView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/docs/wireframes/PhotoView.png
--------------------------------------------------------------------------------
/app/assets/images/IMG_2278.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/IMG_2278.JPG
--------------------------------------------------------------------------------
/app/assets/images/al-hambra.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/al-hambra.jpg
--------------------------------------------------------------------------------
/app/assets/images/antelope.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/antelope.jpg
--------------------------------------------------------------------------------
/app/assets/images/avatar-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/avatar-1.png
--------------------------------------------------------------------------------
/app/assets/images/avatar-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/avatar-2.png
--------------------------------------------------------------------------------
/app/assets/images/avatar-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/avatar-3.png
--------------------------------------------------------------------------------
/app/assets/images/avatar-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/avatar-4.png
--------------------------------------------------------------------------------
/app/assets/images/avatar-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/avatar-5.png
--------------------------------------------------------------------------------
/app/assets/images/avatar-g2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/avatar-g2.png
--------------------------------------------------------------------------------
/app/assets/images/avatar-g3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/avatar-g3.png
--------------------------------------------------------------------------------
/app/assets/images/avatar-g5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/avatar-g5.png
--------------------------------------------------------------------------------
/app/assets/images/avatar-g6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/avatar-g6.png
--------------------------------------------------------------------------------
/app/assets/images/colorado.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/colorado.jpg
--------------------------------------------------------------------------------
/app/assets/images/desert-veg.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/desert-veg.JPG
--------------------------------------------------------------------------------
/app/assets/images/grand-c2.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/grand-c2.JPG
--------------------------------------------------------------------------------
/app/assets/images/homepage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/homepage.png
--------------------------------------------------------------------------------
/app/assets/images/malaga-cat.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/malaga-cat.JPG
--------------------------------------------------------------------------------
/app/assets/images/san-fran.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/san-fran.jpg
--------------------------------------------------------------------------------
/docs/wireframes/UserProfile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/docs/wireframes/UserProfile.png
--------------------------------------------------------------------------------
/app/assets/images/girl-avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/girl-avatar.jpg
--------------------------------------------------------------------------------
/app/assets/images/long-island.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/long-island.jpg
--------------------------------------------------------------------------------
/app/assets/images/malaga_ruins.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/malaga_ruins.JPG
--------------------------------------------------------------------------------
/app/assets/images/plaza-de-esp.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/plaza-de-esp.JPG
--------------------------------------------------------------------------------
/app/assets/images/spring-bloom.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/spring-bloom.JPG
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/app/assets/images/readme/follows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/readme/follows.png
--------------------------------------------------------------------------------
/app/assets/images/readme/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/readme/search.png
--------------------------------------------------------------------------------
/app/views/api/likes/_like.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! like, :id
2 | json.user_id like.user.id
3 | json.post_id like.post.id
4 |
--------------------------------------------------------------------------------
/app/assets/images/readme/posts-feed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/readme/posts-feed.png
--------------------------------------------------------------------------------
/app/assets/images/readme/user-profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/images/readme/user-profile.png
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/fonts.scss:
--------------------------------------------------------------------------------
1 | $serif: georgia,"times new roman",times,serif;
2 | $sans-serif: arial, helvetica, sans-serif;
3 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/static_pages_controller.rb:
--------------------------------------------------------------------------------
1 | class StaticPagesController < ApplicationController
2 |
3 | def root
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/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/assets/stylesheets/custom-fonts/billabong.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/stylesheets/custom-fonts/billabong.eot
--------------------------------------------------------------------------------
/app/assets/stylesheets/custom-fonts/billabong.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/stylesheets/custom-fonts/billabong.ttf
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/app/views/api/posts/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @posts.each do |post|
2 | json.set! post.id do
3 | json.partial! 'post', post: post
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/api/users/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @users.each do |user|
2 | json.set! user.id do
3 | json.partial! 'user', user: user
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ).each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/stylesheets/font-awesome/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
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 |
--------------------------------------------------------------------------------
/public/system/users/images/000/000/004/original/girl-avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/public/system/users/images/000/000/004/original/girl-avatar.jpg
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s-sanel/travelgram/HEAD/app/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/app/views/api/comments/_comment.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! comment, :id, :body
2 | json.user_id comment.user.id
3 | json.username comment.user.username
4 | json.post_id comment.post.id
5 |
--------------------------------------------------------------------------------
/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: '_Travelgram_session'
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/screen-reader.less:
--------------------------------------------------------------------------------
1 | // Screen Readers
2 | // -------------------------
3 |
4 | .sr-only { .sr-only(); }
5 | .sr-only-focusable { .sr-only-focusable(); }
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/fixed-width.less:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .@{fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/_fixed-width.scss:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .#{$fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/util/search_api_util.js:
--------------------------------------------------------------------------------
1 | export const fetchSearchResults = (query) => {
2 | return $.ajax({
3 | method: "GET",
4 | url: "api/search_results",
5 | data: { query }
6 | });
7 | };
8 |
--------------------------------------------------------------------------------
/test/controllers/users_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class UsersControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/sessions_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class SessionsControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/_screen-reader.scss:
--------------------------------------------------------------------------------
1 | // Screen Readers
2 | // -------------------------
3 |
4 | .sr-only { @include sr-only(); }
5 | .sr-only-focusable { @include sr-only-focusable(); }
6 |
--------------------------------------------------------------------------------
/test/controllers/api/likes_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::LikesControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/posts_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::PostsControllerTest < 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 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/users.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the Users controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/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/essions_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::EssionsControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/follows_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::FollowsControllerTest < 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/sessions.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the 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/views/api/search_results/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @results do |user|
2 | json.id user.id
3 | json.name user.name
4 | json.username user.username
5 | json.profile_photo asset_path(user.image.url)
6 | end
7 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/follows.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/follows 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/likes.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/likes 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/posts.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/posts 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/20170424000824_remove_post_url_constraint.rb:
--------------------------------------------------------------------------------
1 | class RemovePostUrlConstraint < ActiveRecord::Migration[5.0]
2 | def change
3 | remove_column :posts, :url
4 | add_column :posts, :url, :string
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/test/controllers/api/search_results_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::SearchResultsControllerTest < 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 |
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ApplicationController.renderer.defaults.merge!(
4 | # http_host: 'example.org',
5 | # https: false
6 | # )
7 |
--------------------------------------------------------------------------------
/db/migrate/20170428020005_add_bio_and_website_to_user.rb:
--------------------------------------------------------------------------------
1 | class AddBioAndWebsiteToUser < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :users, :bio, :string
4 | add_column :users, :website, :string
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/test/controllers/static_pages_controller_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class StaticPagesControllerControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/search_results.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the Api::SearchResults controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/frontend/components/shared/spinner.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function Spinner() {
4 | return (
5 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/app/assets/javascripts/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 |
--------------------------------------------------------------------------------
/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/likes.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/posts.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/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/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/essions.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/follows.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/search_results.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/20170424151949_create_likes.rb:
--------------------------------------------------------------------------------
1 | class CreateLikes < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :likes do |t|
4 | t.integer :user_id
5 | t.integer :post_id
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/frontend/components/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Footer from './shared/footer'
3 |
4 | const App = ({ children }) => (
5 |
9 | );
10 |
11 | export default App;
12 |
--------------------------------------------------------------------------------
/app/assets/javascripts/static_pages_controller.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/controllers/api/search_results_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::SearchResultsController < ApplicationController
2 |
3 | def index
4 | query = params[:query].downcase
5 | @results = User.where("LOWER(username) LIKE ?", "%#{query}%").limit(10)
6 | end
7 |
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20170426151824_create_follows.rb:
--------------------------------------------------------------------------------
1 | class CreateFollows < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :follows do |t|
4 | t.integer :follower_id
5 | t.integer :following_id
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/db/migrate/20170425173221_create_comments.rb:
--------------------------------------------------------------------------------
1 | class CreateComments < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :comments do |t|
4 | t.string :body
5 | t.integer :user_id
6 | t.integer :post_id
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/db/migrate/20170423172049_add_attachment_image_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddAttachmentImageToUsers < ActiveRecord::Migration
2 | def self.up
3 | change_table :users do |t|
4 | t.attachment :image
5 | end
6 | end
7 |
8 | def self.down
9 | remove_attachment :users, :image
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20170423212138_add_attachment_image_to_posts.rb:
--------------------------------------------------------------------------------
1 | class AddAttachmentImageToPosts < ActiveRecord::Migration
2 | def self.up
3 | change_table :posts do |t|
4 | t.attachment :image
5 | end
6 | end
7 |
8 | def self.down
9 | remove_attachment :posts, :image
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/views/static_pages/root.html.erb:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/db/migrate/20170420154504_create_posts.rb:
--------------------------------------------------------------------------------
1 | class CreatePosts < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :posts do |t|
4 | t.string :url, null: false
5 | t.string :description
6 | t.integer :user_id, null: false
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/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/components/shared/error_list.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function ErrorList({ errors }) {
4 | if (!errors) return null;
5 |
6 | const errorItems = errors.map(error =>
7 | { error }
8 | );
9 |
10 | return (
11 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/util/comments_api_util.js:
--------------------------------------------------------------------------------
1 | export const createComment = (post_id, body) => {
2 | return $.ajax({
3 | method: "POST",
4 | url: "api/comments",
5 | data: {comment : {post_id, body}}
6 | });
7 | };
8 |
9 | export const deleteComment = (id) => {
10 | return $.ajax({
11 | method: "DELETE",
12 | url: `/api/comments/${id}`
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/HELP-US-OUT.txt:
--------------------------------------------------------------------------------
1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project,
2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome,
3 | comprehensive icon sets or copy and paste your own.
4 |
5 | Please. Check it out.
6 |
7 | -Dave Gandy
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/colors.scss:
--------------------------------------------------------------------------------
1 | $white: #ffffff;
2 | $red: #ff0000;
3 |
4 | $lightest-gray: #fafafa;
5 | $light-gray: #e6e6e6;
6 | $gray: #939393;
7 | $dark-gray: #666666;
8 | $darkest-gray: #333333;
9 | $separator-gray: #c7c7c7;
10 |
11 | $input-border: #dbdbdb;
12 | $insta-blue : #3897f0;
13 | $insta-blue-disabled: #b4daff;
14 | $footer-text: #003569;
15 |
16 | $gray-link: #262626;
17 |
--------------------------------------------------------------------------------
/frontend/util/likes_api_util.js:
--------------------------------------------------------------------------------
1 | export const createLike = (post_id) => {
2 | return $.ajax({
3 | method: "POST",
4 | url: "/api/likes/",
5 | data: {like: { post_id: post_id }}
6 | });
7 | };
8 |
9 | export const deleteLike = (post_id) => {
10 | return $.ajax({
11 | method: "DELETE",
12 | url: `/api/likes/${post_id}`,
13 | data: {like: { post_id }}
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/util/util.js:
--------------------------------------------------------------------------------
1 | export const getIndex = (arr, obj) => {
2 | let ind;
3 | arr.forEach((el, index) => {
4 | if (el.id === obj.id) {
5 | ind = index;
6 | }
7 | });
8 | return ind;
9 | };
10 |
11 |
12 | export const getIndexById = (arr, id) => {
13 | let ind;
14 | arr.forEach((el, index) => {
15 | if (el.id === id) {
16 | ind = index;
17 | }
18 | });
19 | return ind;
20 | };
21 |
--------------------------------------------------------------------------------
/db/migrate/20170418161629_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration
2 | def change
3 | create_table :users do |t|
4 | t.string :username, null: false
5 | t.string :email, null: false
6 | t.string :password_digest, null:false
7 | t.string :session_token, null:false
8 | t.string :name
9 | t.string :profile_photo
10 |
11 | t.timestamps
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/frontend/reducers/selectors.js:
--------------------------------------------------------------------------------
1 | import { values } from 'lodash';
2 |
3 | export const selectUserPosts = (state, userId) => {
4 | let arrayPosts = Object.values(state.posts);
5 | const foundPosts = arrayPosts.filter(post => {
6 | // return post.user.id == userId;
7 | return post.user_id == userId;
8 | } );
9 | return foundPosts || {};
10 | };
11 |
12 | export const selectAllUsers = (state) => values(state.users);
13 |
--------------------------------------------------------------------------------
/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the rails generate channel command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/app/models/like.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: likes
4 | #
5 | # id :integer not null, primary key
6 | # user_id :integer
7 | # post_id :integer
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class Like < ApplicationRecord
13 |
14 | validates :user_id, :post_id, presence: true
15 |
16 | belongs_to :user
17 | belongs_to :post
18 | end
19 |
--------------------------------------------------------------------------------
/test/models/like_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: likes
4 | #
5 | # id :integer not null, primary key
6 | # user_id :integer
7 | # post_id :integer
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | require 'test_helper'
13 |
14 | class LikeTest < ActiveSupport::TestCase
15 | # test "the truth" do
16 | # assert true
17 | # end
18 | end
19 |
--------------------------------------------------------------------------------
/frontend/reducers/fetching_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_ALL_POSTS, RECEIVE_POST, FETCH_POSTS, FETCH_POST } from '../actions/posts_actions';
2 |
3 | export default function fetchingReducer(state = false, action) {
4 | switch (action.type) {
5 | case FETCH_POSTS:
6 | case FETCH_POST:
7 | return true;
8 | case RECEIVE_ALL_POSTS:
9 | case RECEIVE_POST:
10 | return false;
11 | default:
12 | return state;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/frontend/components/posts/posts.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NavBar from '../navigation/nav_bar';
3 | import PostsFeedContainer from './posts_feed_container';
4 | import PostsFeed from './posts_feed';
5 |
6 | const Posts = ({ children }) => (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 |
16 | export default Posts;
17 |
--------------------------------------------------------------------------------
/frontend/util/follows_api_util.js:
--------------------------------------------------------------------------------
1 | export const createFollow = (follower_id, following_id) => {
2 | return $.ajax({
3 | method: "POST",
4 | url: "api/follows",
5 | data: {follow : {follower_id, following_id}}
6 | });
7 | };
8 |
9 | export const deleteFollow = (follower_id, following_id) => {
10 | return $.ajax({
11 | method: "GET",
12 | url: "api/follows/delete",
13 | data: {follow : {follower_id, following_id}}
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/test/models/follow_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: follows
4 | #
5 | # id :integer not null, primary key
6 | # follower_id :integer
7 | # following_id :integer
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | require 'test_helper'
13 |
14 | class FollowTest < ActiveSupport::TestCase
15 | # test "the truth" do
16 | # assert true
17 | # end
18 | end
19 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/larger.less:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .@{fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .@{fa-css-prefix}-2x { font-size: 2em; }
11 | .@{fa-css-prefix}-3x { font-size: 3em; }
12 | .@{fa-css-prefix}-4x { font-size: 4em; }
13 | .@{fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/frontend/util/session_api_util.js:
--------------------------------------------------------------------------------
1 | export const signup = (user) => {
2 | return $.ajax({
3 | method: 'POST',
4 | url: '/api/users',
5 | data: user
6 | });
7 | };
8 |
9 | export const login = (user) => {
10 | return $.ajax({
11 | method: 'POST',
12 | url: '/api/session',
13 | data: user
14 | });
15 | };
16 |
17 | export const logout = () => {
18 | return $.ajax({
19 | method: 'DELETE',
20 | url: '/api/session',
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/_larger.scss:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .#{$fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .#{$fa-css-prefix}-2x { font-size: 2em; }
11 | .#{$fa-css-prefix}-3x { font-size: 3em; }
12 | .#{$fa-css-prefix}-4x { font-size: 4em; }
13 | .#{$fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/test/models/comment_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: comments
4 | #
5 | # id :integer not null, primary key
6 | # body :string
7 | # user_id :integer
8 | # post_id :integer
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 |
--------------------------------------------------------------------------------
/app/models/comment.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: comments
4 | #
5 | # id :integer not null, primary key
6 | # body :string
7 | # user_id :integer
8 | # post_id :integer
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 |
13 | class Comment < ApplicationRecord
14 | validates :body, :user_id, :post_id, presence: true
15 |
16 | belongs_to :user
17 | belongs_to :post
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/test/fixtures/likes.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: likes
4 | #
5 | # id :integer not null, primary key
6 | # user_id :integer
7 | # post_id :integer
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
13 |
14 | one:
15 | user_id: 1
16 | post_id: 1
17 |
18 | two:
19 | user_id: 1
20 | post_id: 1
21 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/list.less:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: @fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .@{fa-css-prefix}-li {
11 | position: absolute;
12 | left: -@fa-li-width;
13 | width: @fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.@{fa-css-prefix}-lg {
17 | left: (-@fa-li-width + (4em / 14));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/_list.scss:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: $fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .#{$fa-css-prefix}-li {
11 | position: absolute;
12 | left: -$fa-li-width;
13 | width: $fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.#{$fa-css-prefix}-lg {
17 | left: -$fa-li-width + (4em / 14);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/components/user_profile/user_profile_post_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter} from 'react-router';
3 | import PostItemModalContainer from '../posts/modal/post_item_modal_container';
4 |
5 | class UserProfilePostItem extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render(){
11 | return(
12 |
13 | );
14 | }
15 | }
16 |
17 | export default withRouter(UserProfilePostItem);
18 |
--------------------------------------------------------------------------------
/frontend/components/greeting/greeting_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import { logout } from "../../actions/session_actions";
3 | import Greeting from './greeting';
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | currentUser: state.session.currentUser
8 | };
9 | };
10 |
11 | const mapDispatchToProps = (dispatch) => ({
12 | logout: () => dispatch(logout())
13 | });
14 |
15 | export default connect (
16 | mapStateToProps,
17 | mapDispatchToProps
18 | )(Greeting);
19 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/core.less:
--------------------------------------------------------------------------------
1 | // Base Class Definition
2 | // -------------------------
3 |
4 | .@{fa-css-prefix} {
5 | display: inline-block;
6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Travelgram
6 | <%= csrf_meta_tags %>
7 |
8 |
9 |
10 | <%= stylesheet_link_tag 'application', media: 'all' %>
11 | <%= javascript_include_tag 'application' %>
12 |
13 |
14 |
15 | <%= yield %>
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/_core.scss:
--------------------------------------------------------------------------------
1 | // Base Class Definition
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix} {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/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/reducers/search_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_SEARCH_RESULTS, REMOVE_SEARCH_RESULTS } from '../actions/search_actions';
2 | import merge from 'lodash/merge';
3 |
4 | const _nullSearchResults = [];
5 |
6 | const SearchResultsReducer = (oldState = [], action) => {
7 | switch(action.type){
8 | case RECEIVE_SEARCH_RESULTS:
9 | return action.searchResults;
10 | case REMOVE_SEARCH_RESULTS:
11 | return _nullSearchResults;
12 | default:
13 | return oldState;
14 | }
15 | };
16 |
17 | export default SearchResultsReducer;
18 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/font-awesome.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables";
7 | @import "mixins";
8 | @import "path";
9 | @import "core";
10 | @import "larger";
11 | @import "fixed-width";
12 | @import "list";
13 | @import "bordered-pulled";
14 | @import "animated";
15 | @import "rotated-flipped";
16 | @import "stacked";
17 | @import "icons";
18 | @import "screen-reader";
19 |
--------------------------------------------------------------------------------
/frontend/util/users_api_util.js:
--------------------------------------------------------------------------------
1 | export const fetchUsers = () => {
2 | return $.ajax({
3 | method: "GET",
4 | url: "/api/users"
5 | });
6 | };
7 |
8 | export const fetchUser = (id) => {
9 | return $.ajax({
10 | method: "GET",
11 | url: `/api/users/${id}`
12 | });
13 | };
14 |
15 | export const updateUser = (user) => {
16 | return $.ajax({
17 | method: "PATCH",
18 | url: `/api/users/${user.get('user[id]')}`,
19 | dataType: 'json',
20 | contentType: false,
21 | processData: false,
22 | data: user
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/test/fixtures/comments.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: comments
4 | #
5 | # id :integer not null, primary key
6 | # body :string
7 | # user_id :integer
8 | # post_id :integer
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 | one:
16 | body: MyString
17 | user_id: 1
18 | post_id: 1
19 |
20 | two:
21 | body: MyString
22 | user_id: 1
23 | post_id: 1
24 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/components/posts/upload_post_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import { fetchPosts, createPost } from '../../actions/posts_actions';
3 | import UploadPost from './upload_post';
4 |
5 | const mapStateToProps = (state) => {
6 | return {
7 | currentUser: state.session.currentUser,
8 | user: state.user
9 | };
10 | };
11 |
12 | const mapDispatchToProps = (dispatch) => ({
13 | createPost: (post) => dispatch(createPost(post))
14 | });
15 |
16 | export default connect(
17 | mapStateToProps,
18 | mapDispatchToProps
19 | )(UploadPost);
20 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" }
12 | if spring
13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
14 | gem 'spring', spring.version
15 | require 'spring/binstub'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/frontend/store/store.js:
--------------------------------------------------------------------------------
1 | import {createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from '../reducers/root_reducer';
4 | const middlewares = [thunk];
5 |
6 | if (process.env.NODE_ENV !== 'production') {
7 | const { createLogger } = require('redux-logger');
8 | middlewares.push(createLogger());
9 | }
10 |
11 | const configureStore = (preloadedState = {}) => (
12 | createStore(
13 | rootReducer,
14 | preloadedState,
15 | applyMiddleware(...middlewares)
16 | )
17 | );
18 |
19 | export default configureStore;
20 |
--------------------------------------------------------------------------------
/frontend/components/navigation/nav_links_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import { logout } from "../../actions/session_actions";
3 | import NavLinks from './nav_links';
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | currentUser: state.session.currentUser,
8 | user: state.user
9 | };
10 | };
11 |
12 | const mapDispatchToProps = (dispatch) => {
13 | return ({
14 | logout: () => dispatch(logout())
15 | });
16 | };
17 |
18 | export default connect (
19 | mapStateToProps,
20 | mapDispatchToProps
21 | )(NavLinks);
22 |
--------------------------------------------------------------------------------
/frontend/reducers/root_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import SessionReducer from './session_reducer';
3 | import PostsReducer from './posts_reducer';
4 | import UsersReducer from './users_reducer';
5 | import FetchingReducer from './fetching_reducer';
6 | import SearchResultsReducer from './search_reducer';
7 |
8 | const rootReducer = combineReducers({
9 | session: SessionReducer,
10 | posts: PostsReducer,
11 | user: UsersReducer,
12 | searchResults: SearchResultsReducer,
13 | fetching: FetchingReducer
14 | });
15 |
16 | export default rootReducer;
17 |
--------------------------------------------------------------------------------
/app/controllers/api/likes_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::LikesController < ApplicationController
2 |
3 | def create
4 | @like = current_user.likes.new(like_params)
5 | if @like.save
6 | render "api/likes/show"
7 | else
8 | render json: @like.errors, status: 422
9 | end
10 | end
11 |
12 | def destroy
13 | @like = current_user.likes.find_by(post_id: params[:like][:post_id])
14 | @like.destroy
15 | render json: @like
16 | end
17 |
18 | private
19 | def like_params
20 | params.require(:like).permit(:user_id, :post_id)
21 | end
22 |
23 | end
24 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/stacked.less:
--------------------------------------------------------------------------------
1 | // Stacked Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-stack {
5 | position: relative;
6 | display: inline-block;
7 | width: 2em;
8 | height: 2em;
9 | line-height: 2em;
10 | vertical-align: middle;
11 | }
12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
13 | position: absolute;
14 | left: 0;
15 | width: 100%;
16 | text-align: center;
17 | }
18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; }
19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; }
20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; }
21 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/_stacked.scss:
--------------------------------------------------------------------------------
1 | // Stacked Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-stack {
5 | position: relative;
6 | display: inline-block;
7 | width: 2em;
8 | height: 2em;
9 | line-height: 2em;
10 | vertical-align: middle;
11 | }
12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x {
13 | position: absolute;
14 | left: 0;
15 | width: 100%;
16 | text-align: center;
17 | }
18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; }
19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; }
20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; }
21 |
--------------------------------------------------------------------------------
/app/controllers/api/comments_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::CommentsController < ApplicationController
2 |
3 |
4 | def create
5 | @comment = current_user.comments.new(comment_params)
6 | if @comment.save
7 | render "api/comments/show"
8 | else
9 | render json: @comment.errors, status: 422
10 | end
11 | end
12 |
13 | def destroy
14 | @comment = current_user.comments.find(params[:id])
15 | @comment.destroy
16 | render json: @comment
17 | end
18 |
19 | private
20 | def comment_params
21 | params.require(:comment).permit(:user_id, :post_id, :body)
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/font-awesome.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables.less";
7 | @import "mixins.less";
8 | @import "path.less";
9 | @import "core.less";
10 | @import "larger.less";
11 | @import "fixed-width.less";
12 | @import "list.less";
13 | @import "bordered-pulled.less";
14 | @import "animated.less";
15 | @import "rotated-flipped.less";
16 | @import "stacked.less";
17 | @import "icons.less";
18 | @import "screen-reader.less";
19 |
--------------------------------------------------------------------------------
/app/views/api/users/_user.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! user, :id, :username, :name, :email, :bio, :website
2 | json.profile_photo asset_path(user.image.url)
3 |
4 | # json.posts_c user.posts
5 |
6 | json.followers user.follows_as_followee.each do |follow|
7 | json.extract! follow.follower, :id, :username
8 | json.profile_photo asset_path(follow.follower.image.url)
9 | json.follow_id follow.id
10 | end
11 |
12 | json.followees user.follows_as_follower.each do |follow|
13 | json.extract! follow.followee, :id, :username
14 | json.profile_photo asset_path(follow.followee.image.url)
15 | json.follow_id follow.id
16 | end
17 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_footer.scss:
--------------------------------------------------------------------------------
1 | footer {
2 | display: flex;
3 | justify-content: space-between;
4 | align-items: center;
5 |
6 | }
7 |
8 | .footer-content {
9 | flex: 1;
10 | max-width: 1010px;
11 | margin: 0 auto;
12 | color: $gray-link;
13 | display: flex;
14 | justify-content: space-between;
15 | font-size: 12px;
16 | }
17 |
18 | .links {
19 | color: $footer-text;
20 | text-transform: uppercase;
21 | font-weight: 600px;
22 | }
23 |
24 | .mark {
25 | color: $gray;
26 | }
27 |
28 | .footer-nav {
29 | display: flex;
30 | }
31 |
32 | .footer-nav li {
33 | margin-right: 10px;
34 | }
35 |
--------------------------------------------------------------------------------
/app/models/follow.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: follows
4 | #
5 | # id :integer not null, primary key
6 | # follower_id :integer
7 | # following_id :integer
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class Follow < ApplicationRecord
13 | validates :follower, :followee, presence: true
14 |
15 | belongs_to :follower,
16 | class_name: :User,
17 | primary_key: :id,
18 | foreign_key: :follower_id
19 |
20 | belongs_to :followee,
21 | class_name: :User,
22 | primary_key: :id,
23 | foreign_key: :following_id
24 | end
25 |
--------------------------------------------------------------------------------
/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 | def current_user
7 | @currentUser ||= User.find_by_session_token(session[:session_token])
8 | end
9 |
10 | def logged_in?
11 | !!current_user
12 | end
13 |
14 | def login!(user)
15 | @current_user = user
16 | session[:session_token] = user.reset_session_token!
17 | end
18 |
19 | def logout!
20 | current_user.reset_session_token!
21 | @current_user = nil
22 | session[:session_token] = nil
23 | end
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/app/views/api/follows/_follow.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! follow, :id, :follower_id, :following_id
2 |
3 | json.followee follow.followee
4 | json.follower follow.follower
5 |
6 | # json.follower_id follow.follower.id
7 | # json.follower_username follow.follower.username
8 | # json.follower_photo asset_path(follow.follower.image.url)
9 | #
10 | # json.followee_id follow.followee.id
11 | # json.followee_username follow.followee.username
12 | # json.followee_photo asset_path(follow.followee.image.url)
13 |
14 | # json.extract! follow.follower, :id, :username
15 | # json.profile_photo asset_path(follow.follower.image.url)
16 | # json.follow_id follow.id
17 |
--------------------------------------------------------------------------------
/frontend/components/user_profile/edit_profile_container.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'react-redux';
2 | import { fetchUser, updateUser } from '../../actions/users_actions';
3 | import EditProfile from './edit_profile';
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | user: state.user,
8 | currentUser: state.session.currentUser
9 | };
10 | };
11 |
12 | const mapDispatchToProps = (dispatch, ownProps) => ({
13 | fetchUser: (id) => dispatch(fetchUser(id)),
14 | updateUser: (user) => dispatch(updateUser(user))
15 | });
16 |
17 | export default connect(
18 | mapStateToProps,
19 | mapDispatchToProps
20 | )(EditProfile);
21 |
--------------------------------------------------------------------------------
/frontend/components/user_profile/modal/follows_modal_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import { fetchUser } from '../../../actions/users_actions';
3 | import FollowsModal from './follows_modal';
4 |
5 | const mapStateToProps = (state) => ({
6 | currentUser: state.session.currentUser,
7 | user: state.user,
8 | followers: state.user.followers,
9 | followees: state.user.followees
10 | });
11 |
12 | const mapDispatchToProps = (dispatch) => {
13 | return ({
14 | fetchUser: (id) => dispatch(fetchUser(id))
15 | });
16 | };
17 |
18 | export default connect (
19 | mapStateToProps,
20 | mapDispatchToProps
21 | )(FollowsModal);
22 |
--------------------------------------------------------------------------------
/app/controllers/api/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::SessionsController < ApplicationController
2 |
3 |
4 | def create
5 | @user = User.find_by_credentials(
6 | params[:user][:username],
7 | params[:user][:password]
8 | )
9 |
10 | if @user
11 | login!(@user)
12 | render "api/users/show"
13 | else
14 | render json: { login: ["Invalid username/password"] }, status: 401
15 | end
16 | end
17 |
18 | def destroy
19 | @user = current_user
20 | if @user
21 | logout!
22 | render json: {}
23 | else
24 | render json: ["Nobody signed in"], status: 404
25 | end
26 | end
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/frontend/travelgram.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 | document.addEventListener('DOMContentLoaded', () => {
8 | let store;
9 | if (window.currentUser) {
10 | const preloadedState = { session: { currentUser: window.currentUser } };
11 | store = configureStore(preloadedState);
12 | } else {
13 | store = configureStore();
14 | }
15 | Modal.setAppElement(document.body);
16 | const root = document.getElementById('root');
17 | ReactDOM.render( , root);
18 | });
19 |
--------------------------------------------------------------------------------
/app/views/api/posts/_post.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! post, :id, :description
2 | json.url asset_path(post.image.url)
3 | json.url_large asset_path(post.image.url(:large))
4 | json.url_medium asset_path(post.image.url(:medium))
5 | json.created_ago post.created_ago
6 |
7 | json.user_id post.user.id
8 | json.username post.user.username
9 | json.user_profile_photo asset_path(post.user.image.url)
10 |
11 | json.likes post.likes
12 |
13 | json.comments do
14 | json.array! post.comments do |comment|
15 | json.extract! comment, :id, :body
16 | json.user_id comment.user.id
17 | json.username comment.user.username
18 | json.post_id comment.post.id
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/frontend/components/user_profile/user_profile_posts.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router';
3 | import UserProfilePostItem from './user_profile_post_item';
4 |
5 | class UserProfilePosts extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render(){
11 | return(
12 |
13 | {
14 | this.props.posts.reverse().map(post => (
15 |
18 | ))
19 | }
20 |
21 | );
22 | }
23 | }
24 |
25 | export default UserProfilePosts;
26 |
--------------------------------------------------------------------------------
/test/models/post_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: posts
4 | #
5 | # id :integer not null, primary key
6 | # description :string
7 | # user_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # image_file_name :string
11 | # image_content_type :string
12 | # image_file_size :integer
13 | # image_updated_at :datetime
14 | # url :string
15 | #
16 |
17 | require 'test_helper'
18 |
19 | class PostTest < ActiveSupport::TestCase
20 | # test "the truth" do
21 | # assert true
22 | # end
23 | end
24 |
--------------------------------------------------------------------------------
/frontend/reducers/session_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_CURRENT_USER, RECEIVE_ERRORS } from '../actions/session_actions';
2 | import { merge } from 'lodash/merge';
3 |
4 | let defaultState = { currentUser: null, errors: [] };
5 |
6 |
7 | const SessionReducer = (state = defaultState, action) => {
8 | switch (action.type) {
9 | case RECEIVE_CURRENT_USER:
10 | let currentUser = action.currentUser;
11 | return Object.assign({}, defaultState, {currentUser});
12 | case RECEIVE_ERRORS:
13 | let errors = action.errors;
14 | return Object.assign({}, defaultState, { errors } );
15 | default:
16 | return state;
17 | }
18 | };
19 |
20 | export default SessionReducer;
21 |
--------------------------------------------------------------------------------
/test/fixtures/follows.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: follows
4 | #
5 | # id :integer not null, primary key
6 | # follower_id :integer
7 | # following_id :integer
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
13 |
14 | # This model initially had no columns defined. If you add columns to the
15 | # model remove the '{}' from the fixture names and add the columns immediately
16 | # below each fixture, per the syntax in the comments below
17 | #
18 | one: {}
19 | # column: value
20 | #
21 | two: {}
22 | # column: value
23 |
--------------------------------------------------------------------------------
/frontend/components/follow/follow_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import { createFollow, deleteFollow } from "../../actions/follows_actions";
3 | import Follow from './follow';
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | currentUser: state.session.currentUser,
8 | user: state.user
9 | };
10 | };
11 |
12 | const mapDispatchToProps = (dispatch) => ({
13 | createFollow: (follower_id, following_id) => dispatch(createFollow(follower_id, following_id)),
14 | deleteFollow: (follower_id, following_id) => dispatch(deleteFollow(follower_id, following_id))
15 | });
16 |
17 | export default connect (
18 | mapStateToProps,
19 | mapDispatchToProps
20 | )(Follow);
21 |
--------------------------------------------------------------------------------
/frontend/actions/likes_actions.js:
--------------------------------------------------------------------------------
1 | import * as LikeApiUtil from "../util/likes_api_util";
2 | export const RECEIVE_LIKE = "RECEIVE_LIKE";
3 | export const REMOVE_LIKE = "REMOVE_LIKE";
4 |
5 | export const receiveLike = like => ({
6 | type: RECEIVE_LIKE,
7 | like
8 | });
9 |
10 | export const removeLike = like => ({
11 | type: REMOVE_LIKE,
12 | like
13 | });
14 |
15 | export const createLike = postId => dispatch => {
16 | return (
17 | LikeApiUtil.createLike(postId)
18 | .then( like => dispatch(receiveLike(like)))
19 | );
20 | };
21 |
22 | export const deleteLike = postId => dispatch => {
23 | return (
24 | LikeApiUtil.deleteLike(postId)
25 | .then( like => dispatch(removeLike(like)))
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore all logfiles and tempfiles.
11 | /log/*
12 | /tmp/*
13 | !/log/.keep
14 | !/tmp/.keep
15 |
16 | # Ignore Byebug command history file.
17 | .byebug_history
18 |
19 |
20 | node_modules/
21 | bundle.js
22 | bundle.js.map
23 | .byebug_history
24 | .DS_Store
25 | npm-debug.log
26 |
27 | # Ignore application configuration
28 | /config/application.yml
29 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/components/shared/footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Footer = ({ children }) => (
4 |
5 |
6 |
7 |
16 |
@ 2017 TRAVELGRAM
17 |
18 |
19 | );
20 |
21 | export default Footer;
22 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/bordered-pulled.less:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em @fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .@{fa-css-prefix}-pull-left { float: left; }
11 | .@{fa-css-prefix}-pull-right { float: right; }
12 |
13 | .@{fa-css-prefix} {
14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; }
15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; }
16 | }
17 |
18 | /* Deprecated as of 4.4.0 */
19 | .pull-right { float: right; }
20 | .pull-left { float: left; }
21 |
22 | .@{fa-css-prefix} {
23 | &.pull-left { margin-right: .3em; }
24 | &.pull-right { margin-left: .3em; }
25 | }
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
4 | root "static_pages#root"
5 |
6 | namespace :api, defaults: { format: :json } do
7 | resources :users, only: [:index, :create, :edit, :show, :update, :destroy]
8 | resource :session, only: [:create, :destroy, :show]
9 |
10 | resources :posts
11 | resources :likes, only: [:create, :destroy]
12 | resources :comments, only: [:create, :destroy]
13 | # comments nested under posts
14 |
15 | resources :follows, only: [:create, :destroy]
16 | get "follows/delete", to: "follows#delete"
17 |
18 | resources :search_results, only: [:index]
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/frontend/actions/search_actions.js:
--------------------------------------------------------------------------------
1 | import * as SearchAPIUtil from '../util/search_api_util';
2 |
3 | export const RECEIVE_SEARCH_RESULTS = "RECEIVE_SEARCH_RESULTS";
4 | export const REMOVE_SEARCH_RESULTS = "CLEAR_SEARCH_RESULTS";
5 |
6 | export const receiveSearchResults = (searchResults) => {
7 | return {
8 | type: RECEIVE_SEARCH_RESULTS,
9 | searchResults
10 | };
11 | };
12 |
13 | export const removeSearchResults = () => {
14 | return {
15 | type: REMOVE_SEARCH_RESULTS,
16 | };
17 | };
18 |
19 | export const fetchSearchResults = (query) => {
20 | return (dispatch) => {
21 | return SearchAPIUtil.fetchSearchResults(query)
22 | .then((search_results) => dispatch(receiveSearchResults(search_results)));
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/_bordered-pulled.scss:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em $fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .#{$fa-css-prefix}-pull-left { float: left; }
11 | .#{$fa-css-prefix}-pull-right { float: right; }
12 |
13 | .#{$fa-css-prefix} {
14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; }
15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; }
16 | }
17 |
18 | /* Deprecated as of 4.4.0 */
19 | .pull-right { float: right; }
20 | .pull-left { float: left; }
21 |
22 | .#{$fa-css-prefix} {
23 | &.pull-left { margin-right: .3em; }
24 | &.pull-right { margin-left: .3em; }
25 | }
26 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/rotated-flipped.less:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); }
5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
7 |
8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); }
10 |
11 | // Hook for IE8-9
12 | // -------------------------
13 |
14 | :root .@{fa-css-prefix}-rotate-90,
15 | :root .@{fa-css-prefix}-rotate-180,
16 | :root .@{fa-css-prefix}-rotate-270,
17 | :root .@{fa-css-prefix}-flip-horizontal,
18 | :root .@{fa-css-prefix}-flip-vertical {
19 | filter: none;
20 | }
21 |
--------------------------------------------------------------------------------
/app/controllers/api/follows_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::FollowsController < ApplicationController
2 |
3 | def create
4 | @follow = Follow.new(follow_params)
5 | if @follow.save
6 | render "api/follows/show"
7 | else
8 | render json: @follow.errors, status: 422
9 | end
10 | end
11 |
12 | def delete
13 | @follow = Follow.find_by(follower_id: follow_params[:follower_id], following_id: follow_params[:following_id])
14 | @follow.destroy
15 | render json: @follow
16 | end
17 |
18 | def destroy
19 | @follow = Follow.find(params[:id])
20 | @follow.destroy
21 | render json: @follow
22 | end
23 |
24 | private
25 | def follow_params
26 | params.require(:follow).permit(:follower_id, :following_id)
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/docs/api-endpoints.md:
--------------------------------------------------------------------------------
1 | # API Endpoints
2 |
3 | ## HTML API
4 |
5 | ### Root
6 |
7 | - `GET /` - loads React web app
8 |
9 | ## JSON API
10 |
11 | ### Users
12 |
13 | - `POST /api/users`
14 | - `PATCH /api/users/:id`
15 | - `GET /api/users`
16 | - `GET /api/users/:id`
17 |
18 | ### Session
19 |
20 | - `POST /api/session`
21 | - `DELETE /api/session`
22 |
23 | ### Posts
24 |
25 | - `GET /api/posts`
26 | - `POST /api/posts`
27 | - `GET /api/posts/:id`
28 | - `PATCH /api/posts/:id`
29 | - `DELETE /api/posts/:id`
30 |
31 | ### Likes
32 |
33 | - `POST /api/likes`
34 | - `DELETE /api/likes/:id`
35 |
36 | ### Comments
37 |
38 | - `POST /api/comments`
39 | - `DELETE /api/comments/:id`
40 |
41 | ### Follows
42 |
43 | - `POST /api/users/:userId/follows`
44 | - `DELETE /api/users/:userId/follows/:id`
45 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :integer not null, primary key
6 | # username :string not null
7 | # email :string not null
8 | # password_digest :string not null
9 | # session_token :string not null
10 | # name :string
11 | # profile_photo :string
12 | # created_at :datetime
13 | # updated_at :datetime
14 | # image_file_name :string
15 | # image_content_type :string
16 | # image_file_size :integer
17 | # image_updated_at :datetime
18 | #
19 |
20 | require 'test_helper'
21 |
22 | class UserTest < ActiveSupport::TestCase
23 | # test "the truth" do
24 | # assert true
25 | # end
26 | end
27 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/_rotated-flipped.scss:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); }
5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); }
6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); }
7 |
8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); }
9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); }
10 |
11 | // Hook for IE8-9
12 | // -------------------------
13 |
14 | :root .#{$fa-css-prefix}-rotate-90,
15 | :root .#{$fa-css-prefix}-rotate-180,
16 | :root .#{$fa-css-prefix}-rotate-270,
17 | :root .#{$fa-css-prefix}-flip-horizontal,
18 | :root .#{$fa-css-prefix}-flip-vertical {
19 | filter: none;
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/actions/comments_actions.js:
--------------------------------------------------------------------------------
1 | import * as CommentApiUtil from "../util/comments_api_util";
2 | export const RECEIVE_COMMENT = "RECEIVE_COMMENT";
3 | export const REMOVE_COMMENT = "REMOVE_COMMENT";
4 |
5 | export const receiveComment = comment => ({
6 | type: RECEIVE_COMMENT,
7 | comment
8 | });
9 |
10 | export const removeComment = comment => ({
11 | type: REMOVE_COMMENT,
12 | comment
13 | });
14 |
15 | export const createComment = (postId, body) => dispatch => {
16 | return (
17 | CommentApiUtil.createComment(postId, body)
18 | .then( comment => dispatch(receiveComment(comment)))
19 | );
20 | };
21 |
22 | export const deleteComment = id => dispatch => {
23 | return (
24 | CommentApiUtil.deleteComment(id)
25 | .then( comment => dispatch(removeComment(comment)))
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/frontend/components/search/search_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import Search from './search';
3 |
4 | import { fetchSearchResults, removeSearchResults } from '../../actions/search_actions';
5 | import { fetchUser } from '../../actions/users_actions';
6 |
7 | const mapStateToProps = (state) => {
8 | return {
9 | user: state.user,
10 | searchResults: state.searchResults
11 | };
12 | };
13 |
14 | const mapDispatchToProps = (dispatch) => {
15 | return{
16 | fetchSearchResults: (query) => dispatch(fetchSearchResults(query)),
17 | removeSearchResults: () => dispatch(removeSearchResults()),
18 | fetchUser: (id) => dispatch(fetchUser(id))
19 | };
20 | };
21 |
22 | export default connect(
23 | mapStateToProps,
24 | mapDispatchToProps
25 | )(Search);
26 |
--------------------------------------------------------------------------------
/frontend/util/posts_api_util.js:
--------------------------------------------------------------------------------
1 | export const fetchPosts = () => {
2 | return $.ajax({
3 | method: "GET",
4 | url: "/api/posts"
5 | });
6 | };
7 |
8 | export const fetchPost = (id) => {
9 | return $.ajax({
10 | method: "GET",
11 | url: `/api/posts/${id}`
12 | });
13 | };
14 |
15 | export const createPost = (post) => {
16 | return $.ajax({
17 | method: "POST",
18 | url: "/api/posts",
19 | dataType: 'json',
20 | contentType: false,
21 | processData: false,
22 | data: post
23 | });
24 | };
25 |
26 | export const updatePost = (post) => {
27 | return $.ajax({
28 | method: "PATCH",
29 | url: `/api/posts/${post.id}`,
30 | data: {post}
31 | });
32 | };
33 |
34 | export const deletePost = (id) => {
35 | return $.ajax({
36 | method: "DELETE",
37 | url: `/api/posts/${id}`
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/frontend/actions/follows_actions.js:
--------------------------------------------------------------------------------
1 | import * as FollowApiUtil from "../util/follows_api_util";
2 | export const RECEIVE_FOLLOW = "RECEIVE_FOLLOW";
3 | export const REMOVE_FOLLOW = "REMOVE_FOLLOW";
4 |
5 | export const receiveFollow = follow => ({
6 | type: RECEIVE_FOLLOW,
7 | follow
8 | });
9 |
10 | export const removeFollow = follow => ({
11 | type: REMOVE_FOLLOW,
12 | follow
13 | });
14 |
15 | export const createFollow = (follower_id, following_id) => dispatch => {
16 | return (
17 | FollowApiUtil.createFollow(follower_id, following_id)
18 | .then( follow => dispatch(receiveFollow(follow)))
19 | );
20 | };
21 |
22 | export const deleteFollow = (follower_id, following_id) => dispatch => {
23 | return (
24 | FollowApiUtil.deleteFollow(follower_id, following_id)
25 | .then( follow => dispatch(removeFollow(follow)))
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/path.less:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}');
7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'),
8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'),
9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'),
10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'),
11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg');
12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/scss/_path.scss:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}');
7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'),
9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/actions/users_actions.js:
--------------------------------------------------------------------------------
1 | export const RECEIVE_USERS = "RECEIVE_USERS";
2 | export const RECEIVE_USER = "RECEIVE_USER";
3 |
4 | import * as APIUtil from "../util/users_api_util";
5 |
6 | export const receiveAllUsers = (users) => {
7 | return {
8 | type: RECEIVE_USERS,
9 | users
10 | };
11 | };
12 |
13 | export const receiveUser = (user) => {
14 | return {
15 | type: RECEIVE_USER,
16 | user
17 | };
18 | };
19 |
20 | export const fetchUsers = () => dispatch => (
21 | APIUtil.fetchUsers()
22 | .then( (users) => dispatch(receiveAllUsers(users)))
23 | );
24 |
25 | export const fetchUser = user => dispatch => (
26 | APIUtil.fetchUser(user)
27 | .then( (user) => dispatch(receiveUser(user)))
28 | );
29 |
30 | export const updateUser = user => dispatch => (
31 | APIUtil.updateUser(user)
32 | .then( (user) => dispatch(receiveUser(user)))
33 | );
34 |
--------------------------------------------------------------------------------
/frontend/components/session/auth_form_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { login, logout, signup, receiveErrors } from '../../actions/session_actions';
3 | import AuthForm from './auth_form';
4 |
5 | const mapStateToProps = ({ session }) => ({
6 | loggedIn: Boolean(session.currentUser),
7 | errors: session.errors
8 | });
9 |
10 | const mapDispatchToProps = (dispatch, { location }) => {
11 | const formType = location.pathname.slice(1);
12 | const processForm = (formType == 'login') ? login : signup;
13 | return {
14 | processForm: user => dispatch(processForm(user)),
15 | clearErrors: () => dispatch(receiveErrors({})),
16 | demoLogin: () => dispatch(login({user: {username: "guest", password: "123456"}})),
17 | formType
18 | };
19 | };
20 |
21 | export default connect(
22 | mapStateToProps,
23 | mapDispatchToProps
24 | )(AuthForm);
25 |
--------------------------------------------------------------------------------
/frontend/components/user_profile/user_profile_container.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'react-redux';
2 | import { fetchPosts } from '../../actions/posts_actions';
3 | import { fetchUser, updateUser } from '../../actions/users_actions';
4 | import UserProfile from './user_profile';
5 | import { selectUserPosts } from '../../reducers/selectors';
6 |
7 | const mapStateToProps = (state, ownProps) => {
8 | return {
9 | posts: selectUserPosts(state, ownProps.params.user_id),
10 | user: state.user,
11 | currentUser: state.session.currentUser
12 | };
13 | };
14 |
15 | const mapDispatchToProps = (dispatch, ownProps) => ({
16 | fetchPosts: () => dispatch(fetchPosts()),
17 | fetchUser: (id) => dispatch(fetchUser(id)),
18 | updateUser: (user) => dispatch(updateUser(user))
19 | });
20 |
21 | export default connect(
22 | mapStateToProps,
23 | mapDispatchToProps
24 | )(UserProfile);
25 |
--------------------------------------------------------------------------------
/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 Travelgram
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 |
15 | config.paperclip_defaults = {
16 | :storage => :s3,
17 | :s3_credentials => {
18 | :bucket => ENV["s3_bucket"],
19 | :access_key_id => ENV["s3_access_key_id"],
20 | :secret_access_key => ENV["s3_secret_access_key"],
21 | :s3_region => ENV["s3_region"]
22 | }
23 | }
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/animated.less:
--------------------------------------------------------------------------------
1 | // Animated Icons
2 | // --------------------------
3 |
4 | .@{fa-css-prefix}-spin {
5 | -webkit-animation: fa-spin 2s infinite linear;
6 | animation: fa-spin 2s infinite linear;
7 | }
8 |
9 | .@{fa-css-prefix}-pulse {
10 | -webkit-animation: fa-spin 1s infinite steps(8);
11 | animation: fa-spin 1s infinite steps(8);
12 | }
13 |
14 | @-webkit-keyframes fa-spin {
15 | 0% {
16 | -webkit-transform: rotate(0deg);
17 | transform: rotate(0deg);
18 | }
19 | 100% {
20 | -webkit-transform: rotate(359deg);
21 | transform: rotate(359deg);
22 | }
23 | }
24 |
25 | @keyframes fa-spin {
26 | 0% {
27 | -webkit-transform: rotate(0deg);
28 | transform: rotate(0deg);
29 | }
30 | 100% {
31 | -webkit-transform: rotate(359deg);
32 | transform: rotate(359deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/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/assets/stylesheets/font-awesome/scss/_animated.scss:
--------------------------------------------------------------------------------
1 | // Spinning Icons
2 | // --------------------------
3 |
4 | .#{$fa-css-prefix}-spin {
5 | -webkit-animation: fa-spin 2s infinite linear;
6 | animation: fa-spin 2s infinite linear;
7 | }
8 |
9 | .#{$fa-css-prefix}-pulse {
10 | -webkit-animation: fa-spin 1s infinite steps(8);
11 | animation: fa-spin 1s infinite steps(8);
12 | }
13 |
14 | @-webkit-keyframes fa-spin {
15 | 0% {
16 | -webkit-transform: rotate(0deg);
17 | transform: rotate(0deg);
18 | }
19 | 100% {
20 | -webkit-transform: rotate(359deg);
21 | transform: rotate(359deg);
22 | }
23 | }
24 |
25 | @keyframes fa-spin {
26 | 0% {
27 | -webkit-transform: rotate(0deg);
28 | transform: rotate(0deg);
29 | }
30 | 100% {
31 | -webkit-transform: rotate(359deg);
32 | transform: rotate(359deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/test/fixtures/posts.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: posts
4 | #
5 | # id :integer not null, primary key
6 | # description :string
7 | # user_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # image_file_name :string
11 | # image_content_type :string
12 | # image_file_size :integer
13 | # image_updated_at :datetime
14 | # url :string
15 | #
16 |
17 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
18 |
19 | # This model initially had no columns defined. If you add columns to the
20 | # model remove the '{}' from the fixture names and add the columns immediately
21 | # below each fixture, per the syntax in the comments below
22 | #
23 | one: {}
24 | # column: value
25 | #
26 | two: {}
27 | # column: value
28 |
--------------------------------------------------------------------------------
/frontend/components/posts/modal/post_item_modal_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import { fetchPost } from '../../../actions/posts_actions';
3 | import { createLike, deleteLike } from '../../../actions/likes_actions';
4 | import { createComment, deleteComment } from '../../../actions/comments_actions';
5 | import PostItemModal from './post_item_modal';
6 |
7 | const mapStateToProps = (state) => ({
8 | currentUser: state.session.currentUser,
9 | user: state.user
10 | });
11 |
12 | const mapDispatchToProps = (dispatch) => {
13 | return ({
14 | createLike: (postId) => dispatch(createLike(postId)),
15 | deleteLike: (postId) => dispatch(deleteLike(postId)),
16 | createComment: (postId, body) => dispatch(createComment(postId, body)),
17 | deleteComment: id => dispatch(deleteComment(id))
18 | });
19 |
20 | };
21 |
22 | export default connect (
23 | mapStateToProps,
24 | mapDispatchToProps
25 | )(PostItemModal);
26 |
--------------------------------------------------------------------------------
/docs/component-hierarchy.md:
--------------------------------------------------------------------------------
1 | ## Component Hierarchy
2 |
3 | **App**
4 | + LoginForm
5 | + SignupForm
6 | + PhotosIndexContainer
7 |
8 |
9 | **PhotosIndexContainer**
10 | - PhotoListItem
11 | + Likes
12 | + CommentsContainer
13 | + CommentItem
14 | + CommentForm
15 |
16 |
17 | **UserProfileContainer**
18 | + UserInfo
19 | - EditUser
20 | + UserPostIndex
21 | - UserPostItem
22 |
23 | **PostShowContainer**
24 | - PhotoListItem
25 | + Likes
26 | + CommentsContainer
27 | - CommentItem
28 | + CommentForm
29 |
30 | **PostPhotoContainer**
31 | + NewPost
32 |
33 |
34 |
35 | ## Routes
36 |
37 | |Path | Component |
38 | |-------|-------------|
39 | | "/" | "App" |
40 | | "/" | "PhotosIndexContainer" |
41 | | "/login" | "AuthFormContainer" |
42 | | "/signup" | "AuthFormContainer" |
43 | | "/posts/:postId" | "PostShowContainer" |
44 | | "/users/:userId" | "UserProfileContainer" |
45 | | "/create-post" | "PostPhotoContainer" |
46 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/gallery.scss:
--------------------------------------------------------------------------------
1 | .border-box {
2 | -webkit-box-sizing: border-box;
3 | -moz-box-sizing: border-box;
4 |
5 | box-sizing: border-box;
6 | }
7 |
8 | .paddingBlock {
9 | padding: 20px 0;
10 | }
11 |
12 | .eqWrap {
13 | display: flex;
14 | }
15 |
16 | .eq {
17 | padding: 10px;
18 | }
19 |
20 | .eq:nth-of-type(odd) {
21 | background: white;
22 | }
23 |
24 | .eq:nth-of-type(even) {
25 | background: white;
26 | }
27 |
28 | .equalHW {
29 | flex: 1;
30 | }
31 |
32 | .equalHMWrap {
33 | justify-content: space-between;
34 | }
35 |
36 | .equalHM {
37 | width: 32%;
38 | }
39 |
40 | .equalHMRWrap {
41 | justify-content: space-between;
42 | flex-wrap: wrap;
43 | }
44 |
45 | .equalHMR {
46 | width: 32%;
47 | margin-bottom: 2%;
48 | }
49 |
50 | .equalHMVWrap {
51 | flex-wrap: wrap;
52 | }
53 |
54 | .equalHMV {
55 | width: 32%;
56 | margin: %;
57 | }
58 |
59 | .equalHMV:nth-of-type(3n) {
60 | margin-right: 0;
61 | }
62 |
63 | .equalHMV:nth-of-type(3n+1) {
64 | margin-left: 0;
65 | }
66 |
--------------------------------------------------------------------------------
/frontend/actions/session_actions.js:
--------------------------------------------------------------------------------
1 | export const RECEIVE_CURRENT_USER = "RECEIVE_CURRENT_USER";
2 | export const RECEIVE_ERRORS = "RECEIVE_ERRORS";
3 |
4 | import * as APIUtil from "../util/session_api_util";
5 |
6 | export const receiveCurrentUser = (currentUser) => {
7 | return {
8 | type: RECEIVE_CURRENT_USER,
9 | currentUser
10 | };
11 | };
12 |
13 | export const receiveErrors = (errors) => (
14 | {
15 | type: RECEIVE_ERRORS,
16 | errors
17 | }
18 | );
19 |
20 | export const login = user => dispatch => (
21 | APIUtil.login(user)
22 | .then( user => dispatch(receiveCurrentUser(user)),
23 | err => dispatch(receiveErrors(err.responseJSON)))
24 | );
25 |
26 | export const signup = user => dispatch => (
27 | APIUtil.signup(user)
28 | .then( user => dispatch(receiveCurrentUser(user)),
29 | err => dispatch(receiveErrors(err.responseJSON)))
30 | );
31 |
32 | export const logout = () => dispatch => (
33 | APIUtil.logout().then(user => dispatch(receiveCurrentUser(null)))
34 | );
35 |
--------------------------------------------------------------------------------
/frontend/reducers/users_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_USERS, RECEIVE_USER } from '../actions/users_actions';
2 | import { RECEIVE_FOLLOW, REMOVE_FOLLOW } from '../actions/follows_actions';
3 | import { getIndexById } from '../util/util';
4 | import merge from 'lodash/merge';
5 |
6 | const UsersReducer = (oldState = {}, action) => {
7 | Object.freeze(oldState);
8 | switch (action.type) {
9 | case RECEIVE_USERS:
10 | return action.users;
11 | case RECEIVE_USER:
12 | return merge({}, action.user);
13 | case RECEIVE_FOLLOW:
14 | let copyAddFollow = merge({}, oldState);
15 | copyAddFollow.followers.push(action.follow.follower);
16 | return copyAddFollow;
17 | case REMOVE_FOLLOW:
18 | let copyRemFollow = merge({}, oldState);
19 | let followeeIndex = getIndexById(copyRemFollow.followers, action.follow.follower_id);
20 | copyRemFollow.followers.splice(followeeIndex, 1);
21 | return copyRemFollow;
22 | default:
23 | return oldState;
24 | }
25 | };
26 |
27 | export default UsersReducer;
28 |
--------------------------------------------------------------------------------
/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: 9772545d0d1129b096fb7e35b342f706ec982cb707ece7df3ad30a132618e410ed2e081db294d9a1fe669a1ebd02e9d55c7a3041706a6631afa863cecc04a5c8
15 |
16 | test:
17 | secret_key_base: 526ca6a8424e51e8a25f9c7ef115e1d8ef28de27058999249935d128eeef7e6716032938d558bf309839643021c06ad15e7fcdbcb41273640c53a65904ccc903
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/posts/comment_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | class CommentItem extends React.Component {
5 | constructor(props){
6 | super(props);
7 | this.deleteComment = this.deleteComment.bind(this);
8 | }
9 |
10 |
11 | deleteComment(){
12 | this.props.deleteComment(this.props.comment.id);
13 | }
14 |
15 | renderDeleteButton(){
16 | if(this.props.currentUser){
17 | if(this.props.comment.user_id == this.props.currentUser.id){
18 | return ;
19 | }
20 | }
21 | }
22 |
23 | render(){
24 | return(
25 |
26 |
27 | {this.props.comment.username} {this.props.comment.body}
28 |
29 | {this.renderDeleteButton()}
30 |
31 | );
32 | }
33 | }
34 |
35 | export default CommentItem;
36 |
--------------------------------------------------------------------------------
/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :integer not null, primary key
6 | # username :string not null
7 | # email :string not null
8 | # password_digest :string not null
9 | # session_token :string not null
10 | # name :string
11 | # profile_photo :string
12 | # created_at :datetime
13 | # updated_at :datetime
14 | # image_file_name :string
15 | # image_content_type :string
16 | # image_file_size :integer
17 | # image_updated_at :datetime
18 | #
19 |
20 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
21 |
22 | one:
23 | username: MyString
24 | email: MyString
25 | password_digest: MyString
26 | session_token: MyString
27 | name: MyString
28 | profile_photo: MyString
29 |
30 | two:
31 | username: MyString
32 | email: MyString
33 | password_digest: MyString
34 | session_token: MyString
35 | name: MyString
36 | profile_photo: MyString
37 |
--------------------------------------------------------------------------------
/docs/sample-state.md:
--------------------------------------------------------------------------------
1 | ```js
2 | {
3 | currentUser: {
4 | id: 1,
5 | username: "travel-academy",
6 | name: "Travelgram Academy"
7 | },
8 | forms: {
9 | signUp: {errors: []},
10 | logIn: {errors: []},
11 | uploadPhoto: {errors: ["url can't be blank"]}
12 | },
13 | user: {
14 | id: 1,
15 | username: "travel-academy",
16 | name: "Travelgram Academy"
17 | },
18 | posts: {
19 | 1: {
20 | description: "is useful to plan",
21 | url: "http//:www.url.com/image.jpg",
22 | user: {
23 | id: 1,
24 | username: "travel-academy",
25 | name: "Travelgram Academy"
26 | }
27 | comments: {
28 | 1: {
29 | body: "The best beach on the world!"
30 | user_id: 1,
31 | photo_id: 1
32 | },
33 | 2: {
34 | body: "Indeed!"
35 | user_id: 2,
36 | photo_id: 1
37 | }
38 | }
39 | likes: {
40 | 1: {
41 | user_id: 1,
42 | photo_id: 1
43 | }
44 | }
45 | }
46 |
47 | }
48 |
49 | }
50 | ```
51 |
--------------------------------------------------------------------------------
/frontend/components/posts/posts_feed_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import { fetchPosts } from '../../actions/posts_actions';
3 | import { createLike, deleteLike } from '../../actions/likes_actions';
4 | import { createComment, deleteComment } from '../../actions/comments_actions';
5 | import PostsFeed from './posts_feed';
6 |
7 | const mapStateToProps = (state) => {
8 | return {
9 | currentUser: state.session.currentUser,
10 | user: state.user,
11 | posts: Object.keys(state.posts).map(id => state.posts[id]).reverse(),
12 | fetching: state.fetching
13 | };
14 | };
15 |
16 | const mapDispatchToProps = (dispatch) => {
17 | return ({
18 | fetchPosts: () => dispatch(fetchPosts()),
19 | createLike: (postId) => dispatch(createLike(postId)),
20 | deleteLike: (postId) => dispatch(deleteLike(postId)),
21 | createComment: (postId, body) => dispatch(createComment(postId, body)),
22 | deleteComment: id => dispatch(deleteComment(id))
23 | });
24 |
25 | };
26 |
27 | export default connect(
28 | mapStateToProps,
29 | mapDispatchToProps
30 | )(PostsFeed);
31 |
--------------------------------------------------------------------------------
/frontend/util/modal_style.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | overlay : {
3 | position : 'fixed',
4 | top : 0,
5 | left : 0,
6 | right : 0,
7 | bottom : 0,
8 | backgroundColor : 'rgba(0, 0, 0, 0.65)',
9 | zIndex : 10
10 | },
11 | content : {
12 | position : 'absolute',
13 | width: '935px',
14 | height: '540px',
15 | top : '50%',
16 | left : '50%',
17 | right : 'auto',
18 | bottom : 'auto',
19 | marginRight : '-50%',
20 | transform : 'translate(-50%, -50%)',
21 | border : '0px solid #ccc',
22 | background : '#fff',
23 | overflow : 'none',
24 | WebkitOverflowScrolling : 'touch',
25 | borderRadius : '2px',
26 | outline : 'none',
27 | padding : '0px',
28 | opacity : '0',
29 | transition : 'opacity 1s',
30 | zIndex : 11
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/app/controllers/api/users_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::UsersController < ApplicationController
2 |
3 | def index
4 | @users = User.includes(:posts, :followers, :followees).all
5 | end
6 |
7 | def show
8 | @user = User.includes(:posts, :followers, :followees).find(params[:id])
9 | end
10 |
11 | def edit
12 | @user = User.find(params[:id])
13 | end
14 |
15 | def create
16 | @user = User.new(user_params)
17 | if @user.save
18 | login!(@user)
19 | render "api/users/show"
20 | else
21 | render json: @user.errors, status: 422
22 | end
23 | end
24 |
25 | def update
26 | update_params = user_params
27 | update_params = user_params_no_image if params[:user][:image] == "null"
28 | @user = User.find(params[:id])
29 | if @user.update(update_params)
30 | render @user
31 | else
32 | render json: @user.errors, status: 422
33 | end
34 | end
35 |
36 | private
37 | def user_params
38 | params.require(:user).permit(:username, :password, :email, :name, :bio, :website, :image)
39 | end
40 | def user_params_no_image
41 | params.require(:user).permit(:username, :password, :email, :name, :bio, :website)
42 | end
43 |
44 | end
45 |
--------------------------------------------------------------------------------
/frontend/components/search/search_result_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 |
4 | class SearchResultItem extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 |
9 | this.profilePage = this.profilePage.bind(this);
10 | }
11 |
12 | profilePage(){
13 | let id = this.props.user.id;
14 | this.props.router.push(`users/${id}`);
15 | this.props.closeResultsList();
16 | }
17 |
18 | render() {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 | {this.props.user.username}
27 | {this.props.user.name}
28 |
29 |
30 |
31 | );
32 | }
33 |
34 | }
35 |
36 | export default withRouter(SearchResultItem);
37 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require("path");
2 | var webpack = require("webpack");
3 |
4 | var plugins = []; // if using any plugins for both dev and production
5 | var devPlugins = []; // if using any plugins for development
6 |
7 | var prodPlugins = [
8 | new webpack.DefinePlugin({
9 | 'process.env': {
10 | 'NODE_ENV': JSON.stringify('production')
11 | }
12 | }),
13 | new webpack.optimize.UglifyJsPlugin({
14 | compress: {
15 | warnings: true
16 | }
17 | })
18 | ];
19 |
20 | plugins = plugins.concat(
21 | process.env.NODE_ENV === 'production' ? prodPlugins : devPlugins
22 | );
23 |
24 | module.exports = {
25 | context: __dirname,
26 | entry: "./frontend/travelgram.jsx",
27 | output: {
28 | path: path.resolve(__dirname, 'app', 'assets', 'javascripts'),
29 | filename: "bundle.js"
30 | },
31 | plugins: plugins,
32 | module: {
33 | loaders: [
34 | {
35 | test: [/\.jsx?$/, /\.js?$/],
36 | exclude: /node_modules/,
37 | loader: 'babel-loader',
38 | query: {
39 | presets: ['es2015', 'react']
40 | }
41 | }
42 | ]
43 | },
44 | devtool: 'source-maps',
45 | resolve: {
46 | extensions: [".js", ".jsx", "*"]
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "travelgram",
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 | },
14 | "engines": {
15 | "node": "6.7.0",
16 | "npm": "3.10.7"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/s-sanel/travelgram.git"
21 | },
22 | "keywords": [],
23 | "author": "",
24 | "license": "ISC",
25 | "bugs": {
26 | "url": "https://github.com/s-sanel/travelgram/issues"
27 | },
28 | "homepage": "https://github.com/s-sanel/travelgram#readme",
29 | "dependencies": {
30 | "babel-core": "^6.24.1",
31 | "babel-loader": "^6.4.1",
32 | "babel-preset-es2015": "^6.24.1",
33 | "babel-preset-react": "^6.24.1",
34 | "react": "^15.5.4",
35 | "react-dom": "^15.5.4",
36 | "react-modal": "^1.7.7",
37 | "react-redux": "^5.0.4",
38 | "react-router": "^3.0.5",
39 | "redux": "^3.6.0",
40 | "redux-thunk": "^2.2.0",
41 | "webpack": "^2.4.1"
42 | },
43 | "devDependencies": {
44 | "redux-logger": "^3.0.1"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/components/posts/posts_feed.jsx:
--------------------------------------------------------------------------------
1 | import { withRouter } from 'react-router';
2 | import React from 'react';
3 | import PostItem from './post_item';
4 | import Spinner from '../shared/spinner';
5 |
6 |
7 | class PostsFeed extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | componentDidMount(){
13 | this.props.fetchPosts();
14 | }
15 |
16 | componentWillReceiveProps(newProps){
17 | if(!newProps.currentUser){
18 | this.props.router.push("/signup");
19 | }
20 | }
21 |
22 | render() {
23 | if (this.props.fetching) return ;
24 | return (
25 |
26 |
27 |
28 | {
29 | this.props.posts.slice(0,15).map(post => (
30 |
40 | ))
41 | }
42 |
43 |
44 | );
45 | }
46 | }
47 |
48 | export default withRouter(PostsFeed);
49 |
--------------------------------------------------------------------------------
/frontend/actions/posts_actions.js:
--------------------------------------------------------------------------------
1 | import * as PostApiUtil from "../util/posts_api_util";
2 | export const RECEIVE_ALL_POSTS = "RECEIVE_ALL_POSTS";
3 | export const RECEIVE_POST = "RECEIVE_POST";
4 | export const REMOVE_POST = "REMOVE_POST";
5 | export const FETCH_POSTS = "FETCH_POSTS";
6 | export const FETCH_POST = "FETCH_POST";
7 | export const CREATE_POST = "CREATE_POST";
8 |
9 | export const receiveAllPosts = posts => ({
10 | type: RECEIVE_ALL_POSTS,
11 | posts
12 | });
13 |
14 | export const receivePost = post => ({
15 | type: RECEIVE_POST,
16 | post
17 | });
18 |
19 | export const removePost = post => ({
20 | type: REMOVE_POST,
21 | post
22 | });
23 |
24 |
25 | export const fetchPosts = () => {
26 | return (dispatch) => {
27 | dispatch({ type: FETCH_POSTS });
28 | return PostApiUtil.fetchPosts().then(posts => dispatch(receiveAllPosts(posts)));
29 | };
30 | };
31 |
32 |
33 | export const fetchPost = id => dispatch => (
34 | PostApiUtil.fetchPost(id).then(post => dispatch(receivePost(post)))
35 | );
36 |
37 | export const createPost = post => dispatch => (
38 | PostApiUtil.createPost(post).then(post => dispatch(receivePost(post)))
39 | );
40 |
41 | export const updatePost = post => dispatch => (
42 | PostApiUtil.updatePost(post).then(post => dispatch(receivePost(post)))
43 | );
44 |
45 | export const deletePost = post => dispatch => (
46 | PostApiUtil.deletePost(post).then(post => dispatch(removePost(post)))
47 | );
48 |
--------------------------------------------------------------------------------
/frontend/components/navigation/nav_bar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 | import NavLinksContainer from './nav_links_container';
4 | import SearchContainer from '../search/search_container';
5 |
6 | class NavBar extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.homePageNav = this.homePageNav.bind(this);
11 | }
12 |
13 | homePageNav(){
14 | this.props.router.push("/");
15 | }
16 |
17 | profilePageNav(){
18 | this.props.router.push(`/${this.props.currentUser.id}`);
19 | }
20 |
21 | handleLogout(e) {
22 | e.preventDefault();
23 | this.props.logout().then(() => this.props.router.push('/signup'));
24 | }
25 |
26 | render() {
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
Travelgram
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | }
46 |
47 | export default withRouter(NavBar);
48 |
--------------------------------------------------------------------------------
/app/controllers/api/posts_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::PostsController < ApplicationController
2 |
3 | def index
4 | # @posts = Post.where(user: current_user.followees).includes(:comments => [:user]).includes(:user, :likes).order(created_at: :desc).limit(2)
5 | @posts = Post.includes(:comments => [:user]).includes(:user, :likes).order(created_at: :desc)
6 | render :index
7 |
8 | # followees = current_user.followees
9 | # followees.map { |followee| followee.id }
10 | # @posts = Post.where(user_id: followees).includes(:user, comments: :user, likes: :user).order('created_at DESC')
11 |
12 | end
13 |
14 | def show
15 | # @post = Post.includes(:user, :comments, :likes).find(params[:id])
16 | @posts = Post.includes(:comments => [:user]).includes(:user, :likes).all
17 | end
18 |
19 | def edit
20 | @post = Post.includes(:user, :comments, :likes).find(params[:id])
21 | end
22 |
23 |
24 | def create
25 | @post = current_user.posts.new(post_params)
26 | if @post.save
27 | render "api/posts/show"
28 | else
29 | render json: @post.errors, status: 422
30 | end
31 | end
32 |
33 | def update
34 | @post = current_user.posts.find(params[:id])
35 | if @post.update(post_params)
36 | render json: @post
37 | else
38 | render json: @post.errors, status: 422
39 | end
40 | end
41 |
42 | def destroy
43 | @post = current_user.posts.find(params[:id])
44 | @post.destroy
45 | render json: @post
46 | end
47 |
48 | private
49 |
50 | def post_params
51 | params.require(:post).permit(:image, :description)
52 | end
53 |
54 | end
55 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any 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 |
14 | *= require_self
15 | */
16 |
17 | // CSS Reset
18 | @import "base/reset.scss";
19 |
20 | // @import "font-awesome/css/font-awesome.min.css";
21 | @font-face {
22 | font-family: 'Billabong';
23 | src: asset-url('custom-fonts/billabong.eot'), asset-url('custom-fonts/billabong.ttf');
24 | }
25 |
26 | // Core
27 | @import "base/colors.scss";
28 | @import "base/fonts.scss";
29 | @import "base/layout.scss";
30 |
31 | // Components
32 | @import "components/*";
33 | // @import "components/_landing_page.scss";
34 | // @import "components/_main_content.scss";
35 | // @import "components/_posts_feed.scss";
36 | // @import "components/_user_profile_page.scss";
37 |
38 |
39 | @media only screen and (max-width : 768px) {
40 | .landing-page-img { display: none; }
41 | }
42 |
43 | @media only screen and (max-width : 500px) {
44 | .search { display: none; }
45 | }
46 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/reset.scss:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header, hgroup,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video {
14 | margin: 0;
15 | border: 0;
16 | padding: 0;
17 | outline: 0;
18 | font: inherit;
19 | color: inherit;
20 | text-align: inherit;
21 | text-decoration: inherit;
22 | vertical-align: inherit;
23 | box-sizing: inherit;
24 | background: transparent;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1.16;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
49 |
50 | input[type="text"],
51 | input[type="password"],
52 | textarea,
53 | button {
54 | -webkit-appearance: none;
55 | -moz-appearance: none;
56 | appearance: none;
57 | }
58 |
59 | a, button, input[type="submit"] {
60 | cursor: pointer;
61 | }
62 |
63 | .group:after {
64 | content: "";
65 | display: block;
66 | clear: both;
67 | }
68 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/frontend/components/greeting/greeting.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 |
4 | class Greeting extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.handleLogout = this.handleLogout.bind(this);
9 | this.profilePage = this.profilePage.bind(this);
10 | this.uploadPost = this.uploadPost.bind(this);
11 | }
12 |
13 | profilePage(){
14 | let id = this.props.currentUser.id;
15 | this.props.router.push(`users/${id}`);
16 | }
17 |
18 | uploadPost(){
19 | this.props.router.push("/upload-post");
20 | }
21 |
22 | handleLogout(e) {
23 | e.preventDefault();
24 | this.props.logout().then(() => this.props.router.push('/signup'));
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
);
49 | }
50 |
51 | }
52 | export default withRouter(Greeting);
53 |
--------------------------------------------------------------------------------
/frontend/components/posts/add_comment_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | class AddCommentForm extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.state = {body: ""};
9 | this.handleSubmit = this.handleSubmit.bind(this);
10 | }
11 |
12 | handleSubmit(e){
13 | e.preventDefault();
14 | let postId = this.props.post.id;
15 | let body = this.state.body;
16 | this.props.createComment(postId, body).then(() => this.resetState());
17 | if (this.props.incrementCommentCount) this.props.incrementCommentCount();
18 | }
19 |
20 | update(field) {
21 | return e => this.setState({
22 | [field]: e.currentTarget.value
23 | });
24 | }
25 |
26 | resetState(){
27 | this.setState({body: ""});
28 | }
29 |
30 | isDisabledSubmit(){
31 | if(this.state.body.length === 0) {
32 | return "disabled";
33 | }else {
34 | return "";
35 | }
36 | }
37 |
38 | render() {
39 | let post_id = this.props.post.id;
40 | let comm = "input-comment-" + post_id;
41 | let disabled = this.isDisabledSubmit();
42 | let submitClass ="add-comment-submit " + disabled;
43 |
44 | return (
45 |
51 | );
52 |
53 | }
54 |
55 | }
56 |
57 | export default AddCommentForm;
58 |
--------------------------------------------------------------------------------
/frontend/components/root.jsx:
--------------------------------------------------------------------------------
1 | import { Router, Route, IndexRoute, hashHistory } from 'react-router';
2 | import { Provider } from 'react-redux';
3 | import React from 'react';
4 | import App from './app'
5 | import AuthFormContainer from './session/auth_form_container';
6 | import UserProfileContainer from './user_profile/user_profile_container';
7 | import EditProfileContainer from './user_profile/edit_profile_container';
8 | import UploadPostContainer from './posts/upload_post_container';
9 | import Posts from './posts/posts';
10 |
11 | const Root = ({ store }) => {
12 | const _ensureLoggedIn = (nextState, replace) => {
13 | const currentUser = store.getState().session.currentUser;
14 | if (!currentUser) {
15 | replace('/signup');
16 | }
17 | };
18 |
19 | const _redirectIfLoggedIn = (nextState, replace) => {
20 | const currentUser = store.getState().session.currentUser;
21 | if (currentUser) {
22 | replace('/');
23 | }
24 | }
25 |
26 | return (
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | )
40 | };
41 |
42 | export default Root;
43 |
--------------------------------------------------------------------------------
/frontend/components/navigation/nav_links.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 |
4 | class NavLinks extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.handleLogout = this.handleLogout.bind(this);
9 | this.profilePage = this.profilePage.bind(this);
10 | this.uploadPost = this.uploadPost.bind(this);
11 | }
12 |
13 | profilePage(){
14 | let id = this.props.currentUser.id;
15 | this.props.router.push(`users/${id}`);
16 | }
17 |
18 | uploadPost(){
19 | this.props.router.push("/upload-post");
20 | }
21 |
22 | handleLogout(e) {
23 | e.preventDefault();
24 | this.props.logout().then(() => this.props.router.push('/signup'));
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
);
48 | }
49 |
50 | }
51 | export default withRouter(NavLinks);
52 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | # A sample Guardfile
2 | # More info at https://github.com/guard/guard#readme
3 |
4 | ## Uncomment and set this to only include directories you want to watch
5 | # directories %w(app lib config test spec features) \
6 | # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7 |
8 | ## Note: if you are using the `directories` clause above and you are not
9 | ## watching the project directory ('.'), then you will want to move
10 | ## the Guardfile to a watched dir and symlink it back, e.g.
11 | #
12 | # $ mkdir config
13 | # $ mv Guardfile config/
14 | # $ ln -s config/Guardfile .
15 | #
16 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17 |
18 | guard 'livereload' do
19 | extensions = {
20 | css: :css,
21 | scss: :css,
22 | sass: :css,
23 | js: :js,
24 | coffee: :js,
25 | html: :html,
26 | png: :png,
27 | gif: :gif,
28 | jpg: :jpg,
29 | jpeg: :jpeg,
30 | # less: :less, # uncomment if you want LESS stylesheets done in browser
31 | }
32 |
33 | rails_view_exts = %w(erb haml slim)
34 |
35 | # file types LiveReload may optimize refresh for
36 | compiled_exts = extensions.values.uniq
37 | watch(%r{public/.+\.(#{compiled_exts * '|'})})
38 |
39 | extensions.each do |ext, type|
40 | watch(%r{
41 | (?:app|vendor)
42 | (?:/assets/\w+/(?[^.]+) # path+base without extension
43 | (?\.#{ext})) # matching extension (must be first encountered)
44 | (?:\.\w+|$) # other extensions
45 | }x) do |m|
46 | path = m[1]
47 | "/assets/#{path}.#{type}"
48 | end
49 | end
50 |
51 | # file needing a full reload of the page anyway
52 | watch(%r{app/views/.+\.(#{rails_view_exts * '|'})$})
53 | watch(%r{app/helpers/.+\.rb})
54 | watch(%r{config/locales/.+\.yml})
55 | end
56 |
--------------------------------------------------------------------------------
/docs/schema.md:
--------------------------------------------------------------------------------
1 | # Schema Information
2 |
3 | ## users
4 | column name | data type | details
5 | ----------------|-----------|-----------------------
6 | id | integer | not null, primary key
7 | username | string | not null, indexed, unique
8 | email | string | not null, indexed, unique
9 | password_digest | string | not null
10 | session_token | string | not null, indexed, unique
11 | name | string |
12 | profile_photo | string |
13 |
14 | ## posts
15 | column name | data type | details
16 | ------------|-----------|-----------------------
17 | id | integer | not null, primary key
18 | url | string | not null
19 | description | text |
20 | user_id | integer | not null, foreign key (references users), indexed
21 |
22 | ## comments
23 | column name | data type | details
24 | ------------|-----------|-----------------------
25 | id | integer | not null, primary key
26 | body | string | not null
27 | user_id | integer | not null, foreign key (references users), indexed
28 | post_id | integer | not null, foreign key (references photos), indexed
29 |
30 | ## likes
31 | column name | data type | details
32 | ------------|-----------|-----------------------
33 | id | integer | not null, primary key
34 | user_id | integer | not null, foreign key (references users), indexed, unique[post_id]
35 | post_id | integer | not null, foreign key (references photos), indexed
36 |
37 | ## follows
38 | column name | data type | details
39 | ------------|-----------|-----------------------
40 | id | integer | not null, primary key
41 | follower_id | integer | not null, foreign key (references users), indexed, unique[following_id]
42 | following_id| integer | not null, foreign key (references users), indexed
43 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/reducers/posts_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_ALL_POSTS, RECEIVE_POST, REMOVE_POST } from '../actions/posts_actions';
2 | import { RECEIVE_LIKE, REMOVE_LIKE } from '../actions/likes_actions';
3 | import { RECEIVE_COMMENT, REMOVE_COMMENT } from '../actions/comments_actions';
4 | import merge from 'lodash/merge';
5 | import { getIndex } from '../util/util';
6 |
7 |
8 | const PostsReducer = (oldState = {}, action) => {
9 | Object.freeze(oldState);
10 | switch (action.type) {
11 | case RECEIVE_ALL_POSTS:
12 | let posts = action.posts;
13 | return merge({}, oldState, posts);
14 | case RECEIVE_POST:
15 | return merge({}, oldState, { [action.post.id]: action.post });
16 | case REMOVE_POST:
17 | let newState = merge({}, oldState);
18 | delete newState[action.post.id];
19 | return newState;
20 | case RECEIVE_LIKE:
21 | let copyState = merge({}, oldState);
22 | copyState[action.like.post_id].likes.push(action.like);
23 | return copyState;
24 | case REMOVE_LIKE:
25 | let nextState = merge({}, oldState);
26 | let likeIndex = getIndex(nextState[action.like.post_id].likes, action.like);
27 | nextState[action.like.post_id].likes.splice(likeIndex, 1);
28 | return nextState;
29 | case RECEIVE_COMMENT:
30 | let copyAddComState = merge({}, oldState);
31 | copyAddComState[action.comment.post_id].comments.push(action.comment);
32 | return copyAddComState;
33 | case REMOVE_COMMENT:
34 | let copyRemComState = merge({}, oldState);
35 | let commentIndex = getIndex(copyRemComState[action.comment.post_id].comments, action.comment);
36 | copyRemComState[action.comment.post_id].comments.splice(commentIndex, 1);
37 | return copyRemComState;
38 | default:
39 | return oldState;
40 | }
41 | };
42 |
43 |
44 | export default PostsReducer;
45 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/font-awesome/less/mixins.less:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | .fa-icon() {
5 | display: inline-block;
6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
14 | .fa-icon-rotate(@degrees, @rotation) {
15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})";
16 | -webkit-transform: rotate(@degrees);
17 | -ms-transform: rotate(@degrees);
18 | transform: rotate(@degrees);
19 | }
20 |
21 | .fa-icon-flip(@horiz, @vert, @rotation) {
22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)";
23 | -webkit-transform: scale(@horiz, @vert);
24 | -ms-transform: scale(@horiz, @vert);
25 | transform: scale(@horiz, @vert);
26 | }
27 |
28 |
29 | // Only display content to screen readers. A la Bootstrap 4.
30 | //
31 | // See: http://a11yproject.com/posts/how-to-hide-content/
32 |
33 | .sr-only() {
34 | position: absolute;
35 | width: 1px;
36 | height: 1px;
37 | padding: 0;
38 | margin: -1px;
39 | overflow: hidden;
40 | clip: rect(0,0,0,0);
41 | border: 0;
42 | }
43 |
44 | // Use in conjunction with .sr-only to only display content when it's focused.
45 | //
46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
47 | //
48 | // Credit: HTML5 Boilerplate
49 |
50 | .sr-only-focusable() {
51 | &:active,
52 | &:focus {
53 | position: static;
54 | width: auto;
55 | height: auto;
56 | margin: 0;
57 | overflow: visible;
58 | clip: auto;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # FresherNote
2 |
3 | [Travelgram][heroku]
4 |
5 | [Trello link][trello]
6 |
7 | [heroku]: http://www.travelgram.world/
8 | [trello]: https://trello.com/b/fa9hOu1T/travelgram
9 |
10 | ## Minimum Viable Product
11 |
12 | Travelgram is a web application inspired by Instagram built using Ruby on Rails
13 | and React/Redux. By the end of Week 9, this app will, at a minimum, satisfy the
14 | following criteria with smooth, bug-free navigation, adequate seed data and
15 | sufficient CSS styling:
16 |
17 | - [ ] Hosting on Heroku
18 | - [ ] [Production README](../README.md)
19 | - [ ] New account creation, login, and guest/demo login
20 | - [ ] Images
21 | - [ ] Likes
22 | - [ ] Commenting on images
23 | - [ ] Following & Photo feed
24 |
25 | ## Design Docs
26 | * [View Wireframes](wireframes)
27 | * [React Components](components)
28 | * [API endpoints](api-endpoints)
29 | * [DB schema](schema)
30 | * [Sample State](sample-state)
31 |
32 | ## Implementation Timeline
33 |
34 | ### Phase 1: Backend setup and Front End User Authentication (2 days)
35 |
36 | **Objective:** Functioning rails project with front-end Authentication
37 |
38 | ### Phase 2: Image Model, API, and components (2 days)
39 |
40 | **Objective:** Posts with uploaded photos can be created, displayed, edited and deleted.
41 |
42 | ### Phase 3: Likes (1 days)
43 |
44 | **Objective:** Users can like and unlike photos.
45 |
46 | ### Phase 4: Commenting on images (1 day)
47 |
48 | **Objective:** Users can create and delete comments on photos.
49 |
50 | ### Phase 5: Following & Photo feed (2 day)
51 |
52 | **Objective:** Users can follow/unfollow another users. Photo feed displays photos of followed users.
53 |
54 | ### Phase 6: - Pagination / infinite scroll for Photo feed (1 day)
55 |
56 | **Objective:** Add infinite scroll to Photo feed
57 |
58 | ### Bonus Features (TBD)
59 | - [ ] Direct messaging
60 | - [ ] Hash tags
61 |
--------------------------------------------------------------------------------
/app/models/post.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: posts
4 | #
5 | # id :integer not null, primary key
6 | # description :string
7 | # user_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # image_file_name :string
11 | # image_content_type :string
12 | # image_file_size :integer
13 | # image_updated_at :datetime
14 | # url :string
15 | #
16 |
17 | class Post < ApplicationRecord
18 | validates :user_id, presence: true
19 |
20 | has_attached_file :image, s3_protocol: :https, default_url: "beach.jpg",
21 | :styles => {
22 | :thumb => "50x50",
23 | :medium => "300x300",
24 | :large => "600x"
25 | }
26 | validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
27 |
28 | belongs_to :user
29 | has_many :likes
30 | has_many :comments
31 |
32 | def created_ago
33 | diff_in_secs =(Time.now - self.created_at).round
34 | mins = diff_in_secs / 1.minutes
35 | hours = diff_in_secs / 1.hours
36 | days = diff_in_secs / 1.days
37 |
38 | if (days > 730)
39 | "#{days / 365} year"
40 | elsif (days >= 365)
41 | "#{days / 365} years"
42 | elsif (days >= 14)
43 | "#{days / 7} weeks"
44 | elsif (days >= 7)
45 | "#{days / 7} week"
46 | elsif (days > 1)
47 | "#{days} days"
48 | elsif (days > 0)
49 | "#{days} day"
50 | else
51 | if (hours > 1)
52 | "#{hours} hours"
53 | elsif (hours > 0)
54 | "#{hours} hour"
55 | else
56 | if(mins > 1)
57 | "#{mins} minutes"
58 | else
59 | "1 minute"
60 | end
61 | end
62 | end
63 | end
64 |
65 | end
66 |
--------------------------------------------------------------------------------
/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/font-awesome/scss/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | @mixin fa-icon() {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
14 | @mixin fa-icon-rotate($degrees, $rotation) {
15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})";
16 | -webkit-transform: rotate($degrees);
17 | -ms-transform: rotate($degrees);
18 | transform: rotate($degrees);
19 | }
20 |
21 | @mixin fa-icon-flip($horiz, $vert, $rotation) {
22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)";
23 | -webkit-transform: scale($horiz, $vert);
24 | -ms-transform: scale($horiz, $vert);
25 | transform: scale($horiz, $vert);
26 | }
27 |
28 |
29 | // Only display content to screen readers. A la Bootstrap 4.
30 | //
31 | // See: http://a11yproject.com/posts/how-to-hide-content/
32 |
33 | @mixin sr-only {
34 | position: absolute;
35 | width: 1px;
36 | height: 1px;
37 | padding: 0;
38 | margin: -1px;
39 | overflow: hidden;
40 | clip: rect(0,0,0,0);
41 | border: 0;
42 | }
43 |
44 | // Use in conjunction with .sr-only to only display content when it's focused.
45 | //
46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
47 | //
48 | // Credit: HTML5 Boilerplate
49 |
50 | @mixin sr-only-focusable {
51 | &:active,
52 | &:focus {
53 | position: static;
54 | width: auto;
55 | height: auto;
56 | margin: 0;
57 | overflow: visible;
58 | clip: auto;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_upload_post.scss:
--------------------------------------------------------------------------------
1 | .upload-post-main {
2 | padding-top: 60px;
3 | text-align: center;
4 | }
5 |
6 | .upload-post-article {
7 | flex-grow: 1;
8 | margin: 20px auto 30px;
9 | max-width: 535px;
10 | display: flex;
11 | flex-direction: column;
12 | background-color: white;
13 | border: 1px solid #dbdbdb;
14 | border-radius: 3px;
15 | min-height: 500px;
16 | }
17 |
18 | .upload-post-preview {
19 | flex: 1;
20 | padding: 10px;
21 | }
22 |
23 | .upload-post-caption {
24 | overflow: hidden;
25 | padding: 10px;
26 | border-top: 1px solid $input-border;
27 | }
28 |
29 | .description-textarea {
30 | width: 100%;
31 | max-width: 500px;
32 | max-height: 100px;
33 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
34 | -moz-box-sizing: border-box; /* Firefox, other Gecko */
35 | box-sizing: border-box; /* Opera/IE 8+ */
36 |
37 | border: 1px solid $input-border;
38 | color: $gray-link;
39 | font-size: 14px;
40 | // overflow:scroll;
41 | // overflow-y:scroll;
42 | // overflow-x:hidden;
43 | }
44 |
45 | .submit-post {
46 | background: $insta-blue;
47 | border-color: $insta-blue;
48 | color: $white;
49 | font-size: 14px;
50 | padding: 4px;
51 | width: 140px;
52 | border-radius: 5px;
53 | cursor: pointer;
54 | text-align: center;
55 | margin: 10px auto 20px;;
56 | }
57 |
58 |
59 | .choose_file{
60 | position:relative;
61 | display:inline-block;
62 | border-radius:8px;
63 | border:$light-gray solid 1px;
64 | width:90%;
65 | height: 100%;
66 | padding: 4px 6px 4px 8px;
67 | font: normal 14px Myriad Pro, Verdana, Geneva, sans-serif;
68 | color: #7f7f7f;
69 | margin-top: 2px;
70 | background:white;
71 | cursor: pointer;
72 | padding: 5px;
73 | }
74 | .choose_file input[type="file"]{
75 | -webkit-appearance:none;
76 | position:absolute;
77 | top:0; left:0;
78 | opacity:0;
79 | cursor: pointer;
80 | width: 100%;
81 | padding: 5px
82 | }
83 |
--------------------------------------------------------------------------------
/frontend/components/user_profile/user_profile.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NavBar from '../navigation/nav_bar';
3 | import UserProfileDetails from './user_profile_details';
4 | import UserProfilePosts from './user_profile_posts';
5 | import Spinner from '../shared/spinner';
6 | import PostItemModalContainer from '../posts/modal/post_item_modal_container';
7 |
8 | class UserProfile extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | }
12 |
13 | componentDidMount(){
14 | if(!this.props.posts){
15 | this.props.fetchPosts();
16 | }
17 | this.props.fetchUser(parseInt(this.props.params.user_id));
18 | }
19 |
20 | componentWillReceiveProps(newProps){
21 | if(this.props.params.user_id != newProps.params.user_id){
22 | this.props.fetchUser(parseInt(newProps.params.user_id));
23 | }
24 | }
25 |
26 |
27 |
28 | render() {
29 | if (!this.props.user.name) return (
30 |
31 |
32 |
33 | ;
34 |
35 |
36 | );
37 |
38 | return (
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 | {
52 | this.props.posts.reverse().map(post => (
53 |
56 | ))
57 | }
58 |
59 |
60 |
61 |
62 | );
63 | }
64 |
65 | }
66 |
67 |
68 | export default UserProfile;
69 |
--------------------------------------------------------------------------------
/frontend/components/follow/follow.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 | import Spinner from '../shared/spinner';
4 |
5 | class Follow extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {followed: false};
10 | this.unfollow = this.unfollow.bind(this);
11 | this.follow = this.follow.bind(this);
12 | }
13 |
14 | componentDidMount(){
15 | if(this.isUserFollowedByCurrentUser()){
16 | this.setState({followed: true});
17 | }else {
18 | this.setState({followed: false});
19 | }
20 | }
21 |
22 | componentWillReceiveProps(newProps){}
23 |
24 |
25 | handleLogout(e) {
26 | e.preventDefault();
27 | this.props.logout().then(() => this.props.router.push('/signup'));
28 | }
29 |
30 | isUserFollowedByCurrentUser(){
31 | let currentId = this.props.currentUser.id;
32 | let res = this.props.user.followers.find( followee => {
33 | return followee.id == currentId;
34 | });
35 | let val = (res) ? true : false;
36 | return val;
37 | }
38 |
39 | unfollow(){
40 | let follower_id = this.props.currentUser.id;
41 | let following_id = this.props.user.id;
42 | this.props.deleteFollow(follower_id, following_id);
43 | this.setState({followed: false});
44 | }
45 |
46 | follow(){
47 | let follower_id = this.props.currentUser.id;
48 | let following_id = this.props.user.id;
49 | this.props.createFollow(follower_id, following_id);
50 | this.setState({followed: true});
51 | }
52 |
53 | renderButton(){
54 | if (this.isUserFollowedByCurrentUser()){
55 | return Following;
56 | }else {
57 | return Follow;
58 | }
59 | }
60 |
61 | render() {
62 | if (!this.props.user.name) return ;
63 | return (
64 |
65 | {this.renderButton()}
66 |
67 | );
68 | }
69 |
70 | }
71 | export default Follow;
72 |
--------------------------------------------------------------------------------
/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/components/_modal_post_item.scss:
--------------------------------------------------------------------------------
1 | .modal-post-holder{
2 | display: flex;
3 | box-sizing: border-box;
4 | height: 100%;
5 | }
6 |
7 | .modal-post-image {
8 | flex-grow: 3;
9 | width: 65%;
10 | height: 100%;
11 | box-sizing: border-box;
12 | cursor: pointer;
13 | }
14 |
15 | .a{
16 | display: flex;
17 | flex-direction: column;
18 | }
19 |
20 | .modal-post-data {
21 | flex-grow: 1;
22 | width: 320px;
23 | position: relative;
24 | display: flex;
25 | flex-direction: column;
26 | box-sizing: border-box;
27 | }
28 |
29 | .rem-margin {
30 | margin-bottom: -20px;
31 | }
32 |
33 | .modal-close-btn {
34 | position: fixed;
35 | top: 0;
36 | right: 0;
37 | width: 20px;
38 | height: 20px;
39 | z-index: 12;
40 | font-size: 20px;
41 | color: $gray-link;
42 | }
43 |
44 | .new-header {
45 | border-bottom: 1px solid #efefef;
46 | height: 78px;
47 | margin-right: 0;
48 | padding: 20px 0;
49 | position: absolute;
50 | right: 24px;
51 | top: 0;
52 | width: 287px;
53 | }
54 |
55 | .add-com{
56 | position: absolute;
57 | bottom: 0;
58 | }
59 |
60 | .comments-scrollable {
61 | overflow-y: scroll;
62 | height: 100%;
63 | max-height: calc(100% - 30px);
64 | }
65 |
66 | .modal-data {
67 | border-bottom: 1px solid $gray;
68 | margin-right: 0;
69 | padding: 20px 0;
70 | position: absolute;
71 | right: 24px;
72 | top: 0;
73 | width: 287px;
74 | }
75 |
76 | .modal-item-header {
77 | display: flex;
78 | justify-content: flex-start;
79 | align-items: center;
80 | }
81 |
82 | .profile-image {
83 |
84 | }
85 |
86 | .modal-ddd {
87 | bottom: 0;
88 | box-sizing: border-box;
89 | padding-left: 24px;
90 | padding-right: 24px;
91 | position: absolute;
92 | right: 0;
93 | top: 78px;
94 | width: 335px;
95 | }
96 |
97 |
98 | .single-post-footer-items {
99 | max-height: 280px;
100 | height: 280px;
101 | overflow-y: scroll;
102 | padding: 15px;
103 | border-bottom: 1px solid $light-gray;
104 | }
105 |
106 | .btm{
107 | position: fixed;
108 | bottom: 5px;
109 | width: 290px;
110 | }
111 |
112 | .padded {
113 | padding: 15px;
114 | }
115 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 |
3 | # Automatically inject JavaScript needed for LiveReload
4 | config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload)
5 |
6 | # Settings specified here will take precedence over those in config/application.rb.
7 |
8 | # In the development environment your application's code is reloaded on
9 | # every request. This slows down response time but is perfect for development
10 | # since you don't have to restart the web server when you make code changes.
11 | config.cache_classes = false
12 |
13 | # Do not eager load code on boot.
14 | config.eager_load = false
15 |
16 | # Show full error reports.
17 | config.consider_all_requests_local = true
18 |
19 | # Enable/disable caching. By default caching is disabled.
20 | if Rails.root.join('tmp/caching-dev.txt').exist?
21 | config.action_controller.perform_caching = true
22 |
23 | config.cache_store = :memory_store
24 | config.public_file_server.headers = {
25 | 'Cache-Control' => 'public, max-age=172800'
26 | }
27 | else
28 | config.action_controller.perform_caching = false
29 |
30 | config.cache_store = :null_store
31 | end
32 |
33 | # Don't care if the mailer can't send.
34 | config.action_mailer.raise_delivery_errors = false
35 |
36 | config.action_mailer.perform_caching = false
37 |
38 | # Print deprecation notices to the Rails logger.
39 | config.active_support.deprecation = :log
40 |
41 | # Raise an error on page load if there are pending migrations.
42 | config.active_record.migration_error = :page_load
43 |
44 | # Debug mode disables concatenation and preprocessing of assets.
45 | # This option may cause significant delays in view rendering with a large
46 | # number of complex assets.
47 | config.assets.debug = true
48 |
49 | # Suppress logger output for asset requests.
50 | config.assets.quiet = true
51 |
52 | # Raises error for missing translations
53 | # config.action_view.raise_on_missing_translations = true
54 |
55 | # Use an evented file watcher to asynchronously detect changes in source code,
56 | # routes, locales, etc. This feature depends on the listen gem.
57 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
58 | end
59 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_search.scss:
--------------------------------------------------------------------------------
1 | .search-results {
2 | background: white;
3 | width: 250px;
4 | padding: 0px 0;
5 | position: absolute;
6 | top: 70px;
7 | margin: 0 auto;
8 | z-index: 111;
9 | }
10 |
11 | .search-results-list {
12 | overflow-y: scroll;
13 | max-height: 600px;
14 | border-top: 1px solid $light-gray;
15 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
16 | }
17 |
18 |
19 | .search-results-list li {
20 | // height: 50px;
21 | border-bottom: 1px solid $light-gray;
22 | padding: 5px 10px;
23 | overflow: hidden;
24 | }
25 |
26 | .search-results-list li:hover {
27 | background-color: $lightest-gray;
28 | }
29 |
30 | .search-results-list li a {
31 | // margin: 0px;
32 | // display: block;
33 | width: 100%;
34 | height: 100%;
35 | }
36 |
37 |
38 | .search-results-list-item {
39 | display: flex;
40 | justify-content: center;
41 | }
42 |
43 | .search-results-item{
44 | display: flex;
45 | align-items: center;
46 | justify-content: center;
47 | }
48 |
49 | .search-results-item-img {
50 | flex: 1;
51 | }
52 |
53 | .search-results-item-data {
54 | display: flex;
55 | flex-direction: column;
56 | flex: 4;
57 | margin-left: 10px;
58 | }
59 |
60 | .search-results-text-username {
61 | color: $gray-link;
62 | font-weight: bold;
63 | }
64 |
65 | .search-results-text-name {
66 | font-weight: 200;
67 | color: $gray;
68 | }
69 |
70 |
71 | .search-no-results {
72 | color: $gray;
73 | display: flex;
74 | justify-content: center;
75 | align-items: center;
76 | font-weight: 450;
77 | cursor: default;
78 | height: 50px;
79 | border-bottom: 1px solid $light-gray;
80 | padding: 5px 10px;
81 | }
82 |
83 |
84 | .triangle {
85 | position: relative;
86 | width:0;
87 | border-bottom:solid 15px $light-gray;
88 | border-right:solid 15px transparent;
89 | border-left:solid 15px transparent;
90 | margin: 0 auto;
91 | }
92 | .triangle .empty {
93 | position: absolute;
94 | top:2px;
95 | left:-15px;
96 | width:0;
97 | border-bottom:solid 15px white;
98 | border-right:solid 15px transparent;
99 | border-left:solid 15px transparent;
100 | }
101 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | git_source(:github) do |repo_name|
4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
5 | "https://github.com/#{repo_name}.git"
6 | end
7 |
8 |
9 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
10 | gem 'rails', '~> 5.0.2'
11 | # Use postgresql as the database for Active Record
12 | gem 'pg', '~> 0.18'
13 | # Use Puma as the app server
14 | gem 'puma', '~> 3.0'
15 | # Use SCSS for stylesheets
16 | gem 'sass-rails', '~> 5.0'
17 | # Use Uglifier as compressor for JavaScript assets
18 | gem 'uglifier', '>= 1.3.0'
19 | # Use CoffeeScript for .coffee assets and views
20 | gem 'coffee-rails', '~> 4.2'
21 | # See https://github.com/rails/execjs#readme for more supported runtimes
22 | # gem 'therubyracer', platforms: :ruby
23 |
24 | # Use jquery as the JavaScript library
25 | gem 'jquery-rails'
26 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
27 | gem 'jbuilder', '~> 2.5'
28 | # Use Redis adapter to run Action Cable in production
29 | # gem 'redis', '~> 3.0'
30 | # Use ActiveModel has_secure_password
31 | gem 'bcrypt', '~> 3.1.7'
32 |
33 |
34 | gem "paperclip", '~> 5.0.0'
35 | gem 'figaro'
36 | gem 'aws-sdk', '>= 2.0'
37 | gem 'faker'
38 |
39 | # Use Capistrano for deployment
40 | # gem 'capistrano-rails', group: :development
41 |
42 | group :development, :test do
43 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
44 | gem 'byebug', platform: :mri
45 | gem 'better_errors'
46 | gem 'binding_of_caller'
47 | gem 'pry-rails'
48 | gem 'annotate'
49 | end
50 |
51 | group :development do
52 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
53 | gem 'web-console', '>= 3.3.0'
54 | gem 'listen', '~> 3.0.5'
55 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
56 | gem 'spring'
57 | gem 'spring-watcher-listen', '~> 2.0.0'
58 |
59 | #auto sass reloading
60 | gem "guard", ">= 2.2.2", :require => false
61 | gem "guard-livereload", :require => false
62 | gem "rack-livereload"
63 | gem "rb-fsevent", :require => false
64 | end
65 |
66 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
67 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
68 |
--------------------------------------------------------------------------------
/frontend/components/posts/upload_post.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 | import NavBar from '../navigation/nav_bar';
4 |
5 | class UploadPost extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | description: "",
10 | imageFile: null,
11 | imageUrl: null
12 | };
13 | this.updateFile = this.updateFile.bind(this);
14 | this.updateDescription = this.updateDescription.bind(this);
15 | this.handleSubmit = this.handleSubmit.bind(this);
16 | }
17 |
18 | updateFile(e) {
19 | let file = e.currentTarget.files[0];
20 | let fileReader = new FileReader();
21 | fileReader.onloadend = () => this.setState({ imageFile: file, imageUrl: fileReader.result });
22 |
23 | if (file) {
24 | fileReader.readAsDataURL(file);
25 | }
26 | }
27 |
28 | updateDescription(e) {
29 | let description = e.target.value;
30 | this.setState({
31 | description
32 | });
33 | }
34 |
35 | handleSubmit(e) {
36 | e.preventDefault();
37 | let formData = new FormData();
38 | formData.append("post[description]", this.state.description);
39 | formData.append("post[image]", this.state.imageFile);
40 | formData.append("post[url]", "test");
41 | let id = this.props.currentUser.id;
42 | this.props.createPost(formData).then(() => this.props.router.push(`users/${id}`));
43 | }
44 |
45 | render() {
46 | return (
47 |
69 | );
70 | }
71 |
72 | }
73 |
74 |
75 | export default withRouter(UploadPost);
76 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :integer not null, primary key
6 | # username :string not null
7 | # email :string not null
8 | # password_digest :string not null
9 | # session_token :string not null
10 | # name :string
11 | # profile_photo :string
12 | # created_at :datetime
13 | # updated_at :datetime
14 | # image_file_name :string
15 | # image_content_type :string
16 | # image_file_size :integer
17 | # image_updated_at :datetime
18 | #
19 |
20 | class User < ApplicationRecord
21 |
22 | validates :username, :password_digest, :session_token, :email, presence: true
23 | validates :username, uniqueness: true
24 | validates :email, uniqueness: true
25 | validates :password, length: {minimum: 6, allow_nil: true}
26 |
27 | has_attached_file :image, s3_protocol: :https, default_url: "ghost.jpg"
28 | validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
29 |
30 | has_many :posts, dependent: :destroy
31 | has_many :likes
32 | has_many :comments
33 |
34 | has_many :follows_as_followee,
35 | class_name: :Follow,
36 | primary_key: :id,
37 | foreign_key: :following_id
38 |
39 |
40 | has_many :follows_as_follower,
41 | class_name: :Follow,
42 | primary_key: :id,
43 | foreign_key: :follower_id
44 |
45 | has_many :followers,
46 | through: :follows_as_followee,
47 | source: :follower
48 |
49 | has_many :followees,
50 | through: :follows_as_follower,
51 | source: :followee
52 |
53 | after_initialize :ensure_session_token
54 |
55 | attr_reader :password
56 |
57 | def self.find_by_credentials (username, password)
58 | user = User.find_by(username: username)
59 | return nil unless user
60 | user.password_is?(password) ? user : nil
61 | end
62 |
63 | def password= (password)
64 | @password = password
65 | self.password_digest = BCrypt::Password.create(password)
66 | end
67 |
68 | def password_is? (password)
69 | BCrypt::Password.new(self.password_digest).is_password?(password)
70 | end
71 |
72 | def reset_session_token!
73 | self.session_token = User.generate_session_token
74 | self.save!
75 | self.session_token
76 | end
77 |
78 | def self.generate_session_token
79 | SecureRandom.urlsafe_base64(16)
80 | end
81 |
82 | private
83 | def ensure_session_token
84 | self.session_token ||= User.generate_session_token
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/frontend/components/posts/modal/post_item_modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link, withRouter} from 'react-router';
3 | import CommentItem from '../comment_item';
4 | import AddCommentForm from '../add_comment_form';
5 | import PostItemModalData from './post_item_modal_data';
6 |
7 | import Modal from 'react-modal';
8 | import ModalStyle from '../../../util/modal_style';
9 |
10 |
11 | class PostItemModal extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = { modalOpen: false };
15 | this.openModal = this.openModal.bind(this);
16 | this.closeModal = this.closeModal.bind(this);
17 | this.onModalOpen = this.onModalOpen.bind(this);
18 | }
19 |
20 | openModal(){
21 | this.setState({modalOpen: true});
22 | }
23 |
24 | closeModal(){
25 | this.setState({modalOpen: false});
26 | ModalStyle.content.opacity = 0;
27 | }
28 |
29 | onModalOpen(){
30 | ModalStyle.content.opacity = 100;
31 | }
32 |
33 | componentDidMount(){}
34 | componentWillReceiveProps(){}
35 |
36 | render(){
37 | return(
38 |
39 |
40 |
41 |
42 | {this.props.post.comments.length}
43 | {this.props.post.likes.length}
44 |
45 |
46 |
47 |
53 |
54 |
64 |
65 |
66 | );
67 | }
68 | }
69 |
70 | export default withRouter(PostItemModal);
71 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/layout.scss:
--------------------------------------------------------------------------------
1 | body, button, input, textarea {
2 | font-family: -apple-system, BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
3 | font-size:14px;
4 | }
5 |
6 | #root, html, body {
7 | height: 100%;
8 | box-sizing: border-box;
9 | }
10 |
11 | #root {
12 | height: 100%;
13 | background-color: $lightest-gray;
14 | display: flex;
15 | flex-direction: column;
16 | padding: 0px;
17 | }
18 |
19 | #root section {
20 | height: 100%;
21 | flex: 1;
22 | }
23 |
24 | footer {
25 | border: 1px solid $light-gray;
26 | background-color: $white;
27 | // margin-top: 20px;
28 | padding: 25px;
29 | }
30 |
31 | h1 {
32 | font-family: 'Billabong';
33 | font-size: 46px;
34 | margin: 22px auto 14px;
35 | text-align: center;
36 | color: $darkest-gray;
37 | }
38 |
39 | h2 {
40 | font-size: 17px;
41 | line-height: 20px;
42 | margin: 0 40px 10px;
43 | text-align: center;
44 | color: $dark-gray;
45 | font-weight: 600;
46 | }
47 |
48 | a, button {
49 | cursor: pointer;
50 | }
51 | button {
52 | border: 2px solid $dark-gray;
53 | border-radius: 5px;
54 | }
55 |
56 | button:focus {
57 | outline:0px;
58 | }
59 |
60 | a {
61 | color: $insta-blue;
62 | text-decoration:none;
63 | }
64 |
65 | a:visited{
66 | color: $insta-blue;
67 | text-decoration:none
68 | }
69 | a:active {
70 | opacity:.5
71 | }
72 |
73 |
74 | .img-circle {
75 | border-radius: 50%;
76 | }
77 |
78 | .bold {
79 | font-weight: 600;
80 | }
81 |
82 | .border {
83 | border: 1px solid red;
84 | }
85 |
86 | .dotted {
87 | border: 1px dotted blue;
88 | }
89 |
90 |
91 | .loader {
92 | border: 16px solid #f3f3f3;
93 | border-radius: 50%;
94 | border-top: 16px solid #3498db;
95 | width: 120px;
96 | height: 120px;
97 | -webkit-animation: spin 2s linear infinite;
98 | animation: spin 2s linear infinite;
99 | }
100 |
101 | @-webkit-keyframes spin {
102 | 0% { -webkit-transform: rotate(0deg); }
103 | 100% { -webkit-transform: rotate(360deg); }
104 | }
105 |
106 | @keyframes spin {
107 | 0% { transform: rotate(0deg); }
108 | 100% { transform: rotate(360deg); }
109 | }
110 |
111 | .spinner-pos {
112 | margin-top: 100px;
113 | display: flex;
114 | justify-content: center;
115 | }
116 |
117 | .exit-modal-button {
118 | position: absolute;
119 | top: 0;
120 | right: 0;
121 | color: #fff;
122 | font-weight: bold;
123 | margin: 15px 15px 0 0;
124 | width: 17px;
125 | z-index: 12;
126 | }
127 |
128 | .pointer {
129 | cursor: pointer;
130 | }
131 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_main_content.scss:
--------------------------------------------------------------------------------
1 | // ------------- data-root posts page ------------- //
2 | .data-root {
3 | display: flex;
4 | flex-direction: column;
5 | flex: 1 0 auto;
6 | }
7 |
8 | .nav-bar{
9 | background-color: $white;
10 | border-bottom: 1px solid $light-gray;
11 | display: flex;
12 | flex-direction: row;
13 | justify-content: center;
14 | }
15 |
16 | .main-nav-bar-max-width {
17 | max-width: 1010px;
18 | flex: 1;
19 | }
20 |
21 | .main-nav-bar{
22 | background-color: $white;
23 | display: flex;
24 | align-items: center;
25 | justify-content: space-between;
26 | padding: 23px;
27 | }
28 |
29 | .main-nav-bar a {
30 | color: $darkest-gray;
31 | }
32 |
33 | .main-nav-logo {
34 | font-family: "Billabong";
35 | font-size: 33px;
36 | min-width: 160px;
37 | display: flex;
38 | color: $darkest-gray;
39 | }
40 | .search {
41 | // min-width: 80px;
42 | flex: 1;
43 | align-self: center;
44 | display: flex;
45 | justify-content: center;
46 | margin-bottom: -2px;
47 | }
48 | .main-nav-links {
49 | min-width: 140px;
50 | display: flex;
51 | justify-content: flex-end;
52 | align-items: center;
53 | padding-left: 0px;
54 | font-size: 22px;
55 | font-weight: 100;
56 | }
57 |
58 | .main-nav-links a {
59 | color: $dark-gray;
60 | text-decoration: none;
61 | }
62 |
63 | .main-nav-explore{
64 | margin-left: 20px;
65 | }
66 | .main-nav-user{
67 | margin-left: 20px;
68 | }
69 | .upload-photo{
70 | margin-left: 20px;
71 | }
72 |
73 | .main-nav-logout {
74 | margin-left: 20px;
75 | }
76 |
77 | .main-content{
78 | // border: 1px solid red;
79 | background-color: $lightest-gray;
80 | flex: 1;
81 | display: flex;
82 | flex-direction: column;
83 | }
84 |
85 | .main-nav-logo-text {
86 | border-left: 1px solid $gray;
87 | padding-left: 10px;
88 | margin-left: 10px;
89 | }
90 |
91 | .max{
92 | max-width: 1010px;
93 | flex: 1;
94 | }
95 |
96 | .search-form input[type=text]:focus {
97 | // width: 100%;
98 | background-color: $white;
99 | }
100 |
101 | .search-form {
102 | height: 30px;
103 | }
104 |
105 | .search-form > input {
106 | background-image: image-url('search.png');
107 | background-size: 17px 17px;
108 | background-position:5px 3px;
109 | background-repeat: no-repeat;
110 |
111 | background-color: $lightest-gray;
112 | border: solid 1px #dbdbdb;
113 | border-radius: 3px;
114 | color: $gray-link;
115 | font-size: 14px;
116 | outline: none;
117 | padding: 3px 10px 3px 26px;
118 | z-index: 2;
119 | height: 19px;
120 | }
121 |
--------------------------------------------------------------------------------
/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: 20170428020005) 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.string "body"
20 | t.integer "user_id"
21 | t.integer "post_id"
22 | t.datetime "created_at", null: false
23 | t.datetime "updated_at", null: false
24 | end
25 |
26 | create_table "follows", force: :cascade do |t|
27 | t.integer "follower_id"
28 | t.integer "following_id"
29 | t.datetime "created_at", null: false
30 | t.datetime "updated_at", null: false
31 | end
32 |
33 | create_table "likes", force: :cascade do |t|
34 | t.integer "user_id"
35 | t.integer "post_id"
36 | t.datetime "created_at", null: false
37 | t.datetime "updated_at", null: false
38 | end
39 |
40 | create_table "posts", force: :cascade do |t|
41 | t.string "description"
42 | t.integer "user_id", null: false
43 | t.datetime "created_at", null: false
44 | t.datetime "updated_at", null: false
45 | t.string "image_file_name"
46 | t.string "image_content_type"
47 | t.integer "image_file_size"
48 | t.datetime "image_updated_at"
49 | t.string "url"
50 | end
51 |
52 | create_table "users", force: :cascade do |t|
53 | t.string "username", null: false
54 | t.string "email", null: false
55 | t.string "password_digest", null: false
56 | t.string "session_token", null: false
57 | t.string "name"
58 | t.string "profile_photo"
59 | t.datetime "created_at"
60 | t.datetime "updated_at"
61 | t.string "image_file_name"
62 | t.string "image_content_type"
63 | t.integer "image_file_size"
64 | t.datetime "image_updated_at"
65 | t.string "bio"
66 | t.string "website"
67 | end
68 |
69 | end
70 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_landing_page.scss:
--------------------------------------------------------------------------------
1 | .landing-page-main{
2 | box-sizing: border-box;
3 | margin: 30px auto 0;
4 | display: flex;
5 | justify-content: center;
6 | flex-direction: row;
7 | align-items: center;
8 | width: 100%;
9 | // flex:1;
10 | flex: 1 0 auto;
11 | }
12 |
13 | .landing-page-img {
14 | align-self: center;
15 | width: 454px;
16 | height: 618px;
17 | background-color: $lightest-gray;
18 | background-image: image-url('homepage.png');
19 | background-repeat: no-repeat;
20 | margin: 0 10px;
21 | }
22 |
23 | .landing-page-intro{
24 | margin-top: 20px;
25 | min-width: 300px;
26 | max-width: 350px;
27 | flex-grow: 1;
28 | justify-content: center;
29 | }
30 |
31 | .landing-intro-nav{
32 | background-color: $white;
33 | border: 1px solid $light-gray;
34 | border-radius: 1px;
35 | color: $darkest-gray;
36 | font-size: 14px;
37 | margin: 10px 0 10px;
38 | padding: 25px;
39 | text-align: center;
40 | }
41 |
42 | .login-box {
43 | background-color: $white;
44 | border: 1px solid $light-gray;
45 | border-radius: 1px;
46 | margin: 0 0 10px;
47 | padding: 25px;
48 | flex: 1;
49 | }
50 |
51 | .login-box-logo {
52 | border: 0px solid black;
53 | }
54 |
55 | .login-box-form {
56 | display: flex;
57 | flex-direction: column;
58 | /*justify-content: center;*/
59 | }
60 |
61 | .login-box-form input{
62 | background-color: white;
63 | border: 1px solid $input-border;
64 | border-radius: 3px;
65 | box-sizing: border-box;
66 | color: $darkest-gray;
67 | font-size: 14px;
68 | padding: 9px 8px 7px;
69 | -webkit-appearance: none;
70 | width: 100%;
71 | }
72 |
73 | li {
74 | list-style: none;
75 | }
76 |
77 | .demo-login {
78 | background: $insta-blue;
79 | border-color: $insta-blue;
80 | color: $white;
81 | font-size: 14px;
82 | padding: 8px;
83 | }
84 |
85 | .errors{
86 | padding: 0px;
87 | margin-top: 20px;
88 | color: $red;
89 | }
90 |
91 | .or-separator {
92 | text-align: center;
93 | color: $gray;
94 | font-weight: 500;
95 | display: flex;
96 | }
97 |
98 | .or-separator-line {
99 | -webkit-box-flex: 1;
100 | -ms-flex-positive: 1;
101 | flex-grow: 1;
102 | -ms-flex-negative: 1;
103 | flex-shrink: 1;
104 | background-color: $separator-gray;
105 | height: 1px;
106 | position: relative;
107 | top: .45em;
108 | }
109 |
110 | .or-separator-text {
111 | -webkit-box-flex: 0;
112 | -ms-flex-positive: 0;
113 | flex-grow: 0;
114 | -ms-flex-negative: 0;
115 | flex-shrink: 0;
116 | text-transform: uppercase;
117 | font-size: 13px;
118 | line-height: 15px;
119 | margin: 0 18px;
120 | }
121 |
--------------------------------------------------------------------------------
/frontend/components/search/search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 | import SearchResultItem from './search_result_item'
4 |
5 | class Search extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | query: "",
11 | active: false
12 | };
13 |
14 | this.handleInput = this.handleInput.bind(this);
15 | this.closeResultsList = this.closeResultsList.bind(this);
16 | this.handleClick = this.handleClick.bind(this);
17 | }
18 |
19 | handleClick(e) {
20 | if (this.state.query) {
21 | this.setState({ active: false, query: ""});
22 | }
23 | }
24 |
25 | componentDidMount() {
26 | window.addEventListener("click", this.handleClick);
27 | }
28 |
29 | componentWillUnmount() {
30 | window.removeEventListener("click", this.handleClick);
31 | }
32 |
33 | componentWillReceiveProps(nextProps) {
34 | if (this.props.user !== nextProps.user) {
35 | this.setState({ query: '' });
36 | }
37 | }
38 |
39 | handleInput(e) {
40 | e.preventDefault();
41 | let query = e.target.value;
42 | if(query === "") {
43 | this.props.removeSearchResults();
44 | this.setState({query: "", active: false});
45 | }else {
46 | this.setState({
47 | query: query,
48 | active: true },
49 | () => this.props.fetchSearchResults(this.state.query));
50 | }
51 | }
52 |
53 | closeResultsList(){
54 | this.setState({query: "", active: false});
55 | }
56 |
57 | renderResults(){
58 | if(this.props.searchResults.length > 0) {
59 | return(
60 | this.props.searchResults.map((user) =>
61 | )
62 | )
63 | }else {
64 | return No results found.
65 | }
66 | }
67 |
68 | render() {
69 | let resultsList = ""
70 | if( this.state.active) {
71 | resultsList =
72 |
73 |
76 |
77 |
78 | {this.renderResults()}
79 |
80 |
81 |
;
82 | }
83 |
84 | return (
85 |
86 |
87 |
89 |
90 |
91 | {resultsList}
92 |
93 | );
94 | }
95 |
96 | }
97 |
98 | export default withRouter(Search);
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Travelgram
2 |
3 | [Travelgram][heroku]
4 |
5 | [heroku]: https://insta-travelgram.herokuapp.com/#/
6 |
7 | Travelgram is a full-stack web application inspired by Instagram. It utilizes Ruby on Rails on the backend, a PostgreSQL database, and React.js with a Redux architectural framework on the frontend.
8 |
9 | ## Features & Implementation
10 |
11 | ### Secure User Authentication
12 |
13 | Users can sign up and log in with validations in Rails' models to ensure that only valid data is saved to the database. Only registered users can access application features. Passwords are hashed using BCrypt.
14 |
15 | ### Posts Feed
16 | Posts are stored in the database table, with columns for `id`, `user_id` that references the person who uploaded the post, `image` which is an attached file uploaded to AWS (Amazon Web Services). There is an optional column of `description` of a post. Once logged in, user is redirected to posts feed page and an AJAX call is made to the database and posts of the users that current user is following are fetched.
17 |
18 | Each post on posts feed page has owner's profile picture, username, image, likes and comments associated to the post
19 |
20 | Posts are created through update post form, where user can choose image and add a description to a post.
21 | As a post requires a `user_id` column to be successfully saved, posts are always created through current user.
22 |
23 | 
24 | ### Users
25 | Users are stored in the database in a `users` table. User's show page displays their details and posts. Users can edit their own data and change profile photo.
26 |
27 | 
28 |
29 | ### Search
30 |
31 | As the user types in the search bar, a list of users matching the input are fetched from the database through an AJAX call.
32 |
33 | 
34 |
35 | ### Likes and Comments
36 | Users can like and unlike any post by clicking on the heart icon which color indicates action. Same action can be done with double click on the posts image.
37 | User can post a comment by clicking on a cloud icon which gives focus to an input field and when user start typing comment can be submitted.
38 | On the each post on the posts feed page, only last three comments are displayed by default. User can choose to see more comments by clicking on text `load more comments` which every time displays five more latest comments.
39 |
40 | ### Follows
41 |
42 | User can follow and unfollow any other user.
43 |
44 | 
45 |
46 | ## Future Directions for the Project
47 |
48 | In addition to the features already implemented, I plan to continue work on this project. The next steps for Travelgram are outlined below.
49 | * [ ] Direct Messaging
50 | * [ ] Hashtags
51 | * [ ] Full responsiveness (optimization for mobile devices)
52 | * [ ] Infinite scroll
53 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_edit_profile_page.scss:
--------------------------------------------------------------------------------
1 | .edit-profile-main {
2 | padding-top: 60px;
3 | }
4 |
5 | .edit-profile-article {
6 | flex-grow: 1;
7 | margin: 0 auto 30px;
8 | max-width: 935px;
9 | display: flex;
10 | background-color: white;
11 | border: 1px solid #dbdbdb;
12 | border-radius: 3px;
13 | min-height: 400px;
14 | }
15 |
16 | .edit-profile-navigation {
17 | flex-grow: 1;
18 | border-right: 1px solid #dbdbdb;
19 |
20 | }
21 |
22 | .edit-profile-navigation-item{
23 | font-size: 16px;
24 | font-weight: bold;
25 | padding: 20px 10px;
26 | border-left: 3px solid $darkest-gray;
27 | }
28 |
29 | .edit-profile-form-header {
30 | margin: 32px 0 16px 0;
31 | display: flex;
32 | align-items: center;
33 | }
34 |
35 | .edit-profile-header-img {
36 | flex: 1;
37 | text-align: right;
38 | }
39 |
40 | .edit-profile-header-img img {
41 | border-radius: 50%;
42 | }
43 |
44 | .edit-profile-header-name {
45 | flex: 3;
46 | text-align: left;
47 | margin-left: 30px;
48 | font-size: 24px;
49 | font-weight: 600;
50 | }
51 |
52 | .edit-profile-data {
53 | flex-grow: 3;
54 | padding: 10px;
55 | display: flex;
56 | flex-direction: column;
57 | font-size: 16px;
58 | font-weight: bold;
59 | }
60 |
61 | .edit-profile-form {
62 | display: flex;
63 | flex-direction: column;
64 | margin: 32px 0 16px 0;
65 | }
66 |
67 | .edit-profile-data-row{
68 | display: flex;
69 | align-items: center;
70 | margin-bottom: 16px;
71 |
72 | }
73 |
74 | .edit-profile-field-title {
75 | flex: 1;
76 | text-align: right;
77 | margin-right: 30px;
78 | min-width: 100px;
79 | }
80 |
81 | .edit-profile-field-input-holder {
82 | flex: 3;
83 | }
84 |
85 | .edit-profile-field-input-holder > * {
86 | width: 70%;
87 | min-width: 150px;
88 | font-size: 18px;
89 | font-weight: 500;
90 | color: $gray-link;
91 | border-radius: 2px;
92 | border: 1px solid #e6e6e6;
93 | background-color: #ffffff;
94 | padding: 10px;
95 | }
96 |
97 | .edit-bio-textarea {
98 | max-width: 500px;
99 | border: 1px solid $input-border;
100 | }
101 |
102 | .edit-user-submit {
103 | background: $insta-blue;
104 | border-color: $insta-blue;
105 | color: $white;
106 | font-size: 16px;
107 | padding: 8px;
108 | width: 30%;
109 | border-radius: 5px;
110 | margin-top: 20px;
111 | cursor: pointer;
112 | }
113 |
114 | .profile-user-follow-btn {
115 | background: $insta-blue;
116 | color: $white;
117 | padding: 4px 24px;
118 | margin-left: 20px;
119 | border-radius: 3px;
120 | border: 1px solid $insta-blue;
121 | font-size: 14px;
122 | font-weight: 600;
123 | }
124 |
125 | .input-file {
126 | position: absolute;
127 | bottom: 0;
128 | right: 0;
129 | width: 90px;
130 | height: 90px;
131 | cursor: pointer;
132 | opacity: 0;
133 | // background-color: green;
134 | }
135 |
136 | .edit-profile {
137 | overflow: hidden;
138 | // background-color: red;
139 | position: relative;
140 | }
141 |
--------------------------------------------------------------------------------