Merge pull request #107 from houdiniproject/federated_p2p_campaigns

Federated P2P campaigns
This commit is contained in:
Eric Schultz 2018-11-21 14:19:21 -06:00 committed by GitHub
commit 4f27593311
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 1010 additions and 318 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@ client/js/nonprofits/donate/plugins-enabled
.DS_Store .DS_Store
/postgres-data
# Ignore bundler config # Ignore bundler config
/.bundle /.bundle

View file

@ -1,6 +1,6 @@
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '2.3.6' ruby '~> 2.3.6'
gem 'rake' gem 'rake'
gem 'rails', '3.2.22.5' gem 'rails', '3.2.22.5'
gem 'rails_12factor' gem 'rails_12factor'

View file

@ -0,0 +1,9 @@
/* License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later */
body .campaign-banner {
margin: auto;
width: 980px;
max-height: 120px;
text-align: center;
overflow: hidden;
}

View file

@ -6,11 +6,14 @@
@import 'components/simple_tabs'; @import 'components/simple_tabs';
@import 'pikaday'; @import 'pikaday';
@import 'common/image_uploader'; @import 'common/image_uploader';
@import '../common';
body { body {
padding: 0; padding: 0;
background: $fog; background: $fog;
} }
main { main {
display: block; display: block;
padding: 60px 0 50px 0; padding: 60px 0 50px 0;

View file

@ -13,6 +13,7 @@
@import 'components/draggable'; @import 'components/draggable';
@import 'gift_levels'; @import 'gift_levels';
@import '../../nonprofits/donation_form/form'; @import '../../nonprofits/donation_form/form';
@import '../common';
.button--gift { .button--gift {
font-size: 17px; font-size: 17px;
@ -103,6 +104,43 @@
margin-right: 1px; margin-right: 1px;
} }
.campaigner-profile {
display: flex;
flex-wrap: nowrap;
align-items: baseline;
}
.campaigner-profile > figure {
display: flex;
flex-direction: column;
align-items: center;
margin: auto 10px;
align-self: flex-start;
}
.campaigner-profile > figure > .avatar {
flex-basis: 20%;
min-width: 100px;
height: auto;
}
.avatar > img {
clip-path: circle(50% at center);
}
.campaigner-profile > figure > figcaption {
flex-basis: 40%;
padding: 15px;
text-align: left;
}
.campaigner-profile .reason {
flex-basis: 60%;
}
.campaigner-profile .pastelBox-body {
min-height: 100px;
}
@media screen and (max-width: 1000px) { @media screen and (max-width: 1000px) {
.box, .box,

View file

@ -19,6 +19,11 @@
width: 192px; width: 192px;
} }
.image-upload--banner {
width: 100%;
height: auto;
}
.image-upload span { .image-upload span {
margin-top: 30px; margin-top: 30px;
padding: 5px; padding: 5px;
@ -28,6 +33,11 @@
@include basicShadow; @include basicShadow;
} }
.image-upload--banner span {
margin-top: 30px;
margin-bottom: 30px;
}
.image-upload--large span { .image-upload--large span {
margin-top: 60px; margin-top: 60px;
} }

View file

@ -5,6 +5,7 @@ class CampaignsController < ApplicationController
helper_method :current_campaign_editor? helper_method :current_campaign_editor?
before_filter :authenticate_confirmed_user!, only: [:create, :name_and_id, :duplicate] before_filter :authenticate_confirmed_user!, only: [:create, :name_and_id, :duplicate]
before_filter :authenticate_campaign_editor!, only: [:update, :soft_delete] before_filter :authenticate_campaign_editor!, only: [:update, :soft_delete]
before_filter :authenticate_nonprofit_user!, only: [:create_via_template]
before_filter :check_nonprofit_status, only: [:index, :show] before_filter :check_nonprofit_status, only: [:index, :show]
def index def index
@ -36,23 +37,34 @@ class CampaignsController < ApplicationController
@nonprofit = current_nonprofit @nonprofit = current_nonprofit
@url = Format::Url.concat(root_url, @campaign.url) @url = Format::Url.concat(root_url, @campaign.url)
@campaign_background_image = FetchBackgroundImage.with_model(@campaign) if @campaign.child_campaign?
@parent_campaign = @campaign.parent_campaign
respond_to do |format| @peer_to_peer_campaign_param = @parent_campaign.id
format.html else
@peer_to_peer_campaign_param = @campaign.id
end end
@campaign_background_image = FetchBackgroundImage.with_model(@campaign)
end end
def activities def activities
render json: QueryDonations.for_campaign_activities(params[:id]) @campaign = current_campaign
render json: QueryDonations.for_campaign_activities(@campaign.id)
end end
def create def create
Time.use_zone(current_nonprofit.timezone || 'UTC') do Time.use_zone(current_nonprofit.timezone || 'UTC') do
params[:campaign][:end_datetime] = Chronic.parse(params[:campaign][:end_datetime]) if params[:campaign][:end_datetime].present? params[:campaign][:end_datetime] = Chronic.parse(params[:campaign][:end_datetime]) if params[:campaign][:end_datetime].present?
end end
campaign = current_nonprofit.campaigns.create params[:campaign]
json_saved campaign, 'Campaign created! Well done.' if !params[:campaign][:parent_campaign_id]
campaign = current_nonprofit.campaigns.create params[:campaign]
json_saved campaign, 'Campaign created! Well done.'
else
profile_id = params[:campaign][:profile_id]
Profile.find(profile_id).update_attributes params[:profile]
render json: CreatePeerToPeerCampaign.create(params[:campaign], profile_id)
end
end end
def update def update
@ -63,7 +75,6 @@ class CampaignsController < ApplicationController
json_saved current_campaign, 'Successfully updated!' json_saved current_campaign, 'Successfully updated!'
end end
# post 'nonprofits/:np_id/campaigns/:campaign_id/duplicate' # post 'nonprofits/:np_id/campaigns/:campaign_id/duplicate'
def duplicate def duplicate
@ -98,7 +109,22 @@ class CampaignsController < ApplicationController
def peer_to_peer def peer_to_peer
session[:donor_signup_url] = request.env["REQUEST_URI"] session[:donor_signup_url] = request.env["REQUEST_URI"]
@npo = Nonprofit.find_by_id(params[:npo_id]) @nonprofit = Nonprofit.find_by_id(params[:npo_id])
@parent_campaign = Campaign.find_by_id(params[:campaign_id])
if params[:campaign_id].present? && !@parent_campaign
raise ActionController::RoutingError.new('Not Found')
end
if current_user
@profile = current_user.profile
if (@parent_campaign)
@child_campaign = Campaign.where(
profile_id: @profile.id,
parent_campaign_id: @parent_campaign.id
).first
end
end
end end
private private
@ -108,5 +134,4 @@ class CampaignsController < ApplicationController
raise ActionController::RoutingError.new('Not Found') raise ActionController::RoutingError.new('Not Found')
end end
end end
end end

View file

@ -13,6 +13,8 @@ class Campaign < ActiveRecord::Base
:remove_main_image, # for carrierwave :remove_main_image, # for carrierwave
:background_image, :background_image,
:remove_background_image, #bool carrierwave :remove_background_image, #bool carrierwave
:banner_image,
:remove_banner_image,
:published, :published,
:video_url, #str :video_url, #str
:vimeo_video_id, :vimeo_video_id,
@ -30,7 +32,10 @@ class Campaign < ActiveRecord::Base
:hide_thermometer, #bool :hide_thermometer, #bool
:hide_title, # bool :hide_title, # bool
:receipt_message, # text :receipt_message, # text
:hide_custom_amounts # boolean :hide_custom_amounts, # boolean
:parent_campaign_id,
:reason_for_supporting,
:default_reason_for_supporting
validate :end_datetime_cannot_be_in_past, :on => :create validate :end_datetime_cannot_be_in_past, :on => :create
validates :profile, :presence => true validates :profile, :presence => true
@ -47,6 +52,7 @@ class Campaign < ActiveRecord::Base
mount_uploader :main_image, CampaignMainImageUploader mount_uploader :main_image, CampaignMainImageUploader
mount_uploader :background_image, CampaignBackgroundImageUploader mount_uploader :background_image, CampaignBackgroundImageUploader
mount_uploader :banner_image, CampaignBannerImageUploader
has_many :donations has_many :donations
has_many :charges, through: :donations has_many :charges, through: :donations
@ -61,6 +67,9 @@ class Campaign < ActiveRecord::Base
belongs_to :profile belongs_to :profile
belongs_to :nonprofit belongs_to :nonprofit
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 :published, -> {where(:published => true)}
scope :active, -> {where(:published => true).where("end_datetime IS NULL OR end_datetime >= ?", Date.today)} 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)} scope :past, -> {where(:published => true).where("end_datetime < ?", Date.today)}
@ -155,4 +164,21 @@ class Campaign < ActiveRecord::Base
(self.end_datetime.to_date - Date.today).to_i (self.end_datetime.to_date - Date.today).to_i
end end
def child_params
excluded_for_peer_to_peer = %w(
id created_at updated_at slug profile_id url
total_raised show_recurring_amount external_identifier parent_campaign_id
reason_for_supporting metadata
)
attributes.except(*excluded_for_peer_to_peer)
end
def child_campaign?
parent_campaign.present?
end
def parent_campaign?
!child_campaign?
end
end end

View file

@ -0,0 +1,16 @@
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
class CampaignBannerImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
def store_dir
"uploads/campaigns/#{mounted_as}/#{model.id}"
end
def extension_white_list
%w(jpg jpeg png)
end
def cache_dir
"#{Rails.root}/tmp/uploads"
end
end

View file

