Merge branch 'master' into docker-improvement-latest

This commit is contained in:
Eric Schultz 2018-11-30 20:47:50 -06:00 committed by GitHub
commit f8874f49f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 1625 additions and 1103 deletions

3
.gitignore vendored
View file

@ -7,6 +7,7 @@ client/js/nonprofits/donate/plugins-enabled
.DS_Store
/postgres-data
# Ignore bundler config
/.bundle
@ -62,4 +63,4 @@ javascripts/api
!public/css/donate-button.css
!public/css/donate-button.v2.css
!public/svgs
!public/svgs/*
!public/svgs/*

View file

@ -27,7 +27,7 @@ gem 'hamster'
gem 'aws-ses'
gem 'aws-sdk'
# for blocking ip addressses
gem 'rack-attack'

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 'pikaday';
@import 'common/image_uploader';
@import '../common';
body {
padding: 0;
background: $fog;
}
main {
display: block;
padding: 60px 0 50px 0;

View file

@ -13,6 +13,7 @@
@import 'components/draggable';
@import 'gift_levels';
@import '../../nonprofits/donation_form/form';
@import '../common';
.button--gift {
font-size: 17px;
@ -103,6 +104,43 @@
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) {
.box,

View file

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

View file

@ -36,23 +36,34 @@ class CampaignsController < ApplicationController
@nonprofit = current_nonprofit
@url = Format::Url.concat(root_url, @campaign.url)
@campaign_background_image = FetchBackgroundImage.with_model(@campaign)
respond_to do |format|
format.html
if @campaign.child_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)
end
def activities
render json: QueryDonations.for_campaign_activities(params[:id])
@campaign = current_campaign
render json: QueryDonations.for_campaign_activities(@campaign.id)
end
def create
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]
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
def update
@ -63,7 +74,6 @@ class CampaignsController < ApplicationController
json_saved current_campaign, 'Successfully updated!'
end
# post 'nonprofits/:np_id/campaigns/:campaign_id/duplicate'
def duplicate
@ -98,7 +108,22 @@ class CampaignsController < ApplicationController
def peer_to_peer
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
private
@ -108,5 +133,4 @@ class CampaignsController < ApplicationController
raise ActionController::RoutingError.new('Not Found')
end
end
end

View file

@ -13,6 +13,8 @@ class Campaign < ActiveRecord::Base
:remove_main_image, # for carrierwave
:background_image,
:remove_background_image, #bool carrierwave
:banner_image,
:remove_banner_image,
:published,
:video_url, #str
:vimeo_video_id,
@ -30,7 +32,10 @@ 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,
:default_reason_for_supporting
validate :end_datetime_cannot_be_in_past, :on => :create
validates :profile, :presence => true
@ -47,6 +52,7 @@ class Campaign < ActiveRecord::Base
mount_uploader :main_image, CampaignMainImageUploader
mount_uploader :background_image, CampaignBackgroundImageUploader
mount_uploader :banner_image, CampaignBannerImageUploader
has_many :donations
has_many :charges, through: :donations
@ -61,6 +67,9 @@ class Campaign < ActiveRecord::Base
belongs_to :profile
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 :active, -> {where(:published => true).where("end_datetime IS NULL OR end_datetime >= ?", Date.today)}
scope :past, -> {where(:published => true).where("end_datetime < ?", Date.today)}
@ -155,4 +164,24 @@ class Campaign < ActiveRecord::Base
(self.end_datetime.to_date - Date.today).to_i
end
def finished?
self.end_datetime && self.end_datetime < Time.now
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

View file

@ -1,3 +1,4 @@
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
class EmailList < ActiveRecord::Base
attr_accessible :list_name, :mailchimp_list_id, :nonprofit, :tag_master
belongs_to :nonprofit

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>
<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'>
<!--= on 'click' (open_modal 'manageGiftOptionsModal') -->
<i class='fa fa-gift'></i> Gift Options
<!--= on 'click' (open_modal 'campaignSettings') -->
<i class='fa fa-gear'></i> Settings
</a>
<a class='button--tiny edit'>
<!--= on 'click' (open_modal 'customReceiptModal') -->
<i class='fa fa-pencil'></i> Custom Receipt
</a>
<% if @campaign.parent_campaign? %>
<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'>
<!--= 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'>
<i class='fa fa-eye'></i> Preview Mode
</a>
<a class='button--tiny edit'>
<!--= on 'click' (open_modal 'duplicateFundraiserModal') -->
<i class='fa fa-copy'></i> Copy Campaign
</a>
<% if @campaign.parent_campaign? %>
<a class='button--tiny edit'>
<!--= on 'click' (open_modal 'duplicateFundraiserModal') -->
<i class='fa fa-copy'></i> Copy Campaign
</a>
<% end %>
<%= render 'components/messages/deleted_campaign_or_event', type: 'campaign' %>

View file

@ -1,7 +0,0 @@
<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%>
<% selector ||= nil %>
<div class='progressBar--medium-fill' if-branded='background-color, light'>
<!--= style 'width' (cat metrics.percentage_funded '%') -->
</div>

View file

@ -1,6 +1,8 @@
<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%>
<table class='table--striped u-marginBottom--0'>
<% campaigns.each do |campaign |%>
<% campaigns.each do |campaign|%>
<% metric = QueryCampaignMetrics.on_donations(campaign.id) %>
<tr>
<td class='u-padding--0 u-width--200 u-hideIf--400'>
<img src='<%= campaign.main_image_url(:normal) %>'>
@ -18,11 +20,11 @@
<small><%= campaign.summary ? strip_tags(campaign.summary) : strip_tags(campaign.tagline) %></small>
</p>
<% 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 %>
<% if campaign.show_total_raised %>
<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>
<% end %>
</td>

View file

@ -1,39 +0,0 @@
<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%>
<!-- partial start: campaigns/fixed_top_actions -->
<div class='fixedTopAction fixedTopAction--campaign' <%= current_campaign_editor? ? "style='padding-left: 60px'".html_safe : "" %>>
<div class='container'>
<table class='fixedTopAction-table'>
<tr>
<td class='fixedTopAction-left'>
<h1 class='fixedTopAction-title'><%= @campaign.name %></h1><br>
<span class='fixedTopAction-metrics'>
<a if-branded='color, darker' href='<%= @nonprofit.url %>'> <%= @nonprofit.name %></a>
<span class='u-capitalize'><%= Format::Address.city_and_state(@nonprofit.city, @nonprofit.state_code) %></span>
<span>
<!--= show_if campaign_time_remaining -->
<!--= put campaign_time_remaining --> left
</span>
</span>
</td>
<td class='fixedTopAction-right'>
<% if !@campaign.hide_thermometer %>
<div class='fixedTopAction-hideWhenMobile progressBar--medium'>
<%= render 'campaign_progress_bar_inner', selector: 'u-width--full' %>
</div>
<% end %>
<a class='u-marginRight--10 fixedTopAction-promote' title='Promote' if-branded='color, darker'>
<!--= on 'click' (open_modal 'shareModal') -->
Promote</a>
<button class='button' id='js-contribute' title='Contribute' if-branded='background, dark'>
Contribute
</button>
</td>
</tr>
</table>
</div>
</div>
<!-- partial end: campaigns/fixed_top_actions -->

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' %>
<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'>
<section class='layout--two'>
<fieldset>
<label>Title </label>
<input type='text' class='u-marginBottom--0' name='campaign[name]' value='<%= @campaign.name %>'>
</fieldset>
<section class='layout--two'>
<fieldset>
<label>Title </label>
<input type='text' class='u-marginBottom--0' name='campaign[name]' value='<%= @campaign.name %>'>
</fieldset>
<fieldset>
<label class='u-block'>Tagline</label>
<input type='text' name='campaign[tagline]' value='<%= @campaign.tagline %>'>
</fieldset>
</section>
<fieldset>
<label class='u-block'>Tagline</label>
<input type='text' name='campaign[tagline]' value='<%= @campaign.tagline %>'>
</fieldset>
</section>
<hr>
<hr>
<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>
</fieldset>
<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>
<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>
<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>
<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--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 %>
<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>
<!--= 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>
<label>Short Description <small>(used for sharing on social media)</small></label>
<textarea rows='4' name='campaign[summary]'><%= @campaign.summary %></textarea>
</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'>
<span>

View file

@ -10,15 +10,27 @@
<% content_for :javascripts do %>
<script>
<% if @npo %>
appl.def('selected_result', {name: '<%= @npo.name.html_safe %>', id: <%= @npo.id %>})
app.nonprofit_id = <%= @npo.id %>
<% if @nonprofit %>
appl.def('selected_result', {
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 %>
<% if current_user %>
appl.def('is_logged_in', true)
appl.def('is_confirmed', <%= !current_user.confirmed_at.nil? %>)
appl.def('user', {
name: '<%= current_user.profile.name %>',
name: '<%= current_user.profile.name || current_user.email %>',
id: <%= current_user.id %>
})
<% end %>
@ -26,13 +38,15 @@
<%= IncludeAsset.js '/client/js/campaigns/peer_to_peer/page.js' %>
<% 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>
<header class='u-paddingX--15 container'>
<h1>Start a campaign</h1>
<h4>Start raising funds for your favorite nonprofit in less than 10 minutes.</h4>
</div>
<h1>Start my campaign</h1>
</header>
<hr>
@ -43,19 +57,39 @@
<i class='icon icon-bicycle u-fontSize--50'></i>
</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>
</tr></table>
<ul class='u-paddingLeft--10 timeline--checklist'>
<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>
<% if @parent_campaign.present? %>
<li>
<!--= set_attr_if selected_result 'class' 'is-complete' -->
<span class='annot'>10 seconds</span>
<p class='strong u-marginBottom--15'>My campaign</p>
<div class='u-paddingLeft--20 u-maxWidth--500'>
<h4><%= @parent_campaign.name %></h4>
</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>
<!--= set_attr_if is_logged_in 'class' 'is-complete' -->
@ -81,6 +115,8 @@
<p>
<!--= hide_if is_confirmed -->
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 class='pastelBox--green u-padding--5'>
<!--= show_if is_confirmed -->
@ -90,33 +126,44 @@
</li>
<li>
<p class='strong u-marginBottom--15'>Start your campaign</p>
<span class='annot'>5 minutes</span>
<div class='u-paddingLeft--20 u-maxWidth--500'>
<p>Use our simple wizard to create your campaign for
<strong>
<!--= show_if selected_result -->
<!--= put selected_result.name -->.
</strong>
<strong>
<!--= hide_if selected_result -->
your favorite nonprofit.
</strong>
</p>
<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>
<div class='u-centered u-padding--15'>
<button class='button orange'>
<!--= show_if (all is_logged_in is_confirmed selected_result) -->
<!--= on 'click' (open_modal 'newCampaign') -->
Start Campaign
</button>
<% if !@child_campaign %>
<p class='strong u-marginBottom--15'>Start your campaign</p>
<span class='annot'>5 minutes</span>
<div class='u-paddingLeft--20 u-maxWidth--500'>
<p>Use our simple wizard to create your campaign for
<strong>
<!--= show_if selected_result -->
<!--= put selected_result.name -->.
</strong>
<strong>
<!--= hide_if selected_result -->
your favorite nonprofit.
</strong>
</p>
<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>
<div class='u-centered u-padding--15'>
<button class='button orange'>
<!--= show_if (all is_logged_in is_confirmed selected_result) -->
<!--= on 'click' (open_modal selected_result.modal_id) -->
Start Campaign
</button>
</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>
</ul>
</div>
</main>
<% 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 %>

View file

@ -16,14 +16,15 @@
app.days_remaining = '<%= @campaign.days_left %>'
app.recurring_fund = <%= @campaign.recurring_fund? %>
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('campaign_is_deleted', <%= @campaign.deleted || false %>)
appl.def('has_main_image', <%= @campaign.main_image.file.present? %>)
</script>
<%= render 'schema', campaign: @campaign, url: @url %>
<%= render 'common/froala' if current_campaign_editor? %>
appl.def('has_main_image', <%= @campaign.main_image.file.present? %>)
</script>
<%= render 'schema', campaign: @campaign, url: @url %>
<%= render 'common/froala' if current_campaign_editor? %>
<%= IncludeAsset.js '/client/js/campaigns/show/page.js' %>
<% end %>
@ -64,27 +65,35 @@
image_url: @campaign_background_image,
is_editor: current_campaign_editor?,
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' %>
<div class="container u-marginTop--15 <%= @brand_color ? 'is-branded' : '' %>" data-id='<%= @campaign.id %>'>
<section class='box'>
<section class='box'>
<%= render 'campaign_media' %>
</section>
<section class='box-r'>
<% if current_campaign_editor? %>
<% if current_campaign_editor? && !@campaign.child_campaign? %>
<!-- Campaign editor gift option management modal -->
<button class='button edit u-width--full u-marginBottom--15'>
<!--= on 'click' (open_modal 'manageGiftOptionsModal') -->
<i class='fa fa-gift'></i> Manage Gift Options
</button>
<% 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='<%= @campaign.finished? ? "/peer-to-peer?npo_id=#{@nonprofit.id}" : "/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: -->
<div class='ff-sidebar'></div>
@ -95,16 +104,22 @@
</div>
</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>
<% 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'>
<div id='js-campaignBody' class='editable' data-path='/nonprofits/<%= @nonprofit.id %>/campaigns/<%= @campaign.id %>.json' data-key='campaign[body]'>
@ -130,6 +145,10 @@
end_point: "/nonprofits/#{@nonprofit.id}/campaigns/#{@campaign.id}",
image_url: @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',
title: 'Campaign Receipt Message',
type: 'campaign',
@ -141,4 +160,3 @@
<%= render 'components/share_modal', name: @campaign.name, type: 'campaign' %>
<%= render 'common/email_share_modal', fundraiser: @campaign.name, fundraiser_url: @url %>

View file

@ -1,32 +0,0 @@
<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%>
<!-- partial start: components/_guest_footer -->
<section class='group container globalFooter-guest'>
<ul class='col-4 u-noClear u-margin--0 u-paddingRight--20'>
<li class='globalFooter-title'>Get Started</li>
<li><a open-modal='loginModal'>Login</a></li>
<li><a data-ff-open-onboard>NPO Signup</a></li>
<li><a href='/peer-to-peer'>Peer to Peer Campaign</a></li>
</ul>
<ul class='col-4 u-noClear u-margin--0 u-paddingRight--20'>
</ul>
<ul class='col-4 u-noClear u-margin--0'>
<% if Settings.terms_and_privacy&.help_url %>
<li><%= link_to("Help", Settings.terms_and_privacy.help_url) %></li>
<% end %>
</ul>
<%# <div class='globalFooter-mailingListCol'> %>
<%# <a class='button--small blue u-floatR' href='/demo'> %>
<%# Free Demo %>
<%# </a> %>
<%# </div> %>
</section>
<!-- partial end: components/_guest_footer -->

View file

@ -20,7 +20,7 @@
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_this_image -->
<!--= on 'click' (confirm 'Are you sure?' remove_background_image -->
Remove Image
</a>
</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'>
<% 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;'>
<img class='fundraisingHeader--image-aspectRatio' src="<%= asset_path 'graphics/10x6.jpg' %>">
</div>
@ -16,4 +21,4 @@
</section>
<% end %>
</header>
<!-- partial end: fundaising_pages/_header -->
<!-- partial end: fundraising_pages/_header -->

View file

@ -49,7 +49,8 @@
image_url: @event_background_image,
is_editor: current_event_editor?,
hide_title: @event.hide_title,
header_content_partial: 'header_content' %>
header_content_partial: 'header_content',
banner_image_url: nil %>
<%= render 'components/preview_mode_notification' %>

View file

@ -34,7 +34,6 @@
<% if yield(:footer_hidden) != 'hidden' %>
<footer class='globalFooter'>
<%= render 'components/guest_footer' unless current_user %>
<%= render 'components/footer_sub' %>
</footer>
<% end %>

View file

@ -25,10 +25,6 @@
<% unless current_role?([:nonprofit_admin,:nonprofit_associate]) %>
<section class='sideNav-section'>
<a class='sideNav-link' href='/explore'>
<i class='sideNav-icon icon-binocular'></i>
<span class='sideNav-text'>Explore</span>
</a>
<% if Settings.terms_and_privacy&.help_url %>
<a class='sideNav-link' href="<%=Settings.terms_and_privacy.help_url %>">
<i class='sideNav-icon icon-bubble-ask-2'></i>

View file

@ -4,7 +4,7 @@
<section class='sideNav-section'>
<a class='sideNav-link' href='<%= profile_url(current_user.profile) %>'>
<% 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 %>
<i class="sideNav-icon icon-user-1"></i>
<% end %>
@ -46,4 +46,3 @@
</section>
<% end %>

View file

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

View file

@ -0,0 +1,43 @@
// License: LGPL-3.0-or-later
//This is used for federated p2p campaigns
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_p2p_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_p2p_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)
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...")

View file

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

View file

@ -17,10 +17,12 @@ require('../../components/ajax/toggle_soft_delete')(url, 'campaign')
// Initialize the froala wysiwyg
var editable = require('../../common/editable')
editable($('#js-campaignBody'), {
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."
})
if (app.is_parent_campaign) {
editable($('#js-campaignBody'), {
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'), {
button: ["bold", "italic", "formatBlock", "align", "createLink",
@ -34,8 +36,17 @@ editable($('#js-customReceipt'), {
var path = '/nonprofits/' + app.nonprofit_id + '/campaigns/' + app.campaign_id
appl.def('remove_this_image', function() {
appl.remove_background_image(path, 'campaign')
appl.def('remove_banner_image', function() {
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() {

View file

@ -30,7 +30,7 @@ function init(giftsNeedRefresh$, parentState) {
state.giftOptions$ = flyd.mergeAll([
pageloadGifts$
, refreshedGifts$
, flyd.stream([]) // default before ajax loads
, flyd.stream([]) // default before ajax loads
])
return state
}
@ -59,7 +59,7 @@ const giftBox = state => gift => {
, h('p.u-marginBottom--15', gift.description)
, 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', {
on: {click: ev => state.openEditGiftModal$(gift)}
}, [
@ -79,4 +79,3 @@ const totalContributions = gift => {
}
module.exports = {view, init}

View file

@ -167,6 +167,3 @@ const patch = snabbdom.init([
render({state: init(), view, patch, container: document.querySelector('.ff-sidebar')})
// Init the metrics
appl.def('metrics.path_prefix', '/nonprofits/' + app.nonprofit_id + '/campaigns/' + app.campaign_id + '/')
appl.ajax_metrics.index()

View file

@ -7,7 +7,17 @@ module.exports = function(npo_id) {
request.get(campaignsPath).end(function(err, resp){
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){
resp.body.unshift(false)

View file

@ -107,7 +107,8 @@ var froala = function($el, options) {
refresh: function () {
}
},
}
},
videoAllowedAttrs: ["src","width","height","frameborder","allowfullscreen","webkitallowfullscreen","mozallowfullscreen","href","target","id","controls","value","name", "autoplay", "loop", "muted"]
})
$('.froala-popup').parents('.froala-editor').css('z-index', 99999)

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 = {}
data[resource] = {remove_background_image: true}
appl.notify('Removing background image...')
data[resource] = payload
appl.notify(notification)
appl.def('loading', true)
$.ajax({
type: 'put',

View file

@ -1,24 +0,0 @@
// License: LGPL-3.0-or-later
const R = require('ramda')
const h = require('flimflam/h')
const flyd = require('flimflam/flyd')
const wizard = require('flimflam/ui/wizard')
const carousel = require('./carousel')
// the api for this components is the same as the regular ff wizard.
// it wraps the wizard content in a carousel component which adds a horizontally
// scrolling animation whenever the wizard index changes.
// it also makes the heights of each wizard step the same.
const content = (state, content) => {
const count = content.length
const index = state.isCompleted$() ? (state.currentStep$() + 1) : state.currentStep$()
return carousel({count, index, content})
}
const labels = (state, steps) => {
const truncatedSteps = R.map(x => h('span.inline-block.truncate', x), steps)
return wizard.labels(state, truncatedSteps)
}
module.exports = {init: wizard.init, labels, content}

View file

@ -1,42 +0,0 @@
// License: LGPL-3.0-or-later
const R = require('ramda')
const h = require('flimflam/h')
const setWidth = (elm, number) =>
elm.style.width = number * elm.parentElement.offsetWidth + 'px'
const setItemsDimensions = (items, percent) => {
const heights = R.map(x => x.offsetHeight, items)
const tallest = R.reduce((a, b) => a >= b ? a : b , 0, heights)
R.map(x => {
x.style.width = percent + '%'
x.style.height = tallest + 'px'
}, items)
}
const init = (count, percent) => vnode => {
const elm = vnode.elm
const items = elm.childNodes
setItemsDimensions(items, percent)
setWidth(elm, count)
window.addEventListener('resize', () => {
setItemsDimensions(items, percent)
setWidth(elm, count)
})
}
module.exports = obj => {
const percent = 100 / obj.count
return h('div', [
h('div.overflow-hidden', [
h('div.transition-slow.clearfix'
, {
hook: {insert: init(obj.count, percent)}
, style: {transform: `translateZ(0) translateX(-${percent * obj.index}%)`}
}
, R.map(x => h('div.left.p-2.table', [h('div.middle-cell', [x])]), obj.content)
)
])
])
}

View file

@ -14,7 +14,7 @@ function view(state) {
h('a.button--small.facebook.u-width--full.share-button', {
props: {
target: '_blank'
, href: 'https://www.facebook.com/dialog/feed?app_id='+app.facebook_app_id +"&display=popup&caption=" + (app.campaign.name || app.nonprofit.name) + "&link="+window.location.href
, href: 'https://www.facebook.com/dialog/feed?app_id='+app.facebook_app_id +"&display=popup&caption=" + encodeURIComponent(app.campaign.name || app.nonprofit.name) + "&link="+window.location.href
}
}, [h('i.fa.fa-facebook-square'), ` ${I18n.t('nonprofits.donate.followup.share.facebook')}`] )
])

View file

@ -99,10 +99,10 @@ const init = params$ => {
(ev, params) => {
if(!parent) return
if(params.redirect) parent.postMessage(`commitchange:redirect:${params.redirect}`, '*')
else if(params.mode !== 'embedded'){
else if(params.mode !== 'embedded'){
parent.postMessage('commitchange:close', '*');
} else {
if (window.parent) {window.parent.postMessage('commitchange:close', '*');};
if (window.parent) {window.parent.postMessage('commitchange:close', '*');};
}
}
, state.clickFinish$, state.params$ )

View file

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

View file

@ -52,7 +52,6 @@ Commitchange::Application.configure do
config.log_level = :debug
config.threadsafe!
config.dependency_loading = true if $rails_rake_task
# Turn this on if you want to mess with code inside /node_modules
# config.browserify_rails.evaluate_node_modules = true

View file

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

View file

@ -4,7 +4,7 @@ CarrierWave.configure do |config|
config.storage = :aws
config.aws_bucket = Settings.aws.bucket
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_credentials = {
access_key_id: Settings.aws.access_key_id,

View file

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

View file

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

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 by pg_dump version 9.6.10
-- Dumped by pg_dump version 9.6.11
SET statement_timeout = 0;
SET lock_timeout = 0;
@ -392,7 +392,11 @@ CREATE TABLE public.campaigns (
hide_custom_amounts boolean,
show_recurring_amount boolean DEFAULT false,
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);
--
-- 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: -
--
@ -4337,6 +4348,18 @@ INSERT INTO schema_migrations (version) VALUES ('20180608205049');
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 ('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 ('20181120182105');

View file

@ -11,8 +11,8 @@ export interface FundraiserInfo {
name: string
}
function LoadReactPage(element:HTMLElement, events: FundraiserInfo[],
campaigns: FundraiserInfo[],
function LoadReactPage(element:HTMLElement, campaigns: FundraiserInfo[],
events: FundraiserInfo[],
nonprofitId: number,
supporterId:number,
preupdateDonationAction:() => void,

View file

@ -172,7 +172,6 @@ class CreateNewOffsitePaymentPane extends React.Component<CreateOffsitePaymentPa
params.date.validators = [Validations.isDate('MM/DD/YYYY')]
params.gross_amount.validators = [Validations.isGreaterThanOrEqualTo(0.01)];
// params.fee_total.validators = [Validations.optional(Validations.isLessThanOrEqualTo(0))];
@ -205,6 +204,7 @@ class CreateNewOffsitePaymentPane extends React.Component<CreateOffsitePaymentPa
{/* <CurrencyField field={this.form.$('fee_total')} label={"Processing Fees"} mustBeNegative={true}/> */}
<BasicField field={this.form.$('date')} label={"Date"} />
<BasicField field={this.form.$('check_number')} label={"Check or Payment Number/ID"}/>
<SelectField field={this.form.$('campaign')}
label={"Campaign"}
options={this.props.campaigns}/>

