Merge branch 'master' of https://github.com/CactusPuppy/snapcon into 176895512-automatically-redirect-to-registration-after-buying-a-ticket

This commit is contained in:
Jimmy 2021-03-10 14:58:01 -08:00
commit 85c7b31a77
85 changed files with 740 additions and 509 deletions

View file

@ -13,11 +13,13 @@ $(function () {
$("#conference-start-datepicker").datetimepicker({
useCurrent: false,
format: "YYYY-MM-DD"
ignoreReadonly: true,
format: "YYYY-MM-DD",
});
$("#conference-end-datepicker").datetimepicker({
useCurrent: false,
ignoreReadonly: true,
format: "YYYY-MM-DD"
});

View file

@ -147,17 +147,32 @@ function word_count(text, divId, maxcount) {
});
};
function fill_if_empty(text_area, filler) {
let area = $('#' + text_area);
if (!area.val()) {
area.val(filler);
area.trigger('change');
}
}
/* Wait for the DOM to be ready before attaching events to the elements */
$( document ).ready(function() {
/* Set the minimum and maximum proposal abstract word length */
/* Set the minimum and maximum proposal abstract and submission text word length */
$("#event_event_type_id").change(function () {
var $selected = $("#event_event_type_id option:selected")
var max = $selected.data("max-words");
var min = $selected.data("min-words");
// Set the filler text for the submission text
fill_if_empty('event_submission_text', $selected.data("help"));
$("#abstract-maximum-word-count").text(max);
$("#submission-maximum-word-count").text(max);
$("#abstract-minimum-word-count").text(min);
$("#submission-minimum-word-count").text(min);
word_count($('#event_abstract').get(0), 'abstract-count', max);
word_count($('#event_submission_text').get(0), 'submission-count', max);
})
.trigger('change');
@ -167,6 +182,25 @@ $( document ).ready(function() {
var max = $selected.data("max-words");
word_count(this, 'abstract-count', max);
} );
/* Count the submission text length */
$("#event_submission_text").bind('change keyup paste input', function() {
var $selected = $("event_event_type_id option:selected")
var max = $selected.data("max-words");
word_count(this, 'submission-count', max);
});
/* Listen for reset template button, wait for confirm, and reset. */
$('#sub_text_reset').click((e) => {
let $selected = $("#event_event_type_id option:selected");
let $this = $(e.target);
let affirm = confirm($this.data('confirm'));
if (affirm) {
let sub_text = $('#event_submission_text');
sub_text.val($selected.data('help'));
sub_text.trigger('change');
}
});
});
/* Commodity function for modal windows */

View file

@ -3,7 +3,8 @@
#splash {
// Counter the general padding for #content
margin-bottom: -60px;
margin-bottom: -65px;
section {
padding-top: 60px;
padding-bottom: 60px;
@ -144,6 +145,10 @@
}
}
.social-media.trapezoid {
border-top-color: #0C3559; // TODO: Use @conference color?
}
#social-media{
background: none repeat scroll 0 0 #0C3559;
padding: 50px 20px;

View file

@ -49,7 +49,7 @@ module Admin
private
def event_type_params
params.require(:event_type).permit(:title, :length, :minimum_abstract_length, :maximum_abstract_length, :color, :conference_id, :description)
params.require(:event_type).permit(:title, :length, :minimum_abstract_length, :maximum_abstract_length, :submission_instructions, :color, :conference_id, :description)
end
end
end

View file