@ -5,39 +5,42 @@
<p><strong>Manage Campaign</strong></p> <p><strong>Manage Campaign</strong></p>
<a class='button--tiny edit'>
<!--= on 'click' (open_modal 'campaignSettings') -->
<i class='fa fa-gear'></i> Settings
</a>
<a class='button--tiny edit' target='_blank' href="<%= @campaign.url + '/supporters' %>">
<i class='fa fa-th-list'></i> Dashboard
</a>
<a class='button--tiny edit'>
<!--= on 'click' (open_modal 'offlineCampaignDonation') -->
<i class='fa fa-plus'></i> Offsite Donation
</a>
<a class='button--tiny edit'> <a class='button--tiny edit'>
<!--= on 'click' (open_modal 'manageGiftOptionsModal') --> <!--= on 'click' (open_modal 'campaignSettings') -->
<i class='fa fa-gift'></i> Gift Options <i class='fa fa-gear'></i> Settings
</a> </a>
<a class='button--tiny edit'> <% if @campaign.parent_campaign? %>
<!--= on 'click' (open_modal 'customReceiptModal') --> <a class='button--tiny edit' target='_blank' href="<%= @campaign.url + '/supporters' %>">
<i class='fa fa-pencil'></i> Custom Receipt <i class='fa fa-th-list'></i> Dashboard
</a> </a>
<a class='button--tiny edit'>
<!--= on 'click' (open_modal 'offlineCampaignDonation') -->
<i class='fa fa-plus'></i> Offsite Donation
</a>
<a class='button--tiny edit'>
<!--= on 'click' (open_modal 'manageGiftOptionsModal') -->
<i class='fa fa-gift'></i> Gift Options
</a>
<a class='button--tiny edit'>
<!--= on 'click' (open_modal 'customReceiptModal') -->
<i class='fa fa-pencil'></i> Custom Receipt
</a>
<% end %>
<a class='button--tiny edit' href='?preview=t'> <a class='button--tiny edit' href='?preview=t'>
<i class='fa fa-eye'></i> Preview Mode <i class='fa fa-eye'></i> Preview Mode
</a> </a>
<a class='button--tiny edit'> <% if @campaign.parent_campaign? %>
<!--= on 'click' (open_modal 'duplicateFundraiserModal') --> <a class='button--tiny edit'>
<i class='fa fa-copy'></i> Copy Campaign <!--= on 'click' (open_modal 'duplicateFundraiserModal') -->
</a> <i class='fa fa-copy'></i> Copy Campaign
</a>
<% end %>
<%= render 'components/messages/deleted_campaign_or_event', type: 'campaign' %> <%= render 'components/messages/deleted_campaign_or_event', type: 'campaign' %>

View file

@ -1,6 +1,8 @@
<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%> <%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%>
<table class='table--striped u-marginBottom--0'> <table class='table--striped u-marginBottom--0'>
<% campaigns.each do |campaign |%> <% campaigns.each do |campaign|%>
<% metric = QueryCampaignMetrics.on_donations(campaign.id) %>
<tr> <tr>
<td class='u-padding--0 u-width--200 u-hideIf--400'> <td class='u-padding--0 u-width--200 u-hideIf--400'>
<img src='<%= campaign.main_image_url(:normal) %>'> <img src='<%= campaign.main_image_url(:normal) %>'>
@ -18,11 +20,11 @@
<small><%= campaign.summary ? strip_tags(campaign.summary) : strip_tags(campaign.tagline) %></small> <small><%= campaign.summary ? strip_tags(campaign.summary) : strip_tags(campaign.tagline) %></small>
</p> </p>
<% if campaign.show_total_count %> <% if campaign.show_total_count %>
<span class='pastelBox--white u-padding--3'><%= campaign.donations.count %> <small>supporters</small></span> <span class='pastelBox--white u-padding--3'><%= metric['supporters_count'] %> <small>supporters</small></span>
<% end %> <% end %>
<% if campaign.show_total_raised %> <% if campaign.show_total_raised %>
<span class='pastelBox--white u-padding--3'> <span class='pastelBox--white u-padding--3'>
<%= print_currency campaign.total_raised, campaign.nonprofit.currency_symbol %> <small>raised</small> <%= print_currency metric['total_raised'], campaign.nonprofit.currency_symbol %> <small>raised</small>
</span> </span>
<% end %> <% end %>
</td> </td>

View file

@ -0,0 +1,77 @@
<!-- partial campaigns/new-modal -->
<!-- include 'campaigns/new/index' -->
<div class='modal' id='newPeerToPeerCampaign'>
<%= render 'common/modal_header', title: @parent_campaign.name %>
<div class='wizard-steps' style='display:none;'>
<!--= wizard.init 'new_p2p_campaign_wiz' -->
<%= render 'components/wizard/step_index', wizard_name: 'new_p2p_campaign_wiz' %>
<div class='modal-body' style='display: table;'>
<div class='wizard-step reason-step'>
<!--= wizard.set_step 'new_p2p_campaign_wiz' 'About you' -->
<form parsley-validate>
<!--= on 'submit' (def 'new_p2p_campaign' form_object) (wizard.advance 'new_p2p_campaign_wiz') -->
<input type='hidden' name='campaign[profile_id]' value='<%= current_user.profile.id %>'>
<input type='hidden' name='campaign[parent_campaign_id]' value='<%= @parent_campaign.id %>'>
<p>Personalize your campaign page with your name and photo to greatly increase the success of your campaign.</p>
<div class='layout--three'>
<fieldset>
<label>Name</label>
<input type='text' name='profile[name]' placeholder='Your Name' value='<%= @profile.name %>' required>
</fieldset>
<fieldset>
<label>City</label>
<input type='text' name='profile[city]' placeholder='City' value='<%= @profile.city %>'>
</fieldset>
<fieldset>
<label>State</label>
<%= render 'common/states_dropdown', name: 'profile[state_code]', default: @profile.state_code %>
</fieldset>
</div>
<%= render 'components/forms/submit_button', button_text: 'Next', scope: 'new_p2p_campaign_wiz', branded: true %>
</form>
</div>
<div class='wizard-step amount-step'>
<!--= wizard.set_step 'new_p2p_campaign_wiz' 'Customize' -->
<form parsley-validate>
<!--= on 'submit' create_p2p_campaign -->
<!--= log new_p2p_campaign -->
<div class='u-margin--auto'>
<fieldset class='col-right-12'>
<label>What's your reason for supporting <%= @parent_campaign.name %>?</label>
<p>Your passion is contagious! Inspire giving by telling your network why youre getting involved in this campaign.</p>
<textarea class='u-marginTop--15' rows='4' name='campaign[reason_for_supporting]' required><%= @parent_campaign.default_reason_for_supporting %></textarea>
</fieldset>
<fieldset class='group u-marginBottom--0'>
<label class='u-paddingTop--5'>Goal Amount</label>
<div class='prepend--dollar'>
<input class='input--100 u-marginBottom--5' value='1000' type='number' name='campaign[goal_amount_dollars]' required min='1'>
</div>
</fieldset>
</div>
<%= render 'components/forms/submit_button', button_text: 'Preview Campaign!', scope: 'new_p2p_campaign_wiz', branded: true %>
</form>
</div>
</div>
</div>
</div>
<!-- end partial campaigns/new-modal -->

View file