View file

@ -371,7 +371,7 @@ class EditPaymentPane extends React.Component<EditPaymentPaneProps & InjectedInt
</table>;
let checkNumber = this.props.data.offsite_payment && this.props.data.offsite_payment.check_number ?
let checkNumber = this.props.data.offsite_payment ?
<BasicField field={this.form.$('check_number')} label={"Check or Payment Number/ID"}/> : false;
let offsitePayment = this.props.data.kind === "OffsitePayment" ? (<div>

View file

@ -36,14 +36,14 @@ module CreateCampaignGift
if ((donation.recurring_donation != nil) && (campaign_gift_option.amount_recurring != nil && campaign_gift_option.amount_recurring > 0))
# it's a recurring_donation. Is it enough? for the gift level?
unless donation.recurring_donation.amount >= (campaign_gift_option.amount_recurring)
unless donation.recurring_donation.amount == (campaign_gift_option.amount_recurring)
AdminMailer.delay.notify_failed_gift(donation, campaign_gift_option)
raise ParamValidation::ValidationError.new("#{params[:campaign_gift_option_id]} gift options requires a recurring donation of at least #{campaign_gift_option.amount_recurring} for donation #{donation.id}", {:key => :campaign_gift_option_id})
raise ParamValidation::ValidationError.new("#{params[:campaign_gift_option_id]} gift options requires a recurring donation of #{campaign_gift_option.amount_recurring} for donation #{donation.id}", {:key => :campaign_gift_option_id})
end
else
unless donation.amount >= (campaign_gift_option.amount_one_time)
unless donation.amount == (campaign_gift_option.amount_one_time)
AdminMailer.delay.notify_failed_gift(donation, campaign_gift_option)
raise ParamValidation::ValidationError.new("#{params[:campaign_gift_option_id]} gift options requires a donation of at least #{campaign_gift_option.amount_one_time} for donation #{donation.id}", {:key => :campaign_gift_option_id})
raise ParamValidation::ValidationError.new("#{params[:campaign_gift_option_id]} gift options requires a donation of #{campaign_gift_option.amount_one_time} for donation #{donation.id}", {:key => :campaign_gift_option_id})
end
end
@ -65,11 +65,11 @@ module CreateCampaignGift
campaign_gift_option = cg.campaign_gift_option
if ((donation.recurring_donation != nil) && (campaign_gift_option.amount_recurring != nil && campaign_gift_option.amount_recurring > 0))
# it's a recurring_donation. Is it enough? for the gift level?
unless donation.recurring_donation.amount >= (campaign_gift_option.amount_recurring)
unless donation.recurring_donation.amount == (campaign_gift_option.amount_recurring)
raise ParamValidation::ValidationError.new("#{campaign_gift_option.id} gift options requires a recurring donation of at least #{campaign_gift_option.amount_recurring}", {:key => :campaign_gift_option_id})
end
else
unless donation.amount >= (campaign_gift_option.amount_one_time)
unless donation.amount == (campaign_gift_option.amount_one_time)
raise ParamValidation::ValidationError.new("#{campaign_gift_option.id} gift options requires a donation of at least #{campaign_gift_option.amount_one_time}", {:key => :campaign_gift_option_id})
end
end

View file

@ -0,0 +1,33 @@
# 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?
campaign.as_json
end
end

View file

@ -2,21 +2,31 @@
module QueryCampaignMetrics
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(SUM(payments.gross_amount), 0) AS total_raised",
"campaigns.goal_amount",
"campaigns.show_total_count",
"campaigns.show_total_raised")
"COALESCE(SUM(payments.gross_amount), 0) AS total_raised"
)
.from("campaigns")
.left_join(
.join(
["donations", "donations.campaign_id=campaigns.id"],
["payments", "payments.donation_id=donations.id"]
)
.where("campaigns.id=$id", id: campaign_id)
.group_by('campaigns.id')
.where("campaigns.id IN (#{QueryCampaigns
.get_campaign_and_children(campaign_id)
.parse
})")
.execute
.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