@ -175,7 +175,7 @@ module Admin
def event_params
params.require(:event).permit(
# Set also in proposals controller
:title, :subtitle, :event_type_id, :abstract, :description, :require_registration, :difficulty_level_id,
:title, :subtitle, :event_type_id, :abstract, :submission_text, :description, :require_registration, :difficulty_level_id,
# Set only in admin/events controller
:track_id, :state, :language, :is_highlight, :max_attendees,
# Not used anymore?

View file

@ -66,6 +66,16 @@ module Admin
def edit; end
def destroy
if @user.destroy
redirect_to admin_users_path,
notice: "User #{@user.id} (#{@user.email}) deleted."
else
redirect_to admin_users_path,
error: "User #{@user.id} (#{@user.emai}) could not be deleted. #{@user.full_messages.join(',')}"
end
end
private
def user_params

View file

@ -6,8 +6,11 @@ class ConferencesController < ApplicationController
load_and_authorize_resource find_by: :short_title, except: :show
def index
@current = Conference.where('end_date >= ?', Date.current).reorder(start_date: :asc)
@antiquated = Conference.where('end_date < ?', Date.current)
@current = Conference.upcoming.reorder(start_date: :asc)
@antiquated = Conference.past
if @antiquated.empty? && @current.empty? && User.empty?
render :new_install
end
end
def show
@ -68,6 +71,47 @@ class ConferencesController < ApplicationController
end
end
def calendar
respond_to do |format|
format.ics do
calendar = Icalendar::Calendar.new
Conference.all.each do |conf|
if params[:full]
event_schedules = conf.program.selected_event_schedules(
includes: [{ event: %i[event_type speakers submitter] }]
)
calendar = icalendar_proposals(calendar, event_schedules.map(&:event), conf)
else
calendar.event do |e|
e.dtstart = conf.start_date
e.dtstart.ical_params = { 'VALUE'=>'DATE' }
e.dtend = conf.end_date
e.dtend.ical_params = { 'VALUE'=>'DATE' }
e.duration = "P#{(conf.end_date - conf.start_date + 1).floor}D"
e.created = conf.created_at
e.last_modified = conf.updated_at
e.summary = conf.title
e.description = conf.description
e.uid = conf.guid
e.url = conference_url(conf.short_title)
v = conf.venue
if v
e.geo = v.latitude, v.longitude if v.latitude && v.longitude
location = ''
location += "#{v.street}, " if v.street
location += "#{v.postalcode} #{v.city}, " if v.postalcode && v.city
location += v.country_name if v.country_name
e.location = location if location
end
end
end
end
calendar.publish
render inline: calendar.to_ical
end
end
end
private
def conference_finder_conditions

View file

@ -170,7 +170,7 @@ class ProposalsController < ApplicationController
def event_params
params.require(:event).permit(:event_type_id, :track_id, :difficulty_level_id,
:title, :subtitle, :abstract, :description,
:title, :subtitle, :abstract, :submission_text, :description,
:require_registration, :max_attendees, :language,
speaker_ids: [], volunteer_ids: []
)

View file

@ -21,6 +21,12 @@ class SchedulesController < ApplicationController
format.xml do
@events_xml = event_schedules.map(&:event).group_by{ |event| event.time.to_date } if event_schedules
end
format.ics do
cal = Icalendar::Calendar.new
cal = icalendar_proposals(cal, event_schedules.map(&:event), @conference)
cal.publish
render inline: cal.to_ical
end
format.html do
@rooms = @conference.venue.rooms if @conference.venue

View file

@ -35,7 +35,8 @@ class UserDatatable < AjaxDatatablesRails::Base
roles: record.roles.any? ? show_roles(record.get_roles) : 'None',
view_url: admin_user_path(record),
edit_url: edit_admin_user_path(record),
DT_RowId: record.id
DT_RowId: record.id,
confirmed: record.confirmed_at.present?
}
end
end

View file

@ -27,4 +27,33 @@ module ConferenceHelper
markdown(ticket.description.split("\n").first&.strip)
end
# adds events to icalendar for proposals in a conference
def icalendar_proposals(calendar, proposals, conference)
proposals.each do |proposal|
calendar.event do |e|
e.dtstart = proposal.time
e.dtend = proposal.time + proposal.event_type.length * 60
e.duration = "PT#{proposal.event_type.length}M"
e.created = proposal.created_at
e.last_modified = proposal.updated_at
e.summary = proposal.title
e.description = proposal.abstract
e.uid = proposal.guid
e.url = conference_program_proposal_url(conference.short_title, proposal.id)
v = conference.venue
if v
e.geo = v.latitude, v.longitude if v.latitude && v.longitude
location = ''
location += "#{proposal.room.name} - " if proposal.room.name
location += " - #{v.street}, " if v.street
location += "#{v.postalcode} #{v.city}, " if v.postalcode && v.city
location += "#{v.country_name}, " if v.country_name
e.location = location
end
e.categories = conference.title, "Difficulty: #{proposal.difficulty_level.title}", "Track: #{proposal.track.name}"
end
end
calendar
end
end