@ -4,160 +4,205 @@
<%= render 'common/modal_header', title: 'Campaign Settings' %> <%= render 'common/modal_header', title: 'Campaign Settings' %>
<form class='form--flatFields' autosubmit action='/nonprofits/<%=@nonprofit.id%>/campaigns/<%=@campaign.id%>' method='put' data-reload parsley-validate> <form class='form--flatFields' autosubmit action='/nonprofits/<%=@nonprofit.id%>/campaigns/<%=@campaign.id%>' method='put' data-reload parsley-validate>
<% if @campaign.child_campaign? %>
<div class='modal-body'>
<section class='layout--three'>
<fieldset>
<label>Goal</label>
<div class='prepend--dollar u-inlineBlock'>
<input type='number' parsley-type='number' class='input--150' name='campaign[goal_amount_dollars]' value='<%= sprintf("%.2f", @campaign.goal_amount / 100.0) %>'>
</div>
</fieldset>
</section>
</div>
<% else %>
<div class='modal-body'> <div class='modal-body'>
<section class='layout--two'> <section class='layout--two'>
<fieldset> <fieldset>
<label>Title </label> <label>Title </label>
<input type='text' class='u-marginBottom--0' name='campaign[name]' value='<%= @campaign.name %>'> <input type='text' class='u-marginBottom--0' name='campaign[name]' value='<%= @campaign.name %>'>
</fieldset> </fieldset>
<fieldset> <fieldset>
<label class='u-block'>Tagline</label> <label class='u-block'>Tagline</label>
<input type='text' name='campaign[tagline]' value='<%= @campaign.tagline %>'> <input type='text' name='campaign[tagline]' value='<%= @campaign.tagline %>'>
</fieldset> </fieldset>
</section> </section>
<hr> <hr>
<section class='layout--three'> <section class='layout--three'>
<fieldset> <fieldset>
<label>Goal</label> <label>Goal</label>
<div class='prepend--dollar u-inlineBlock'> <div class='prepend--dollar u-inlineBlock'>
<input type='number' parsley-type='number' class='input--150' name='campaign[goal_amount_dollars]' value='<%= sprintf("%.2f", @campaign.goal_amount / 100.0) %>'> <input type='number' parsley-type='number' class='input--150' name='campaign[goal_amount_dollars]' value='<%= sprintf("%.2f", @campaign.goal_amount / 100.0) %>'>
</div> </div>
</fieldset> </fieldset>
</fieldset>
<fieldset>
<label>End Date & Time</label>
<div pikaday-timepicker='MM/DD/YYYY hh:mm a'>
<input class='u-width--200 u-bold u-inlineBlock' type='text' name='campaign[end_datetime]' required parsley-trigger='change' placeholder='MM/DD/YYYY HH:MM' value='<%= Format::Date.full(@campaign.end_datetime, @nonprofit.timezone) %>'>
<a class='button edit u-inlineBlock'>Set</a>
</div>
</fieldset>
</section>
<hr>
<%= render 'components/forms/slug_field', fundraiser: @campaign, url: @nonprofit.url + '/campaigns' %>
<hr>
<section class='layout--three u-overflow--hidden'>
<fieldset>
<label>Main Image <small>(at least 500x400)</small></label>
<p><small>Used for previews and social media</small></p>
<div class='image-upload u-inlineBlock' style='background-image: url("<%= @campaign.main_image_url(:thumb) %>");'>
<span><i class='fa fa-pencil'></i> Edit</span>
<input type='file' name='campaign[main_image]'>
</div>
</fieldset>
<fieldset>
<label>Header Image <small>(1000x600)</small></label>
<p><small>Used at top of campaign page</small></p>
<% if @campaign_background_image %>
<div class='image-upload u-inlineBlock' style='background-image:url(<%= @campaign_background_image %>)'>
<!--= on 'click' (open_modal 'uploadBackgroundImage') -->
<span><i class='fa fa-pencil'></i> Edit</span>
</div>
<% else %>
<a class='button edit'>
<!--= on 'click' (open_modal 'uploadBackgroundImage') -->
<i class='fa fa-plus'></i> Add Image </i>
</a>
<% end %>
</fieldset>
<fieldset>
<label>Campaign Video </label>
<p><small>Videos are optional but recommended</small></p>
<% if @campaign.vimeo_video_id || @campaign.youtube_video_id%>
<% if @campaign.vimeo_video_id %>
<div class='image-upload u-inlineBlock'>
<!--= set_attr 'style' vimeo_image_url -->
<!--= on 'click' (open_modal 'videoModal') -->
<span> <i class='fa fa-pencil'></i> Edit</span>
</div>
<% else%>
<div class='image-upload u-inlineBlock' style="background-image:url('http://img.youtube.com/vi/<%=@campaign.youtube_video_id%>/0.jpg')">
<!--= on 'click' (open_modal 'videoModal') -->
<span> <i class='fa fa-pencil'></i> Edit</span>
</div>
<% end %>
<% else %>
<a class='button edit'><i class='fa fa-plus'></i> Add Video</a>
<!--= on 'click' (open_modal 'videoModal') -->
<% end %>
</fieldset>
</section>
<hr>
<section class='layout--two'>
<fieldset>
<input type='hidden' name='campaign[published]' value='false'>
<input id='campaign-published-input' type='checkbox' name='campaign[published]' <%= @campaign.published ? "checked='checked'" : '' %>>
<label class='u-marginBottom--10' for='campaign-published-input'>Published?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[show_total_raised]'>
<input id='show_total_raised-checkbox' type='checkbox' name='campaign[show_total_raised]' <%= @campaign.show_total_raised ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='show_total_raised-checkbox'>Show total dollars raised?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[show_total_count]'>
<input id='show_total_count-checkbox' type='checkbox' name='campaign[show_total_count]' <%= @campaign.show_total_count ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='show_total_count-checkbox'>Show total supporters?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[hide_goal]'>
<input id='hide_goal-checkbox' type='checkbox' name='campaign[hide_goal]' <%= @campaign.hide_goal ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='hide_goal-checkbox'>Hide goal amount?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[hide_thermometer]'>
<input id='hide_thermometer-checkbox' type='checkbox' name='campaign[hide_thermometer]' <%= @campaign.hide_thermometer ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='hide_thermometer-checkbox'>Hide thermometer?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[hide_activity_feed]'>
<input id='hide_activity_feed-checkbox' type='checkbox' name='campaign[hide_activity_feed]' <%= @campaign.hide_activity_feed ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='hide_activity_feed-checkbox'>Hide activity feed?</label>
</fieldset>
<% if @campaign_background_image %>
<fieldset> <fieldset>
<input type='hidden' value='false' name='campaign[hide_title]'> <label>End Date & Time</label>
<input id='hide_title-checkbox' type='checkbox' name='campaign[hide_title]' <%= @campaign.hide_title ? 'checked="checked"' : '' %>> <div pikaday-timepicker='MM/DD/YYYY hh:mm a'>
<label for='hide_title-checkbox'>Hide main title?</label> <input class='u-width--200 u-bold u-inlineBlock' type='text' name='campaign[end_datetime]' required parsley-trigger='change' placeholder='MM/DD/YYYY HH:MM' value='<%= Format::Date.full(@campaign.end_datetime, @nonprofit.timezone) %>'>
<a class='button edit u-inlineBlock'>Set</a>
</div>
</fieldset>
</section>
<hr>
<%= render 'components/forms/slug_field', fundraiser: @campaign, url: @nonprofit.url + '/campaigns' %>
<hr>
<section class='layout--three u-overflow--hidden'>
<fieldset>
<label>Main Image <small>(at least 500x400)</small></label>
<p><small>Used for previews and social media</small></p>
<div class='image-upload u-inlineBlock' style='background-image: url("<%= @campaign.main_image_url(:thumb) %>");'>
<span><i class='fa fa-pencil'></i> Edit</span>
<input type='file' name='campaign[main_image]'>
</div>
</fieldset> </fieldset>
<fieldset>
<label>Header Image <small>(1000x600)</small></label>
<p><small>Used at top of campaign page</small></p>
<% if @campaign_background_image %>
<div class='image-upload u-inlineBlock' style='background-image:url(<%= @campaign_background_image %>)'>
<!--= on 'click' (open_modal 'uploadBackgroundImage') -->
<span><i class='fa fa-pencil'></i> Edit</span>
</div>
<% else %>
<a class='button edit'>
<!--= on 'click' (open_modal 'uploadBackgroundImage') -->
<i class='fa fa-plus'></i> Add Image </i>
</a>
<% end %>
</fieldset>
<fieldset>
<label>Campaign Video </label>
<p><small>Videos are optional but recommended</small></p>
<% if @campaign.vimeo_video_id || @campaign.youtube_video_id%>
<% if @campaign.vimeo_video_id %>
<div class='image-upload u-inlineBlock'>
<!--= set_attr 'style' vimeo_image_url -->
<!--= on 'click' (open_modal 'videoModal') -->
<span> <i class='fa fa-pencil'></i> Edit</span>
</div>
<% else%>
<div class='image-upload u-inlineBlock' style="background-image:url('http://img.youtube.com/vi/<%=@campaign.youtube_video_id%>/0.jpg')">
<!--= on 'click' (open_modal 'videoModal') -->
<span> <i class='fa fa-pencil'></i> Edit</span>
</div>
<% end %>
<% else %>
<a class='button edit'><i class='fa fa-plus'></i> Add Video</a>
<!--= on 'click' (open_modal 'videoModal') -->
<% end %>
</fieldset>
</section>
<hr>
<section class='layout--one'>
<fieldset>
<label>Banner <small>(1000x120)</small></label>
<p><small>Custom image at the very top of the campaign page</small></p>
<% if @campaign.banner_image_url.present? %>
<div class='image-upload image-upload--banner u-inlineBlock u-marginBottom--15' style='background-image:url(<%= @campaign.banner_image_url %>)'>
<!--= on 'click' (open_modal 'uploadBannerImage') -->
<span><i class='fa fa-pencil'></i> Edit</span>
</div>
<% else %>
<a class='button edit u-marginBottom--15'>
<!--= on 'click' (open_modal 'uploadBannerImage') -->
<i class='fa fa-plus'></i> Add Image </i>
</a>
<% end %>
</fieldset>
</section>
<% if !@campaign.child_campaign? %>
<hr>
<section class='layout--two'>
<fieldset>
<label>Default reason for peer-to-peer campaigns</label>
<p><small>Use it to suggest a good, pre-filled reason for campaigners</small></p>
<textarea class='u-marginTop--15' rows='4' name='campaign[default_reason_for_supporting]'><%= @campaign.default_reason_for_supporting %></textarea>
</fieldset>
</section>
<% end %> <% end %>
<hr>
<section class='layout--two'>
<fieldset>
<input type='hidden' name='campaign[published]' value='false'>
<input id='campaign-published-input' type='checkbox' name='campaign[published]' <%= @campaign.published ? "checked='checked'" : '' %>>
<label class='u-marginBottom--10' for='campaign-published-input'>Published?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[show_total_raised]'>
<input id='show_total_raised-checkbox' type='checkbox' name='campaign[show_total_raised]' <%= @campaign.show_total_raised ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='show_total_raised-checkbox'>Show total dollars raised?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[show_total_count]'>
<input id='show_total_count-checkbox' type='checkbox' name='campaign[show_total_count]' <%= @campaign.show_total_count ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='show_total_count-checkbox'>Show total supporters?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[hide_goal]'>
<input id='hide_goal-checkbox' type='checkbox' name='campaign[hide_goal]' <%= @campaign.hide_goal ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='hide_goal-checkbox'>Hide goal amount?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[hide_thermometer]'>
<input id='hide_thermometer-checkbox' type='checkbox' name='campaign[hide_thermometer]' <%= @campaign.hide_thermometer ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='hide_thermometer-checkbox'>Hide thermometer?</label>
</fieldset>
<fieldset>
<input type='hidden' value='false' name='campaign[hide_activity_feed]'>
<input id='hide_activity_feed-checkbox' type='checkbox' name='campaign[hide_activity_feed]' <%= @campaign.hide_activity_feed ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='hide_activity_feed-checkbox'>Hide activity feed?</label>
</fieldset>
<% if @campaign_background_image %>
<fieldset>
<input type='hidden' value='false' name='campaign[hide_title]'>
<input id='hide_title-checkbox' type='checkbox' name='campaign[hide_title]' <%= @campaign.hide_title ? 'checked="checked"' : '' %>>
<label for='hide_title-checkbox'>Hide main title?</label>
</fieldset>
<% end %>
<fieldset>
<!--= show_if length gift_options.data -->
<input type='hidden' value='false' name='campaign[hide_custom_amounts]'>
<input id='hide_custom_amounts' type='checkbox' name='campaign[hide_custom_amounts]' <%= @campaign.hide_custom_amounts ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='hide_custom_amounts'>Disallow regular contributions during campaign?</label>
</fieldset>
</section>
<hr>
<fieldset> <fieldset>
<!--= show_if length gift_options.data --> <label>Short Description <small>(used for sharing on social media)</small></label>
<input type='hidden' value='false' name='campaign[hide_custom_amounts]'> <textarea rows='4' name='campaign[summary]'><%= @campaign.summary %></textarea>
<input id='hide_custom_amounts' type='checkbox' name='campaign[hide_custom_amounts]' <%= @campaign.hide_custom_amounts ? 'checked="checked"' : '' %>>
<label class='u-marginBottom--10' for='hide_custom_amounts'>Disallow regular contributions during campaign?</label>
</fieldset> </fieldset>
</section> <div><p class='error'></p></div>
</div>
<hr> <% end %>
<fieldset>
<label>Short Description <small>(used for sharing on social media)</small></label>
<textarea rows='4' name='campaign[summary]'><%= @campaign.summary %></textarea>
</fieldset>
<div><p class='error'></p></div>
</div>
<footer class='modal-footer u-padding--15 layout--two'> <footer class='modal-footer u-padding--15 layout--two'>
<span> <span>