View file

@ -50,7 +50,7 @@ module QueryCampaigns
["payments AS onetime", "onetime.id=payments.id AND onetime.kind='Donation'"],
["payments AS offsite", "offsite.id=payments.id AND offsite.kind='OffsitePayment'"],
["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
@ -62,15 +62,25 @@ module QueryCampaigns
def self.name_and_id(npo_id)
Psql.execute(
Qexpr.new.select(
"campaigns.name",
"campaigns.id")
.from("campaigns")
.where("campaigns.nonprofit_id=$id", id: npo_id)
.where("campaigns.deleted='f' OR campaigns.deleted IS NULL")
.order_by("campaigns.name ASC")
)
np = Nonprofit.find(npo_id)
campaigns = np.campaigns.not_deleted.includes(:profile).order('campaigns.name ASC')
output = campaigns.map do |i|
{
'name' => i.name,
'id' => i.id,
'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

View file

@ -19,7 +19,9 @@ module QueryDonations
.join(:supporters, "supporters.id=donations.supporter_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")
.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")
.order_by("donations.date")
)
@ -27,7 +29,9 @@ module QueryDonations
def self.for_campaign_activities(id)
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
end

View file

@ -140,7 +140,8 @@ module QueryPayments
inner_donation_search = inner_donation_search.where('donations.event_id=$id', id: query[:event_id])
end
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
expr = Qexpr.new.select('payments.id').from('payments')
.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(','))
end
if query[:campaign_id].present?
campaign_search = campaign_and_child_query_as_raw_string
expr = expr
.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
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")
@ -224,7 +226,8 @@ module QueryPayments
inner_donation_search = inner_donation_search.where('donations.event_id=$id', id: query[:event_id])
end
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
expr = Qexpr.new.select('payments.id').from('payments')
.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(','))
end
if query[:campaign_id].present?
campaign_search = campaign_and_child_query_as_raw_string
expr = expr
.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
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")
@ -446,4 +450,8 @@ module QueryPayments
}
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