View file

@ -14,7 +14,6 @@
# updated_at :datetime
# program_id :integer
#
# cannot delete program if there are events submitted
class Cfp < ApplicationRecord
TYPES = %w(events booths tracks).freeze

View file

@ -61,6 +61,9 @@ class Conference < ApplicationRecord
has_one :email_settings, dependent: :destroy
has_one :program, dependent: :destroy
has_one :venue, dependent: :destroy
delegate :city, :country_name, to: :venue, allow_nil: true
delegate :name, :street, to: :venue, prefix: true, allow_nil: true
has_many :ticket_purchases, dependent: :destroy
has_many :physical_tickets, through: :ticket_purchases
has_many :payments, dependent: :destroy

View file

@ -7,6 +7,7 @@
# id :bigint not null, primary key
# abstract :text
# comments_count :integer default(0), not null
# committee_review :text
# description :text
# guid :string not null
# is_highlight :boolean default(FALSE)
@ -18,6 +19,7 @@
# require_registration :boolean
# start_time :datetime
# state :string default("new"), not null
# submission_text :text
# subtitle :string
# title :string not null
# week :integer
@ -73,6 +75,7 @@ class Event < ApplicationRecord
before_create :generate_guid
validate :abstract_limit
validate :submission_limit
validate :before_end_of_conference, on: :create
validates :title, presence: true
validates :abstract, presence: true
@ -217,6 +220,10 @@ class Event < ApplicationRecord
abstract.to_s.split.size
end
def submission_word_count
submission_text.to_s.split.size
end
def self.get_state_color(state)
COLORS[state.to_sym] || '#00FFFF' # azure
end
@ -341,16 +348,28 @@ class Event < ApplicationRecord
errors.add(:max_attendees, "cannot be more than the room's capacity (#{room.size})") if max_attendees && (max_attendees > room.size)
end
def abstract_limit
# If we don't have an event type, there is no need to count anything
return unless event_type && abstract
def word_limit(field)
# If we don't have an event type or the requested field, don't count
return unless event_type && respond_to?(field) && self[field]
len = abstract.split.size
len = self[field].split.size
# TODO: Use different limits for different text fields
# Uncomment the two lines below this when the separate word limits are implemented.
# max_words = event_type["maximum_#{field}_length"]
# min_words = event_type["minimum_#{field}_length"]
max_words = event_type.maximum_abstract_length
min_words = event_type.minimum_abstract_length
errors.add(:abstract, "cannot have less than #{min_words} words") if len < min_words
errors.add(:abstract, "cannot have more than #{max_words} words") if len > max_words
errors.add(field.to_sym, "cannot have less than #{min_words} words") if len < min_words
errors.add(field.to_sym, "cannot have more than #{max_words} words") if len > max_words
end
def abstract_limit
word_limit(:abstract)
end
def submission_limit
word_limit(:submission_text)
end
# TODO: create a module to be mixed into model to perform same operation

View file

@ -10,6 +10,7 @@
# length :integer default(30)
# maximum_abstract_length :integer default(500)
# minimum_abstract_length :integer default(0)
# submission_instructions :text
# title :string not null
# created_at :datetime
# updated_at :datetime

View file

@ -22,7 +22,6 @@
#
# index_programs_on_selected_schedule_id (selected_schedule_id)
#
# cannot delete program if there are events submitted
class Program < ApplicationRecord
has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id }

View file

@ -24,8 +24,8 @@ class SurveyQuestion < ActiveRecord::Base
validates :title, presence: true
validates :possible_answers, :max_choices, :min_choices, presence: true, if: :choice?
validates :min_choices, numericality: { greater_than_or_equal_to: 1 }, allow_blank: true
validates :max_choices, numericality: { greater_than_or_equal_to: 1 }, allow_blank: true
validates :min_choices, numericality: { greater_than_or_equal_to: 1 }, allow_blank: true, if: :choice?
validates :max_choices, numericality: { greater_than_or_equal_to: 1 }, allow_blank: true, if: :choice?
validate :max_choices_greater_than_min

View file

