diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 7b80c2d84..93b401d76 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -46,6 +46,7 @@ linters: - "app/views/admin/events/index.html.haml" - "app/views/admin/tracks/index.html.haml" - "app/views/admin/tracks/show.html.haml" + - "app/views/admin/registrations/index.html.haml" - "app/views/admin/versions/_object_desc_and_link.html.haml" - "app/views/conference_registrations/show.html.haml" - "app/views/layouts/_admin_sidebar.html.haml" diff --git a/Gemfile.lock b/Gemfile.lock index 325221c9e..d874769dd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,47 +11,47 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (1.1.0) - actioncable (7.0.8.1) - actionpack (= 7.0.8.1) - activesupport (= 7.0.8.1) + actioncable (7.0.8.4) + actionpack (= 7.0.8.4) + activesupport (= 7.0.8.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.1) - actionpack (= 7.0.8.1) - activejob (= 7.0.8.1) - activerecord (= 7.0.8.1) - activestorage (= 7.0.8.1) - activesupport (= 7.0.8.1) + actionmailbox (7.0.8.4) + actionpack (= 7.0.8.4) + activejob (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8.1) - actionpack (= 7.0.8.1) - actionview (= 7.0.8.1) - activejob (= 7.0.8.1) - activesupport (= 7.0.8.1) + actionmailer (7.0.8.4) + actionpack (= 7.0.8.4) + actionview (= 7.0.8.4) + activejob (= 7.0.8.4) + activesupport (= 7.0.8.4) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.8.1) - actionview (= 7.0.8.1) - activesupport (= 7.0.8.1) + actionpack (7.0.8.4) + actionview (= 7.0.8.4) + activesupport (= 7.0.8.4) rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.1) - actionpack (= 7.0.8.1) - activerecord (= 7.0.8.1) - activestorage (= 7.0.8.1) - activesupport (= 7.0.8.1) + actiontext (7.0.8.4) + actionpack (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.1) - activesupport (= 7.0.8.1) + actionview (7.0.8.4) + activesupport (= 7.0.8.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -61,22 +61,22 @@ GEM activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (7.0.8.1) - activesupport (= 7.0.8.1) + activejob (7.0.8.4) + activesupport (= 7.0.8.4) globalid (>= 0.3.6) - activemodel (7.0.8.1) - activesupport (= 7.0.8.1) - activerecord (7.0.8.1) - activemodel (= 7.0.8.1) - activesupport (= 7.0.8.1) - activestorage (7.0.8.1) - actionpack (= 7.0.8.1) - activejob (= 7.0.8.1) - activerecord (= 7.0.8.1) - activesupport (= 7.0.8.1) + activemodel (7.0.8.4) + activesupport (= 7.0.8.4) + activerecord (7.0.8.4) + activemodel (= 7.0.8.4) + activesupport (= 7.0.8.4) + activestorage (7.0.8.4) + actionpack (= 7.0.8.4) + activejob (= 7.0.8.4) + activerecord (= 7.0.8.4) + activesupport (= 7.0.8.4) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.8.1) + activesupport (7.0.8.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -113,7 +113,7 @@ GEM bootstrap-switch-rails (3.3.3) bootstrap3-datetimepicker-rails (4.17.47) momentjs-rails (>= 2.8.1) - builder (3.2.4) + builder (3.3.0) bullet (7.0.2) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) @@ -158,7 +158,7 @@ GEM rest-client (>= 2.0.0) cocoon (1.2.15) colorize (1.1.0) - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.3) countable-rails (0.0.1) railties (>= 3.1) crack (1.0.0) @@ -235,7 +235,7 @@ GEM dotenv-rails (2.8.1) dotenv (= 2.8.1) railties (>= 3.2) - erubi (1.12.0) + erubi (1.13.0) execjs (2.9.1) factory_bot (6.4.6) activesupport (>= 5.0.0) @@ -290,7 +290,7 @@ GEM httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.14.4) + i18n (1.14.5) concurrent-ruby (~> 1.0) i18n_data (0.17.1) simple_po_parser (~> 1.1) @@ -349,7 +349,7 @@ GEM rake mini_magick (4.12.0) mini_mime (1.1.5) - minitest (5.22.3) + minitest (5.24.1) momentjs-rails (2.29.4.1) railties (>= 3.1) monetize (1.13.0) @@ -379,11 +379,11 @@ GEM next_rails (1.3.0) colorize (>= 0.8.1) nio4r (2.7.0) - nokogiri (1.16.3-arm64-darwin) + nokogiri (1.16.6-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.3-x86_64-darwin) + nokogiri (1.16.6-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.3-x86_64-linux) + nokogiri (1.16.6-x86_64-linux) racc (~> 1.4) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) @@ -466,7 +466,7 @@ GEM public_suffix (5.0.4) puma (6.4.2) nio4r (~> 2.0) - racc (1.7.3) + racc (1.8.0) rack (2.2.9) rack-openid (1.4.2) rack (>= 1.1.0) @@ -475,20 +475,20 @@ GEM rack rack-test (2.1.0) rack (>= 1.3) - rails (7.0.8.1) - actioncable (= 7.0.8.1) - actionmailbox (= 7.0.8.1) - actionmailer (= 7.0.8.1) - actionpack (= 7.0.8.1) - actiontext (= 7.0.8.1) - actionview (= 7.0.8.1) - activejob (= 7.0.8.1) - activemodel (= 7.0.8.1) - activerecord (= 7.0.8.1) - activestorage (= 7.0.8.1) - activesupport (= 7.0.8.1) + rails (7.0.8.4) + actioncable (= 7.0.8.4) + actionmailbox (= 7.0.8.4) + actionmailer (= 7.0.8.4) + actionpack (= 7.0.8.4) + actiontext (= 7.0.8.4) + actionview (= 7.0.8.4) + activejob (= 7.0.8.4) + activemodel (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) bundler (>= 1.15.0) - railties (= 7.0.8.1) + railties (= 7.0.8.4) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -503,9 +503,9 @@ GEM rails-i18n (7.0.8) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.0.8.1) - actionpack (= 7.0.8.1) - activesupport (= 7.0.8.1) + railties (7.0.8.4) + actionpack (= 7.0.8.4) + activesupport (= 7.0.8.4) method_source rake (>= 12.2) thor (~> 1.0) @@ -526,7 +526,8 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.2.6) + rexml (3.3.2) + strscan rolify (6.0.1) rqrcode (2.2.0) chunky_png (~> 1.0) @@ -646,6 +647,7 @@ GEM dante (>= 0.2.0) multi_json (~> 1.0) stripe (> 5, < 6) + strscan (3.1.0) sys-uname (1.2.2) ffi (~> 1.1) sysexits (1.2.0) diff --git a/app/assets/javascripts/osem-schedule.js b/app/assets/javascripts/osem-schedule.js index 45df23908..b38ae2cc8 100644 --- a/app/assets/javascripts/osem-schedule.js +++ b/app/assets/javascripts/osem-schedule.js @@ -15,10 +15,10 @@ var Schedule = { schedule_id = schedule_id_param; }, remove: function(element) { - var e = $("#" + element); - var event_schedule_id = e.attr("event_schedule_id"); - if(event_schedule_id != null){ - var my_url = url + '/' + event_schedule_id; + var e = $("#" + element); + var event_schedule_id = parseInt(e.attr("event_schedule_id")); + if(event_schedule_id > 0) { + var my_url = `${url}/${event_schedule_id}`; var success_callback = function(data) { e.attr("event_schedule_id", null); e.appendTo($(".unscheduled-events")); @@ -41,18 +41,18 @@ var Schedule = { }, add: function (previous_parent, new_parent, event) { event.appendTo(new_parent); - var event_schedule_id = event.attr("event_schedule_id"); + // Event Schedule Id could be an empty string. + var event_schedule_id = parseInt(event.attr("event_schedule_id")); var my_url = url; var type = 'POST'; var params = { event_schedule: { room_id: new_parent.attr("room_id"), start_time: (new_parent.attr("date") + ' ' + new_parent.attr("hour")) }}; - if(event_schedule_id != null){ + if (event_schedule_id > 0) { type = 'PUT'; - my_url += ('/' + event_schedule_id); - } - else{ + my_url += `/${event_schedule_id}`; + } else { params['event_schedule']['event_id'] = event.attr("event_id"); params['event_schedule']['schedule_id'] = schedule_id; } @@ -80,6 +80,32 @@ $(document).ready( function() { $('.unscheduled-events .schedule-event-delete-button').hide(); $('.non_schedulable .schedule-event-delete-button').hide(); + $('#current-event-btn').on('click', function() { + var now = new Date(); + var closestEvent = null; + var smallestDiff = Infinity; + + $('.event-item').each(function() { + let $event = $(this), eventTimeStr = $event.data('time'); + + if (!eventTimeStr) { return; } + + var eventTime = new Date(eventTimeStr); + var diff = Math.abs(eventTime - now); + if (diff < smallestDiff) { + smallestDiff = diff; + closestEvent = $event; + } + }); + + if (closestEvent) { + // Instead of relying on hash it's probably better to scroll using javascript + // Since the users and click button->scroll->click again, which won't re-scroll + $('.highlighted').removeClass('highlighted'); + $(closestEvent).addClass('highlighted').get(0).scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }); + // set events as draggable $('.schedule-event').not('.non_schedulable').draggable({ snap: '.schedule-room-slot', diff --git a/app/assets/javascripts/osem-switch.js b/app/assets/javascripts/osem-switch.js index 5e8c47921..b4517c1cb 100644 --- a/app/assets/javascripts/osem-switch.js +++ b/app/assets/javascripts/osem-switch.js @@ -1,7 +1,5 @@ function checkboxSwitch(selector){ - $(selector).bootstrapSwitch( - - ); + $(selector).bootstrapSwitch(); $(selector).on('switchChange.bootstrapSwitch', function(event, state) { var url = $(this).attr('url') + state; diff --git a/app/assets/stylesheets/osem-schedule.scss b/app/assets/stylesheets/osem-schedule.scss index f722a41e9..6a1fcc52c 100644 --- a/app/assets/stylesheets/osem-schedule.scss +++ b/app/assets/stylesheets/osem-schedule.scss @@ -185,3 +185,10 @@ h3.event-panel-title small { line-height: 1.4; } + +#current-event-btn { + position: fixed; + bottom: 40px; + right: 40px; + z-index: 1000; +} diff --git a/app/controllers/admin/conferences_controller.rb b/app/controllers/admin/conferences_controller.rb index fc149b54f..17d2bc131 100644 --- a/app/controllers/admin/conferences_controller.rb +++ b/app/controllers/admin/conferences_controller.rb @@ -181,7 +181,7 @@ def conference_params :vpositions_attributes, :use_volunteers, :color, :sponsorship_levels_attributes, :sponsors_attributes, :registration_limit, :organization_id, :ticket_layout, - :booth_limit, :custom_css) + :booth_limit, :custom_css, :registered_attendees_message) end end end diff --git a/app/controllers/admin/event_types_controller.rb b/app/controllers/admin/event_types_controller.rb index 0cd2eed20..f0d7656d9 100644 --- a/app/controllers/admin/event_types_controller.rb +++ b/app/controllers/admin/event_types_controller.rb @@ -50,7 +50,7 @@ def destroy def event_type_params params.require(:event_type).permit(:title, :length, :minimum_abstract_length, :maximum_abstract_length, - :submission_template, :color, :conference_id, :description) + :submission_template, :color, :conference_id, :description, :enable_public_submission) end end end diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb index adf1bde7a..939b4d967 100644 --- a/app/controllers/admin/events_controller.rb +++ b/app/controllers/admin/events_controller.rb @@ -182,6 +182,17 @@ def toggle_attendance end end + def destroy + @event = Event.find(params[:id]) + if @event.destroy + flash[:notice] = 'Event successfully deleted.' + redirect_to admin_conference_program_events_path(@conference.short_title) + else + flash[:alert] = 'Event could not be deleted.' + redirect_to admin_conference_program_event_path(@conference.short_title, @event) + end + end + private def event_params diff --git a/app/controllers/admin/schedules_controller.rb b/app/controllers/admin/schedules_controller.rb index f3176245c..6e716caaf 100644 --- a/app/controllers/admin/schedules_controller.rb +++ b/app/controllers/admin/schedules_controller.rb @@ -4,6 +4,7 @@ module Admin class SchedulesController < Admin::BaseController # By authorizing 'conference' resource, we can ensure there will be no unauthorized access to # the schedule of a conference, which should not be accessed in the first place + before_action :set_conference load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource :schedule, through: :program, except: %i[new create] @@ -68,8 +69,63 @@ def destroy end end + def upload_csv + authorize! :update, @conference + return flash[:alert] = 'No file was attached!' unless file_present? + + if process_csv + flash[:notice] = 'Schedule uploaded successfully!' + else + flash[:alert] = 'Failed to process CSV file.' + end + + redirect_to admin_conference_schedules_path(@conference) + end + private + def set_conference + @conference = Conference.find_by!(short_title: params[:conference_id]) + end + + def file_present? + params[:schedule] && params[:schedule][:file].present? + end + + def process_csv + file = params[:schedule][:file] + CSV.foreach(file.path, headers: true) do |row| + process_row(row) + end + true + rescue StandardError => e + Rails.logger.error "CSV Processing Error: #{e.message}" + false + end + + def process_row(row) + event_date = parse_date(row['Date']) + event_time = parse_time(row['Start_Time']) + event_start_time = combine_datetime(event_date, event_time) + + room = Room.find_or_create_by(name: row['Room']) + event = Event.find_by(id: row['Event_ID']) + + event&.update(start_time: event_start_time, room: room) + end + + def parse_date(date_str) + Date.strptime(date_str, '%m/%d/%y') + end + + def parse_time(time_str) + Time.parse(time_str) + end + + def combine_datetime(date, time) + DateTime.new(date.year, date.month, date.day, time.hour, time.min, time.sec, time.zone) + end + def schedule_params params.require(:schedule).permit(:track_id) if params[:schedule] end diff --git a/app/controllers/admin/splashpages_controller.rb b/app/controllers/admin/splashpages_controller.rb index 7e50bb22b..56ead72df 100644 --- a/app/controllers/admin/splashpages_controller.rb +++ b/app/controllers/admin/splashpages_controller.rb @@ -51,7 +51,8 @@ def splashpage_params :include_venue, :include_registrations, :include_tickets, :include_lodgings, :include_sponsors, :include_social_media, - :include_booths, :include_happening_now) + :include_booths, :include_happening_now, + :include_committee) end end end diff --git a/app/controllers/admin/venue_commercials_controller.rb b/app/controllers/admin/venue_commercials_controller.rb index 11a87d199..753817fd0 100644 --- a/app/controllers/admin/venue_commercials_controller.rb +++ b/app/controllers/admin/venue_commercials_controller.rb @@ -38,7 +38,7 @@ def destroy end def render_commercial - result = Commercial.render_from_url(params[:url]) + result = Commercial.render_from_url(params[:url], params[:title]) if result[:error] render plain: result[:error], status: :bad_request else diff --git a/app/controllers/conference_registrations_controller.rb b/app/controllers/conference_registrations_controller.rb index 8df31ea05..b20a0fe84 100644 --- a/app/controllers/conference_registrations_controller.rb +++ b/app/controllers/conference_registrations_controller.rb @@ -29,11 +29,16 @@ def new end def show - @total_price = Ticket.total_price_user(@conference, current_user, paid: true) - @tickets = current_user.ticket_purchases.by_conference(@conference).paid - @total_price_per_ticket = @tickets.group(:ticket_id).sum('amount_paid * quantity') - @ticket_payments = @tickets.group_by(&:ticket_id) - @total_quantity = @tickets.group(:ticket_id).sum(:quantity) + @purchases = current_user.ticket_purchases.by_conference(@conference).paid + summed_per_ticket_per_currency = @purchases.group(:ticket_id, :currency).sum('amount_paid_cents * quantity') + @total_price_per_ticket_per_currency = summed_per_ticket_per_currency.each_with_object({}) do |((ticket_id, currency), amount), hash| + hash[[ticket_id, currency]] = Money.new(amount, currency) + end + @total_quantity = @purchases.group(:ticket_id, :currency).sum(:quantity) + sum_total_currency = @purchases.group(:currency).sum('amount_paid_cents * quantity') + @total_price_per_currency = sum_total_currency.each_with_object({}) do |(currency, amount), hash| + hash[currency] = Money.new(amount, currency) + end end def edit; end diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 3eb6fd6f8..492516829 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -14,6 +14,7 @@ def index end # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity def show # load conference with header content @conference = Conference.unscoped.eager_load( @@ -32,6 +33,7 @@ def show # User messages at the top of the page. @unpaid_tickets = current_user_has_unpaid_tickets? @user_needs_to_register = current_user_needs_to_register? + @user_registered = current_user_registered? @image_url = @splashpage.banner_photo_url || @conference.picture_url @@ -65,8 +67,12 @@ def show ).order('sponsorship_levels.position ASC', 'sponsors.name') @sponsors = @conference.sponsors end + if @splashpage.include_committee? + @organizers = User.with_role(:organizer, @conference) + end end # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity def calendar respond_to do |format| @@ -136,6 +142,10 @@ def current_user_needs_to_register? current_user_tickets.where(ticket: @conference.registration_tickets).paid.any? end + def current_user_registered? + current_user && @conference.user_registered?(current_user) + end + def current_user_has_unpaid_tickets? current_user && current_user_tickets.unpaid.any? end diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb index 94d8d9859..17a4b94b2 100644 --- a/app/controllers/payments_controller.rb +++ b/app/controllers/payments_controller.rb @@ -23,15 +23,14 @@ def new @has_registration_ticket = params[:has_registration_ticket] @unpaid_ticket_purchases = current_user.ticket_purchases.unpaid.by_conference(@conference) - @converted_prices = {} - @unpaid_ticket_purchases.each do |ticket_purchase| - @converted_prices[ticket_purchase.id] = ticket_purchase.amount_paid - end @currency = selected_currency end def create @payment = Payment.new payment_params + session[:selected_currency] = params[:currency] if params[:currency].present? + selected_currency = session[:selected_currency] || @conference.tickets.first.price_currency + from_currency = @conference.tickets.first.price_currency if @payment.purchase && @payment.save update_purchased_ticket_purchases @@ -51,7 +50,8 @@ def create notice: 'Thanks! Your ticket is booked successfully.' end else - @total_amount_to_pay = convert_currency(@conference, Ticket.total_price(@conference, current_user, paid: false), from_currency, selected_currency) + # TODO-SNAPCON: This case is not tested at all + @total_amount_to_pay = CurrencyConversion.convert_currency(@conference, Ticket.total_price(@conference, current_user, paid: false), from_currency, selected_currency) @unpaid_ticket_purchases = current_user.ticket_purchases.unpaid.by_conference(@conference) flash.now[:error] = @payment.errors.full_messages.to_sentence + ' Please try again with correct credentials.' render :new diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 48d62c817..2c3281e9a 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -41,6 +41,7 @@ def edit def create @url = conference_program_proposals_path(@conference.short_title) + @superevents = @program.events.where(superevent: true) # We allow proposal submission and sign up on same page. # If user is not signed in then first create new user and then sign them in @@ -80,6 +81,7 @@ def create def update @url = conference_program_proposal_path(@conference.short_title, params[:id]) + @superevents = @program.events.where(superevent: true) track = Track.find_by(id: params[:event][:track_id]) if track && !track.cfp_active diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index d661a9b3e..5d29ef60d 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -2,6 +2,7 @@ class UsersController < ApplicationController before_action :authenticate_user!, only: :search + before_action :set_currency_options, only: [:edit, :new, :create, :update] load_and_authorize_resource # GET /users/1 @@ -40,7 +41,7 @@ def search def user_params params[:user][:timezone] = params[:user][:timezone].presence || nil - params.require(:user).permit(:name, :biography, :nickname, :affiliation, + params.require(:user).permit(:name, :biography, :nickname, :affiliation, :default_currency, :picture, :picture_cache, :timezone) end @@ -49,5 +50,9 @@ def user_params def load_user @user ||= (params[:id] && params[:id] != 'current' && User.find(params[:id])) || current_user end + # rubocop:enable Naming/MemoizedInstanceVariableName + def set_currency_options + @currency_options = CurrencyConversion::VALID_CURRENCIES.map { |currency| [currency, currency] } + end end diff --git a/app/datatables/registration_datatable.rb b/app/datatables/registration_datatable.rb index 1639ef422..d8969fff8 100644 --- a/app/datatables/registration_datatable.rb +++ b/app/datatables/registration_datatable.rb @@ -5,6 +5,8 @@ class RegistrationDatatable < AjaxDatatablesRails::ActiveRecord def_delegator :@view, :dom_id def_delegator :@view, :edit_admin_conference_registration_path + # def_delegator :@view, :delete_admin_conference_registration_path + def_delegator :@view, :admin_conference_registration_toggle_attendance_path def initialize(params, opts = {}) @view = opts[:view_context] @@ -17,6 +19,8 @@ def view_columns name: { source: 'User.name' }, email: { source: 'User.email' }, accepted_code_of_conduct: { source: 'Registration.accepted_code_of_conduct', searchable: false }, + attended: { source: 'Registration.attended', searchable: false }, + ticket_price: { source: 'TicketPurchase.amount_paid' }, ticket_type: { source: 'Ticket.title' }, actions: { source: 'Registration.id', searchable: false, orderable: false } } @@ -34,6 +38,14 @@ def conference_role_titles(record) end.compact end + def registration_ticket(record) + record.user.tickets.for_registration(conference) + end + + def registration_ticket_price(record) + record.user.ticket_purchases.where(ticket: registration_ticket(record)).first.amount_paid + end + def data records.map do |record| { @@ -42,7 +54,9 @@ def data roles: conference_role_titles(record.user), email: record.email, accepted_code_of_conduct: !!record.accepted_code_of_conduct, # rubocop:disable Style/DoubleNegation - ticket_type: record.user.tickets.where(conference: conference).pluck(:title), + ticket_type: registration_ticket(record).title, + ticket_price: registration_ticket_price(record), + attended: record.attended?, edit_url: edit_admin_conference_registration_path(conference, record), DT_RowId: dom_id(record) } @@ -50,7 +64,7 @@ def data end def get_raw_records # rubocop:disable Naming/AccessorMethodName - conference.registrations.includes(user: %i[roles tickets]).references(:users, :roles).distinct + conference.registrations.includes(user: %i[roles tickets ticket_purchases]).references(:users, :roles).distinct end # override upstream santitation, which converts everything to strings diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 6edc74c84..492c98a7f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -122,8 +122,12 @@ def volunteer_links(event) end, ', ') end - def event_types_sentence(conference) - conference.event_types.map { |et| et.title.pluralize }.to_sentence + def event_types_sentence(conference, is_admin) + if is_admin + conference.event_types.map { |et| et.title.pluralize }.to_sentence + else + conference.event_types.available_for_public.map { |et| et.title.pluralize }.to_sentence + end end def sign_in_path diff --git a/app/helpers/conference_helper.rb b/app/helpers/conference_helper.rb index d393b6099..3a2c3a136 100644 --- a/app/helpers/conference_helper.rb +++ b/app/helpers/conference_helper.rb @@ -55,8 +55,9 @@ def icalendar_proposals(calendar, proposals, conference) 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}" + e.categories = conference.title + e.categories << "Difficulty: #{proposal.difficulty_level.title}" if proposal.difficulty_level.present? + e.categories << "Track: #{proposal.track.name}" if proposal.track.present? end end calendar diff --git a/app/helpers/event_types_helper.rb b/app/helpers/event_types_helper.rb index ca7c3e511..f1306cfec 100644 --- a/app/helpers/event_types_helper.rb +++ b/app/helpers/event_types_helper.rb @@ -13,9 +13,9 @@ def event_type_select_options(event_types = {}) "#{type.title} - #{show_time(type.length)}", type.id, { data: { - min_words: type.minimum_abstract_length, - max_words: type.maximum_abstract_length, - instructions: type.submission_template + min_words: type.minimum_abstract_length, + max_words: type.maximum_abstract_length, + template: type.submission_template } } ] end diff --git a/app/models/commercial.rb b/app/models/commercial.rb index 1fcc0a45a..b01da1aff 100644 --- a/app/models/commercial.rb +++ b/app/models/commercial.rb @@ -28,22 +28,30 @@ class Commercial < ApplicationRecord validate :valid_url - def self.render_from_url(url) + def self.render_from_url(url, title = nil) register_provider begin resource = OEmbed::Providers.get(url, maxwidth: 560, maxheight: 315) { html: resource.html.html_safe } rescue StandardError - { html: iframe_fallback(url) } + { html: EmbeddableURL.new(url, title).render_embed.html_safe } # { error: exception.message } end end - def self.iframe_fallback(url) - "".html_safe + # TODO: Is this necessary? + def self.iframe_fallback(url, title) + iframe = <<~HTML + + HTML + iframe.html_safe end def self.read_file(file) + require 'csv' errors = {} errors[:no_event] = [] errors[:validation_errors] = [] @@ -67,7 +75,8 @@ def self.read_file(file) commercial = event.commercials.new(title: title, url: url) unless commercial.save - errors[:validation_errors] << ("Could not create materials for event with ID #{event.id} (" + commercial.errors.full_messages.to_sentence + ')') + errors[:validation_errors] << + "Could not create materials for event with ID #{event.id} (#{commercial.errors.full_messages.to_sentence})" end end errors @@ -76,7 +85,9 @@ def self.read_file(file) private def valid_url - result = Commercial.render_from_url(url) + return unless url + + result = Commercial.render_from_url(url, title) errors.add(:base, result[:error]) if result[:error] end diff --git a/app/models/conference.rb b/app/models/conference.rb index 21717f7ef..20e1ce441 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -4,32 +4,33 @@ # # Table name: conferences # -# id :bigint not null, primary key -# booth_limit :integer default(0) -# color :string -# custom_css :text -# custom_domain :string -# description :text -# end_date :date not null -# end_hour :integer default(20) -# events_per_week :text -# guid :string not null -# logo_file_name :string -# picture :string -# registration_limit :integer default(0) -# revision :integer default(0), not null -# short_title :string not null -# start_date :date not null -# start_hour :integer default(9) -# ticket_layout :integer default("portrait") -# timezone :string not null -# title :string not null -# use_vdays :boolean default(FALSE) -# use_volunteers :boolean -# use_vpositions :boolean default(FALSE) -# created_at :datetime -# updated_at :datetime -# organization_id :integer +# id :bigint not null, primary key +# booth_limit :integer default(0) +# color :string +# custom_css :text +# custom_domain :string +# description :text +# end_date :date not null +# end_hour :integer default(20) +# events_per_week :text +# guid :string not null +# logo_file_name :string +# picture :string +# registered_attendees_message :text +# registration_limit :integer default(0) +# revision :integer default(0), not null +# short_title :string not null +# start_date :date not null +# start_hour :integer default(9) +# ticket_layout :integer default("portrait") +# timezone :string not null +# title :string not null +# use_vdays :boolean default(FALSE) +# use_volunteers :boolean +# use_vpositions :boolean default(FALSE) +# created_at :datetime +# updated_at :datetime +# organization_id :integer # # Indexes # diff --git a/app/models/currency_conversion.rb b/app/models/currency_conversion.rb index ffc2ca197..9d25094c6 100644 --- a/app/models/currency_conversion.rb +++ b/app/models/currency_conversion.rb @@ -24,6 +24,8 @@ class CurrencyConversion < ApplicationRecord VALID_CURRENCIES = %w[AUD CAD CHF CNY EUR GBP JPY USD].freeze belongs_to :conference validates :rate, numericality: { greater_than: 0 } + # Ensure from_currency and to_currency are among the VALID_CURRENCIES + validates :from_currency, :to_currency, inclusion: { in: VALID_CURRENCIES } validates :from_currency, uniqueness: { scope: :to_currency }, on: :create def self.convert_currency(conference, amount, from_currency, to_currency) diff --git a/app/models/event_type.rb b/app/models/event_type.rb index 0d2943dc9..9f2c3d72e 100644 --- a/app/models/event_type.rb +++ b/app/models/event_type.rb @@ -4,17 +4,18 @@ # # Table name: event_types # -# id :bigint not null, primary key -# color :string -# description :string -# length :integer default(30) -# maximum_abstract_length :integer default(500) -# minimum_abstract_length :integer default(0) -# submission_template :text -# title :string not null -# created_at :datetime -# updated_at :datetime -# program_id :integer +# id :bigint not null, primary key +# color :string +# description :string +# enable_public_submission :boolean default(TRUE), not null +# length :integer default(30) +# maximum_abstract_length :integer default(500) +# minimum_abstract_length :integer default(0) +# submission_template :text +# title :string not null +# created_at :datetime +# updated_at :datetime +# program_id :integer # class EventType < ApplicationRecord belongs_to :program, touch: true @@ -31,9 +32,12 @@ class EventType < ApplicationRecord validates :color, format: /\A#[0-9A-F]{6}\z/ before_validation :capitalize_color + before_validation :strip_title alias_attribute :name, :title + scope :available_for_public, -> { where(enable_public_submission: true) } + private ## @@ -53,4 +57,8 @@ def capitalize_color def conference_id program.conference_id end + + def strip_title + self.title = title.strip unless title.nil? + end end diff --git a/app/models/splashpage.rb b/app/models/splashpage.rb index 650e06575..9043f20c2 100644 --- a/app/models/splashpage.rb +++ b/app/models/splashpage.rb @@ -11,6 +11,7 @@ # banner_photo_updated_at :datetime # include_booths :boolean # include_cfp :boolean default(FALSE) +# include_committee :boolean # include_happening_now :boolean # include_lodgings :boolean # include_program :boolean diff --git a/app/models/ticket.rb b/app/models/ticket.rb index fd6efeace..f2c3997df 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -80,16 +80,11 @@ def self.total_price(conference, user, paid: false) result || Money.new(0, 'USD') end - def self.total_price_user(conference, user, paid: false) - tickets = TicketPurchase.where(conference: conference, user: user, paid: paid) - tickets.inject(0) { |sum, ticket| sum + (ticket.amount_paid * ticket.quantity) } - end - def tickets_turnover_total(id) ticket = Ticket.find(id) return Money.new(0, 'USD') unless ticket - sum = ticket.ticket_purchases.paid.total + sum = ticket.price_cents * ticket.ticket_purchases.paid.total_quantity Money.new(sum, ticket.price_currency) end diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index e76731dec..2577269e8 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -4,17 +4,18 @@ # # Table name: ticket_purchases # -# id :bigint not null, primary key -# amount_paid :float default(0.0) -# currency :string -# paid :boolean default(FALSE) -# quantity :integer default(1) -# week :integer -# created_at :datetime -# conference_id :integer -# payment_id :integer -# ticket_id :integer -# user_id :integer +# id :bigint not null, primary key +# amount_paid :float default(0.0) +# amount_paid_cents :integer default(0) +# currency :string +# paid :boolean default(FALSE) +# quantity :integer default(1) +# week :integer +# created_at :datetime +# conference_id :integer +# payment_id :integer +# ticket_id :integer +# user_id :integer # class TicketPurchase < ApplicationRecord @@ -30,12 +31,13 @@ class TicketPurchase < ApplicationRecord delegate :title, to: :ticket delegate :description, to: :ticket - delegate :price, to: :ticket delegate :price_cents, to: :ticket delegate :price_currency, to: :ticket has_many :physical_tickets + monetize :amount_paid_cents, with_model_currency: :currency, as: 'purchase_price' + scope :paid, -> { where(paid: true) } scope :unpaid, -> { where(paid: false) } scope :by_conference, ->(conference) { where(conference_id: conference.id) } @@ -71,12 +73,13 @@ def self.purchase_ticket(conference, quantity, ticket, user, currency) purchase.pay(nil) end if quantity > 0 - purchase = new(ticket_id: ticket.id, - conference_id: conference.id, - user_id: user.id, - quantity: quantity, - amount_paid: converted_amount, - currency: currency) + purchase = new(ticket_id: ticket.id, + conference_id: conference.id, + user_id: user.id, + quantity: quantity, + amount_paid: converted_amount.to_f, + amount_paid_cents: converted_amount.fractional, + currency: currency) purchase.pay(nil) if converted_amount.zero? end purchase @@ -97,6 +100,11 @@ def self.total sum('amount_paid * quantity') end + # Total quantity + def self.total_quantity + sum('quantity') + end + def pay(payment) update(paid: true, payment: payment) PhysicalTicket.transaction do diff --git a/app/models/user.rb b/app/models/user.rb index bcf38b4db..bfa75bb97 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,6 +16,7 @@ # confirmed_at :datetime # current_sign_in_at :datetime # current_sign_in_ip :string +# default_currency :string # email :string default(""), not null # email_public :boolean default(FALSE) # encrypted_password :string default(""), not null diff --git a/app/serializers/conference_serializer.rb b/app/serializers/conference_serializer.rb index 32d83dc71..08bce729a 100644 --- a/app/serializers/conference_serializer.rb +++ b/app/serializers/conference_serializer.rb @@ -4,32 +4,33 @@ # # Table name: conferences # -# id :bigint not null, primary key -# booth_limit :integer default(0) -# color :string -# custom_css :text -# custom_domain :string -# description :text -# end_date :date not null -# end_hour :integer default(20) -# events_per_week :text -# guid :string not null -# logo_file_name :string -# picture :string -# registration_limit :integer default(0) -# revision :integer default(0), not null -# short_title :string not null -# start_date :date not null -# start_hour :integer default(9) -# ticket_layout :integer default("portrait") -# timezone :string not null -# title :string not null -# use_vdays :boolean default(FALSE) -# use_volunteers :boolean -# use_vpositions :boolean default(FALSE) -# created_at :datetime -# updated_at :datetime -# organization_id :integer +# id :bigint not null, primary key +# booth_limit :integer default(0) +# color :string +# custom_css :text +# custom_domain :string +# description :text +# end_date :date not null +# end_hour :integer default(20) +# events_per_week :text +# guid :string not null +# logo_file_name :string +# picture :string +# registered_attendees_message :text +# registration_limit :integer default(0) +# revision :integer default(0), not null +# short_title :string not null +# start_date :date not null +# start_hour :integer default(9) +# ticket_layout :integer default("portrait") +# timezone :string not null +# title :string not null +# use_vdays :boolean default(FALSE) +# use_volunteers :boolean +# use_vpositions :boolean default(FALSE) +# created_at :datetime +# updated_at :datetime +# organization_id :integer # # Indexes # diff --git a/app/services/embeddable_url.rb b/app/services/embeddable_url.rb new file mode 100644 index 000000000..289a69208 --- /dev/null +++ b/app/services/embeddable_url.rb @@ -0,0 +1,99 @@ +# Transform a URL to a version that allows iframes + +class EmbeddableURL + attr_accessor :url, :title + + DEFAULT_FRAME_ATTRS = 'width=560 height=315 frameborder=0 allowfullscreen'.freeze + + TRANSFORMATIONS = { + /snap\.berkeley\.edu/ => :snap, + /docs\.google\.com/ => :google_docs, + /dropbox\.com/ => :dropbox + }.freeze + + def initialize(url, title) + # Do some normalizing so that URIs parse correctly. + if url + self.url = url.strip + end + self.title = title + end + + def render_embed + return render_dropbox if url.include?('dropbox.com') + + # TODO-A11Y: Set an iframe title + "" + end + + def iframe_url + TRANSFORMATIONS.each do |regex, fn| + return send(fn, url) if url.match?(regex) + end + url + end + + # TODO: Consider adjusting the id / loading if > 1 dropbox embed per page. + def render_dropbox + <<~HTML +
+ + +
+ HTML + end + + private + + def optional_params + return '' unless url.include?('snap.berkeley') + + 'allow="geolocation;microphone;camera"' + end + + def google_docs(url) + # replace /edit, /share, /comment with /embed and remove the querystring + url.gsub(%r{(/edit|/share|/comment).*}, '/embed') + end + + def dropbox(url) + # debugger + uri = URI.parse(url) + params = URI.decode_www_form(uri.query)&.to_h + params.delete('raw') + params['dl'] = '0' + # params['rlkey'] = params['rlkey'] + uri.query = params.to_query + uri.to_s + end + + def snap(url) + uri = URI.parse(url) + return url if uri.query.blank? + + args = URI.decode_www_form(uri.query).to_h + username = args['username'] || args['user'] + projectname = args['projectname'] || args['project'] + + return url if username.blank? || projectname.blank? + + query = URI.encode_www_form({ + 'projectname' => projectname, + 'username' => username, + 'showTitle' => 'true', + 'showAuthor' => 'true', + 'editButton' => 'true', + 'pauseButton' => 'true' + }) + URI::HTTPS.build(host: uri.host, path: '/embed', query: query).to_s + end + + def iframe_title + if title + "title='#{title} Embedded Media'" + else + 'title="Embedded Media for Presentation"' + end + end +end diff --git a/app/views/admin/cfps/_events_cfp.html.haml b/app/views/admin/cfps/_events_cfp.html.haml index 6e8066b4a..e04020cd4 100644 --- a/app/views/admin/cfps/_events_cfp.html.haml +++ b/app/views/admin/cfps/_events_cfp.html.haml @@ -21,7 +21,7 @@ %dt Event types: %dd - = event_types_sentence(conference) + = event_types_sentence(conference, true) %dt Tracks: %dd diff --git a/app/views/admin/conferences/_form_fields.html.haml b/app/views/admin/conferences/_form_fields.html.haml index f5705f455..c5439f1ac 100644 --- a/app/views/admin/conferences/_form_fields.html.haml +++ b/app/views/admin/conferences/_form_fields.html.haml @@ -23,6 +23,10 @@ = f.label :description = f.text_area :description, rows: 5, data: { provide: 'markdown' }, class: 'form-control' .help-block= markdown_hint('Splash page content') + .form-group + = f.label :registered_attendees_message, 'Message for Registered Attendees' + = f.text_area :registered_attendees_message, rows: 5, data: { provide: 'markdown' }, class: 'form-control' + .help-block= markdown_hint('Splash page content') .form-group = f.color_field :color, size: 6, class: 'form-control' %span.help-block @@ -46,52 +50,8 @@ = f.select :ticket_layout, Conference.ticket_layouts.keys, {}, class: 'form-control' %span.help-block Layout type for tickets of the conference. - -%h4 Scheduling -%hr -.form-group - = f.label :timezone - = f.time_zone_select :timezone, nil, { default: 'UTC' }, { class: 'form-control' } - %span.help-block - Please select in what time zone your conference will take place. -.form-group - = f.label :start_date, "Start Date" - %abbr{title: 'This field is required'} * - = f.text_field :start_date, id: 'conference-start-datepicker', required: true, class: 'form-control' -.form-group - = f.label :end_date, "End Date" - %abbr{title: 'This field is required'} * - = f.text_field :end_date, id: 'conference-end-datepicker', required: true, class: 'form-control' -- unless f.object.new_record? - .form-group - = f.label :start_hour - = f.number_field :start_hour, size: 2, min: 0, max: 23, class: 'form-control' - %span.help-block - = rescheduling_hint(@affected_event_count) - .form-group - = f.label :end_hour - = f.number_field :end_hour, size: 2, min: 1, max: 24, class: 'form-control' - %span.help-block - = rescheduling_hint(@affected_event_count) - %h4 Registrations - %hr - .form-group - = f.label :registration_limit - = f.number_field :registration_limit, in: 0..9999, class: 'form-control' - %span.help-block - Limit the number of registrations to the conference (0 no limit). Please note that the registration limit - does not apply to speakers of confirmed events, they will still be able to register even if the limit has been reached. - You currently have #{pluralize(@conference.registrations.count, 'registration')}. - %h4 - Booths - %hr - .form-group - = f.label :booth_limit - = f.number_field :booth_limit, in: 0..9999, class: 'form-control' - %span.help-block - #{(t'booth').capitalize} limit is the maximum number of #{(t'booth').pluralize} - that you can accept for this conference. By setting this number (0 no limit) you can be sure that you are not going to accept more #{(t'booth').pluralize} - than the conference can accommodate. You currently have #{pluralize(@conference.booths.accepted.count, "accepted #{t'booth'}")}. +=render partial: 'scheduling_form_fields', locals: {f: f} +=render partial: 'registration_and_booth_form_fields', locals: {f: f} %p.text-right - if f.object.new_record? = f.submit nil, { class: 'btn btn-success' } diff --git a/app/views/admin/conferences/_registration_and_booth_form_fields.html.haml b/app/views/admin/conferences/_registration_and_booth_form_fields.html.haml new file mode 100644 index 000000000..c7857cec6 --- /dev/null +++ b/app/views/admin/conferences/_registration_and_booth_form_fields.html.haml @@ -0,0 +1,19 @@ +- unless f.object.new_record? + %h4 Registrations + %hr + .form-group + = f.label :registration_limit + = f.number_field :registration_limit, in: 0..9999, class: 'form-control' + %span.help-block + Limit the number of registrations to the conference (0 no limit). Please note that the registration limit + does not apply to speakers of confirmed events, they will still be able to register even if the limit has been reached. + You currently have #{pluralize(@conference.registrations.count, 'registration')}. + %h4 Booths + %hr + .form-group + = f.label :booth_limit + = f.number_field :booth_limit, in: 0..9999, class: 'form-control' + %span.help-block + #{(t'booth').capitalize} limit is the maximum number of #{(t'booth').pluralize} + that you can accept for this conference. By setting this number (0 no limit) you can be sure that you are not going to accept more #{(t'booth').pluralize} + than the conference can accommodate. You currently have #{pluralize(@conference.booths.accepted.count, "accepted #{t'booth'}")}. diff --git a/app/views/admin/conferences/_scheduling_form_fields.html.haml b/app/views/admin/conferences/_scheduling_form_fields.html.haml new file mode 100644 index 000000000..65a2c48cb --- /dev/null +++ b/app/views/admin/conferences/_scheduling_form_fields.html.haml @@ -0,0 +1,26 @@ +%h4 Scheduling +%hr +.form-group + = f.label :timezone + = f.time_zone_select :timezone, nil, { default: 'UTC' }, { class: 'form-control' } + %span.help-block + Please select in what time zone your conference will take place. +.form-group + = f.label :start_date, "Start Date" + %abbr{title: 'This field is required'} * + = f.text_field :start_date, id: 'conference-start-datepicker', required: true, class: 'form-control' +.form-group + = f.label :end_date, "End Date" + %abbr{title: 'This field is required'} * + = f.text_field :end_date, id: 'conference-end-datepicker', required: true, class: 'form-control' +- unless f.object.new_record? + .form-group + = f.label :start_hour + = f.number_field :start_hour, size: 2, min: 0, max: 23, class: 'form-control' + %span.help-block + = rescheduling_hint(@affected_event_count) + .form-group + = f.label :end_hour + = f.number_field :end_hour, size: 2, min: 1, max: 24, class: 'form-control' + %span.help-block + = rescheduling_hint(@affected_event_count) diff --git a/app/views/admin/currency_conversions/index.html.haml b/app/views/admin/currency_conversions/index.html.haml index 23e435454..63350cd0e 100644 --- a/app/views/admin/currency_conversions/index.html.haml +++ b/app/views/admin/currency_conversions/index.html.haml @@ -4,9 +4,6 @@ %h1 Currency Conversions %p.text-muted Enter the currency conversions for this conference - %p.alert.alert-warning - %strong Warning: - Currency conversion feature has not been implemented yet. Do not add any currency conversions. .row .col-md-12 %table.table.table-hover#currency-conversions diff --git a/app/views/admin/event_types/_form.html.haml b/app/views/admin/event_types/_form.html.haml index d0a2739c1..234d50293 100644 --- a/app/views/admin/event_types/_form.html.haml +++ b/app/views/admin/event_types/_form.html.haml @@ -15,6 +15,9 @@ = f.label :description = f.text_area :description, class: 'form-control', rows: 5, data: { provide: 'markdown' } .help-block= markdown_hint + .form-group + = f.label "Allow public submission?", for: :enable_public_submission + = f.check_box :enable_public_submission, class: 'switch-checkbox' .form-group = f.label :minimum_abstract_length %abbr{title: 'This field is required'} * diff --git a/app/views/admin/event_types/index.html.haml b/app/views/admin/event_types/index.html.haml index 56411fb78..b4784b446 100644 --- a/app/views/admin/event_types/index.html.haml +++ b/app/views/admin/event_types/index.html.haml @@ -11,7 +11,8 @@ %tr %th Title %th Description - %th Instructions + %th Enable Public Submission + %th Template %th Length %th Abstract Length %th Color @@ -23,6 +24,8 @@ = event_type.title %td = markdown(event_type.description) + %td + = event_type.enable_public_submission ? 'Yes' : 'No' %td = markdown(event_type.submission_template) %td diff --git a/app/views/admin/events/_proposal.html.haml b/app/views/admin/events/_proposal.html.haml index ae50c63d2..0fc5aa459 100644 --- a/app/views/admin/events/_proposal.html.haml +++ b/app/views/admin/events/_proposal.html.haml @@ -11,6 +11,8 @@ = 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' + = link_to 'Delete', admin_conference_program_event_path(@conference.short_title, @event), method: :delete, data: { confirm: 'Are you sure you want to delete this event?' }, class: 'btn btn-mini btn-danger' + .row .col-md-12 diff --git a/app/views/admin/events/index.html.haml b/app/views/admin/events/index.html.haml index 6f68faee1..2d1ac6372 100644 --- a/app/views/admin/events/index.html.haml +++ b/app/views/admin/events/index.html.haml @@ -66,6 +66,7 @@ %th Rating %th Submitter %th Speakers + %th Volunteers - if @program.languages.present? %th Language %th Requires Registration diff --git a/app/views/admin/physical_tickets/_physical_ticket.html.haml b/app/views/admin/physical_tickets/_physical_ticket.html.haml index 241e88754..b9cc99a02 100644 --- a/app/views/admin/physical_tickets/_physical_ticket.html.haml +++ b/app/views/admin/physical_tickets/_physical_ticket.html.haml @@ -3,7 +3,8 @@ %td= physical_ticket.ticket.title %td= physical_ticket.ticket.registration_ticket? ? 'Yes' : 'No' %td= physical_ticket.user&.email - %td= humanized_money_with_symbol physical_ticket.ticket_purchase.amount_paid + %td= humanized_money_with_symbol(physical_ticket.ticket_purchase.purchase_price) + %td= physical_ticket.ticket_purchase.currency || 'USD' %td - if physical_ticket.ticket_scannings.present? %span Checked in: diff --git a/app/views/admin/physical_tickets/index.html.haml b/app/views/admin/physical_tickets/index.html.haml index 80835f901..96722dc98 100644 --- a/app/views/admin/physical_tickets/index.html.haml +++ b/app/views/admin/physical_tickets/index.html.haml @@ -24,6 +24,7 @@ %th Registration? %th User %th Paid + %th Currency %th Attedance %th Actions %tbody diff --git a/app/views/admin/programs/show.html.haml b/app/views/admin/programs/show.html.haml index db1e91ffb..f2c1949fa 100644 --- a/app/views/admin/programs/show.html.haml +++ b/app/views/admin/programs/show.html.haml @@ -22,7 +22,7 @@ %dt Event types: %dd - = event_types_sentence(@conference) + = event_types_sentence(@conference, true) %dt Tracks: %dd diff --git a/app/views/admin/registrations/index.html.haml b/app/views/admin/registrations/index.html.haml index 45f66b232..c756e8dd2 100644 --- a/app/views/admin/registrations/index.html.haml +++ b/app/views/admin/registrations/index.html.haml @@ -28,6 +28,8 @@ %th{ width: '25%' } Name %th{ width: '0' } E-Mail %th{ width: '0' } Ticket Type + %th{ width: '0' } Price + %th{ width: '0' } Attended %th{ width: '0' } %abbr{ title: 'Code of Conduct' } CoC %th{ width: '0' } Actions @@ -55,12 +57,12 @@ { "data": "name", "className": "truncate", - "render": function(data, type, row) { - var content = '' + data + '
'; - $.each(row.roles, function(index, role){ - content += ' ' + role + '' - }); - return content; + "render": (data, type, row) => { + return ` + ${data} +
+ ${row.roles.map(role => ` ${role}`)} + `; } }, { @@ -69,20 +71,42 @@ { "data": "ticket_type" }, + { + "data": "ticket_price", + "render": data => `$${data}` + }, + { + "data": "attended", + "search": data => data, + "render": (data, _type, row) => { + let js_url = "#{toggle_attendance_admin_conference_registration_path(@conference.short_title, id: 'ROW_ID')}"; + return ` + ${data} + + `; + } + }, { "data": "accepted_code_of_conduct", "className": "code-of-conduct text-center", "searchable": false, + "visible": codeOfConductPresent }, { "data": "actions", "className": "actions", "searchable": false, "sortable": false, - "render": function (data, type, row, meta) { - return '
'+ - 'Edit'+ - '
'; + "render": (data, type, row) => { + return `
+ Edit +
`; } } ] diff --git a/app/views/admin/schedules/show.html.haml b/app/views/admin/schedules/show.html.haml index 5777f4836..0b1d0f56a 100644 --- a/app/views/admin/schedules/show.html.haml +++ b/app/views/admin/schedules/show.html.haml @@ -4,10 +4,37 @@ .col-md-12 .page-header %h1= "#{@conference.title} Schedule" + + .btn-group.pull-right + %button.btn.btn-primary{ title: 'Mass import of schedule for events', + data: { toggle: 'modal', target: '#mass-schedule-modal' } } + Upload with CSV file + %p.text-muted Create the schedules for the conference = render 'schedules/event_types_key', event_types: @event_types, favourites: false +.modal#mass-schedule-modal + .modal-dialog + .modal-content + .modal-header + %h1 + Schedule events + .modal-body + = form_for :schedule, url: upload_csv_admin_conference_schedules_path(@conference), html: { multipart: true }, method: :post do |f| + .form-group + = f.file_field :file, as: :file + %span.help-block + Upload your CSV file with data in the following format: + %b Event_ID, Start_Time, Date, Room + for instance: + %pre + Event_ID, Start_Time, Date, Room + osemdemo, 10:00, 6/1/2024, Australia + osemdemo, 11:00, 6/2/2024, Brazil + .modal-footer + = f.submit 'Upload', class: 'btn btn-primary' + - if @rooms.present? .row .col-md-2 diff --git a/app/views/admin/splashpages/_form.html.haml b/app/views/admin/splashpages/_form.html.haml index 338ad4a2e..f494e9a05 100644 --- a/app/views/admin/splashpages/_form.html.haml +++ b/app/views/admin/splashpages/_form.html.haml @@ -55,6 +55,10 @@ %label = f.check_box :include_social_media Display the social media links? + .checkbox + %label + = f.check_box :include_committee + Display the committee? %h4 Access %hr diff --git a/app/views/admin/splashpages/show.html.haml b/app/views/admin/splashpages/show.html.haml index c6fa8a70d..97fd04ebb 100644 --- a/app/views/admin/splashpages/show.html.haml +++ b/app/views/admin/splashpages/show.html.haml @@ -56,6 +56,9 @@ %li %i{ class: "fa-li #{icon_for_todo @splashpage.include_social_media?}" } Display social media links + %li + %i{ class: "fa-li #{icon_for_todo @splashpage.include_committee?}" } + Display social media links %li - if @conference.splashpage && @conference.splashpage.public? %i{ class: "fa-li #{icon_for_todo @splashpage.public?}" } diff --git a/app/views/conference_registrations/show.html.haml b/app/views/conference_registrations/show.html.haml index 9d89752ab..88e4fac45 100644 --- a/app/views/conference_registrations/show.html.haml +++ b/app/views/conference_registrations/show.html.haml @@ -86,27 +86,27 @@ %i.fa-solid.fa-square-dashed.fa-stack-2x %i.fa-solid.fa-ticket.fa-stack-1x Tickets - - if @tickets.any? - Total Purchased: - = "(#{@tickets.first.price.symbol}#{humanized_money @total_price})" - %ul - .col-md-12 - - @ticket_payments.each_pair do |ticket_id, tickets| - %li - = @total_quantity[ticket_id] - = tickets.first.title - = word_pluralize(@total_quantity[ticket_id], 'Ticket') - for - = tickets.first.price.symbol - = humanized_money @total_price_per_ticket[ticket_id] - %br - .btn-group{ role: 'group' } - = link_to 'View all tickets', - conference_physical_tickets_path(@conference.short_title), - class: 'btn btn-success' - = link_to 'Get more tickets', - conference_tickets_path(@conference.short_title), - class: 'btn btn-default' + - if @purchases.any? + .col-md-12 + - @total_price_per_currency.each do |currency, total_price| + %h4 + Total Purchased (#{currency}): + = humanized_money_with_symbol(total_price) + %ul + - @total_price_per_ticket_per_currency.select { |(id, curr)| curr == currency }.each do |(ticket_id, curr), total_amount| + - purchase = @purchases.find { |p| p.ticket_id == ticket_id && p.currency == curr } + %li + - title = purchase.title.titleize.gsub(/(?i)\bticket\b/, 'Tickets') + - title = title.include?('Tickets') ? title : "#{title} Tickets" + = "#{@total_quantity[[ticket_id, currency]]} #{title} for " + = humanized_money_with_symbol(total_amount) + .btn-group{ role: 'group' } + = link_to 'View all tickets', + conference_physical_tickets_path(@conference.short_title), + class: 'btn btn-success' + = link_to 'Get more tickets', + conference_tickets_path(@conference.short_title), + class: 'btn btn-default' - else %p You haven't bought any tickets. @@ -125,7 +125,7 @@ - if @registration .btn-group-vertical.pull-right = link_to 'Edit your Registration', edit_conference_conference_registration_path(@conference.short_title), class: 'btn btn-success', disabled: @conference.end_date < Date.today - - if @tickets.any? + - if @purchases.any? = link_to 'Unregister', conference_conference_registration_path(@conference.short_title), method: :delete, class: 'btn btn-danger btn-xs', data: { confirm: "Your ticket purchases won't be refunded. Are you sure you want to unregister?" }, disabled: @conference.end_date < Date.today - else diff --git a/app/views/conferences/_about_and_happening_now.haml b/app/views/conferences/_about_and_happening_now.haml index 239cbbce4..05141967d 100644 --- a/app/views/conferences/_about_and_happening_now.haml +++ b/app/views/conferences/_about_and_happening_now.haml @@ -8,6 +8,8 @@ = content_for :about do #about + -if @user_registered && conference.registered_attendees_message.present? + = markdown(conference.registered_attendees_message, false) = markdown(conference.description, false) %section#about-and-happening-now diff --git a/app/views/conferences/_committee.haml b/app/views/conferences/_committee.haml new file mode 100644 index 000000000..5d8b66634 --- /dev/null +++ b/app/views/conferences/_committee.haml @@ -0,0 +1,18 @@ +-# _committee.haml +.row + .col-md-12 + %h3.text-center + Committee +.row + .col-sd-6 + .row.row-centered + - organizers.shuffle.each do |organizer| + .col-sd-3.col-sm-1.col-xs-3.col-centered + = link_to user_path(organizer), class: 'thumbnail', + style: 'border: none; background: none; padding: 20px;' do + - if organizer.profile_picture.present? + = image_tag organizer.profile_picture(size: 150), + class: ['img-responsive', 'img-circle'], + title: organizer.name + .caption + %h4.text-center= organizer.name diff --git a/app/views/conferences/show.html.haml b/app/views/conferences/show.html.haml index 29c5014e2..49aa07b7e 100644 --- a/app/views/conferences/show.html.haml +++ b/app/views/conferences/show.html.haml @@ -86,6 +86,10 @@ sponsorship_levels: @sponsorship_levels, sponsors: @sponsors + -# committee + - if @conference.splashpage.include_committee? + = render 'committee', organizers: @organizers + -# footer - if @conference.splashpage.include_social_media? && @conference.contact.has_social_media? = render 'social_media', contact: @conference.contact, cached: true diff --git a/app/views/payments/_payment.html.haml b/app/views/payments/_payment.html.haml index 27a85af5d..76544211e 100644 --- a/app/views/payments/_payment.html.haml +++ b/app/views/payments/_payment.html.haml @@ -15,9 +15,9 @@ %td = ticket.quantity %td - = humanized_money_with_symbol @converted_prices[ticket.id] + = humanized_money_with_symbol ticket.purchase_price %td - = humanized_money_with_symbol (@converted_prices[ticket.id] * ticket.quantity) + = humanized_money_with_symbol ticket.purchase_price * ticket.quantity = form_tag conference_payments_path(@conference.short_title, :has_registration_ticket => @has_registration_ticket) do %script.stripe-button{ src: "https://checkout.stripe.com/checkout.js", data: { amount: @total_amount_to_pay.cents, diff --git a/app/views/proposals/_encouragement_text.html.haml b/app/views/proposals/_encouragement_text.html.haml index f7f6033f0..8f831582f 100644 --- a/app/views/proposals/_encouragement_text.html.haml +++ b/app/views/proposals/_encouragement_text.html.haml @@ -1,7 +1,7 @@ %p.lead - if @program.event_types.any? You can submit proposals for - = "#{event_types_sentence(@conference)}." + = "#{event_types_sentence(@conference, current_user&.is_admin || false)}." - if @program.tracks.confirmed.cfp_active.any? Proposals should fit in one of the = "#{pluralize(@program.tracks.confirmed.cfp_active.count, 'track')}:" diff --git a/app/views/proposals/_submission_type_content_form.haml b/app/views/proposals/_submission_type_content_form.haml index 6a66fa5a1..76b915047 100644 --- a/app/views/proposals/_submission_type_content_form.haml +++ b/app/views/proposals/_submission_type_content_form.haml @@ -2,8 +2,9 @@ %p Please select a submission type, then fill in the abstract and extended details. .form-group - = f.label :event_type_id, 'Type' - = f.select :event_type_id, event_type_select_options(@conference.program.event_types), { include_blank: false }, { class: 'select-help-toggle form-control' } + = f.label :event_type_id, "Type" + - visible_event_types = current_user&.is_admin ? @conference.program.event_types : @conference.program.event_types.available_for_public + = f.select :event_type_id, event_type_select_options(visible_event_types), { include_blank: false }, { class: 'select-help-toggle form-control' } - program.event_types.each do |event_type| .help-block.event_event_type_id.collapse{ id: "#{dom_id(event_type)}-help" } diff --git a/app/views/schedules/_date_event_types.haml b/app/views/schedules/_date_event_types.haml index 2ba3cd4d2..c129e782e 100644 --- a/app/views/schedules/_date_event_types.haml +++ b/app/views/schedules/_date_event_types.haml @@ -5,6 +5,8 @@ The conference timezone is #{timezone_mapping(conference.timezone)}. - if current_user Visit your #{link_to('user profile page', edit_user_path(current_user.id))} to set your timezone. + - else + (#{link_to('Log in', new_user_session_path)} to view the schedule in your preferred timezone.) - if current_user %p.text-center Click the star next to each event to add or remove it from diff --git a/app/views/schedules/_event.html.haml b/app/views/schedules/_event.html.haml index 122d3e1e1..e3438bde0 100644 --- a/app/views/schedules/_event.html.haml +++ b/app/views/schedules/_event.html.haml @@ -14,7 +14,7 @@ %h3.event-panel-title = link_to conference_program_proposal_path(@conference.short_title, event.id), - style: color_style do + style: "#{color_style}; line-height: 1.7" do = event.title - if event.subtitle.present? %br diff --git a/app/views/schedules/_event_mini.html.haml b/app/views/schedules/_event_mini.html.haml index b0e29509c..27acb4a77 100644 --- a/app/views/schedules/_event_mini.html.haml +++ b/app/views/schedules/_event_mini.html.haml @@ -25,8 +25,8 @@ %span= " at #{new_start_time.strftime('%l:%M %P')}" = join_event_link(event, event_schedule, current_user, small: true) %p - = truncate(markdown(event.abstract), length: 250, escape: false) - - if event.subevents.present? + = markdown(truncate(event.abstract, length: 256)) + - if event.program_subevents.present? %ul - event.program_subevents.each do |subevent| %li= link_to(subevent.title, conference_program_proposal_path(@conference.short_title, subevent.id)) diff --git a/app/views/schedules/events.html.haml b/app/views/schedules/events.html.haml index 4c45a80b6..a6365171a 100644 --- a/app/views/schedules/events.html.haml +++ b/app/views/schedules/events.html.haml @@ -40,37 +40,34 @@ .row / scheduled events :ruby - date = nil - time = nil - tz_object = current_user&.timezone ? current_user : @conference + prev_date, prev_time = nil + display_tz = current_user&.timezone.presence || @conference.timezone / TODO-SNAPCON: Explore caching this. - - @events_schedules.each do |event_schedule| - - next if event_schedule.event.parent_event.present? - - new_start_time = convert_timezone(event_schedule.start_time, @conference.timezone, tz_object.timezone) - - start_ymd = new_start_time.strftime('%Y-%m-%d') - - unless start_ymd.eql?(date) - .col-xs-12.col-md-12 - .date-content - %span.date-title{ id: start_ymd } - = inyourtz(event_schedule.start_time, @conference.timezone) do - = date = start_ymd - %a.pull-right{ title: "Go up", href: "#program" } - %i.fa-solid.fa-angles-up.fa-lg{ 'aria-hidden': true } - .col-xs-12.col-md-1 - - if !event_schedule.start_time.strftime('%H:%M').eql?(time) || !start_ymd.eql?(date) - - time = new_start_time.strftime('%H:%M') - = inyourtz(event_schedule.start_time, @conference.timezone) do - = time + ' ' + timezone_text(tz_object) - .col-xs-12.col-md-11 - - cache [@program, event_schedule, event_schedule.event, current_user, event_schedule.happening_now?, '#scheduled#full#panel'] do - = render 'event', event: event_schedule.event, event_schedule: event_schedule + - @events_schedules.select { |es| es.event.parent_event.nil? }.each do |event_schedule| + - event = event_schedule.event + - start_time = convert_timezone(event_schedule.start_time, @conference.timezone, display_tz) + + - unless start_time.to_date.eql?(prev_date) + .date-content + %h2.date-title{ style: "margin: 0;" } + %a{ name: start_time.to_date } + = prev_date = start_time.to_date + %a.pull-right{ title: "Go up", href: "#program" } + %i.fa-solid.fa-angles-up.fa-lg{ 'aria-hidden': true } + - if !start_time.eql?(prev_time) + - prev_time = start_time + %h3= start_time.strftime('%H:%M %p %Z') + .col-12 + - cache [@program, event_schedule, event, current_user, event_schedule.happening_now?, '#scheduled#full#panel'] do + .event-item{ data: { time: event_schedule.start_time.iso8601 }, id: dom_id(event) } + = render 'event', event: event, event_schedule: event_schedule / confirmed events that are not scheduled - if @unscheduled_events.any? - .col-xs-12.col-md-12 + .col-12 .date-content - %span.date-title#unscheduled + %h2.date-title#unscheduled{ style: "margin: 0;" } Unscheduled events %a.pull-right{ title: "Go up", href: "#program" } %i.fa-solid.fa-angles-up.fa-lg{ 'aria-hidden': true } @@ -79,6 +76,8 @@ .unscheduled-event - cache [@program, event, current_user, '#unscheduled#full#panel'] do = render 'event', event: event, event_schedule: nil + %button.btn.btn-primary#current-event-btn{ type: "button" } + Jump to Current Event :javascript $('.program-selector').on('click', function(e) { diff --git a/app/views/shared/_media_item.html.haml b/app/views/shared/_media_item.html.haml index 367d04a2f..8e36eb4ad 100644 --- a/app/views/shared/_media_item.html.haml +++ b/app/views/shared/_media_item.html.haml @@ -15,7 +15,7 @@ - elsif commercial.commercial_type == 'YouTube' %iframe{width: '560', height: '315', frameborder: '0', allowfullscreen: 'true', src: "https://www.youtube.com/embed/#{commercial.commercial_id}?rel=0"} - elsif commercial.url - = Commercial.render_from_url(commercial.url)[:html] + = Commercial.render_from_url(commercial.url, commercial.title)[:html] - if commercial.url = link_to('Open in a new tab', commercial.url, target: '_blank', class: 'btn btn-info btn-xs') %br diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index d1b967620..aff35bd69 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -23,6 +23,9 @@ The timezone setting will update the event schedules to show using the time you selected. Your browser is current set to %span.js-localTimezone + .form-group + = f.label :default_currency, 'Default Currency' + = f.select :default_currency, options_for_select(@currency_options, selected: @user.default_currency), {}, { class: 'form-control' } .form-group = f.label :avatar %br diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index ac033bf9d..90ac51ca2 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -11,6 +11,6 @@ # end # These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym 'RESTful' -# end +ActiveSupport::Inflector.inflections(:en) do |inflect| + inflect.acronym 'URL' +end diff --git a/config/puma.rb b/config/puma.rb index 35d73834e..c401d6fc2 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -25,12 +25,13 @@ # processes). # workers ENV.fetch('WEB_CONCURRENCY') { 2 } +# Set a 10 minute timeout in development for debugging. +worker_timeout 60 * 60 * 10 if ENV.fetch('RAILS_ENV') == 'development' # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write # process behavior so workers use less memory. -# preload_app! lowlevel_error_handler do |ex, env| diff --git a/config/routes.rb b/config/routes.rb index 6e49e0666..62ac3dfdd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -40,6 +40,11 @@ resource :ticket_scanning, only: [:create] resources :comments, only: [:index] resources :conferences do + resources :schedules do + collection do + post :upload_csv + end + end resources :surveys do resources :survey_questions, except: :index end diff --git a/db/migrate/20240308190204_add_currency_to_payments.rb b/db/migrate/20230418211400_add_currency_to_payments.rb similarity index 100% rename from db/migrate/20240308190204_add_currency_to_payments.rb rename to db/migrate/20230418211400_add_currency_to_payments.rb diff --git a/db/migrate/20240315025823_add_currency_to_ticket_purchases.rb b/db/migrate/20230418211410_add_currency_to_ticket_purchases.rb similarity index 61% rename from db/migrate/20240315025823_add_currency_to_ticket_purchases.rb rename to db/migrate/20230418211410_add_currency_to_ticket_purchases.rb index 70e4f9d96..6fa9de448 100644 --- a/db/migrate/20240315025823_add_currency_to_ticket_purchases.rb +++ b/db/migrate/20230418211410_add_currency_to_ticket_purchases.rb @@ -1,5 +1,5 @@ class AddCurrencyToTicketPurchases < ActiveRecord::Migration[7.0] def change - add_column :ticket_purchases, :currency, :string + # add_column :ticket_purchases, :currency, :string end end diff --git a/db/migrate/20230418211400_create_currency_conversions.rb b/db/migrate/20230418211420_create_currency_conversions.rb similarity index 79% rename from db/migrate/20230418211400_create_currency_conversions.rb rename to db/migrate/20230418211420_create_currency_conversions.rb index 9c5100085..3403e779c 100644 --- a/db/migrate/20230418211400_create_currency_conversions.rb +++ b/db/migrate/20230418211420_create_currency_conversions.rb @@ -1,6 +1,6 @@ class CreateCurrencyConversions < ActiveRecord::Migration[7.0] def change - create_table :currency_conversions do |t| + create_table :currency_conversions, if_not_exists: true do |t| t.decimal :rate t.string :from_currency t.string :to_currency diff --git a/db/migrate/20240318164346_add_enable_public_submission_to_event_types.rb b/db/migrate/20240318164346_add_enable_public_submission_to_event_types.rb new file mode 100644 index 000000000..f653893c8 --- /dev/null +++ b/db/migrate/20240318164346_add_enable_public_submission_to_event_types.rb @@ -0,0 +1,5 @@ +class AddEnablePublicSubmissionToEventTypes < ActiveRecord::Migration[7.0] + def change + add_column :event_types, :enable_public_submission, :boolean, default: true, null: false + end +end diff --git a/db/migrate/20240414231910_add_include_committee_to_splashpage.rb b/db/migrate/20240414231910_add_include_committee_to_splashpage.rb new file mode 100644 index 000000000..87b637c12 --- /dev/null +++ b/db/migrate/20240414231910_add_include_committee_to_splashpage.rb @@ -0,0 +1,5 @@ +class AddIncludeCommitteeToSplashpage < ActiveRecord::Migration[7.0] + def change + add_column :splashpages, :include_committee, :boolean + end +end diff --git a/db/migrate/20240417212948_set_currency_for_null_ticket_purchase_currency.rb b/db/migrate/20240417212948_set_currency_for_null_ticket_purchase_currency.rb new file mode 100644 index 000000000..839d594c4 --- /dev/null +++ b/db/migrate/20240417212948_set_currency_for_null_ticket_purchase_currency.rb @@ -0,0 +1,5 @@ +class SetCurrencyForNullTicketPurchaseCurrency < ActiveRecord::Migration[7.0] + def up + # TicketPurchase.where(currency: nil).update_all(currency: 'USD') + end +end diff --git a/db/migrate/20240422200831_add_amount_paid_cents_to_ticket_purchases.rb b/db/migrate/20240422200831_add_amount_paid_cents_to_ticket_purchases.rb new file mode 100644 index 000000000..b8176309f --- /dev/null +++ b/db/migrate/20240422200831_add_amount_paid_cents_to_ticket_purchases.rb @@ -0,0 +1,12 @@ +class AddAmountPaidCentsToTicketPurchases < ActiveRecord::Migration[7.0] + def up + add_column :ticket_purchases, :amount_paid_cents, :integer, default: 0 + + TicketPurchase.reset_column_information + end + + def down + # Remove the amount_paid_cents column if you roll back this migration + remove_column :ticket_purchases, :amount_paid_cents + end +end diff --git a/db/migrate/20240422230019_add_registered_attendees_message_to_conferences.rb b/db/migrate/20240422230019_add_registered_attendees_message_to_conferences.rb new file mode 100644 index 000000000..579907263 --- /dev/null +++ b/db/migrate/20240422230019_add_registered_attendees_message_to_conferences.rb @@ -0,0 +1,5 @@ +class AddRegisteredAttendeesMessageToConferences < ActiveRecord::Migration[7.0] + def change + add_column :conferences, :registered_attendees_message, :text + end +end diff --git a/db/migrate/20240801042356_add_default_currency_to_users.rb b/db/migrate/20240801042356_add_default_currency_to_users.rb new file mode 100644 index 000000000..9684e5a48 --- /dev/null +++ b/db/migrate/20240801042356_add_default_currency_to_users.rb @@ -0,0 +1,7 @@ +class AddDefaultCurrencyToUsers < ActiveRecord::Migration[7.0] + def change + unless ActiveRecord::Base.connection.column_exists?(:users, :default_currency) + add_column :users, :default_currency, :text + end + end +end diff --git a/db/schema.rb b/db/schema.rb index c3879f928..18d82ff98 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_03_15_025823) do +ActiveRecord::Schema[7.0].define(version: 2024_08_01_042356) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -109,6 +109,7 @@ t.string "custom_domain" t.integer "booth_limit", default: 0 t.text "custom_css" + t.text "registered_attendees_message" t.index ["organization_id"], name: "index_conferences_on_organization_id" end @@ -234,6 +235,7 @@ t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil t.text "submission_template" + t.boolean "enable_public_submission", default: true, null: false end create_table "event_users", force: :cascade do |t| @@ -467,6 +469,7 @@ t.boolean "include_booths" t.boolean "shuffle_highlights", default: false, null: false t.boolean "include_happening_now" + t.boolean "include_committee" end create_table "sponsors", force: :cascade do |t| @@ -545,6 +548,7 @@ t.integer "week" t.float "amount_paid", default: 0.0 t.string "currency" + t.integer "amount_paid_cents", default: 0 end create_table "ticket_scannings", force: :cascade do |t| @@ -624,6 +628,7 @@ t.boolean "is_disabled", default: false t.string "picture" t.string "timezone" + t.string "default_currency" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true diff --git a/features/conferences/all_events.feature b/features/conferences/all_events.feature index 4df1fe39b..409c91da1 100644 --- a/features/conferences/all_events.feature +++ b/features/conferences/all_events.feature @@ -9,7 +9,7 @@ Scenario: Display events using the conference's timezone And I should see "All events are currently displayed in PDT (UTC -7)." And I should see the following data: 8:00 am - 8:30 am PDT, 8:30 am - 9:00 am PDT And I should see the following data in order: Dates, 2014-05-03, 2014-05-04, 2014-05-05, 2014-05-06, 2014-05-07, Unscheduled - And I should see the following data in order: 2014-05-03, 08:00 PDT, first_scheduled_event, 8:00 am, first_scheduled_subevent, 08:30 PDT, multiple_speaker_event + And I should see the following data in order: 2014-05-03, 08:00 AM PDT, first_scheduled_event, 8:00 am, first_scheduled_subevent, 08:30 AM PDT, multiple_speaker_event Scenario: Display events using the user's timezone Given I sign in with username "admin" and password "password123" @@ -18,4 +18,4 @@ Scenario: Display events using the user's timezone And I should see "All events are currently displayed in AEST (UTC 10)." And I should see the following data: 1:00 am - 1:30 am AEST, 1:30 am - 2:00 am AEST And I should see the following data in order: Dates, 2014-05-04, 2014-05-05, 2014-05-06, 2014-05-07, 2014-05-08, Unscheduled - And I should see the following data in order: 2014-05-04, 01:00 AEST, first_scheduled_event, 1:00 am, first_scheduled_subevent, 01:30 AEST, multiple_speaker_event + And I should see the following data in order: 2014-05-04, 01:00 AM AEST, first_scheduled_event, 1:00 am, first_scheduled_subevent, 01:30 AM AEST, multiple_speaker_event diff --git a/lib/tasks/migrate_config.rake b/lib/tasks/migrate_config.rake index 99081611d..7ee42cdc8 100644 --- a/lib/tasks/migrate_config.rake +++ b/lib/tasks/migrate_config.rake @@ -40,5 +40,18 @@ namespace :data do puts "Migrated config/config.yml to .env.#{Rails.env}" end + + task :update_ticket_purchase_currency do + TicketPurchase.find_each do |purchase| + converted_amount = CurrencyConversion.convert_currency( + purchase.conference, + purchase.ticket.price_cents, + purchase.price_currency, + purchase.currency + ) + + purchase.update_column(:amount_paid_cents, converted_amount.fractional) + end + end end end diff --git a/lib/tasks/migrate_currency.rake b/lib/tasks/migrate_currency.rake new file mode 100644 index 000000000..b05304131 --- /dev/null +++ b/lib/tasks/migrate_currency.rake @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +namespace :data do + namespace :migrate do + desc 'Update Currency of past ticket purchases' + task :update_ticket_purchase_currency do + TicketPurchase.where(currency: nil).update_all(currency: 'USD') + TicketPurchase.find_each do |purchase| + converted_amount = CurrencyConversion.convert_currency( + purchase.conference, + purchase.ticket.price_cents, + purchase.price_currency, + purchase.currency + ) + + purchase.update_column(:amount_paid_cents, converted_amount) + end + end + end +end diff --git a/spec/controllers/conference_registration_controller_spec.rb b/spec/controllers/conference_registration_controller_spec.rb index a32fe6dbd..dfe04003b 100644 --- a/spec/controllers/conference_registration_controller_spec.rb +++ b/spec/controllers/conference_registration_controller_spec.rb @@ -265,28 +265,41 @@ end end - context 'user has purchased a ticket' do + context 'user has not purchased any ticket' do before do - @ticket = create(:ticket, conference: conference) - @purchased_ticket = create(:ticket_purchase, conference: conference, - user: user, - ticket: @ticket) get :show, params: { conference_id: conference.short_title } end - it 'does not assign price of purchased tickets to total_price and purchased tickets to tickets without payment' do - expect(assigns(:total_price)).to eq 0 + it 'assigns an empty array to tickets variables' do + expect(assigns(:purchases)).to match_array [] end end - context 'user has not purchased any ticket' do + context 'when user has purchased tickets' do + let!(:purchase) { create(:ticket_purchase, user: user, conference: conference, quantity: 2, amount_paid_cents: 10_000, currency: 'USD', paid: true, ticket_id: 1) } + let!(:purchase_not_paid) { create(:ticket_purchase, user: user, conference: conference, quantity: 2, amount_paid_cents: 10_000, currency: 'USD') } + let!(:purchase_diff_id) { create(:ticket_purchase, user: user, conference: conference, quantity: 1, amount_paid_cents: 10_000, currency: 'USD', paid: true, ticket_id: 2) } + let!(:purchase_diff_curr) { create(:ticket_purchase, user: user, conference: conference, quantity: 1, amount_paid_cents: 10_000, currency: 'CAD', paid: true, ticket_id: 1) } + before do + sign_in user get :show, params: { conference_id: conference.short_title } end - it 'assigns 0 dollars to total_price and empty array to tickets variables' do - expect(assigns(:total_price)).to eq 0 - expect(assigns(:tickets)).to match_array [] + it 'assigns @purchases correctly' do + expect(assigns(:purchases)).to contain_exactly(purchase, purchase_diff_id, purchase_diff_curr) + end + + it 'assigns @total_price_per_ticket_per_currency correctly' do + expect(assigns(:total_price_per_ticket_per_currency)).to eq({ [purchase.ticket_id, 'USD'] => Money.new(20_000, 'USD'), [purchase_diff_id.ticket_id, 'USD'] => Money.new(10_000, 'USD'), [purchase_diff_curr.ticket_id, 'CAD'] => Money.new(10_000, 'CAD') }) + end + + it 'assigns @total_quantity correctly' do + expect(assigns(:total_quantity)).to eq({ [purchase.ticket_id, 'USD'] => 2, [purchase_diff_id.ticket_id, 'USD'] => 1, [purchase_diff_curr.ticket_id, 'CAD'] => 1 }) + end + + it 'assigns @total_price_per_currency correctly' do + expect(assigns(:total_price_per_currency)).to eq({ 'USD' => Money.new(30_000, 'USD'), 'CAD' => Money.new(10_000, 'CAD') }) end end end diff --git a/spec/factories/conferences.rb b/spec/factories/conferences.rb index 336082652..5c1f9377a 100644 --- a/spec/factories/conferences.rb +++ b/spec/factories/conferences.rb @@ -4,32 +4,33 @@ # # Table name: conferences # -# id :bigint not null, primary key -# booth_limit :integer default(0) -# color :string -# custom_css :text -# custom_domain :string -# description :text -# end_date :date not null -# end_hour :integer default(20) -# events_per_week :text -# guid :string not null -# logo_file_name :string -# picture :string -# registration_limit :integer default(0) -# revision :integer default(0), not null -# short_title :string not null -# start_date :date not null -# start_hour :integer default(9) -# ticket_layout :integer default("portrait") -# timezone :string not null -# title :string not null -# use_vdays :boolean default(FALSE) -# use_volunteers :boolean -# use_vpositions :boolean default(FALSE) -# created_at :datetime -# updated_at :datetime -# organization_id :integer +# id :bigint not null, primary key +# booth_limit :integer default(0) +# color :string +# custom_css :text +# custom_domain :string +# description :text +# end_date :date not null +# end_hour :integer default(20) +# events_per_week :text +# guid :string not null +# logo_file_name :string +# picture :string +# registered_attendees_message :text +# registration_limit :integer default(0) +# revision :integer default(0), not null +# short_title :string not null +# start_date :date not null +# start_hour :integer default(9) +# ticket_layout :integer default("portrait") +# timezone :string not null +# title :string not null +# use_vdays :boolean default(FALSE) +# use_volunteers :boolean +# use_vpositions :boolean default(FALSE) +# created_at :datetime +# updated_at :datetime +# organization_id :integer # # Indexes # diff --git a/spec/factories/event_types.rb b/spec/factories/event_types.rb index e3801ca14..d8e3ee758 100644 --- a/spec/factories/event_types.rb +++ b/spec/factories/event_types.rb @@ -4,17 +4,18 @@ # # Table name: event_types # -# id :bigint not null, primary key -# color :string -# description :string -# length :integer default(30) -# maximum_abstract_length :integer default(500) -# minimum_abstract_length :integer default(0) -# submission_template :text -# title :string not null -# created_at :datetime -# updated_at :datetime -# program_id :integer +# id :bigint not null, primary key +# color :string +# description :string +# enable_public_submission :boolean default(TRUE), not null +# length :integer default(30) +# maximum_abstract_length :integer default(500) +# minimum_abstract_length :integer default(0) +# submission_template :text +# title :string not null +# created_at :datetime +# updated_at :datetime +# program_id :integer # FactoryBot.define do @@ -22,6 +23,7 @@ title { 'Example Event Type' } length { 30 } description { 'Example Event Description\nThis event type is an example.' } + enable_public_submission { true } minimum_abstract_length { 0 } maximum_abstract_length { 500 } submission_template { 'Example Event Template _with_ **markdown**' } diff --git a/spec/factories/splashpages.rb b/spec/factories/splashpages.rb index 6bb3e70ff..a095cffdb 100644 --- a/spec/factories/splashpages.rb +++ b/spec/factories/splashpages.rb @@ -11,6 +11,7 @@ # banner_photo_updated_at :datetime # include_booths :boolean # include_cfp :boolean default(FALSE) +# include_committee :boolean # include_happening_now :boolean # include_lodgings :boolean # include_program :boolean diff --git a/spec/factories/ticket_purchases.rb b/spec/factories/ticket_purchases.rb index 1eb1f347d..9f55a3ca7 100644 --- a/spec/factories/ticket_purchases.rb +++ b/spec/factories/ticket_purchases.rb @@ -4,17 +4,18 @@ # # Table name: ticket_purchases # -# id :bigint not null, primary key -# amount_paid :float default(0.0) -# currency :string -# paid :boolean default(FALSE) -# quantity :integer default(1) -# week :integer -# created_at :datetime -# conference_id :integer -# payment_id :integer -# ticket_id :integer -# user_id :integer +# id :bigint not null, primary key +# amount_paid :float default(0.0) +# amount_paid_cents :integer default(0) +# currency :string +# paid :boolean default(FALSE) +# quantity :integer default(1) +# week :integer +# created_at :datetime +# conference_id :integer +# payment_id :integer +# ticket_id :integer +# user_id :integer # FactoryBot.define do factory :ticket_purchase do diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 78784fa4f..f9135376c 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -16,6 +16,7 @@ # confirmed_at :datetime # current_sign_in_at :datetime # current_sign_in_ip :string +# default_currency :string # email :string default(""), not null # email_public :boolean default(FALSE) # encrypted_password :string default(""), not null diff --git a/spec/features/conference_spec.rb b/spec/features/conference_spec.rb index 9be0860a5..eb00a0799 100644 --- a/spec/features/conference_spec.rb +++ b/spec/features/conference_spec.rb @@ -84,4 +84,38 @@ it_behaves_like 'add and update conference' end + + describe 'user views' do + let!(:conference) { create(:full_conference, description: 'Welcome to this conference!', registered_attendees_message: 'This is an exclusive message!') } + let!(:registered_user) { create(:user) } + let!(:not_registered_user) { create(:user) } + let!(:registration) { create(:registration, user: registered_user, conference: conference) } + + context 'when user is registered for conference' do + before do + sign_in registered_user + visit conference_path(conference.short_title) + end + + it 'shows registered attendees message and description' do + expect(page).to have_content('Welcome to this conference!') + expect(page).to have_content('This is an exclusive message!') + end + end + + context 'when user is not registered for conference' do + before do + sign_in not_registered_user + visit conference_path(conference) + end + + it 'shows conference description' do + expect(page).to have_content('Welcome to this conference!') + end + + it 'does not show registered attendees message' do + expect(page).to have_no_content('This is an exclusive message!') + end + end + end end diff --git a/spec/features/event_schedules_spec.rb b/spec/features/event_schedules_spec.rb new file mode 100644 index 000000000..bbb3eaf20 --- /dev/null +++ b/spec/features/event_schedules_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe EventSchedule, :js do + Timecop.return + let(:test_date) { Time.current } + let!(:conference) do + create(:full_conference, start_date: test_date - 1.hour, end_date: test_date + 5.days, start_hour: 0, end_hour: 24) + end + let!(:program) { conference.program } + let!(:selected_schedule) { create(:schedule, program: program) } + let!(:scheduled_event_early) do + program.update!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed', abstract: '`markdown`') + end + let!(:event_schedule_early) do + create(:event_schedule, event: scheduled_event_early, schedule: selected_schedule, + start_time: test_date - 1.hours) + end + let!(:scheduled_event_mid) do + program.update!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:event_schedule_mid) do + create(:event_schedule, event: scheduled_event_mid, schedule: selected_schedule, + start_time: test_date) + end + let!(:scheduled_event_late) do + program.update!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:event_schedule_late) do + create(:event_schedule, event: scheduled_event_late, schedule: selected_schedule, + start_time: test_date + 1.hours) + end + + before do + login_as(create(:user), scope: :user) + visit events_conference_schedule_path(conference_id: conference.short_title, favourites: false) + end + + it 'jumps to the closest event' do + find('#current-event-btn').click + highlighted_element = page.find('.highlighted', visible: true, wait: 1) + expect(highlighted_element[:id]).to include("event_#{scheduled_event_mid.id}") + end +end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 9e4108337..808a2827d 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -155,4 +155,22 @@ expect(conference_logo_url(conference2)).to include('2.png') end end + + describe '#event_type_sentence' do + before do + create(:event_type, title: 'Keynote', program: conference.program, enable_public_submission: false) + end + + context 'when a user is an admin' do + it 'returns a sentence with all event types' do + expect(helper.event_types_sentence(conference, true)).to eq 'Talks, Workshops, and Keynotes' + end + end + + context 'when a user is not an admin' do + it 'returns a sentence only event types that allow public submission' do + expect(helper.event_types_sentence(conference, false)).to eq 'Talks and Workshops' + end + end + end end diff --git a/spec/models/conference_spec.rb b/spec/models/conference_spec.rb index 0f81d8ae6..e96d69fac 100644 --- a/spec/models/conference_spec.rb +++ b/spec/models/conference_spec.rb @@ -4,32 +4,33 @@ # # Table name: conferences # -# id :bigint not null, primary key -# booth_limit :integer default(0) -# color :string -# custom_css :text -# custom_domain :string -# description :text -# end_date :date not null -# end_hour :integer default(20) -# events_per_week :text -# guid :string not null -# logo_file_name :string -# picture :string -# registration_limit :integer default(0) -# revision :integer default(0), not null -# short_title :string not null -# start_date :date not null -# start_hour :integer default(9) -# ticket_layout :integer default("portrait") -# timezone :string not null -# title :string not null -# use_vdays :boolean default(FALSE) -# use_volunteers :boolean -# use_vpositions :boolean default(FALSE) -# created_at :datetime -# updated_at :datetime -# organization_id :integer +# id :bigint not null, primary key +# booth_limit :integer default(0) +# color :string +# custom_css :text +# custom_domain :string +# description :text +# end_date :date not null +# end_hour :integer default(20) +# events_per_week :text +# guid :string not null +# logo_file_name :string +# picture :string +# registered_attendees_message :text +# registration_limit :integer default(0) +# revision :integer default(0), not null +# short_title :string not null +# start_date :date not null +# start_hour :integer default(9) +# ticket_layout :integer default("portrait") +# timezone :string not null +# title :string not null +# use_vdays :boolean default(FALSE) +# use_volunteers :boolean +# use_vpositions :boolean default(FALSE) +# created_at :datetime +# updated_at :datetime +# organization_id :integer # # Indexes # diff --git a/spec/models/event_type_spec.rb b/spec/models/event_type_spec.rb index 2c30203c2..840e84da8 100644 --- a/spec/models/event_type_spec.rb +++ b/spec/models/event_type_spec.rb @@ -4,17 +4,18 @@ # # Table name: event_types # -# id :bigint not null, primary key -# color :string -# description :string -# length :integer default(30) -# maximum_abstract_length :integer default(500) -# minimum_abstract_length :integer default(0) -# submission_template :text -# title :string not null -# created_at :datetime -# updated_at :datetime -# program_id :integer +# id :bigint not null, primary key +# color :string +# description :string +# enable_public_submission :boolean default(TRUE), not null +# length :integer default(30) +# maximum_abstract_length :integer default(500) +# minimum_abstract_length :integer default(0) +# submission_template :text +# title :string not null +# created_at :datetime +# updated_at :datetime +# program_id :integer # require 'spec_helper' @@ -61,5 +62,25 @@ expect(build(:event_type, program: conference.program, length: 37)).not_to be_valid end end + + describe 'title' do + it 'removes leading and trailing whitespace from title' do + event_type = EventType.new(title: ' Movie ') + event_type.valid? + expect(event_type.title).to eq('Movie') + end + end + end + + describe 'scope :available_for_public' do + it 'includes event types with enable_public_submission set to true' do + public_event_type = create(:event_type, program: conference.program, enable_public_submission: true) + expect(EventType.available_for_public).to include(public_event_type) + end + + it 'excludes event types with enable_public_submission set to false' do + non_public_event_type = create(:event_type, program: conference.program, enable_public_submission: false) + expect(EventType.available_for_public).not_to include(non_public_event_type) + end end end diff --git a/spec/models/ticket_purchase_spec.rb b/spec/models/ticket_purchase_spec.rb index 6a705336d..e5c944a26 100644 --- a/spec/models/ticket_purchase_spec.rb +++ b/spec/models/ticket_purchase_spec.rb @@ -4,17 +4,18 @@ # # Table name: ticket_purchases # -# id :bigint not null, primary key -# amount_paid :float default(0.0) -# currency :string -# paid :boolean default(FALSE) -# quantity :integer default(1) -# week :integer -# created_at :datetime -# conference_id :integer -# payment_id :integer -# ticket_id :integer -# user_id :integer +# id :bigint not null, primary key +# amount_paid :float default(0.0) +# amount_paid_cents :integer default(0) +# currency :string +# paid :boolean default(FALSE) +# quantity :integer default(1) +# week :integer +# created_at :datetime +# conference_id :integer +# payment_id :integer +# ticket_id :integer +# user_id :integer # require 'spec_helper' @@ -67,6 +68,16 @@ end end + describe 'monetization' do + let!(:purchase) { create(:ticket_purchase, amount_paid_cents: 1000, currency: 'USD') } + + it 'correctly monetizes amount_paid_cents' do + expect(purchase.purchase_price).to be_a(Money) + expect(purchase.purchase_price.currency.iso_code).to eq('USD') + expect(purchase.purchase_price.fractional).to eq(1000) + end + end + describe 'self#purchase' do let!(:participant) { create(:user) } let!(:ticket_1) { create(:ticket) } diff --git a/spec/models/ticket_spec.rb b/spec/models/ticket_spec.rb index 36d074afd..9b295a039 100644 --- a/spec/models/ticket_spec.rb +++ b/spec/models/ticket_spec.rb @@ -21,7 +21,7 @@ describe Ticket do let(:conference) { create(:conference) } - let(:ticket) { create(:ticket, price: 50, price_currency: 'USD', conference: conference) } + let(:ticket) { create(:ticket, price: 50, price_currency: 'USD', conference: conference, price_cents: 5_000) } let(:user) { create(:user) } describe 'validation' do @@ -83,13 +83,13 @@ subject { ticket.tickets_turnover_total ticket.id } let!(:purchase1) do - create(:ticket_purchase, ticket: ticket, amount_paid: 5_000, quantity: 1, paid: true, user: user) + create(:ticket_purchase, ticket: ticket, amount_paid: 50, amount_paid_cents: 5_000, quantity: 1, currency: 'USD', paid: true, user: user) end let!(:purchase2) do - create(:ticket_purchase, ticket: ticket, amount_paid: 5_000, quantity: 2, paid: true, user: user) + create(:ticket_purchase, ticket: ticket, amount_paid: 50, amount_paid_cents: 5_000, quantity: 2, currency: 'USD', paid: true, user: user) end let!(:purchase3) do - create(:ticket_purchase, ticket: ticket, amount_paid: 5_000, quantity: 10, paid: false, user: user) + create(:ticket_purchase, ticket: ticket, amount_paid: 50, amount_paid_cents: 5_000, quantity: 10, currency: 'USD', paid: false, user: user) end it 'returns turnover as Money with ticket\'s currency' do diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index 7c8df641a..a054de04b 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -61,8 +61,6 @@ withdrawn]) } - it { is_expected.to validate_inclusion_of(:cfp_active).in_array([true, false]) } - context 'when self_organized_and_accepted_or_confirmed? returns true' do before do allow(subject).to receive(:self_organized_and_accepted_or_confirmed?).and_return(true) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a999ef03a..151acca47 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -16,6 +16,7 @@ # confirmed_at :datetime # current_sign_in_at :datetime # current_sign_in_ip :string +# default_currency :string # email :string default(""), not null # email_public :boolean default(FALSE) # encrypted_password :string default(""), not null diff --git a/spec/serializers/conference_serializer_spec.rb b/spec/serializers/conference_serializer_spec.rb index dd81619ff..9931bc72f 100644 --- a/spec/serializers/conference_serializer_spec.rb +++ b/spec/serializers/conference_serializer_spec.rb @@ -4,32 +4,33 @@ # # Table name: conferences # -# id :bigint not null, primary key -# booth_limit :integer default(0) -# color :string -# custom_css :text -# custom_domain :string -# description :text -# end_date :date not null -# end_hour :integer default(20) -# events_per_week :text -# guid :string not null -# logo_file_name :string -# picture :string -# registration_limit :integer default(0) -# revision :integer default(0), not null -# short_title :string not null -# start_date :date not null -# start_hour :integer default(9) -# ticket_layout :integer default("portrait") -# timezone :string not null -# title :string not null -# use_vdays :boolean default(FALSE) -# use_volunteers :boolean -# use_vpositions :boolean default(FALSE) -# created_at :datetime -# updated_at :datetime -# organization_id :integer +# id :bigint not null, primary key +# booth_limit :integer default(0) +# color :string +# custom_css :text +# custom_domain :string +# description :text +# end_date :date not null +# end_hour :integer default(20) +# events_per_week :text +# guid :string not null +# logo_file_name :string +# picture :string +# registered_attendees_message :text +# registration_limit :integer default(0) +# revision :integer default(0), not null +# short_title :string not null +# start_date :date not null +# start_hour :integer default(9) +# ticket_layout :integer default("portrait") +# timezone :string not null +# title :string not null +# use_vdays :boolean default(FALSE) +# use_volunteers :boolean +# use_vpositions :boolean default(FALSE) +# created_at :datetime +# updated_at :datetime +# organization_id :integer # # Indexes # diff --git a/spec/services/embeddable_url_spec.rb b/spec/services/embeddable_url_spec.rb new file mode 100644 index 000000000..b88a94a37 --- /dev/null +++ b/spec/services/embeddable_url_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe EmbeddableURL do + describe '#iframe_url' do + it 'returns the original url if no transformations apply' do + url = 'https://example.com' + expect(EmbeddableURL.new(url, 'title').iframe_url).to eq url + end + + it 'transforms a Google Drive URL' do + url = EmbeddableURL.new('https://docs.google.com/presentation/d/1eGbEQtcOPW2N2P5rKfBVfSo2zn4C307Sh6C7vpJsruE/edit#slide=id.g1088c029399_0_47', 'title').iframe_url + expect(url).to include '/embed' + expect(url).not_to include('/edit') + end + + it 'transforms a Dropbox URL' do + url = EmbeddableURL.new('https://www.dropbox.com/scl/fi/49gkp6ghfnxgqex64zvzd/Guzdial-SnapCon23.pdf?rlkey=ecwvmcmfscqtwfq21l3kzqcul&dl=1', 'title').iframe_url + expect(url).to include('dl=0') + expect(url).not_to include('raw=') + end + + it 'transforms a Snap! Project URL' do + url = EmbeddableURL.new('https://snap.berkeley.edu/project?username=jedi_force&projectname=Autograder%2dlite', 'title').iframe_url + expect(url).to include('/embed') + end + end + + # it 'parses snap url' do + # url = 'https://snap.berkeley.edu/project?username=avi_shor&projectname=stamps' + # transformed_url = Commercial.generate_snap_embed(url) + # expected_url = 'https://snap.berkeley.edu/embed?projectname=stamps&username=avi_shor&showTitle=true&showAuthor=true&editButton=true&pauseButton=true' + # expect(transformed_url).to eq expected_url + # end + # TODO: Test ifram generation, snap-embedding +end