Refactoring of proposal workflow. Fixes #215

This commit is contained in:
Stella Rouzi 2015-04-16 18:34:41 +03:00
parent 333c5e39bf
commit e6bb168c5e
35 changed files with 491 additions and 192 deletions

View file

@ -45,7 +45,7 @@ gem 'sass-rails', '>= 4.0.2'
gem 'uglifier', '>= 1.3.0'
# as the front-end framework
gem 'bootstrap-sass'
gem 'bootstrap-sass', '~> 3.3.4.1'
gem 'autoprefixer-rails'
gem 'formtastic-bootstrap'
gem 'formtastic', '~> 2.3.0.rc3'

View file

@ -56,8 +56,9 @@ GEM
uuidtools
arel (5.0.1.20140414130214)
ast (2.0.0)
autoprefixer-rails (4.0.1)
autoprefixer-rails (5.1.9)
execjs
json
awesome_nested_set (3.0.0.rc.5)
activerecord (>= 4.0.0, < 5)
axlsx (2.0.1)
@ -68,8 +69,9 @@ GEM
axlsx (>= 2.0.1)
rails (>= 3.1)
bcrypt (3.1.7)
bootstrap-sass (3.3.1.0)
sass (~> 3.2)
bootstrap-sass (3.3.4.1)
autoprefixer-rails (>= 5.0.0.1)
sass (>= 3.2.19)
bootstrap-switch-rails (3.0.2)
bootstrap3-datetimepicker-rails (3.0.3)
momentjs-rails (>= 2.8.1)
@ -138,7 +140,7 @@ GEM
diff-lcs (1.2.5)
docile (1.1.3)
erubis (2.7.0)
execjs (2.0.2)
execjs (2.5.2)
factory_girl (4.4.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.4.1)
@ -440,7 +442,7 @@ DEPENDENCIES
autoprefixer-rails
awesome_nested_set (~> 3.0.0.rc.5)
axlsx_rails
bootstrap-sass
bootstrap-sass (~> 3.3.4.1)
bootstrap-switch-rails (~> 3.0.0)
bootstrap3-datetimepicker-rails (~> 3.0.2)
byebug

View file