@ -69,9 +69,9 @@ class TicketPdf < Prawn::Document
draw_text @conference.title.to_s, at: [@mid_horizontal + 30, cursor - 30], size: 12
draw_text @conference.organization.name.to_s, at: [@mid_horizontal + 30, cursor - 50], size: 12
if @conference.venue
draw_text @conference.venue.name, at: [@mid_horizontal + 30, cursor - 70]
draw_text @conference.venue.street, at: [@mid_horizontal + 30, cursor - 90]
draw_text @conference.venue.city, at: [@mid_horizontal + 30, cursor - 110]
draw_text @conference.venue_name, at: [@mid_horizontal + 30, cursor - 70]
draw_text @conference.venue_street, at: [@mid_horizontal + 30, cursor - 90]
draw_text @conference.city, at: [@mid_horizontal + 30, cursor - 110]
end
move_up 130
move_down @mid_vertical

View file

@ -7,6 +7,7 @@
# id :bigint not null, primary key
# abstract :text
# comments_count :integer default(0), not null
# committee_review :text
# description :text
# guid :string not null
# is_highlight :boolean default(FALSE)
@ -18,6 +19,7 @@
# require_registration :boolean
# start_time :datetime
# state :string default("new"), not null
# submission_text :text
# subtitle :string
# title :string not null
# week :integer

View file

@ -6,6 +6,8 @@ class PictureUploader < CarrierWave::Uploader::Base
include CarrierWave::Compatibility::Paperclip
include CarrierWave::BombShelter
storage :file
# use cloudinary if it's configured
if Cloudinary.config.cloud_name
# use https by default

View file

@ -15,6 +15,7 @@
= f.input :description, as: :text, hint: markdown_hint, input_html: { rows: 5, data: { provide: 'markdown-editable' } }
= f.input :minimum_abstract_length, input_html: {size: 3}
= f.input :maximum_abstract_length, input_html: {size: 3}
= f.input :submission_instructions, as: :text, hint: markdown_hint, input_html: { rows: 5, data: { provide: 'markdown-editable' } }
= f.input :color, input_html: { size: 6, type: 'color' }
%p.text-right
= f.action :submit, as: :button, button_html: { class: 'btn btn-primary' }

View file

@ -10,6 +10,7 @@
%thead
%th Title
%th Description
%th Instructions
%th Length
%th Abstract Length
%th Color
@ -21,6 +22,8 @@
= event_type.title
%td
= markdown(event_type.description)
%td
= markdown(event_type.submission_instructions)
%td
= event_type.length
Minutes

View file

@ -7,6 +7,8 @@
%small
= @event.subtitle
.btn-group.pull-right
- if @event.public
= link_to 'Preview', conference_program_proposal_path(@conference.short_title, @event.id), class: 'btn btn-mini btn-primary'
= link_to 'Registrations', registrations_admin_conference_program_event_path(@conference.short_title, @event), class: 'btn btn-success'
= link_to 'Edit', edit_admin_conference_program_event_path(@conference.short_title, @event), class: 'btn btn-mini btn-primary'
@ -111,6 +113,10 @@
%td
%b Abstract
%td= markdown(@event.abstract)
%tr
%td
%b Submission Description
%td= markdown(@event.submission_text)
%tr
%td
%b Requirements

View file

@ -15,7 +15,7 @@
.row
.col-md-12
= f.input :title
= f.input :title, input_html: { autofocus: true }
= f.input :mandatory
.survey-possible-answers{ class: @survey_question.choice? ? '' : 'hidden' }
= f.input :possible_answers, hint: 'Comma separated', input_html: { rows: 3 }

View file

@ -7,4 +7,4 @@
.panel-body
- question_replies = survey_question.survey_replies
- if question_replies.any?
= pie_chart question_replies.group(:text).count, library: { legend: 'bottom', plotOptions: { pie: { dataLabels: { enabled: false }, showInLegend: true } } }
= pie_chart question_replies.group(:text).count, library: { legend: { position: 'bottom' }, plotOptions: { pie: { dataLabels: { enabled: false }, showInLegend: true } } }

View file

