diff --git a/app/assets/stylesheets/campaign_templates.css.scss b/app/assets/stylesheets/campaign_templates.css.scss new file mode 100644 index 00000000..a4fd6cfc --- /dev/null +++ b/app/assets/stylesheets/campaign_templates.css.scss @@ -0,0 +1 @@ +@import 'common/fundraisers'; diff --git a/app/assets/stylesheets/campaigns/custom_layout/safety_around_water.css.scss b/app/assets/stylesheets/campaigns/custom_layout/safety_around_water.css.scss index 0be0266f..c6d8efe4 100644 --- a/app/assets/stylesheets/campaigns/custom_layout/safety_around_water.css.scss +++ b/app/assets/stylesheets/campaigns/custom_layout/safety_around_water.css.scss @@ -1,7 +1,7 @@ @import 'mixins'; @import 'common/fundraisers'; -button, a.js-contributeButton { +main button, main a.js-contributeButton { background: #01a490 !important; } diff --git a/app/assets/stylesheets/campaigns/peer_to_peer/page.css.scss b/app/assets/stylesheets/campaigns/peer_to_peer/page.css.scss index 76fb21a7..bab7abff 100644 --- a/app/assets/stylesheets/campaigns/peer_to_peer/page.css.scss +++ b/app/assets/stylesheets/campaigns/peer_to_peer/page.css.scss @@ -11,6 +11,14 @@ body { padding: 0; background: $fog; } + +body > .ymca-banner { + margin: auto; + width: 100vw; + height: 120px; + background-color: #01a490; +} + main { display: block; padding: 60px 0 50px 0; diff --git a/app/controllers/campaigns_controller.rb b/app/controllers/campaigns_controller.rb index 121e40c8..00fa75c3 100644 --- a/app/controllers/campaigns_controller.rb +++ b/app/controllers/campaigns_controller.rb @@ -36,6 +36,13 @@ class CampaignsController < ApplicationController @nonprofit = current_nonprofit @url = Format::Url.concat(root_url, @campaign.url) + if @campaign.parent_campaign + @parent_campaign = @campaign.parent_campaign + @peer_to_peer_campaign_param = @parent_campaign.id + else + @peer_to_peer_campaign_param = @campaign.id + end + @campaign_background_image = FetchBackgroundImage.with_model(@campaign) if @nonprofit.custom_layout.blank? @@ -55,7 +62,15 @@ class CampaignsController < ApplicationController Time.use_zone(current_nonprofit.timezone || 'UTC') do params[:campaign][:end_datetime] = Chronic.parse(params[:campaign][:end_datetime]) if params[:campaign][:end_datetime].present? end - campaign = current_nonprofit.campaigns.create params[:campaign] + + if !params[:campaign][:parent_campaign_id] + campaign = current_nonprofit.campaigns.create params[:campaign] + else + profile_id = params[:campaign][:profile_id] + Profile.find(profile_id).update_attributes params[:profile] + campaign = create_peer_to_peer_campaign params[:campaign], profile_id + end + json_saved campaign, 'Campaign created! Well done.' end @@ -103,6 +118,31 @@ class CampaignsController < ApplicationController def peer_to_peer session[:donor_signup_url] = request.env["REQUEST_URI"] @npo = Nonprofit.find_by_id(params[:npo_id]) + @campaign = Campaign.find_by_id(params[:campaign_id]) + @profile = current_user.profile if current_user + end + + def custom_layout + @campaign = current_campaign + @timezone = Format::Timezone.to_proxy(current_nonprofit.timezone) + if @campaign.deleted && !current_campaign_editor? + redirect_to nonprofit_path(current_nonprofit) + flash[:notice] = "Sorry, we couldn't find that campaign" + return + end + @nonprofit = current_nonprofit + @url = Format::Url.concat(root_url, @campaign.url) + + if @campaign.parent_campaign + @parent_campaign = @campaign.parent_campaign + @peer_to_peer_campaign_param = @parent_campaign.id + else + @peer_to_peer_campaign_param = @campaign.id + end + + @campaign_background_image = FetchBackgroundImage.with_model(@campaign) + + render template: "nonprofits/custom_campaign_layouts/safety_around_water" end private @@ -113,4 +153,25 @@ class CampaignsController < ApplicationController end end + # TODO: refactor + def create_peer_to_peer_campaign(params, profile_id) + parent_campaign = Campaign.find(params[:parent_campaign_id]) + profile = Profile.find(profile_id) + + p2p_params = params.except(:nonprofit_id, :summary,:goal_amount) + p2p_params.merge!(parent_campaign.child_params) + p2p_params[:slug] = Format::Url.convert_to_slug "#{p2p_params[:name]}-#{profile.name}" + + campaign = Campaign.create(p2p_params) + + return campaign unless campaign.errors.empty? + + gift_option_params = [] + parent_campaign.campaign_gift_options.each do |option| + excluded_for_peer_to_peer = %w(id campaign_id created_at updated_at) + campaign.campaign_gift_options.create option.attributes.except(*excluded_for_peer_to_peer) + end + + campaign + end end diff --git a/app/controllers/nonprofits/campaign_templates_controller.rb b/app/controllers/nonprofits/campaign_templates_controller.rb new file mode 100644 index 00000000..72d4ba64 --- /dev/null +++ b/app/controllers/nonprofits/campaign_templates_controller.rb @@ -0,0 +1,18 @@ +module Nonprofits + class CampaignTemplatesController < ApplicationController + include NonprofitHelper + + before_filter :authenticate_nonprofit_admin!, only: :create + before_filter :authenticate_nonprofit_user!, only: [:index, :show] + + def index + @templates = CampaignTemplate.all + end + + def create + puts params + + render :status_ok + end + end +end diff --git a/app/models/campaign.rb b/app/models/campaign.rb index fbc09bb5..a8d4b241 100644 --- a/app/models/campaign.rb +++ b/app/models/campaign.rb @@ -32,7 +32,9 @@ class Campaign < ActiveRecord::Base :hide_thermometer, #bool :hide_title, # bool :receipt_message, # text - :hide_custom_amounts # boolean + :hide_custom_amounts, # boolean + :parent_campaign_id, + :reason_for_supporting validate :end_datetime_cannot_be_in_past, :on => :create validates :profile, :presence => true @@ -65,6 +67,9 @@ class Campaign < ActiveRecord::Base belongs_to :nonprofit belongs_to :campaign_template + belongs_to :parent_campaign, class_name: 'Campaign' + has_many :children_campaigns, class_name: 'Campaign', foreign_key: 'parent_campaign_id' + scope :published, -> {where(:published => true)} scope :active, -> {where(:published => true).where("end_datetime IS NULL OR end_datetime >= ?", Date.today)} scope :past, -> {where(:published => true).where("end_datetime < ?", Date.today)} @@ -159,4 +164,22 @@ class Campaign < ActiveRecord::Base (self.end_datetime.to_date - Date.today).to_i end + def self.create_from_template(template_id) + # building params handled by another object + # not sure this method is needed eventually + end + + def customizable_attributes_list + campaign_template.customizable_attributes_list if campaign_template + end + + def child_params + excluded_for_peer_to_peer = %w( + id created_at updated_at slug profile_id campaign_template_id url + total_raised show_recurring_amount external_identifier parent_campaign_id + reason_for_supporting + ) + excluded_for_peer_to_peer.push(customizable_attributes_list) + attributes.except(*excluded_for_peer_to_peer) + end end diff --git a/app/views/campaigns/_new_peer_to_peer_modal.html.erb b/app/views/campaigns/_new_peer_to_peer_modal.html.erb new file mode 100644 index 00000000..7d32811d --- /dev/null +++ b/app/views/campaigns/_new_peer_to_peer_modal.html.erb @@ -0,0 +1,84 @@ + + + + + + diff --git a/app/views/campaigns/index.html.erb b/app/views/campaigns/index.html.erb index 87a824f3..5a3be09b 100644 --- a/app/views/campaigns/index.html.erb +++ b/app/views/campaigns/index.html.erb @@ -25,6 +25,10 @@ has_mosaic: true %> New Campaign <% end %> + + Campaign templates + + <% if @active_campaigns.empty? %>

No active campaigns

<% else %> diff --git a/app/views/campaigns/peer_to_peer.html.erb b/app/views/campaigns/peer_to_peer.html.erb index 5cbdfa6c..7aa041db 100644 --- a/app/views/campaigns/peer_to_peer.html.erb +++ b/app/views/campaigns/peer_to_peer.html.erb @@ -11,8 +11,20 @@ <% content_for :javascripts do %> + <%= IncludeAsset.js '/client/js/campaigns/index/page.js' %> +<% end %> + + +<%= render 'components/header', + icon_class: 'icon-thermometer-medium', + title: 'Campaign Templates', + profile: @nonprofit, + has_mosaic: true +%> + +
+ <% if current_user %> + New Template + <% end %> + + <% if @templates.empty? %> +

No templates yet

+ <% else %> +
+ + + + <% @templates.each do |template|%> + + + + + <% end %> +
+ + +
+ <%= template.template_name %> +
+

+ Campaign title: <%= template.name %> +

+ +

+ Campaign summary:<%= template.summary ? strip_tags(template.summary) : strip_tags(template.tagline) %> +

+ +

+ Customizable attributes: <%= template.customizable_attributes_list %> +

+
+ +
+ <% end %> +
+ +<% if current_user %> + <%= render 'nonprofits/campaign_templates/new_modal' %> +<% end %> diff --git a/app/views/nonprofits/custom_campaign_layouts/safety_around_water.html.erb b/app/views/nonprofits/custom_campaign_layouts/safety_around_water.html.erb index 078fc99f..3920dc02 100644 --- a/app/views/nonprofits/custom_campaign_layouts/safety_around_water.html.erb +++ b/app/views/nonprofits/custom_campaign_layouts/safety_around_water.html.erb @@ -90,6 +90,20 @@ <% end %> +<<<<<<< HEAD +======= +<<<<<<< HEAD + <% if !@campaign.parent_campaign %> + + <% end %> + +======= +>>>>>>> custom-layout +>>>>>>> p2p-campaigns
@@ -102,6 +116,41 @@ +<<<<<<< HEAD +======= +<<<<<<< HEAD + <% if @campaign.parent_campaign %> +
+ +
+
+
+ /> +
+
+

<%= @campaign.profile.name %>

+

<%= @campaign.profile.city %>

+ <% if @campaign.profile.state_code && @campaign.profile.city %> +

<%= @campaign.profile.city_state %> <%= @campaign.profile.state_code %>

+ <% end %> +
+
+ +
+
I am supporting the Y becauseā€¦
+
+ <%= @campaign.reason_for_supporting %> +
+ + + Start Your Own Campaign for <%= @nonprofit.name %> + +
+
+
+ <% end %> +======= +>>>>>>> p2p-campaigns
@@ -121,6 +170,7 @@ (customizable reason) +<<<<<<< HEAD <% unless current_campaign_editor? %> Start Your Own Campaign for <%= @nonprofit.name %> @@ -129,6 +179,16 @@
+======= + + + Start Your Own Campaign for <%= @nonprofit.name %> + + + + +>>>>>>> custom-layout +>>>>>>> p2p-campaigns

<%= @campaign.name %>

diff --git a/client/js/campaign_templates/index/page.js b/client/js/campaign_templates/index/page.js new file mode 100644 index 00000000..6c565463 --- /dev/null +++ b/client/js/campaign_templates/index/page.js @@ -0,0 +1,2 @@ +if(app.user) + require('../new/wizard') diff --git a/client/js/campaign_templates/new/wizard.js b/client/js/campaign_templates/new/wizard.js new file mode 100644 index 00000000..172a4a6a --- /dev/null +++ b/client/js/campaign_templates/new/wizard.js @@ -0,0 +1,50 @@ +require('../../common/pikaday-timepicker') +require('../../components/wizard') +require('../../common/image_uploader') +var checkName = require('../../common/ajax/check_campaign_or_event_name') +var format_err = require('../../common/format_response_error') + +appl.def('advance_campaign_template_name_step', function(form_obj) { + appl.def('new_campaign_template', form_obj) + appl.wizard.advance('new_campaign_template_wiz') +}) + +// Post a new campaign template +appl.def('create_campaign_template', function(el) { + var form_data = utils.toFormData(appl.prev_elem(el)) + form_data = utils.mergeFormData(form_data, appl.new_campaign_template) + appl.def('new_campaign_template_wiz.loading', true) + + post_campaign_template(form_data) + .then(function(req) { + appl.notify("Redirecting to your campaign template...") + appl.redirect(JSON.parse(req.response).url) + }) + .catch(function(req) { + appl.def('new_campaign_template_wiz.loading', false) + appl.def('new_campaign_template_wiz.error', req.responseText) + }) +}) + + +var Pikaday = require('pikaday') +var moment = require('moment') +new Pikaday({ + field: document.querySelector('.js-date-picker'), + format: 'M/D/YYYY', + minDate: moment().toDate() +}) + +// Using the bare-bones XMLHttpRequest API so we can post form data and upload the image +function post_campaign_template(form_data) { + return new Promise(function(resolve, reject) { + var req = new XMLHttpRequest() + req.open("POST", '/nonprofits/' + app.nonprofit_id + '/campaign_templates') + req.setRequestHeader('X-CSRF-Token', window._csrf) + req.send(form_data) + req.onload = function(ev) { + if(req.status === 200) resolve(req) + else reject(req) + } + }) +} diff --git a/client/js/campaigns/new/peer_to_peer_wizard.js b/client/js/campaigns/new/peer_to_peer_wizard.js new file mode 100644 index 00000000..5070c5c2 --- /dev/null +++ b/client/js/campaigns/new/peer_to_peer_wizard.js @@ -0,0 +1,40 @@ +require('../../components/wizard') +var format_err = require('../../common/format_response_error') + +appl.def('advance_p2p_campaign_name_step', function(form_obj) { + var name = form_obj['campaign[name]'] + appl.def('new_p2p_campaign', form_obj) + appl.wizard.advance('new_p2p_campaign_wiz') +}) + +// Post a new campaign. +appl.def('create_p2p_campaign', function(el) { + var form_data = utils.toFormData(appl.prev_elem(el)) + form_data = utils.mergeFormData(form_data, appl.new_p2p_campaign) + appl.def('new_p2p_campaign_wiz.loading', true) + + post_campaign(form_data) + .then(function(req) { + appl.notify("Redirecting to your campaign...") + appl.redirect(JSON.parse(req.response).url) + }) + .catch(function(req) { + appl.def('new_p2p_campaign_wiz.loading', false) + appl.def('new_p2p_campaign_wiz.error', req.responseText) + }) +}) + +// Using the bare-bones XMLHttpRequest API so we can post form data and upload the image +function post_campaign(form_data) { + return new Promise(function(resolve, reject) { + var req = new XMLHttpRequest() + req.open("POST", '/nonprofits/' + app.nonprofit_id + '/campaigns') + req.setRequestHeader('X-CSRF-Token', window._csrf) + console.log(form_data) + req.send(form_data) + req.onload = function(ev) { + if(req.status === 200) resolve(req) + else reject(req) + } + }) +} diff --git a/client/js/campaigns/new/wizard.js b/client/js/campaigns/new/wizard.js index 2c73149b..ab107da1 100644 --- a/client/js/campaigns/new/wizard.js +++ b/client/js/campaigns/new/wizard.js @@ -20,6 +20,8 @@ appl.def('create_campaign', function(el) { form_data = utils.mergeFormData(form_data, appl.new_campaign) appl.def('new_campaign_wiz.loading', true) +// TODO: for p2p capmaigns, merge with preset campaing params + post_campaign(form_data) .then(function(req) { appl.notify("Redirecting to your campaign...") diff --git a/client/js/campaigns/peer_to_peer/page.js b/client/js/campaigns/peer_to_peer/page.js index a51ea457..63e828be 100644 --- a/client/js/campaigns/peer_to_peer/page.js +++ b/client/js/campaigns/peer_to_peer/page.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD // License: LGPL-3.0-or-later require('../new/wizard') @@ -114,3 +115,7 @@ main.onclick = function(ev) { } appl.def('search_results', []) } +======= +require('../new/peer_to_peer_wizard') +require('../../common/image_uploader') +>>>>>>> 2dc48070e... Merge branch 'p2p-campaigns' into h-custom-layout diff --git a/config/routes.rb b/config/routes.rb index 39f0f3e5..6fbd3c1e 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -110,6 +110,8 @@ Commitchange::Application.routes.draw do post(:send_code) end + resources(:campaign_templates, {only: [:index, :create]}) + post 'tracking', controller: 'trackings', action: 'create' end diff --git a/db/migrate/201810202124317_add_parent_campaign_id_to_campaigns.rb b/db/migrate/201810202124317_add_parent_campaign_id_to_campaigns.rb new file mode 100644 index 00000000..bcb567ec --- /dev/null +++ b/db/migrate/201810202124317_add_parent_campaign_id_to_campaigns.rb @@ -0,0 +1,5 @@ +class AddParentCampaignIdToCampaigns < ActiveRecord::Migration + def change + add_column :campaigns, :parent_campaign_id, :integer + end +end diff --git a/db/migrate/201810202124318_add_reason_for_supporting_to_campaigns.rb b/db/migrate/201810202124318_add_reason_for_supporting_to_campaigns.rb new file mode 100644 index 00000000..2f6791ca --- /dev/null +++ b/db/migrate/201810202124318_add_reason_for_supporting_to_campaigns.rb @@ -0,0 +1,5 @@ +class AddReasonForSupportingToCampaigns < ActiveRecord::Migration + def change + add_column :campaigns, :reason_for_supporting, :text + end +end diff --git a/db/structure.sql b/db/structure.sql index b146ebc2..2c2dd0f5 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -438,7 +438,13 @@ CREATE TABLE public.campaigns ( show_recurring_amount boolean DEFAULT false, end_datetime timestamp without time zone, external_identifier character varying(255), +<<<<<<< HEAD campaign_template_id integer +======= + campaign_template_id integer, + parent_campaign_id integer, + reason_for_supporting text +>>>>>>> p2p-campaigns ); @@ -4413,3 +4419,8 @@ INSERT INTO schema_migrations (version) VALUES ('201810202124316'); INSERT INTO schema_migrations (version) VALUES ('201810202124317'); +<<<<<<< HEAD +======= +INSERT INTO schema_migrations (version) VALUES ('201810202124318'); + +>>>>>>> p2p-campaigns diff --git a/spec/controllers/campaign_templates_controller_spec.rb b/spec/controllers/campaign_templates_controller_spec.rb new file mode 100644 index 00000000..a9c4283b --- /dev/null +++ b/spec/controllers/campaign_templates_controller_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +RSpec.describe CampaignTemplatesController, :type => :controller do + + describe "GET #index" do + it "returns http success" do + get :index + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/helpers/campaign_templates_helper_spec.rb b/spec/helpers/campaign_templates_helper_spec.rb new file mode 100644 index 00000000..dcf22e0f --- /dev/null +++ b/spec/helpers/campaign_templates_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the CampaignTemplatesHelper. For example: +# +# describe CampaignTemplatesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe CampaignTemplatesHelper, :type => :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/campaign_templates/index.html.erb_spec.rb b/spec/views/campaign_templates/index.html.erb_spec.rb new file mode 100644 index 00000000..d0b202bf --- /dev/null +++ b/spec/views/campaign_templates/index.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "campaign_templates/index.html.erb", :type => :view do + pending "add some examples to (or delete) #{__FILE__}" +end