View file

@ -10,15 +10,27 @@
<% content_for :javascripts do %> <% content_for :javascripts do %>
<script> <script>
<% if @npo %> <% if @nonprofit %>
appl.def('selected_result', {name: '<%= @npo.name.html_safe %>', id: <%= @npo.id %>}) appl.def('selected_result', {
app.nonprofit_id = <%= @npo.id %> name: '<%= @nonprofit.name.html_safe %>',
id: <%= @nonprofit.id %>,
modal_id: 'newCampaign'
})
app.nonprofit_id = <%= @nonprofit.id %>
<% elsif @parent_campaign %>
appl.def('selected_result', {
name: '<%= @parent_campaign.nonprofit.name.html_safe %>',
parent_campaign_id: <%= @parent_campaign.id %>,
campaign_name: '<%= @parent_campaign.name %>',
modal_id: 'newPeerToPeerCampaign'
})
app.nonprofit_id = <%= @parent_campaign.nonprofit.id %>
<% end %> <% end %>
<% if current_user %> <% if current_user %>
appl.def('is_logged_in', true) appl.def('is_logged_in', true)
appl.def('is_confirmed', <%= !current_user.confirmed_at.nil? %>) appl.def('is_confirmed', <%= !current_user.confirmed_at.nil? %>)
appl.def('user', { appl.def('user', {
name: '<%= current_user.profile.name %>', name: '<%= current_user.profile.name || current_user.email %>',
id: <%= current_user.id %> id: <%= current_user.id %>
}) })
<% end %> <% end %>
@ -26,13 +38,15 @@
<%= IncludeAsset.js '/client/js/campaigns/peer_to_peer/page.js' %> <%= IncludeAsset.js '/client/js/campaigns/peer_to_peer/page.js' %>
<% end %> <% end %>
<%= render 'layouts/top_nav' %> <% if @parent_campaign && @parent_campaign.banner_image_url %>
<div class='campaign-banner u-margin'>
<img src='<%= @parent_campaign.banner_image_url %>'>
</div>
<% end %>
<main> <main>
<header class='u-paddingX--15 container'> <header class='u-paddingX--15 container'>
<h1>Start a campaign</h1> <h1>Start my campaign</h1>
<h4>Start raising funds for your favorite nonprofit in less than 10 minutes.</h4>
</div>
</header> </header>
<hr> <hr>
@ -43,19 +57,39 @@
<i class='icon icon-bicycle u-fontSize--50'></i> <i class='icon icon-bicycle u-fontSize--50'></i>
</td> </td>
<td> <td>
<h5 class='u-paddingLeft--20'>Below is a roadmap to get you started on your campaign</h5> <h5 class='u-paddingLeft--20'>Below is a roadmap to get you started with your campaign</h5>
</td> </td>
</tr></table> </tr></table>
<ul class='u-paddingLeft--10 timeline--checklist'> <ul class='u-paddingLeft--10 timeline--checklist'>
<li> <% if @parent_campaign.present? %>
<!--= set_attr_if selected_result 'class' 'is-complete' --> <li>
<span class='annot'>10 seconds</span> <!--= set_attr_if selected_result 'class' 'is-complete' -->
<p class='strong u-marginBottom--15'>Search for a nonprofit that you want to fundraise for</p> <span class='annot'>10 seconds</span>
<div class='u-paddingLeft--20 u-maxWidth--500'> <p class='strong u-marginBottom--15'>My campaign</p>
<%= render 'search_for_npo' %> <div class='u-paddingLeft--20 u-maxWidth--500'>
</div> <h4><%= @parent_campaign.name %></h4>
</li> </div>
</li>
<% elsif @nonprofit.present?%>
<li>
<!--= set_attr_if selected_result 'class' 'is-complete' -->
<span class='annot'>10 seconds</span>
<p class='strong u-marginBottom--15'>My nonprofit</p>
<div class='u-paddingLeft--20 u-maxWidth--500'>
<h4><%= @nonprofit.name %></h4>
</div>
</li>
<% else %>
<li>
<!--= set_attr_if selected_result 'class' 'is-complete' -->
<span class='annot'>10 seconds</span>
<p class='strong u-marginBottom--15'>Search for a nonprofit that you want to fundraise for</p>
<div class='u-paddingLeft--20 u-maxWidth--500'>
<%= render 'search_for_npo' %>
</div>
</li>
<% end %>
<li> <li>
<!--= set_attr_if is_logged_in 'class' 'is-complete' --> <!--= set_attr_if is_logged_in 'class' 'is-complete' -->
@ -81,6 +115,8 @@
<p> <p>
<!--= hide_if is_confirmed --> <!--= hide_if is_confirmed -->
Simply click the confirmation link in the email we sent you when you created your <%= Settings.general.name %> account. Simply click the confirmation link in the email we sent you when you created your <%= Settings.general.name %> account.
<br/><br/>
Once you've clicked the confirmation link, come back here and <a href="javascript:window.location.reload(true)">reload this page</a>.
</p> </p>
<p class='pastelBox--green u-padding--5'> <p class='pastelBox--green u-padding--5'>
<!--= show_if is_confirmed --> <!--= show_if is_confirmed -->
@ -90,33 +126,44 @@
</li> </li>
<li> <li>
<p class='strong u-marginBottom--15'>Start your campaign</p> <% if !@child_campaign %>
<span class='annot'>5 minutes</span> <p class='strong u-marginBottom--15'>Start your campaign</p>
<div class='u-paddingLeft--20 u-maxWidth--500'> <span class='annot'>5 minutes</span>
<p>Use our simple wizard to create your campaign for <div class='u-paddingLeft--20 u-maxWidth--500'>
<strong> <p>Use our simple wizard to create your campaign for
<!--= show_if selected_result --> <strong>
<!--= put selected_result.name -->. <!--= show_if selected_result -->
</strong> <!--= put selected_result.name -->.
<strong> </strong>
<!--= hide_if selected_result --> <strong>
your favorite nonprofit. <!--= hide_if selected_result -->
</strong> your favorite nonprofit.
</p> </strong>
<p><small>If you need any further help setting up your campaign, check out <a target='_blank' href='/help/peer-to-peer-campaigns-essentials-package-feature'>this handy article</a>.</small></p> </p>
<div class='u-centered u-padding--15'> <p><small>If you need any further help setting up your campaign, check out <a target='_blank' href='/help/peer-to-peer-campaigns-essentials-package-feature'>this handy article</a>.</small></p>
<button class='button orange'> <div class='u-centered u-padding--15'>
<!--= show_if (all is_logged_in is_confirmed selected_result) --> <button class='button orange'>
<!--= on 'click' (open_modal 'newCampaign') --> <!--= show_if (all is_logged_in is_confirmed selected_result) -->
Start Campaign <!--= on 'click' (open_modal selected_result.modal_id) -->
</button> Start Campaign
</button>
</div>
</div> </div>
</div> <% else %>
<p>
Seems like you already have a campaign for that cause! <%= link_to "Click here to see it", @child_campaign.url %>.
</p>
<% end %>
</li> </li>
</ul> </ul>
</div> </div>
</main> </main>
<% if current_user %> <% if current_user %>
<%= render 'campaigns/new_modal' %> <% if @nonprofit.present? %>
<%= render 'campaigns/new_modal' %>
<% elsif @parent_campaign.present? %>
<%= render 'campaigns/new_peer_to_peer_modal' %>
<% end %>
<% end %> <% end %>

View file