View file

@ -14,7 +14,9 @@ module QuerySupporters
.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')
.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')
.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)
end
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
if query[:event_id].present?

1209
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -61,8 +61,8 @@
"less": "^3.0.4",
"less-loader": "^4.1.0",
"lodash": "^4.17.5",
"node-sass": "^4.9.0",
"phantomjs-prebuilt": "2.1.12",
"node-sass": "^4.10.0",
"phantomjs-prebuilt": "^2.1.16",
"postcss-cssnext": "^2.9.0",
"postcss-import": "^9.1.0",
"postcss-loader": "^2.1.1",

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

@ -44,6 +44,8 @@ describe InsertDuplicate do
let(:common_result_attributes) {
{
nonprofit_id: nonprofit.id,
parent_campaign_id: nil,
reason_for_supporting: nil,
profile_id: profile.id,
:background_image => nil,
:body => nil,
@ -71,6 +73,8 @@ describe InsertDuplicate do
:video_url => nil,
:vimeo_video_id => nil,
:youtube_video_id => nil,
banner_image: nil,
default_reason_for_supporting: nil,
name: copy_name,
slug: copy_slug,
@ -114,7 +118,8 @@ describe InsertDuplicate do
expect(result.attributes.with_indifferent_access).to eq(common_result_attributes.merge(
{
id: result.id,
end_datetime: nil
end_datetime: nil,
banner_image: nil
}).with_indifferent_access)
validate_cgo(result)
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
end
it 'errors when at max copies' do
@name = short_slug
@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
})
}
it 'has 999 as the max_copies' do
expect(algo.max_copies).to eq 999
end
end
end