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 @@
+
+
+
+
+
+ <%= render 'common/modal_header', title: @campaign.name %>
+
+
+
+
+ <%= render 'components/wizard/step_index', wizard_name: 'new_p2p_campaign_wiz' %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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|%>
+
+
+
+ |
+
+
+ <%= 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 %>
+
+
+
+ <% 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 %>
+
+
+
+
+
+
+ <% end %>
+=======
+>>>>>>> p2p-campaigns
+=======
+
+
+ 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