@ -36,6 +36,7 @@
//= require osem-tickets
//= require bootstrap-switch
//= require osem-switch
//= require osem-bootstrap
$(document).ready(function() {
$('a[disabled=disabled]').click(function(event){

View file

@ -0,0 +1,24 @@
$(function() {
// add a hash to the URL when the user clicks on a tab
$('a[data-toggle="tab"]').on('click', function(e) {
history.pushState(null, null, $(this).attr('href'));
});
// navigate to a tab when the history changes
window.addEventListener("popstate", function(e) {
var activeTab = $('a[href="' + location.hash + '"]');
if (activeTab.length) {
activeTab.tab('show');
} else {
$('.nav-tabs a').first().tab('show');
}
});
});
$(function() {
var hash = window.location.hash;
hash && $('ul.nav a[href="' + hash + '"]').tab('show');
});
$(function () {
$('[data-toggle="popover"]').popover({html: true})
});

View file

@ -27,11 +27,15 @@ $(function () {
$("#" + id).toggle();
});
});
$("#commercial_commercial_type").change(function () {
$(".media-type").hide();
$('#' + $(this).val().toLowerCase() + '-help').show();
$(".select-help-toggle").change(function () {
var id = $(this).attr('id');
$('.' + id).collapse('hide');
$('#' + $(this).val() + '-help.' + id).collapse('show');
});
$('.dropdown-toggle').dropdown();
/**
* Adds the default template as value to the regarding email textarea field.
*/

View file

@ -15,7 +15,7 @@ class CommercialsController < ApplicationController
authorize! :create, @commercial
if @commercial.save
redirect_to edit_conference_proposal_path(conference_id: @conference.short_title, id: @event.id),
redirect_to edit_conference_proposal_path(conference_id: @conference.short_title, id: @event.id, anchor: 'commercials-content'),
notice: 'Commercial was successfully created.'
else
flash[:error] = "An error prohibited this Commercial from being saved: #{@commercial.errors.full_messages.join('. ')}."
@ -25,7 +25,7 @@ class CommercialsController < ApplicationController
def update
if @commercial.update(commercial_params)
redirect_to edit_conference_proposal_path(conference_id: @conference.short_title, id: @event.id),
redirect_to edit_conference_proposal_path(conference_id: @conference.short_title, id: @event.id, anchor: 'commercials-content'),
notice: 'Commercial was successfully updated.'
else
flash[:error] = "An error prohibited this Commercial from being saved: #{@commercial.errors.full_messages.join('. ')}."

View file

@ -1,5 +1,5 @@
class ProposalController < ApplicationController
before_filter :authenticate_user!, except: [:show]
before_filter :authenticate_user!, except: [:show, :new, :create]
load_resource :conference, find_by: :short_title
load_and_authorize_resource :event, parent: false, through: :conference
@ -13,6 +13,7 @@ class ProposalController < ApplicationController
end
def new
@user = User.new
@url = conference_proposal_index_path(@conference.short_title)
end
@ -24,35 +25,37 @@ class ProposalController < ApplicationController
def create
@url = conference_proposal_index_path(@conference.short_title)
unless current_user
@user = User.new(params[:user])
if @user.save
sign_in(@user)
else
flash[:error] = "Could not save user: #{@user.errors.full_messages.join(', ')}"
render action: 'new'
return
end
end
params[:event].delete :user
@event = Event.new(params[:event])
@event.conference = @conference
# First, update the submitter's info, if they've changed anything
current_user.assign_attributes(params[:user])
if current_user.changed?
current_user.save
end
@event.event_users.new(user: current_user,
event_role: 'submitter')
@event.event_users.new(user: current_user,
event_role: 'speaker')
if !@event.save
unless @event.save
flash[:error] = "Could not submit proposal: #{@event.errors.full_messages.join(', ')}"
render action: 'new'
return
end
ahoy.track 'Event submission', title: 'New submission'
if @conference.user_registered?(current_user)
redirect_to(conference_proposal_index_path(@conference.short_title),
notice: 'Event was successfully submitted.')
else
redirect_to(new_conference_conference_registrations_path(conference_id: @conference.short_title),
alert: 'Event was successfully submitted. You should register for the conference now.')
end
flash[:notice] = 'Proposal was successfully submitted.'
redirect_to conference_proposal_index_path(@conference.short_title)
end
def update

View file

@ -1,4 +1,34 @@
module ApplicationHelper
def pluralize_without_count(count, noun, text = nil)
if count != 0
count == 1 ? "#{noun}#{text}" : "#{noun.pluralize}#{text}"
end
end
def event_status_icon(event)
case event.state
when 'new'
'fa-eye'
when 'unconfirmed'
'fa-check text-muted'
when 'confirmed'
'fa-check text-success'
when 'rejected', 'withdrawn', 'canceled'
'fa-ban'
end
end
def event_progress_color(progress)
progress = progress.to_i
case progress
when 100 then 'progress-bar-success'
when 86 then 'progress-bar-info'
when 71 then 'progress-bar-warning'
when 14, 29, 43, 57 then 'progress-bar-danger'
end
end
def target_progress_color(progress)
progress = progress.to_i
result =
@ -67,9 +97,9 @@ module ApplicationHelper
def class_for_todo(bool)
if bool
return 'list-group-item todolist-ok'
return 'todolist-ok'
else
return 'list-group-item todolist-missing'
return 'todolist-missing'
end
end

View file

@ -52,6 +52,10 @@ class Ability
can [:show, :create], Registration do |registration|
registration.new_record?
end
can [:show, :create], Event do |event|
event.new_record?
end
end
# Abilities for signed in users

View file

@ -3,7 +3,8 @@ class Commercial < ActiveRecord::Base
attr_accessible :commercial_id, :commercial_type
validates :commercial_id, :commercial_type, presence: true
validates :commercial_id, presence: true
validates :commercial_type, presence: true
validates :commercial_type, inclusion: { in: CONFIG['commercial_types'].values }, allow_blank: true
validates :commercial_type, inclusion: { in: CONFIG['commercial_types'].values }
end

View file

@ -911,10 +911,10 @@ class Conference < ActiveRecord::Base
# Creates default EventTypes for this Conference. Used as before_create.
#
def create_event_types
event_types << EventType.create(title: 'Talk', length: 30, color: '#FF0000',
event_types << EventType.create(title: 'Talk', length: 30, color: '#FF0000', description: 'Presentation in lecture format',
minimum_abstract_length: 0,
maximum_abstract_length: 500)
event_types << EventType.create(title: 'Workshop', length: 60, color: '#0000FF',
event_types << EventType.create(title: 'Workshop', length: 60, color: '#0000FF', description: 'Interactive hands-on practice',
minimum_abstract_length: 0,
maximum_abstract_length: 500)
true

View file

@ -26,17 +26,18 @@ class Event < ActiveRecord::Base
accepts_nested_attributes_for :event_users, allow_destroy: true
accepts_nested_attributes_for :users
before_create :generate_guid
validate :abstract_limit
validate :before_end_of_conference, on: :create
validate :name_and_biography_exists
validates :title, presence: true
validates :abstract, presence: true
validates :event_type, presence: true
validates :conference, presence: true
scope :confirmed, -> { where(state: 'confirmed') }
scope :highlighted, -> { where(is_highlight: true) }
state_machine initial: :new do
state :new
@ -212,6 +213,32 @@ class Event < ActiveRecord::Base
result.to_a.to_sentence
end
##
#
# Returns +Hash+
def progress_status
{
registered: self.conference.user_registered?(self.submitter),
commercials: self.commercials.any?,
biography: !self.submitter.biography.blank?,
subtitle: !self.subtitle.blank?,
difficulty_level: !self.difficulty_level.blank?,
title: true,
abstract: true
}.with_indifferent_access
end
##
# Returns the progress of the proposal's set up
#
# ====Returns
# * +Fixnum+ -> Progress in Percent
def calculate_progress
result = self.progress_status
true_items = result.select { |_key, value| value }.length
(true_items / result.length.to_f * 100).round(0).to_s
end
private
def abstract_limit
@ -225,11 +252,6 @@ class Event < ActiveRecord::Base
errors.add(:abstract, "cannot have more than #{max_words} words") if len > max_words
end
def name_and_biography_exists
errors.add(:biography, "cant' be blank") if submitter.biography.blank?
errors.add(:name, " can't be blank") if submitter.name.blank?
end
# TODO: create a module to be mixed into model to perform same operation
# venue.rb has same functionality which can be shared
# TODO: rename guid to UUID as guid is specifically Microsoft term

View file

@ -1,6 +1,6 @@
class EventType < ActiveRecord::Base
attr_accessible :title, :length, :minimum_abstract_length, :maximum_abstract_length, :color,
:conference_id
:conference_id, :description
belongs_to :conference
has_many :events, dependent: :restrict_with_error

View file

@ -8,13 +8,8 @@
.row
.col-md-8
= semantic_form_for(@commercial, :url => (@commercial.new_record? ? admin_conference_commercials_path : admin_conference_commercial_path(@conference.short_title, @commercial))) do |f|
= f.input :commercial_type, as: :select, label: 'Conference Promo Media Type', class: 'form-control', collection: CONFIG['commercial_types'].values, include_blank: false, hint: 'This media-item will be used to represent this conference at various places in OSEM.'
= f.input :commercial_type, as: :select, label: 'Conference Promo Media Type', input_html: { class: 'select-help-toggle' }, collection: CONFIG['commercial_types'].values, include_blank: false, hint: 'This media-item will be used to represent this conference at various places in OSEM.'
= f.input :commercial_id, label: 'Conference Promo Media ID', as: :string
%p{ class: 'help-block media-type', id: 'youtube-help' } Go to your YouTube video, click on "share" and copy everything behind http://youtu.be/"
%p{ class: 'help-block media-type', id: 'slideshare-help', style: 'display:none' } Go to your SlideShare, click on "share" -> "embed" and copy the id
%p{ class: 'help-block media-type', id: 'flickr-help', style: 'display:none' } Go to your flickr image, click on "Grab the link" -> "Show short url" and copy everything behind https://flic.kr/p/
%p{ class: 'help-block media-type', id: 'vimeo-help', style: 'display:none' } Go to your vimeo video, click on "share" and copy everything behind http://vimeo.com/
%p{ class: 'help-block media-type', id: 'speakerdeck-help', style: 'display:none' } Go to your SpeakerDeck, click on "share" -> "embed" and copy the data-id
%p{ class: 'help-block media-type', id: 'instagram-help', style: 'display:none' } Go to your Instagram photo page and copy everything behind instagram.com/p/ (without trailing /# hash symbol)
= render partial: 'commercials/commercial_help'
%p.text-right
= f.action :submit, as: :button, button_html: { class: 'btn btn-primary' }

View file

@ -6,7 +6,7 @@
.progress-bar{ 'role'=>'progressbar', 'aria-valuenow'=>"#{conference_progress['process']}", 'aria-valuemin'=>'0',
'aria-valuemax'=>'100', 'style'=>"width: #{conference_progress['process']}%;" }
= conference_progress['process'] + '%'
%li{'class'=>class_for_todo(conference_progress['registration'])}
%li{'class'=>"list-group-item #{class_for_todo(conference_progress['registration'])}"}
%span{'class'=>icon_for_todo(conference_progress['registration'])}
- if can? :update, @conference
- if conference.registration_period
@ -15,43 +15,43 @@
= link_to 'Set up registration period', new_admin_conference_registration_period_path(conference_progress['short_title'])
- else
Set up registration period
%li{'class'=>class_for_todo(conference_progress['cfp'])}
%li{'class'=>"list-group-item #{class_for_todo(conference_progress['cfp'])}"}
%span{'class'=>icon_for_todo(conference_progress['cfp'])}
- if can? :update, CallForPaper.new(conference_id: @conference.id)
= link_to 'Set up call for papers', admin_conference_call_for_paper_path(conference_progress['short_title'])
- else
Set up call for papers
%li{'class'=>class_for_todo(conference_progress['venue'])}
%li{'class'=>"list-group-item #{class_for_todo(conference_progress['venue'])}"}
%span{'class'=>icon_for_todo(conference_progress['venue'])}
- if can? :update, @conference.venue
= link_to 'Add venue', edit_admin_conference_venue_path(conference_progress['short_title'])
- else
Add venue
%li{'class'=>class_for_todo(conference_progress['rooms'])}
%li{'class'=>"list-group-item #{class_for_todo(conference_progress['rooms'])}"}
%span{'class'=>icon_for_todo(conference_progress['rooms'])}
- if can? :update, @conference.rooms.build
= link_to 'Add rooms', admin_conference_rooms_path(conference_progress['short_title'])
- else
Add rooms
%li{'class'=>class_for_todo(conference_progress['tracks'])}
%li{'class'=>"list-group-item #{class_for_todo(conference_progress['tracks'])}"}
%span{'class'=>icon_for_todo(conference_progress['tracks'])}
- if can? :update, @conference.tracks.build
= link_to 'Add tracks', admin_conference_tracks_path(conference_progress['short_title'])
- else
Add tracks
%li{'class'=>class_for_todo(conference_progress['event_types'])}
%li{'class'=>"list-group-item #{class_for_todo(conference_progress['event_types'])}"}
%span{'class'=>icon_for_todo(conference_progress['event_types'])}
- if can? :update, @conference.event_types.build
= link_to 'Add event types', admin_conference_event_types_path(conference_progress['short_title'])
- else
Add event types
%li{'class'=>class_for_todo(conference_progress['difficulty_levels'])}
%li{'class'=>"list-group-item #{class_for_todo(conference_progress['difficulty_levels'])}"}
%span{'class'=>icon_for_todo(conference_progress['difficulty_levels'])}
- if can? :update, @conference.difficulty_levels.build
= link_to 'Add difficulty levels', admin_conference_difficulty_levels_path(conference_progress['short_title'])
- else
Add difficulty levels
%li{class: class_for_todo(conference_progress['splashpage'])}
%li{class: "list-group-item #{class_for_todo(conference_progress['splashpage'])}"}
%span{'class'=>icon_for_todo(conference_progress['splashpage'])}
- if can? :update, @conference
= link_to 'Set up a Splashpage', admin_conference_splashpage_path(conference_progress['short_title'])

View file

@ -11,8 +11,9 @@
= semantic_form_for(@event_type, :url => (@event_type.new_record? ? admin_conference_event_types_path : admin_conference_event_type_path(@conference.short_title, @event_type))) do |f|
= f.input :title
= f.input :length, :input_html => {:size => 3}
= f.input :description
= f.input :minimum_abstract_length, :input_html => {:size => 3}
= f.input :maximum_abstract_length, :input_html => {:size => 3}
= f.input :color, :input_html => { :size => 6, :type => 'color' }
%p.text-right
= f.action :submit, as: :button, button_html: { class: 'btn btn-primary' }
= f.action :submit, as: :button, button_html: { class: 'btn btn-primary' }

View file

@ -0,0 +1,6 @@
%p{ class: 'help-block commercial_commercial_type collapse in', id: 'YouTube-help' } Go to your YouTube video, click on "share" and copy everything behind http://youtu.be/"
%p{ class: 'help-block commercial_commercial_type collapse', id: 'SlideShare-help' } Go to your SlideShare, click on "share" -> "embed" and copy the id
%p{ class: 'help-block commercial_commercial_type collapse', id: 'Flickr-help' } Go to your flickr image, click on "Grab the link" -> "Show short url" and copy everything behind https://flic.kr/p/
%p{ class: 'help-block commercial_commercial_type collapse', id: 'Vimeo-help' } Go to your vimeo video, click on "share" and copy everything behind http://vimeo.com/
%p{ class: 'help-block commercial_commercial_type collapse', id: 'Speakerdeck-help' } Go to your SpeakerDeck, click on "share" -> "embed" and copy the data-id
%p{ class: 'help-block commercial_commercial_type collapse', id: 'Instagram-help' } Go to your Instagram photo page and copy everything behind instagram.com/p/ (without trailing /# hash symbol)

View file

@ -1,9 +1,4 @@
= f.input :commercial_type, as: :select, label: 'Conference Promo Media Type', class: 'form-control', collection: CONFIG['commercial_types'].values, include_blank: false, hint: 'This media-item will be used to represent this conference at various places in OSEM.'
= f.input :commercial_id, label: 'Conference Promo Media ID', as: :string
%p{ class: 'help-block media-type', id: 'youtube-help' } Go to your YouTube video, click on "share" and copy everything behind http://youtu.be/"
%p{ class: 'help-block media-type', id: 'slideshare-help', style: 'display:none' } Go to your SlideShare, click on "share" -> "embed" and copy the id
%p{ class: 'help-block media-type', id: 'flickr-help', style: 'display:none' } Go to your flickr image, click on "Grab the link" -> "Show short url" and copy everything behind https://flic.kr/p/
%p{ class: 'help-block media-type', id: 'vimeo-help', style: 'display:none' } Go to your vimeo video, click on "share" and copy everything behind http://vimeo.com/
%p{ class: 'help-block media-type', id: 'speakerdeck-help', style: 'display:none' } Go to your SpeakerDeck, click on "share" -> "embed" and copy the data-id
%p{ class: 'help-block media-type', id: 'instagram-help', style: 'display:none' } Go to your Instagram photo page and copy everything behind instagram.com/p/ (without trailing /# hash symbol)
= f.action :submit, as: :button, button_html: { class: 'btn btn-primary' }
= f.input :commercial_type, as: :select, label: 'Type', input_html: { class: 'form-control select-help-toggle' }, collection: CONFIG['commercial_types'].values, include_blank: false, hint: 'This media-item will be used to represent this conference at various places in OSEM.'
= f.input :commercial_id, label: 'ID', as: :string, input_html: { required: 'required' }
= render partial: 'commercial_help'
= f.action :submit, as: :button, button_html: { class: 'btn btn-primary pull-right' }

View file

@ -7,4 +7,3 @@
.col-md-12
= semantic_form_for @commercial, url: conference_proposal_commercial_path(conference_id: @conference.short_title, proposal_id: @event.id, id: @commercial.id) do |f|
= render 'form', f: f
= link_to 'Back', edit_conference_proposal_path(conference_id: @conference.short_title, id: @event.id)

View file

@ -7,4 +7,3 @@
.col-md-12
= semantic_form_for @commercial, url: conference_proposal_commercials_path(conference_id: @conference.short_title, proposal_id: @event.id) do |f|
= render 'form', f: f
= link_to 'Back', edit_conference_proposal_path(conference_id: @conference.short_title, id: @event.id)

View file

@ -31,7 +31,7 @@
- if !current_user.nil? && current_user.proposal_count(conference) > 0
= link_to "My Proposals", conference_proposal_index_path(conference.short_title), :class =>"btn btn-default"
- elsif conference.cfp_open?
= link_to "Submit Proposal", conference_proposal_index_path(conference.short_title), :class =>"btn btn-default"
= link_to "Submit Proposal", new_conference_proposal_path(conference.short_title), :class =>"btn btn-default"
- if current_user.nil? || !current_user.subscribed?(conference)
= link_to 'Subscribe', conference_subscriptions_path(conference.short_title), method: :post, class: 'btn btn-default'
- else

View file

@ -21,10 +21,7 @@
= semantic_form_for(@registration, url: conference_conference_registrations_path(@conference.short_title)) do |f|
- if !current_user
= f.fields_for :user do |u|
= u.input :username, input_html: {required: 'required', autocomplete: 'off'}
= u.input :email, input_html: {required: 'required', autocomplete: 'off'}
= u.input :password, input_html: {required: 'required', autocomplete: 'off'}
= u.input :password_confirmation, :required => true, input_html: {required: 'required', autocomplete: 'off'}
= render partial: 'devise/shared/sign_up_fields', locals: { u: u}
- if @conference.questions.any?
= render partial: 'questions', locals: { f: f }
- if @conference.events.workshops.any?
@ -64,4 +61,4 @@
%p.text-center
or sign in using
.text-center
= render 'devise/shared/openid'
= render 'devise/shared/openid'

View file

@ -7,11 +7,7 @@
Sign Up
.panel-body
= semantic_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
= f.input :username, required: true
= f.input :email
= f.input :name, required: true
= f.input :password
= f.input :password_confirmation
= render partial: 'devise/shared/sign_up_fields', locals: { u: f}
%p.text-right
= f.action :submit, as: :button, label: 'Sign Up', button_html: {class: 'btn btn-success' }
- unless omniauth_configured.empty?

View file

@ -0,0 +1,4 @@
= u.input :username, input_html: {required: 'required', autocomplete: 'off'}
= u.input :email, input_html: {required: 'required', autocomplete: 'off'}
= u.input :password, input_html: {required: 'required', autocomplete: 'off', id: 'password_inline'}
= u.input :password_confirmation, :required => true, input_html: {required: 'required', autocomplete: 'off'}

View file

@ -0,0 +1,23 @@
%p.lead
- if @conference.event_types.any?
You can submit proposals for
= "#{event_types(@conference)}."
- if @conference.tracks.any?
Proposals should fit in one of the
= "#{pluralize(@conference.tracks.count, 'track')}:"
= "#{tracks(@conference)}."
- if @conference.call_for_paper
The submission period has begun
%em
= @conference.call_for_paper.start_date.strftime('%A, %B %-d. %Y')
and closes
%em
= @conference.call_for_paper.end_date.strftime('%A, %B %-d. %Y.')
That means you have only
- days = (@conference.call_for_paper.end_date - Date.today).to_i
%b
= pluralize(days, 'days')
left!
Remember
= @conference.title
will only be as good as the content you present. Submit early, submit often!

View file

@ -12,10 +12,10 @@
= render 'proposal/proposal_form'
#commercials-content.tab-pane
%p.text-muted
You can add commercials for your event. These commercials will be displayed on the event detail site.
You can add commercials for your proposal. These commercials will be displayed on the
= link_to 'public proposal page.', conference_proposal_path(@conference.short_title, @event)
If you don't add a commercial, the conference commercial will be displayed!
We currently support the following commercial types: YouTube, Vimeo, Instagram, Flickr, Speakerdeck and SlideShare.
- @event.commercials.each_slice(3) do |slice|
.row
- slice.each do |commercial|

View file

@ -1,55 +1,53 @@
= semantic_form_for(@event, :url => @url) do |f|
%section#basic
= f.inputs :name => "Session Information" do
- if can? :update, @event or can? :create, @event
= f.input :title, :as => :string, :required => true
- else
Title: #{@event.title}
%br
%br
= f.input :subtitle, :as => :string
%section#details
- if can? :update, @event or can? :create, @event
= f.input :event_type_id, as: :select,
= semantic_form_for(@event, url: @url) do |f|
= f.inputs name: 'Proposal Information' do
= f.input :title, as: :string, required: true
= f.input :subtitle, as: :string
= f.input :event_type_id, as: :select,
collection: @conference.event_types.map {|type| ["#{type.title} - #{show_time(type.length)}", type.id,
data: { min_words: type.minimum_abstract_length, max_words: type.maximum_abstract_length }]},
include_blank: false, label: 'Session Type'
- else
Event type: #{@event.event_type.title}
include_blank: false, label: 'Type', input_html: { class: 'select-help-toggle' }
- @conference.event_types.each do |event_type|
%span{ class: 'help-block select-help-text event_event_type_id collapse', id: "#{event_type.id}-help" }
= event_type.description
:javascript
$("##{@conference.event_types.first.id}-help").collapse('show');
= f.input :difficulty_level, as: :select, collection: @conference.difficulty_levels, input_html: { class: 'select-help-toggle' },
include_blank: '(Please select)' if @conference.difficulty_levels.any?
- @conference.difficulty_levels.each do |difficulty_level|
%span{ class: 'help-block select-help-text collapse event_difficulty_level_id', id: "#{difficulty_level.id}-help" }
= difficulty_level.description
= f.input :abstract, input_html: { rows: 5 },
required: true, hint: link_to('Tips to improve your presentations', 'http://blog.hubspot.com/blog/tabid/6307/bid/5975/10-Rules-to-Instantly-Improve-Your-Presentations.aspx')
%p
You have used
%span#abstract-count #{@event.abstract_word_count}
words. Abstracts must be between
%span#abstract-minimum-word-count
0
and
%span#abstract-maximum-word-count
250
words.
= f.input :require_registration, label: 'Require participants to register to your event'
- if current_user.has_any_role? :admin, { name: :organizer, resource: @conference }, { name: :cfp, resource: @conference }
= f.label :is_highlight
%br
= f.check_box :is_highlight, class: 'switch-checkbox', data: { size: "small" }
= f.input :is_highlight
%p.text-right
= link_to '#description', "data-toggle"=>"collapse" do
Do you require something special?
.collapse#description
= f.input :description, input_html: { rows: 5 }, label: 'Requirements', placeholder: 'Eg. Whiteboard, printer, or something like that.'
%br
= f.input :difficulty_level, :as => :select, :collection => @conference.difficulty_levels, :include_blank => "(Please select)" if @conference.difficulty_levels.any?
= f.input :require_registration
= f.input :abstract, :input_html => {:rows => 5, :class => "span11"},
:required => true, :label => "Session Abstract"
You have used
%span#abstract-count #{@event.abstract_word_count}
words. Abstracts must be between
%span#abstract-minimum-word-count
0
and
%span#abstract-maximum-word-count
250
words.
%br
%br
= f.input :description, :input_html => {:rows => 5, :class=> "span11"}, :hint => "This will only be shown to organizers, not to attendees."
%section#information
- if current_user.name.blank? || current_user.biography.blank?
= f.inputs :name => "Your Information" do
= semantic_fields_for current_user do |u|
- if current_user.name.blank?
= u.input :name, :as => :string, :required => true
- if current_user.biography.blank?
= u.input :biography, :required => true, :input_html => {:rows => 5, :class => 'span11', "onkeyup" => "word_count(this, 'biography-count', 150)"}
You have used
%span#biography-count #{current_user.biography_word_count}
words. Biographies are limited to 150 words.
%p.text-right
= f.action :submit, :as => :button, :button_html => {:class => "btn btn-success"}
= f.action :submit, :as => :button, :button_html => {:class => "btn btn-success"}, label: 'Update Proposal'

View file

@ -0,0 +1,36 @@
- progress_status = event.progress_status
%ul.list-unstyled
%li{'class'=>class_for_todo(progress_status['registered'])}
%span{'class'=>icon_for_todo(progress_status['registered'])}
- if progress_status['registered']
= link_to 'Edit your registration', edit_conference_conference_registrations_path(event.conference.short_title)
- else
= link_to 'Register to the conference', new_conference_conference_registrations_path(event.conference.short_title)
%li{'class'=>class_for_todo(progress_status['biography'])}
%span{'class'=>icon_for_todo(progress_status['biography'])}
- if progress_status['biography']
= link_to 'Edit your biography', edit_user_path(event.submitter)
- else
= link_to 'Fill out your biography', edit_user_path(event.submitter)
%li{'class'=>class_for_todo(progress_status['subtitle'])}
%span{'class'=>icon_for_todo(progress_status['subtitle'])}
- if progress_status['subtitle']
= link_to 'Edit the subtitle', edit_conference_proposal_path(event.conference.short_title, event)
- else
= link_to 'Add a subtitle', edit_conference_proposal_path(event.conference.short_title, event)
%li{'class'=>class_for_todo(progress_status['commercials'])}
%span{'class'=>icon_for_todo(progress_status['commercials'])}
- if progress_status['commercials']
= link_to 'Edit the commercials', edit_conference_proposal_path(event.conference.short_title, event, anchor: 'commercials-content')
- else
= link_to 'Add a commercial', new_conference_proposal_commercial_path(event.conference.short_title, event)
%li{'class'=>class_for_todo(progress_status['difficulty_level'])}
%span{'class'=>icon_for_todo(progress_status['difficulty_level'])}
- if progress_status['difficulty_level']
= link_to 'Change the difficulty level', edit_conference_proposal_path(event.conference.short_title, event)
- else
= link_to 'Add a difficulty level', edit_conference_proposal_path(event.conference.short_title, event)

View file

@ -3,45 +3,99 @@
.col-md-12.page-header
%h1
= "My Proposals for #{@conference.title}"
- if @conference.cfp_open? || (current_user.has_role? :organizer, @conference)
= link_to "New Proposal", new_conference_proposal_path(@conference.short_title), :class => "btn btn-success pull-right"
- if current_user.proposal_count(@conference) > 0
.row
.col-md-12
= render partial: 'encouragement_text'
- if @events.any?
- if @events.highlighted.any?
.row
.col-md-12
%p.lead
%strong
Congratulations!
Some of your proposals have been selected as a highlight of #{@conference.title}!
%ul
- @events.highlighted.each do |event|
%li= link_to event.title, conference_proposal_path(@conference.short_title, event)
.row
.col-md-12
%table.table.table-bordered.table-striped
%thead
%th
%b Title
%th
%b Status
%th
%p.text-right
= link_to '#status-help', class: 'btn btn-default', "data-toggle"=>"collapse" do
Help?
.collapse#status-help
%p
%strong
What happens next with my proposal?
%p
If you submit a proposal, the conference organizers will review it and either accept or reject it.
%br
If your proposal is accepted, the conference organizers expect you to confirm that you will be able to hold it.
%br
If your proposal is rejected, you can either live with that or adapt it and resubmit it for review again.
%br
If something changes and you can't hold the presentation any more, you should withdraw it.
%p
%strong
Why do I need to add more information?
%p
The more information you add to your proposal, the more likely it is that the conference organizers accept your proposal.
%br
It will also be more likely that visitors find your proposal interesting enough to attend.
%p
%strong
Why do I need to register to the conference?
%p
Knowing the number of visitors for the conference helps the organizers plan better.
%table.table.table-striped
- @events.each do |event|
%tr
%td
%td{style: "padding:20px 8px 20px 8px;"}
%span{ title: event.state.humanize, class: "fa #{event_status_icon(event)}" }
%td.col-md-7{style: "padding:20px 8px 20px 8px;"}
= link_to event.title, conference_proposal_path(@conference.short_title, event.id)
%td
- if event.state == 'new'
Review pending
- else
= event.state.humanize
- if event.state == 'confirmed' && event.require_registration == true
(Pre-registered: #{pre_registered(event).count})
- if event.confirmed? && !@conference.user_registered?(current_user)
%br
= link_to "Register to attend", new_conference_conference_registrations_path(@conference.short_title), :style => "font-size:10px;"
%td
%br
%small.text-muted
= event.event_type.title
= "(#{event.event_type.length} min)"
- if event.state == 'confirmed' && event.require_registration == true
,
Pre-registered: #{pre_registered(event).count}
%td.col-md-2{style: "padding:20px 8px 20px 8px;"}
= link_to 'Complete your todo list', 'javascript: void(0)', "type"=>"button", "data-trigger"=>"focus", "data-toggle"=>"popover", "title"=>"Your todo list", "data-content"=>"#{render partial: 'tooltip', locals: { event: event} }"
- progress_percentage = event.calculate_progress
.progress
%div{class: "progress-bar #{event_progress_color(progress_percentage)}", style: "width:#{progress_percentage}%;"}
= event.progress_status.reject{ |_key, value| value }.length
left
%td.col-md-3{style: "padding:20px 0px 20px 0px;"}
.pull-right
- if event.transition_possible? :confirm
= link_to 'Confirm',
confirm_conference_proposal_path(@conference.short_title, event),
method: :patch, class: 'btn btn-mini btn-success', id: "confirm_proposal_#{event.id}"
= link_to 'Edit', edit_conference_proposal_path(@conference.short_title, event.id),
class: 'btn btn-mini btn-primary', id: "edit_proposal_#{event.id}"
- if event.transition_possible? :withdraw
= link_to 'Withdraw', conference_proposal_path(@conference.short_title, event.id), method: :delete,
confirm: 'Are you sure you want to withdraw this proposal?', class: 'btn btn-mini btn-warning',
data: { confirm: 'Are you sure you want to withdraw this proposal?' }, class: 'btn btn-mini btn-warning',
id: "delete_proposal_#{event.id}"
- if event.state == 'withdrawn' || event.state == 'rejected'
= link_to 'Re-Submit',
restart_conference_proposal_path(@conference.short_title, event.id),
method: :patch, class: 'btn btn-mini btn-success', id: "review_event_#{event.id}"
= link_to 'Edit', edit_conference_proposal_path(@conference.short_title, event.id),
class: 'btn btn-default', id: "edit_proposal_#{event.id}"
.row
.col-md-12
- if @conference.cfp_open? || (current_user.has_role? :organizer, @conference)
= link_to "New Proposal", new_conference_proposal_path(@conference.short_title), :class => "btn btn-success pull-right"

View file

@ -1,28 +1,93 @@
.container
.row
.col-md-12
%p
- if @conference.event_types.any?
You can submit proposals for
= "#{event_types(@conference)}."
- if @conference.tracks.any?
Proposals should fit in one of the
= "#{pluralize(@conference.tracks.count, 'track')}:"
= "#{tracks(@conference)}."
The submission period has begun
%em
= @conference.call_for_paper.start_date.strftime('%A, %B %-d. %Y')
and closes
%em
= @conference.call_for_paper.end_date.strftime('%A, %B %-d. %Y.')
That means you have only
- days = (@conference.call_for_paper.end_date - Date.today).to_i
%b
= pluralize(days, 'days')
left!
Remember
= @conference.short_title
will only be as good as the sessions you present. Submit early, submit often!
.page-header
%h1 New Proposal
.row
.col-md-12
= render 'proposal_form'
= render partial: 'encouragement_text'
.row
.col-md-8
- if !current_user
%legend
%span
=link_to('#signup', role: 'tab', "aria-controls" => "home", "data-toggle" => "tab") do
= CONFIG['name']
Account
%span.pull-right#account-already
=link_to('#signin', role: 'tab', "aria-controls" => "home", "data-toggle" => "tab") do
Already have an account?
.tab-content
.tab-pane.active{role: 'tabpanel', id: 'signup'}
= semantic_form_for(@event, url: @url) do |f|
- unless current_user
= semantic_fields_for @user do |u|
= render partial: 'devise/shared/sign_up_fields', locals: { u: u }
= f.inputs name: 'Proposal Information' do
= f.input :title, as: :string, required: true
= f.input :event_type_id, as: :select,
collection: @conference.event_types.map {|type| ["#{type.title} - #{show_time(type.length)}", type.id,
data: { min_words: type.minimum_abstract_length, max_words: type.maximum_abstract_length }]},
include_blank: false, label: 'Type', input_html: { class: 'select-help-toggle' }
- @conference.event_types.each do |event_type|
%span{ class: 'help-block event_event_type_id collapse', id: "#{event_type.id}-help" }
= event_type.description
:javascript
$("##{@conference.event_types.first.id}-help").collapse('show');
= f.input :abstract, input_html: { rows: 5 },
required: true, hint: link_to('Tips to improve your presentations', 'http://blog.hubspot.com/blog/tabid/6307/bid/5975/10-Rules-to-Instantly-Improve-Your-Presentations.aspx')
%p
You have used
%span#abstract-count #{@event.abstract_word_count}
words. Abstracts must be between
%span#abstract-minimum-word-count
0
and
%span#abstract-maximum-word-count
250
words.
= f.input :require_registration, label: 'Require participants to register to your event'
%p.text-right
= link_to '#description', "data-toggle"=>"collapse", id: 'description_link' do
Do you require something special?
.collapse#description
= f.input :description, input_html: { rows: 5 }, label: 'Requirements', placeholder: 'Eg. Whiteboard, printer, or something like that.'
%p.text-right
= f.action :submit, :as => :button, :button_html => {:class => "btn btn-success"}, label: 'Create Proposal'
.tab-pane{role: 'tabpanel', id: 'signin'}
- if !CONFIG['authentication']['ichain']['enabled']
= form_tag(new_user_session_path, class: 'form-horizontal') do
%legend
%span
Sign In
.form-group
%label{for: "user[login]", class: 'col-sm-2 control-label'}
Username
.col-sm-10
= text_field_tag 'user[login]', nil, placeholder: 'Username', class: 'form-control', required: 'required'
.form-group
%label{for: "user[password]", class: 'col-sm-2 control-label'}
Password
.col-sm-10
= password_field_tag 'user[password]', nil, placeholder: 'Password', class: 'form-control', required: 'required'
.form-group
.col-sm-12
%button.btn.btn-success.pull-right
Sign in
- unless omniauth_configured.empty?
.form-group
%hr
%p.text-center
or sign in using
.text-center
= render 'devise/shared/openid'

View file

@ -0,0 +1,5 @@
class AddDescriptionToEventType < ActiveRecord::Migration
def change
add_column :event_types, :description, :string
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150304135935) do
ActiveRecord::Schema.define(version: 20150415121038) do
create_table "ahoy_events", force: true do |t|
t.uuid "visit_id"
@ -196,6 +196,7 @@ ActiveRecord::Schema.define(version: 20150304135935) do
t.integer "minimum_abstract_length", default: 0
t.integer "maximum_abstract_length", default: 500
t.string "color"
t.string "description"
end
create_table "event_users", force: true do |t|
@ -275,8 +276,8 @@ ActiveRecord::Schema.define(version: 20150304135935) do
end
create_table "qanswers", force: true do |t|
t.integer "question_id"
t.integer "answer_id"
t.integer "question_id"
t.integer "answer_id"
t.datetime "created_at"
t.datetime "updated_at"
end

View file

@ -75,7 +75,7 @@ feature Commercial do
sign_out
end
scenario 'adds a invalid commercial to an event', feature: true, js: true do
scenario 'adds a valid commercial to an event', feature: true, js: true do
visit edit_conference_proposal_path(conference.short_title, event.id)
click_link 'Commercials'
@ -89,7 +89,7 @@ feature Commercial do
expect(event.commercials.count).to eq(@expected_count)
end
scenario 'adds a valid commercial to an event', feature: true, js: true do
scenario 'adds an invalid commercial to an event', feature: true, js: true do
visit edit_conference_proposal_path(conference.short_title, event.id)
click_link 'Commercials'
click_link 'Add Commercial'
@ -97,7 +97,6 @@ feature Commercial do
select('SlideShare', from: 'commercial_commercial_type')
click_button 'Create Commercial'
expect(flash).to eq("An error prohibited this Commercial from being saved: Commercial can't be blank.")
expect(event.commercials.count).to eq(@expected_count - 1)
end
@ -125,7 +124,6 @@ feature Commercial do
select('SlideShare', from: 'commercial_commercial_type')
fill_in 'commercial_commercial_id', with: ''
click_button 'Update Commercial'
expect(flash).to eq("An error prohibited this Commercial from being saved: Commercial can't be blank.")
expect(event.commercials.count).to eq(@expected_count)
end

View file

@ -67,26 +67,60 @@ feature Event do
event_role: 'submitter')]
end
scenario 'submits a valid proposal', feature: true, js: true do
scenario 'not signed_in user submits proposal' do
expected_count_event = Event.count + 1
expected_count_user = User.count + 1
visit new_conference_proposal_path(conference.short_title)
fill_in 'user_username', with: 'Test User'
fill_in 'user_email', with: 'testuser@osem.io'
fill_in 'password_inline', with: 'testuserpassword'
fill_in 'user_password_confirmation', with: 'testuserpassword'
fill_in 'event_title', with: 'Example Proposal'
select('Example Event Type', from: 'event[event_type_id]')
fill_in 'event_abstract', with: 'Lorem ipsum abstract'
click_button 'Create Proposal'
expect(flash).to eq('Proposal was successfully submitted.')
expect(Event.count).to eq(expected_count_event)
expect(User.count).to eq(expected_count_user)
end
scenario 'update a proposal' do
proposal = create(:event)
sign_in proposal.submitter
visit edit_conference_proposal_path(proposal.conference.short_title, proposal)
fill_in 'event_subtitle', with: 'My event subtitle'
select('Easy', from: 'event[difficulty_level_id]')
click_button 'Update Proposal'
expect(flash).to eq('Proposal was successfully updated.')
end
scenario 'signed_in user submits a valid proposal', feature: true, js: true do
sign_in participant_without_bio
expected_count = Event.count + 1
visit conference_proposal_index_path(conference.short_title)
click_link 'New Proposal'
fill_in 'event_title', with: 'Example Proposal'
fill_in 'event_subtitle', with: 'Example Proposal Subtitle'
select('Example Event Type', from: 'event[event_type_id]')
fill_in 'event_abstract', with: 'Lorem ipsum abstract'
click_link 'description_link'
fill_in 'event_description', with: 'Lorem ipsum description'
fill_in 'user_biography', with: 'Lorem ipsum biography'
click_button 'Create Proposal'
expect(flash).to eq('Proposal was successfully submitted.')
click_button 'Create Event'
expect(flash).to eq('Event was successfully submitted. You should register for the conference now.')
expect(current_path).to eq(new_conference_conference_registrations_path(conference.short_title))
expect(current_path).to eq(conference_proposal_index_path(conference.short_title))
expect(Event.count).to eq(expected_count)
end
@ -94,7 +128,7 @@ feature Event do
sign_in participant
visit conference_proposal_index_path(conference.short_title)
expect(page.has_content?('Example Proposal')).to be true
expect(page.has_content?('Unconfirmed')).to be true
expect(@event.state).to eq('unconfirmed')
click_link "confirm_proposal_#{@event.id}"
expect(flash).
to eq('The proposal was confirmed. Please register to attend the conference.')
@ -107,7 +141,6 @@ feature Event do
@event.confirm!
visit conference_proposal_index_path(conference.short_title)
expect(page.has_content?('Example Proposal')).to be true
expect(page.has_content?('Confirmed')).to be true
click_link "delete_proposal_#{@event.id}"
expect(flash).to eq('Proposal was successfully withdrawn.')
@event.reload

View file

@ -43,6 +43,9 @@ describe 'User' do
it{ should be_able_to(:show, Registration.new)}
it{ should_not be_able_to(:manage, registration)}
it{ should be_able_to(:create, Event)}
it{ should be_able_to(:show, Event.new)}
it{ should_not be_able_to(:manage, :any)}
end