@ -20,6 +20,7 @@
%th{ width: '0' } Conferences Attended
%th{ width: '50%' } Roles
%th{ width: '0' } Actions
%th{ style: 'display: none' } Confirmed?
%tbody
:javascript
@ -37,6 +38,7 @@
{
"data": "confirmed_at",
"render": function (data, type, row, meta) {
console.log(meta)
return '<input type="checkbox" class="switch-checkbox" '+
'id="user_'+row.id+'_confirmed" '+
'name="user_'+row.id+'_confirmed" '+
@ -73,6 +75,11 @@
'<a class="btn-primary" href="'+data.edit_url+'">Edit</a>'+
'</div>';
}
},
{
"data": "confirmed",
"className": 'hidden',
"render": false
}
]
});

View file

@ -12,8 +12,10 @@
.tab-content
#user-info-content.tab-pane{class: "#{'active' unless params[:tab] == 'submissions-content'}"}
- if can? :edit, @user
.pull-right
.pull-right.btn-group
= link_to 'Edit', edit_admin_user_path(@user), class: 'btn btn-primary'
= link_to 'Delete', admin_user_path(@user),method: :delete, class: 'btn btn-danger',
data: {confirm: "Are you sure?"}
%table.table
- @show_attributes.each do |attr|
%tr

View file

@ -7,6 +7,11 @@
= event.title
%small
= event.subtitle
.text-muted
= registered_text(event)
- if event.scheduled?
(Scheduled on: #{event.time.to_date})
.panel-body
-# %p
-# = canceled_replacement_event_label(event, event_schedule)

View file

@ -9,9 +9,9 @@
-if @conference.venue
at
%strong
= "#{@conference.venue.name},"
= "#{@conference.venue.street},"
= "#{@conference.venue.city} / #{@conference.venue.country_name}."
= "#{@conference.venue_name},"
= "#{@conference.venue_street},"
= "#{@conference.city} / #{@conference.country_name}."
%small
= date_string(@conference.start_date, @conference.end_date)
- unless @conference.code_of_conduct.blank?

View file

@ -12,10 +12,10 @@
= date_string(conference.start_date, conference.end_date)
- if conference.venue
%p
= "#{conference.venue.city}/#{conference.venue.country_name}"
= "#{conference.city}/#{conference.country_name}"
- unless conference.description.blank?
%p
= markdown(conference.description)
= markdown(conference.description, escape_html=false)
.col-md-2
.btn-group-vertical
- if !@conference || @conference != conference

View file

@ -27,4 +27,4 @@
- if contact.email?
= mail_to "#{ contact.email }" do
%i.fa.fa-envelope-o.fa-4x
.trapezoid
.trapezoid.social-media

View file

@ -10,16 +10,19 @@
.col-md-12
%p.text-right
%button{ type: 'button', class: 'btn btn-link btn-sm', 'data-toggle' => 'collapse', 'data-target' => '#antiquated', 'aria-expanded' => 'true', 'aria-controls' => 'antiquated'}
Older conferences
Past Conferences
%span.notranslate
= "(#{@antiquated.count})"
%i.fa.fa-chevron-right
%i.fa.fa-chevron-down{ style: 'display: none' }
#antiquated.collapse
%i.fa.fa-chevron-right{ style: 'display: none' }
%i.fa.fa-chevron-down
#antiquated
- @antiquated.each do |conference|
= render '/conferences/conference_details', conference: conference
- if @antiquated.empty? && @current.empty? && User.empty?
= render partial: 'new_install'
%p
Add the events to your calendar:
%span.btn-group
= link_to("Days only", calendar_url(protocol: 'webcal', format: 'ics'), class: 'btn btn-default')
= link_to("Detailed", calendar_url(protocol: 'webcal', format: 'ics', full: true), class: 'btn btn-default')
-content_for :script_body do
:javascript

View file

@ -17,5 +17,5 @@
will the be administrator of it.
%p
We hope you enjoy using OSEM, if you have any question don't hesitate to
= link_to('http://osem.io/#contact') do
= link_to('https://osem.io/#contact') do
contact us!

View file

@ -23,8 +23,8 @@
%li
= link_to(conference_conference_registration_path(@conference)) do
%span.fa.fa-id-badge
= @conference.short_title
registration
= @conference.short_title
registration
-if @conference && @conference.program
%li
= link_to(conference_program_proposals_path(@conference.short_title)) do

View file

@ -9,9 +9,9 @@
- if @conference.venue
at
%strong
#{@conference.venue.name},
#{@conference.venue.street},
#{@conference.venue.city} / #{@conference.venue.country_name}.
#{@conference.venue_name},
#{@conference.venue_street},
#{@conference.city} / #{@conference.country_name}.
%small
= date_string(@conference.start_date, @conference.end_date)
.row

View file

@ -13,7 +13,7 @@
= f.input :event_type_id, as: :select,
collection: @conference.program.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 }]},
data: { min_words: type.minimum_abstract_length, max_words: type.maximum_abstract_length, help: type.description }]},
include_blank: false, label: 'Type', input_html: { class: 'select-help-toggle' }
- if @program.languages.present?
@ -46,7 +46,30 @@
250
words.
- if current_user.is_admin? or @program.cfp.enable_registrations?
%br
- @conference.program.event_types.each do |event_type|
%span{ class: 'help-block select-help-text event_event_type_id collapse', id: "#{event_type.id}-help" }
%h3
= event_type.name
Instructions
= markdown(event_type.submission_instructions)
= f.input :submission_text, input_html: { rows: 5, data: { provide: 'markdown' }, placeholder: '' },
hint: markdown_hint('Only conference organizers will read this.')
%button.btn.btn-primary.primary-button{ type: 'button', id: 'sub_text_reset', data: { confirm: 'Do you really want to reset your submission text to the provided template?' } } Reset to Template
%p
You have used
%span#submission-count #{@event.submission_word_count}
words. Submission descriptions must be between
%span#submission-minimum-word-count
0
and
%span#submission-maximum-word-count
250
words.
- if current_user.is_admin? or @program.cfp&.enable_registrations?
= f.inputs 'Enable pre-registration' do
= f.input :require_registration, label: 'Require participants to register to your event'
- message = @event.room ? "Value must be between 1 and #{@event.room.size}" : 'Check room capacity after scheduling.'