@ -16,14 +16,14 @@
app.days_remaining = '<%= @campaign.days_left %>' app.days_remaining = '<%= @campaign.days_left %>'
app.recurring_fund = <%= @campaign.recurring_fund? %> app.recurring_fund = <%= @campaign.recurring_fund? %>
app.vimeo_id = "<%= @campaign.vimeo_video_id ? @campaign.vimeo_video_id : '' %>" app.vimeo_id = "<%= @campaign.vimeo_video_id ? @campaign.vimeo_video_id : '' %>"
app.current_campaign_editor = <%= current_campaign_editor? %> app.current_campaign_editor = <%= current_campaign_editor? %>
app.is_parent_campaign = <%= @campaign.parent_campaign? %>
appl.def('has_video', <%= @campaign.video_url.present? %>) appl.def('has_video', <%= @campaign.video_url.present? %>)
appl.def('campaign_is_deleted', <%= @campaign.deleted || false %>) appl.def('campaign_is_deleted', <%= @campaign.deleted || false %>)
appl.def('has_main_image', <%= @campaign.main_image.file.present? %>) appl.def('has_main_image', <%= @campaign.main_image.file.present? %>)
</script> </script>
<%= render 'schema', campaign: @campaign, url: @url %>
<%= render 'common/froala' if current_campaign_editor? %>
<%= render 'schema', campaign: @campaign, url: @url %>
<%= IncludeAsset.js '/client/js/campaigns/show/page.js' %> <%= IncludeAsset.js '/client/js/campaigns/show/page.js' %>
<% end %> <% end %>
@ -64,20 +64,20 @@
image_url: @campaign_background_image, image_url: @campaign_background_image,
is_editor: current_campaign_editor?, is_editor: current_campaign_editor?,
hide_title: @campaign.hide_title && @campaign_background_image, hide_title: @campaign.hide_title && @campaign_background_image,
header_content_partial: 'header_content' %> header_content_partial: 'header_content',
banner_image_url: @campaign.banner_image&.url %>
<%= render 'components/preview_mode_notification' %> <%= render 'components/preview_mode_notification' %>
<div class="container u-marginTop--15 <%= @brand_color ? 'is-branded' : '' %>" data-id='<%= @campaign.id %>'> <div class="container u-marginTop--15 <%= @brand_color ? 'is-branded' : '' %>" data-id='<%= @campaign.id %>'>
<section class='box'>
<section class='box'>
<%= render 'campaign_media' %> <%= render 'campaign_media' %>
</section> </section>
<section class='box-r'> <section class='box-r'>
<% if current_campaign_editor? %> <% if current_campaign_editor? && !@campaign.child_campaign? %>
<!-- Campaign editor gift option management modal --> <!-- Campaign editor gift option management modal -->
<button class='button edit u-width--full u-marginBottom--15'> <button class='button edit u-width--full u-marginBottom--15'>
<!--= on 'click' (open_modal 'manageGiftOptionsModal') --> <!--= on 'click' (open_modal 'manageGiftOptionsModal') -->
@ -85,6 +85,14 @@
</button> </button>
<% end %> <% end %>
<% if !@campaign.child_campaign? %>
<aside class='u-marginBottom--15 pastelBox--grey'>
<a class='button u-width--full' target='_blank' if-branded='background-color, dark' href='/peer-to-peer?campaign_id=<%= @peer_to_peer_campaign_param %>'>
Start Your Own Campaign for <%= @nonprofit.name %>
</a>
</aside>
<% end %>
<!-- flimflam gift options javascript gets rendered into this div: --> <!-- flimflam gift options javascript gets rendered into this div: -->
<div class='ff-sidebar'></div> <div class='ff-sidebar'></div>
@ -95,16 +103,22 @@
</div> </div>
</aside> </aside>
<% unless current_campaign_editor? %>
<aside class='u-marginTop--15'>
<a class='button u-width--full' target='_blank' if-branded='background-color, dark' href='/peer-to-peer?npo_id=<%=@nonprofit.id %>'>
Start Your Own Campaign for <%= @nonprofit.name %>
</a>
</aside>
<% end %>
</section> </section>
<% if @campaign.child_campaign? %>
<%= render 'components/fundraising_pages/campaigner_profile',
profile: @campaign.profile,
campaign_name: @campaign.name,
reason_for_supporting: @campaign.reason_for_supporting
%>
<section class='box u-centered' style="margin-top:20px; margin-bottom:20px;">
<a class='button--jumbo' target='_blank' if-branded='background-color, dark' href='/peer-to-peer?campaign_id=<%= @peer_to_peer_campaign_param %>'>
Start Your Own Campaign for <%= @nonprofit.name %>
</a>
</section>
<% end %>
<section class='box'> <section class='box'>
<div id='js-campaignBody' class='editable' data-path='/nonprofits/<%= @nonprofit.id %>/campaigns/<%= @campaign.id %>.json' data-key='campaign[body]'> <div id='js-campaignBody' class='editable' data-path='/nonprofits/<%= @nonprofit.id %>/campaigns/<%= @campaign.id %>.json' data-key='campaign[body]'>
@ -130,6 +144,10 @@
end_point: "/nonprofits/#{@nonprofit.id}/campaigns/#{@campaign.id}", end_point: "/nonprofits/#{@nonprofit.id}/campaigns/#{@campaign.id}",
image_url: @campaign_background_image, image_url: @campaign_background_image,
input_name: 'campaign[background_image]' %> input_name: 'campaign[background_image]' %>
<%= render 'components/upload_banner_image',
end_point: "/nonprofits/#{@nonprofit.id}/campaigns/#{@campaign.id}",
image_url: @campaign.banner_image_url,
input_name: 'campaign[banner_image]' %>
<%= render 'components/custom_receipt_modal', <%= render 'components/custom_receipt_modal',
title: 'Campaign Receipt Message', title: 'Campaign Receipt Message',
type: 'campaign', type: 'campaign',
@ -141,4 +159,3 @@
<%= render 'components/share_modal', name: @campaign.name, type: 'campaign' %> <%= render 'components/share_modal', name: @campaign.name, type: 'campaign' %>
<%= render 'common/email_share_modal', fundraiser: @campaign.name, fundraiser_url: @url %> <%= render 'common/email_share_modal', fundraiser: @campaign.name, fundraiser_url: @url %>

View file

@ -20,7 +20,7 @@
Upload <%= image_url ? 'Different Image' : '' %> Upload <%= image_url ? 'Different Image' : '' %>
</button> </button>
<a class='button--small red u-marginTop--10 u-width--full' <%= image_url ? '' : 'disabled' %>> <a class='button--small red u-marginTop--10 u-width--full' <%= image_url ? '' : 'disabled' %>>
<!--= on 'click' (confirm 'Are you sure?' remove_this_image --> <!--= on 'click' (confirm 'Are you sure?' remove_background_image -->
Remove Image Remove Image
</a> </a>
</div> </div>

View file

@ -0,0 +1,31 @@
<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%>
<div class='modal' id='uploadBannerImage'>
<%= render 'common/modal_header', title: 'Banner Image' %>
<div class='modal-body'>
<fieldset class='u-centered'>
<p>Select and upload a banner image for this page.</p>
<p class='u-marginBottom--20'>For best results, the image should be <strong>980px by 120px</strong> in size.</p>
<form autosubmit action='<%= end_point %>' method='put' data-reload>
<div class='image-upload image-upload--banner' style='background-image:url(<%= image_url ? image_url : '' %>)'>
<span><i class='fa fa-image'></i> Select</span>
<input type='file' name="<%= input_name %>" required>
</div>
<p class='u-color--red'>
<!--= show_if (length image_upload.error) -->
<small><!--= put image_upload.error --></small>
</p>
<div class='u-width--500 u-margin--auto'>
<button type='submit' class='button--small u-width--full u-marginTop--20' data-loading='Uploading...'>
Upload <%= image_url ? 'Different Image' : '' %>
</button>
<a class='button--small red u-marginTop--10 u-width--full' <%= image_url ? '' : 'disabled' %>>
<!--= on 'click' (confirm 'Are you sure?' remove_banner_image ) -->
Remove Image
</a>
</div>
</form>
</fieldset>
</div> <!-- modal-body -->
</div>

View file

@ -0,0 +1,5 @@
<section class='box' style="font-style: italic">
<div class="well" style="padding-left:16px;">
<h4><%= profile.name %> <small>(<%= profile.city %><% if profile.state_code && profile.city %><%= profile.city_state %> <%= profile.state_code %><% end %>)</small></h4>
<blockquote><p>"<%= reason_for_supporting %>"</p></blockquote></div>
</section>

View file

@ -6,6 +6,11 @@
<header class="container fundraisingHeader--plain" <%= css_style.html_safe %> id='js-fundraisingHeader'> <header class="container fundraisingHeader--plain" <%= css_style.html_safe %> id='js-fundraisingHeader'>
<% if banner_image_url %>
<div class='campaign-banner u-margin'>
<img src='<%= banner_image_url %>'>
</div>
<% end %>
<div class='fundraisingHeader--image-container' id='js-fundraisingHeader-image' style='display: none;'> <div class='fundraisingHeader--image-container' id='js-fundraisingHeader-image' style='display: none;'>
<img class='fundraisingHeader--image-aspectRatio' src="<%= asset_path 'graphics/10x6.jpg' %>"> <img class='fundraisingHeader--image-aspectRatio' src="<%= asset_path 'graphics/10x6.jpg' %>">
</div> </div>
@ -16,4 +21,4 @@
</section> </section>
<% end %> <% end %>
</header> </header>
<!-- partial end: fundaising_pages/_header --> <!-- partial end: fundraising_pages/_header -->

View file

@ -4,7 +4,7 @@
<section class='sideNav-section'> <section class='sideNav-section'>
<a class='sideNav-link' href='<%= profile_url(current_user.profile) %>'> <a class='sideNav-link' href='<%= profile_url(current_user.profile) %>'>
<% if current_user.profile.picture? %> <% if current_user.profile.picture? %>
<img class='sideNav-profile'src="<%= current_user.profile.get_profile_picture(:tiny) %>')"> <img class='sideNav-profile' src="<%= current_user.profile.get_profile_picture(:tiny) %>">
<% else %> <% else %>
<i class="sideNav-icon icon-user-1"></i> <i class="sideNav-icon icon-user-1"></i>
<% end %> <% end %>
@ -46,4 +46,3 @@
</section> </section>
<% end %> <% end %>

View file

@ -64,7 +64,8 @@
<%= render 'components/fundraising_pages/header', <%= render 'components/fundraising_pages/header',
image_url: @nonprofit_background_image, image_url: @nonprofit_background_image,
is_editor: current_nonprofit_user?, is_editor: current_nonprofit_user?,
header_content_partial: 'header_content' %> header_content_partial: 'header_content',
banner_image_url: nil %>
<%= render 'components/preview_mode_notification' %> <%= render 'components/preview_mode_notification' %>

View file

@ -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)
}
})
}

View file

