diff --git a/app/assets/javascripts/osem-schedule.js b/app/assets/javascripts/osem-schedule.js index 45df23908..c233c877d 100644 --- a/app/assets/javascripts/osem-schedule.js +++ b/app/assets/javascripts/osem-schedule.js @@ -80,6 +80,35 @@ $(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 closestEventId = null; + var smallestDiff = Infinity; + var i = 0; + + $('.event-item').each(function() { + + var eventTimeStr = $(this).data('time'); + + if (eventTimeStr) { + var eventTime = new Date(eventTimeStr); + var diff = Math.abs(eventTime - now); + + if (diff < smallestDiff) { + smallestDiff = diff; + closestEventId = $(this).attr('class').split(' ')[1]; + } + } + }); + + if (closestEventId) { + //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'); + $('.' + closestEventId).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/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/commercials_controller.rb b/app/controllers/admin/commercials_controller.rb index 073e135e4..093ea3914 100644 --- a/app/controllers/admin/commercials_controller.rb +++ b/app/controllers/admin/commercials_controller.rb @@ -55,30 +55,44 @@ def render_commercial # Received a file from user # Reads file and creates commercial for event # File content example: - # EventID:MyURL + # EventID, Title, URL def mass_upload errors = Commercial.read_file(params[:file]) if params[:file] if !params[:file] flash[:error] = 'Empty file detected while adding materials to Event' - elsif errors.all? { |_k, v| v.blank? } - flash[:notice] = 'Successfully added materials.' - else - errors_text = '' - errors_text += 'Unable to find event with ID: ' + errors[:no_event].join(', ') + '. ' if errors[:no_event].any? - if errors[:validation_errors].any? - errors_text += 'There were some errors: ' + errors[:validation_errors].join('. ') - end + elsif errors.present? + errors_text = aggregate_errors(errors) + flash[:notice] = if errors_text.length > 4096 + 'Errors are too long to be displayed. Please check the logs.' + else + errors_text + end - flash[:error] = errors_text + else + flash[:notice] = 'Successfully added materials.' end redirect_back(fallback_location: root_path) end private + # Aggregate errors and ensure that they do not exceed 4 KB in total size + def aggregate_errors(errors) + errors_text = '' + if errors[:no_event].any? + errors_text += 'Unable to find events with IDs: ' + errors[:no_event].join(', ') + '. ' + end + if errors[:validation_errors].any? + errors_text += 'Validation errors: ' + errors[:validation_errors].join('. ') + end + errors_text + end + def commercial_params - params.require(:commercial).permit(:title, :url) + params.require(:commercial).permit(:title, :url).tap do |params| + params[:url] = Commercial.generate_snap_embed(params[:url]) if params[:url] + end 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/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 ef90c670c..1e60e6466 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( @@ -65,8 +66,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| diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb index 94d8d9859..e86f9ffb7 100644 --- a/app/controllers/payments_controller.rb +++ b/app/controllers/payments_controller.rb @@ -23,10 +23,6 @@ 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 diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 48d62c817..23087362d 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -216,6 +216,6 @@ def event_params end def user_params - params.require(:user).permit(:email, :password, :password_confirmation, :username) + params.require(:user).permit(:email, :password, :password_confirmation, :username, :is_admin) end end 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/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/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 d5ac9a463..42d4ccadf 100644 --- a/app/models/commercial.rb +++ b/app/models/commercial.rb @@ -14,6 +14,8 @@ # commercial_id :string # commercialable_id :integer # +require 'csv' + class Commercial < ApplicationRecord require 'oembed' @@ -28,6 +30,7 @@ class Commercial < ApplicationRecord def self.render_from_url(url) register_provider + url = generate_snap_embed(url) begin resource = OEmbed::Providers.get(url, maxwidth: 560, maxheight: 315) { html: resource.html.html_safe } @@ -37,6 +40,34 @@ def self.render_from_url(url) end end + def self.generate_snap_embed(url) + return url unless url + + uri = URI.parse(url) + if uri.host == 'snap.berkeley.edu' && uri.path == '/project' + args = URI.decode_www_form(uri.query).to_h + else + return url + end + if args.key?('username') && args.key?('projectname') + new_query = URI.encode_www_form({ + 'projectname' => args['projectname'], + 'username' => args['username'], + 'showTitle' => 'true', + 'showAuthor' => 'true', + 'editButton' => 'true', + 'pauseButton' => 'true' + }) + new_uri = URI::HTTPS.build( + host: uri.host, + path: '/embed', + query: new_query + ) + return new_uri.to_s + end + url + end + def self.iframe_fallback(url) "".html_safe end @@ -46,17 +77,24 @@ def self.read_file(file) errors[:no_event] = [] errors[:validation_errors] = [] - file.read.each_line do |line| - # Get the event id (text before :) - id = line.match(/:/).pre_match.to_i - # Get the commercial url (text after :) - url = line.match(/:/).post_match + # Check if the file has a .csv extension + unless File.extname(file.original_filename).casecmp('.csv').zero? + errors[:validation_errors] << 'File must be a CSV.' + return errors + end + + CSV.foreach(file.path, headers: true) do |row| + # You can access columns by their names if headers are included in the file + id = row['Event_ID'].to_i + title = row['Title'] + url = row['URL'] + event = Event.find_by(id: id) - # Go to next event, if the event is not found + # Go to next event if the event is not found (errors[:no_event] << id) && next unless event - commercial = event.commercials.new(url: url) + 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 + ')') end 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 954d67af3..5eced20ec 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/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/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 46429af29..6f68faee1 100644 --- a/app/views/admin/events/index.html.haml +++ b/app/views/admin/events/index.html.haml @@ -35,12 +35,13 @@ .form-group = f.file_field 'file', as: :file %span.help-block - Upload your file with data in the following format: - %b Event_ID:Commercial_Link + Upload your file.csv with data in the following format: + %b Event_ID,Title,URL for instance: %pre - 11:https://youtube.com/myvideo - 23:https://vimeo.com/myvideo + Event_ID,Title,URL + 1,Session Recording,https://youtube.com/myvideo + 2,Demo Video,https://vimeo.com/myvideo .modal-footer = f.submit nil, class: 'btn btn-primary' .row 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/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/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/_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..c7b947e25 100644 --- a/app/views/proposals/_submission_type_content_form.haml +++ b/app/views/proposals/_submission_type_content_form.haml @@ -2,8 +2,11 @@ %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" + - if current_user&.is_admin + = f.select :event_type_id, event_type_select_options(@conference.program.event_types), { include_blank: false }, { class: 'select-help-toggle form-control' } + - else + = f.select :event_type_id, event_type_select_options(@conference.program.event_types.available_for_public), { 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/events.html.haml b/app/views/schedules/events.html.haml index 4c45a80b6..14a1669ca 100644 --- a/app/views/schedules/events.html.haml +++ b/app/views/schedules/events.html.haml @@ -52,7 +52,7 @@ - unless start_ymd.eql?(date) .col-xs-12.col-md-12 .date-content - %span.date-title{ id: start_ymd } + %span.date-title = inyourtz(event_schedule.start_time, @conference.timezone) do = date = start_ymd %a.pull-right{ title: "Go up", href: "#program" } @@ -64,7 +64,8 @@ = 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 + .event-item{ data: { time: event_schedule.start_time.iso8601 }, class: "event-#{event_schedule.event.id}" } + = render 'event', event: event_schedule.event, event_schedule: event_schedule / confirmed events that are not scheduled - if @unscheduled_events.any? @@ -79,6 +80,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/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/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/20240308190204_add_currency_to_payments.rb deleted file mode 100644 index 467a04f43..000000000 --- a/db/migrate/20240308190204_add_currency_to_payments.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddCurrencyToPayments < ActiveRecord::Migration[7.0] - def change - add_column :payments, :currency, :string - end -end diff --git a/db/migrate/20240315025823_add_currency_to_ticket_purchases.rb b/db/migrate/20240315025823_add_currency_to_ticket_purchases.rb deleted file mode 100644 index 70e4f9d96..000000000 --- a/db/migrate/20240315025823_add_currency_to_ticket_purchases.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddCurrencyToTicketPurchases < ActiveRecord::Migration[7.0] - def change - add_column :ticket_purchases, :currency, :string - end -end 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..759aa644c --- /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..349471109 --- /dev/null +++ b/db/migrate/20240422200831_add_amount_paid_cents_to_ticket_purchases.rb @@ -0,0 +1,23 @@ +class AddAmountPaidCentsToTicketPurchases < ActiveRecord::Migration[7.0] + def up + add_column :ticket_purchases, :amount_paid_cents, :integer, default: 0 + + TicketPurchase.reset_column_information + + TicketPurchase.find_each do |purchase| + converted_amount = CurrencyConversion.convert_currency( + purchase.conference, + purchase.price, + purchase.price_currency, + purchase.currency + ) + + purchase.update_column(:amount_paid_cents, converted_amount.fractional) + end + 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/schema.rb b/db/schema.rb index c3879f928..240119034 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_04_22_200831) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -234,6 +234,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 +468,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 +547,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 +627,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/package-lock.json b/package-lock.json index e5e7a779a..fedc26385 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "snapcon", + "name": "snapcon2", "lockfileVersion": 2, "requires": true, "packages": { 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/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/event_schedules_spec.rb b/spec/features/event_schedules_spec.rb new file mode 100644 index 000000000..cc01677a5 --- /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[:class]).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/commercial_spec.rb b/spec/models/commercial_spec.rb index a09077f8e..2adc17c97 100644 --- a/spec/models/commercial_spec.rb +++ b/spec/models/commercial_spec.rb @@ -29,4 +29,11 @@ commercial = build(:conference_commercial) expect(commercial.valid?).to be true 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 end 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/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