View file

@ -33,20 +33,20 @@
= f.input :title, as: :string, required: true, input_html: { required: true }
= f.input :event_type_id, as: :select,
collection: @program.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 }]},
data: { min_words: type.minimum_abstract_length, max_words: type.maximum_abstract_length, help: type.description }]},
include_blank: false, label: 'Type', input_html: { class: 'select-help-toggle' }
- if @program.languages.present?
= f.input :language, as: :select,
collection: @languages,
include_blank: false, label: 'Language', input_html: { class: 'select-help-toggle' }
- @program.event_types.each do |event_type|
%div{ class: 'help-block event_event_type_id collapse', id: "#{event_type.id}-help"}
%strong Directions
%div
= markdown(event_type.description)
- if @program.languages.present?
= f.input :language, as: :select,
collection: @languages,
include_blank: false, label: 'Language', input_html: { class: 'select-help-toggle' }
= f.input :abstract, required: true, input_html: { rows: 5, data: { provide: 'markdown' } },
hint: markdown_hint
@ -61,6 +61,23 @@
250
words.
%br
- @conference.program.event_types.each do |event_type|
%span{ class: 'help-block select-help-text event_event_type_id collapse', id: "#{event_type.id}-help" }
%h3
= event_type.name
Instructions
%p Please use this as the template for your submission.
This part of the submission is intended only for the conference committee.
%hr
= markdown(event_type.submission_instructions)
= f.input :submission_text, input_html: { rows: 5, data: { provide: 'markdown' } },
hint: markdown_hint
%button.btn.btn-primary.primary-button{ type: 'button', id: 'sub_text_reset', data: { confirm: 'Do you really want to reset your submission text to the provided template?' } } Reset to Template
- if @program.cfp.enable_registrations?
= f.input :require_registration, label: 'Require participants to register to your event'

View file

@ -26,9 +26,13 @@
.visible-md-inline.visible-lg-inline
= render partial: 'carousel', locals: { date: date, hrs_per_slide: 3 }
%p.pull-right
= link_to app_conference_schedule_path do
Get the mobile app!
%p
%span
= link_to conference_schedule_url(protocol: 'webcal', format: 'ics') do
Add the schedule to your calendar
%span.pull-right
= link_to app_conference_schedule_path do
Get the mobile app!
:javascript
// change of active tab and the button title when a date is clicked