├── log
└── .keep
├── app
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── concerns
│ │ └── .keep
│ ├── reward.rb
│ ├── contribution.rb
│ ├── category.rb
│ ├── campaign.rb
│ └── user.rb
├── assets
│ ├── images
│ │ ├── .keep
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── back.png
│ │ ├── Github.png
│ │ ├── forward.png
│ │ ├── future.png
│ │ ├── gleft.png
│ │ ├── gright.png
│ │ ├── party.png
│ │ ├── briefcase.png
│ │ ├── coachella.png
│ │ ├── creative.png
│ │ ├── gardenbot.png
│ │ ├── linkedin.png
│ │ ├── IndieFomoFavicon.ico
│ │ ├── categories
│ │ │ ├── health.png
│ │ │ ├── home.png
│ │ │ ├── music.png
│ │ │ ├── travel.png
│ │ │ ├── fashion.png
│ │ │ ├── outdoors.png
│ │ │ ├── projects.png
│ │ │ └── technology.png
│ │ ├── img_placeholder.png
│ │ ├── minimalistic_shelf.png
│ │ ├── profile_placeholder.png
│ │ ├── GitHub-Mark-Light-32px.png
│ │ └── github.svg
│ ├── stylesheets
│ │ ├── table.css
│ │ ├── errors.css
│ │ ├── api
│ │ │ ├── rewards.scss
│ │ │ ├── session.scss
│ │ │ ├── users.scss
│ │ │ ├── campaigns.scss
│ │ │ ├── contributions.scss
│ │ │ ├── reset.css
│ │ │ └── modal_style.js
│ │ ├── categories.scss
│ │ ├── contributions.scss
│ │ ├── static_pages_controller.scss
│ │ ├── user_contributions_list.css
│ │ ├── progress_bar.css
│ │ ├── footer.css
│ │ ├── user_campaign_list.css
│ │ ├── searchbar.css
│ │ ├── category_show.css
│ │ ├── modal_style.js
│ │ ├── campaign_new.css
│ │ ├── home_page.css
│ │ ├── categories.css
│ │ ├── campaign_edit.css
│ │ ├── reward_tile.css
│ │ ├── newRewards.css
│ │ ├── campaign_index.scss
│ │ ├── discovery.css
│ │ └── campaign_show.css
│ └── javascripts
│ │ ├── api
│ │ ├── users.coffee
│ │ ├── campaigns.coffee
│ │ ├── rewards.coffee
│ │ ├── session.coffee
│ │ └── contributions.coffee
│ │ ├── campaigns.coffee
│ │ ├── categories.coffee
│ │ ├── search.coffee
│ │ ├── contributions.coffee
│ │ ├── static_pages_controller.coffee
│ │ └── application.js
├── controllers
│ ├── concerns
│ │ └── .keep
│ ├── static_pages_controller.rb
│ ├── api
│ │ ├── categories_controller.rb
│ │ ├── search_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── contributions_controller.rb
│ │ ├── charges_controller.rb
│ │ ├── users_controller.rb
│ │ ├── rewards_controller.rb
│ │ └── campaigns_controller.rb
│ └── application_controller.rb
├── helpers
│ ├── search_helper.rb
│ ├── api
│ │ ├── users_helper.rb
│ │ ├── rewards_helper.rb
│ │ ├── session_helper.rb
│ │ ├── campaigns_helper.rb
│ │ └── contributions_helper.rb
│ ├── campaigns_helper.rb
│ ├── categories_helper.rb
│ ├── application_helper.rb
│ ├── contributions_helper.rb
│ └── static_pages_controller_helper.rb
└── views
│ ├── api
│ ├── sessions
│ │ ├── show.json.jbuilder
│ │ └── _user.json.jbuilder
│ ├── users
│ │ ├── show.json.jbuilder
│ │ ├── new.json.jbuilder
│ │ └── _user.json.jbuilder
│ ├── search
│ │ └── index.json.jbuilder
│ ├── categories
│ │ └── index.json.jbuilder
│ ├── rewards
│ │ ├── show.json.jbuilder
│ │ └── index.json.jbuilder
│ ├── contributions
│ │ └── show.json.jbuilder
│ └── campaigns
│ │ ├── show.json.jbuilder
│ │ └── index.json.jbuilder
│ ├── layouts
│ └── application.html.erb
│ └── static_pages
│ └── root.html.erb
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── public
├── favicon.ico
├── robots.txt
├── 500.html
├── 422.html
└── 404.html
├── test
├── helpers
│ └── .keep
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── user_test.rb
│ ├── campaign_test.rb
│ ├── reward_test.rb
│ └── api
│ │ ├── category_test.rb
│ │ └── contribution_test.rb
├── controllers
│ ├── .keep
│ ├── search_controller_test.rb
│ ├── api
│ │ ├── users_controller_test.rb
│ │ ├── campaigns_controller_test.rb
│ │ ├── rewards_controller_test.rb
│ │ ├── session_controller_test.rb
│ │ └── contributions_controller_test.rb
│ ├── campaigns_controller_test.rb
│ ├── categories_controller_test.rb
│ ├── contributions_controller_test.rb
│ └── static_pages_controller_controller_test.rb
├── fixtures
│ ├── .keep
│ ├── campaigns.yml
│ ├── rewards.yml
│ ├── users.yml
│ └── api
│ │ ├── categories.yml
│ │ └── contributions.yml
├── integration
│ └── .keep
└── test_helper.rb
├── .ruby-version
├── vendor
└── assets
│ ├── javascripts
│ └── .keep
│ └── stylesheets
│ └── .keep
├── docs
├── images
│ ├── rewards.png
│ ├── categories.png
│ ├── indiefomo.png
│ ├── campaign_show.png
│ ├── progress-bar.png
│ └── campaign-creation.png
├── wireframes
│ ├── SignUp.png
│ ├── Landing Page.png
│ ├── CampaignShowPage.png
│ ├── RewardsComponent.png
│ ├── CampaignComponent.png
│ ├── CampaignIndexPage.png
│ ├── CreateACampaignForm.png
│ ├── NavigationComponent.png
│ ├── UserProfileShowPage.png
│ ├── CreateCampaignRewards.png
│ ├── UserConrtibutionPage.png
│ ├── CampaignCategoryShowPage.png
│ └── CreateACampaignForm-Edit.png
├── sample_data.md
├── api-endpoints
├── sample-state.md
├── schema.md
├── PRODUCTION_README.md
├── README.md
└── component-hierarchy.md
├── frontend
├── util
│ ├── category_api_util.js
│ ├── search_api_util.js
│ ├── contributions_api_util.js
│ ├── user_api_util.js
│ ├── auth_api_util.js
│ ├── rewards_api_util.js
│ └── campaign_api_util.js
├── reducers
│ ├── search_reducer.js
│ ├── category_reducer.js
│ ├── selectors.js
│ ├── tile_slider_reducer.js
│ ├── user_reducer.js
│ ├── session_reducer.js
│ ├── root_reducer.js
│ ├── rewards_reducer.js
│ ├── contribution_reducer.js
│ └── campaign_reducer.js
├── store
│ └── store.js
├── components
│ ├── app.jsx
│ ├── categories
│ │ ├── category_container.js
│ │ ├── category_tile.jsx
│ │ ├── category_show_container.js
│ │ ├── category_index.jsx
│ │ └── category_show.jsx
│ ├── campaign
│ │ ├── new_campaign_container.js
│ │ ├── edit_campaign_container.js
│ │ ├── campaign_index_container.js
│ │ ├── user_profile_component.jsx
│ │ ├── campaign_show_container.js
│ │ ├── campaign_index.jsx
│ │ ├── progress_block.jsx
│ │ ├── campaign_index_item.jsx
│ │ ├── campaign_show.jsx
│ │ ├── rewards
│ │ │ ├── rewards_showpage.jsx
│ │ │ └── reward.jsx
│ │ └── new_campaign.jsx
│ ├── home_page
│ │ ├── tile_slider_container.js
│ │ ├── discovery.js
│ │ ├── home_page.js
│ │ ├── carousel_component.jsx
│ │ └── tile_slider.jsx
│ ├── session
│ │ ├── session_form_container.js
│ │ └── session_form.jsx
│ ├── user
│ │ ├── user_container.js
│ │ ├── campaigns.jsx
│ │ ├── contributions.jsx
│ │ ├── profile.jsx
│ │ └── user_show.jsx
│ ├── header
│ │ └── header_container.js
│ ├── footer.jsx
│ └── root.jsx
├── actions
│ ├── search_action.js
│ ├── category_actions.js
│ ├── user_actions.js
│ ├── contribution_actions.js
│ ├── session_actions.js
│ ├── campaign_actions.js
│ └── reward_actions.js
└── indie_fomo.jsx
├── bin
├── bundle
├── rake
├── rails
├── spring
└── setup
├── config
├── boot.rb
├── initializers
│ ├── cookies_serializer.rb
│ ├── session_store.rb
│ ├── mime_types.rb
│ ├── stripe.rb
│ ├── filter_parameter_logging.rb
│ ├── backtrace_silencers.rb
│ ├── assets.rb
│ ├── to_time_preserves_timezone.rb
│ ├── wrap_parameters.rb
│ └── inflections.rb
├── environment.rb
├── database.yml
├── locales
│ └── en.yml
├── secrets.yml
├── application.rb
├── environments
│ ├── development.rb
│ ├── test.rb
│ └── production.rb
└── routes.rb
├── db
└── migrate
│ ├── 20170419202534_remove_index.rb
│ ├── 20170513234717_add_date_to_contribution.rb
│ ├── 20170418215930_drop_index.rb
│ ├── 20170419150144_remove_constraints.rb
│ ├── 20170514002446_fixdate.rb
│ ├── 20170426011902_add_col_to_contribution.rb
│ ├── 20170514001735_migration.rb
│ ├── 20170425215609_fix_category.rb
│ ├── 20170507020926_add_photo_to_categories.rb
│ ├── 20170427133706_fix_date_in_campaigns.rb
│ ├── 20170421185402_add_attachment_image_to_users.rb
│ ├── 20170421192056_add_attachment_image_to_campaigns.rb
│ ├── 20170427185626_create_rewards.rb
│ ├── 20170425214923_fix.rb
│ ├── 20170420142900_create_campaigns.rb
│ ├── 20170514165849_create_pg_search_documents.rb
│ └── 20170418155621_create_users.rb
├── config.ru
├── Rakefile
├── .gitignore
├── webpack.config.js
├── package.json
├── Guardfile
├── Gemfile
└── README.md
/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/.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 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.3.1
2 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/helpers/search_helper.rb:
--------------------------------------------------------------------------------
1 | module SearchHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/users_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::UsersHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/campaigns_helper.rb:
--------------------------------------------------------------------------------
1 | module CampaignsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/categories_helper.rb:
--------------------------------------------------------------------------------
1 | module CategoriesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/table.css:
--------------------------------------------------------------------------------
1 | .table {
2 | width: 100%;
3 | }
4 |
--------------------------------------------------------------------------------
/app/helpers/api/rewards_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::RewardsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/session_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::SessionHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/campaigns_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::CampaignsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/contributions_helper.rb:
--------------------------------------------------------------------------------
1 | module ContributionsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/api/contributions_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::ContributionsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/static_pages_controller_helper.rb:
--------------------------------------------------------------------------------
1 | module StaticPagesControllerHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/models/reward.rb:
--------------------------------------------------------------------------------
1 | class Reward < ActiveRecord::Base
2 | belongs_to :campaign
3 | end
4 |
--------------------------------------------------------------------------------
/app/assets/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/1.png
--------------------------------------------------------------------------------
/app/assets/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/2.png
--------------------------------------------------------------------------------
/app/assets/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/3.png
--------------------------------------------------------------------------------
/app/assets/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/4.png
--------------------------------------------------------------------------------
/docs/images/rewards.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/images/rewards.png
--------------------------------------------------------------------------------
/app/assets/images/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/back.png
--------------------------------------------------------------------------------
/app/views/api/sessions/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "api/sessions/user", :locals => {user: @user}
2 |
--------------------------------------------------------------------------------
/docs/images/categories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/images/categories.png
--------------------------------------------------------------------------------
/docs/images/indiefomo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/images/indiefomo.png
--------------------------------------------------------------------------------
/docs/wireframes/SignUp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/SignUp.png
--------------------------------------------------------------------------------
/app/assets/images/Github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/Github.png
--------------------------------------------------------------------------------
/app/assets/images/forward.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/forward.png
--------------------------------------------------------------------------------
/app/assets/images/future.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/future.png
--------------------------------------------------------------------------------
/app/assets/images/gleft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/gleft.png
--------------------------------------------------------------------------------
/app/assets/images/gright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/gright.png
--------------------------------------------------------------------------------
/app/assets/images/party.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/party.png
--------------------------------------------------------------------------------
/docs/images/campaign_show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/images/campaign_show.png
--------------------------------------------------------------------------------
/docs/images/progress-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/images/progress-bar.png
--------------------------------------------------------------------------------
/app/assets/images/briefcase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/briefcase.png
--------------------------------------------------------------------------------
/app/assets/images/coachella.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/coachella.png
--------------------------------------------------------------------------------
/app/assets/images/creative.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/creative.png
--------------------------------------------------------------------------------
/app/assets/images/gardenbot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/gardenbot.png
--------------------------------------------------------------------------------
/app/assets/images/linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/linkedin.png
--------------------------------------------------------------------------------
/docs/images/campaign-creation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/images/campaign-creation.png
--------------------------------------------------------------------------------
/docs/wireframes/Landing Page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/Landing Page.png
--------------------------------------------------------------------------------
/docs/wireframes/CampaignShowPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/CampaignShowPage.png
--------------------------------------------------------------------------------
/docs/wireframes/RewardsComponent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/RewardsComponent.png
--------------------------------------------------------------------------------
/app/assets/images/IndieFomoFavicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/IndieFomoFavicon.ico
--------------------------------------------------------------------------------
/app/assets/images/categories/health.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/categories/health.png
--------------------------------------------------------------------------------
/app/assets/images/categories/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/categories/home.png
--------------------------------------------------------------------------------
/app/assets/images/categories/music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/categories/music.png
--------------------------------------------------------------------------------
/app/assets/images/categories/travel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/categories/travel.png
--------------------------------------------------------------------------------
/app/assets/images/img_placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/img_placeholder.png
--------------------------------------------------------------------------------
/app/controllers/static_pages_controller.rb:
--------------------------------------------------------------------------------
1 | class StaticPagesController < ApplicationController
2 | def root
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/views/api/users/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "api/users/user", :locals => {user: @user, contributions: @contributions}
2 |
--------------------------------------------------------------------------------
/docs/wireframes/CampaignComponent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/CampaignComponent.png
--------------------------------------------------------------------------------
/docs/wireframes/CampaignIndexPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/CampaignIndexPage.png
--------------------------------------------------------------------------------
/docs/wireframes/CreateACampaignForm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/CreateACampaignForm.png
--------------------------------------------------------------------------------
/docs/wireframes/NavigationComponent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/NavigationComponent.png
--------------------------------------------------------------------------------
/docs/wireframes/UserProfileShowPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/UserProfileShowPage.png
--------------------------------------------------------------------------------
/app/assets/images/categories/fashion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/categories/fashion.png
--------------------------------------------------------------------------------
/app/assets/images/categories/outdoors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/categories/outdoors.png
--------------------------------------------------------------------------------
/app/assets/images/categories/projects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/categories/projects.png
--------------------------------------------------------------------------------
/app/assets/images/minimalistic_shelf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/minimalistic_shelf.png
--------------------------------------------------------------------------------
/app/assets/images/profile_placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/profile_placeholder.png
--------------------------------------------------------------------------------
/docs/sample_data.md:
--------------------------------------------------------------------------------
1 | p = User.create(username: 'p', password: 'password', first_name: 'P',
2 | last_name: 'L', email: 'pl@gmail.com')
3 |
--------------------------------------------------------------------------------
/docs/wireframes/CreateCampaignRewards.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/CreateCampaignRewards.png
--------------------------------------------------------------------------------
/docs/wireframes/UserConrtibutionPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/UserConrtibutionPage.png
--------------------------------------------------------------------------------
/app/assets/images/GitHub-Mark-Light-32px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/GitHub-Mark-Light-32px.png
--------------------------------------------------------------------------------
/app/assets/images/categories/technology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/app/assets/images/categories/technology.png
--------------------------------------------------------------------------------
/docs/wireframes/CampaignCategoryShowPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/CampaignCategoryShowPage.png
--------------------------------------------------------------------------------
/docs/wireframes/CreateACampaignForm-Edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plupinska/IndieFomo/HEAD/docs/wireframes/CreateACampaignForm-Edit.png
--------------------------------------------------------------------------------
/frontend/util/category_api_util.js:
--------------------------------------------------------------------------------
1 | export const getCategories = () => {
2 | return $.ajax({
3 | url: '/api/categories'
4 | });
5 | };
6 |
--------------------------------------------------------------------------------
/app/views/api/users/new.json.jbuilder:
--------------------------------------------------------------------------------
1 | if @user
2 | json.extract! @user, :first_name, :last_name, :email, :id
3 | else
4 | {user: null}
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 |
--------------------------------------------------------------------------------
/app/views/api/search/index.json.jbuilder:
--------------------------------------------------------------------------------
1 |
2 |
3 | json.array! @campaigns do |campaign|
4 | json.id campaign.id
5 | json.title campaign.title
6 | end
7 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/errors.css:
--------------------------------------------------------------------------------
1 | .new-campaign-errors {
2 | box-sizing: border-box;
3 | height: 10px;
4 | padding-left: 50px;
5 | color: red;
6 | }
7 |
--------------------------------------------------------------------------------
/app/views/api/sessions/_user.json.jbuilder:
--------------------------------------------------------------------------------
1 |
2 | if user
3 | json.extract! user, :first_name, :last_name, :email, :id
4 | else
5 | {user: null}
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20170419202534_remove_index.rb:
--------------------------------------------------------------------------------
1 | class RemoveIndex < ActiveRecord::Migration
2 | def change
3 | remove_column :users, :username
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class UserTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/test/models/campaign_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class CampaignTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/reward_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RewardTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
4 |
--------------------------------------------------------------------------------
/test/models/api/category_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::CategoryTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_IndieFomo_session'
4 |
--------------------------------------------------------------------------------
/app/views/api/categories/index.json.jbuilder:
--------------------------------------------------------------------------------
1 |
2 | json.array! @categories do |category|
3 |
4 | json.id category.id
5 | json.cat category.cat
6 | json.image category.image
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20170513234717_add_date_to_contribution.rb:
--------------------------------------------------------------------------------
1 | class AddDateToContribution < ActiveRecord::Migration
2 | def change
3 | add_column :contributions, :date, :date
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/test/models/api/contribution_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::ContributionTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20170418215930_drop_index.rb:
--------------------------------------------------------------------------------
1 | class DropIndex < ActiveRecord::Migration
2 | def change
3 | remove_index :users, :first_name
4 | remove_index :users, :last_name
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20170419150144_remove_constraints.rb:
--------------------------------------------------------------------------------
1 | class RemoveConstraints < ActiveRecord::Migration
2 | def change
3 | change_column :users, :username, :string, :null => true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/frontend/util/search_api_util.js:
--------------------------------------------------------------------------------
1 | export const searchCampaigns = (searchField) => {
2 |
3 | return $.ajax({
4 | url: 'api/search',
5 | data: {searchField: searchField}
6 | });
7 | };
8 |
--------------------------------------------------------------------------------
/test/controllers/search_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class SearchControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/api/rewards/show.json.jbuilder:
--------------------------------------------------------------------------------
1 |
2 | json.campaign_id @reward.campaign_id
3 | json.title @reward.title
4 | json.id @reward.id
5 | json.description @reward.description
6 | json.price @reward.price
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 |
--------------------------------------------------------------------------------
/db/migrate/20170514002446_fixdate.rb:
--------------------------------------------------------------------------------
1 | class Fixdate < ActiveRecord::Migration
2 | def change
3 | remove_column :contributions, :end_date
4 | add_column :contributions, :date, :string
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/test/controllers/api/users_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::UsersControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/campaigns_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class CampaignsControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/categories_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class CategoriesControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20170426011902_add_col_to_contribution.rb:
--------------------------------------------------------------------------------
1 | class AddColToContribution < ActiveRecord::Migration
2 | def change
3 | add_column :contributions, :campaign_id, :integer, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20170514001735_migration.rb:
--------------------------------------------------------------------------------
1 | class Migration < ActiveRecord::Migration
2 | def change
3 | remove_column :contributions, :date
4 | add_column :contributions, :end_date, :string
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/test/controllers/api/campaigns_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::CampaignsControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/rewards_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::RewardsControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/session_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::SessionControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/contributions_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ContributionsControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20170425215609_fix_category.rb:
--------------------------------------------------------------------------------
1 | class FixCategory < ActiveRecord::Migration
2 | def change
3 | remove_column :categories, :type
4 | add_column :categories, :cat, :string, null: false
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20170507020926_add_photo_to_categories.rb:
--------------------------------------------------------------------------------
1 | class AddPhotoToCategories < ActiveRecord::Migration
2 | def change
3 | change_table :categories do |t|
4 | t.attachment :image
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/api/contributions_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Api::ContributionsControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/rewards.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/rewards 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/session.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/session 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 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/categories.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the Categories 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/campaigns.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/campaigns 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/contributions.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the contributions 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/20170427133706_fix_date_in_campaigns.rb:
--------------------------------------------------------------------------------
1 | class FixDateInCampaigns < ActiveRecord::Migration
2 | def change
3 | remove_column :campaigns, :end_date
4 | add_column :campaigns, :end_date, :datetime
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/test/controllers/static_pages_controller_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class StaticPagesControllerControllerTest < ActionController::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/config/initializers/stripe.rb:
--------------------------------------------------------------------------------
1 | # Rails.configuration.stripe = {
2 | # :publishable_key => ENV['PUBLISHABLE_KEY'],
3 | # :secret_key => ENV['SECRET_KEY']
4 | # }
5 | #
6 | # Stripe.api_key = Rails.configuration.stripe[:secret_key]
7 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/contributions.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the api/contributions 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/contributions/show.json.jbuilder:
--------------------------------------------------------------------------------
1 |
2 | json.user_id @contribution.user_id
3 | json.reward_id @contribution.reward_id
4 | json.amount @contribution.amount
5 | json.campaign_id @contribution.campaign_id
6 | json.date @contribution.date
7 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/static_pages_controller.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the StaticPagesController controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/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/campaigns.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/categories.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/search.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/campaigns.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/rewards.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/session.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/contributions.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/contributions.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_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 |
--------------------------------------------------------------------------------
/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 File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/app/models/contribution.rb:
--------------------------------------------------------------------------------
1 | class Contribution < ActiveRecord::Base
2 | validates_numericality_of :amount, {greater_than_or_equal_to: 1, allow_nil: false, message: "Please enter an amount greater than zero!"}
3 |
4 | belongs_to :user
5 | belongs_to :campaign
6 | belongs_to :reward
7 | end
8 |
--------------------------------------------------------------------------------
/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', __FILE__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/db/migrate/20170421185402_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 |
--------------------------------------------------------------------------------
/app/controllers/api/categories_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::CategoriesController < ApplicationController
2 |
3 | def index
4 | @categories = Category.all
5 | if @categories
6 | render :index
7 | else
8 | render json: @categories.errors.full_messages
9 | end
10 | end
11 |
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/app/models/category.rb:
--------------------------------------------------------------------------------
1 | class Category < ActiveRecord::Base
2 | include PgSearch
3 | pg_search_scope :search_by_title, :against => [:cat]
4 | has_attached_file :image, default_url: "profile_placeholder.png"
5 | validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
6 |
7 | has_many :campaigns
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20170421192056_add_attachment_image_to_campaigns.rb:
--------------------------------------------------------------------------------
1 | class AddAttachmentImageToCampaigns < ActiveRecord::Migration
2 | def self.up
3 | change_table :campaigns do |t|
4 | t.attachment :image
5 | end
6 | end
7 |
8 | def self.down
9 | remove_attachment :campaigns, :image
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/views/api/rewards/index.json.jbuilder:
--------------------------------------------------------------------------------
1 |
2 | if @campaign
3 | #
4 | @campaign.rewards.each do |reward|
5 | json.id reward.id
6 | json.title reward.title
7 | json.description reward.description
8 | json.price reward.price
9 | json.campaign_id reward.campaign_id
10 | end
11 | else
12 | nil
13 | end
14 |
--------------------------------------------------------------------------------
/app/controllers/api/search_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::SearchController < ApplicationController
2 |
3 | def index
4 |
5 | if params[:searchField]
6 | @campaigns = Campaign.search_by_title(params[:searchField]).take(5)
7 | render :index
8 | else
9 | render json: "No results found"
10 | end
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/frontend/util/contributions_api_util.js:
--------------------------------------------------------------------------------
1 | export const makeContribution = (contribution) => {
2 |
3 | return $.ajax({
4 | method: 'POST',
5 | url: `api/contributions`,
6 | data: contribution
7 | });
8 | };
9 |
10 | export const fetchContribution = (id) => {
11 | return $.ajax({
12 | url: `api/contributions/${id}`
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/db/migrate/20170427185626_create_rewards.rb:
--------------------------------------------------------------------------------
1 | class CreateRewards < ActiveRecord::Migration
2 | def change
3 | create_table :rewards do |t|
4 | t.integer :campaign_id
5 | t.string :title
6 | t.string :description
7 | t.integer :price
8 | t.attachment :image
9 |
10 | t.timestamps null: false
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require File.expand_path('../../config/environment', __FILE__)
3 | require 'rails/test_help'
4 |
5 | class ActiveSupport::TestCase
6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
7 | fixtures :all
8 |
9 | # Add more helper methods to be used by all tests here...
10 | end
11 |
--------------------------------------------------------------------------------
/frontend/reducers/search_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_SEARCH } from '../actions/search_action';
2 |
3 | const SearchReducer = (state = {}, action) => {
4 | Object.freeze(state);
5 |
6 | switch (action.type) {
7 | case RECEIVE_SEARCH:
8 | return action.campaigns;
9 | default:
10 | return state;
11 |
12 | }
13 | };
14 |
15 | export default SearchReducer;
16 |
--------------------------------------------------------------------------------
/frontend/store/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import RootReducer from '../reducers/root_reducer';
4 |
5 | const configureStore = (preloadedState = {}) => {
6 |
7 | return createStore(
8 | RootReducer,
9 | preloadedState,
10 | applyMiddleware(thunk)
11 | );
12 | };
13 |
14 | export default configureStore;
15 |
--------------------------------------------------------------------------------
/frontend/util/user_api_util.js:
--------------------------------------------------------------------------------
1 | export const getUser = (id) => {
2 |
3 | return $.ajax({
4 | url: `api/users/${id}`
5 | });
6 | };
7 |
8 | export const editUser = (user) => {
9 |
10 | return $.ajax({
11 | url: `api/users/${user.get("user[id]")}`,
12 | method: 'PATCH',
13 | dataType: "json",
14 | contentType: false,
15 | processData: false,
16 | data: user
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/test/fixtures/campaigns.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | # This model initially had no columns defined. If you add columns to the
4 | # model remove the '{}' from the fixture names and add the columns immediately
5 | # below each fixture, per the syntax in the comments below
6 | #
7 | one: {}
8 | # column: value
9 | #
10 | two: {}
11 | # column: value
12 |
--------------------------------------------------------------------------------
/test/fixtures/rewards.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | # This model initially had no columns defined. If you add columns to the
4 | # model remove the '{}' from the fixture names and add the columns immediately
5 | # below each fixture, per the syntax in the comments below
6 | #
7 | one: {}
8 | # column: value
9 | #
10 | two: {}
11 | # column: value
12 |
--------------------------------------------------------------------------------
/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | # This model initially had no columns defined. If you add columns to the
4 | # model remove the '{}' from the fixture names and add the columns immediately
5 | # below each fixture, per the syntax in the comments below
6 | #
7 | one: {}
8 | # column: value
9 | #
10 | two: {}
11 | # column: value
12 |
--------------------------------------------------------------------------------
/test/fixtures/api/categories.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | # This model initially had no columns defined. If you add columns to the
4 | # model remove the '{}' from the fixture names and add the columns immediately
5 | # below each fixture, per the syntax in the comments below
6 | #
7 | one: {}
8 | # column: value
9 | #
10 | two: {}
11 | # column: value
12 |
--------------------------------------------------------------------------------
/frontend/components/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import HeaderContainer from './header/header_container';
3 | import Footer from './footer';
4 | import { Link } from 'react-router';
5 |
6 |
7 | const App = ({children}) => {
8 | return(
9 |
10 |
11 | {children}
12 |
13 |
14 | );
15 | };
16 |
17 |
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/frontend/reducers/category_reducer.js:
--------------------------------------------------------------------------------
1 | import {RECEIVE_CATEGORIES, RECEIVE_CATEGORY_ERRORS} from '../actions/category_actions';
2 | import merge from 'lodash/merge';
3 |
4 |
5 | const categoryReducer = (state = null, action) => {
6 | switch (action.type) {
7 | case RECEIVE_CATEGORIES:
8 | return action.categories;
9 | default:
10 | return state;
11 | }
12 | };
13 |
14 | export default categoryReducer;
15 |
--------------------------------------------------------------------------------
/test/fixtures/api/contributions.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | # This model initially had no columns defined. If you add columns to the
4 | # model remove the '{}' from the fixture names and add the columns immediately
5 | # below each fixture, per the syntax in the comments below
6 | #
7 | one: {}
8 | # column: value
9 | #
10 | two: {}
11 | # column: value
12 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/db/migrate/20170425214923_fix.rb:
--------------------------------------------------------------------------------
1 | class Fix < ActiveRecord::Migration
2 | def change
3 | #NOT NEEDED
4 | # drop_table :category
5 | # drop_table :contribution
6 |
7 | create_table :categories do |t|
8 | t.string :type
9 |
10 | t.timestamps null: false
11 | end
12 |
13 | create_table :contributions do |t|
14 | t.integer :user_id
15 | t.integer :reward_id
16 | t.integer :amount
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/frontend/actions/search_action.js:
--------------------------------------------------------------------------------
1 | import * as SearchApi from '../util/search_api_util';
2 | export const RECEIVE_SEARCH = "RECEIVE_SEARCH";
3 |
4 | export const receiveSearch = (campaigns) => {
5 | return {
6 | type: RECEIVE_SEARCH,
7 | campaigns: campaigns
8 | };
9 | };
10 |
11 | export const searchCampaigns = (searchField) => (dispatch) => {
12 | return SearchApi.searchCampaigns(searchField)
13 | .then((camps) => dispatch(receiveSearch(camps)));
14 | };
15 |
--------------------------------------------------------------------------------
/db/migrate/20170420142900_create_campaigns.rb:
--------------------------------------------------------------------------------
1 | class CreateCampaigns < ActiveRecord::Migration
2 | def change
3 | create_table :campaigns do |t|
4 | t.integer :user_id, null: false
5 | t.string :title
6 | t.text :descriptions
7 | t.string :tagline
8 | t.integer :category_id
9 | t.string :image_url
10 | t.date :end_date
11 | t.float :target_amount
12 |
13 | t.timestamps null: false
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/app/controllers/api/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::SessionsController < ApplicationController
2 |
3 | def create
4 | @user = User.find_by_credentials(params[:user][:email], params[:user][:password])
5 |
6 | if @user
7 | login!(@user)
8 | render :show
9 | else
10 | render json: @user.errors.full_messages, status: 422
11 | end
12 | end
13 |
14 | def destroy
15 | @user = current_user
16 | logout!
17 | render :show
18 | end
19 |
20 | end
21 |
--------------------------------------------------------------------------------
/frontend/reducers/selectors.js:
--------------------------------------------------------------------------------
1 | export const selectCampaigns = (state) => {
2 |
3 | if (state.campaigns.campaign) {
4 | return Object.keys(state.campaigns.campaign).map(key => state.campaigns.campaign[key]);
5 | } else {
6 | return [null];
7 | }
8 |
9 | };
10 |
11 | export const selectRewards = (state) => {
12 |
13 | if (state.rewards) {
14 | return Object.keys(state.rewards).map(key => state.rewards[key]);
15 | } else {
16 | return [null];
17 | }
18 |
19 | };
20 |
--------------------------------------------------------------------------------
/frontend/util/auth_api_util.js:
--------------------------------------------------------------------------------
1 | export const signUp = (user) => {
2 |
3 | return $.ajax({
4 | url: '/api/users',
5 | method: 'POST',
6 | data: {user}
7 | });
8 | };
9 |
10 |
11 | export const signIn = (user) => {
12 |
13 | return $.ajax({
14 | url: '/api/session',
15 | method: 'POST',
16 | data: {user}
17 | });
18 | };
19 |
20 | export const signOut = () => {
21 |
22 | return $.ajax({
23 | url: `/api/session/`,
24 | method: 'DELETE'
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/frontend/reducers/tile_slider_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_CAMPAIGNS, RECEIVE_ERRORS} from '../actions/campaign_actions';
2 | import merge from 'lodash/merge';
3 |
4 | const TileSliderReducer = (oldState={}, action) => {
5 |
6 | Object.freeze(oldState);
7 | switch(action.type) {
8 | case "RECEIVE_CAMPAIGNS":
9 | const allCampaigns = action.campaigns;
10 | // this returns an array
11 | return allCampaigns;
12 | default:
13 | return oldState;
14 | }
15 |
16 | };
17 |
18 | export default TileSliderReducer;
19 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/config/initializers/to_time_preserves_timezone.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Preserve the timezone of the receiver when calling to `to_time`.
4 | # Ruby 2.4 will change the behavior of `to_time` to preserve the timezone
5 | # when converting to an instance of `Time` instead of the previous behavior
6 | # of converting to the local system timezone.
7 | #
8 | # Rails 5.0 introduced this config option so that apps made with earlier
9 | # versions of Rails are not affected when upgrading.
10 | ActiveSupport.to_time_preserves_timezone = true
11 |
--------------------------------------------------------------------------------
/frontend/reducers/user_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_USER, UPDATE_USER, RECEIVE_ERRORS } from '../actions/user_actions';
2 | import { RECEIVE_CURRENT_USER } from '../actions/session_actions';
3 | import merge from 'lodash/merge';
4 |
5 | const UserReducer = (state = {}, action) => {
6 |
7 | switch(action.type) {
8 | case RECEIVE_USER:
9 | let x = merge({}, state, action.user);
10 | return x;
11 | case UPDATE_USER:
12 | return merge({}, state, action.user);
13 | default:
14 | return state;
15 | }
16 | };
17 |
18 | export default UserReducer;
19 |
--------------------------------------------------------------------------------
/db/migrate/20170514165849_create_pg_search_documents.rb:
--------------------------------------------------------------------------------
1 | class CreatePgSearchDocuments < ActiveRecord::Migration
2 | def self.up
3 | say_with_time("Creating table for pg_search multisearch") do
4 | create_table :pg_search_documents do |t|
5 | t.text :content
6 | t.belongs_to :searchable, :polymorphic => true, :index => true
7 | t.timestamps null: false
8 | end
9 | end
10 | end
11 |
12 | def self.down
13 | say_with_time("Dropping table for pg_search multisearch") do
14 | drop_table :pg_search_documents
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/frontend/components/categories/category_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import {getCategories} from '../../actions/category_actions';
3 | import CategoryIndex from './category_index';
4 |
5 |
6 | const mapStateToProps = (state) => {
7 |
8 | return {
9 | categories: state.categories,
10 | errors: state.errors
11 | };
12 | };
13 |
14 | const mapDispatchToProps = (dispatch) => {
15 | return {
16 | getCategories: () => dispatch(getCategories())
17 | };
18 | };
19 |
20 | export default connect(mapStateToProps, mapDispatchToProps)(CategoryIndex);
21 |
--------------------------------------------------------------------------------
/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] if respond_to?(:wrap_parameters)
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/campaign/new_campaign_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createCampaign } from '../../actions/campaign_actions';
3 | import NewCampaign from './new_campaign';
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | return {
8 | user: state.session.currentUser,
9 | errors: state.campaigns.errors
10 | };
11 | };
12 |
13 | const mapDispatchToProps = (dispatch) => {
14 |
15 | return {
16 | createCampaign: (camp) => dispatch(createCampaign(camp))
17 | };
18 | };
19 |
20 | export default connect(mapStateToProps, mapDispatchToProps)(NewCampaign);
21 |
--------------------------------------------------------------------------------
/frontend/components/home_page/tile_slider_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { fetchCampaigns } from '../../actions/campaign_actions';
3 | import TileSlider from './tile_slider';
4 | import { selectCampaigns } from '../../reducers/selectors';
5 |
6 | const mapStateToProps = (state) => {
7 |
8 | return {
9 | campaigns: selectCampaigns(state),
10 | };
11 | };
12 |
13 | const mapDispatchToProps = (dispatch) => {
14 |
15 | return {
16 | fetchCampaigns: (num) => dispatch(fetchCampaigns(num))
17 | };
18 | };
19 |
20 | export default connect(mapStateToProps, mapDispatchToProps)(TileSlider);
21 |
--------------------------------------------------------------------------------
/frontend/components/home_page/discovery.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TileSliderContainer from './tile_slider_container';
3 | import { Link } from 'react-router';
4 | class Discovery extends React.Component {
5 |
6 | render() {
7 |
8 | return(
9 |
10 |
11 |
Top Picks
12 |
13 | See All
14 |
15 |
16 |
17 |
18 | );
19 | }
20 | }
21 |
22 | export default Discovery;
23 |
--------------------------------------------------------------------------------
/app/views/api/campaigns/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # json.extract! @campaign, :user_id, :title, :descriptions,
2 | # :tagline, :category_id, asset_path(:image[:url]), :end_date, :target_amount
3 |
4 |
5 | json.id @campaign.id
6 | json.total_contributions @total_contributions
7 | json.num_contributions @num_contributions
8 | json.user_id @campaign.user_id
9 | json.title @campaign.title
10 | json.descriptions @campaign.descriptions
11 | json.tagline @campaign.tagline
12 | json.category_id @campaign.category_id
13 | json.image_url asset_path(@campaign.image.url)
14 | json.end_date @campaign.end_date
15 | json.target_amount @campaign.target_amount
16 |
17 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: postgresql
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: IndieFomo_development
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: IndieFomo_test
22 |
23 | production:
24 | <<: *default
25 | database: IndieFomo_production
26 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/user_contributions_list.css:
--------------------------------------------------------------------------------
1 |
2 | .table-names {
3 | height: 30px;
4 | width: 100%;
5 | display: flex;
6 | flex-direction: row;
7 | justify-content: space-between;
8 | align-items: center;
9 | }
10 |
11 | .table-col {
12 | font-size: 18px;
13 | border-bottom: 1px solid #e4e4e4;
14 | height: 100%;
15 | width: 25%;
16 | }
17 |
18 | .link-nav {
19 | color: black;
20 | }
21 |
22 | .user-reward-item {
23 | height: 30px;
24 | display: flex;
25 | flex-direction: row;
26 | justify-content: space-between;
27 | align-items: center;
28 | }
29 |
30 | .table-row {
31 | margin-top: 20px;
32 | height: 100%;
33 | width: 25%;
34 | }
35 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/progress_bar.css:
--------------------------------------------------------------------------------
1 | .progress-bar {
2 | height: 25%;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: flex-start;
6 | }
7 |
8 |
9 | .dollars {
10 | font-size: 20px;
11 | }
12 |
13 | .funds-1 {
14 | font-size: 14px;
15 | }
16 |
17 | .bar {
18 | display: inline-block;
19 | min-height: 7px;
20 | background-color: #f5f5f5;
21 | width: 100%;
22 | margin-top: 5px;
23 | margin-bottom: 5px;
24 | }
25 |
26 | .percentage {
27 | max-width: 100%;
28 | background-color: #0eb4b6;
29 | min-height: 7px;
30 | }
31 |
32 | .funds-2 {
33 | margin-top: 2px;
34 | font-size: 14px;
35 | }
36 |
37 | .moment {
38 | font-size: 14px;
39 | }
40 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | # Prevent CSRF attacks by raising an exception.
3 | # For APIs, you may want to use :null_session instead.
4 | protect_from_forgery with: :exception
5 |
6 | helper_method :current_user, :logged_in?
7 |
8 | def current_user
9 | @current_user ||= User.find_by(session_token: session[:session_token])
10 | end
11 |
12 | def login!(user)
13 | @current_user = user
14 | session[:session_token] = user.reset_session_token!
15 | end
16 |
17 | def logged_in?
18 | !!current_user
19 | end
20 |
21 | def logout!
22 | session[:session_token] = nil
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/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.
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 |
--------------------------------------------------------------------------------
/.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 the default SQLite database.
11 | /db/*.sqlite3
12 | /db/*.sqlite3-journal
13 |
14 | # Ignore all logfiles and tempfiles.
15 | /log/*
16 | !/log/.keep
17 | /tmp
18 |
19 | node_modules/
20 | bundle.js
21 | bundle.js.map
22 | .byebug_history
23 | .DS_Store
24 | npm-debug.log
25 |
26 | # Ignore application configuration
27 | /config/application.yml
28 |
--------------------------------------------------------------------------------
/app/controllers/api/contributions_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::ContributionsController < ApplicationController
2 |
3 | def create
4 |
5 | @contribution = Contribution.create(contribution_params)
6 | if contribution_params[:amount].to_i <= 0
7 | render json: "Please enter a valid amount.", status: 422
8 |
9 | elsif @contribution.save
10 | render :show
11 | else
12 | render json: @contribution.errors.full_messages, status: 422
13 | end
14 | end
15 |
16 | def show
17 | @contrubution = Contribution.find(params[:id])
18 | render :show
19 | end
20 |
21 | private
22 | def contribution_params
23 | params.permit(:user_id, :reward_id, :amount, :campaign_id, :date)
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/frontend/actions/category_actions.js:
--------------------------------------------------------------------------------
1 | import * as CategoryApiUtil from '../util/category_api_util';
2 | export const RECEIVE_CATEGORIES = 'RECEIVE_CATEGORIES';
3 | export const RECEIVE_CATEGORY_ERRORS = 'RECEIVE_CATEGORY_ERRORS';
4 |
5 | export const receiveCategories = (categories) => {
6 | return {
7 | type: RECEIVE_CATEGORIES,
8 | categories: categories
9 | };
10 | };
11 |
12 | export const receiveCategoryErrors = (err) => {
13 | return {
14 | type: RECEIVE_CATEGORY_ERRORS,
15 | error: err
16 | };
17 | };
18 |
19 | export const getCategories = () => (dispatch) => {
20 | return CategoryApiUtil.getCategories()
21 | .then((cat) => dispatch(receiveCategories(cat)),
22 | (err) => dispatch(receiveCategoryErrors(err)));
23 | };
24 |
--------------------------------------------------------------------------------
/frontend/components/session/session_form_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { login, logout, signup } from '../../actions/session_actions';
3 | import SessionForm from './session_form';
4 |
5 | const mapStateToProps = ( { session }) => {
6 |
7 | return {
8 | loggedIn: Boolean(session.currentUser),
9 | errors: session.errors
10 | };
11 | };
12 |
13 | const mapDispatchToProps = (dispatch, ownProps) => {
14 | const formType = ownProps.formType;
15 | const processForm = (formType === 'login') ? login : signup;
16 |
17 | return {
18 | processForm: (user) => dispatch(processForm(user)),
19 | formType
20 | };
21 | };
22 |
23 | export default connect(mapStateToProps, mapDispatchToProps)(SessionForm);
24 |
--------------------------------------------------------------------------------
/frontend/indie_fomo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { signUp, signIn, signOut} from './util/auth_api_util';
4 | import configureStore from './store/store';
5 | import Root from './components/root';
6 | import Modal from 'react-modal';
7 |
8 |
9 |
10 | document.addEventListener('DOMContentLoaded', () => {
11 | let store;
12 | if (window.currentUser) {
13 | const preloadedState = {session: {currentUser: window.currentUser, errors: {}}};
14 | store = configureStore(preloadedState);
15 | } else {
16 | store = configureStore();
17 | }
18 |
19 | Modal.setAppElement(document.body);
20 | const root = document.getElementById('root');
21 | ReactDOM.render( , root);
22 | });
23 |
--------------------------------------------------------------------------------
/app/controllers/api/charges_controller.rb:
--------------------------------------------------------------------------------
1 | # class Api::ChargesController < ApplicationController
2 | #
3 | # def new
4 | # end
5 | #
6 | # def create
7 | # # default for now. Stripe ammounts must be in cents
8 | #
9 | # @amount = params[:amount]
10 | #
11 | # customer = Stripe::Customer.create(
12 | # :email => params[:stripeEmail],
13 | # :source => params[:stripeToken]
14 | # )
15 | #
16 | # charge = Stripe::Charge.create(
17 | # :customer => customer.id,
18 | # :amount => @amount,
19 | # :description => 'Rails Stripe customer',
20 | # :currency => 'usd'
21 | # )
22 | #
23 | # rescue = Stripe::CardError => e
24 | # flash[:error] = e.message
25 | # redirect_to new_charge_path
26 | # end
27 | #
28 | # end
29 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | IndieFomo
5 |
6 |
7 | <%= favicon_link_tag 'IndieFomoFavicon.ico' %>
8 |
9 | <%= stylesheet_link_tag 'application', media: 'all' %>
10 | <%= javascript_include_tag 'application' %>
11 | <%= csrf_meta_tags %>
12 |
13 |
14 |
15 | <%= yield %>
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/db/migrate/20170418155621_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 :first_name, null: false
6 | t.string :last_name, null: false
7 | t.text :about_me, default: ""
8 | t.string :email, null: false
9 | t.string :password_digest, null: false
10 | t.string :session_token, null: false
11 |
12 | t.timestamps null: false
13 | end
14 |
15 | add_index :users, :username, unique: true
16 | add_index :users, :first_name, unique: true
17 | add_index :users, :last_name, unique: true
18 | add_index :users, :email, unique: true
19 | add_index :users, :session_token, unique: true
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/footer.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | width: 100%;
3 | height: 200px;
4 | background-color: black;
5 | display: flex;
6 | margin-top: 150px;
7 | color: white;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: space-around;
11 | align-items: center;
12 | }
13 |
14 | .contact {
15 | display: flex;
16 | width: 100%;
17 | justify-content: space-around;
18 | align-items: center;
19 | }
20 |
21 | .linkedin {
22 | width: 70%;
23 | height: 70%;
24 | padding: 10px;
25 | }
26 |
27 | .personal {
28 | width: 55%;
29 | height: 55%;
30 | padding: 10px;
31 | }
32 |
33 | .github {
34 | width: 120%;
35 | height: 120%;
36 | }
37 |
38 | .patrycja {
39 | width: 75%;
40 | font-size: 14px;
41 | text-align: left;
42 | }
43 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/reset.css:
--------------------------------------------------------------------------------
1 | html, body, div, span, h1, h2, h3, h4, p, a, img,
2 | figure, canvas, aside article, table, section,
3 | nav, menu, main, video, Link, button, label, input, form,
4 | ul, li, main{
5 | margin: 0;
6 | padding: 0;
7 | border: 0;
8 | font-size: 100%;
9 | font-family: 'Maven Pro', sans-serif;;
10 | font: inherit;
11 | background: transparent;
12 | }
13 |
14 | a {
15 | text-decoration: none;
16 | }
17 |
18 | ul, li {
19 | list-style: none;
20 | display: inline-block;
21 | }
22 | /* This is useful when implementing a grid system so that all image elements
23 | will take up 100% width of their containers */
24 | img {
25 | width: 100%
26 | }
27 |
28 | html, body{
29 | height:100%;
30 | font-family: 'Maven Pro', sans-serif;;
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/components/home_page/home_page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 | import Carousel from './carousel_component';
4 | import Discovery from './discovery';
5 | import CategoryContainer from '../categories/category_container';
6 |
7 |
8 | class HomePage extends React.Component {
9 |
10 | render() {
11 | return(
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
29 | export default withRouter(HomePage);
30 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/user_campaign_list.css:
--------------------------------------------------------------------------------
1 | .usr-campaign-list {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .profile-content-container {
7 | width: 85%;
8 | }
9 |
10 | .user-campaign-item {
11 | display: flex;
12 | flex-direction: row;
13 | border-bottom: 1px solid #e4e4e4;
14 | padding-top: 10px;
15 | padding-bottom: 10px;
16 | }
17 |
18 | .list-img {
19 | width: 160px;
20 | height: 160px;
21 | }
22 |
23 | .user-campaign-item-details {
24 | margin-left: 30px;
25 | }
26 |
27 | .stats-u {
28 | padding-top: 10px;
29 | height: 50px;
30 | display: flex;
31 | flex-direction: column;
32 | justify-content: flex-start;
33 | }
34 |
35 | .UCTitle {
36 | font-size: 18px;
37 | color: #FC3468;
38 | }
39 |
40 | .stats-span {
41 | color: black;
42 | }
43 |
--------------------------------------------------------------------------------
/app/views/api/campaigns/index.json.jbuilder:
--------------------------------------------------------------------------------
1 |
2 | @campaigns.each do |campaign|
3 |
4 | json.set! campaign.id do
5 | json.num_contributions Contribution.where(campaign_id: campaign.id).count
6 | json.total_contributions Contribution.where(campaign_id: campaign.id).sum(:amount)
7 | json.user campaign.user
8 | json.id campaign.id
9 | json.title campaign.title
10 | json.descriptions campaign.descriptions
11 | json.tagline campaign.tagline
12 | json.category_id campaign.category_id
13 | json.image_url campaign.image_url
14 | json.end_date campaign.end_date
15 | json.target_amount campaign.target_amount
16 | json.image_url asset_path(campaign.image.url)
17 | if campaign.category
18 | json.cat campaign.category.cat
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/docs/api-endpoints:
--------------------------------------------------------------------------------
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/profile`
14 | - `PATCH /api/profile/:id`
15 | - `GET /api/profile/:id`
16 |
17 | ### Session
18 |
19 | - `POST /api/session`
20 | - `DELETE /api/session`
21 |
22 | ### Campaign
23 |
24 | - `GET /api/campaign/:id`
25 | - `GET /api/campaign/new`
26 | - `PATCH /api/campaign/:id`
27 | - `POST /api/campaign`
28 | - `DELETE /api/campaign/:id`
29 |
30 | ### Category
31 | - `GET /api/category/:id`
32 |
33 | ### Contribution
34 | - `GET /api/contribution/:id`
35 | - `POST /api/contribution`
36 |
37 | ### Reward
38 | - `GET api/reward/:id/contributions`
39 | - `POST /api/reward/contributions`
40 | - `PATCH /api/reward/:id/contributions`
41 |
--------------------------------------------------------------------------------
/app/assets/images/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/components/categories/category_tile.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter, hashHistory } from 'react-router';
3 |
4 | class CategoryTile extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | handleClick(e) {
10 | e.preventDefault();
11 | const url = `/categories/${this.props.category.id}/campaigns`;
12 | this.props.router.push(url);
13 | }
14 |
15 | render() {
16 | return(
17 |
23 | );
24 | }
25 |
26 | }
27 | export default withRouter(CategoryTile);
28 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/searchbar.css:
--------------------------------------------------------------------------------
1 | .searchResults-true {
2 | background: white;
3 | position: fixed;
4 | margin-top: 35px;
5 | width: 300px;
6 | z-index: 10;
7 | display: flex;
8 | overflow: hidden;
9 | overflow-y: hidden;
10 | flex-direction: column;
11 | }
12 |
13 | .searchResults-false {
14 | position: fixed;
15 | width: 273px;
16 | height: 5px;
17 | }
18 |
19 | .s-result {
20 | background: white;
21 | border: 1px solid #dad7d7;
22 | padding: 10px;
23 | color: black;
24 | height: 20px;
25 |
26 | }
27 |
28 | .s-result:hover {
29 | cursor: pointer;
30 | color: #FC3468;
31 | }
32 |
33 | .explore-dropdown {
34 | width: 100px;
35 | }
36 |
37 | .search {
38 | display: flex;
39 | justify-content: center;
40 | height: 34px;
41 | width: 200px;
42 | }
43 |
44 | .searchBar {
45 | width: 200px;
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/components/user/user_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { getUser, editUser } from '../../actions/user_actions';
3 | import UserShow from './user_show';
4 | import { fetchCampaign } from '../../actions/campaign_actions';
5 |
6 | const mapStateToProps = (state, ownProps) => {
7 |
8 | return {
9 | user: state.user,
10 | loggedIn: Boolean(state.session.currentUser),
11 | campaigns: state.user.campaigns,
12 | contributions: state.user.contributions
13 | };
14 | };
15 |
16 | const mapDispatchToProps = (dispatch) => {
17 |
18 | return {
19 | getUser: (id) => dispatch(getUser(id)),
20 | editUser: (user) => dispatch(editUser(user)),
21 | fetchCampaign: (id) => dispatch(fetchCampaign(id))
22 | };
23 | };
24 |
25 | export default connect(mapStateToProps, mapDispatchToProps)(UserShow);
26 |
--------------------------------------------------------------------------------
/frontend/reducers/session_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_CURRENT_USER, RECEIVE_SESSION_ERRORS} from '../actions/session_actions';
2 | import merge from 'lodash/merge';
3 |
4 | const _nullUser = Object.freeze({
5 | currentUser: null,
6 | errors: null
7 | });
8 |
9 | const SessionReducer = (state = _nullUser, action) => {
10 |
11 | switch (action.type) {
12 | case RECEIVE_CURRENT_USER:
13 | const currentUser = action.currentUser;
14 | if (currentUser) {
15 | return merge({}, state, {currentUser: currentUser, errors: null} );
16 | } else {
17 | return _nullUser;
18 | }
19 | case RECEIVE_SESSION_ERRORS:
20 | const errors = action.errors.base[0];
21 | return merge({}, _nullUser, {errors: errors});
22 | default:
23 | return state;
24 | }
25 | };
26 |
27 | export default SessionReducer;
28 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/category_show.css:
--------------------------------------------------------------------------------
1 | .category-show-wrapper {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | width: 80%;
7 | display: block;
8 | align-items: center;
9 | margin-top: 25px;
10 | margin-bottom: 50px;
11 | }
12 |
13 | .category-show-wrapper > h1 {
14 | /*border: 4px solid blue;*/
15 | height: 30px;
16 | color: black;
17 | font-size: 24px;
18 | width: 65%;
19 | margin-top: 40px;
20 | margin-bottom: 40px;
21 | margin-left: 50px;
22 | }
23 |
24 | .category-campaign-tile {
25 | box-sizing: border-box;
26 | display: flex;
27 | flex-direction: row;
28 | justify-content: flex-start;
29 | align-items: center;
30 | margin-top: 1%;
31 | height: 90%;
32 | width: 100%;
33 | margin: 0;
34 | flex-wrap: wrap;
35 | margin-left: 50px;
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/app/views/static_pages/root.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
--------------------------------------------------------------------------------
/frontend/reducers/root_reducer.js:
--------------------------------------------------------------------------------
1 | import {combineReducers} from 'redux';
2 | import SessionReducer from './session_reducer';
3 | import CampaignsReducer from './campaign_reducer';
4 | import UserReducer from './user_reducer';
5 | import TileSliderReducer from './tile_slider_reducer';
6 | import ContributionReducer from './contribution_reducer';
7 | import RewardsReducer from './rewards_reducer';
8 | import categoryReducer from './category_reducer';
9 | import SearchReducer from './search_reducer';
10 |
11 |
12 | const RootReducer = combineReducers({
13 | session: SessionReducer,
14 | campaigns: CampaignsReducer,
15 | user: UserReducer,
16 | slider: TileSliderReducer,
17 | contribution: ContributionReducer,
18 | rewards: RewardsReducer,
19 | categories: categoryReducer,
20 | searchResults: SearchReducer
21 | });
22 |
23 | export default RootReducer;
24 |
--------------------------------------------------------------------------------
/frontend/reducers/rewards_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_REWARD, RECEIVE_ALL_REWARDS, RECEIVE_REWARD_ERRORS, DELETE_REWARD} from '../actions/reward_actions';
2 | import merge from 'lodash/merge';
3 |
4 |
5 | const RewardsReducer = (state = {}, action) => {
6 |
7 | switch (action.type) {
8 | case RECEIVE_REWARD:
9 | return merge({}, state, {[action.reward.id]: action.reward});
10 | case RECEIVE_ALL_REWARDS:
11 | const oldS = merge({}, state);
12 | if (action.rewards.id) {
13 | return merge({}, oldS, {[action.rewards.id]: action.rewards});
14 | } else {
15 | return state;
16 | }
17 | case DELETE_REWARD:
18 | let newState = merge({}, state);
19 | delete newState[action.reward.id];
20 |
21 | return newState;
22 | default:
23 | return state;
24 | }
25 | };
26 |
27 | export default RewardsReducer;
28 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/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(255, 255, 255, 0.75)'
9 | },
10 | content : {
11 | position : 'absolute',
12 | top : '20%',
13 | left : '35%',
14 | right : '35%',
15 | bottom : '20%',
16 | border : '1px solid #ccc',
17 | background : '#fff',
18 | overflow : 'auto',
19 | WebkitOverflowScrolling : 'touch',
20 | borderRadius : '4px',
21 | outline : 'none',
22 | padding : '20px'
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/components/campaign/edit_campaign_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { fetchCampaign, updateCampaign, updateCampaignForm } from '../../actions/campaign_actions';
3 | import EditCampaign from './edit_campaign_component';
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 |
7 | let campaign = state.campaigns.campaign;
8 |
9 | return {
10 | campaign,
11 | user: state.session.currentUser,
12 | errors: state.campaigns.errors
13 | };
14 | };
15 |
16 | const mapDispatchToProps = (dispatch) => {
17 | return {
18 | fetchCampaign: (id) => dispatch(fetchCampaign(id)),
19 | updateCampaign: (campaign) => dispatch(updateCampaign(campaign)),
20 | updateCampaignForm: (campaign) => dispatch(updateCampaignForm(campaign))
21 | };
22 | };
23 |
24 | export default connect(mapStateToProps, mapDispatchToProps)(EditCampaign);
25 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | const webpack = require('webpack');
3 | const prod = process.argv.indexOf('-p') !== -1;
4 |
5 | module.exports = {
6 | entry: "./frontend/indie_fomo.jsx",
7 | output: {
8 | path: path.resolve(__dirname, 'app', 'assets', 'javascripts'),
9 | filename: "bundle.js"
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test: [/\.jsx?$/],
15 | exclude: /(node_modules)/,
16 | loader: 'babel-loader',
17 | query: {
18 | presets: ['es2015', 'react']
19 | }
20 | }
21 | ]
22 | },
23 | devtool: 'source-map',
24 | resolve: {
25 | extensions: ['.js', '.jsx', '*']
26 | },
27 | plugins: [
28 | new webpack.DefinePlugin({
29 | 'process.env': {
30 | NODE_ENV: JSON.stringify('production')
31 | }
32 | }),
33 | new webpack.optimize.UglifyJsPlugin()
34 | ],
35 | };
36 |
--------------------------------------------------------------------------------
/frontend/util/rewards_api_util.js:
--------------------------------------------------------------------------------
1 | export const createReward = (reward) => {
2 |
3 | return $.ajax({
4 | url: `api/campaigns/${reward.campaign_id}/rewards`,
5 | method: 'POST',
6 | data: reward,
7 | });
8 | };
9 |
10 | export const editReward = (reward) => {
11 | return $.ajax({
12 | url: `api/campaigns/${reward.campaign_id}/rewards/${reward.id}`,
13 | method: 'PATCH',
14 | data: reward
15 | });
16 | };
17 |
18 | export const deleteReward = (reward) => {
19 |
20 | return $.ajax({
21 | url: `api/campaigns/${reward.campaign_id}/rewards/${reward.id}`,
22 | method: 'DELETE'
23 | });
24 | };
25 |
26 | export const getReward = (reward) => {
27 | return $.ajax({
28 | url: `api/campaigns/${reward.campaign_id}/rewards/${reward.id}`,
29 | });
30 | };
31 |
32 | export const getAllRewards = (id) => {
33 | return $.ajax({
34 | url: `api/campaigns/${id}/rewards`
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 |
4 | # path to your application root.
5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
6 |
7 | Dir.chdir APP_ROOT do
8 | # This script is a starting point to setup your application.
9 | # Add necessary setup steps to this file:
10 |
11 | puts "== Installing dependencies =="
12 | system "gem install bundler --conservative"
13 | system "bundle check || bundle install"
14 |
15 | # puts "\n== Copying sample files =="
16 | # unless File.exist?("config/database.yml")
17 | # system "cp config/database.yml.sample config/database.yml"
18 | # end
19 |
20 | puts "\n== Preparing database =="
21 | system "bin/rake db:setup"
22 |
23 | puts "\n== Removing old logs and tempfiles =="
24 | system "rm -f log/*"
25 | system "rm -rf tmp/cache"
26 |
27 | puts "\n== Restarting application server =="
28 | system "touch tmp/restart.txt"
29 | end
30 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/api/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(255, 255, 255, 0.75)'
9 | },
10 | content : {
11 | position : 'absolute',
12 | top : '10%',
13 | left : '40%',
14 | right : '40%',
15 | bottom : '10%',
16 | border : '1px solid #ccc',
17 | background : '#f5f5f5',
18 | overflow : 'auto',
19 | WebkitOverflowScrolling : 'touch',
20 | borderRadius : '4px',
21 | outline : 'none',
22 | padding : '20px',
23 |
24 |
25 | }
26 | };
27 | Contact GitHub API Training Shop Blog About
28 |
--------------------------------------------------------------------------------
/app/models/campaign.rb:
--------------------------------------------------------------------------------
1 | class Campaign < ActiveRecord::Base
2 | include PgSearch
3 | validates_numericality_of :target_amount, {greater_than_or_equal_to: 1, allow_nil: false, message: "Please enter an amount greater than zero!"}
4 | validates :title, length: { minimum: 1, allow_nil: false, message: "Please enter a title!"}
5 |
6 | pg_search_scope :search_by_title, :against => {
7 | :title => 'A',
8 | :tagline => 'B',
9 | }, :using => {
10 | :tsearch => {:prefix => true}
11 | }
12 |
13 | has_many :rewards
14 | belongs_to :category
15 | has_many :contributions,
16 | primary_key: :id,
17 | foreign_key: :campaign_id,
18 | class_name: "Contribution"
19 |
20 | belongs_to :user,
21 | primary_key: :id,
22 | foreign_key: :user_id,
23 | class_name: "User"
24 |
25 | has_attached_file :image, default_url: "img_placeholder.png"
26 | validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/app/controllers/api/users_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::UsersController < ApplicationController
2 |
3 | def create
4 | @user = User.create(user_params)
5 |
6 | if @user.save
7 | login!(@user)
8 | render :show
9 | else
10 | render json: @user.errors.full_messages, status:422
11 | end
12 | end
13 |
14 | def update
15 | @user = User.find(params[:id])
16 | @contributions = Contribution.where(user_id: params[:id])
17 |
18 | if @user.update(user_params)
19 | @user.save
20 | render :show
21 |
22 | else
23 | render json: @user.errors.full_messages, status:422
24 |
25 | end
26 | end
27 |
28 | def show
29 | @user = User.find(params[:id])
30 | @contributions = Contribution.where(user_id: params[:id])
31 | render :show
32 | end
33 |
34 | private
35 | def user_params
36 | params.require(:user).permit(:password, :email, :first_name, :last_name, :image, :about_me)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/frontend/components/header/header_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { logout, loginguest } from '../../actions/session_actions';
3 | import Header from './header';
4 | import { searchCampaigns } from '../../actions/search_action';
5 | import { getCategories } from '../../actions/category_actions';
6 |
7 | const mapStateToProps = (state) => {
8 |
9 | return {
10 | currentUser: state.session.currentUser,
11 | loggedIn: Boolean(state.session.currentUser),
12 | searchResults: state.searchResults,
13 | categories: state.categories
14 | };
15 | };
16 |
17 | const mapDispatchToProps = (dispatch) => {
18 |
19 | return {
20 | logout: () => dispatch(logout()),
21 | loginguest: () => dispatch(loginguest()),
22 | searchCampaigns: (sf) => dispatch(searchCampaigns(sf)),
23 | getCategories: () => dispatch(getCategories())
24 | };
25 | };
26 |
27 | export default connect(mapStateToProps, mapDispatchToProps)(Header);
28 |
--------------------------------------------------------------------------------
/app/controllers/api/rewards_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::RewardsController < ApplicationController
2 |
3 | def index
4 | @campaign = Campaign.find(params[:campaign_id])
5 | render :index
6 | end
7 |
8 | def show
9 | @reward = Reward.find(params[:id])
10 | render :show
11 | end
12 |
13 |
14 | def create
15 |
16 | @reward = Reward.create(reward_params)
17 |
18 | if @reward.save
19 | render :show
20 | else
21 | render json: "Error, please try again!", status: 422
22 | end
23 | end
24 |
25 | def update
26 | @reward = Reward.find(params[:id])
27 | if @reward.save
28 | render :show
29 | else
30 | render json: "Update uncussesful, please try again!", status: 422
31 | end
32 | end
33 |
34 | def destroy
35 |
36 | @reward = Reward.find(params[:id])
37 | @reward.destroy
38 | render :show
39 | end
40 |
41 | def reward_params
42 | params.permit(:campaign_id, :title, :description, :price)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/frontend/components/campaign/campaign_index_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { fetchCampaigns, fetchCampaign } from '../../actions/campaign_actions';
3 | import CampaignIndex from './campaign_index';
4 | import { selectCampaigns } from '../../reducers/selectors';
5 | import { makeContribution } from '../../actions/contribution_actions';
6 |
7 | const mapStateToProps = (state) => {
8 | let camps = null;
9 | if (state.campaigns){
10 | camps = Object.keys(state.campaigns).map((el) => state.campaigns[el]);
11 | }
12 | return {
13 | campaigns: camps,
14 | errors: state.errors
15 | };
16 | };
17 |
18 | const mapDispatchToProps = (dispatch) => {
19 |
20 | return {
21 | fetchCampaigns: () => dispatch(fetchCampaigns()),
22 | fetchCampaign: (id) => dispatch(fetchCampaign(id)),
23 | makeContribution: (contribution) => dispatch(makeContribution(contribution))
24 | };
25 | };
26 |
27 | export default connect(mapStateToProps, mapDispatchToProps)(CampaignIndex);
28 |
--------------------------------------------------------------------------------
/frontend/reducers/contribution_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_CONTRIBUTION, RECEIVE_CONTRIBUTION_ERRORS } from '../actions/contribution_actions';
2 | import { RECEIVE_CAMPAIGNS, RECEIVE_CAMPAIGN } from '../actions/campaign_actions';
3 | import merge from 'lodash/merge';
4 |
5 |
6 | const ContributionReducer = (state = {}, action) => {
7 |
8 | switch (action.type) {
9 | case RECEIVE_CONTRIBUTION:
10 | let old = merge({}, state);
11 | if (old.errors) {
12 | old.errors = "";
13 | }
14 | return merge(old, action.contribution);
15 | case RECEIVE_CONTRIBUTION_ERRORS:
16 | let old1 = merge({}, state);
17 | return merge(old1, { errors: action.errors.responseText});
18 | case RECEIVE_CAMPAIGN:
19 | let old2 = merge({}, state);
20 | return merge(old2, { errors: ""});
21 | case RECEIVE_CAMPAIGNS:
22 | let old3 = merge({}, state);
23 | return merge(old3, { errors: ""});
24 | default:
25 | return state;
26 | }
27 | };
28 |
29 | export default ContributionReducer;
30 |
--------------------------------------------------------------------------------
/frontend/components/home_page/carousel_component.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Slider from 'react-slick';
3 |
4 | class Carousel extends React.Component {
5 | render() {
6 | var settings = {
7 | dots: false,
8 | slidesToShow: 3,
9 | infinite: true,
10 | slidesToScroll: 1,
11 | autoplay: true,
12 | autoplaySpeed: 2000,
13 | arrows: false
14 | };
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
27 | export default Carousel;
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 `rake 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: dca024cf7cfa24fc3eb1d7fcfc0316b408b14316e83db2b4c956695744c8b9acab21decd5f802ecec319d38b045b5aa3c2a35ef0f9885728fb1dd94507bdbe69
15 |
16 | test:
17 | secret_key_base: b9c3d4aff5b163055214d9dd6c03dca1c86cf0f87bf23154bf21a07afc7a58d7bd7acfde29f2ad2ed8b4a949f23c0dd4ddc8eb36517bd8a8cf72dd45dae2a8a0
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 |
--------------------------------------------------------------------------------
/frontend/actions/user_actions.js:
--------------------------------------------------------------------------------
1 | import * as UserAPIUtil from '../util/user_api_util';
2 | export const RECEIVE_USER = "RECEIVE_USER";
3 | export const UPDATE_USER = "UPDATE_USER";
4 | export const RECEIVE_ERRORS = "RECEIVE_ERRORS";
5 |
6 | export const receiveUser = (user) => {
7 |
8 | return {
9 | type: RECEIVE_USER,
10 | user
11 | };
12 | };
13 |
14 | export const updateUser = (user) => {
15 |
16 | return {
17 | type: UPDATE_USER,
18 | user
19 | };
20 | };
21 |
22 | export const receiveErrors = (errors) => {
23 |
24 | return {
25 | type: RECEIVE_ERRORS,
26 | errors
27 | };
28 | };
29 |
30 | export const getUser = (id) => (dispatch) => {
31 |
32 | return UserAPIUtil.getUser(id)
33 | .then((usr) => dispatch(receiveUser(usr)),
34 | (err) => dispatch(receiveErrors(err.responseJSON)));
35 | };
36 |
37 | export const editUser = (user) => (dispatch) => {
38 |
39 | return UserAPIUtil.editUser(user)
40 | .then((usr) => dispatch(receiveUser(usr)),
41 | (err) => dispatch(receiveErrors(err.responseJSON)));
42 | };
43 |
--------------------------------------------------------------------------------
/frontend/actions/contribution_actions.js:
--------------------------------------------------------------------------------
1 | import * as ContributionApiUtil from '../util/contributions_api_util';
2 | export const RECEIVE_CONTRIBUTION = 'RECEIVE_CONTRIBUTION';
3 | export const RECEIVE_CONTRIBUTION_ERRORS = 'RECEIVE_ERRORS';
4 |
5 | export const receiveContribution = (contribution) => {
6 | return {
7 | type: RECEIVE_CONTRIBUTION,
8 | contribution: contribution
9 | };
10 | };
11 |
12 | export const receiveContributionErrors = (errors) => {
13 | return {
14 | type: RECEIVE_CONTRIBUTION_ERRORS,
15 | errors: errors
16 | };
17 | };
18 |
19 | export const getContribution = (id) => (dispatch) => {
20 |
21 | return ContributionApiUtil.getContribution(id)
22 | .then((cont) => dispatch(receiveContribution(cont)),
23 | (err) => dispatch(receiveContributionErrors(err)));
24 | };
25 |
26 | export const makeContribution = (contribution) => (dispatch) => {
27 |
28 | return ContributionApiUtil.makeContribution(contribution)
29 | .then((cont) => dispatch(receiveContribution(cont)),
30 | (err) => dispatch(receiveContributionErrors(err)));
31 | };
32 |
--------------------------------------------------------------------------------
/frontend/components/footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class Footer extends React.Component {
4 |
5 | render() {
6 | return(
7 |
8 |
25 |
Made by: Patrycja Lupinska
26 |
27 | );
28 | }
29 | }
30 |
31 |
32 |
33 | export default Footer;
34 |
--------------------------------------------------------------------------------
/frontend/util/campaign_api_util.js:
--------------------------------------------------------------------------------
1 | export const getCampaigns = (number = 'all', category = "none") => {
2 |
3 | return $.ajax({
4 | url: `/api/campaigns`,
5 | data: { num: number, category: category},
6 | method: 'GET'
7 | });
8 | };
9 |
10 | export const getCampaign = (id) => {
11 | return $.ajax({
12 | url: `/api/campaigns/${id}`
13 | });
14 | };
15 |
16 | export const updateCampaignForm = (campaign) => {
17 | // Campaign is nested inside of form data, we retreive it with get
18 | return $.ajax({
19 | url: `/api/campaigns/${campaign.get("campaign[id]")}`,
20 | method: 'PATCH',
21 | dataType: "json",
22 | contentType: false,
23 | processData: false,
24 | data: campaign
25 | });
26 | };
27 |
28 | export const updateCampaign = (campaign) => {
29 | return $.ajax({
30 | url: `/api/campaigns/${campaign.id}`,
31 | method: 'PATCH',
32 | data: campaign
33 | });
34 | };
35 |
36 | export const createCampaign = (campaign) => {
37 |
38 | return $.ajax({
39 | url: `api/campaigns`,
40 | method: `POST`,
41 | data: {campaign},
42 | });
43 | };
44 |
--------------------------------------------------------------------------------
/frontend/components/categories/category_show_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { fetchCampaigns, fetchCampaign } from '../../actions/campaign_actions';
3 | import CategoryShow from './category_show';
4 | import { selectCampaigns } from '../../reducers/selectors';
5 | import { getCategories } from '../../actions/category_actions';
6 |
7 | const mapStateToProps = (state, ownProps) => {
8 | let camps;
9 |
10 | if (state.campaigns) {
11 | if (!state.campaigns.campaign && !Object.keys(state.campaigns)) {
12 | camps = [];
13 | } else if (state.campaigns) {
14 |
15 | camps = Object.keys(state.campaigns).map((key) => state.campaigns[key]);
16 | }
17 | }
18 |
19 |
20 | return {
21 | categoryId: ownProps.params.id,
22 | campaigns: camps
23 | };
24 | };
25 |
26 | const mapDispatchToProps = (dispatch) => {
27 | return {
28 | fetchCampaigns: (number, category) => dispatch(fetchCampaigns(number, category)),
29 | fetchCampaign: (id) => dispatch(fetchCampaign(id)),
30 | getCategories: () => dispatch(getCategories())
31 | };
32 | };
33 |
34 | export default connect(mapStateToProps, mapDispatchToProps)(CategoryShow);
35 |
--------------------------------------------------------------------------------
/frontend/components/campaign/user_profile_component.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class UserProfileBlock extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = {
7 | user: null
8 | };
9 | }
10 |
11 | componentDidMount() {
12 | this.props.getUser(this.props.userId).then(usr => {
13 | this.setState({user: usr});
14 | });
15 | }
16 |
17 | render() {
18 | if (this.state.user) {
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {this.state.user.user.first_name} {this.state.user.user.last_name}
29 |
30 |
31 | {this.state.user.user.about_me}
32 |
33 |
34 |
35 | );
36 | } else {
37 | return(
38 |
39 | );
40 | }
41 | }
42 | }
43 |
44 | export default UserProfileBlock;
45 |
46 |
47 | // export default UserProfileBlock;
48 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/campaign_new.css:
--------------------------------------------------------------------------------
1 | .create-campaign {
2 | height: 100%;
3 | width: 80%;
4 |
5 | }
6 |
7 | .c-form {
8 | display: flex;
9 | flex-direction: column;
10 | font-size: 18px;
11 | }
12 |
13 | .create-campaign > h1 {
14 | padding-top: 40px;
15 | padding-left: 50px;
16 | font-weight: 400;
17 | font-size: 32px;
18 | font-weight: bold;
19 | }
20 |
21 | .create-campaign > h2 {
22 | padding-left: 50px;
23 | padding-top: 2px;
24 | padding-bottom: 10px;
25 | font-size: 24px;
26 | }
27 |
28 | .target-amount {
29 | padding-left: 50px;
30 | padding-top: 50px;
31 | padding-bottom: 50px;
32 | width: 300px;
33 | }
34 |
35 | .target-amount > input {
36 | width: 50px;
37 | height: 20px;
38 | }
39 |
40 | span {
41 | color: #0eb4b6;
42 | }
43 |
44 | .campaign-title {
45 | padding-left: 50px;
46 | padding-top: 50px;
47 | padding-bottom: 50px;
48 | width: 600px;
49 | }
50 |
51 | .submit-button1 > input {
52 | margin-left: 50px;
53 | margin: 20px 0px;
54 | background-color: #FC3468;
55 | color: white;
56 | border: none;
57 | text-align: center;
58 | min-height: 30px;
59 | margin-left: 50px;
60 | }
61 |
62 | .submit-button1 >input:hover {
63 | background: #ff1854;
64 | cursor: pointer;
65 | }
66 |
--------------------------------------------------------------------------------
/frontend/components/categories/category_index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CategoryTile from './category_tile';
3 |
4 | class CategoryIndex extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.makeCategoryTiles = this.makeCategoryTiles.bind(this);
9 | }
10 |
11 | componentWillMount() {
12 | this.props.getCategories();
13 | }
14 |
15 | makeCategoryTiles() {
16 | const tils = this.props.categories.map((cat, idx) => {
17 | return(
18 |
19 |
20 |
21 | );
22 | });
23 |
24 | return tils;
25 | }
26 |
27 | render() {
28 | let catTiles;
29 | if (this.props.categories) {
30 | catTiles = this.makeCategoryTiles();
31 | }
32 |
33 | if (catTiles) {
34 |
35 | return(
36 |
37 |
Explore categories
38 |
39 |
40 | {catTiles}
41 |
42 |
43 | );
44 | } else {
45 |
46 | return(
47 | Waiting...
48 | );
49 | }
50 |
51 | }
52 |
53 | }
54 |
55 | export default CategoryIndex;
56 |
--------------------------------------------------------------------------------
/frontend/components/campaign/campaign_show_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { fetchCampaign } from '../../actions/campaign_actions';
3 | import { getUser } from '../../actions/user_actions';
4 | import CampaignShow from './campaign_show';
5 | import { makeContribution } from '../../actions/contribution_actions';
6 | import { selectRewards } from '../../reducers/selectors';
7 | import { getAllRewards } from '../../actions/reward_actions';
8 |
9 |
10 | const mapStateToProps = (state, ownProps) => {
11 | let campaignId = ownProps.params.id;
12 | let usr = state.session.currentUser;
13 |
14 |
15 | return {
16 | rewards: selectRewards(state),
17 | campaignId,
18 | campaign: state.campaigns.campaign,
19 | currentUser: usr,
20 | contributionErrors: state.contribution.errors
21 | };
22 | };
23 |
24 | const mapDispatchToProps = (dispatch) => {
25 |
26 | return {
27 | fetchCampaign: (id) => dispatch(fetchCampaign(id)),
28 | getUser: (id) => dispatch(getUser(id)),
29 | makeContribution: (contribution) => dispatch(makeContribution(contribution)),
30 | getAllRewards: (id) => dispatch(getAllRewards(id))
31 | };
32 | };
33 |
34 | export default connect(mapStateToProps, mapDispatchToProps)(CampaignShow);
35 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/home_page.css:
--------------------------------------------------------------------------------
1 | .home-page {
2 | display: flex;
3 | flex-direction: column;
4 | width: 100%;
5 | /*height: ;*/
6 | background: white;
7 | justify-content: flex start;
8 | align-items: center;
9 | position: relative;
10 | }
11 |
12 | .ReactModalPortal {
13 | z-index: 100;
14 | }
15 | .carousel > div {
16 | box-sizing: border-box;
17 | display: flex;
18 | flex-direction: row;
19 | align-items: center;
20 | justify-content: flex start;
21 | height: 95%;
22 | }
23 |
24 | .carousel {
25 | width: 100%;
26 | min-height: 250px;
27 | max-height: 249px;
28 | overflow-x: hidden;
29 | overflow-y: hidden;
30 | }
31 |
32 | .slick-arrow {
33 | display: none;
34 | }
35 |
36 | /*.discover-slider > div {
37 | height: 100%;
38 | padding-top: 200px;
39 | width: 100%;
40 | }*/
41 |
42 |
43 | .slide > h3 {
44 | height: 100%;
45 | width: 100%;
46 | border: 1px solid #eee;
47 | }
48 |
49 | .slick-track {
50 | height: 100%;
51 | }
52 |
53 | .slider-img {
54 | min-height: 100%;
55 | overflow: hidden;
56 | }
57 |
58 | .carousel-img {
59 | margin: 0;
60 | width: 100%;
61 | height: 100%;
62 | min-height: 250px;
63 | max-height: 320px;
64 | }
65 | .slick-initialized {
66 | height: 100%;
67 | width: 100%;
68 | }
69 |
--------------------------------------------------------------------------------
/frontend/components/campaign/campaign_index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter, hashHistory } from 'react-router';
3 | import CampaignIndexItem from './campaign_index_item';
4 |
5 |
6 | class CampaignIndex extends React.Component {
7 | constructor(props) {
8 | super(props);
9 |
10 | }
11 |
12 | componentWillMount() {
13 | this.props.fetchCampaigns();
14 | }
15 |
16 | render() {
17 |
18 | if (this.props.campaigns[0] ) {
19 |
20 | const AllCampaigns = this.props.campaigns.map((campaign) => {
21 | return(
22 |
23 |
25 |
26 | );
27 | });
28 |
29 | return(
30 |
31 |
32 |
All Campaigns
33 |
34 |
35 | {AllCampaigns}
36 |
37 |
38 | );
39 | } else {
40 | return(
41 | Waiting...
42 | );
43 | }
44 |
45 | }
46 | }
47 |
48 | export default withRouter(CampaignIndex);
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "IndieFomo",
3 | "version": "1.0.0",
4 | "description": "== README",
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 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/plupinska/IndieFomo.git"
17 | },
18 | "keywords": [],
19 | "author": "",
20 | "license": "ISC",
21 | "bugs": {
22 | "url": "https://github.com/plupinska/IndieFomo/issues"
23 | },
24 | "homepage": "https://github.com/plupinska/IndieFomo#readme",
25 | "dependencies": {
26 | "babel-core": "^6.24.1",
27 | "babel-loader": "^6.4.1",
28 | "babel-preset-es2015": "^6.24.1",
29 | "babel-preset-react": "^6.24.1",
30 | "loadash": "0.0.1",
31 | "react": "^15.5.4",
32 | "react-dom": "^15.5.4",
33 | "react-fontawesome": "^1.6.1",
34 | "react-modal": "^1.7.7",
35 | "react-redux": "^5.0.4",
36 | "react-redux-modal": "^0.5.2",
37 | "react-router": "^3.0.5",
38 | "react-slick": "^0.14.11",
39 | "redux": "^3.6.0",
40 | "redux-logger": "^3.0.1",
41 | "redux-thunk": "^2.2.0",
42 | "webpack": "^2.4.1"
43 | },
44 | "engines": {
45 | "node": "6.9.2",
46 | "npm": "3.10.9"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/frontend/reducers/campaign_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_CAMPAIGNS, RECEIVE_CAMPAIGN, RECEIVE_CAMPAIGN_ERRORS} from '../actions/campaign_actions';
2 | import merge from 'lodash/merge';
3 | import { RECEIVE_CONTRIBUTION } from '../actions/contribution_actions';
4 |
5 | let _oldState = Object.freeze({
6 | campaign: null,
7 | errors: null
8 | });
9 |
10 |
11 | const CampaignsReducer = (state = _oldState, action) => {
12 |
13 | switch(action.type) {
14 | case RECEIVE_CAMPAIGNS:
15 |
16 | return action.campaigns;
17 | case RECEIVE_CAMPAIGN:
18 | const newState = merge({}, state);
19 |
20 | return merge(newState, {campaign: action.campaign, errors: ""});
21 | case RECEIVE_CAMPAIGN_ERRORS:
22 |
23 | const old = merge({}, state);
24 | return merge(old, {errors: action.errors.responseText});
25 | case "RECEIVE_CONTRIBUTION":
26 | let z = merge({}, state);
27 | let updated = merge(z, {campaign: action.campaign, errors: ""});
28 | let newAmount = updated.campaign.total_contributions + action.contribution.amount;
29 | let newCount = updated.campaign.num_contributions + 1;
30 | updated.campaign.total_contributions = newAmount;
31 | updated.campaign.num_contributions = newCount;
32 | return updated;
33 | default:
34 | return state;
35 | }
36 | };
37 |
38 | export default CampaignsReducer;
39 |
--------------------------------------------------------------------------------
/frontend/components/campaign/progress_block.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | class ProgressBlock extends React.Component {
5 | constructor(props) {
6 | super(props);
7 |
8 | }
9 |
10 | render() {
11 |
12 | const num_backers = this.props.campaign.num_contributions;
13 | let percentage = Math.round((this.props.campaign.total_contributions / this.props.campaign.target_amount)*100) || 0;
14 | return (
15 |
16 |
17 | ${this.props.campaign.total_contributions} USD raised by {this.props.campaign.num_contributions} backers
18 |
19 |
22 |
23 | {percentage}% of ${this.props.campaign.target_amount} goal
24 |
25 |
30 Days left
26 |
27 | );
28 | }
29 | }
30 |
31 | const mapStateToProps = (state) => {
32 |
33 | return {
34 | campaign: state.campaigns.campaign,
35 | };
36 | };
37 |
38 | const mapDispatchToProps = (dispatch) => {
39 | return {
40 |
41 | }
42 | }
43 |
44 |
45 |
46 | export default connect(mapStateToProps, mapDispatchToProps)(ProgressBlock);
47 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 | validates :email, :password_digest, :session_token, presence: true
3 | validates :password, length: {minimum: 6, allow_nil: true}
4 | after_initialize :ensure_session_token
5 |
6 | has_attached_file :image, default_url: "profile_placeholder.png",
7 | :styles => {
8 | :thumb => "40x40",
9 | :medium => "355x225"
10 | }
11 | validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
12 |
13 | has_many :campaigns
14 | has_many :contributions
15 |
16 | attr_reader :password
17 |
18 | def self.generate_session_token!
19 | SecureRandom::urlsafe_base64(16)
20 | end
21 |
22 | def password=(password)
23 | @password = password
24 | self.password_digest = BCrypt::Password.create(password)
25 | end
26 |
27 | def is_password?(password)
28 | BCrypt::Password.new(self.password_digest).is_password?(password)
29 | end
30 |
31 | def self.find_by_credentials(email, password)
32 | user = User.find_by(email: email)
33 | return user if user && user.is_password?(password)
34 | nil
35 | end
36 |
37 | def reset_session_token!
38 | self.session_token = User.generate_session_token!
39 | self.save!
40 | self.session_token
41 | end
42 |
43 | private
44 | def ensure_session_token
45 | self.session_token ||= User.generate_session_token!
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/frontend/actions/session_actions.js:
--------------------------------------------------------------------------------
1 | import * as SessionAPIUtil from '../util/auth_api_util';
2 | export const RECEIVE_CURRENT_USER = "RECEIVE_CURRENT_USER";
3 | export const RECEIVE_SESSION_ERRORS = "RECEIVE_SESSION_ERRORS";
4 |
5 | export const receiveCurrentUser = (currentUser) => {
6 |
7 | return {
8 | type: RECEIVE_CURRENT_USER,
9 | currentUser: currentUser
10 | };
11 | };
12 |
13 | export const receiveErrors = (errors) => {
14 |
15 | return {
16 | type: RECEIVE_SESSION_ERRORS,
17 | errors: errors
18 | };
19 | };
20 |
21 | export const signup = (user) => (dispatch) => {
22 |
23 | return SessionAPIUtil.signUp(user)
24 | .then((usr) => {
25 |
26 | dispatch(receiveCurrentUser(usr) );
27 | },
28 | (err) => {
29 | dispatch(receiveErrors(err.responseJSON));
30 |
31 | });
32 | };
33 |
34 | export const login = (user) => (dispatch) => {
35 |
36 | return SessionAPIUtil.signIn(user)
37 | .then((usr) => dispatch(receiveCurrentUser(usr)),
38 | (err) => dispatch(receiveErrors(err.responseJSON)));
39 | };
40 |
41 | export const loginguest = () => (dispatch) => {
42 | return SessionAPIUtil.signIn({email: "guest@gmail.com", password: "password" })
43 | .then((usr) => dispatch(receiveCurrentUser(usr)),
44 | (err) => dispatch(receiveErrors(err.responseJSON)));
45 | };
46 |
47 | export const logout = () => (dispatch) => {
48 |
49 | return SessionAPIUtil.signOut()
50 | .then(user => dispatch(receiveCurrentUser(null)));
51 | };
52 |
--------------------------------------------------------------------------------
/frontend/components/user/campaigns.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router';
3 | class UserCampaignList extends React.Component {
4 |
5 | constructor(props) {
6 | super(props);
7 |
8 | }
9 |
10 | makeTile() {
11 |
12 | return this.props.campaigns.map((camp, idx) => {
13 |
14 | return(
15 |
16 |
17 |
18 |
19 |
20 |
21 | Campaign Title: {camp.title}
22 |
23 |
24 |
25 |
26 |
27 |
Total Contributions: {camp.total_contributions}
28 |
% of Target: {Math.floor((camp.total_contributions/camp.target_amount) * 100)}
29 |
30 |
31 |
32 | );
33 | });
34 | }
35 |
36 | render() {
37 | const tiles = this.makeTile();
38 | return(
39 |
40 | {tiles}
41 |
42 | );
43 | }
44 |
45 | }
46 |
47 | export default UserCampaignList;
48 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/categories.css:
--------------------------------------------------------------------------------
1 | .cat-wrap {
2 | display: flex;
3 | flex-direction: column;
4 | min-height: 575px;
5 | }
6 |
7 | .cat-wrap > h1 {
8 | margin-left: 11px;
9 | }
10 |
11 | .categories {
12 | margin: 30px;
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | align-items: center;
17 | font-size: 18px; width: 950px;
18 | min-height: 550px;
19 | }
20 |
21 | .category-container {
22 | display: flex;
23 | flex-direction: row;
24 | flex-wrap: wrap;
25 | justify-content: space-around;
26 | align-items: center;
27 | width: 950px;
28 | min-height: 500px;
29 | }
30 |
31 | .category-tile {
32 | background: black;
33 | /*z-index: 7;*/
34 | }
35 |
36 | .category-tile img {
37 | opacity: 0.5;
38 | }
39 |
40 | .category-tile img:hover {
41 | opacity: 0.8;
42 | cursor: pointer;
43 | }
44 |
45 |
46 | .category-tile > a {
47 | display: flex;
48 | flex-direction: column;
49 | align-items: center;
50 | text-align: center;
51 | color: white;
52 | z-index: 10;
53 | }
54 |
55 | .category-tile > a:hover {
56 | display: flex;
57 | flex-direction: column;
58 | align-items: center;
59 | text-align: center;
60 | color: white;
61 | z-index: 10;
62 |
63 | }
64 |
65 | .title:hover {
66 | opacity: 0.8;
67 | cursor: pointer;
68 | }
69 |
70 | .title {
71 | position: absolute;
72 | margin-top: 100px;
73 | width: 140px;
74 | background: transparent;
75 | font-weight: bold;
76 | text-align: center;
77 | font-size: 16px;
78 | }
79 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
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 IndieFomo
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 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
17 | # config.time_zone = 'Central Time (US & Canada)'
18 |
19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
21 | # config.i18n.default_locale = :de
22 |
23 | config.paperclip_defaults = {
24 | :storage => :s3,
25 | :s3_protocol => :https,
26 | :s3_credentials => {
27 | :bucket => ENV["s3_bucket"],
28 | :access_key_id => ENV["s3_access_key_id"],
29 | :secret_access_key => ENV["s3_secret_access_key"],
30 | :s3_region => ENV["s3_region"]
31 | }
32 | }
33 |
34 | # Do not swallow errors in after_commit/after_rollback callbacks.
35 | config.active_record.raise_in_transactional_callbacks = true
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/views/api/users/_user.json.jbuilder:
--------------------------------------------------------------------------------
1 |
2 | if (@user)
3 | json.id @user.id
4 | json.first_name @user.first_name
5 | json.last_name @user.last_name
6 | json.email @user.email
7 | json.image_url asset_path(@user.image.url)
8 | json.about_me @user.about_me
9 |
10 | if (@contributions)
11 | json.contributions @contributions.each do |contribution|
12 | json.user_id contribution.user_id
13 | json.reward_id contribution.reward_id
14 |
15 | if (contribution.reward)
16 | json.reward Reward.find(contribution.reward_id)
17 | end
18 | json.amount contribution.amount
19 | json.campaign_id contribution.campaign_id
20 | json.date contribution.date
21 | json.campaign_title Campaign.find(contribution.campaign_id).title
22 | end
23 | end
24 |
25 | if (@user.campaigns)
26 | json.campaigns @user.campaigns.each do |campaign|
27 | json.num_contributions Contribution.where(campaign_id: campaign.id).count
28 | json.total_contributions Contribution.where(campaign_id: campaign.id).sum(:amount)
29 | json.user campaign.user
30 | json.id campaign.id
31 | json.title campaign.title
32 | json.descriptions campaign.descriptions
33 | json.tagline campaign.tagline
34 | json.category_id campaign.category_id
35 | json.image_url campaign.image_url
36 | json.end_date campaign.end_date
37 | json.target_amount campaign.target_amount
38 | json.image_url asset_path(campaign.image.url)
39 | end
40 | end
41 | else
42 | {user: nil}
43 | end
44 |
--------------------------------------------------------------------------------
/docs/sample-state.md:
--------------------------------------------------------------------------------
1 | ```js
2 | // session slice
3 | {
4 | session: {
5 | currentUser: {
6 | id: 1,
7 | username: 'patrycja'
8 | },
9 | errors: []
10 | }
11 | }
12 |
13 |
14 | {
15 | currentUser: {
16 | id: 1,
17 | fname: 'p'
18 | lname: 'l',
19 | username: 'pl'
20 | Mycampaigns: {
21 | 1: {
22 | owner: {username, name},
23 | title: "Taco Stand",
24 | description: "Veggie Tacos",
25 | tagline: "Taco your soul",
26 | category: "Food",
27 | image: 'url',
28 | time_left: '5',
29 | target_amount: '10000000',
30 | contributions: '4',
31 | reward: {
32 | id: {
33 | title: title,
34 | description: description,
35 | image_url: url,
36 | price: price
37 | }
38 | }
39 | }
40 | }
41 | },
42 |
43 | forms: {
44 | signup: {errors: []},
45 | login: {errors: []},
46 | createCampaign: {errors: []}
47 | addReward: {errors: []}
48 | },
49 |
50 | campaigns: {
51 | id: {
52 | owner: {username, name},
53 | title: "Taco Stand",
54 | description: "Veggie Tacos",
55 | tagline: "Taco your soul",
56 | category: "Food",
57 | image: 'url',
58 | time_left: '5',
59 | target_amount: '10000000',
60 | contributions: '4',
61 | rewards: {
62 | id: {
63 | title: title,
64 | description: description,
65 | image_url: url,
66 | price: price
67 | }
68 | }
69 | }
70 | }
71 |
72 |
73 |
74 |
75 | }
76 |
77 |
78 |
79 |
80 |
81 | ```
82 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/components/campaign/campaign_index_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 |
4 | class CampaignIndexItem extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.handleClick = this.handleClick.bind(this);
9 |
10 | }
11 |
12 | handleClick(e) {
13 | e.preventDefault();
14 | const url = `/campaigns/${this.props.campaign.id}`;
15 | this.props.router.push(url);
16 | }
17 |
18 | render() {
19 |
20 | const percentage = Math.round((this.props.campaign.total_contributions / this.props.campaign.target_amount)*100);
21 |
22 | return(
23 |
24 |
26 |
27 |
28 |
29 |
{this.props.campaign.title}
30 |
{this.props.campaign.tagline}
31 |
32 |
33 |
34 |
35 | ${this.props.campaign.total_contributions} USD raised by {this.props.campaign.num_contributions} backers
36 |
37 |
38 |
41 |
42 |
43 | {percentage}% of ${this.props.campaign.target_amount} goal
44 |
45 |
30 days left
46 |
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | }
54 |
55 | export default withRouter(CampaignIndexItem);
56 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Automatically inject JavaScript needed for LiveReload
20 | config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload)
21 |
22 | # Print deprecation notices to the Rails logger.
23 | config.active_support.deprecation = :log
24 |
25 | # Raise an error on page load if there are pending migrations.
26 | config.active_record.migration_error = :page_load
27 |
28 | # Debug mode disables concatenation and preprocessing of assets.
29 | # This option may cause significant delays in view rendering with a large
30 | # number of complex assets.
31 | config.assets.debug = true
32 |
33 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
34 | # yet still be able to expire them through the digest params.
35 | config.assets.digest = true
36 |
37 | # Adds additional error checking when serving assets at runtime.
38 | # Checks for improperly declared sprockets dependencies.
39 | # Raises helpful error messages.
40 | config.assets.raise_runtime_errors = true
41 |
42 | # Raises error for missing translations
43 | # config.action_view.raise_on_missing_translations = true
44 | end
45 |
--------------------------------------------------------------------------------
/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 static file server for tests with Cache-Control for performance.
16 | config.serve_static_files = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Randomize the order test cases are executed.
35 | config.active_support.test_order = :random
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 |
--------------------------------------------------------------------------------
/docs/schema.md:
--------------------------------------------------------------------------------
1 | # Schema Information
2 |
3 |
4 | ## user
5 |
6 | Column Name | Data Type | Details
7 | ------------ | ------------- | ------------
8 | id | integer | not null, primary key
9 | username | string | not null, indexed, unique
10 | first_name | string| not null, indexed
11 | last_name | string | not null, indexed
12 | about me | text | not null
13 | email | string | not null, indexed, unique
14 | password_digest | string | not null
15 | session_token | string | not null, indexed, unique
16 | location | string| not null
17 | image_url | string | indexed
18 |
19 | ## campaign
20 |
21 | Column Name | Data Type | Details
22 | ------------ | ------------- | ------------
23 | id | integer | not null, primary key
24 | user_id | integer | not null, indexed, unique, foreign key
25 | title | string | not null, indexed
26 | description | text | not null, indexed
27 | tagline | string | not null, indexed
28 | category_id | integer | not null
29 | image_url | integer | indexed
30 | end_date | date | not null, indexed
31 | target_amount | integer | not null, indexed
32 |
33 |
34 | ## contribution
35 |
36 | Column Name | Data Type | Details
37 | ------------ | ------------- | ------------
38 | id | integer | not null, primary key
39 | user_id | string | not null, indexed, unique, foreign key
40 | reward_id | integer | not null, foreign_key
41 | amount | float | not null, indexed
42 |
43 | ## reward
44 |
45 | Column Name | Data Type | Details
46 | ------------ | ------------- | ------------
47 | id | integer | not null, primary key
48 | campaign_id | integer | not null, indexed, foreign key
49 | title | string| not null, indexed
50 | description | string | not null, indexed
51 | image_url | string | not null, indexed
52 | price | integer | not null
53 |
54 | ## category
55 |
56 | Column Name | Data Type | Details
57 | ------------ | ------------- | ------------
58 | id | integer | not null, primary key
59 | type | string | not null, unique
60 |
--------------------------------------------------------------------------------
/frontend/actions/campaign_actions.js:
--------------------------------------------------------------------------------
1 | import * as CampaignApiUtil from '../util/campaign_api_util';
2 | export const RECEIVE_CAMPAIGNS = "RECEIVE_CAMPAIGNS";
3 | export const RECEIVE_CAMPAIGN = "RECEIVE_CAMPAIGN";
4 | export const RECEIVE_CAMPAIGN_ERRORS = "RECEIVE_CAMPAIGN_ERRORS";
5 |
6 | export const receiveCampaigns = (campaigns) => {
7 |
8 | return {
9 | type: RECEIVE_CAMPAIGNS,
10 | campaigns: campaigns
11 | };
12 | };
13 |
14 | export const receiveCampaign = (campaign) => {
15 |
16 | return {
17 | type: RECEIVE_CAMPAIGN,
18 | campaign: campaign
19 | };
20 | };
21 |
22 | export const receiveErrors = (errors) => {
23 | return {
24 | type: RECEIVE_CAMPAIGN_ERRORS,
25 | errors: errors
26 | };
27 | };
28 |
29 | export const fetchCampaigns = (num, cat) => (dispatch) => {
30 |
31 | return CampaignApiUtil.getCampaigns(num, cat)
32 | .then((campaigns) => dispatch(receiveCampaigns(campaigns)),
33 | (err) => dispatch(receiveErrors(err)));
34 | };
35 |
36 | export const fetchCampaign = (id) => (dispatch) => {
37 | return CampaignApiUtil.getCampaign(id)
38 | .then((camp) => dispatch(receiveCampaign(camp)),
39 | (err) => dispatch(receiveErrors(err)));
40 | };
41 |
42 | export const createCampaign = (campaign) => (dispatch) => {
43 |
44 | return CampaignApiUtil.createCampaign(campaign)
45 | .then((camp) => dispatch(receiveCampaign(camp)),
46 | (err) => dispatch(receiveErrors(err)));
47 | };
48 |
49 | export const updateCampaign = (campaign) => (dispatch) => {
50 |
51 | return CampaignApiUtil.updateCampaign(campaign)
52 | .then((camp) => dispatch(receiveCampaign(camp)),
53 | (err) => dispatch(receiveErrors(err)));
54 | };
55 |
56 | export const updateCampaignForm = (campaign) => (dispatch) => {
57 |
58 | return CampaignApiUtil.updateCampaignForm(campaign)
59 | .then((camp) => dispatch(receiveCampaign(camp)),
60 | (err) => dispatch(receiveErrors(err)));
61 | }
62 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 |
4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
5 | gem 'rails', '4.2.8'
6 | # Use sqlite3 as the database for Active Record
7 | gem 'pg'
8 | # Use SCSS for stylesheets
9 | gem 'sass-rails', '~> 5.0'
10 | # Use Uglifier as compressor for JavaScript assets
11 | gem 'uglifier', '>= 1.3.0'
12 | # Use CoffeeScript for .coffee assets and views
13 | gem 'coffee-rails', '~> 4.1.0'
14 | # See https://github.com/rails/execjs#readme for more supported runtimes
15 | # gem 'therubyracer', platforms: :ruby
16 |
17 | # Use jquery as the JavaScript library
18 | gem 'jquery-rails'
19 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
20 | gem 'jbuilder', '~> 2.0'
21 | # bundle exec rake doc:rails generates the API under doc/api.
22 | gem 'sdoc', '~> 0.4.0', group: :doc
23 |
24 | # Use ActiveModel has_secure_password
25 | gem 'bcrypt', '~> 3.1.7'
26 | gem 'paperclip'
27 | gem 'aws-sdk'
28 | gem "figaro"
29 | # Use Unicorn as the app server
30 | # gem 'unicorn'
31 | gem 'momentjs-rails'
32 | # Use Capistrano for deployment
33 | # gem 'capistrano-rails', group: :development
34 | gem 'stripe'
35 | gem 'faker'
36 | group :development, :test do
37 | # Call 'byebug' anywhere in the code to stop execution and get a console
38 | gem 'byebug'
39 | gem 'annotate'
40 | gem 'better_errors'
41 | gem 'binding_of_caller'
42 | gem 'pry-rails'
43 | end
44 |
45 | group :development do
46 | # Access an IRB console on exception pages or by using <%= console %> in views
47 | gem 'web-console', '~> 2.0'
48 | # for css uploads
49 | gem 'guard'
50 | gem 'guard-livereload'
51 | gem 'rack-livereload'
52 | gem 'rb-fsevent'
53 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
54 | gem 'spring'
55 | gem "pg_search"
56 | end
57 |
58 | group :production do
59 | gem 'rails_12factor'
60 | gem "pg_search"
61 | end
62 |
--------------------------------------------------------------------------------
/frontend/components/categories/category_show.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CampaignIndexItem from '../campaign/campaign_index_item';
3 |
4 | class CategoryShow extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.makeCampaignTiles = this.makeCampaignTiles.bind(this);
9 | this.allOfCategory = this.allOfCategory.bind(this);
10 | this.state = {
11 | category: null,
12 | };
13 | }
14 |
15 | componentWillMount() {
16 | this.props.fetchCampaigns("all", this.props.categoryId).then((cats) => {
17 |
18 | cats = Object.keys(cats.campaigns).map(camp => cats.campaigns[camp])
19 | this.setState({
20 | category: cats[0].cat
21 | })
22 | });
23 | }
24 |
25 | makeCampaignTiles() {
26 | const tiles = this.props.campaigns.map((camp, idx) => {
27 | return(
28 |
29 |
31 |
32 | );
33 | });
34 |
35 | return tiles;
36 | }
37 |
38 | allOfCategory() {
39 | let catId = this.props.categoryId;
40 | let uniformCategory = true;
41 |
42 | this.props.campaigns.forEach((camp) => {
43 | if (camp.category_id != catId) {
44 | uniformCategory = false;
45 | }
46 | });
47 |
48 | return uniformCategory;
49 | }
50 |
51 | render() {
52 |
53 | let tiles = [];
54 | if (this.props.campaigns[0] && this.allOfCategory()) {
55 | tiles = this.makeCampaignTiles();
56 | }
57 |
58 | if (tiles.length > 0) {
59 | return(
60 |
61 |
{this.state.category}
62 |
63 | {tiles}
64 |
65 |
66 | );
67 | } else {
68 | return(
69 | Waiting..
70 | );
71 | }
72 | }
73 | }
74 |
75 | export default CategoryShow;
76 |
--------------------------------------------------------------------------------
/frontend/actions/reward_actions.js:
--------------------------------------------------------------------------------
1 | import * as RewardsApiUtil from '../util/rewards_api_util';
2 | export const RECEIVE_REWARD = 'RECEIVE_REWARD';
3 | export const RECEIVE_REWARD_ERRORS = 'RECEIVE_REWARD_ERRORS';
4 | export const RECEIVE_ALL_REWARDS = 'RECEIVE_ALL_REWARDS';
5 | export const DELETE_REWARD = 'DELETE_REWARD';
6 |
7 | export const receiveReward = (reward) => {
8 | return {
9 | type: RECEIVE_REWARD,
10 | reward: reward
11 | };
12 | };
13 |
14 | export const receiveAllRewards = (rewards) => {
15 | return {
16 | type: RECEIVE_ALL_REWARDS,
17 | rewards: rewards
18 | };
19 | };
20 |
21 | export const receiveRewardErrors = (errors) => {
22 | return {
23 | type: RECEIVE_REWARD_ERRORS,
24 | errors: errors
25 | };
26 | };
27 |
28 | export const deleteReward = (reward) => {
29 |
30 | return {
31 | type: DELETE_REWARD,
32 | reward
33 | }
34 | }
35 |
36 | export const createReward = (reward) => (dispatch) => {
37 |
38 | return RewardsApiUtil.createReward(reward)
39 | .then((rew) => dispatch(receiveReward(rew)),
40 | (err) => dispatch(receiveRewardErrors(err)));
41 | };
42 |
43 | export const editReward = (reward) => (dispatch) => {
44 | return RewardsApiUtil.editReward(reward)
45 | .then((rew) => dispatch(receiveReward(rew)),
46 | (err) => dispatch(receiveRewardErrors(err)));
47 | };
48 |
49 | export const removeReward = (reward) => (dispatch) => {
50 |
51 | return RewardsApiUtil.deleteReward(reward)
52 | .then((rew) => dispatch(deleteReward(rew)),
53 | (err) => dispatch(receiveRewardErrors(err)));
54 | };
55 |
56 | export const getReward = (reward) => (dispatch) => {
57 | return RewardsApiUtil.getReward(reward)
58 | .then((rew) => dispatch(receiveReward(rew)),
59 | (err) => dispatch(receiveRewardErrors(err)));
60 | };
61 |
62 | export const getAllRewards = (id) => (dispatch) => {
63 | return RewardsApiUtil.getAllRewards(id)
64 | .then((rewards) => dispatch(receiveAllRewards(rewards),
65 | (err) => dispatch(receiveAllRewards)));
66 | };
67 |
--------------------------------------------------------------------------------
/docs/PRODUCTION_README.md:
--------------------------------------------------------------------------------
1 | [IndieFomo](https://github.com/plupinska/IndieFomo/blob/master/docs/api-endpoints)
2 |
3 | # IndieFomo
4 | Indie Fomo is a web aplication inspired by Indiegogo built using Ruby on Rails on the backend, a PostgresQL database, and React/Redux front end framework.
5 | 
6 | ## Features And Implementation
7 |
8 | ### Campaign Creation
9 | On the backend, campaigns are stored in a single table with foreign keys connecting each campaign to it's owner and category. Each campaign has many associated rewards which are stored in a separate table. Each respective campaign has two associated views. First of which is the campaign show page that lists campaign funding details, a description, and its associated rewards. The second view is an overview tile that links the user to the campaign show page.
10 | 
11 | ### Reward Creation
12 | On the backend, rewards are stored in a single table with a foreign key associating each reward to its campaign. Each reward is structured as a nested resource under its campaign so users can add rewards as they create their campaigns. Rewards are rendered twice in a 'preview' mode where users can delete and edit rewards before launching their finished campaigns as well as in a final show view that is rendered on the campaign show page.
13 | 
14 | ### Contributions
15 | On the backend, contributions are stored in a single table with foreign keys connecting them to a user, optional reward, and campaign associated with each contribution. Users can make contributions on the campaign show page by either opting to purchase a reward or by creating a custom amount contribution. Total contributions as well as the contribution count are rendered on the campaign show page. The contributions are represented as a progress bar that expresses the total contributions as a percentage of target campaign amount. The progress bar dynamically updates as users donate to the campaign.
16 | 
17 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # IndieFomo
2 |
3 |
4 | ## Minimum Viable Product
5 | Indie Fomo is a web aplication inspired by Indiegogo built using Ruby on Rails
6 | on the backend, a PostgresQL database, and React/Redux front end framework.
7 | By the end of week 9, this app will have at minimum:
8 |
9 | - [] Hosting on Heroku
10 | - [] A production README, replacing this README
11 | - [] New account creation, login, and guest/demo login
12 | - [] Profiles (User has a profile, can edit certain information)
13 | - [] Campaigns (Create and Update)
14 | - [] Contributions (users can contribute to campaigns)
15 | - [] Rewards (Listed on Campaign Show pages as well as user profile)
16 | - [] Bonus: Categories
17 | - [] Bonus: Search
18 |
19 | ## Design Docs
20 | * [Api-Endpoints](https://github.com/plupinska/IndieFomo/blob/master/docs/api-endpoints)
21 | * [Component-Hierarchy](https://github.com/plupinska/IndieFomo/blob/master/docs/component-hierarchy)
22 | * [Sample-State](https://github.com/plupinska/IndieFomo/blob/master/docs/sample-state.md)
23 | * [Schema](https://github.com/plupinska/IndieFomo/blob/master/docs/schema.md)
24 | * [Link To Heroku](https://indiefomo.herokuapp.com/)
25 | ## Implementation Timeline
26 |
27 | ### Phase 1: Backend setup and Front End User Authentication (2 days)
28 |
29 | **Objective:** Functioning rails project with front-end Authentication. Work on Nav bar.
30 |
31 | ### Phase 2: User Model, API, and components (2 days)
32 |
33 | **Objective:** User can sign up, log in, and view/edit their Profile.
34 |
35 | ### Phase 3: Campaigns (2 days)
36 |
37 | **Objective:** Finish create and Edit forms for Campaigns. A Campaign can be created, user can navigate to its show page.
38 |
39 | ### Phase 4: Rewards (1 day)
40 |
41 | **Objective:** User is able to Add a Reward Component. Add Reward component to Campaign Show page and User Profile.
42 |
43 | ### Phase 5: Work on Landing Page (2/3 days)
44 |
45 | **Objective:** Work on creating components for landing page. Work on Styling.
46 |
47 |
48 |
49 | ### Bonus Features (TBD)
50 | - [ ] Search Component
51 | - [ ] Category
52 |
--------------------------------------------------------------------------------
/frontend/components/campaign/campaign_show.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 | import CampaignOverview from './campaign_overview_component';
4 | import RewardsShowPage from './rewards/rewards_showpage';
5 |
6 | class CampaignShow extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | componentWillMount() {
13 | this.props.fetchCampaign(this.props.campaignId);
14 | this.props.getAllRewards(this.props.campaignId);
15 | }
16 |
17 | componentWillReceiveProps(newProps) {
18 |
19 | if (this.props.campaignId !== newProps.params.id) {
20 | this.props.fetchCampaign(newProps.params.id);
21 | this.props.getAllRewards(newProps.params.id);
22 | }
23 | }
24 |
25 | render() {
26 |
27 | if (this.props.campaign) {
28 | return(
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
Overview
46 | {this.props.campaign.descriptions}
47 |
48 |
49 |
50 |
Rewards
51 |
53 |
54 |
55 |
56 | );
57 | } else {
58 | return (
59 | Waiting..
60 | );
61 | }
62 | }
63 | }
64 | export default withRouter(CampaignShow);
65 |
--------------------------------------------------------------------------------
/frontend/components/user/contributions.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router';
3 | class UserContributions extends React.Component {
4 | constructor(props) {
5 | super(props);
6 |
7 | }
8 |
9 | formatDate(str) {
10 | let monthHash = {1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May',
11 | 6: 'Jun', 7: 'Jul', 8: 'Aug', 9: 'Sept', 10: 'Oct', 11: 'Nov', 12: 'Dec'};
12 |
13 | let date = str;
14 | if (str.includes('-')) {
15 | date = str.split('-');
16 | let yy = date[0];
17 | let dd = date[2];
18 | let month = parseInt(date[1]);
19 | month = monthHash[month];
20 |
21 | date = month + ' ' + dd + ' ' + yy;
22 | }
23 |
24 | return date;
25 | }
26 |
27 | makeTile() {
28 |
29 | return this.props.contributions.map((contribution, idx) => {
30 | let date = this.formatDate(contribution.date);
31 | let reward;
32 |
33 | if (contribution.reward_id) {
34 | reward = contribution.reward.title;
35 | } else {
36 | reward = "No reward for this one :(";
37 | }
38 | return(
39 |
40 |
{date}
41 |
{contribution.campaign_title}
42 |
{contribution.amount}
43 |
{reward}
44 |
45 | );
46 | });
47 | }
48 |
49 | render() {
50 | const contributions = this.makeTile();
51 |
52 | return(
53 |
54 |
55 |
Date
56 |
Campaign
57 |
Amount
58 |
Reward
59 |
60 |
61 |
62 | {contributions}
63 |
64 |
65 | );
66 | }
67 | }
68 |
69 | export default UserContributions;
70 |
--------------------------------------------------------------------------------
/app/controllers/api/campaigns_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::CampaignsController < ApplicationController
2 |
3 | def index
4 | #look into services(Service Objects)
5 | if params["num"] == "all";
6 | if params["category"] === "none"
7 | @campaigns = Campaign.includes(:user, :contributions).all
8 | elsif params["category"] != "none"
9 | if params["category"].to_i > 0
10 | @campaigns = Campaign.includes(:category).where(category_id: params["category"].to_i)
11 | else
12 | category_id = Category.find(cat: params["category"])
13 | @campaigns = Campaign.where(category_id: category_id)
14 | end
15 | else
16 | @campaigns = Campaign.where(cat: params["cat"])
17 | end
18 | else params["num"].to_i.is_a?(Integer)
19 | @campaigns = Campaign.includes(:user, :contributions).take(params["num"].to_i)
20 | end
21 | render :index
22 | end
23 |
24 | def create
25 | @campaign = Campaign.create(campaign_params)
26 |
27 | if @campaign.save
28 | render :show
29 | else
30 | render json: @campaign.errors.full_messages, status:422
31 | end
32 | end
33 |
34 | def show
35 | @campaign = Campaign.find(params[:id])
36 | contribution = Contribution.includes(:campaign).where(campaign_id: params[:id])
37 | @num_contributions = contribution.count
38 | @total_contributions = contribution.sum(:amount)
39 | render :show
40 | end
41 |
42 | def update
43 | @campaign = Campaign.find(params[:id])
44 |
45 | if @campaign.update(campaign_params)
46 | category = Category.find_by(cat: category_title_param[:category_name]).id
47 | @campaign.update(category_id: category)
48 | render :show
49 | else
50 | render json: @campaign.errors.full_messages, status: 422
51 | end
52 | end
53 |
54 | def destroy
55 | @campaign = Campaign.find(params[:id])
56 | @campaign.destroy!
57 | render :show
58 | end
59 |
60 | private
61 | def campaign_params
62 | params.require(:campaign).permit(:user_id, :title, :descriptions,
63 | :tagline, :image, :end_date, :target_amount)
64 | end
65 |
66 | def category_title_param
67 | params.require(:campaign).permit(:category_name)
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/frontend/components/campaign/rewards/rewards_showpage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import Reward from './reward';
4 | import {removeReward} from '../../../actions/reward_actions';
5 | import {makeContribution} from '../../../actions/contribution_actions';
6 |
7 | class RewardsShowPage extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | render() {
13 | let rewardTiles = null;
14 |
15 | let theseRewards = [];
16 | let campId = parseInt(this.props.campaignId);
17 | this.props.rewards.forEach((reward) => {
18 | if (reward.campaign_id === campId) {
19 | theseRewards.push(reward);
20 | }
21 | });
22 |
23 | rewardTiles= theseRewards.map((rew, idx) => {
24 |
25 | return(
26 |
28 | );
29 | });
30 |
31 |
32 | if (this.props.rewards.length > 0 ) {
33 |
34 | return(
35 |
36 | { rewardTiles }
37 |
38 | );
39 | } else {
40 |
41 | return(
42 |
43 |
44 | );
45 | }
46 | }
47 | }
48 |
49 | const mapStateToProps = (state, ownProps) => {
50 |
51 | let campid = null;
52 | if (ownProps.params) {
53 | campid = ownProps.campaignId;
54 | } else {
55 | campid = state.campaigns.campaign.id;
56 | }
57 | let rews = [];
58 | if (Object.keys(state.rewards).length > 0) {
59 | rews = Object.keys(state.rewards).map((key) => state.rewards[key]);
60 | }
61 |
62 | let showbool = ownProps.onShow;
63 |
64 | return {
65 | rewards: rews,
66 | campaignId: parseInt(campid),
67 | user: state.session.currentUser,
68 | onShow: showbool
69 | };
70 | };
71 |
72 | const mapDispatchToProps = (dispatch) => {
73 | return{
74 | removeReward: (reward) => dispatch(removeReward(reward)),
75 | makeContribution: (contribution) => dispatch(makeContribution(contribution))
76 | };
77 | };
78 |
79 |
80 | export default connect(mapStateToProps, mapDispatchToProps)(RewardsShowPage);
81 |
--------------------------------------------------------------------------------
/frontend/components/root.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import { Router, Route, IndexRoute, hashHistory } from 'react-router';
4 | import App from './app';
5 | import CampaignIndexContainer from './campaign/campaign_index_container';
6 | import CampaignShowContainer from './campaign/campaign_show_container';
7 | import UserShowContainer from './user/user_container';
8 | import HomePage from './home_page/home_page';
9 | import NewCampaignContainer from './campaign/new_campaign_container';
10 | import EditCampaignComponent from './campaign/edit_campaign_container';
11 | import NewRewardContainer from './campaign/rewards/new_reward_component';
12 | import CategoryShowContainer from './categories/category_show_container';
13 |
14 | const Root = ({store}) => {
15 |
16 | const _redirectIfLoggedIn = (nextState, replace) => {
17 | const currentUser = store.getState().session.currentUser;
18 | if (currentUser) {
19 | replace('/');
20 | }
21 | };
22 |
23 | const _redirectUnlessLoggedIn = (nextState, replace) => {
24 | const currentUser = store.getState().session.currentUser;
25 |
26 | const url = location.hash.split('/');
27 | const userId = url[2];
28 | if (currentUser && currentUser.id !== parseInt(userId)) {
29 | replace('/');
30 | }
31 | };
32 |
33 | const redirect = (nextState, replace) => {
34 | replace('/');
35 | };
36 |
37 |
38 | return(
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default Root;
57 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | # The priority is based upon order of creation: first created -> highest priority.
3 | # See how all your routes lay out with "rake routes".
4 | root "static_pages#root"
5 |
6 | namespace :api, defaults: {format: :json} do
7 |
8 | resources :users, only: [:update, :show, :create]
9 | resource :session, only: [:create, :destroy]
10 | resources :contributions, only: [:create, :show]
11 | resources :search, only: [:index]
12 | # resources :charges
13 |
14 | resources :campaigns do
15 | resources :rewards, only: [:create, :destroy, :update, :index, :show]
16 | end
17 |
18 | resources :categories do
19 | resources :campaigns, only: [:index]
20 | end
21 | end
22 | # You can have the root of your site routed with "root"
23 | # root 'welcome#index'
24 |
25 | # Example of regular route:
26 | # get 'products/:id' => 'catalog#view'
27 |
28 | # Example of named route that can be invoked with purchase_url(id: product.id)
29 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
30 |
31 | # Example resource route (maps HTTP verbs to controller actions automatically):
32 | # resources :products
33 |
34 | # Example resource route with options:
35 | # resources :products do
36 | # member do
37 | # get 'short'
38 | # post 'toggle'
39 | # end
40 | #
41 | # collection do
42 | # get 'sold'
43 | # end
44 | # end
45 |
46 | # Example resource route with sub-resources:
47 | # resources :products do
48 | # resources :comments, :sales
49 | # resource :seller
50 | # end
51 |
52 | # Example resource route with more complex sub-resources:
53 | # resources :products do
54 | # resources :comments
55 | # resources :sales do
56 | # get 'recent', on: :collection
57 | # end
58 | # end
59 |
60 | # Example resource route with concerns:
61 | # concern :toggleable do
62 | # post 'toggle'
63 | # end
64 | # resources :posts, concerns: :toggleable
65 | # resources :photos, concerns: :toggleable
66 |
67 | # Example resource route within a namespace:
68 | # namespace :admin do
69 | # # Directs /admin/products/* to Admin::ProductsController
70 | # # (app/controllers/admin/products_controller.rb)
71 | # resources :products
72 | # end
73 | end
74 |
--------------------------------------------------------------------------------
/docs/component-hierarchy.md:
--------------------------------------------------------------------------------
1 | # Component Hierarchy
2 |
3 | ### Landing Page
4 |
5 | **NavBarComponent**
6 | - Logo
7 | - Link to Github
8 | - Link to eventual Portfolio
9 | - SearchForm Container/Component
10 | - Auth Form Container/Component
11 | - Sign Up
12 | - Sign In
13 | - Demo Account
14 |
15 | **HomePageComponent**
16 | - Carousel Component
17 | - Image Component(Link to Campaign)
18 | -Campaign Index Slider Component
19 | - Slider Menu
20 | - Most Popular label
21 | - button to Campaign Index Page
22 | - CampaignIndexComponent
23 | - CampaignTile
24 | - Category Index Component
25 | -Category Components(Link to Campaign Category Index page)
26 |
27 |
28 | ### UserProfileComponent
29 | - Nav Bar
30 | - Header, showing username
31 | - Profile Component
32 | - User Image Upload
33 | - About me User Edit
34 | - About me show Component
35 | - Contributions Component
36 | - table with campaign, contribution, and perk
37 |
38 | ### Create A Campaign Component
39 | - Tagline
40 | - Campaign Form Component
41 |
42 | ### Edit A Campaign Page
43 | - Main Detail Label
44 | - FormtoEditComponent
45 |
46 | ### Add A Reward
47 | - Input Form Component to create rewards associated with campaigns
48 |
49 | ### User Contribution Index page
50 | - Table Component listing Contributions
51 | - Only accessible by user
52 |
53 | ### User Campaign Index Page
54 | - All Campaign Tiles that belong to user
55 | - Campaign Tiles link to Campaign Show page
56 |
57 | ### Campaign Index Page
58 | - All Campaign Tiles
59 |
60 | ### Category Show Page
61 | - All Campaign Tiles matching Category
62 |
63 |
64 |
65 | ## Routes
66 |
67 | |Path | Component |
68 | |-------|-------------|
69 | | "/sign-up" | "Auth Form Container" |
70 | | "/sign-in" | "Auth Form Container" |
71 | | "/" | "Landing Page" |
72 | | "/profile/:id" | "UserProfile Container" |
73 | | "/profile/:id/contributions" | "User Contributions" |
74 | | "/profile/:id/campaigns" | "User Campaigns"
75 | | "/campaign/new" | "Create A Campaign Container" |
76 | | "/campaign/:id/edit" | "Edit A Campaign Container" |
77 | | "/campaign/:id/addrewards" | "Add A Reward Component"
78 | | "/search-results" | "SearchResultsContainer" |
79 | | "/campaigns" | "Campaign Index Page" |
80 | | "/campaign/:id" | "Campaign Show Page" |
81 | | "/categories/:id" | "Category Show Page"
82 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/campaign_edit.css:
--------------------------------------------------------------------------------
1 | .edit-campaign {
2 | height: 100%;
3 | width: 80%;
4 | }
5 |
6 | .edit-campaign > h1 {
7 | padding-top: 40px;
8 | padding-left: 50px;
9 | font-weight: 400;
10 | font-size: 32px;
11 | font-weight: bold;
12 | }
13 |
14 | .c-form {
15 | font-size: 18px;
16 | }
17 |
18 | /*note that cform inherits from campaign new cform css*/
19 |
20 | .campaign-description {
21 | padding-left: 25px;
22 | margin-left: 25px;
23 | margin-top: 10px;
24 | padding-top: 20px;
25 | width: 600px;
26 | }
27 |
28 | .campaign-description > input {
29 | width: 375px;
30 | height: 45px;
31 | margin-top: 5px;
32 | }
33 |
34 | .tagline {
35 | padding-left: 25px;
36 | margin-left: 25px;
37 | margin-top: 10px;
38 | padding-top: 20px;
39 | width: 600px;
40 | }
41 |
42 | .tagline > input {
43 | width: 375px;
44 | height: 45px;
45 | margin-top: 5px;
46 | }
47 |
48 | .category_dropdown {
49 | margin-top: 10px;
50 | padding-left: 25px;
51 | margin-left: 25px;
52 | width: 600px;
53 | padding-top: 20px;
54 | }
55 |
56 | .campaign-img-preview {
57 | display: flex;
58 | flex-direction: column;
59 | justify-content: space-around;
60 | margin-top: 10px;
61 | padding-left: 25px;
62 | margin-left: 25px;
63 | width: 600px;
64 | padding-top: 20px;
65 | padding-bottom: 20px;
66 | border: none;
67 | }
68 |
69 | .row-div {
70 | position: relative;
71 | display: flex;
72 | flex-direction: row-reverse;
73 | justify-content: flex-end;
74 | align-items: center;
75 | }
76 |
77 | .campaign-img > img {
78 | position: absolute;
79 | top:0;
80 | left: 0;
81 | margin-left: 508px;
82 | width: 80px;
83 | height: 80px;
84 | margin-top: -27px;
85 | background-size: cover;
86 | background-color: #c3c3c3;
87 | border-radius: 100%;
88 | border: 1px solid #e4e4e4;
89 | }
90 |
91 | .category {
92 | padding-top: 5px;
93 | }
94 |
95 | .submit-button > input {
96 | margin-left: 50px;
97 | margin-top: 10px;
98 | background-color: #FC3468;
99 | color: white;
100 | align-items: center;
101 | min-height: 30px;
102 | padding:5px;
103 | border: none;
104 | }
105 |
106 | .submit-button > input:hover {
107 | cursor: pointer;
108 | background: #ff1854;
109 | }
110 |
111 | .file-upload > input {
112 | text-align: center;
113 | font-size: 12px;
114 | }
115 |
116 | .campaign-errors {
117 | color: red;
118 | }
119 |
--------------------------------------------------------------------------------
/frontend/components/session/session_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 |
4 | class SessionForm extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | password: "Password",
9 | first_name: "First Name",
10 | last_name: "Last Name",
11 | email: "Email",
12 | errors: this.props.errors
13 | };
14 | this.handleSubmit = this.handleSubmit.bind(this);
15 | }
16 |
17 | handleSubmit(e) {
18 | e.preventDefault();
19 | const user = this.state;
20 |
21 | this.props.processForm(user).then(() => this.props.modalClose());
22 | }
23 |
24 | update(field) {
25 | return e => this.setState({
26 | [field]: e.currentTarget.value
27 | });
28 | }
29 |
30 |
31 |
32 | handleFormType(type) {
33 | if (this.props.formType === "signup") {
34 | return(
35 |
36 |
40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 | );
51 | }
52 | }
53 |
54 | renderErrors() {
55 | if (this.props.errors) {
56 |
57 | return(
58 |
59 | {this.props.errors}
60 |
61 | );
62 | }
63 | }
64 |
65 | render() {
66 | return(
67 |
68 |
91 |
92 | );
93 | }
94 | }
95 |
96 | export default withRouter(SessionForm);
97 |
--------------------------------------------------------------------------------
/frontend/components/user/profile.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | class UserProfile extends React.Component {
5 | constructor(props) {
6 | super(props);
7 |
8 | this.state = {
9 | user: null,
10 | imageFile: null,
11 | imageUrl: null,
12 | };
13 |
14 | this.updateFile = this.updateFile.bind(this);
15 | this.handleSubmit = this.handleSubmit.bind(this);
16 | }
17 |
18 | componentWillReceiveProps(newProps) {
19 | if (this.props.user.id != newProps.user.id) {
20 | this.setState({user: newProps.user.id, imageUrl: newProps.user.image_ur});
21 | }
22 | }
23 |
24 | updateFile(e) {
25 | let file = e.currentTarget.files[0];
26 | var fileReader = new FileReader();
27 | fileReader.onloadend = () => {
28 | this.setState({imageFile: file, imageUrl: fileReader.result});
29 | };
30 | if (file) {
31 | fileReader.readAsDataURL(file);
32 | }
33 | }
34 |
35 | handleSubmit(e) {
36 | var formData = new FormData();
37 | formData.append("user[image]", this.state.imageFile);
38 | formData.append("user[id]", this.props.user.id);
39 | this.props.editUser(formData).then(usr => {
40 | this.setState({user: usr});
41 | });
42 | }
43 |
44 | render() {
45 | return(
46 |
47 |
48 |
49 |
User Image
50 |
51 |
52 |
53 |
54 |
55 |
56 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
About Me
75 |
76 | {this.props.user.about_me}
77 |
78 |
79 |
80 |
81 | );
82 |
83 | }
84 | }
85 |
86 | export default UserProfile;
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [IndieFomo](http://www.indiefomo.com/#/)
2 |
3 | # IndieFomo
4 | Indie Fomo is a web aplication inspired by Indiegogo built using Ruby on Rails on the backend, a PostgreSQL database, and React with Redux on the front end.
5 | 
6 |
7 | ## Features And Implementation
8 |
9 | ### Campaign Creation
10 | On the backend, campaigns are stored in a single table with foreign keys connecting each campaign to it's owner and category. Each campaign has many associated rewards which are stored in a separate table. Each respective campaign has two associated views. First of which is the campaign show page that lists campaign funding details, a description, and its associated rewards. The second view is an overview tile that links the user to the campaign show page.
11 | 
12 | ### Reward Creation
13 | On the backend, rewards are stored in a single table with a foreign key associating each reward to its campaign. Each reward is structured as a nested resource under its campaign so users can add rewards as they create their campaigns. Rewards are rendered twice in a 'preview' mode where users can delete and edit rewards before launching their finished campaigns as well as in a final show view that is rendered on the campaign show page.
14 | 
15 | ### Contributions
16 | On the backend, contributions are stored in a single table with foreign keys connecting them to a user, optional reward, and campaign associated with each contribution. Users can make contributions on the campaign show page by either opting to purchase a reward or by creating a custom amount contribution. This contribution history can then be accessed via the users profile page. Total contributions as well as the contribution count are rendered on the campaign show page. The contributions are represented as a progress bar that expresses the total contributions as a percentage of target campaign amount. The progress bar dynamically updates as users donate to the campaign.
17 | 
18 | ### Users and Profiles
19 | The Users table stores profile and user data including passwords and session tokens. Password authentication achieved with the BCrypt Gem.
20 | ### Search and Categories
21 | Users can explore available campaigns by either looking through the category tiles that link to a category show page that filters campaigns by category, or they can user the search tool to find campaigns by title or tagline. The search function uses the pg_search gem uses search scopes against the Campaign table, lending higher weight to results containing keywords in the title.
22 | 
23 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/reward_tile.css:
--------------------------------------------------------------------------------
1 | .reward-show {
2 | border: 1px solid #dddddd;
3 | height: 200px;
4 | width: 220px;
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: space-between;
8 | align-items: flex-start;
9 | margin-bottom: 30px;
10 | }
11 |
12 | .reward-show:hover {
13 | cursor: pointer;
14 | }
15 |
16 | .reward {
17 | margin-bottom: 30px;
18 | border: 1px solid #dddddd;
19 | height: 200px;
20 | width: 220px;
21 | display: flex;
22 | flex-direction: column;
23 | justify-content: space-between;
24 | align-items: flex-start;
25 | }
26 |
27 | .reward > ul{
28 | display: flex;
29 | padding: 5px;
30 | box-sizing: border-box;
31 | justify-content: space-between;
32 | flex-direction: column;
33 | width: 100%;
34 | height: 50%;
35 | }
36 |
37 | .reward-show > ul {
38 | padding: 5px;
39 | justify-content: space-between;
40 | display: flex;
41 | flex-direction: column;
42 | width: 100%;
43 | height: 50%;
44 | }
45 |
46 | .time-left{
47 | width: 100%;
48 | display: flex;
49 | flex-direction: row;
50 | justify-content: center;
51 | justify-content: center;
52 | }
53 | .get-it {
54 | display: flex;
55 | flex-direction: column;
56 | justify-content: center;
57 | background-color: #FC3468;
58 | color: white;
59 | align-items: center;
60 | height: 30px;
61 | width: 100%;
62 | }
63 |
64 | .contribute-thanks {
65 | display: flex;
66 | flex-direction: row;
67 | font-size: 14px;
68 | justify-content: center;
69 | background-color: #FC3468;
70 | color: white;
71 | align-items: center;
72 | height: 30px;
73 | width: 100%;
74 | }
75 |
76 | .get-it:hover {
77 | cursor: pointer;
78 | }
79 |
80 |
81 | .eta {
82 | background-color: #f5f5f5;
83 | width: 100%;
84 | height: 30px;
85 | display: flex;
86 | flex-direction: column;
87 | justify-content: center;
88 | align-items: center;
89 | }
90 |
91 | .delete {
92 | background-color: #f5f5f5;
93 | width: 100%;
94 | height: 30px;
95 | display: flex;
96 | flex-direction: column;
97 | justify-content: center;
98 | align-items: center;
99 | }
100 |
101 | .delete:hover {
102 | background-color: #FC3468;
103 | color: white;
104 | cursor: pointer;
105 | width: 100%;
106 | height: 30px;
107 | display: flex;
108 | flex-direction: column;
109 | justify-content: center;
110 | align-items: center;
111 | }
112 |
113 |
114 | .reward-prices {
115 | font-weight: bold;
116 | font-size: 18px;
117 | }
118 |
119 | .reward-titles {
120 | color: #878787;
121 | font-size: 18px;
122 | }
123 |
124 | .reward-descriptions {
125 | font-size: 14px;
126 | width: 212px;
127 | }
128 |
--------------------------------------------------------------------------------
/frontend/components/home_page/tile_slider.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Slider from 'react-slick';
3 | import CampaignIndexItem from '../campaign/campaign_index_item';
4 | import {selectCampaigns} from '../../reducers/selectors';
5 |
6 | class TileSlider extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | index: 0,
12 | tiles: [],
13 | move: false,
14 | direction: 'right'
15 | };
16 |
17 | this.movement = this.movement.bind(this);
18 | }
19 |
20 | componentDidMount() {
21 | this.props.fetchCampaigns(6).then((camps) => {
22 | let campaigns = Object.keys(camps.campaigns).map(key => camps.campaigns[key])
23 | this.setState({tiles: campaigns});
24 | });
25 | }
26 |
27 | slideLeft(e){
28 | this.movement(e);
29 | }
30 |
31 | slideRight(e){
32 | this.movement(e);
33 | }
34 |
35 | movement(e){
36 | const campaignTiles = this.state.tiles;
37 |
38 | if (e.currentTarget.className === 'slider-left-button') {
39 | campaignTiles.push(campaignTiles.shift());
40 | } else if (e.currentTarget.className === 'slider-right-button') {
41 | campaignTiles.unshift(campaignTiles.pop());
42 | }
43 |
44 | this.setState({move:false, tiles: campaignTiles});
45 | }
46 |
47 |
48 | render() {
49 | let camps = this.state.tiles;
50 | let slidercname = "";
51 |
52 | if(this.state.move){
53 | slidercname = (this.state.dir === 'left' ? " slider-move-left" : " slider-move-right");
54 | }
55 |
56 | if (camps[0]) {
57 | return(
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 | } else {
81 | return(
82 | Waiting..
83 | );
84 | }
85 | }
86 | }
87 |
88 | export default TileSlider;
89 |
--------------------------------------------------------------------------------
/frontend/components/user/user_show.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 | import UserProfile from './profile';
4 | import UserCampaignList from './campaigns';
5 | import UserContributions from './contributions';
6 |
7 | class UserShow extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | user: null,
12 | show: "profile"
13 | };
14 |
15 | this.handleClick = this.handleClick.bind(this);
16 | this.properContent = this.properContent.bind(this);
17 | }
18 |
19 | componentDidMount() {
20 | this.props.getUser(this.props.params.id);
21 | }
22 |
23 | handleClick(e) {
24 |
25 | switch (e.target.className) {
26 | case "user-nav-location loc-profile":
27 | return this.setState({show: "profile"});
28 | case "user-nav-location loc-campaigns":
29 | return this.setState({show: "campaigns"});
30 | case "user-nav-location loc-contributions":
31 | return this.setState({show: "contributions"});
32 | default:
33 | break;
34 | }
35 | }
36 |
37 |
38 | properContent() {
39 | switch (this.state.show) {
40 | case "profile":
41 | return(
42 |
44 | );
45 | case "campaigns":
46 | return(
47 |
49 | );
50 | case "contributions":
51 | return(
52 |
53 | );
54 | default:
55 | return(
56 |
59 | );
60 | }
61 | }
62 |
63 | render() {
64 | const name = this.props.user ? this.props.user.first_name : "";
65 | const type = this.state.show;
66 | const content = this.properContent();
67 |
68 | if (this.props.user.id) {
69 | return(
70 |
71 |
72 |
73 |
74 |
75 | Profile
76 |
77 |
78 | Campaigns: {this.props.campaigns.count}
79 |
80 |
81 | Contributions: {this.props.contributions.count}
82 |
83 |
84 |
85 |
86 |
Hi, {name}
87 |
88 |
89 |
90 | {content}
91 |
92 |
93 | );
94 | } else {
95 | return (
96 |
97 | );
98 | }
99 | }
100 | }
101 |
102 |
103 |
104 | export default withRouter(UserShow);
105 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/newRewards.css:
--------------------------------------------------------------------------------
1 | .create-reward {
2 | display: flex;
3 | flex-direction: column;
4 | width: 80%;
5 | height: 100%;
6 | justify-content: flex-start;
7 | align-items: flex-start;
8 | }
9 |
10 | .reward-- {
11 | width: 50%;
12 | padding-left: 50px;
13 |
14 | }
15 |
16 | .reward-- > h1 {
17 | margin-top: 30px;
18 | font-weight: bold;
19 | font-size: 32px;
20 | height: 100%;
21 | }
22 |
23 | .reward-- > h2 {
24 | font-size: 24px;
25 | }
26 |
27 | .create-reward > main {
28 | display: flex;
29 | flex-direction: row;
30 | justify-content: space-between;
31 | }
32 |
33 | .reward-form {
34 | display: flex;
35 | flex-direction: column;
36 | width: 50%;
37 | font-size: 18px;
38 | align-items: space-between;
39 | }
40 |
41 | .rewards-show {
42 | padding-left: 150px;
43 | height: 100%;
44 | display: block;
45 | }
46 |
47 | .create-reward > h1 {
48 | padding-top: 10px;
49 | padding-left: 50px;
50 | font-weight: 400;
51 | font-size: 24px;
52 | }
53 |
54 | .create-reward > h2 {
55 | padding-top: 10px;
56 | padding-left: 50px;
57 | font-weight: 400;
58 | font-size: 14px;
59 | }
60 |
61 | .reward-title {
62 | padding-left: 25px;
63 | margin-left: 25px;
64 | padding-top: 20px;
65 | padding-bottom: 20px;
66 | width: 90%;
67 | /*border: 1px solid #e4e4e4;*/
68 |
69 | }
70 |
71 | .reward-title > input {
72 | width: 375px;
73 | height: 45px;
74 |
75 | }
76 |
77 | .rewards-img {
78 | padding-top: 10px;
79 | padding-left: 50px;
80 | font-weight: 400;
81 | font-size: 14px;
82 |
83 | }
84 |
85 | .reward-description {
86 | padding-left: 25px;
87 | margin-left: 25px;
88 | margin-top: 10px;
89 | padding-top: 20px;
90 | padding-bottom: 20px;
91 | width: 90%;
92 | }
93 |
94 | .reward-description > input {
95 | width: 375px;
96 | height: 45px;
97 |
98 | }
99 |
100 | .reward-price {
101 | padding-left: 25px;
102 | margin-left: 25px;
103 | margin-top: 10px;
104 | padding-top: 20px;
105 | padding-bottom: 20px;
106 | width: 90%;
107 | /*border: 1px solid #e4e4e4;*/
108 |
109 | }
110 |
111 | .reward-price > input {
112 | width: 150px;
113 | height: 35px;
114 |
115 | }
116 |
117 | .submit-buttons > input{
118 | margin-left: 50px;
119 | margin-top: 10px;
120 | background-color: #FC3468;
121 | color: white;
122 | width: 130px;
123 | text-align: center;
124 | align-items: center;
125 | min-height: 30px;
126 | padding:5px;
127 | border: none;
128 | }
129 |
130 | .submit-buttons > input:hover {
131 | cursor: pointer;
132 | background: #ff1854;
133 | }
134 |
135 | .r-form {
136 | width: 100%;
137 | display: flex;
138 | flex-direction: column;
139 | }
140 |
141 | .file-upload {
142 | max-width:500px;
143 | }
144 |
145 | .file-upload > input {
146 | text-align: center;
147 | font-size: 12px;
148 | }
149 |
150 | .rewards-show {
151 | display: flex;
152 | width: 500px;
153 | height: 500px;
154 | /*border: 1px solid red;*/
155 | }
156 |
157 | .rewards-item {
158 | width: 230px;
159 | height: 300px;
160 | display: flex;
161 | flex-direction: column;
162 | justify-content: space-between;
163 | padding-bottom: 25px;
164 | }
165 |
166 | .rewards-details {
167 | width: 100;
168 | height: 40%;
169 | display: flex;
170 | flex-direction: column;
171 | justify-content: space-between;
172 | padding-left: 10px;
173 |
174 | /*border: 1px solid #e4e4e4;*/
175 | }
176 |
--------------------------------------------------------------------------------
/frontend/components/campaign/rewards/reward.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class Reward extends React.Component {
4 |
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | hover: false,
9 | madeContribution: false
10 | };
11 |
12 | this.contribute = this.contribute.bind(this);
13 | this.handleButtonType = this.handleButtonType.bind(this);
14 | this.deleteReward = this.deleteReward.bind(this);
15 | this.onhoverIn = this.onhoverIn.bind(this);
16 | this.onhoverOut = this.onhoverOut.bind(this);
17 | }
18 |
19 | onhoverIn() {
20 | this.setState({hover: true});
21 | }
22 |
23 | onhoverOut() {
24 | this.setState({hover: false});
25 | }
26 |
27 | deleteReward() {
28 | this.props.deleteReward({campaign_id: this.props.campaignId, id: this.props.reward.id})
29 | }
30 |
31 | handleButtonType() {
32 | if (this.state.madeContribution) {
33 | return(
34 | THANK YOU FOR CONTRIBUTING!
35 | );
36 | }
37 | else if (this.state.hover && this.props.user.id) {
38 | return(
39 | GET THIS PERK
40 | );
41 | } else {
42 | return(
43 | ESTIMATED June 2018
44 | );
45 | }
46 | }
47 |
48 | getThisDate() {
49 | let monthHash = {1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May',
50 | 6: 'Jun', 7: 'Jul', 8: 'Aug', 9: 'Sept', 10: 'Oct', 11: 'Nov', 12: 'Dec'};
51 | let today = new Date();
52 | let day = today.getDate();
53 | let mm = today.getMonth() + 1;
54 | let yy = today.getFullYear();
55 |
56 | if (day < 10) {
57 | day = '0' + day;
58 | }
59 | mm = parseInt(mm);
60 | mm = monthHash[mm];
61 |
62 | today = mm + ' ' + day + ' ' + yy;
63 | return today;
64 | }
65 |
66 | contribute() {
67 | let date = this.getThisDate();
68 |
69 | this.props.makeContribution({user_id: this.props.user.id, reward_id: this.props.reward.id,
70 | amount: this.props.reward.price, campaign_id: this.props.campaignId, date: date }).then((contribution) => {
71 | this.setState({madeContribution: true});
72 | });
73 | }
74 |
75 |
76 | render() {
77 |
78 | if (this.props.onShow) {
79 | return(
80 |
81 |
82 | ${this.props.reward.price}
83 | {this.props.reward.title}
84 | {this.props.reward.description}
85 |
86 |
87 | {this.handleButtonType()}
88 |
89 |
90 | );
91 | } else {
92 | return(
93 |
94 |
95 | ${this.props.reward.price}
96 | {this.props.reward.title}
97 | {this.props.reward.description}
98 |
99 |
Delete
100 |
101 | );
102 | }
103 |
104 | }
105 | }
106 |
107 | export default Reward;
108 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/campaign_index.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the Campaigns controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
5 | .all-campaigns {
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | width: 80%;
10 | display: block;
11 | align-items: center;
12 | margin-top: 25px;
13 | margin-bottom: 50px;
14 | // border: 1px solid black;
15 | }
16 |
17 | .campaigns-x {
18 | box-sizing: border-box;
19 | display: flex;
20 | flex-direction: row;
21 | justify-content: flex-start;
22 | align-items: center;
23 | margin-top: 1%;
24 | height: 90%;
25 | width: 100%;
26 | margin: 0;
27 | flex-wrap: wrap;
28 | margin-left: 100px;
29 | // border: 1px solid yellow;
30 |
31 | }
32 |
33 | .title- {
34 | font-size: 24px;
35 | width: 65%;
36 | margin-top: 40px;
37 | margin-bottom: 40px;
38 | margin-left: 100px;
39 | }
40 |
41 | .campaign-tile {
42 | position: relative;
43 | display: flex;
44 | justify-content: space-between;
45 | flex-direction: row;
46 | flex-wrap: wrap;
47 | border: 1px solid rgba(0, 0, 0, 0.0980392);
48 | margin: 4px;
49 | width: 275px;
50 | height: 450px;
51 | margin-bottom: 10px;
52 | }
53 |
54 | .campaign-tile:hover {
55 | cursor: pointer;
56 | border: 1px solid lightgrey;
57 | }
58 |
59 | img {
60 | display: block;
61 | align-self: flex-start;
62 | justify-content: center;
63 | height: 222px;
64 | width: 100%;
65 | overflow: hidden;
66 |
67 | }
68 |
69 | .description-index-tiles {
70 | // padding: 2%;
71 | align-items: flex-start;
72 | justify-content: space-between;
73 | display: flex;
74 | flex-direction: column;
75 | flex: auto;
76 | width: 100%;
77 | }
78 |
79 | .description-container {
80 | display: flex;
81 | flex-direction: column;
82 | padding: 2px;
83 | justify-content: flex-start;
84 | align-items: baseline;
85 | height: 40%;
86 | }
87 |
88 |
89 | .text {
90 | padding: 2%;
91 | display: block;
92 | width: 100%;
93 | }
94 |
95 | .tagline-text {
96 | color: #878787;
97 | padding-left: 2%;
98 | padding-right: 2%;
99 | width: 95%;
100 | font: 10px
101 | }
102 |
103 | #item {
104 | // display: flex;
105 | // width: 21%;
106 | // height: auto;
107 | // padding: 10px;
108 | }
109 |
110 |
111 | .campaign-tile {
112 | display: flex;
113 | // width: 225px;;
114 | // height: auto;
115 | // overflow-y: visible;
116 | }
117 |
118 | .description-container {
119 | // border: 1px solid black;
120 | height: 200px;
121 | width: 100%;
122 | display: flex;
123 | flex-direction: column;
124 | justify-content: center;
125 | }
126 |
127 |
128 | .progress-tile-bar {
129 | // border: 1px solid black;
130 | height: 100%;
131 | width: 95%;
132 | display: flex;
133 | flex-direction: column;
134 | justify-content: flex-end;
135 | padding: 5px;
136 | }
137 |
138 | .dollars-tile {
139 | font-size: 12px;
140 | }
141 |
142 | .funds-tile{
143 | font-size: 12px;
144 | }
145 |
146 | .bar-tile {
147 | display: inline-block;
148 | background-color: #f5f5f5;
149 | min-height: 5px;
150 | width: 100%;
151 | margin-top: 5px;
152 | margin-bottom: 5px;
153 | }
154 |
155 | .percentage-tile {
156 | max-width: 100%;
157 | background-color: #ff1854;
158 | min-height: 5px;
159 | }
160 |
161 | .funds-tile {
162 | margin-top: 2px;
163 | font-size: 12px;
164 | }
165 |
166 | .moment-tile {
167 | font-size: 12px;
168 | }
169 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like
20 | # NGINX, varnish or squid.
21 | # config.action_dispatch.rack_cache = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
26 |
27 | # Compress JavaScripts and CSS.
28 | config.assets.js_compressor = :uglifier
29 | # config.assets.css_compressor = :sass
30 |
31 | # Do not fallback to assets pipeline if a precompiled asset is missed.
32 | config.assets.compile = false
33 |
34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35 | # yet still be able to expire them through the digest params.
36 | config.assets.digest = true
37 |
38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
39 |
40 | # Specifies the header that your server uses for sending files.
41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
43 |
44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
45 | config.force_ssl = true
46 |
47 | # Use the lowest log level to ensure availability of diagnostic information
48 | # when problems arise.
49 | config.log_level = :debug
50 |
51 | # Prepend all log lines with the following tags.
52 | # config.log_tags = [ :subdomain, :uuid ]
53 |
54 | # Use a different logger for distributed setups.
55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
61 | # config.action_controller.asset_host = 'http://assets.example.com'
62 |
63 | # Ignore bad email addresses and do not raise email delivery errors.
64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
65 | # config.action_mailer.raise_delivery_errors = false
66 |
67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
68 | # the I18n.default_locale when a translation cannot be found).
69 | config.i18n.fallbacks = true
70 |
71 | # Send deprecation notices to registered listeners.
72 | config.active_support.deprecation = :notify
73 |
74 | # Use default logging formatter so that PID and timestamp are not suppressed.
75 | config.log_formatter = ::Logger::Formatter.new
76 |
77 | # Do not dump schema after migrations.
78 | config.active_record.dump_schema_after_migration = false
79 | end
80 |
--------------------------------------------------------------------------------
/frontend/components/campaign/new_campaign.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router';
3 |
4 |
5 | class NewCampaign extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | user_id: null,
10 | title: null,
11 | target_amount: 0,
12 | descriptions: "",
13 | errors: ""
14 | };
15 |
16 | this.handleSubmit = this.handleSubmit.bind(this);
17 | }
18 |
19 | handleSubmit(e) {
20 | e.preventDefault();
21 | let newCamp = this.state;
22 | delete newCamp["errors"];
23 |
24 | return this.props.createCampaign(newCamp)
25 | .then((camp) => this.success(camp),
26 | (err) => this.fail(err));
27 | }
28 |
29 | success(camp) {
30 |
31 | const url = `campaign/${camp.campaign.id}/edit`;
32 | this.props.router.push(url);
33 | }
34 |
35 | fail(err) {
36 |
37 | this.setState({errors: err});
38 | }
39 |
40 | componentWillMount() {
41 | if (!this.props.user) {
42 | this.props.router.push('/');
43 | } else {
44 | this.setState({user_id: this.props.user.id});
45 | }
46 | }
47 |
48 | update(field) {
49 | return e => this.setState({
50 | [field]: e.currentTarget.value
51 | });
52 | }
53 |
54 | getErrors() {
55 |
56 | if (this.props.errors.includes("Target") && this.props.errors.includes("Title")) {
57 | return (
58 |
59 |
Please enter a valid Amount and Title!
60 |
61 | );
62 | } else if (this.props.errors.includes("Title")) {
63 | return (
64 |
65 |
Please enter a valid Title!
66 |
67 | );
68 | } else if (this.props.errors.includes("Target")) {
69 | return (
70 |
71 |
Please enter a valid Amount!
72 |
73 | );
74 | }
75 |
76 | }
77 |
78 | render() {
79 | let errs = null;
80 |
81 | if (this.props.errors) {
82 | errs = this.getErrors();
83 | }
84 |
85 | return(
86 |
87 |
Start a Campaign
88 |
Crowdfund your Fomo
89 |
90 |
91 |
117 |
118 | {errs}
119 |
120 |
121 | );
122 | }
123 | }
124 |
125 | export default withRouter(NewCampaign);
126 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/discovery.css:
--------------------------------------------------------------------------------
1 | .discovery {
2 | display: flex;
3 | flex-direction: column;
4 | width: 1100px;
5 | justify-content: space-around;
6 | align-items: center;
7 | min-height: 600px;
8 | margin-top: 20px;
9 | box-sizing: border-box;
10 | /*border: 1px solid blue;*/
11 | }
12 |
13 |
14 | .discover-slider {
15 | display: flex;
16 | flex-direction: row;
17 | align-items: center;
18 | justify-content: center;
19 | width: 100%;
20 | /*border: 1px solid black;*/
21 | margin: 20px;
22 | }
23 |
24 | .tile-slider {
25 | display: flex;
26 | justify-content: space-around;
27 | align-items: center;
28 | display: flex;
29 | /*border: 1px solid red;*/
30 | width: calc(100% - 50px);
31 |
32 | /*height: 450px;*/
33 | }
34 |
35 | .slider-move-left {
36 | display: flex;
37 | justify-content: space-around;
38 | align-items: center;
39 | display: flex;
40 | /*border: 1px solid red;*/
41 | width: calc(100% - 50px);
42 | overflow: hidden;
43 | }
44 |
45 | .slider-move-right {
46 | display: flex;
47 | justify-content: space-around;
48 | align-items: center;
49 | display: flex;
50 | /*border: 1px solid black;*/
51 | width: calc(100% - 50px);
52 | overflow: hidden;
53 | }
54 |
55 | /*CSS Animation for Discovery Slider*/
56 | .slider-move-right > div{
57 | animation-name: slidermoveright;
58 | animation-duration: 0.5s;
59 | }
60 | @keyframes slidermoveright{
61 | 0% {}
62 | }
63 | .slider-move-left > div {
64 | animation-name: slidermoveleft;
65 | animation-duration: 0.5s;
66 | }
67 | @keyframes slidermoveleft{
68 | 0% {}
69 | }
70 |
71 |
72 | .slider-left-button {
73 | /*border: 1px solid green;*/
74 | width: 50px;
75 | height: 50px;
76 | color: black;
77 | }
78 |
79 | .discover-tile > div {
80 | max-width: 230px;
81 | }
82 |
83 | .arrows {
84 | width: 100%;
85 | height:100%;
86 | color: grey;
87 | }
88 |
89 | .arrows:hover {
90 | cursor: pointer;
91 | }
92 |
93 | .arrows:active {
94 | width: 95%;
95 | height: 95%;
96 | }
97 |
98 | .slider-right-button {
99 | /*border: 1px solid green;*/
100 | width: 50px;
101 | height: 50px;
102 | color: black;
103 | }
104 |
105 | .tile-slide-container {
106 | /*border: 1px solid yellow;*/
107 | display: flex;
108 | margin: 20px;
109 | padding-top: 150px;
110 | }
111 |
112 | .discovery-2 {
113 | width: 100%;
114 | height: 550px;
115 | display: flex;
116 | flex-direction: column;
117 | justify-content: space-between;
118 | align-items: center;
119 | margin: 10px;
120 | }
121 |
122 | .explore {
123 | color: black;
124 | z-index: 90;
125 | }
126 |
127 | .explore:hover {
128 | cursor: pointer;
129 | color: #ff1854;
130 | }
131 |
132 | .explore-nav {
133 | display: flex;
134 | flex-direction: row;
135 | justify-content: space-between;
136 | align-items: center;
137 | text-align: start;
138 | color: black;
139 | height: 50px;
140 | width: 90%;
141 | font-size: 18px;
142 | z-index: 90;
143 | padding: 10px;
144 | margin: 10px;
145 | /*position: relative;*/
146 | }
147 |
148 | .discovery > h1 {
149 | align-items: flex-start;
150 | width: 100%;
151 | /*border-bottom: 1px solid black;*/
152 | }
153 |
154 | .slick-prev {
155 | padding-right: 7px;
156 | }
157 |
158 | .slick-next {
159 | padding-left: 7px;
160 | }
161 |
162 | .slick-arrow, .slick-prev, .slick-next {
163 | color: transparent;
164 | width: 24px;
165 | height: 24px;
166 | align-self: center;
167 | }
168 |
169 | .slick-prev:before {
170 | content: '<';
171 | /*zoom: 8%;*/
172 | }
173 |
174 | .slick-next:before {
175 | content: '>';
176 | }
177 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/campaign_show.css:
--------------------------------------------------------------------------------
1 | .campaign-show {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: flex-start;
6 | width: 1200px;
7 | margin-top: 50px;
8 | margin-bottom: 50px;
9 | }
10 |
11 | .overview-tile {
12 | height: 400px;
13 | width: 100%;
14 | display: flex;
15 | flex-direction: column;
16 | justify-content: center;
17 | align-items: center;
18 | margin-top: 40px;
19 | }
20 | /*SHOW AND OVERVIEW COMPONENTS COMBINED*/
21 | .photo {
22 | width: 620px;
23 | height: 400;
24 | }
25 |
26 | .show-img {
27 | width: 100%;
28 | height: 280px;
29 | }
30 |
31 | .campaign-basics {
32 | width: 600px;
33 | display: flex;
34 | flex-direction: column;
35 | justify-content: flex-start;
36 | box-sizing: border-box;
37 | margin-left: 40px;
38 | }
39 |
40 | .campaign-basics > h1{
41 | font-size: 32px;
42 | padding: 5px;
43 | font-weight: bold;
44 | }
45 | .campaign-basics > h2 {
46 | padding-left: 5px;
47 | padding-right:5px;
48 | color: #878787;
49 | padding-bottom: 5px;
50 | font-size: 24px;
51 | }
52 |
53 | .user-profile {
54 | display: flex;
55 | flex-direction: row;
56 | justify-content: flex-start;
57 | align-items: flex-start;
58 | padding: 5px;
59 | padding-bottom: 10px;
60 | }
61 |
62 | .campaign-overview {
63 | display: flex;
64 | flex-direction: row;
65 | justify-content: space-around;
66 | margin-top: 30px;
67 | width: 80%;
68 | height: 500px;
69 | }
70 |
71 | .contribute {
72 | display: flex;
73 | flex-direction: row;
74 | height: 43px;
75 | justify-content: space-between;
76 | align-items: center;
77 | margin-top: 10px;
78 | }
79 |
80 | .contribution-errors {
81 | color: red;
82 | margin-top: 5px;
83 | height: 20px;
84 | }
85 |
86 | .back-it {
87 | background-color: #FC3468;
88 | color: white;
89 | align-items: center;
90 | min-height: 30px;
91 | width: 120px;
92 | padding:5px;
93 | margin: 3px;
94 |
95 | }
96 |
97 | .check-out {
98 | background-color: #FC3468;
99 | color: white;
100 | align-items: center;
101 | min-height: 30px;
102 | padding:5px;
103 | margin: 3px;
104 | width: 120px;
105 | }
106 |
107 | .back-it:hover {
108 | cursor: pointer;
109 | background: #ff1854;
110 | }
111 |
112 |
113 | .check-out:hover {
114 | cursor: pointer;
115 | background: #ff1854;
116 | }
117 |
118 | .usr-tag {
119 | display: flex;
120 | flex-direction: row;
121 | align-items: center;
122 | }
123 |
124 | .right-profile {
125 | font-size: 14px;
126 | }
127 |
128 |
129 | .about_me {
130 | color: #878787;
131 | }
132 |
133 | .user-profile {
134 | display: flex;
135 | flex-direction: row;
136 | justify-content: flex-start;
137 | align-items: center;
138 |
139 | }
140 |
141 | .user-tag {
142 | border-radius: 100%;
143 | height: 40px;
144 | width: 40px;
145 | margin-right: 5px;
146 | }
147 |
148 | .more-details {
149 | display: flex;
150 | flex-direction: row;
151 | justify-content: space-between;
152 | margin-top: 40px;
153 | width: 80%;
154 | height: auto;
155 | margin-bottom: 100px;
156 | }
157 |
158 | .show-description {
159 | font-size: 18px;
160 | }
161 |
162 | .show-description > h1 {
163 | font-size: 24px;
164 | color: #878787;
165 | padding-bottom: 5px;
166 | }
167 |
168 | .rewards > h1 {
169 | font-size: 24px;
170 | color: #878787;
171 | padding-bottom: 5px;
172 | }
173 |
174 | .show-description {
175 | display: flex;
176 | flex-direction: column;
177 | justify-content: flex-start;
178 | border-top: 2px solid #eee;
179 | width:465px;
180 | padding-top: 10px;
181 | }
182 |
183 | .rewards {
184 | width: 450px;
185 | display: flex;
186 | flex-direction: column;
187 | justify-content: flex-start;
188 | height: 280px;
189 | border-top: 2px solid #eee;
190 | padding-top: 10px;
191 | }
192 |
--------------------------------------------------------------------------------