@ -20,6 +20,8 @@ appl.def('create_campaign', function(el) {
form_data = utils.mergeFormData(form_data, appl.new_campaign) form_data = utils.mergeFormData(form_data, appl.new_campaign)
appl.def('new_campaign_wiz.loading', true) appl.def('new_campaign_wiz.loading', true)
// TODO: for p2p capmaigns, merge with preset campaing params
post_campaign(form_data) post_campaign(form_data)
.then(function(req) { .then(function(req) {
appl.notify("Redirecting to your campaign...") appl.notify("Redirecting to your campaign...")

View file

@ -1,13 +1,12 @@
// License: LGPL-3.0-or-later // License: LGPL-3.0-or-later
require('../new/peer_to_peer_wizard')
require('../../common/image_uploader')
require('../new/wizard')
var request = require("../../common/client") var request = require("../../common/client")
require('../../common/onboard')
// setting up some default values // setting up some default values
appl.def('is_signing_up', true) appl.def('is_signing_up', true)
.def('selected_result_index', -1) .def('selected_result_index', -1)
appl.def('search_nonprofits', function(value){ appl.def('search_nonprofits', function(value){
@ -23,8 +22,8 @@ appl.def('search_nonprofits', function(value){
// if the the input is empty, it clears the dropdown // if the the input is empty, it clears the dropdown
if (!value) { if (!value) {
appl.def('search_results', []) appl.def('search_results', [])
return return
} }
// logic for controlling the dropdown options with up // logic for controlling the dropdown options with up
@ -67,12 +66,12 @@ function setIndexWithArrows(dir) {
if(dir === 'down') { if(dir === 'down') {
var search_length = appl.search_results.length -1 var search_length = appl.search_results.length -1
appl.def('selected_result_index', appl.selected_result_index === search_length appl.def('selected_result_index', appl.selected_result_index === search_length
? search_length ? search_length
: appl.selected_result_index += 1) : appl.selected_result_index += 1)
} else { } else {
appl.def('selected_result_index', appl.selected_result_index === 0 appl.def('selected_result_index', appl.selected_result_index === 0
? 0 ? 0
: appl.selected_result_index -= 1) : appl.selected_result_index -= 1)
} }
} }

View file

@ -17,10 +17,12 @@ require('../../components/ajax/toggle_soft_delete')(url, 'campaign')
// Initialize the froala wysiwyg // Initialize the froala wysiwyg
var editable = require('../../common/editable') var editable = require('../../common/editable')
editable($('#js-campaignBody'), { if (app.is_parent_campaign) {
sticky: true, editable($('#js-campaignBody'), {
placeholder: "Add your campaign's story here. We strongly recommend that this section is filled out with at least 250 words. It will be saved automatically as you type. You can add images, videos and custom HTML too." sticky: true,
}) placeholder: "Add your campaign's story here. We strongly recommend that this section is filled out with at least 250 words. It will be saved automatically as you type. You can add images, videos and custom HTML too."
})
}
editable($('#js-customReceipt'), { editable($('#js-customReceipt'), {
button: ["bold", "italic", "formatBlock", "align", "createLink", button: ["bold", "italic", "formatBlock", "align", "createLink",
@ -34,8 +36,17 @@ editable($('#js-customReceipt'), {
var path = '/nonprofits/' + app.nonprofit_id + '/campaigns/' + app.campaign_id var path = '/nonprofits/' + app.nonprofit_id + '/campaigns/' + app.campaign_id
appl.def('remove_this_image', function() { appl.def('remove_banner_image', function() {
appl.remove_background_image(path, 'campaign') var url = '/nonprofits/' + app.nonprofit_id + '/campaigns/' + app.campaign_id
var notification = 'Removing banner image...'
var payload = {remove_banner_image : true}
appl.remove_image(url, 'campaign', notification, payload)
})
appl.def('remove_background_image', function(url, resource) {
var notification = 'Removing background image...'
var payload = {remove_background_image : true}
appl.remove_image(url, resource, notification, payload)
}) })
appl.def('count_story_words', function() { appl.def('count_story_words', function() {

View file

@ -59,7 +59,7 @@ const giftBox = state => gift => {
, h('p.u-marginBottom--15', gift.description) , h('p.u-marginBottom--15', gift.description)
, h('div', [ giftButton(state, gift) ]) , h('div', [ giftButton(state, gift) ])
]) ])
, app.current_campaign_editor // Show edit button only if the current user is a campaign editor , (app.current_campaign_editor && app.is_parent_campaign) // Show edit button only if the current user is a parent campaign editor
? h('button.button--tiny.absolute.edit.hasShadow', { ? h('button.button--tiny.absolute.edit.hasShadow', {
on: {click: ev => state.openEditGiftModal$(gift)} on: {click: ev => state.openEditGiftModal$(gift)}
}, [ }, [
@ -79,4 +79,3 @@ const totalContributions = gift => {
} }
module.exports = {view, init} module.exports = {view, init}

View file

@ -7,7 +7,17 @@ module.exports = function(npo_id) {
request.get(campaignsPath).end(function(err, resp){ request.get(campaignsPath).end(function(err, resp){
resp.body.unshift(false) resp.body.unshift(false)
appl.def('campaigns.data', resp.body) let campaign_id_names = resp.body.map((i) => {
if (i.isChildCampaign)
{
return {id: i.id, name: i.name + " - " + i.creator}
}
else
{
return {id: i.id, name: i.name}
}
})
appl.def('campaigns.data', campaign_id_names)
}) })
request.get(eventsPath).end(function(err, resp){ request.get(eventsPath).end(function(err, resp){
resp.body.unshift(false) resp.body.unshift(false)

View file

@ -17,10 +17,10 @@ $('.image-upload input').change(function(e) {
} }
}) })
appl.def('remove_background_image', function(url, resource) { appl.def('remove_image', function(url, resource, notification, payload) {
var data = {} var data = {}
data[resource] = {remove_background_image: true} data[resource] = payload
appl.notify('Removing background image...') appl.notify(notification)
appl.def('loading', true) appl.def('loading', true)
$.ajax({ $.ajax({
type: 'put', type: 'put',

View file

@ -63,7 +63,7 @@ Config.schema do
required(:aws).schema do required(:aws).schema do
# the region your AWS bucket is in # the region your AWS bucket is in
required(:region).filled(:str?) optional(:region).filled(:str?)
# the name of your aws bucket # the name of your aws bucket
required(:bucket).filled(:str?) required(:bucket).filled(:str?)
@ -295,4 +295,3 @@ Settings.reload!
# Initialize the rails application # Initialize the rails application
Commitchange::Application.initialize! Commitchange::Application.initialize!

View file

@ -8,4 +8,3 @@ AWS.config({
s3 = AWS::S3.new s3 = AWS::S3.new
S3Bucket = s3.buckets[Settings.aws.bucket] S3Bucket = s3.buckets[Settings.aws.bucket]

View file

@ -4,7 +4,7 @@ CarrierWave.configure do |config|
config.storage = :aws config.storage = :aws
config.aws_bucket = Settings.aws.bucket config.aws_bucket = Settings.aws.bucket
config.aws_acl = :public_read config.aws_acl = :public_read
config.asset_host = Settings.image&.host || "https://s3-#{Settings.aws.region}.amazonaws.com/#{Settings.aws.bucket}" config.asset_host = Settings.image&.host || "https://#{Settings.aws.bucket}.s3.amazonaws.com"
config.aws_authenticated_url_expiration = 60 * 60 * 24 * 365 config.aws_authenticated_url_expiration = 60 * 60 * 24 * 365
config.aws_credentials = { config.aws_credentials = {
access_key_id: Settings.aws.access_key_id, access_key_id: Settings.aws.access_key_id,

View file

@ -144,6 +144,7 @@ Commitchange::Application.routes.draw do
get(:activities, {on: :member}) get(:activities, {on: :member})
put(:soft_delete, {on: :member}) put(:soft_delete, {on: :member})
get(:name_and_id, {on: :collection}) get(:name_and_id, {on: :collection})
post :create_from_template, on: :collection
resources(:campaign_gift_options, {only: [:index, :show, :create, :update, :destroy]}) do resources(:campaign_gift_options, {only: [:index, :show, :create, :update, :destroy]}) do
put(:update_order, {on: :collection}) put(:update_order, {on: :collection})
end end

View file

@ -20,7 +20,6 @@ default:
aws: aws:
access_key_id: <%= ENV['AWS_ACCESS_KEY'] %> access_key_id: <%= ENV['AWS_ACCESS_KEY'] %>
secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
region: test
bucket: <%= ENV['S3_BUCKET_NAME'] %> bucket: <%= ENV['S3_BUCKET_NAME'] %>
mailer: mailer:

View file

@ -0,0 +1,5 @@
class AddParentCampaignIdToCampaigns < ActiveRecord::Migration
def change
add_column :campaigns, :parent_campaign_id, :integer
end
end

View file

@ -0,0 +1,5 @@
class AddReasonForSupportingToCampaigns < ActiveRecord::Migration
def change
add_column :campaigns, :reason_for_supporting, :text
end
end

View file

@ -0,0 +1,5 @@
class AddDefaultReasonForSupportingToCampaigns < ActiveRecord::Migration
def change
add_column :campaigns, :default_reason_for_supporting, :text
end
end

View file

@ -0,0 +1,5 @@
class AddBannerImageToCampaigns < ActiveRecord::Migration
def change
add_column :campaigns, :banner_image, :string
end
end

View file

@ -0,0 +1,5 @@
class AddIndexParentCampaignIdToCampaign < ActiveRecord::Migration
def change
add_index :campaigns, :parent_campaign_id
end
end

View file

@ -3,7 +3,7 @@
-- --
-- Dumped from database version 9.6.5 -- Dumped from database version 9.6.5
-- Dumped by pg_dump version 9.6.10 -- Dumped by pg_dump version 9.6.11
SET statement_timeout = 0; SET statement_timeout = 0;
SET lock_timeout = 0; SET lock_timeout = 0;
@ -392,7 +392,11 @@ CREATE TABLE public.campaigns (
hide_custom_amounts boolean, hide_custom_amounts boolean,
show_recurring_amount boolean DEFAULT false, show_recurring_amount boolean DEFAULT false,
end_datetime timestamp without time zone, end_datetime timestamp without time zone,
external_identifier character varying(255) external_identifier character varying(255),
parent_campaign_id integer,
reason_for_supporting text,
default_reason_for_supporting text,
banner_image character varying(255)
); );
@ -3143,6 +3147,13 @@ CREATE INDEX index_activities_on_supporter_id ON public.activities USING btree (
CREATE INDEX index_campaign_gifts_on_campaign_gift_option_id ON public.campaign_gifts USING btree (campaign_gift_option_id); CREATE INDEX index_campaign_gifts_on_campaign_gift_option_id ON public.campaign_gifts USING btree (campaign_gift_option_id);
--
-- Name: index_campaigns_on_parent_campaign_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_campaigns_on_parent_campaign_id ON public.campaigns USING btree (parent_campaign_id);
-- --
-- Name: index_cards_on_id_and_holder_type_and_holder_id_and_inactive; Type: INDEX; Schema: public; Owner: - -- Name: index_cards_on_id_and_holder_type_and_holder_id_and_inactive; Type: INDEX; Schema: public; Owner: -
-- --
@ -4337,6 +4348,18 @@ INSERT INTO schema_migrations (version) VALUES ('20180608205049');
INSERT INTO schema_migrations (version) VALUES ('20180608212658'); INSERT INTO schema_migrations (version) VALUES ('20180608212658');
INSERT INTO schema_migrations (version) VALUES ('20180703165400');
INSERT INTO schema_migrations (version) VALUES ('20180703165401');
INSERT INTO schema_migrations (version) VALUES ('20180703165402');
INSERT INTO schema_migrations (version) VALUES ('20180703165403');
INSERT INTO schema_migrations (version) VALUES ('20180703165404');
INSERT INTO schema_migrations (version) VALUES ('20180703165405');
INSERT INTO schema_migrations (version) VALUES ('20180713213748'); INSERT INTO schema_migrations (version) VALUES ('20180713213748');
INSERT INTO schema_migrations (version) VALUES ('20180713215825'); INSERT INTO schema_migrations (version) VALUES ('20180713215825');
@ -4347,3 +4370,5 @@ INSERT INTO schema_migrations (version) VALUES ('20181002160627');
INSERT INTO schema_migrations (version) VALUES ('20181003212559'); INSERT INTO schema_migrations (version) VALUES ('20181003212559');
INSERT INTO schema_migrations (version) VALUES ('20181120182105');

View file

@ -0,0 +1,39 @@
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
module CreatePeerToPeerCampaign
def self.create(campaign_params, profile_id)
begin
parent_campaign = Campaign.find(campaign_params[:parent_campaign_id])
rescue ActiveRecord::RecordNotFound
return { errors: { parent_campaign_id: 'not found' } }.as_json
end
p2p_params = campaign_params.except(:nonprofit_id, :summary,:goal_amount)
p2p_params.merge!(parent_campaign.child_params)
profile = Profile.find(profile_id)
base_slug = Format::Url.convert_to_slug "#{p2p_params[:name]}-#{profile.name}"
algo = SlugP2pCampaignNamingAlgorithm.new(p2p_params[:nonprofit_id])
p2p_params[:slug] = algo.create_copy_name(base_slug)
campaign = Campaign.create(p2p_params)
campaign.published = true
campaign.profile = profile
campaign.save
campaign.update_attribute(:main_image, parent_campaign.main_image) unless !parent_campaign.main_image rescue AWS::S3::Errors::NoSuchKey
campaign.update_attribute(:background_image, parent_campaign.background_image) unless !parent_campaign.background_image rescue AWS::S3::Errors::NoSuchKey
campaign.update_attribute(:banner_image, parent_campaign.banner_image) unless !parent_campaign.banner_image rescue AWS::S3::Errors::NoSuchKey
return { errors: campaign.errors.messages }.as_json 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.as_json
end
end

View file

@ -2,21 +2,31 @@
module QueryCampaignMetrics module QueryCampaignMetrics
def self.on_donations(campaign_id) def self.on_donations(campaign_id)
Qx.select( campaign = Campaign.find(campaign_id)
result = Qx.select(
"COALESCE(COUNT(DISTINCT donations.id), 0) AS supporters_count", "COALESCE(COUNT(DISTINCT donations.id), 0) AS supporters_count",
"COALESCE(SUM(payments.gross_amount), 0) AS total_raised", "COALESCE(SUM(payments.gross_amount), 0) AS total_raised"
"campaigns.goal_amount", )
"campaigns.show_total_count",
"campaigns.show_total_raised")
.from("campaigns") .from("campaigns")
.left_join( .join(
["donations", "donations.campaign_id=campaigns.id"], ["donations", "donations.campaign_id=campaigns.id"],
["payments", "payments.donation_id=donations.id"] ["payments", "payments.donation_id=donations.id"]
) )
.where("campaigns.id=$id", id: campaign_id) .where("campaigns.id IN (#{QueryCampaigns
.group_by('campaigns.id') .get_campaign_and_children(campaign_id)
.parse
})")
.execute .execute
.last .last
return {
'supporters_count' => result['supporters_count'],
'total_raised'=> result['total_raised'],
'goal_amount'=> campaign.goal_amount,
'show_total_count'=> campaign.show_total_count,
'show_total_raised'=> campaign.show_total_raised
}
end end
end end

View file

@ -50,7 +50,7 @@ module QueryCampaigns
["payments AS onetime", "onetime.id=payments.id AND onetime.kind='Donation'"], ["payments AS onetime", "onetime.id=payments.id AND onetime.kind='Donation'"],
["payments AS offsite", "offsite.id=payments.id AND offsite.kind='OffsitePayment'"], ["payments AS offsite", "offsite.id=payments.id AND offsite.kind='OffsitePayment'"],
["payments AS recurring", "recurring.id=payments.id AND recurring.kind='RecurringDonation'"]) ["payments AS recurring", "recurring.id=payments.id AND recurring.kind='RecurringDonation'"])
.where("donations.campaign_id" => campaign_id) .where("donations.campaign_id IN (#{QueryCampaigns.get_campaign_and_children(campaign_id).parse})")
end end
@ -62,15 +62,25 @@ module QueryCampaigns
def self.name_and_id(npo_id) def self.name_and_id(npo_id)
Psql.execute(
Qexpr.new.select( np = Nonprofit.find(npo_id)
"campaigns.name", campaigns = np.campaigns.not_deleted.includes(:profile).order('campaigns.name ASC')
"campaigns.id") output = campaigns.map do |i|
.from("campaigns") {
.where("campaigns.nonprofit_id=$id", id: npo_id) 'name' => i.name,
.where("campaigns.deleted='f' OR campaigns.deleted IS NULL") 'id' => i.id,
.order_by("campaigns.name ASC") 'isChildCampaign' => i.child_campaign?,
) 'creator' => i.profile&.name || "user ##{i.profile.id}"
}
end
output
end
def self.get_campaign_and_children(campaign_id)
Qx.select("id")
.from('campaigns')
.where("campaigns.id = $id OR campaigns.parent_campaign_id=$id",
id: campaign_id)
end end

View file

@ -19,7 +19,9 @@ module QueryDonations
.join(:supporters, "supporters.id=donations.supporter_id") .join(:supporters, "supporters.id=donations.supporter_id")
.left_outer_join(:campaign_gifts, "campaign_gifts.donation_id=donations.id") .left_outer_join(:campaign_gifts, "campaign_gifts.donation_id=donations.id")
.left_outer_join(:campaign_gift_options, "campaign_gift_options.id=campaign_gifts.campaign_gift_option_id") .left_outer_join(:campaign_gift_options, "campaign_gift_options.id=campaign_gifts.campaign_gift_option_id")
.where("donations.campaign_id=$id", id: campaign_id) .where("donations.campaign_id IN (#{QueryCampaigns
.get_campaign_and_children(campaign_id)
.parse})")
.group_by("donations.id", "supporters.id") .group_by("donations.id", "supporters.id")
.order_by("donations.date") .order_by("donations.date")
) )
@ -27,7 +29,9 @@ module QueryDonations
def self.for_campaign_activities(id) def self.for_campaign_activities(id)
QueryDonations.activities_expression(['donations.recurring']) QueryDonations.activities_expression(['donations.recurring'])
.where('donations.campaign_id=$id', id: id) .where("donations.campaign_id IN (#{QueryCampaigns
.get_campaign_and_children(id)
.parse})")
.execute .execute
end end

View file

@ -140,7 +140,8 @@ module QueryPayments
inner_donation_search = inner_donation_search.where('donations.event_id=$id', id: query[:event_id]) inner_donation_search = inner_donation_search.where('donations.event_id=$id', id: query[:event_id])
end end
if (query[:campaign_id].present?) if (query[:campaign_id].present?)
inner_donation_search = inner_donation_search.where('donations.campaign_id=$id', id: query[:campaign_id]) campaign_search = campaign_and_child_query_as_raw_string
inner_donation_search = inner_donation_search.where("donations.campaign_id IN (#{campaign_search})", id: query[:campaign_id])
end end
expr = Qexpr.new.select('payments.id').from('payments') expr = Qexpr.new.select('payments.id').from('payments')
.left_outer_join('supporters', "supporters.id=payments.supporter_id") .left_outer_join('supporters', "supporters.id=payments.supporter_id")
@ -191,9 +192,10 @@ module QueryPayments
expr = expr.where('payments.kind IN ($kinds)', kinds: query[:donation_type].split(',')) expr = expr.where('payments.kind IN ($kinds)', kinds: query[:donation_type].split(','))
end end
if query[:campaign_id].present? if query[:campaign_id].present?
campaign_search = campaign_and_child_query_as_raw_string
expr = expr expr = expr
.left_outer_join("campaigns", "campaigns.id=donations.campaign_id" ) .left_outer_join("campaigns", "campaigns.id=donations.campaign_id" )
.where("campaigns.id=$id", id: query[:campaign_id]) .where("campaigns.id IN (#{campaign_search})", id: query[:campaign_id])
end end
if query[:event_id].present? if query[:event_id].present?
tickets_subquery = Qexpr.new.select("payment_id", "MAX(event_id) AS event_id").from("tickets").where('tickets.event_id=$event_id', event_id: query[:event_id]).group_by("payment_id").as("tix") tickets_subquery = Qexpr.new.select("payment_id", "MAX(event_id) AS event_id").from("tickets").where('tickets.event_id=$event_id', event_id: query[:event_id]).group_by("payment_id").as("tix")
@ -224,7 +226,8 @@ module QueryPayments
inner_donation_search = inner_donation_search.where('donations.event_id=$id', id: query[:event_id]) inner_donation_search = inner_donation_search.where('donations.event_id=$id', id: query[:event_id])
end end
if (query[:campaign_id].present?) if (query[:campaign_id].present?)
inner_donation_search = inner_donation_search.where('donations.campaign_id=$id', id: query[:campaign_id]) campaign_search = campaign_and_child_query_as_raw_string
inner_donation_search = inner_donation_search.where("donations.campaign_id IN (#{campaign_search})", id: query[:campaign_id])
end end
expr = Qexpr.new.select('payments.id').from('payments') expr = Qexpr.new.select('payments.id').from('payments')
.left_outer_join('supporters', "supporters.id=payments.supporter_id") .left_outer_join('supporters', "supporters.id=payments.supporter_id")
@ -278,9 +281,10 @@ module QueryPayments
expr = expr.where('payments.kind IN ($kinds)', kinds: query[:donation_type].split(',')) expr = expr.where('payments.kind IN ($kinds)', kinds: query[:donation_type].split(','))
end end
if query[:campaign_id].present? if query[:campaign_id].present?
campaign_search = campaign_and_child_query_as_raw_string
expr = expr expr = expr
.left_outer_join("campaigns", "campaigns.id=donations.campaign_id" ) .left_outer_join("campaigns", "campaigns.id=donations.campaign_id" )
.where("campaigns.id=$id", id: query[:campaign_id]) .where("campaigns.id IN (#{campaign_search})", id: query[:campaign_id])
end end
if query[:event_id].present? if query[:event_id].present?
tickets_subquery = Qexpr.new.select("payment_id", "MAX(event_id) AS event_id").from("tickets").where('tickets.event_id=$event_id', event_id: query[:event_id]).group_by("payment_id").as("tix") tickets_subquery = Qexpr.new.select("payment_id", "MAX(event_id) AS event_id").from("tickets").where('tickets.event_id=$event_id', event_id: query[:event_id]).group_by("payment_id").as("tix")
@ -446,4 +450,8 @@ module QueryPayments
} }
end end
def self.campaign_and_child_query_as_raw_string
"SELECT c_temp.id from campaigns c_temp where c_temp.id=$id OR c_temp.parent_campaign_id=$id"
end
end end

View file

@ -14,7 +14,9 @@ module QuerySupporters
.left_outer_join('campaign_gifts', 'donations.id=campaign_gifts.donation_id') .left_outer_join('campaign_gifts', 'donations.id=campaign_gifts.donation_id')
.left_outer_join('campaign_gift_options', 'campaign_gifts.campaign_gift_option_id=campaign_gift_options.id') .left_outer_join('campaign_gift_options', 'campaign_gifts.campaign_gift_option_id=campaign_gift_options.id')
.where("supporters.nonprofit_id=$id", id: np_id) .where("supporters.nonprofit_id=$id", id: np_id)
.where("donations.campaign_id=$id", id: campaign_id) .where("donations.campaign_id IN (#{QueryCampaigns
.get_campaign_and_children(campaign_id)
.parse})")
.group_by('supporters.id') .group_by('supporters.id')
.order_by('MAX(donations.date) DESC') .order_by('MAX(donations.date) DESC')
@ -253,7 +255,9 @@ module QuerySupporters
expr = expr.and_where("tags.ids @> ARRAY[$tag_ids]", tag_ids: tag_ids) expr = expr.and_where("tags.ids @> ARRAY[$tag_ids]", tag_ids: tag_ids)
end end
if query[:campaign_id].present? if query[:campaign_id].present?
expr = expr.add_join("donations", "donations.supporter_id=supporters.id AND donations.campaign_id=#{query[:campaign_id].to_i}") expr = expr.add_join("donations", "donations.supporter_id=supporters.id AND donations.campaign_id IN (#{QueryCampaigns
.get_campaign_and_children(query[:campaign_id].to_i)
.parse})")
end end
if query[:event_id].present? if query[:event_id].present?

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -0,0 +1,78 @@
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
require 'rails_helper'
describe CreatePeerToPeerCampaign do
describe '.create' do
let!(:profile) { force_create(:profile, :user => force_create(:user)) }
let!(:parent_campaign) { force_create(:campaign, name: 'Parent campaign') }
context 'on success' do
it 'returns a hash' do
campaign_params = { name: 'Child campaign', parent_campaign_id: parent_campaign.id, goal_amount_dollars: "1000" }
Timecop.freeze(2020, 4, 5) do
result = CreatePeerToPeerCampaign.create(campaign_params, profile.id)
expect(result).to be_kind_of Hash
end
end
it 'returns created peer-to-peer campaign' do
campaign_params = { name: 'Child campaign', parent_campaign_id: parent_campaign.id, goal_amount_dollars: "1000" }
Timecop.freeze(2020, 4, 5) do
result = CreatePeerToPeerCampaign.create(campaign_params, profile.id)
expect(result).not_to include 'errors'
expect(result['parent_campaign_id']).to eq parent_campaign.id
expect(result['created_at']).to eq 'Sun, 05 Apr 2020 00:00:00 UTC +00:00'
end
end
it 'assigns proper slug' do
campaign_params = { name: 'Child campaign', parent_campaign_id: parent_campaign.id, goal_amount_dollars: "1000" }
Timecop.freeze(2020, 4, 5) do
result = CreatePeerToPeerCampaign.create(campaign_params, profile.id)
expect(result).not_to include 'errors'
expect(result['slug']).to eq 'child-campaign_000'
end
end
it 'saves campaign' do
campaign_params = { name: 'Child campaign', parent_campaign_id: parent_campaign.id, goal_amount_dollars: "1000" }
Timecop.freeze(2020, 4, 5) do
expect { CreatePeerToPeerCampaign.create(campaign_params, profile.id) }.to change(Campaign, :count).by 1
end
end
end
context 'on failure' do
it "returns an error if parent campaign can't be found" do
campaign_params = {}
Timecop.freeze(2020, 4, 5) do
result = CreatePeerToPeerCampaign.create(campaign_params, profile.id)
expect(result).to be_kind_of Hash
expect(result['errors']['parent_campaign_id']).to eq 'not found'
end
end
it 'returns a list of error messages for attribute validation' do
campaign_params = { parent_campaign_id: parent_campaign.id }
Timecop.freeze(2020, 4, 5) do
result = CreatePeerToPeerCampaign.create(campaign_params, profile.id)
expect(result).to be_kind_of Hash
expect(result).to include 'errors'
expect(result['errors']['goal_amount']).to match ["can't be blank", 'is not a number']
end
end
it "doesn't save campaign" do
campaign_params = {}
Timecop.freeze(2020, 4, 5) do
expect { CreatePeerToPeerCampaign.create(campaign_params, profile.id) }.not_to change(Campaign, :count)
end
end
end
end
end

View file

@ -114,7 +114,8 @@ describe InsertDuplicate do
expect(result.attributes.with_indifferent_access).to eq(common_result_attributes.merge( expect(result.attributes.with_indifferent_access).to eq(common_result_attributes.merge(
{ {
id: result.id, id: result.id,
end_datetime: nil end_datetime: nil,
banner_image: nil
}).with_indifferent_access) }).with_indifferent_access)
validate_cgo(result) validate_cgo(result)
end end

View file

@ -0,0 +1,81 @@
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
require 'rails_helper'
describe QueryCampaignMetrics do
describe 'calculates your metrics plus children' do
let(:nonprofit) {force_create(:nonprofit)}
let(:campaign) {force_create(:campaign, nonprofit:nonprofit, show_total_count:false, show_total_raised: false, goal_amount: 16000)}
let(:campaign_child) {force_create(:campaign, nonprofit:nonprofit, parent_campaign:campaign, show_total_count:true, show_total_raised: true, goal_amount: 8000)}
let(:campaign_child_2) {force_create(:campaign, nonprofit:nonprofit, parent_campaign:campaign, show_total_count:true, show_total_raised: true, goal_amount: 4000 )}
let(:donation) { force_create(:donation, campaign: campaign, amount: 1000)}
let(:payment) { force_create(:payment, donation: donation, gross_amount:1000)}
let(:donation2) { force_create(:donation, campaign: campaign, amount: 2000)}
let(:payment2) { force_create(:payment, donation: donation2, gross_amount:2000)}
let(:donation3) { force_create(:donation, campaign: campaign_child, amount: 4000)}
let(:payment3) { force_create(:payment, donation: donation3, gross_amount:4000)}
let(:donation4) { force_create(:donation, campaign: campaign_child_2, amount: 8000)}
let(:payment4) { force_create(:payment, donation: donation4, gross_amount:8000)}
let(:payments) do
payment
payment2
payment3
payment4
end
let (:campaign_metric) do
payments
QueryCampaignMetrics.on_donations(campaign.id)
end
let(:campaign_child_metric) do
payments
QueryCampaignMetrics.on_donations(campaign_child.id)
end
let(:campaign_child_2_metric) do
payments
QueryCampaignMetrics.on_donations(campaign_child_2.id)
end
it 'campaign metric is valid' do
expect(campaign_metric['supporters_count']).to eq 4
expect(campaign_metric['total_raised']).to eq 15000
expect(campaign_metric['goal_amount']).to eq 16000
expect(campaign_metric['show_total_count']).to eq false
expect(campaign_metric['show_total_raised']).to eq false
end
it 'campaign child metric is valid' do
expect(campaign_child_metric['supporters_count']).to eq 1
expect(campaign_child_metric['total_raised']).to eq 4000
expect(campaign_child_metric['goal_amount']).to eq 8000
expect(campaign_child_metric['show_total_count']).to eq true
expect(campaign_child_metric['show_total_raised']).to eq true
end
it 'campaign child 2metric is valid' do
expect(campaign_child_2_metric['supporters_count']).to eq 1
expect(campaign_child_2_metric['total_raised']).to eq 8000
expect(campaign_child_2_metric['goal_amount']).to eq 4000
expect(campaign_child_2_metric['show_total_count']).to eq true
expect(campaign_child_2_metric['show_total_raised']).to eq true
end
end
end

View file

@ -46,20 +46,8 @@ describe SlugP2pCampaignNamingAlgorithm do
expect(algo.create_copy_name(@name2)).to eq short_slug_copy_today_plus_1 expect(algo.create_copy_name(@name2)).to eq short_slug_copy_today_plus_1
end end
it 'errors when at max copies' do it 'has 999 as the max_copies' do
@name = short_slug expect(algo.max_copies).to eq 999
@copy_base = copy_base
campaign
campaigns_at_max_copies
expect{ algo.create_copy_name(@name) }.to(raise_error{|error|
expect(error).to be_a ArgumentError
})
campaigns_at_max_copies.each {|i|
expect {algo.create_copy_name(i.slug)}.to(raise_error{|error|
expect(error).to be_a ArgumentError
})
}
end end
end end
end end